No OneTemporary

File Metadata

Created
Fri, Jun 7, 7:48 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/ConfigProjectPanel.cpp b/src/ConfigProjectPanel.cpp
index d631962e..4b8d003b 100644
--- a/src/ConfigProjectPanel.cpp
+++ b/src/ConfigProjectPanel.cpp
@@ -1,111 +1,112 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "ConfigProjectPanel.h"
#include "calligraplansettings.h"
#include <kactioncollection.h>
#include <QFileDialog>
namespace KPlato
{
ConfigProjectPanel::ConfigProjectPanel( QWidget *parent )
: ConfigProjectPanelImpl( parent )
{
}
//-----------------------------
ConfigProjectPanelImpl::ConfigProjectPanelImpl(QWidget *p )
: QWidget(p)
{
setupUi(this);
initDescription();
connect(resourceFileBrowseBtn, &QAbstractButton::clicked, this, &ConfigProjectPanelImpl::resourceFileBrowseBtnClicked);
connect(projectsPlaceBrowseBtn, &QAbstractButton::clicked, this, &ConfigProjectPanelImpl::projectsPlaceBrowseBtnClicked);
}
void ConfigProjectPanelImpl::resourceFileBrowseBtnClicked()
{
QFileDialog dialog(this, tr("Shared resources file"));
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setNameFilters(QStringList()<<tr("Plan file (*.plan)"));
if (dialog.exec()) {
kcfg_SharedResourcesFile->setText(dialog.selectedFiles().value(0));
}
}
void ConfigProjectPanelImpl::projectsPlaceBrowseBtnClicked()
{
QFileDialog dialog(this, tr("Shared projects place"));
dialog.setFileMode(QFileDialog::Directory);
if (dialog.exec()) {
kcfg_SharedProjectsPlace->setText(dialog.directory().absolutePath());
}
}
void ConfigProjectPanelImpl::initDescription()
{
toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
KActionCollection *collection = new KActionCollection( this ); //krazy:exclude=tipsandthis
kcfg_ProjectDescription->setRichTextSupport( KRichTextWidget::SupportBold |
KRichTextWidget::SupportItalic |
KRichTextWidget::SupportUnderline |
KRichTextWidget::SupportStrikeOut |
KRichTextWidget::SupportChangeListStyle |
KRichTextWidget::SupportAlignment |
KRichTextWidget::SupportFormatPainting );
collection->addActions(kcfg_ProjectDescription->createActions());
toolbar->addAction( collection->action( "format_text_bold" ) );
toolbar->addAction( collection->action( "format_text_italic" ) );
toolbar->addAction( collection->action( "format_text_underline" ) );
toolbar->addAction( collection->action( "format_text_strikeout" ) );
toolbar->addSeparator();
toolbar->addAction( collection->action( "format_list_style" ) );
toolbar->addSeparator();
toolbar->addAction( collection->action( "format_align_left" ) );
toolbar->addAction( collection->action( "format_align_center" ) );
toolbar->addAction( collection->action( "format_align_right" ) );
toolbar->addAction( collection->action( "format_align_justify" ) );
toolbar->addSeparator();
// toolbar->addAction( collection->action( "format_painter" ) );
kcfg_ProjectDescription->append( "" );
kcfg_ProjectDescription->setReadOnly( false );
kcfg_ProjectDescription->setOverwriteMode( false );
kcfg_ProjectDescription->setLineWrapMode( KTextEdit::WidgetWidth );
kcfg_ProjectDescription->setTabChangesFocus( true );
}
} //KPlato namespace
diff --git a/src/ConfigWorkVacationPanel.cpp b/src/ConfigWorkVacationPanel.cpp
index 7a16c2ff..fc784278 100644
--- a/src/ConfigWorkVacationPanel.cpp
+++ b/src/ConfigWorkVacationPanel.cpp
@@ -1,91 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "ConfigWorkVacationPanel.h"
#include "calligraplansettings.h"
#include <kactioncollection.h>
#ifdef HAVE_KHOLIDAYS
#include <KHolidays/HolidayRegion>
#endif
#include <QFileDialog>
namespace KPlato
{
ConfigWorkVacationPanel::ConfigWorkVacationPanel( QWidget *parent )
: ConfigWorkVacationPanelImpl( parent )
{
}
//-----------------------------
ConfigWorkVacationPanelImpl::ConfigWorkVacationPanelImpl(QWidget *p )
: QWidget(p)
{
setupUi(this);
kcfg_Region->hide();
#ifdef HAVE_KHOLIDAYS
int idx = 0;
const QString regionCode = kcfg_Region->text();
region->addItem(i18n("Default"), "Default");
foreach(const QString &s, KHolidays::HolidayRegion::regionCodes()) {
region->addItem(KHolidays::HolidayRegion::name(s), s);
int row = region->count() - 1;
region->setItemData(row, KHolidays::HolidayRegion::description(s), Qt::ToolTipRole);
if (s == regionCode) {
idx = row;
}
}
connect(region, SIGNAL(currentIndexChanged(int)), this, SLOT(slotRegionChanged(int)));
connect(kcfg_Region, &QLineEdit::textChanged, this, &ConfigWorkVacationPanelImpl::slotRegionCodeChanged);
region->setCurrentIndex(idx);
#else
holidaysWidget->hide();
#endif
}
#ifdef HAVE_KHOLIDAYS
void ConfigWorkVacationPanelImpl::slotRegionChanged(int idx)
{
QString r = region->itemData(idx).toString();
if (r != kcfg_Region->text()) {
kcfg_Region->setText(r);
}
}
void ConfigWorkVacationPanelImpl::slotRegionCodeChanged(const QString &code)
{
QString r = region->itemData(region->currentIndex()).toString();
if (r != code) {
for (int idx = 0; idx < region->count(); ++idx) {
if (region->itemData(idx).toString() == code) {
region->setCurrentIndex(idx);
break;
}
}
}
}
#endif
} //KPlato namespace
diff --git a/src/KPlatoXmlLoader.cpp b/src/KPlatoXmlLoader.cpp
index 9dec4677..b503ea00 100644
--- a/src/KPlatoXmlLoader.cpp
+++ b/src/KPlatoXmlLoader.cpp
@@ -1,172 +1,173 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
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 "KPlatoXmlLoader.h"
#include "kptconfig.h"
#include "kptpackage.h"
#include "kptxmlloaderobject.h"
#include "kptproject.h"
#include "KoXmlReader.h"
#include <KLocalizedString>
#include <kmessagebox.h>
extern int kplatoXmlDebugArea();
namespace KPlato
{
KPlatoXmlLoader::KPlatoXmlLoader( XMLLoaderObject &loader, Project* project )
: KPlatoXmlLoaderBase(),
m_loader( loader ),
m_project( project )
{
}
QString KPlatoXmlLoader::errorMessage() const
{
return m_message;
}
Package *KPlatoXmlLoader::package() const
{
return m_package;
}
QString KPlatoXmlLoader::timeTag() const
{
return m_timeTag;
}
bool KPlatoXmlLoader::load( const KoXmlElement& plan )
{
debugPlanXml<<"plan";
QString syntaxVersion = plan.attribute( "version" );
m_loader.setVersion( syntaxVersion );
if ( syntaxVersion.isEmpty() ) {
KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0, i18n( "This document has no syntax version.\n"
"Opening it in Plan may lose information." ),
i18n( "File-Format Error" ), KGuiItem( i18n( "Continue" ) ) );
if ( ret == KMessageBox::Cancel ) {
m_message = "USER_CANCELED";
return false;
}
// set to max version and hope for the best
m_loader.setVersion( KPLATO_MAX_FILE_SYNTAX_VERSION );
} else if ( syntaxVersion > KPLATO_MAX_FILE_SYNTAX_VERSION ) {
KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0, i18n( "This document was created with a newer version of KPlato than Plan can load.\n"
"Syntax version: %1\n"
"Opening it in this version of Plan may lose some information.", syntaxVersion ),
i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) );
if ( ret == KMessageBox::Cancel ) {
m_message = "USER_CANCELED";
return false;
}
}
m_loader.startLoad();
bool result = false;
KoXmlNode n = plan.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "project" ) {
m_loader.setProject( m_project );
result = load( m_project, e, m_loader );
if ( result ) {
if ( m_project->id().isEmpty() ) {
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId( m_project );
}
} else {
m_loader.addMsg( XMLLoaderObject::Errors, "Loading of project failed" );
errorPlanXml <<"Loading of project failed";
//TODO add some ui here
}
}
}
m_loader.stopLoad();
return result;
}
bool KPlatoXmlLoader::loadWorkpackage( const KoXmlElement& plan )
{
debugPlanXml;
bool ok = false;
if ( m_loader.workVersion() > KPLATOWORK_MAX_FILE_SYNTAX_VERSION ) {
KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0, i18n( "This document was created with a newer version of KPlatoWork (syntax version: %1)\n"
"Opening it in this version of PlanWork will lose some information.", m_loader.workVersion() ),
i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) );
if ( ret == KMessageBox::Cancel ) {
m_message = "USER_CANCELED";
return false;
}
}
m_loader.startLoad();
Project *proj = new Project();
Package *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_loader.setProject( proj );
ok = load( proj, e, m_loader );
if ( ! ok ) {
m_loader.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" );
//TODO add some ui here
}
} else if ( e.tagName() == "workpackage" ) {
m_timeTag = e.attribute( "time-tag" );
package->ownerId = e.attribute( "owner-id" );
package->ownerName = e.attribute( "owner" );
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 ) {
WorkPackage &wp = static_cast<Task*>( proj->childNode( 0 ) )->workPackage();
if ( wp.ownerId().isEmpty() ) {
wp.setOwnerId( package->ownerId );
wp.setOwnerName( package->ownerName );
}
}
return ok;
}
} // namespace KPlato
diff --git a/src/KPtViewAdaptor.cpp b/src/KPtViewAdaptor.cpp
index 17ec8416..c97a41ef 100644
--- a/src/KPtViewAdaptor.cpp
+++ b/src/KPtViewAdaptor.cpp
@@ -1,91 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 2006 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
(C) 2004 Laurent Montel <montel@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA
*/
+// clazy:excludeall=qstring-arg
#include "KPtViewAdaptor.h"
#include "kptview.h"
namespace KPlato
{
/************************************************
*
* ViewAdaptor
*
************************************************/
ViewAdaptor::ViewAdaptor( View* t )
: KoViewAdaptor( t )
{
// setAutoRelaySignals(true);
m_view = t;
}
ViewAdaptor::~ViewAdaptor()
{
}
// void ViewAdaptor::slotEditResource()
// {
// m_view->slotEditResource();
// }
//
// void ViewAdaptor::slotEditCut()
// {
// m_view->slotEditCut();
// }
//
// void ViewAdaptor::slotEditCopy()
// {
// m_view->slotEditCopy();
// }
//
// void ViewAdaptor::slotEditPaste()
// {
// m_view->slotEditPaste();
// }
//
// void ViewAdaptor::slotAddTask()
// {
// m_view->slotAddTask();
// }
//
// void ViewAdaptor::slotAddSubTask()
// {
// m_view->slotAddSubTask();
// }
//
// void ViewAdaptor::slotAddMilestone()
// {
// m_view->slotAddMilestone();
// }
//
// void ViewAdaptor::slotProjectEdit()
// {
// m_view->slotProjectEdit();
// }
//
// void ViewAdaptor::slotConfigure()
// {
// m_view->slotConfigure();
// }
} //KPlato namespace
diff --git a/src/about/aboutpage.cpp b/src/about/aboutpage.cpp
index 6207764b..6227f884 100644
--- a/src/about/aboutpage.cpp
+++ b/src/about/aboutpage.cpp
@@ -1,322 +1,323 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011 Dag Andersen <danders@get2net.dk>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "aboutpage.h"
#include "kptproject.h"
#include <KoIcon.h>
#include <QTextCodec>
#include <QApplication>
#include <QDir>
#include <QString>
#include <QUrl>
#include <QStandardPaths>
#include <kiconloader.h>
#include <KFormat>
#include <KLocalizedString>
#include <khtml_part.h>
KPlatoAboutPage::KPlatoAboutPage()
: m_project( 0 )
{
}
KPlatoAboutPage::~KPlatoAboutPage()
{
}
QString KPlatoAboutPage::main()
{
KIconLoader *iconloader = KIconLoader::global();
QString res = loadFile( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligraplan/about/main.html" ));
if ( res.isEmpty() ) {
return res;
}
const char *const up_icon_id = koIconNameCStr("go-up");
const QString up_icon_path = iconloader->iconPath(up_icon_id, KIconLoader::Small);
const char *const continue_icon_id =
(QApplication::isRightToLeft() ? koIconNameCStr("go-previous") : koIconNameCStr("go-next"));
const QString continue_icon_path = iconloader->iconPath(continue_icon_id, KIconLoader::Small);
QString icon_up = "<img width='16' height='16' src=\"" + up_icon_path + "\">";
QString icon_path = "<img width='16' height='16' src=\"" + continue_icon_path + "\">";
res = res.arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage.css" ) );
if ( qApp->layoutDirection() == Qt::RightToLeft )
res = res.arg( "@import \"%1\";" ).arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage_rtl.css" ) );
else
res = res.arg( "" );
res = res.arg( i18n( "Plan" ) )
.arg( i18nc("KDE 4 tag line, see http://kde.org/img/kde40.png", "Be free.") )
.arg( i18n("Plan is a Project Planning and Management application.") )
.arg( i18n("Welcome to Plan.") )
.arg( i18n("These introductory pages should give you an idea of how to use Plan and what you can use it for.") )
.arg( icon_path ).arg( i18n( "A short introduction." ) )
.arg( icon_path ).arg( i18n( "Tips on how to manipulate and inspect data." ) )
.arg( icon_path ).arg( i18n( "A small tutorial to get you started." ) )
.arg( icon_up ).arg( i18n( "Close" ) )
.arg( i18n(
"<em>Note:</em> To view these pages when you are in other parts of Plan, choose the menu option <em>Help -> Introduction to Plan</em>."
) )
;
return res;
}
QString KPlatoAboutPage::intro()
{
KIconLoader *iconloader = KIconLoader::global();
QString res = loadFile( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligraplan/about/intro.html" ));
if ( res.isEmpty() ) {
return res;
}
const char *const up_icon_id = koIconNameCStr("go-up");
const QString up_icon_path = iconloader->iconPath(up_icon_id, KIconLoader::Small);
QString icon_up = "<img width='16' height='16' src=\"" + up_icon_path + "\">";
const char *const prev_icon_id = koIconNameCStr("go-previous");
const QString prev_icon_path = iconloader->iconPath(prev_icon_id, KIconLoader::Small);
const char *const next_icon_id = koIconNameCStr("go-next");
const QString next_icon_path = iconloader->iconPath(next_icon_id, KIconLoader::Small);
const QString continue_icon_path = QApplication::isRightToLeft() ? prev_icon_path : next_icon_path;
const QString back_icon_path = QApplication::isRightToLeft() ? next_icon_path : prev_icon_path;
res = res.arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage.css" ) );
if ( qApp->layoutDirection() == Qt::RightToLeft )
res = res.arg( "@import \"%1\";" ).arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage_rtl.css" ) );
else
res = res.arg( "" );
res = res.arg( i18n("Plan is a Project Planning and Management application.") )
.arg( i18n(
"Plan is intended for managing moderately large projects with multiple resources. To enable you to model your project adequately, Plan offers different types of task dependencies and timing constraints. Usually you will define your tasks, estimate the effort needed to perform each task, allocate resources and then schedule the project according to the dependency network and resource availability."
"<p>You can find more information online at <a href=\"https://userbase.kde.org/Plan\">https://userbase.kde.org/Plan</a></p>"
) )
.arg( icon_up )
.arg( i18n( "Close" ) )
.arg( "<img width='16' height='16' src=\"%1\">" ).arg( continue_icon_path )
.arg( i18n( "Next: Tips" ) )
;
return res;
}
QString KPlatoAboutPage::tips()
{
QString res = loadFile( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligraplan/about/tips.html" ));
if ( res.isEmpty() ) {
return res;
}
KIconLoader *iconloader = KIconLoader::global();
QString viewmag_icon_path =
iconloader->iconPath(koIconNameCStr("zoom-in"), KIconLoader::Small);
QString history_icon_path =
iconloader->iconPath(koIconNameCStr("view-history"), KIconLoader::Small);
QString openterm_icon_path =
iconloader->iconPath(koIconNameCStr("utilities-terminal"), KIconLoader::Small);
QString locationbar_erase_rtl_icon_path =
iconloader->iconPath(koIconNameCStr("edit-clear-locationbar-rtl"), KIconLoader::Small);
QString locationbar_erase_icon_path =
iconloader->iconPath(koIconNameCStr("edit-clear-locationbar-ltr"), KIconLoader::Small);
QString window_fullscreen_icon_path =
iconloader->iconPath(koIconNameCStr("view-fullscreen"), KIconLoader::Small);
QString view_left_right_icon_path =
iconloader->iconPath(koIconNameCStr("view-split-left-right"), KIconLoader::Small);
const char *const up_icon_id = koIconNameCStr("go-up");
const QString up_icon_path = iconloader->iconPath(up_icon_id, KIconLoader::Small);
QString icon_up = "<img width='16' height='16' src=\"" + up_icon_path + "\">";
const char *const prev_icon_id = koIconNameCStr("go-previous");
const QString prev_icon_path = iconloader->iconPath(prev_icon_id, KIconLoader::Small);
const char *const next_icon_id = koIconNameCStr("go-next");
const QString next_icon_path = iconloader->iconPath(next_icon_id, KIconLoader::Small);
const QString continue_icon_path = QApplication::isRightToLeft() ? prev_icon_path : next_icon_path;
const QString back_icon_path = QApplication::isRightToLeft() ? next_icon_path : prev_icon_path;
res = res.arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage.css" ) );
if ( qApp->layoutDirection() == Qt::RightToLeft )
res = res.arg( "@import \"%1\";" ).arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage_rtl.css" ) );
else
res = res.arg( "" );
res = res.arg( i18n("Editing tips.") )
.arg( i18n(
"<br/><b>To</b> edit project data, different views and editors can be selected in the View Selector docker."
"<br/><b>The</b> views are generally used to inspect data after the project has been scheduled. No data will appear in the views if the project has not been scheduled. Scheduling is done in the Schedules editor."
"<br/><b>You</b> can edit attributes in the various editors by selecting the item you want to edit (doubleclick or press F2), or open a dialog using the context menu."
"</ul>"
) )
.arg( icon_up )
.arg( i18n( "Close" ) )
.arg( "<img width='16' height='16' src=\"%1\">" ).arg( continue_icon_path )
.arg( i18n( "Next: Create a simple project" ) )
;
return res;
}
QString KPlatoAboutPage::tutorial( const QString &header, const QString &text, const QString &nextpage, const QString &nexttext)
{
QString res = loadFile( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligraplan/about/tutorial.html" ));
if ( res.isEmpty() ) {
return res;
}
KIconLoader *iconloader = KIconLoader::global();
const char *const up_icon_id = koIconNameCStr("go-up");
const QString up_icon_path = iconloader->iconPath(up_icon_id, KIconLoader::Small);
QString icon_up = "<img width='16' height='16' src=\"" + up_icon_path + "\">";
const char *const prev_icon_id = koIconNameCStr("go-previous");
const QString prev_icon_path = iconloader->iconPath(prev_icon_id, KIconLoader::Small);
const char *const next_icon_id = koIconNameCStr("go-next");
const QString next_icon_path = iconloader->iconPath(next_icon_id, KIconLoader::Small);
const QString continue_icon_path = QApplication::isRightToLeft() ? prev_icon_path : next_icon_path;
const QString back_icon_path = QApplication::isRightToLeft() ? next_icon_path : prev_icon_path;
res = res.arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage.css" ) );
if ( qApp->layoutDirection() == Qt::RightToLeft )
res = res.arg( "@import \"%1\";" ).arg( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/infopage/kde_infopage_rtl.css" ) );
else
res = res.arg( "" );
res = res.arg( header )
.arg( text )
.arg( "about:close" )
.arg( icon_up )
.arg( i18n("Close") )
.arg( "about:plan/" + nextpage )
.arg( "<img width='16' height='16' src=\"%1\">" ).arg( continue_icon_path )
.arg( nexttext )
;
return res;
}
QString KPlatoAboutPage::tutorial1()
{
return tutorial(
i18n("Create the simplest project ever."),
i18nc( "1=datetime",
"Select the task editor <em>Editors->Tasks</em>:"
"<ul>"
"<li>Create a task by selecting <em>Add Task</em> in the toolbar.</li>"
"<li>Set <em>Type</em> to <em>Duration</em>.</li>"
"<li>Set <em>Estimate</em> to <em>8 hours</em>.</li>"
"<li>Set <em>Constraint</em> to <em>As Soon As Possible</em>.</li>"
"</ul>"
"Select the schedules editor <em>Editors->Schedules</em>:"
"<ul>"
"<li>Create a schedule by selecting <em>Add Schedule</em> in the toolbar.</li>"
"<li>Calculate the schedule by selecting <em>Calculate</em> in the toolbar.</li>"
"</ul>"
"The task should now have been scheduled to start %1 with a duration of 8 hours. You can check this by selecting the Gantt chart <em>Views->Gantt</em>."
, KFormat().formatRelativeDateTime(m_project->startTime(), QLocale::LongFormat) ),
"tutorial2",
i18n( "Next: Resource allocation" )
);
}
QString KPlatoAboutPage::tutorial2()
{
DateTime dt = m_project->constraintStartTime();
if ( m_project->defaultCalendar() ) {
dt = m_project->defaultCalendar()->firstAvailableAfter( dt, m_project->constraintEndTime() );
}
return tutorial(
i18n("Allocate a resource to the task."),
i18nc( "1=datetime",
"Select the task editor <em>Editors->Tasks</em>:"
"<ul>"
"<li>Enter a name (e.g. 'John') in the <em>Allocation</em> column."
" (Plan will automatically create a resource with name 'John' under resource group 'Resources'.</li>"
"<li>Set <em>Type</em> to <em>Effort</em>.</li>"
"</ul>"
"Now you need to schedule the project again with the new allocation:"
"<br/>Select the schedules editor <em>Editors->Schedules</em> and calculate the schedule by selecting <em>Calculate</em> in the toolbar."
"<p>The task should be scheduled to start %1 with a duration of 8 hours. You can check this by selecting the Gantt chart <em>Views->Gantt</em>.<p>"
, KFormat().formatRelativeDateTime(dt, QLocale::LongFormat) ),
"main",
i18n( "Next: Introduction" )
);
}
void KPlatoAboutPage::generatePage( KHTMLPart &part, const QUrl &url)
{
QString html;
if (url.url() == "about:plan/main")
html = main();
else if (url.url() == "about:plan/intro")
html = intro();
else if (url.url() == "about:plan/tips")
html = tips();
else if (url.url() == "about:plan/tutorial")
html = tutorial1();
else if (url.url() == "about:plan/tutorial1")
html = tutorial1();
else if (url.url() == "about:plan/tutorial2")
html = tutorial2();
else
html = main();
part.begin();
part.write( html );
part.end();
}
QString KPlatoAboutPage::loadFile( const QString& file )
{
QString res;
if ( file.isEmpty() )
return res;
QFile f( file );
if ( !f.open( QIODevice::ReadOnly ) )
return res;
QTextStream t( &f );
res = t.readAll();
// otherwise all embedded objects are referenced as about:/...
QString basehref = QLatin1String("<BASE HREF=\"file:") +
file.left( file.lastIndexOf( '/' )) +
QLatin1String("/\">\n");
res.replace("<head>", "<head>\n\t" + basehref, Qt::CaseInsensitive);
return res;
}
diff --git a/src/kptbuiltinschedulerplugin.cpp b/src/kptbuiltinschedulerplugin.cpp
index a0f669a2..e0cad8cf 100644
--- a/src/kptbuiltinschedulerplugin.cpp
+++ b/src/kptbuiltinschedulerplugin.cpp
@@ -1,177 +1,178 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "kptbuiltinschedulerplugin.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include <KLocalizedString>
namespace KPlato
{
BuiltinSchedulerPlugin::BuiltinSchedulerPlugin(QObject *parent)
: SchedulerPlugin(parent)
{
setName( i18nc( "Network = task dependency network", "Network Scheduler" ) );
setComment( xi18nc( "@info:tooltip", "Built-in network (PERT) based scheduler" ) );
}
BuiltinSchedulerPlugin::~BuiltinSchedulerPlugin()
{
}
QString BuiltinSchedulerPlugin::description() const
{
return xi18nc( "@info:whatsthis", "<title>Network (PERT) Scheduler</title>"
"<para>The network scheduler generally schedules tasks according to their dependencies."
" When a task is scheduled it is scheduled in full, booking the allocated resources if available."
" If overbooking is not allowed, subsequent tasks that requests the same resource"
" will be scheduled later in time.</para>"
"<para>Tasks with time constraints will be scheduled first to minimize the problem"
" with resource conflicts</para>"
"<para><note>This scheduler does not handle resource conflicts well."
"<nl/>You can try a different scheduler if available."
" You may also change resource allocations or add dummy dependencies to avoid the conflicts.</note></para>"
);
}
void BuiltinSchedulerPlugin::calculate( Project &project, ScheduleManager *sm, bool nothread )
{
KPlatoScheduler *job = new KPlatoScheduler( &project, sm );
m_jobs << job;
connect(job, &SchedulerThread::jobStarted, this, &BuiltinSchedulerPlugin::slotStarted);
connect(job, &SchedulerThread::jobFinished, this, &BuiltinSchedulerPlugin::slotFinished);
// connect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), &project, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
// connect(this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), &project, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)));
sm->setScheduling( true );
if ( nothread ) {
connect(job, &SchedulerThread::maxProgressChanged, sm, &ScheduleManager::setMaxProgress);
connect(job, &SchedulerThread::progressChanged, sm, &ScheduleManager::setProgress);
job->doRun();
} else {
job->start();
}
m_synctimer.start();
}
void BuiltinSchedulerPlugin::slotStarted( SchedulerThread *job )
{
qDebug()<<"BuiltinSchedulerPlugin::slotStarted:"<<job->mainProject()<<job->mainManager();
emit sigCalculationStarted( job->mainProject(), job->mainManager() );
}
void BuiltinSchedulerPlugin::slotFinished( SchedulerThread *job )
{
ScheduleManager *sm = job->mainManager();
Project *mp = job->mainProject();
qDebug()<<"BuiltinSchedulerPlugin::slotFinished:"<<mp<<sm<<job->isStopped();
if ( job->isStopped() ) {
sm->setCalculationResult( ScheduleManager::CalculationCanceled );
} else {
updateLog( job );
Project *tp = static_cast<KPlatoScheduler*>( job )->project();
ScheduleManager *tm = static_cast<KPlatoScheduler*>( job )->manager();
updateProject( tp, tm, mp, sm );
sm->setCalculationResult( ScheduleManager::CalculationDone );
}
sm->setScheduling( false );
m_jobs.removeAt( m_jobs.indexOf( job ) );
if ( m_jobs.isEmpty() ) {
m_synctimer.stop();
}
emit sigCalculationFinished( mp, sm );
disconnect(this, &BuiltinSchedulerPlugin::sigCalculationStarted, mp, &Project::sigCalculationStarted);
disconnect(this, &BuiltinSchedulerPlugin::sigCalculationFinished, mp, &Project::sigCalculationFinished);
job->deleteLater();
qDebug()<<"BuiltinSchedulerPlugin::slotFinished: <<<";
}
//--------------------
KPlatoScheduler::KPlatoScheduler( Project *project, ScheduleManager *sm, QObject *parent )
: SchedulerThread( project, sm, parent)
{
qDebug()<<"KPlatoScheduler::KPlatoScheduler:"<<m_mainmanager<<m_mainmanager->name()<<m_mainmanagerId;
}
KPlatoScheduler::~KPlatoScheduler()
{
qDebug()<<"KPlatoScheduler::~KPlatoScheduler:"<<QThread::currentThreadId();
}
void KPlatoScheduler::stopScheduling()
{
m_stopScheduling = true;
if ( m_project ) {
m_project->stopcalculation = true;
}
}
void KPlatoScheduler::run()
{
if ( m_haltScheduling ) {
deleteLater();
return;
}
if ( m_stopScheduling ) {
return;
}
{ // mutex -->
m_projectMutex.lock();
m_managerMutex.lock();
m_project = new Project();
loadProject( m_project, m_pdoc );
m_project->setName( "Schedule: " + m_project->name() ); //Debug
m_manager = m_project->scheduleManager( m_mainmanagerId );
Q_ASSERT( m_manager );
Q_ASSERT( m_manager->expected() );
Q_ASSERT( m_manager != m_mainmanager );
Q_ASSERT( m_manager->scheduleId() == m_mainmanager->scheduleId() );
Q_ASSERT( m_manager->expected() != m_mainmanager->expected() );
m_manager->setName( "Schedule: " + m_manager->name() ); //Debug
m_managerMutex.unlock();
m_projectMutex.unlock();
} // <--- mutex
connect(m_project, SIGNAL(maxProgress(int)), this, SLOT(setMaxProgress(int)));
connect(m_project, SIGNAL(sigProgress(int)), this, SLOT(setProgress(int)));
bool x = connect(m_manager, SIGNAL(sigLogAdded(KPlato::Schedule::Log)), this, SLOT(slotAddLog(KPlato::Schedule::Log)));
Q_ASSERT( x ); Q_UNUSED( x );
m_project->calculate( *m_manager );
if ( m_haltScheduling ) {
deleteLater();
}
}
} //namespace KPlato
diff --git a/src/kptcolorsconfigpanel.cpp b/src/kptcolorsconfigpanel.cpp
index 93f2a6e5..cdacc80d 100644
--- a/src/kptcolorsconfigpanel.cpp
+++ b/src/kptcolorsconfigpanel.cpp
@@ -1,36 +1,37 @@
/* This file is part of the KDE project
Copyright (C) 2011 Dag Andersen <danders@get2net.dk>
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 "kptcolorsconfigpanel.h"
#include "calligraplansettings.h"
namespace KPlato
{
ColorsConfigPanel::ColorsConfigPanel(QWidget *p )
: QWidget(p)
{
setupUi(this);
kcfg_ColorGradientType->addItem( i18n( "Linear" ) );
kcfg_ColorGradientType->addItem( i18n( "Flat" ) );
}
} //KPlato namespace
diff --git a/src/kptconfig.cpp b/src/kptconfig.cpp
index bba8f9f6..a6264a72 100644
--- a/src/kptconfig.cpp
+++ b/src/kptconfig.cpp
@@ -1,328 +1,329 @@
/* This file is part of the KDE project
Copyright (C) 2004, 2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptconfig.h"
#include "calligraplansettings.h"
#include "kptconfigskeleton.h"
#include "kptfactory.h"
#include "kptproject.h"
#include "kptdebug.h"
#include <QBrush>
namespace KPlato
{
Config::Config()
: ConfigBase()
{
debugPlan<<"Leader:"<<KPlatoSettings::leader();
}
Config::~Config()
{
}
void Config::readConfig()
{
}
void Config::saveSettings()
{
if ( ! m_readWrite ) {
return;
}
KPlatoSettings::self()->save();
}
bool Config::isWorkingday(int day) const
{
switch (day) {
case Qt::Monday: return KPlatoSettings::monday(); break;
case Qt::Tuesday: return KPlatoSettings::tuesday(); break;
case Qt::Wednesday: return KPlatoSettings::wednesday(); break;
case Qt::Thursday: return KPlatoSettings::thursday(); break;
case Qt::Friday: return KPlatoSettings::friday(); break;
case Qt::Saturday: return KPlatoSettings::saturday(); break;
case Qt::Sunday: return KPlatoSettings::sunday(); break;
default: break;
};
return false;
}
QTime Config::dayStartTime(int day) const
{
switch (day) {
case Qt::Monday: return QTime::fromString(KPlatoSettings::mondayStart()); break;
case Qt::Tuesday: return QTime::fromString(KPlatoSettings::tuesdayStart()); break;
case Qt::Wednesday: return QTime::fromString(KPlatoSettings::wednesdayStart()); break;
case Qt::Thursday: return QTime::fromString(KPlatoSettings::thursdayStart()); break;
case Qt::Friday: return QTime::fromString(KPlatoSettings::fridayStart()); break;
case Qt::Saturday: return QTime::fromString(KPlatoSettings::saturdayStart()); break;
case Qt::Sunday: return QTime::fromString(KPlatoSettings::sundayStart()); break;
default: break;
};
return QTime();
}
int Config::dayLength(int day) const
{
QTime start = dayStartTime(day);
QTime end;
int value = 0;
switch (day) {
case Qt::Monday:
end = QTime::fromString(KPlatoSettings::mondayEnd());
break;
case Qt::Tuesday:
end = QTime::fromString(KPlatoSettings::tuesdayEnd());
break;
case Qt::Wednesday:
end = QTime::fromString(KPlatoSettings::wednesdayEnd());
break;
case Qt::Thursday:
end = QTime::fromString(KPlatoSettings::thursdayEnd());
break;
case Qt::Friday:
end = QTime::fromString(KPlatoSettings::fridayEnd());
break;
case Qt::Saturday:
end = QTime::fromString(KPlatoSettings::saturdayEnd());
break;
case Qt::Sunday:
end = QTime::fromString(KPlatoSettings::sundayEnd());
break;
default: break;
};
value = start.msecsTo(end);
if (value < 0) {
value = (24*60*60*1000) + value;
} else if (value == 0 && start == QTime(0, 0)) {
value = 24*60*60*1000;
}
return value;
}
void Config::setDefaultValues( Project &project )
{
project.setLeader( KPlatoSettings::manager() );
project.setUseSharedResources( KPlatoSettings::useSharedResources() );
project.setSharedResourcesFile( KPlatoSettings::sharedResourcesFile() );
project.setSharedProjectsUrl( QUrl(KPlatoSettings::sharedProjectsPlace()) );
project.setDescription( KPlatoSettings::projectDescription() );
StandardWorktime *v = project.standardWorktime();
Q_ASSERT(v);
if (v) {
v->setYear(KPlatoSettings::hoursPrYear());
v->setMonth(KPlatoSettings::hoursPrMonth());
v->setWeek(KPlatoSettings::hoursPrWeek());
v->setDay(KPlatoSettings::hoursPrDay());
}
}
void Config::setDefaultValues( Task &task )
{
task.setLeader( KPlatoSettings::leader() );
task.setDescription( KPlatoSettings::description() );
task.setConstraint( (Node::ConstraintType) KPlatoSettings::constraintType() );
// avoid problems with start <= end & end >= start
task.setConstraintStartTime( DateTime() );
task.setConstraintEndTime( DateTime() );
switch ( KPlatoSettings::startTimeUsage() ) {
case KPlatoSettings::EnumStartTimeUsage::CurrentdateTime:
task.setConstraintStartTime( DateTime( QDateTime::currentDateTime() ) );
break;
case KPlatoSettings::EnumStartTimeUsage::CurrentDate:
task.setConstraintStartTime( DateTime( QDate::currentDate(), KPlatoSettings::constraintStartTime().time() ) );
break;
case KPlatoSettings::EnumStartTimeUsage::SpecifiedDateTime: //fall through
default:
task.setConstraintStartTime( DateTime( KPlatoSettings::constraintStartTime() ) );
break;
}
switch ( KPlatoSettings::endTimeUsage() ) {
case KPlatoSettings::EnumEndTimeUsage::CurrentdateTime:
task.setConstraintEndTime( DateTime( QDateTime::currentDateTime() ) );
break;
case KPlatoSettings::EnumEndTimeUsage::CurrentDate:
task.setConstraintEndTime( DateTime( QDate::currentDate(), KPlatoSettings::constraintEndTime().time() ) );
break;
case KPlatoSettings::EnumEndTimeUsage::SpecifiedDateTime: //fall through
default:
task.setConstraintEndTime( DateTime( KPlatoSettings::constraintEndTime() ) );
break;
}
task.estimate()->setType( (Estimate::Type) KPlatoSettings::estimateType() );
task.estimate()->setUnit( (Duration::Unit) KPlatoSettings::unit() );
task.estimate()->setExpectedEstimate( KPlatoSettings::expectedEstimate() );
task.estimate()->setPessimisticRatio( KPlatoSettings::pessimisticRatio() );
task.estimate()->setOptimisticRatio( KPlatoSettings::optimisticRatio() );
}
int Config::minimumDurationUnit() const
{
return KPlatoSettings::minimumDurationUnit();
}
int Config::maximumDurationUnit() const
{
return KPlatoSettings::maximumDurationUnit();
}
bool Config::checkForWorkPackages() const
{
return KPlatoSettings::checkForWorkPackages();
}
QUrl Config::retrieveUrl() const
{
return KPlatoSettings::retrieveUrl();
}
QBrush Config::summaryTaskDefaultColor() const
{
QColor c = KPlatoSettings::summaryTaskDefaultColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
bool Config::summaryTaskLevelColorsEnabled() const
{
return KPlatoSettings::summaryTaskLevelColorsEnabled();
}
QBrush Config::summaryTaskLevelColor_1() const
{
QColor c = KPlatoSettings::summaryTaskLevelColor_1();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::summaryTaskLevelColor_2() const
{
QColor c = KPlatoSettings::summaryTaskLevelColor_2();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::summaryTaskLevelColor_3() const
{
QColor c = KPlatoSettings::summaryTaskLevelColor_3();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::summaryTaskLevelColor_4() const
{
QColor c = KPlatoSettings::summaryTaskLevelColor_4();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::taskNormalColor() const
{
QColor c = KPlatoSettings::taskNormalColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::taskErrorColor() const
{
QColor c = KPlatoSettings::taskErrorColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::taskCriticalColor() const
{
QColor c = KPlatoSettings::taskCriticalColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::taskFinishedColor() const
{
QColor c = KPlatoSettings::taskFinishedColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::milestoneNormalColor() const
{
QColor c = KPlatoSettings::milestoneNormalColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::milestoneErrorColor() const
{
QColor c = KPlatoSettings::milestoneErrorColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::milestoneCriticalColor() const
{
QColor c = KPlatoSettings::milestoneCriticalColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
QBrush Config::milestoneFinishedColor() const
{
QColor c = KPlatoSettings::milestoneFinishedColor();
if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) {
return gradientBrush( c );
}
return c;
}
} //KPlato namespace
diff --git a/src/kptconfigbehaviorpanel.cpp b/src/kptconfigbehaviorpanel.cpp
index 77593ae3..68131638 100644
--- a/src/kptconfigbehaviorpanel.cpp
+++ b/src/kptconfigbehaviorpanel.cpp
@@ -1,54 +1,55 @@
/* This file is part of the KDE project
Copyright (C) 2004, 2007 Dag Andersen <danders@get2net.dk>
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 "kptconfigbehaviorpanel.h"
#include "kptdatetime.h"
#include "kptfactory.h"
namespace KPlato
{
ConfigBehaviorPanel::ConfigBehaviorPanel(Behavior &behavior, QWidget *p, const char *n)
: ConfigBehaviorPanelBase(p, n),
m_oldvalues(behavior),
m_behavior(behavior)
{
setStartValues();
allowOverbooking->setEnabled(false); // not yet used
}
void ConfigBehaviorPanel::setStartValues() {
calculationGroup->setButton(m_oldvalues.calculationMode);
allowOverbooking->setChecked(m_oldvalues.allowOverbooking);
}
bool ConfigBehaviorPanel::ok() {
return true;
}
bool ConfigBehaviorPanel::apply() {
m_behavior.calculationMode = calculationGroup->selectedId();
m_behavior.allowOverbooking = allowOverbooking->isChecked();
return true;
}
} //KPlato namespace
diff --git a/src/kptconfigskeleton.cpp b/src/kptconfigskeleton.cpp
index 752b9fdb..1c124505 100644
--- a/src/kptconfigskeleton.cpp
+++ b/src/kptconfigskeleton.cpp
@@ -1,32 +1,33 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "kptconfigskeleton.h"
#include "kptfactory.h"
#include <KoComponentData.h>
KPlatoConfigSkeleton::KPlatoConfigSkeleton()
: KConfigSkeleton( KPlato::Factory::global().config() )
{}
KPlatoConfigSkeleton::~KPlatoConfigSkeleton()
{}
diff --git a/src/kptcontext.cpp b/src/kptcontext.cpp
index 36f46d61..87bb2e5b 100644
--- a/src/kptcontext.cpp
+++ b/src/kptcontext.cpp
@@ -1,132 +1,133 @@
/* This file is part of the KDE project
Copyright (C) 2005, 2007, 2011 Dag Andersen <danders@get2net.dk>
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 "kptcontext.h"
#include "kptview.h"
#include "kptdebug.h"
#include <QDomDocument>
namespace KPlato
{
Context::Context()
: currentEstimateType(0),
currentSchedule(0),
m_contextLoaded( false )
{
ganttview.ganttviewsize = -1;
ganttview.taskviewsize = -1;
accountsview.accountsviewsize = -1;
accountsview.periodviewsize = -1;
}
Context::~Context() {
}
const KoXmlElement &Context::context() const
{
return m_context;
}
bool Context::setContent( const QString &str )
{
KoXmlDocument doc;
if ( doc.setContent( str ) ) {
return load( doc );
}
return false;
}
bool Context::load( const KoXmlDocument &document ) {
m_document = document; // create a copy, document is deleted under our feet
// Check if this is the right app
KoXmlElement elm = m_document.documentElement();
QString value = elm.attribute( "mime", QString() );
if ( value.isEmpty() ) {
errorPlan << "No mime type specified!";
// setErrorMessage( i18n( "Invalid document. No mimetype specified." ) );
return false;
} else if ( value != "application/x-vnd.kde.plan" ) {
if ( value == "application/x-vnd.kde.kplato" ) {
// accept, since we forgot to change kplato to plan for so long...
} else {
errorPlan << "Unknown mime type " << value;
// setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.kplato, got %1", value ) );
return false;
}
}
/* QString m_syntaxVersion = elm.attribute( "version", "0.0" );
if ( m_syntaxVersion > "0.0" ) {
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.", m_syntaxVersion ),
i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) );
if ( ret == KMessageBox::Cancel ) {
setErrorMessage( "USER_CANCELED" );
return false;
}
}
*/
/*
#ifdef KOXML_USE_QDOM
int numNodes = elm.childNodes().count();
#else
int numNodes = elm.childNodesCount();
#endif
*/
KoXmlNode n = elm.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement element = n.toElement();
if ( element.tagName() == "context" ) {
m_context = element;
m_contextLoaded = true;
}
}
return true;
}
QDomDocument Context::save( const View *view ) const {
QDomDocument document( "plan.context" );
document.appendChild( document.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "context" );
doc.setAttribute( "editor", "Plan" );
doc.setAttribute( "mime", "application/x-vnd.kde.plan" );
doc.setAttribute( "version", QString::number(0.0) );
document.appendChild( doc );
QDomElement e = doc.ownerDocument().createElement("context");
doc.appendChild( e );
view->saveContext( e );
return document;
}
} //KPlato namespace
diff --git a/src/kptfactory.cpp b/src/kptfactory.cpp
index 7d290d23..5f32f6e1 100644
--- a/src/kptfactory.cpp
+++ b/src/kptfactory.cpp
@@ -1,91 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
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 <KoResourcePaths.h>
#include <KoDockRegistry.h>
#include <KoComponentData.h>
#include <kiconloader.h>
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);
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/kptfactoryinit.cpp b/src/kptfactoryinit.cpp
index e93fd261..3179285d 100644
--- a/src/kptfactoryinit.cpp
+++ b/src/kptfactoryinit.cpp
@@ -1,22 +1,23 @@
/* This file is part of the KDE project
Copyright (C) 2005 David Faure <faure@kde.org>
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 "kptfactoryinit.h"
#include "moc_kptfactoryinit.cpp"
diff --git a/src/kptinsertfiledlg.cpp b/src/kptinsertfiledlg.cpp
index c86fc252..cd964ab3 100644
--- a/src/kptinsertfiledlg.cpp
+++ b/src/kptinsertfiledlg.cpp
@@ -1,129 +1,130 @@
/* This file is part of the KDE project
Copyright (C) 20079 Dag Andersen <danders@get2net.dk>
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 "kptinsertfiledlg.h"
#include "kptnode.h"
#include "kptproject.h"
#include <KLocalizedString>
#include <KIO/StatJob>
namespace KPlato
{
InsertFileDialog::InsertFileDialog( Project &project, Node *currentNode, QWidget *parent )
: KoDialog(parent)
{
setCaption( i18n("Insert File") );
setButtons( KoDialog::Ok | KoDialog::Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
m_panel = new InsertFilePanel( project, currentNode, this );
setMainWidget( m_panel );
enableButtonOk(false);
connect( m_panel, &InsertFilePanel::enableButtonOk, this, &KoDialog::enableButtonOk );
}
QUrl InsertFileDialog::url() const
{
return m_panel->url();
}
Node *InsertFileDialog::parentNode() const
{
return m_panel->parentNode();
}
Node *InsertFileDialog::afterNode() const
{
return m_panel->afterNode();
}
//------------------------
InsertFilePanel::InsertFilePanel( Project &project, Node *currentNode, QWidget *parent )
: QWidget( parent ),
m_project( project ),
m_node( currentNode )
{
ui.setupUi( this );
if ( currentNode == 0 || currentNode->type() == Node::Type_Project ) {
ui.ui_isAfter->setEnabled( false );
ui.ui_isParent->setEnabled( false );
ui.ui_useProject->setChecked( true );
ui.ui_name->setText( project.name() );
} else {
ui.ui_isAfter->setChecked( true );
ui.ui_name->setText( currentNode->name() );
}
connect( ui.ui_url, &KUrlRequester::textChanged, this, &InsertFilePanel::changed );
connect( ui.ui_url, &KUrlRequester::openFileDialog, this, &InsertFilePanel::slotOpenFileDialog );
}
void InsertFilePanel::slotOpenFileDialog( KUrlRequester * )
{
ui.ui_url->setFilter( "*.plan" );
}
void InsertFilePanel::changed( const QString &text )
{
KIO::StatJob* statJob = KIO::stat( QUrl::fromUserInput(text) );
statJob->setSide( KIO::StatJob::SourceSide );
const bool isUrlReadable = statJob->exec();
emit enableButtonOk( isUrlReadable );
}
QUrl InsertFilePanel::url() const
{
return ui.ui_url->url();
}
Node *InsertFilePanel::parentNode() const
{
if ( ui.ui_useProject->isChecked() ) {
return &(m_project);
}
if ( ui.ui_isParent->isChecked() ) {
return m_node;
}
if ( ui.ui_isAfter->isChecked() ) {
return m_node->parentNode();
}
return &(m_project);
}
Node *InsertFilePanel::afterNode() const
{
if ( ui.ui_isAfter->isChecked() ) {
return m_node;
}
return 0;
}
} //KPlato namespace
diff --git a/src/kptmaindocument.cpp b/src/kptmaindocument.cpp
index f3a15728..21209209 100644
--- a/src/kptmaindocument.cpp
+++ b/src/kptmaindocument.cpp
@@ -1,1582 +1,1583 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2004, 2010, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2006 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
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 "kptpackage.h"
#include "kptworkpackagemergedialog.h"
#include "kptdebug.h"
#include <KoStore.h>
#include <KoXmlReader.h>
#include <KoStoreDevice.h>
#include <KoOdfReadStore.h>
#include <KoUpdater.h>
#include <KoProgressUpdater.h>
#include <KoDocumentInfo.h>
#include <QApplication>
#include <QPainter>
#include <QDir>
#include <QMutableMapIterator>
#include <QTemporaryFile>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <KIO/CopyJob>
#include <kundo2command.h>
#ifdef HAVE_KHOLIDAYS
#include <KHolidays/HolidayRegion>
#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<<plugin;
m_schedulerPlugins[key] = plugin;
}
void MainDocument::configChanged()
{
//m_project->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<KoUpdater> 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<<newProject->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<<n->name()<<s;
n->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<<n->name()<<s;
n->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;
QDomDocument document( "plan" );
document.appendChild( document.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "plan" );
doc.setAttribute( "editor", "Plan" );
doc.setAttribute( "mime", "application/x-vnd.kde.plan" );
doc.setAttribute( "version", PLAN_FILE_SYNTAX_VERSION );
document.appendChild( doc );
// Save the project
m_project->save( doc );
return 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:"<<nwritten<<"- expected:"<< s.size();
}
return nwritten == (int)s.size();
}
bool MainDocument::saveWorkPackageFormat( const QString &file, const Node *node, long id, Resource *resource )
{
debugPlan <<"Saving to store";
KoStore::Backend backend = KoStore::Zip;
#ifdef QCA2
/* if ( d->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<<url;
if ( ! url.isLocalFile() ) {
debugPlan<<"TODO: download if url not local";
return false;
}
KoStore *store = KoStore::createStore( url.path(), KoStore::Read, "", KoStore::Auto );
if ( store->bad() ) {
// d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file );
debugPlan<<"bad store"<<url.toDisplayString();
delete store;
// QApplication::restoreOverrideCursor();
return false;
}
if ( ! store->open( "root" ) ) { // "old" file format (maindoc.xml)
// i18n( "File does not have a maindoc.xml: %1", file );
debugPlan<<"No root"<<url.toDisplayString();
delete store;
// QApplication::restoreOverrideCursor();
return false;
}
Package *package = 0;
KoXmlDocument doc;
QString errorMsg; // Error variables for QDomDocument::setContent
int errorLine, errorColumn;
bool ok = doc.setContent( store->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:"<<package->timeTag<<package->ownerId<<package->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<Task*>( proj->childNode( 0 ) );
package->toTask = qobject_cast<Task*>( 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:"<<package->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:"<<proj->childNode( 0 )->name()<<package->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:"<<doc->url().fileName()<<"to:"<<tmpfile.fileName();
return false;
}
package->documents.insert( tmpfile.fileName(), doc->url() );
tmpfile.setAutoRemove( false );
debugPlan<<"extracted:"<<doc->url().fileName()<<"->"<<tmpfile.fileName();
return true;
}
void MainDocument::autoCheckForWorkPackages()
{
if ( m_config.checkForWorkPackages() ) {
checkForWorkPackages( true );
}
QTimer::singleShot ( 10000, this, &MainDocument::autoCheckForWorkPackages );
}
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() ) {
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<QDateTime, Package*> 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, SIGNAL(finished(int)), SLOT(workPackageMergeDialogFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
}
}
void MainDocument::workPackageMergeDialogFinished( int result )
{
WorkPackageMergeDialog *dlg = qobject_cast<WorkPackageMergeDialog*>( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == KoDialog::Yes ) {
// merge the oldest first
foreach( int i, dlg->checkedList() ) {
const QList<Package*> &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:"<<dir.path();
return;
}
}
QFileInfo from( file );
QString name = KPlatoSettings::saveUrl().path() + '/' + from.fileName();
if ( file.rename( name ) ) {
return;
}
name = KPlatoSettings::saveUrl().path() + '/';
name += from.completeBaseName() + "-%1";
if ( ! from.suffix().isEmpty() ) {
name += '.' + from.suffix();
}
int i = 0;
bool ok = false;
while ( ! ok && i < 1000 ) {
++i;
ok = QFile::rename( file.fileName(), name.arg( i ) );
}
if ( ! ok ) {
//TODO message
debugPlan<<"terminateWorkPackage: Failed to save"<<file.fileName();
}
}
}
void MainDocument::mergeWorkPackage( const Package *package )
{
const Project &proj = *(package->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<QDate> &dates = org.entries().keys();
for (const QDate &d : dates) {
if ( ! curr.entries().contains( d ) ) {
debugPlan<<"remove entry "<<d;
cmd->addCommand( new RemoveCompletionEntryCmd( org, d ) );
}
}
// add new entries / modify existing
const QList<QDate> &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<QString, QUrl>::const_iterator it = package->documents.constBegin();
QMap<QString, QUrl>::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:"<<src<<it.value();
}
}
}
if ( ! docsaved && cmd->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>( 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();
MainDocument *doc = qobject_cast<MainDocument*>( 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();
MainDocument *doc = qobject_cast<MainDocument*>( 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()<<"error="<<error;
if ( ! error.isEmpty() ) {
KMessageBox::error( 0, error );
}
MainDocument *doc = qobject_cast<MainDocument*>( 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 QUrl &url)
{
m_sharedProjectsFiles.clear();
QFileInfo fi(url.path());
if (!fi.exists()) {
return;
}
if (fi.isFile()) {
m_sharedProjectsFiles = QList<QUrl>() << url;
debugPlan<<"Get all projects in file:"<<url;
} else if (fi.isDir()) {
// Get all plan files in this directory
debugPlan<<"Get all projects in dir:"<<url;
QDir dir = fi.dir();
foreach(const QString &f, dir.entryList(QStringList()<<"*.plan")) {
QString path = dir.canonicalPath();
if (path.isEmpty()) {
continue;
}
path += '/' + f;
QUrl u(path);
u.setScheme("file");
m_sharedProjectsFiles << u;
}
} else {
warnPlan<<"Unknown url:"<<url<<url.path()<<url.fileName();
return;
}
clearResourceAssignments();
}
void MainDocument::slotInsertSharedProject()
{
debugPlan<<m_sharedProjectsFiles;
if (m_sharedProjectsFiles.isEmpty()) {
return;
}
Part *part = new Part( this );
MainDocument *doc = new MainDocument( part );
doc->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();
MainDocument *doc = qobject_cast<MainDocument*>( sender() );
if (doc) {
Project &p = doc->getProject();
debugPlanShared<<m_project->id()<<"Loaded project:"<<p.id()<<p.name();
if (p.id() != m_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<<res->name()<<"added:"<<app->auxcilliaryInfo()<<app;
}
}
}
}
}
doc->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()<<"error="<<error;
if ( ! error.isEmpty() ) {
KMessageBox::error( 0, error );
}
MainDocument *doc = qobject_cast<MainDocument*>( 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<QString> 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<Calendar*> &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<Calendar*> sortedRemoveCalendars(Project &shared, const QList<Calendar*> &lst) {
QList<Calendar*> 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:"<<n<<n->name();
NodeDeleteCmd cmd(n);
cmd.execute();
}
foreach(ScheduleManager *m, project.scheduleManagers()) {
debugPlanShared<<"Project not empty, delete schedule:"<<m<<m->name();
DeleteScheduleManagerCmd cmd(project, m);
cmd.execute();
}
foreach(Account *a, project.accounts().accountList()) {
debugPlanShared<<"Project not empty, delete account:"<<a<<a->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<ResourceGroup*> removedGroups;
QList<Resource*> removedResources;
QList<Calendar*> 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<<endl<<" This:"<<l2<<endl<<"Shared:"<<l1;
QList<ResourceGroup*> 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:"<<group<<group->id();
}
group->setName(g->name());
group->setType(g->type());
debugPlanShared<<"Updated group:"<<group<<group->id();
}
}
QList<Resource*> 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:"<<resource<<resource->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:"<<resource<<resource->id();
}
}
QList<Calendar*> 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:"<<calendar<<calendar->id();
}
*calendar = *c;
debugPlanShared<<"Updated calendar:"<<calendar<<calendar->id();
}
}
debugPlanShared<<"Remove:"<<endl<<"calendars:"<<removecalendars<<endl<<"resources:"<<removeresources<<endl<<"groups:"<<removegroups;
while (!removecalendars.isEmpty()) {
for (int i = 0; i < removecalendars.count(); ++i) {
Calendar *c = removecalendars.at(i);
if (c->childCount() == 0) {
removecalendars.removeAt(i);
debugPlanShared<<"Delete calendar:"<<c<<c->id();
CalendarRemoveCmd cmd(&project, c);
cmd.execute();
}
}
}
for (Resource *r : qAsConst(removeresources)) {
debugPlanShared<<"Delete resource:"<<r<<r->id();
RemoveResourceCmd cmd(r->parentGroup(), r);
cmd.execute();
}
for (ResourceGroup *g : qAsConst(removegroups)) {
debugPlanShared<<"Delete group:"<<g<<g->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<<mod<<m_viewlistModified;
KoDocument::setModified( mod || m_viewlistModified ); // Must always call to activate autosave
}
void MainDocument::viewlistModified()
{
if ( ! m_viewlistModified ) {
m_viewlistModified = true;
}
setModified( true ); // Must always call to activate autosave
}
// called after user has created a new project in welcome view
void MainDocument::slotProjectCreated()
{
if (url().isEmpty() && !m_project->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()<<Q_FUNC_INFO<<"in week";
} else if (subcalendar) {
c = new Calendar(i18n("Holidays"));
m_project->addCalendar(c, week);
qDebug()<<Q_FUNC_INFO<<"subcalendar";
} else if (separate) {
c = new Calendar(i18n("Holidays"));
m_project->addCalendar(c);
qDebug()<<Q_FUNC_INFO<<"separate";
} else {
Q_ASSERT(false); // something wrong
}
qDebug()<<Q_FUNC_INFO<<KPlatoSettings::region();
if (c == 0) {
warnPlan<<Q_FUNC_INFO<<"Failed to generate holidays. Bad option:"<<KPlatoSettings::generateHolidaysChoice();
return;
}
c->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/kptpart.cpp b/src/kptpart.cpp
index 7331b03f..f692b919 100644
--- a/src/kptpart.cpp
+++ b/src/kptpart.cpp
@@ -1,213 +1,214 @@
/* This file is part of the KDE project
Copyright (C) 2012 C. Boemann <cbo@kogmbh.com>
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 "kptpart.h"
#include "kptview.h"
#include "kptmaindocument.h"
#include "kptfactory.h"
#include "welcome/WelcomeView.h"
#include "kpthtmlview.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoComponentData.h>
#include <KRecentFilesAction>
#include <KXMLGUIFactory>
#include <KConfigGroup>
#include <KHelpClient>
#include <KRun>
#include <KDesktopFile>
#include <KAboutData>
#include <QStackedWidget>
#include <QDesktopServices>
Part::Part(QObject *parent)
: KoPart(Factory::global(), parent)
, startUpWidget(0)
{
setTemplatesResourcePath(QLatin1String("calligraplan/templates/"));
KDesktopFile df(componentData().aboutData().desktopFileName() + ".desktop");
new Help(df.readDocPath());
}
Part::~Part()
{
}
void Part::setDocument(KPlato::MainDocument *document)
{
KoPart::setDocument(document);
m_document = document;
}
KoView *Part::createViewInstance(KoDocument *document, QWidget *parent)
{
// synchronize view selector
View *view = dynamic_cast<View*>(views().value(0));
/*FIXME
if (view && m_context) {
QDomDocument doc = m_context->save(view);
m_context->setContent(doc.toString());
}*/
view = new View(this, qobject_cast<MainDocument*>(document), parent);
// connect(view, SIGNAL(destroyed()), this, SLOT(slotViewDestroyed()));
// connect(document, SIGNAL(viewListItemAdded(const ViewListItem*,const ViewListItem*,int)), view, SLOT(addViewListItem(const ViewListItem*,const ViewListItem*,int)));
// connect(document, SIGNAL(viewListItemRemoved(const ViewListItem*)), view, SLOT(removeViewListItem(const ViewListItem*)));
return view;
}
KoMainWindow *Part::createMainWindow()
{
KoMainWindow *w = new KoMainWindow(PLAN_MIME_TYPE, componentData());
QAction *handbookAction = w->action("help_contents");
if (handbookAction) {
// we do not want to use khelpcenter as we do not install docs
disconnect(handbookAction, 0, 0, 0);
connect(handbookAction, &QAction::triggered, this, &Part::slotHelpContents);
}
return w;
}
void Part::slotHelpContents()
{
QDesktopServices::openUrl(QUrl(Help::page("Manual")));
}
void Part::showStartUpWidget(KoMainWindow *parent)
{
m_toolbarVisible = parent->factory()->container("mainToolBar", parent)->isVisible();
if (m_toolbarVisible) {
parent->factory()->container("mainToolBar", parent)->hide();
}
if (startUpWidget) {
startUpWidget->show();
} else {
createStarUpWidget(parent);
parent->setCentralWidget(startUpWidget);
}
parent->setPartToOpen(this);
}
void Part::openTemplate(const QUrl &url)
{
debugPlan<<"Open shared resources template:"<<url;
m_document->setLoadingTemplate(true);
m_document->setLoadingSharedResourcesTemplate(url.fileName() == "SharedResources.plant");
KoPart::openTemplate(url);
m_document->setLoadingTemplate(false);
}
void Part::openTaskModule(const QUrl &url)
{
Part *part = new Part(0);
MainDocument *doc = new MainDocument(part);
part->setDocument(doc);
doc->setIsTaskModule(true);
mainWindows().first()->openDocument(part, url);
}
void Part::createStarUpWidget(KoMainWindow *parent)
{
startUpWidget = new QStackedWidget(parent);
startUpWidget->addWidget(createWelcomeView(parent));
startUpWidget->addWidget(createIntroductionView());
}
void Part::finish()
{
mainWindows().first()->setRootDocument(document(), this);
if (m_toolbarVisible) {
KoPart::mainWindows().first()->factory()->container("mainToolBar", mainWindows().first())->show();
}
}
QWidget *Part::createWelcomeView(KoMainWindow *mw)
{
MainDocument *doc = static_cast<MainDocument*>(document());
WelcomeView *v = new WelcomeView(this, doc, startUpWidget);
v->setProject(&(doc->getProject()));
KSharedConfigPtr configPtr = Factory::global().config();
KRecentFilesAction recent("x", 0);
recent.loadEntries(configPtr->group("RecentFiles"));
v->setRecentFiles(recent.items());
connect(v, &WelcomeView::loadSharedResources, doc, &MainDocument::insertResourcesFile);
connect(v, &WelcomeView::recentProject, mw, &KoMainWindow::slotFileOpenRecent);
connect(v, &WelcomeView::showIntroduction, this, &Part::slotShowIntroduction);
connect(v, &WelcomeView::projectCreated, doc, &MainDocument::slotProjectCreated);
connect(v, &WelcomeView::finished, this, &Part::finish);
connect(v, SIGNAL(openTemplate(QUrl)), this, SLOT(openTemplate(QUrl)));
return v;
}
void Part::slotShowIntroduction()
{
startUpWidget->setCurrentIndex(1);
slotOpenUrlRequest(static_cast<HtmlView*>(startUpWidget->currentWidget()), QUrl("about:plan/main"));
}
void Part::slotOpenUrlRequest( HtmlView *v, const QUrl &url )
{
debugPlan<<url;
if ( url.scheme() == QLatin1String("about") ) {
if ( url.url() == QLatin1String("about:close") ) {
startUpWidget->setCurrentIndex(0);
return;
}
if ( url.url().startsWith( QLatin1String( "about:plan" ) ) ) {
MainDocument *doc = static_cast<MainDocument*>(document());
doc->aboutPage().generatePage( v->htmlPart(), url );
return;
}
}
if ( url.scheme() == QLatin1String("help") ) {
KHelpClient::invokeHelp( "", url.fileName() );
return;
}
// try to open the url
debugPlan<<url<<"is external, discard";
new KRun( url, currentMainwindow() );
}
QWidget *Part::createIntroductionView()
{
HtmlView *v = new HtmlView(this, document(), startUpWidget );
v->htmlPart().setJScriptEnabled(false);
v->htmlPart().setJavaEnabled(false);
v->htmlPart().setMetaRefreshEnabled(false);
v->htmlPart().setPluginsEnabled(false);
slotOpenUrlRequest( v, QUrl( "about:plan/main" ) );
connect( v, &HtmlView::openUrlRequest, this, &Part::slotOpenUrlRequest );
return v;
}
diff --git a/src/kptprintingcontrolprivate.cpp b/src/kptprintingcontrolprivate.cpp
index a5cd63a3..e2bc41c7 100644
--- a/src/kptprintingcontrolprivate.cpp
+++ b/src/kptprintingcontrolprivate.cpp
@@ -1,57 +1,58 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2002 - 2011 Dag Andersen <danders@get2net.dk>
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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 "kptprintingcontrolprivate.h"
#include <kptviewbase.h>
#include <QSpinBox>
#include <QPrintDialog>
namespace KPlato
{
PrintingControlPrivate::PrintingControlPrivate( PrintingDialog *job, QPrintDialog *dia )
: QObject( dia ),
m_job( job ),
m_dia( dia )
{
connect(job, SIGNAL(changed()), SLOT(slotChanged()));
}
void PrintingControlPrivate::slotChanged()
{
if ( ! m_job || ! m_dia ) {
return;
}
QSpinBox *to = m_dia->findChild<QSpinBox*>("to");
QSpinBox *from = m_dia->findChild<QSpinBox*>("from");
if ( to && from ) {
from->setMinimum( m_job->documentFirstPage() );
from->setMaximum( m_job->documentLastPage() );
from->setValue( from->minimum() );
to->setMinimum( from->minimum() );
to->setMaximum( from->maximum() );
to->setValue( to->maximum() );
}
}
} //KPlato namespace
diff --git a/src/kptschedulerpluginloader.cpp b/src/kptschedulerpluginloader.cpp
index 6764e125..18be1c74 100644
--- a/src/kptschedulerpluginloader.cpp
+++ b/src/kptschedulerpluginloader.cpp
@@ -1,108 +1,109 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptschedulerpluginloader.h"
#include "kptschedulerplugin.h"
#include "kptdebug.h"
#include <KoPluginLoader.h>
#include <QPluginLoader>
#include <QLocale>
namespace KPlato
{
SchedulerPluginLoader::SchedulerPluginLoader(QObject * parent)
: QObject(parent)
{
}
SchedulerPluginLoader::~SchedulerPluginLoader()
{
}
static
QJsonValue readLocalValue(const QJsonObject &json, const QString &key)
{
// start with language_country
const QString localeName = QLocale().name();
QString localKey = key + QLatin1Char('[') + localeName + QLatin1Char(']');
QJsonObject::ConstIterator it = json.constFind(localKey);
if (it != json.constEnd()) {
return it.value();
}
// drop _country
const int separatorIndex = localeName.indexOf(QLatin1Char('_'));
if (separatorIndex != -1) {
const int localKeySeparatorIndex = key.length() + 1 + separatorIndex;
localKey[localKeySeparatorIndex] = QLatin1Char(']');
localKey.truncate(localKeySeparatorIndex + 1);
it = json.constFind(localKey);
if (it != json.constEnd()) {
return it.value();
}
}
// default to unlocalized value
return json.value(key);
}
void SchedulerPluginLoader::loadAllPlugins()
{
debugPlan << "Load all plugins";
const QList<QPluginLoader *> offers = KoPluginLoader::pluginLoaders(QStringLiteral("calligraplan/schedulers"));
foreach(QPluginLoader *pluginLoader, offers) {
KPluginFactory *factory = qobject_cast<KPluginFactory*>(pluginLoader->instance());
if (!factory)
{
errorPlan << "KPluginFactory could not load the plugin:" << pluginLoader->fileName();
continue;
}
SchedulerPlugin *plugin = factory->create<SchedulerPlugin>(this);
if (plugin) {
QJsonObject json = pluginLoader->metaData().value("MetaData").toObject();
json = json.value("KPlugin").toObject();
const QString key = json.value(QLatin1String("Name")).toString(); // use unlocalized name as plugin identifier
const QString name = readLocalValue(json, QLatin1String("Name")).toString();
const QString comment = readLocalValue(json, QLatin1String("Description")).toString();
debugPlan << "Load plugin:" << key << name << ", " << comment;
plugin->setName( name );
plugin->setComment( comment );
emit pluginLoaded( key, plugin);
} else {
debugPlan << "KPluginFactory could not create SchedulerPlugin:" << pluginLoader->fileName();
}
}
qDeleteAll(offers);
}
} //namespace KPlato
diff --git a/src/kptschedulesdocker.cpp b/src/kptschedulesdocker.cpp
index fb43e9dc..f4776091 100644
--- a/src/kptschedulesdocker.cpp
+++ b/src/kptschedulesdocker.cpp
@@ -1,111 +1,112 @@
/* This file is part of the KDE project
* Copyright (C) 2009, 2012 Dag Andersen <danders@get2net.dk>
*
* 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 "kptschedulesdocker.h"
#include "kptschedule.h"
#include <kptdebug.h>
#include <KLocalizedString>
#include <QAbstractItemView>
#include <QModelIndex>
#include <QModelIndexList>
#include <QTreeView>
namespace KPlato
{
SchedulesDocker::SchedulesDocker()
{
setWindowTitle(i18n("Schedule Selector"));
m_view = new QTreeView( this );
m_sfModel.setSourceModel( &m_model );
m_view->setModel( &m_sfModel );
m_sfModel.setFilterKeyColumn ( ScheduleModel::ScheduleScheduled );
m_sfModel.setFilterRole( Qt::EditRole );
m_sfModel.setFilterFixedString( "true" );
m_sfModel.setDynamicSortFilter ( true );
for( int c = 1; c < m_model.columnCount(); ++c ) {
m_view->setColumnHidden( c, true );
}
m_view->setHeaderHidden( true );
m_view->setSelectionMode( QAbstractItemView::SingleSelection );
m_view->setSelectionBehavior( QAbstractItemView::SelectRows );
setWidget(m_view);
connect( m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SchedulesDocker::slotSelectionChanged );
}
SchedulesDocker::~SchedulesDocker()
{
}
void SchedulesDocker::slotSelectionChanged()
{
emit selectionChanged( selectedSchedule() );
}
void SchedulesDocker::setProject( Project *project )
{
debugPlan<<project;
m_model.setProject( project );
}
ScheduleManager *SchedulesDocker::selectedSchedule() const
{
QModelIndexList lst = m_view->selectionModel()->selectedRows();
Q_ASSERT( lst.count() <= 1 );
ScheduleManager *sm = 0;
if ( ! lst.isEmpty() ) {
sm = m_model.manager( m_sfModel.mapToSource( lst.first() ) );
}
return sm;
}
void SchedulesDocker::setSelectedSchedule( ScheduleManager *sm )
{
qDebug()<<"setSelectedSchedule:"<<sm<<m_model.index( sm );
QModelIndex idx = m_sfModel.mapFromSource( m_model.index( sm ) );
if ( sm ) {
Q_ASSERT( idx.isValid() );
}
m_view->selectionModel()->select( idx, QItemSelectionModel::ClearAndSelect );
qDebug()<<"setSelectedSchedule:"<<sm<<idx;
}
//--------------------
SchedulesDockerFactory::SchedulesDockerFactory()
{
}
QString SchedulesDockerFactory::id() const
{
return QString("KPlatoSchedulesDocker");
}
QDockWidget* SchedulesDockerFactory::createDockWidget()
{
return new SchedulesDocker();
}
} //namespace KPlato
diff --git a/src/kpttaskdefaultpanel.cpp b/src/kpttaskdefaultpanel.cpp
index 48e1e6ca..e1cdc287 100644
--- a/src/kpttaskdefaultpanel.cpp
+++ b/src/kpttaskdefaultpanel.cpp
@@ -1,178 +1,179 @@
/* This file is part of the KDE project
Copyright (C) 2004, 2007 Dag Andersen <danders@get2net.dk>
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 "kpttaskdefaultpanel.h"
#include "kptduration.h"
#include "kptmycombobox_p.h"
#include "calligraplansettings.h"
#ifdef PLAN_KDEPIMLIBS_FOUND
#include <akonadi/contact/emailaddressselectiondialog.h>
#include <akonadi/contact/emailaddressselectionwidget.h>
#include <akonadi/contact/emailaddressselection.h>
#endif
#include <QDateTime>
#include <kactioncollection.h>
#include <ktextedit.h>
namespace KPlato
{
TaskDefaultPanel::TaskDefaultPanel( QWidget *parent )
: ConfigTaskPanelImpl( parent )
{
}
//-----------------------------
ConfigTaskPanelImpl::ConfigTaskPanelImpl(QWidget *p )
: QWidget(p)
{
setupUi(this);
kcfg_ExpectedEstimate->setMinimumUnit( (Duration::Unit)KPlatoSettings::self()->minimumDurationUnit() );
kcfg_ExpectedEstimate->setMaximumUnit( (Duration::Unit)KPlatoSettings::self()->maximumDurationUnit() );
#ifndef PLAN_KDEPIMLIBS_FOUND
chooseLeader->hide();
#endif
// FIXME
// [Bug 311940] New: Plan crashes when typing a text in the filter textbox before the textbook is fully loaded when selecting a contact from the adressbook
chooseLeader->hide();
initDescription();
connect(chooseLeader, &QAbstractButton::clicked, this, &ConfigTaskPanelImpl::changeLeader);
connect( kcfg_ConstraintStartTime, &QDateTimeEdit::dateTimeChanged, this, &ConfigTaskPanelImpl::startDateTimeChanged );
connect( kcfg_ConstraintEndTime, &QDateTimeEdit::dateTimeChanged, this, &ConfigTaskPanelImpl::endDateTimeChanged );
// Hack to have an interface to kcfg wo adding a custom class for this
kcfg_Unit->addItems( Duration::unitList( true ) );
connect( kcfg_ExpectedEstimate, &DurationSpinBox::unitChanged, this, &ConfigTaskPanelImpl::unitChanged );
kcfg_Unit->hide();
connect( kcfg_Unit, SIGNAL(currentIndexChanged(int)), SLOT(currentUnitChanged(int)) );
}
void ConfigTaskPanelImpl::initDescription()
{
toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
KActionCollection *collection = new KActionCollection( this ); //krazy:exclude=tipsandthis
kcfg_Description->setRichTextSupport( KRichTextWidget::SupportBold |
KRichTextWidget::SupportItalic |
KRichTextWidget::SupportUnderline |
KRichTextWidget::SupportStrikeOut |
KRichTextWidget::SupportChangeListStyle |
KRichTextWidget::SupportAlignment |
KRichTextWidget::SupportFormatPainting );
collection->addActions(kcfg_Description->createActions());
toolbar->addAction( collection->action( "format_text_bold" ) );
toolbar->addAction( collection->action( "format_text_italic" ) );
toolbar->addAction( collection->action( "format_text_underline" ) );
toolbar->addAction( collection->action( "format_text_strikeout" ) );
toolbar->addSeparator();
toolbar->addAction( collection->action( "format_list_style" ) );
toolbar->addSeparator();
toolbar->addAction( collection->action( "format_align_left" ) );
toolbar->addAction( collection->action( "format_align_center" ) );
toolbar->addAction( collection->action( "format_align_right" ) );
toolbar->addAction( collection->action( "format_align_justify" ) );
toolbar->addSeparator();
// toolbar->addAction( collection->action( "format_painter" ) );
// kcfg_Description->append( "" );
kcfg_Description->setReadOnly( false );
kcfg_Description->setOverwriteMode( false );
kcfg_Description->setLineWrapMode( KTextEdit::WidgetWidth );
kcfg_Description->setTabChangesFocus( true );
}
void ConfigTaskPanelImpl::changeLeader()
{
#ifdef PLAN_KDEPIMLIBS_FOUND
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog( this );
if ( dlg->exec() && dlg ) {
QStringList names;
const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
foreach ( const Akonadi::EmailAddressSelection &selection, selections ) {
QString s = selection.name();
if ( ! selection.email().isEmpty() ) {
if ( ! selection.name().isEmpty() ) {
s += " <";
}
s += selection.email();
if ( ! selection.name().isEmpty() ) {
s += '>';
}
if ( ! s.isEmpty() ) {
names << s;
}
}
}
if ( ! names.isEmpty() ) {
kcfg_Leader->setText( names.join( ", " ) );
}
}
#endif
}
void ConfigTaskPanelImpl::startDateTimeChanged( const QDateTime &dt )
{
if ( dt > kcfg_ConstraintEndTime->dateTime() ) {
kcfg_ConstraintEndTime->setDateTime( dt );
}
}
void ConfigTaskPanelImpl::endDateTimeChanged( const QDateTime &dt )
{
if ( kcfg_ConstraintStartTime->dateTime() > dt ) {
kcfg_ConstraintStartTime->setDateTime( dt );
}
}
void ConfigTaskPanelImpl::unitChanged( int unit )
{
if ( kcfg_Unit->currentIndex() != unit ) {
kcfg_Unit->setCurrentIndex( unit );
// kcfg uses the activated() signal to track changes
kcfg_Unit->emitActivated( unit );
}
}
void ConfigTaskPanelImpl::currentUnitChanged( int unit )
{
// to get values set at startup
if ( unit != kcfg_ExpectedEstimate->unit() ) {
kcfg_ExpectedEstimate->setUnit( (Duration::Unit)unit );
}
}
} //KPlato namespace
diff --git a/src/kptview.cpp b/src/kptview.cpp
index 5b543dae..8e7d4629 100644
--- a/src/kptview.cpp
+++ b/src/kptview.cpp
@@ -1,3258 +1,3259 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2002 - 2011 Dag Andersen <danders@get2net.dk>
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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 "kptview.h"
#include <kmessagebox.h>
#include <KRecentFilesAction>
#include "KoDocumentInfo.h"
#include "KoMainWindow.h"
#include <KoIcon.h>
#include <KoResourcePaths.h>
#include <QAction>
#include <QApplication>
#include <QLabel>
#include <QString>
#include <QStringList>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QPrintDialog>
#include <QDomDocument>
#include <QDomElement>
#include <kundo2command.h>
#include <QTimer>
#include <QDockWidget>
#include <QMenu>
#include <QTemporaryFile>
#include <QFileDialog>
#include <kactioncollection.h>
#include <kactionmenu.h>
#include <kstandardaction.h>
#include <ktoolbar.h>
#include <kconfigdialogmanager.h>
#include <kxmlguifactory.h>
#include <ktoggleaction.h>
#include <kconfigdialog.h>
#include <ktoolinvocation.h>
#include <krun.h>
#include <khelpclient.h>
#include <KoPart.h>
#include <KoComponentData.h>
#include <KoPluginLoader.h>
#include "kptlocale.h"
#include "kptviewbase.h"
#include "kptaccountsview.h"
#include "kptaccountseditor.h"
#include "kptcalendareditor.h"
#include "kptfactory.h"
#include "kptmilestoneprogressdialog.h"
#include "kpttaskdescriptiondialog.h"
#include "kptnode.h"
#include "kptmaindocument.h"
#include "kptproject.h"
#include "kptmainprojectdialog.h"
#include "kpttask.h"
#include "kptsummarytaskdialog.h"
#include "kpttaskdialog.h"
#include "kpttaskprogressdialog.h"
#include "kptganttview.h"
#include "kpttaskeditor.h"
#include "kptdependencyeditor.h"
#include "kptperteditor.h"
#include "kptdatetime.h"
#include "kptcommand.h"
#include "kptrelation.h"
#include "kptrelationdialog.h"
#include "kptresourceappointmentsview.h"
#include "kptresourceeditor.h"
#include "kptscheduleeditor.h"
#include "kptresourcedialog.h"
#include "kptresource.h"
#include "kptstandardworktimedialog.h"
#include "kptwbsdefinitiondialog.h"
#include "kptresourceassignmentview.h"
#include "kpttaskstatusview.h"
#include "kptsplitterview.h"
#include "kptpertresult.h"
#include "ConfigProjectPanel.h"
#include "ConfigWorkVacationPanel.h"
#include "kpttaskdefaultpanel.h"
#include "kptworkpackageconfigpanel.h"
#include "kptcolorsconfigpanel.h"
#include "kptinsertfiledlg.h"
#include "kpthtmlview.h"
#include "about/aboutpage.h"
#include "kptlocaleconfigmoneydialog.h"
#include "kptflatproxymodel.h"
#include "kpttaskstatusmodel.h"
#include "reportsgenerator/ReportsGeneratorView.h"
#ifdef PLAN_USE_KREPORT
#include "reports/reportview.h"
#include "reports/reportdata.h"
#endif
#include "kptviewlistdialog.h"
#include "kptviewlistdocker.h"
#include "kptviewlist.h"
#include "kptschedulesdocker.h"
#include "kptpart.h"
#include "kptdebug.h"
#include "calligraplansettings.h"
#include "kptprintingcontrolprivate.h"
// #include "KPtViewAdaptor.h"
#include <assert.h>
namespace KPlato
{
//-------------------------------
ConfigDialog::ConfigDialog(QWidget *parent, const QString& name, KConfigSkeleton *config )
: KConfigDialog( parent, name, config ),
m_config( config )
{
KConfigDialogManager::changedMap()->insert("KRichTextWidget", SIGNAL(textChanged()) );
}
bool ConfigDialog::hasChanged()
{
QRegExp kcfg( "kcfg_*" );
foreach ( KRichTextWidget *w, findChildren<KRichTextWidget*>( kcfg ) ) {
KConfigSkeletonItem *item = m_config->findItem( w->objectName().mid(5) );
if ( ! item->isEqual( w->toHtml() ) ) {
return true;
}
}
return false;
}
void ConfigDialog::updateSettings()
{
bool changed = false;
QRegExp kcfg( "kcfg_*" );
foreach ( KRichTextWidget *w, findChildren<KRichTextWidget*>( kcfg ) ) {
KConfigSkeletonItem *item = m_config->findItem( w->objectName().mid(5) );
if ( ! item ) {
warnPlan << "The setting '" << w->objectName().mid(5) << "' has disappeared!";
continue;
}
if ( ! item->isEqual( QVariant( w->toHtml() ) ) ) {
item->setProperty( QVariant( w->toHtml() ) );
changed = true;
}
}
if ( changed ) {
m_config->save();
}
}
void ConfigDialog::updateWidgets()
{
QRegExp kcfg( "kcfg_*" );
foreach ( KRichTextWidget *w, findChildren<KRichTextWidget*>( kcfg ) ) {
KConfigSkeletonItem *item = m_config->findItem( w->objectName().mid(5) );
if ( ! item ) {
warnPlan << "The setting '" << w->objectName().mid(5) << "' has disappeared!";
continue;
}
if ( ! item->isEqual( QVariant( w->toHtml() ) ) ) {
w->setHtml( item->property().toString() );
}
}
}
void ConfigDialog::updateWidgetsDefault()
{
bool usedefault = m_config->useDefaults( true );
updateWidgets();
m_config->useDefaults( usedefault );
}
bool ConfigDialog::isDefault()
{
bool bUseDefaults = m_config->useDefaults(true);
bool result = !hasChanged();
m_config->useDefaults(bUseDefaults);
return result;
}
//------------------------------------
View::View(KoPart *part, MainDocument *doc, QWidget *parent)
: KoView(part, doc, parent),
m_currentEstimateType( Estimate::Use_Expected ),
m_scheduleActionGroup( new QActionGroup( this ) ),
m_readWrite( false ),
m_defaultView(1),
m_partpart (part)
{
//debugPlan;
doc->registerView( this );
setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName());
if ( !doc->isReadWrite() )
setXMLFile( "calligraplan_readonly.rc" );
else
setXMLFile( "calligraplan.rc" );
// new ViewAdaptor( this );
m_sp = new QSplitter( this );
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setMargin(0);
layout->addWidget( m_sp );
ViewListDocker *docker = 0;
if ( mainWindow() == 0 ) {
// Don't use docker if embedded
m_viewlist = new ViewListWidget(doc, m_sp);
m_viewlist->setProject( &( getProject() ) );
connect( m_viewlist, &ViewListWidget::selectionChanged, this, &View::slotSelectionChanged);
connect( this, &View::currentScheduleManagerChanged, m_viewlist, &ViewListWidget::setSelectedSchedule);
connect( m_viewlist, &ViewListWidget::updateViewInfo, this, &View::slotUpdateViewInfo);
} else {
ViewListDockerFactory vl(this);
docker = static_cast<ViewListDocker *>(mainWindow()->createDockWidget(&vl));
if (docker->view() != this) {
docker->setView(this);
}
m_viewlist = docker->viewList();
#if 0 //SchedulesDocker
SchedulesDockerFactory sdf;
SchedulesDocker *sd = dynamic_cast<SchedulesDocker*>( createDockWidget( &sdf ) );
Q_ASSERT( sd );
sd->setProject( &getProject() );
connect(sd, SIGNAL(selectionChanged(KPlato::ScheduleManager*)), SLOT(slotSelectionChanged(KPlato::ScheduleManager*)));
connect(this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), sd, SLOT(setSelectedSchedule(KPlato::ScheduleManager*)));
#endif
}
m_tab = new QStackedWidget( m_sp );
////////////////////////////////////////////////////////////////////////////////////////////////////
// Add sub views
createIntroductionView();
// The menu items
// ------ File
/* actionCreateTemplate = new QAction( i18n( "&Create Template From Document..." ), this );
actionCollection()->addAction("file_createtemplate", actionCreateTemplate );
connect( actionCreateTemplate, SIGNAL(triggered(bool)), SLOT(slotCreateTemplate()) );
*/
actionCreateNewProject = new QAction( i18n( "&Create New Project..." ), this );
actionCollection()->addAction("file_createnewproject", actionCreateNewProject );
connect( actionCreateNewProject, &QAction::triggered, this, &View::slotCreateNewProject );
// ------ Edit
actionCut = actionCollection()->addAction(KStandardAction::Cut, "edit_cut", this, SLOT(slotEditCut()));
actionCopy = actionCollection()->addAction(KStandardAction::Copy, "edit_copy", this, SLOT(slotEditCopy()));
actionPaste = actionCollection()->addAction(KStandardAction::Paste, "edit_paste", this, SLOT(slotEditPaste()));
// ------ View
actionCollection()->addAction( KStandardAction::Redisplay, "view_refresh" , this, SLOT(slotRefreshView()) );
actionViewSelector = new KToggleAction(i18n("Show Selector"), this);
actionCollection()->addAction("view_show_selector", actionViewSelector );
connect( actionViewSelector, &QAction::triggered, this, &View::slotViewSelector );
// ------ Insert
// ------ Project
actionEditMainProject = new QAction(koIcon("view-time-schedule-edit"), i18n("Edit Main Project..."), this);
actionCollection()->addAction("project_edit", actionEditMainProject );
connect( actionEditMainProject, &QAction::triggered, this, &View::slotProjectEdit );
actionEditStandardWorktime = new QAction(koIcon("configure"), i18n("Define Estimate Conversions..."), this);
actionCollection()->addAction("project_worktime", actionEditStandardWorktime );
connect( actionEditStandardWorktime, &QAction::triggered, this, &View::slotProjectWorktime );
// ------ Tools
actionDefineWBS = new QAction(koIcon("configure"), i18n("Define WBS Pattern..."), this);
actionCollection()->addAction("tools_define_wbs", actionDefineWBS );
connect( actionDefineWBS, &QAction::triggered, this, &View::slotDefineWBS );
actionInsertFile = new QAction(koIcon("document-import"), i18n("Insert Project File..."), this);
actionCollection()->addAction("insert_file", actionInsertFile );
connect( actionInsertFile, &QAction::triggered, this, &View::slotInsertFile );
// ------ Settings
actionConfigure = new QAction(koIcon("configure"), i18n("Configure Plan..."), this);
actionCollection()->addAction("configure", actionConfigure );
connect( actionConfigure, &QAction::triggered, this, &View::slotConfigure );
actionCurrencyConfig = new QAction(koIcon("configure"), i18n("Define Currency..."), this);
actionCollection()->addAction( "config_currency", actionCurrencyConfig );
connect( actionCurrencyConfig, &QAction::triggered, this, &View::slotCurrencyConfig );
#ifdef PLAN_USE_KREPORT
actionOpenReportFile = new QAction(koIcon("document-open"), i18n("Open Report Definition File..."), this);
actionCollection()->addAction( "reportdesigner_open_file", actionOpenReportFile );
connect( actionOpenReportFile, SIGNAL(triggered(bool)), SLOT(slotOpenReportFile()) );
#endif
// ------ Help
actionIntroduction = new QAction(koIcon("dialog-information"), i18n("Introduction to Plan"), this);
actionCollection()->addAction("plan_introduction", actionIntroduction );
connect( actionIntroduction, &QAction::triggered, this, &View::slotIntroduction );
// ------ Popup
actionOpenNode = new QAction(koIcon("document-edit"), i18n("Edit..."), this);
actionCollection()->addAction("node_properties", actionOpenNode );
connect( actionOpenNode, SIGNAL(triggered(bool)), SLOT(slotOpenNode()) );
actionTaskProgress = new QAction(koIcon("document-edit"), i18n("Progress..."), this);
actionCollection()->addAction("task_progress", actionTaskProgress );
connect( actionTaskProgress, &QAction::triggered, this, &View::slotTaskProgress );
actionDeleteTask = new QAction(koIcon("edit-delete"), i18n("Delete Task"), this);
actionCollection()->addAction("delete_task", actionDeleteTask );
connect( actionDeleteTask, SIGNAL(triggered(bool)), SLOT(slotDeleteTask()) );
actionTaskDescription = new QAction(koIcon("document-edit"), i18n("Description..."), this);
actionCollection()->addAction("task_description", actionTaskDescription );
connect( actionTaskDescription, &QAction::triggered, this, &View::slotTaskDescription );
actionIndentTask = new QAction(koIcon("format-indent-more"), i18n("Indent Task"), this);
actionCollection()->addAction("indent_task", actionIndentTask );
connect( actionIndentTask, &QAction::triggered, this, &View::slotIndentTask );
actionUnindentTask= new QAction(koIcon("format-indent-less"), i18n("Unindent Task"), this);
actionCollection()->addAction("unindent_task", actionUnindentTask );
connect( actionUnindentTask, &QAction::triggered, this, &View::slotUnindentTask );
actionMoveTaskUp = new QAction(koIcon("arrow-up"), i18n("Move Task Up"), this);
actionCollection()->addAction("move_task_up", actionMoveTaskUp );
connect( actionMoveTaskUp, &QAction::triggered, this, &View::slotMoveTaskUp );
actionMoveTaskDown = new QAction(koIcon("arrow-down"), i18n("Move Task Down"), this);
actionCollection()->addAction("move_task_down", actionMoveTaskDown );
connect( actionMoveTaskDown, &QAction::triggered, this, &View::slotMoveTaskDown );
actionEditResource = new QAction(koIcon("document-edit"), i18n("Edit Resource..."), this);
actionCollection()->addAction("edit_resource", actionEditResource );
connect( actionEditResource, SIGNAL(triggered(bool)), SLOT(slotEditResource()) );
actionEditRelation = new QAction(koIcon("document-edit"), i18n("Edit Dependency..."), this);
actionCollection()->addAction("edit_dependency", actionEditRelation );
connect( actionEditRelation, SIGNAL(triggered(bool)), SLOT(slotModifyRelation()) );
actionDeleteRelation = new QAction(koIcon("edit-delete"), i18n("Delete Dependency"), this);
actionCollection()->addAction("delete_dependency", actionDeleteRelation );
connect( actionDeleteRelation, &QAction::triggered, this, &View::slotDeleteRelation );
// Viewlist popup
connect( m_viewlist, &ViewListWidget::createView, this, &View::slotCreateView );
m_estlabel = new QLabel( "", 0 );
if ( statusBar() ) {
addStatusBarItem( m_estlabel, 0, true );
}
connect( &getProject(), &Project::scheduleChanged, this, &View::slotScheduleChanged );
connect( &getProject(), &Project::scheduleAdded, this, &View::slotScheduleAdded );
connect( &getProject(), &Project::scheduleRemoved, this, &View::slotScheduleRemoved );
slotPlugScheduleActions();
connect( doc, &MainDocument::changed, this, &View::slotUpdate );
connect( m_scheduleActionGroup, &QActionGroup::triggered, this, &View::slotViewSchedule );
connect( getPart(), &MainDocument::workPackageLoaded, this, &View::slotWorkPackageLoaded );
// hide unused dockers
QTimer::singleShot( 0, this, &View::hideToolDocker );
// create views after dockers hidden, views take time for large projects
QTimer::singleShot( 100, this, &View::initiateViews );
const QList<KPluginFactory *> pluginFactories =
KoPluginLoader::instantiatePluginFactories(QStringLiteral("calligraplan/extensions"));
foreach (KPluginFactory* factory, pluginFactories) {
QObject *object = factory->create<QObject>(this, QVariantList());
KXMLGUIClient *clientPlugin = dynamic_cast<KXMLGUIClient*>(object);
if (clientPlugin) {
insertChildClient(clientPlugin);
} else {
// not our/valid plugin, so delete the created object
object->deleteLater();
}
}
// do not watch task module changes if we are editing one
if (!doc->isTaskModule()) {
QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (!dir.isEmpty()) {
dir += "/taskmodules";
m_dirwatch.addDir(dir, KDirWatch::WatchFiles);
QStringList modules = KoResourcePaths::findAllResources( "calligraplan_taskmodules", "*.plan", KoResourcePaths::NoDuplicates|KoResourcePaths::Recursive );
for (const QString &f : modules) {
m_dirwatch.addFile(f);
}
connect(&m_dirwatch, &KDirWatch::created, this, &View::taskModuleFileChanged);
connect(&m_dirwatch, &KDirWatch::deleted, this, &View::taskModuleFileChanged);
}
}
//debugPlan<<" end";
}
View::~View()
{
ViewBase *view = currentView();
if (view) {
// deactivate view to remove dockers etc
slotGuiActivated(view, false);
}
/* removeStatusBarItem( m_estlabel );
delete m_estlabel;*/
}
// hackish way to get rid of unused dockers, but as long as no official way exists...
void View::hideToolDocker()
{
if ( mainWindow() ) {
QStringList lst; lst << "KPlatoViewList" << "Scripting";
QStringList names;
foreach ( QDockWidget *w, mainWindow()->dockWidgets() ) {
if ( ! lst.contains( w->objectName() ) ) {
names << w->windowTitle();
w->setFeatures( QDockWidget::DockWidgetClosable );
w->hide();
}
}
foreach(const KActionCollection *c, KActionCollection::allCollections()) {
KActionMenu *a = qobject_cast<KActionMenu*>(c->action("settings_dockers_menu"));
if ( a ) {
QList<QAction*> actions = a->menu()->actions();
foreach ( QAction *act, actions ) {
if ( names.contains( act->text() ) ) {
a->removeAction( act );
}
}
a->addSeparator();
break;
}
}
}
}
void View::initiateViews()
{
QApplication::setOverrideCursor( Qt::WaitCursor );
createViews();
connect( m_viewlist, &ViewListWidget::activated, this, &View::slotViewActivated );
// after createViews() !!
connect( m_viewlist, &ViewListWidget::viewListItemRemoved, this, &View::slotViewListItemRemoved );
// after createViews() !!
connect( m_viewlist, &ViewListWidget::viewListItemInserted, this, &View::slotViewListItemInserted );
QDockWidget *docker = qobject_cast<QDockWidget*>( m_viewlist->parent() );
if ( docker ) {
// after createViews() !!
connect( m_viewlist, SIGNAL(modified()), docker, SLOT(slotModified()));
connect( m_viewlist, SIGNAL(modified()), getPart(), SLOT(viewlistModified()));
connect(getPart(), SIGNAL(viewlistModified(bool)), docker, SLOT(updateWindowTitle(bool)));
}
connect( m_tab, &QStackedWidget::currentChanged, this, &View::slotCurrentChanged );
slotSelectDefaultView();
loadContext();
QApplication::restoreOverrideCursor();
}
void View::slotCreateNewProject()
{
debugPlan;
if ( KMessageBox::Continue == KMessageBox::warningContinueCancel( this,
xi18nc( "@info",
"<note>This action cannot be undone.</note><nl/><nl/>"
"Create a new Project from the current project "
"with new project- and task identities.<nl/>"
"Resource- and calendar identities are not changed.<nl/>"
"All scheduling information is removed.<nl/>"
"<nl/>Do you want to continue?" ) ) )
{
emit currentScheduleManagerChanged(0);
getPart()->createNewProject();
slotOpenNode( &getProject() );
}
}
void View::createViews()
{
Context *ctx = getPart()->context();
if ( ctx && ctx->isLoaded() ) {
debugPlan<<"isLoaded";
KoXmlNode n = ctx->context().namedItem( "categories" );
if ( n.isNull() ) {
warnPlan<<"No categories";
} else {
n = n.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() != "category") {
continue;
}
debugPlan<<"category: "<<e.attribute( "tag" );
ViewListItem *cat;
QString cn = e.attribute( "name" );
QString ct = e.attribute( "tag" );
if ( cn.isEmpty() ) {
cn = defaultCategoryInfo( ct ).name;
}
cat = m_viewlist->addCategory( ct, cn );
KoXmlNode n1 = e.firstChild();
for ( ; ! n1.isNull(); n1 = n1.nextSibling() ) {
if ( ! n1.isElement() ) {
continue;
}
KoXmlElement e1 = n1.toElement();
if (e1.tagName() != "view") {
continue;
}
ViewBase *v = 0;
QString type = e1.attribute( "viewtype" );
QString tag = e1.attribute( "tag" );
QString name = e1.attribute( "name" );
QString tip = e1.attribute( "tooltip" );
v = createView( cat, type, tag, name, tip );
//KoXmlNode settings = e1.namedItem( "settings " ); ????
KoXmlNode settings = e1.firstChild();
for ( ; ! settings.isNull(); settings = settings.nextSibling() ) {
if ( settings.nodeName() == "settings" ) {
break;
}
}
if ( v && settings.isElement() ) {
debugPlan<<" settings";
v->loadContext( settings.toElement() );
}
}
}
}
} else {
debugPlan<<"Default";
ViewListItem *cat;
QString ct = "Editors";
cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name );
createCalendarEditor( cat, "CalendarEditor", QString(), TIP_USE_DEFAULT_TEXT );
createAccountsEditor( cat, "AccountsEditor", QString(), TIP_USE_DEFAULT_TEXT );
createResourceEditor( cat, "ResourceEditor", QString(), TIP_USE_DEFAULT_TEXT );
createTaskEditor( cat, "TaskEditor", QString(), TIP_USE_DEFAULT_TEXT );
createDependencyEditor( cat, "DependencyEditor", QString(), TIP_USE_DEFAULT_TEXT );
// Do not show by default
// createPertEditor( cat, "PertEditor", QString(), TIP_USE_DEFAULT_TEXT );
createScheduleHandler( cat, "ScheduleHandlerView", QString(), TIP_USE_DEFAULT_TEXT );
ct = "Views";
cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name );
createGanttView( cat, "GanttView", QString(), TIP_USE_DEFAULT_TEXT );
createMilestoneGanttView( cat, "MilestoneGanttView", QString(), TIP_USE_DEFAULT_TEXT );
createResourceAppointmentsView( cat, "ResourceAppointmentsView", QString(), TIP_USE_DEFAULT_TEXT );
createResourceAppointmentsGanttView( cat, "ResourceAppointmentsGanttView", QString(), TIP_USE_DEFAULT_TEXT );
createAccountsView( cat, "AccountsView", QString(), TIP_USE_DEFAULT_TEXT );
ct = "Execution";
cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name );
createProjectStatusView( cat, "ProjectStatusView", QString(), TIP_USE_DEFAULT_TEXT );
createPerformanceStatusView( cat, "PerformanceStatusView", QString(), TIP_USE_DEFAULT_TEXT );
createTaskStatusView( cat, "TaskStatusView", QString(), TIP_USE_DEFAULT_TEXT );
createTaskView( cat, "TaskView", QString(), TIP_USE_DEFAULT_TEXT );
createTaskWorkPackageView( cat, "TaskWorkPackageView", QString(), TIP_USE_DEFAULT_TEXT );
ct = "Reports";
cat = m_viewlist->addCategory(ct, defaultCategoryInfo(ct).name);
createReportsGeneratorView(cat, "ReportsGeneratorView", i18n("Generate reports"), TIP_USE_DEFAULT_TEXT);
#ifdef PLAN_USE_KREPORT
// Let user add reports explicitly, we prefer reportsgenerator now
// A little hack to get the user started...
#if 0
ReportView *rv = qobject_cast<ReportView*>( createReportView( cat, "ReportView", i18n( "Task Status Report" ), TIP_USE_DEFAULT_TEXT ) );
if ( rv ) {
QDomDocument doc;
doc.setContent( standardTaskStatusReport() );
rv->loadXML( doc );
}
#endif
#endif
}
}
ViewBase *View::createView( ViewListItem *cat, const QString &type, const QString &tag, const QString &name, const QString &tip, int index )
{
ViewBase *v = 0;
//NOTE: type is the same as classname (so if it is changed...)
if ( type == "CalendarEditor" ) {
v = createCalendarEditor( cat, tag, name, tip, index );
} else if ( type == "AccountsEditor" ) {
v = createAccountsEditor( cat, tag, name, tip, index );
} else if ( type == "ResourceEditor" ) {
v = createResourceEditor( cat, tag, name, tip, index );
} else if ( type == "TaskEditor" ) {
v = createTaskEditor( cat, tag, name, tip, index );
} else if ( type == "DependencyEditor" ) {
v = createDependencyEditor( cat, tag, name, tip, index );
} else if ( type == "PertEditor" ) {
v = createPertEditor( cat, tag, name, tip, index );
} else if ( type == "ScheduleEditor" ) {
v = createScheduleEditor( cat, tag, name, tip, index );
} else if ( type == "ScheduleHandlerView" ) {
v = createScheduleHandler( cat, tag, name, tip, index );
} else if ( type == "ProjectStatusView" ) {
v = createProjectStatusView( cat, tag, name, tip, index );
} else if ( type == "TaskStatusView" ) {
v = createTaskStatusView( cat, tag, name, tip, index );
} else if ( type == "TaskView" ) {
v = createTaskView( cat, tag, name, tip, index );
} else if ( type == "TaskWorkPackageView" ) {
v = createTaskWorkPackageView( cat, tag, name, tip, index );
} else if ( type == "GanttView" ) {
v = createGanttView( cat, tag, name, tip, index );
} else if ( type == "MilestoneGanttView" ) {
v = createMilestoneGanttView( cat, tag, name, tip, index );
} else if ( type == "ResourceAppointmentsView" ) {
v = createResourceAppointmentsView( cat, tag, name, tip, index );
} else if ( type == "ResourceAppointmentsGanttView" ) {
v = createResourceAppointmentsGanttView( cat, tag, name, tip, index );
} else if ( type == "AccountsView" ) {
v = createAccountsView( cat, tag, name, tip, index );
} else if ( type == "PerformanceStatusView" ) {
v = createPerformanceStatusView( cat, tag, name, tip, index );
} else if ( type == "ReportsGeneratorView" ) {
v = createReportsGeneratorView(cat, tag, name, tip, index);
} else if ( type == "ReportView" ) {
#ifdef PLAN_USE_KREPORT
v = createReportView( cat, tag, name, tip, index );
#endif
} else {
warnPlan<<"Unknown viewtype: "<<type;
}
return v;
}
void View::slotUpdateViewInfo( ViewListItem *itm )
{
if ( itm->type() == ViewListItem::ItemType_SubView ) {
itm->setViewInfo( defaultViewInfo( itm->viewType() ) );
} else if ( itm->type() == ViewListItem::ItemType_Category ) {
ViewInfo vi = defaultCategoryInfo( itm->tag() );
itm->setViewInfo( vi );
}
}
ViewInfo View::defaultViewInfo( const QString &type ) const
{
ViewInfo vi;
if ( type == "CalendarEditor" ) {
vi.name = i18n( "Work & Vacation" );
vi.tip = xi18nc( "@info:tooltip", "Edit working- and vacation days for resources" );
} else if ( type == "AccountsEditor" ) {
vi.name = i18n( "Cost Breakdown Structure" );
vi.tip = xi18nc( "@info:tooltip", "Edit cost breakdown structure." );
} else if ( type == "ResourceEditor" ) {
vi.name = i18n( "Resources" );
vi.tip = xi18nc( "@info:tooltip", "Edit resource breakdown structure" );
} else if ( type == "TaskEditor" ) {
vi.name = i18n( "Tasks" );
vi.tip = xi18nc( "@info:tooltip", "Edit work breakdown structure" );
} else if ( type == "DependencyEditor" ) {
vi.name = i18n( "Dependencies (Graphic)" );
vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" );
} else if ( type == "PertEditor" ) {
vi.name = i18n( "Dependencies (List)" );
vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" );
} else if ( type == "ScheduleEditor" ) {
// This view is not used stand-alone atm
vi.name = i18n( "Schedules" );
} else if ( type == "ScheduleHandlerView" ) {
vi.name = i18n( "Schedules" );
vi.tip = xi18nc( "@info:tooltip", "Calculate and analyze project schedules" );
} else if ( type == "ProjectStatusView" ) {
vi.name = i18n( "Project Performance Chart" );
vi.tip = xi18nc( "@info:tooltip", "View project status information" );
} else if ( type == "TaskStatusView" ) {
vi.name = i18n( "Task Status" );
vi.tip = xi18nc( "@info:tooltip", "View task progress information" );
} else if ( type == "TaskView" ) {
vi.name = i18n( "Task Execution" );
vi.tip = xi18nc( "@info:tooltip", "View task execution information" );
} else if ( type == "TaskWorkPackageView" ) {
vi.name = i18n( "Work Package View" );
vi.tip = xi18nc( "@info:tooltip", "View task work package information" );
} else if ( type == "GanttView" ) {
vi.name = i18n( "Gantt" );
vi.tip = xi18nc( "@info:tooltip", "View Gantt chart" );
} else if ( type == "MilestoneGanttView" ) {
vi.name = i18n( "Milestone Gantt" );
vi.tip = xi18nc( "@info:tooltip", "View milestone Gantt chart" );
} else if ( type == "ResourceAppointmentsView" ) {
vi.name = i18n( "Resource Assignments" );
vi.tip = xi18nc( "@info:tooltip", "View resource assignments in a table" );
} else if ( type == "ResourceAppointmentsGanttView" ) {
vi.name = i18n( "Resource Assignments (Gantt)" );
vi.tip = xi18nc( "@info:tooltip", "View resource assignments in Gantt chart" );
} else if ( type == "AccountsView" ) {
vi.name = i18n( "Cost Breakdown" );
vi.tip = xi18nc( "@info:tooltip", "View planned and actual cost" );
} else if ( type == "PerformanceStatusView" ) {
vi.name = i18n( "Tasks Performance Chart" );
vi.tip = xi18nc( "@info:tooltip", "View tasks performance status information" );
} else if ( type == "ReportsGeneratorView" ) {
vi.name = i18n( "Reports Generator" );
vi.tip = xi18nc( "@info:tooltip", "Generate reports" );
} else if ( type == "ReportView" ) {
vi.name = i18n( "Report" );
vi.tip = xi18nc( "@info:tooltip", "View report" );
} else {
warnPlan<<"Unknown viewtype: "<<type;
}
return vi;
}
ViewInfo View::defaultCategoryInfo( const QString &type ) const
{
ViewInfo vi;
if ( type == "Editors" ) {
vi.name = i18n( "Editors" );
} else if ( type == "Views" ) {
vi.name = i18n( "Views" );
} else if ( type == "Execution" ) {
vi.name = i18nc( "Project execution views", "Execution" );
} else if ( type == "Reports" ) {
vi.name = i18n( "Reports" );
}
return vi;
}
void View::slotOpenUrlRequest( HtmlView *v, const QUrl &url )
{
debugPlan<<url;
if ( url.scheme() == QLatin1String("about") ) {
if ( url.url() == QLatin1String("about:close") ) {
int view = m_visitedViews.count() < 2 ? qMin(m_defaultView, m_tab->count()-1) : m_visitedViews.at(m_visitedViews.count() - 2);
debugPlan<<"Prev:"<<view<<m_visitedViews;
m_tab->setCurrentIndex(view);
return;
}
if ( url.url().startsWith( QLatin1String( "about:plan" ) ) ) {
getPart()->aboutPage().generatePage( v->htmlPart(), url );
return;
}
}
if ( url.scheme() == QLatin1String("help") ) {
KHelpClient::invokeHelp( "", url.fileName() );
return;
}
// try to open the url
debugPlan<<url<<"is external, try to open";
new KRun( url, mainWindow() );
}
ViewBase *View::createIntroductionView()
{
HtmlView *v = new HtmlView(getKoPart(), getPart(), m_tab );
v->htmlPart().setJScriptEnabled(false);
v->htmlPart().setJavaEnabled(false);
v->htmlPart().setMetaRefreshEnabled(false);
v->htmlPart().setPluginsEnabled(false);
slotOpenUrlRequest( v, QUrl( "about:plan/main" ) );
connect( v, &HtmlView::openUrlRequest, this, &View::slotOpenUrlRequest );
m_tab->addWidget( v );
return v;
}
ViewBase *View::createResourceAppointmentsGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceAppointmentsGanttView *v = new ResourceAppointmentsGanttView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceAppointmentsGanttView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->setProject( &( getProject() ) );
v->setScheduleManager( currentScheduleManager() );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createResourceAppointmentsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceAppointmentsView *v = new ResourceAppointmentsView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceAppointmentsView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->setProject( &( getProject() ) );
v->setScheduleManager( currentScheduleManager() );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createResourceEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceEditor *resourceeditor = new ResourceEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( resourceeditor );
resourceeditor->setProject( &(getProject()) );
ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceeditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( resourceeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( resourceeditor, &ResourceEditor::deleteObjectList, this, &View::slotDeleteResourceObjects );
connect( resourceeditor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
resourceeditor->updateReadWrite( m_readWrite );
return resourceeditor;
}
ViewBase *View::createTaskEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskEditor *taskeditor = new TaskEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( taskeditor );
m_defaultView = m_tab->count() - 1;
ViewListItem *i = m_viewlist->addView( cat, tag, name, taskeditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
taskeditor->setProject( &(getProject()) );
taskeditor->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), taskeditor, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( taskeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( taskeditor, &TaskEditor::addTask, this, &View::slotAddTask );
connect( taskeditor, &TaskEditor::addMilestone, this, &View::slotAddMilestone );
connect( taskeditor, &TaskEditor::addSubtask, this, &View::slotAddSubTask );
connect( taskeditor, &TaskEditor::addSubMilestone, this, &View::slotAddSubMilestone );
connect( taskeditor, SIGNAL(deleteTaskList(QList<Node*>)), SLOT(slotDeleteTask(QList<Node*>)) );
connect( taskeditor, &TaskEditor::moveTaskUp, this, &View::slotMoveTaskUp );
connect( taskeditor, &TaskEditor::moveTaskDown, this, &View::slotMoveTaskDown );
connect( taskeditor, &TaskEditor::indentTask, this, &View::slotIndentTask );
connect( taskeditor, &TaskEditor::unindentTask, this, &View::slotUnindentTask );
connect(taskeditor, &TaskEditor::saveTaskModule, this, &View::saveTaskModule);
connect(taskeditor, &TaskEditor::removeTaskModule, this, &View::removeTaskModule);
connect(taskeditor, SIGNAL(openDocument(QUrl)), static_cast<KPlato::Part*>(m_partpart), SLOT(openTaskModule(QUrl)));
connect(this, &View::taskModulesChanged, taskeditor, &TaskEditor::setTaskModules);
connect( taskeditor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
taskeditor->updateReadWrite( m_readWrite );
// last:
QStringList modules = KoResourcePaths::findAllResources( "calligraplan_taskmodules", "*.plan", KoResourcePaths::NoDuplicates|KoResourcePaths::Recursive );
debugPlan<<modules;
taskeditor->setTaskModules( modules );
return taskeditor;
}
ViewBase *View::createAccountsEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
AccountsEditor *ae = new AccountsEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( ae );
ViewListItem *i = m_viewlist->addView( cat, tag, name, ae, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "AccountsEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
ae->draw( getProject() );
connect( ae, &ViewBase::guiActivated, this, &View::slotGuiActivated );
ae->updateReadWrite( m_readWrite );
return ae;
}
ViewBase *View::createCalendarEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
CalendarEditor *calendareditor = new CalendarEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( calendareditor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, calendareditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "CalendarEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
calendareditor->draw( getProject() );
connect( calendareditor, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( calendareditor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
calendareditor->updateReadWrite( m_readWrite );
return calendareditor;
}
ViewBase *View::createScheduleHandler( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ScheduleHandlerView *handler = new ScheduleHandlerView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( handler );
ViewListItem *i = m_viewlist->addView( cat, tag, name, handler, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ScheduleHandlerView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( handler->scheduleEditor(), &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager );
connect( handler->scheduleEditor(), &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager );
connect( handler->scheduleEditor(), &ScheduleEditor::moveScheduleManager, this, &View::slotMoveScheduleManager);
connect( handler->scheduleEditor(), &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule );
connect( handler->scheduleEditor(), &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule );
connect( handler, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, &View::currentScheduleManagerChanged, handler, &ScheduleHandlerView::currentScheduleManagerChanged );
connect( handler, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
connect(handler, SIGNAL(editNode(KPlato::Node*)), this, SLOT(slotOpenNode(KPlato::Node*)));
connect(handler, SIGNAL(editResource(KPlato::Resource*)), this, SLOT(slotEditResource(KPlato::Resource*)));
handler->draw( getProject() );
handler->updateReadWrite( m_readWrite );
return handler;
}
ScheduleEditor *View::createScheduleEditor( QWidget *parent )
{
ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), parent );
connect( scheduleeditor, &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager );
connect( scheduleeditor, &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager );
connect( scheduleeditor, &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule );
connect( scheduleeditor, &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule );
scheduleeditor->updateReadWrite( m_readWrite );
return scheduleeditor;
}
ViewBase *View::createScheduleEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( scheduleeditor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, scheduleeditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ScheduleEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
scheduleeditor->setProject( &( getProject() ) );
connect( scheduleeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( scheduleeditor, &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager );
connect( scheduleeditor, &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager );
connect( scheduleeditor, &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule );
connect( scheduleeditor, &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule );
scheduleeditor->updateReadWrite( m_readWrite );
return scheduleeditor;
}
ViewBase *View::createDependencyEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
DependencyEditor *editor = new DependencyEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( editor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, editor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "DependencyEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
editor->draw( getProject() );
connect( editor, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( editor, SIGNAL(addRelation(KPlato::Node*,KPlato::Node*,int)), SLOT(slotAddRelation(KPlato::Node*,KPlato::Node*,int)) );
connect( editor, SIGNAL(modifyRelation(KPlato::Relation*,int)), SLOT(slotModifyRelation(KPlato::Relation*,int)) );
connect( editor, SIGNAL(modifyRelation(KPlato::Relation*)), SLOT(slotModifyRelation(KPlato::Relation*)) );
connect( editor, SIGNAL(editNode(KPlato::Node*)), SLOT(slotOpenNode(KPlato::Node*)) );
connect( editor, &DependencyEditor::addTask, this, &View::slotAddTask );
connect( editor, &DependencyEditor::addMilestone, this, &View::slotAddMilestone );
connect( editor, &DependencyEditor::addSubMilestone, this, &View::slotAddSubMilestone );
connect( editor, &DependencyEditor::addSubtask, this, &View::slotAddSubTask );
connect( editor, SIGNAL(deleteTaskList(QList<Node*>)), SLOT(slotDeleteTask(QList<Node*>)) );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), editor, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( editor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
editor->updateReadWrite( m_readWrite );
editor->setScheduleManager( currentScheduleManager() );
return editor;
}
ViewBase *View::createPertEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
PertEditor *perteditor = new PertEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( perteditor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, perteditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "PertEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
perteditor->draw( getProject() );
connect( perteditor, &ViewBase::guiActivated, this, &View::slotGuiActivated );
m_updatePertEditor = true;
perteditor->updateReadWrite( m_readWrite );
return perteditor;
}
ViewBase *View::createProjectStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ProjectStatusView *v = new ProjectStatusView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ProjectStatusView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
v->updateReadWrite( m_readWrite );
v->setProject( &getProject() );
v->setScheduleManager( currentScheduleManager() );
return v;
}
ViewBase *View::createPerformanceStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
PerformanceStatusView *v = new PerformanceStatusView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "PerformanceStatusView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->updateReadWrite( m_readWrite );
v->setProject( &getProject() );
v->setScheduleManager( currentScheduleManager() );
return v;
}
ViewBase *View::createTaskStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskStatusView *taskstatusview = new TaskStatusView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( taskstatusview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, taskstatusview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskStatusView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( taskstatusview, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), taskstatusview, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( taskstatusview, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
taskstatusview->updateReadWrite( m_readWrite );
taskstatusview->draw( getProject() );
taskstatusview->setScheduleManager( currentScheduleManager() );
return taskstatusview;
}
ViewBase *View::createTaskView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskView *v = new TaskView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->draw( getProject() );
v->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createTaskWorkPackageView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskWorkPackageView *v = new TaskWorkPackageView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskWorkPackageView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->setProject( &getProject() );
v->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
connect( v, &TaskWorkPackageView::mailWorkpackage, this, &View::slotMailWorkpackage );
connect( v, &TaskWorkPackageView::mailWorkpackages, this, &View::slotMailWorkpackages );
connect(v, SIGNAL(checkForWorkPackages()), getPart(), SLOT(checkForWorkPackages()));
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
GanttView *ganttview = new GanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() );
m_tab->addWidget( ganttview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "GanttView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
ganttview->setProject( &( getProject() ) );
ganttview->setScheduleManager( currentScheduleManager() );
connect( ganttview, &ViewBase::guiActivated, this, &View::slotGuiActivated );
/* TODO: Review these
connect( ganttview, SIGNAL(addRelation(KPlato::Node*,KPlato::Node*,int)), SLOT(slotAddRelation(KPlato::Node*,KPlato::Node*,int)) );
connect( ganttview, SIGNAL(modifyRelation(KPlato::Relation*,int)), SLOT(slotModifyRelation(KPlato::Relation*,int)) );
connect( ganttview, SIGNAL(modifyRelation(KPlato::Relation*)), SLOT(slotModifyRelation(KPlato::Relation*)) );
connect( ganttview, SIGNAL(itemDoubleClicked()), SLOT(slotOpenNode()) );
connect( ganttview, SIGNAL(itemRenamed(KPlato::Node*,QString)), this, SLOT(slotRenameNode(KPlato::Node*,QString)) );*/
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), ganttview, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( ganttview, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
ganttview->updateReadWrite( m_readWrite );
return ganttview;
}
ViewBase *View::createMilestoneGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
MilestoneGanttView *ganttview = new MilestoneGanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() );
m_tab->addWidget( ganttview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "MilestoneGanttView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
ganttview->setProject( &( getProject() ) );
ganttview->setScheduleManager( currentScheduleManager() );
connect( ganttview, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), ganttview, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( ganttview, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
ganttview->updateReadWrite( m_readWrite );
return ganttview;
}
ViewBase *View::createAccountsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
AccountsView *accountsview = new AccountsView(getKoPart(), &getProject(), getPart(), m_tab );
m_tab->addWidget( accountsview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, accountsview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "AccountsView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
accountsview->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), accountsview, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( accountsview, &ViewBase::guiActivated, this, &View::slotGuiActivated );
accountsview->updateReadWrite( m_readWrite );
return accountsview;
}
ViewBase *View::createResourceAssignmentView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceAssignmentView *resourceAssignmentView = new ResourceAssignmentView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( resourceAssignmentView );
m_updateResourceAssignmentView = true;
ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceAssignmentView, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceAssignmentView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
resourceAssignmentView->draw( getProject() );
connect( resourceAssignmentView, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( resourceAssignmentView, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
resourceAssignmentView->updateReadWrite( m_readWrite );
return resourceAssignmentView;
}
ViewBase *View::createReportsGeneratorView(ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index)
{
ReportsGeneratorView *v = new ReportsGeneratorView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView(cat, tag, name, v, getPart(), "", index);
ViewInfo vi = defaultViewInfo( "ReportsGeneratorView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->setProject( &getProject() );
connect( this, &View::currentScheduleManagerChanged, v, &ViewBase::setScheduleManager );
connect( this, &View::currentScheduleManagerChanged, v, &ViewBase::slotRefreshView);
v->setScheduleManager( currentScheduleManager() );
connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createReportView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
#ifdef PLAN_USE_KREPORT
ReportView *v = new ReportView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ReportView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->setProject( &getProject() );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(setScheduleManager(KPlato::ScheduleManager*)) );
connect( this, SIGNAL(currentScheduleManagerChanged(KPlato::ScheduleManager*)), v, SLOT(slotRefreshView()));
v->setScheduleManager( currentScheduleManager() );
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
v->updateReadWrite( m_readWrite );
return v;
#else
Q_UNUSED(cat)
Q_UNUSED(tag)
Q_UNUSED(name)
Q_UNUSED(tip)
Q_UNUSED(index)
return 0;
#endif
}
Project& View::getProject() const
{
return getPart() ->getProject();
}
KoPrintJob * View::createPrintJob()
{
KoView *v = qobject_cast<KoView*>( canvas() );
if ( v == 0 ) {
return 0;
}
return v->createPrintJob();
}
ViewBase *View::currentView() const
{
return qobject_cast<ViewBase*>( m_tab->currentWidget() );
}
void View::slotEditCut()
{
ViewBase *v = currentView();
if ( v ) {
v->slotEditCut();
}
}
void View::slotEditCopy()
{
ViewBase *v = currentView();
if ( v ) {
v->slotEditCopy();
}
}
void View::slotEditPaste()
{
ViewBase *v = currentView();
if ( v ) {
v->slotEditPaste();
}
}
void View::slotRefreshView()
{
ViewBase *v = currentView();
if ( v ) {
debugPlan<<v;
v->slotRefreshView();
}
}
void View::slotViewSelector( bool show )
{
//debugPlan;
m_viewlist->setVisible( show );
}
void View::slotInsertResourcesFile(const QString &file, const QUrl &projects)
{
getPart()->insertResourcesFile(QUrl(file), projects);
}
void View::slotInsertFile()
{
InsertFileDialog *dlg = new InsertFileDialog( getProject(), currentTask(), this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotInsertFileFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void View::slotInsertFileFinished( int result )
{
InsertFileDialog *dlg = qobject_cast<InsertFileDialog*>( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == QDialog::Accepted ) {
getPart()->insertFile( dlg->url(), dlg->parentNode(), dlg->afterNode() );
}
dlg->deleteLater();
}
void View::slotProjectEdit()
{
slotOpenNode( &getProject() );
}
void View::slotProjectWorktime()
{
StandardWorktimeDialog *dia = new StandardWorktimeDialog( getProject(), this );
connect(dia, SIGNAL(finished(int)), this, SLOT(slotProjectWorktimeFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotProjectWorktimeFinished( int result )
{
StandardWorktimeDialog *dia = qobject_cast<StandardWorktimeDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * cmd = dia->buildCommand();
if ( cmd ) {
//debugPlan<<"Modifying calendar(s)";
getPart() ->addCommand( cmd ); //also executes
}
}
dia->deleteLater();
}
void View::slotSelectionChanged( ScheduleManager *sm ) {
debugPlan<<sm;
if ( sm == 0 ) {
return;
}
QAction *a = m_scheduleActions.key(sm->expected());
if (!a) {
debugPlan<<sm<<"could not find action for schedule:"<<sm->expected();
return;
}
a->setChecked( true ); // this doesn't trigger QActionGroup
slotViewSchedule( a );
}
QList<QAction*> View::sortedActionList()
{
QMap<QString, QAction*> lst;
const QMap<QAction*, Schedule*> map = m_scheduleActions;
QMap<QAction*, Schedule*>::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
lst.insert(it.key()->objectName(), it.key());
}
return lst.values();
}
void View::slotScheduleRemoved( const MainSchedule *sch )
{
debugPlan<<sch<<sch->name();
QAction *a = 0;
QAction *checked = m_scheduleActionGroup->checkedAction();
QMapIterator<QAction*, Schedule*> i( m_scheduleActions );
while (i.hasNext()) {
i.next();
if ( i.value() == sch ) {
a = i.key();
break;
}
}
if ( a ) {
unplugActionList( "view_schedule_list" );
delete a;
plugActionList( "view_schedule_list", sortedActionList() );
if ( checked && checked != a ) {
checked->setChecked( true );
} else if ( ! m_scheduleActions.isEmpty() ) {
m_scheduleActions.firstKey()->setChecked( true );
}
}
slotViewSchedule( m_scheduleActionGroup->checkedAction() );
}
void View::slotScheduleAdded( const MainSchedule *sch )
{
if ( sch->type() != Schedule::Expected ) {
return; // Only view expected
}
MainSchedule *s = const_cast<MainSchedule*>( sch );
// debugPlan<<sch->name()<<" deleted="<<sch->isDeleted()<<"scheduled="<<sch->isScheduled();
QAction *checked = m_scheduleActionGroup->checkedAction();
if ( ! sch->isDeleted() && sch->isScheduled() ) {
unplugActionList( "view_schedule_list" );
QAction *act = addScheduleAction( s );
plugActionList( "view_schedule_list", sortedActionList() );
if ( checked ) {
checked->setChecked( true );
} else if ( act ) {
act->setChecked( true );
} else if ( ! m_scheduleActions.isEmpty() ) {
m_scheduleActions.firstKey()->setChecked( true );
}
}
slotViewSchedule( m_scheduleActionGroup->checkedAction() );
}
void View::slotScheduleChanged( MainSchedule *sch )
{
// debugPlan<<sch->name()<<" deleted="<<sch->isDeleted()<<"scheduled="<<sch->isScheduled();
if ( sch->isDeleted() || ! sch->isScheduled() ) {
slotScheduleRemoved( sch );
return;
}
if (QAction *a = m_scheduleActions.key(sch)) {
slotScheduleRemoved( sch ); // hmmm, how to avoid this?
}
slotScheduleAdded( sch );
}
QAction *View::addScheduleAction( Schedule *sch )
{
QAction *act = 0;
if ( ! sch->isDeleted() && sch->isScheduled() ) {
QString n = sch->name();
act = new KToggleAction( n, this);
actionCollection()->addAction(n, act );
m_scheduleActions.insert( act, sch );
m_scheduleActionGroup->addAction( act );
//debugPlan<<"Add:"<<n;
connect( act, &QObject::destroyed, this, &View::slotActionDestroyed );
}
return act;
}
void View::slotViewScheduleManager(ScheduleManager *sm)
{
QApplication::setOverrideCursor( Qt::WaitCursor );
setLabel(sm);
emit currentScheduleManagerChanged(sm);
QApplication::restoreOverrideCursor();
}
void View::slotViewSchedule( QAction *act )
{
//debugPlan<<act;
ScheduleManager *sm = 0;
if ( act != 0 ) {
Schedule *sch = m_scheduleActions.value( act, 0 );
sm = sch->manager();
}
setLabel( 0 );
slotViewScheduleManager(sm);
}
void View::slotActionDestroyed( QObject *o )
{
//debugPlan<<o->name();
m_scheduleActions.remove( static_cast<QAction*>( o ) );
// slotViewSchedule( m_scheduleActionGroup->checkedAction() );
}
void View::slotPlugScheduleActions()
{
//debugPlan<<activeScheduleId();
long id = activeScheduleId();
unplugActionList( "view_schedule_list" );
const QMap<QAction*, Schedule*> map = m_scheduleActions;
QMap<QAction*, Schedule*>::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
m_scheduleActionGroup->removeAction(it.key());
delete it.key();
}
m_scheduleActions.clear();
QAction *ca = 0;
foreach( ScheduleManager *sm, getProject().allScheduleManagers() ) {
Schedule *sch = sm->expected();
if ( sch == 0 ) {
continue;
}
QAction *act = addScheduleAction( sch );
if ( act && id == sch->id() ) {
ca = act;
}
}
plugActionList( "view_schedule_list", sortedActionList() );
if ( ca == 0 && m_scheduleActionGroup->actions().count() > 0 ) {
ca = m_scheduleActionGroup->actions().constFirst();
}
if ( ca ) {
ca->setChecked( true );
}
slotViewSchedule( ca );
}
void View::slotProjectCalculated( ScheduleManager *sm )
{
// we only get here if current schedule was calculated
if ( sm->isScheduled() ) {
slotSelectionChanged( sm );
}
}
void View::slotCalculateSchedule( Project *project, ScheduleManager *sm )
{
if ( project == 0 || sm == 0 ) {
return;
}
if ( sm->parentManager() && ! sm->parentManager()->isScheduled() ) {
// the parent must be scheduled
return;
}
if ( sm == currentScheduleManager() ) {
connect( project, &Project::projectCalculated, this, &View::slotProjectCalculated );
}
CalculateScheduleCmd *cmd = new CalculateScheduleCmd( *project, sm, kundo2_i18nc("@info:status 1=schedule name", "Calculate %1", sm->name() ) );
getPart() ->addCommand( cmd );
slotUpdate();
}
void View::slotRemoveCommands()
{
while ( ! m_undocommands.isEmpty() ) {
m_undocommands.last()->undo();
delete m_undocommands.takeLast();
}
}
void View::slotBaselineSchedule( Project *project, ScheduleManager *sm )
{
if ( project == 0 || sm == 0 ) {
return;
}
if ( ! sm->isBaselined() && project->isBaselined() ) {
KMessageBox::sorry( this, i18n( "Cannot baseline. The project is already baselined." ) );
return;
}
KUndo2Command *cmd;
if ( sm->isBaselined() ) {
KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "This schedule is baselined. Do you want to remove the baseline?" ) );
if ( res == KMessageBox::Cancel ) {
return;
}
cmd = new ResetBaselineScheduleCmd( *sm, kundo2_i18n( "Reset baseline %1", sm->name() ) );
} else {
cmd = new BaselineScheduleCmd( *sm, kundo2_i18n( "Baseline %1", sm->name() ) );
}
getPart() ->addCommand( cmd );
}
void View::slotAddScheduleManager( Project *project )
{
if ( project == 0 ) {
return;
}
ScheduleManager *sm = project->createScheduleManager();
AddScheduleManagerCmd *cmd = new AddScheduleManagerCmd( *project, sm, -1, kundo2_i18n( "Add schedule %1", sm->name() ) );
getPart() ->addCommand( cmd );
}
void View::slotDeleteScheduleManager( Project *project, ScheduleManager *sm )
{
if ( project == 0 || sm == 0) {
return;
}
DeleteScheduleManagerCmd *cmd = new DeleteScheduleManagerCmd( *project, sm, kundo2_i18n( "Delete schedule %1", sm->name() ) );
getPart() ->addCommand( cmd );
}
void View::slotMoveScheduleManager( ScheduleManager *sm, ScheduleManager *parent, int index )
{
if ( sm == 0 ) {
return;
}
MoveScheduleManagerCmd *cmd = new MoveScheduleManagerCmd( sm, parent, index, kundo2_i18n( "Move schedule %1", sm->name() ) );
getPart() ->addCommand( cmd );
}
void View::slotAddSubTask()
{
Task * node = getProject().createTask( getPart() ->config().taskDefaults() );
SubTaskAddDialog *dia = new SubTaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this );
connect(dia, &QDialog::finished, this, &View::slotAddSubTaskFinished);
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotAddSubTaskFinished( int result )
{
SubTaskAddDialog *dia = qobject_cast<SubTaskAddDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command *m = dia->buildCommand();
getPart() ->addCommand( m ); // add task to project
}
dia->deleteLater();
}
void View::slotAddTask()
{
Task * node = getProject().createTask( getPart() ->config().taskDefaults() );
TaskAddDialog *dia = new TaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this );
connect(dia, &QDialog::finished, this, &View::slotAddTaskFinished);
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotAddTaskFinished( int result )
{
TaskAddDialog *dia = qobject_cast<TaskAddDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command *m = dia->buildCommand();
getPart() ->addCommand( m ); // add task to project
}
dia->deleteLater();
}
void View::slotAddMilestone()
{
Task * node = getProject().createTask();
node->estimate() ->clear();
TaskAddDialog *dia = new TaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this );
connect(dia, &QDialog::finished, this, &View::slotAddMilestoneFinished);
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotAddMilestoneFinished( int result )
{
TaskAddDialog *dia = qobject_cast<TaskAddDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
MacroCommand *c = new MacroCommand( kundo2_i18n( "Add milestone" ) );
c->addCommand( dia->buildCommand() );
getPart() ->addCommand( c ); // add task to project
}
dia->deleteLater();
}
void View::slotAddSubMilestone()
{
Task * node = getProject().createTask();
node->estimate() ->clear();
SubTaskAddDialog *dia = new SubTaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this );
connect(dia, &QDialog::finished, this, &View::slotAddSubMilestoneFinished);
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotAddSubMilestoneFinished( int result )
{
SubTaskAddDialog *dia = qobject_cast<SubTaskAddDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
MacroCommand *c = new MacroCommand( kundo2_i18n( "Add sub-milestone" ) );
c->addCommand( dia->buildCommand() );
getPart() ->addCommand( c ); // add task to project
}
dia->deleteLater();
}
void View::slotDefineWBS()
{
//debugPlan;
Project &p = getProject();
WBSDefinitionDialog *dia = new WBSDefinitionDialog( p, p.wbsDefinition(), this );
connect(dia, SIGNAL(finished(int)), SLOT(slotDefineWBSFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotDefineWBSFinished( int result )
{
//debugPlan;
WBSDefinitionDialog *dia = qobject_cast<WBSDefinitionDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted ) {
KUndo2Command *cmd = dia->buildCommand();
if ( cmd ) {
getPart()->addCommand( cmd );
}
}
dia->deleteLater();
}
void View::slotConfigure()
{
//debugPlan;
if( KConfigDialog::showDialog("Plan Settings") ) {
return;
}
ConfigDialog *dialog = new ConfigDialog( this, "Plan Settings", KPlatoSettings::self() );
dialog->addPage(new ConfigProjectPanel(), i18n("Project Defaults"), koIconName("calligraplan") );
dialog->addPage(new ConfigWorkVacationPanel(), i18n("Work & Vacation"), koIconName("view-calendar") );
dialog->addPage(new TaskDefaultPanel(), i18n("Task Defaults"), koIconName("view-task") );
dialog->addPage(new ColorsConfigPanel(), i18n("Task Colors"), koIconName("fill-color") );
dialog->addPage(new WorkPackageConfigPanel(), i18n("Work Package"), koIconName("calligraplanwork") );
dialog->show();
}
void View::slotIntroduction()
{
m_tab->setCurrentIndex(0);
}
Calendar *View::currentCalendar()
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return 0;
}
return v->currentCalendar();
}
Node *View::currentNode() const
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return 0;
}
Node * task = v->currentNode();
if ( 0 != task ) {
return task;
}
return &( getProject() );
}
Task *View::currentTask() const
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return 0;
}
Node * task = v->currentNode();
if ( task ) {
return dynamic_cast<Task*>( task );
}
return 0;
}
Resource *View::currentResource()
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return 0;
}
return v->currentResource();
}
ResourceGroup *View::currentResourceGroup()
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return 0;
}
return v->currentResourceGroup();
}
void View::slotOpenNode()
{
//debugPlan;
Node * node = currentNode();
slotOpenNode( node );
}
void View::slotOpenNode( Node *node )
{
//debugPlan;
if ( !node )
return ;
switch ( node->type() ) {
case Node::Type_Project: {
Project * project = static_cast<Project *>( node );
MainProjectDialog *dia = new MainProjectDialog( *project, this );
connect(dia, &MainProjectDialog::dialogFinished, this, &View::slotProjectEditFinished);
connect(dia, &MainProjectDialog::sigLoadSharedResources, this, &View::slotInsertResourcesFile);
connect(dia, &MainProjectDialog::loadResourceAssignments, getPart(), &MainDocument::loadResourceAssignments);
connect(dia, &MainProjectDialog::clearResourceAssignments, getPart(), &MainDocument::clearResourceAssignments);
dia->show();
dia->raise();
dia->activateWindow();
break;
}
case Node::Type_Subproject:
//TODO
break;
case Node::Type_Task: {
Task *task = static_cast<Task *>( node );
TaskDialog *dia = new TaskDialog( getProject(), *task, getProject().accounts(), this );
connect(dia, &QDialog::finished, this, &View::slotTaskEditFinished);
dia->show();
dia->raise();
dia->activateWindow();
break;
}
case Node::Type_Milestone: {
// Use the normal task dialog for now.
// Maybe milestone should have it's own dialog, but we need to be able to
// enter a duration in case we accidentally set a tasks duration to zero
// and hence, create a milestone
Task *task = static_cast<Task *>( node );
TaskDialog *dia = new TaskDialog( getProject(), *task, getProject().accounts(), this );
connect(dia, &QDialog::finished, this, &View::slotTaskEditFinished);
dia->show();
dia->raise();
dia->activateWindow();
break;
}
case Node::Type_Summarytask: {
Task *task = dynamic_cast<Task *>( node );
Q_ASSERT( task );
SummaryTaskDialog *dia = new SummaryTaskDialog( *task, this );
connect(dia, SIGNAL(finished(int)), SLOT(slotSummaryTaskEditFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
break;
}
default:
break; // avoid warnings
}
}
void View::slotProjectEditFinished( int result )
{
MainProjectDialog *dia = qobject_cast<MainProjectDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * cmd = dia->buildCommand();
if ( cmd ) {
getPart() ->addCommand( cmd );
}
}
dia->deleteLater();
}
void View::slotTaskEditFinished( int result )
{
TaskDialog *dia = qobject_cast<TaskDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * cmd = dia->buildCommand();
if ( cmd ) {
getPart() ->addCommand( cmd );
}
}
dia->deleteLater();
}
void View::slotSummaryTaskEditFinished( int result )
{
SummaryTaskDialog *dia = qobject_cast<SummaryTaskDialog*>( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * cmd = dia->buildCommand();
if ( cmd ) {
getPart() ->addCommand( cmd );
}
}
dia->deleteLater();
}
ScheduleManager *View::currentScheduleManager() const
{
Schedule *s = m_scheduleActions.value( m_scheduleActionGroup->checkedAction() );
return s == 0 ? 0 : s->manager();
}
long View::activeScheduleId() const
{
Schedule *s = m_scheduleActions.value( m_scheduleActionGroup->checkedAction() );
return s == 0 ? -1 : s->id();
}
void View::setActiveSchedule( long id )
{
if ( id != -1 ) {
QMap<QAction*, Schedule*>::const_iterator it = m_scheduleActions.constBegin();
for (; it != m_scheduleActions.constEnd(); ++it ) {
if ( it.value()->id() == id ) {
it.key()->setChecked( true );
slotViewSchedule( it.key() ); // signal not emitted from group, so trigger it here
break;
}
}
}
}
void View::slotTaskProgress()
{
//debugPlan;
Node * node = currentNode();
if ( !node )
return ;
switch ( node->type() ) {
case Node::Type_Project: {
break;
}
case Node::Type_Subproject:
//TODO
break;
case Node::Type_Task: {
Task *task = dynamic_cast<Task *>( node );
Q_ASSERT( task );
TaskProgressDialog *dia = new TaskProgressDialog( *task, currentScheduleManager(), getProject().standardWorktime(), this );
connect(dia, SIGNAL(finished(int)), SLOT(slotTaskProgressFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
break;
}
case Node::Type_Milestone: {
Task *task = dynamic_cast<Task *>( node );
Q_ASSERT( task );
MilestoneProgressDialog *dia = new MilestoneProgressDialog( *task, this );
connect(dia, SIGNAL(finished(int)), SLOT(slotMilestoneProgressFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
break;
}
case Node::Type_Summarytask: {
// TODO
break;
}
default:
break; // avoid warnings
}
}
void View::slotTaskProgressFinished( int result )
{
TaskProgressDialog *dia = qobject_cast<TaskProgressDialog*>(sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * m = dia->buildCommand();
if ( m ) {
getPart() ->addCommand( m );
}
}
dia->deleteLater();
}
void View::slotMilestoneProgressFinished( int result )
{
MilestoneProgressDialog *dia = qobject_cast<MilestoneProgressDialog*>(sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * m = dia->buildCommand();
if ( m ) {
getPart() ->addCommand( m );
}
}
dia->deleteLater();
}
void View::slotTaskDescription()
{
//debugPlan;
Node * node = currentNode();
if ( !node )
return ;
switch ( node->type() ) {
case Node::Type_Project: {
break;
}
case Node::Type_Subproject:
//TODO
break;
case Node::Type_Task:
case Node::Type_Milestone:
case Node::Type_Summarytask: {
Task *task = dynamic_cast<Task *>( node );
Q_ASSERT( task );
TaskDescriptionDialog *dia = new TaskDescriptionDialog( *task, this );
connect(dia, SIGNAL(finished(int)), SLOT(slotTaskDescriptionFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
break;
}
default:
break; // avoid warnings
}
}
void View::slotTaskDescriptionFinished( int result )
{
TaskDescriptionDialog *dia = qobject_cast<TaskDescriptionDialog*>(sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * m = dia->buildCommand();
if ( m ) {
getPart() ->addCommand( m );
}
}
dia->deleteLater();
}
void View::slotDeleteTask( QList<Node*> lst )
{
//debugPlan;
foreach ( Node *n, lst ) {
if ( n->isScheduled() ) {
KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A task that has been scheduled will be deleted. This will invalidate the schedule." ) );
if ( res == KMessageBox::Cancel ) {
return;
}
break;
}
}
if ( lst.count() == 1 ) {
getPart()->addCommand( new NodeDeleteCmd( lst.takeFirst(), kundo2_i18n( "Delete task" ) ) );
return;
}
int num = 0;
MacroCommand *cmd = new MacroCommand( kundo2_i18np( "Delete task", "Delete tasks", lst.count() ) );
while ( !lst.isEmpty() ) {
Node *node = lst.takeFirst();
if ( node == 0 || node->parentNode() == 0 ) {
debugPlan << ( node ?"Task is main project" :"No current task" );
continue;
}
bool del = true;
foreach ( Node *n, lst ) {
if ( node->isChildOf( n ) ) {
del = false; // node is going to be deleted when we delete n
break;
}
}
if ( del ) {
//debugPlan<<num<<": delete:"<<node->name();
cmd->addCommand( new NodeDeleteCmd( node, kundo2_i18n( "Delete task" ) ) );
num++;
}
}
if ( num > 0 ) {
getPart()->addCommand( cmd );
} else {
delete cmd;
}
}
void View::slotDeleteTask( Node *node )
{
//debugPlan;
if ( node == 0 || node->parentNode() == 0 ) {
debugPlan << ( node ?"Task is main project" :"No current task" );
return ;
}
if ( node->isScheduled() ) {
KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "This task has been scheduled. This will invalidate the schedule." ) );
if ( res == KMessageBox::Cancel ) {
return;
}
}
NodeDeleteCmd *cmd = new NodeDeleteCmd( node, kundo2_i18n( "Delete task" ) );
getPart() ->addCommand( cmd );
}
void View::slotDeleteTask()
{
//debugPlan;
return slotDeleteTask( currentNode() );
}
void View::slotIndentTask()
{
//debugPlan;
Node * node = currentNode();
if ( node == 0 || node->parentNode() == 0 ) {
debugPlan << ( node ?"Task is main project" :"No current task" );
return ;
}
if ( getProject().canIndentTask( node ) ) {
NodeIndentCmd * cmd = new NodeIndentCmd( *node, kundo2_i18n( "Indent task" ) );
getPart() ->addCommand( cmd );
}
}
void View::slotUnindentTask()
{
//debugPlan;
Node * node = currentNode();
if ( node == 0 || node->parentNode() == 0 ) {
debugPlan << ( node ?"Task is main project" :"No current task" );
return ;
}
if ( getProject().canUnindentTask( node ) ) {
NodeUnindentCmd * cmd = new NodeUnindentCmd( *node, kundo2_i18n( "Unindent task" ) );
getPart() ->addCommand( cmd );
}
}
void View::slotMoveTaskUp()
{
//debugPlan;
Node * task = currentNode();
if ( 0 == task ) {
// is always != 0. At least we would get the Project, but you never know who might change that
// so better be careful
errorPlan << "No current task" << endl;
return ;
}
if ( Node::Type_Project == task->type() ) {
debugPlan <<"The root node cannot be moved up";
return ;
}
if ( getProject().canMoveTaskUp( task ) ) {
NodeMoveUpCmd * cmd = new NodeMoveUpCmd( *task, kundo2_i18n( "Move task up" ) );
getPart() ->addCommand( cmd );
}
}
void View::slotMoveTaskDown()
{
//debugPlan;
Node * task = currentNode();
if ( 0 == task ) {
// is always != 0. At least we would get the Project, but you never know who might change that
// so better be careful
return ;
}
if ( Node::Type_Project == task->type() ) {
debugPlan <<"The root node cannot be moved down";
return ;
}
if ( getProject().canMoveTaskDown( task ) ) {
NodeMoveDownCmd * cmd = new NodeMoveDownCmd( *task, kundo2_i18n( "Move task down" ) );
getPart() ->addCommand( cmd );
}
}
void View::slotAddRelation( Node *par, Node *child )
{
//debugPlan;
Relation * rel = new Relation( par, child );
AddRelationDialog *dia = new AddRelationDialog( getProject(), rel, this );
connect(dia, SIGNAL(finished(int)), SLOT(slotAddRelationFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotAddRelationFinished( int result )
{
AddRelationDialog *dia = qobject_cast<AddRelationDialog*>(sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * m = dia->buildCommand();
if ( m ) {
getPart() ->addCommand( m );
}
}
dia->deleteLater();
}
void View::slotAddRelation( Node *par, Node *child, int linkType )
{
//debugPlan;
if ( linkType == Relation::FinishStart ||
linkType == Relation::StartStart ||
linkType == Relation::FinishFinish ) {
Relation * rel = new Relation( par, child, static_cast<Relation::Type>( linkType ) );
getPart() ->addCommand( new AddRelationCmd( getProject(), rel, kundo2_i18n( "Add task dependency" ) ) );
} else {
slotAddRelation( par, child );
}
}
void View::slotModifyRelation( Relation *rel )
{
//debugPlan;
ModifyRelationDialog *dia = new ModifyRelationDialog( getProject(), rel, this );
connect(dia, SIGNAL(finished(int)), SLOT(slotModifyRelationFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotModifyRelationFinished( int result )
{
ModifyRelationDialog *dia = qobject_cast<ModifyRelationDialog*>( sender() );
if ( dia == 0 ) {
return ;
}
if ( result == QDialog::Accepted) {
KUndo2Command *cmd = dia->buildCommand();
if ( cmd ) {
getPart() ->addCommand( cmd );
}
}
dia->deleteLater();
}
void View::slotModifyRelation( Relation *rel, int linkType )
{
//debugPlan;
if ( linkType == Relation::FinishStart ||
linkType == Relation::StartStart ||
linkType == Relation::FinishFinish ) {
getPart() ->addCommand( new ModifyRelationTypeCmd( rel, static_cast<Relation::Type>( linkType ) ) );
} else {
slotModifyRelation( rel );
}
}
void View::slotModifyRelation()
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return;
}
Relation *rel = v->currentRelation();
if ( rel ) {
slotModifyRelation( rel );
}
}
void View::slotDeleteRelation()
{
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v == 0 ) {
return;
}
Relation *rel = v->currentRelation();
if ( rel ) {
getPart()->addCommand( new DeleteRelationCmd( getProject(), rel, kundo2_i18n( "Delete task dependency" ) ) );
}
}
void View::slotEditResource()
{
//debugPlan;
slotEditResource( currentResource() );
}
void View::slotEditResource( Resource *resource )
{
if ( resource == 0 ) {
return ;
}
ResourceDialog *dia = new ResourceDialog( getProject(), resource, this );
connect(dia, SIGNAL(finished(int)), SLOT(slotEditResourceFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotEditResourceFinished( int result )
{
//debugPlan;
ResourceDialog *dia = qobject_cast<ResourceDialog*>( sender() );
if ( dia == 0 ) {
return ;
}
if ( result == QDialog::Accepted) {
KUndo2Command * cmd = dia->buildCommand();
if ( cmd )
getPart() ->addCommand( cmd );
}
dia->deleteLater();
}
void View::slotDeleteResource( Resource *resource )
{
getPart()->addCommand( new RemoveResourceCmd( resource->parentGroup(), resource, kundo2_i18n( "Delete resource" ) ) );
}
void View::slotDeleteResourceGroup( ResourceGroup *group )
{
getPart()->addCommand( new RemoveResourceGroupCmd( group->project(), group, kundo2_i18n( "Delete resourcegroup" ) ) );
}
void View::slotDeleteResourceObjects( QObjectList lst )
{
//debugPlan;
foreach ( QObject *o, lst ) {
Resource *r = qobject_cast<Resource*>( o );
if ( r && r->isScheduled() ) {
KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A resource that has been scheduled will be deleted. This will invalidate the schedule." ) );
if ( res == KMessageBox::Cancel ) {
return;
}
break;
}
ResourceGroup *g = qobject_cast<ResourceGroup*>( o );
if ( g && g->isScheduled() ) {
KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A resource that has been scheduled will be deleted. This will invalidate the schedule." ) );
if ( res == KMessageBox::Cancel ) {
return;
}
break;
}
}
if ( lst.count() == 1 ) {
Resource *r = qobject_cast<Resource*>( lst.first() );
if ( r ) {
slotDeleteResource( r );
} else {
ResourceGroup *g = qobject_cast<ResourceGroup*>( lst.first() );
if ( g ) {
slotDeleteResourceGroup( g );
}
}
return;
}
// int num = 0;
MacroCommand *cmd = 0, *rc = 0, *gc = 0;
foreach ( QObject *o, lst ) {
Resource *r = qobject_cast<Resource*>( o );
if ( r ) {
if ( rc == 0 ) rc = new MacroCommand( KUndo2MagicString() );
rc->addCommand( new RemoveResourceCmd( r->parentGroup(), r ) );
continue;
}
ResourceGroup *g = qobject_cast<ResourceGroup*>( o );
if ( g ) {
if ( gc == 0 ) gc = new MacroCommand( KUndo2MagicString() );
gc->addCommand( new RemoveResourceGroupCmd( g->project(), g ) );
}
}
if ( rc || gc ) {
KUndo2MagicString s;
if ( rc && gc ) {
s = kundo2_i18n( "Delete resourcegroups and resources" );
} else if ( rc ) {
s = kundo2_i18np( "Delete resource", "Delete resources", lst.count() );
} else {
s = kundo2_i18np( "Delete resourcegroup", "Delete resourcegroups", lst.count() );
}
cmd = new MacroCommand( s );
}
if ( rc )
cmd->addCommand( rc );
if ( gc )
cmd->addCommand( gc );
if ( cmd )
getPart()->addCommand( cmd );
}
void View::updateReadWrite( bool readwrite )
{
m_readWrite = readwrite;
m_viewlist->setReadWrite( readwrite );
}
MainDocument *View::getPart() const
{
return ( MainDocument * ) koDocument();
}
KoPart *View::getKoPart() const
{
return m_partpart;
}
void View::slotConnectNode()
{
//debugPlan;
/* NodeItem *curr = ganttview->currentItem();
if (curr) {
debugPlan<<"node="<<curr->getNode().name();
}*/
}
QMenu * View::popupMenu( const QString& name )
{
//debugPlan;
if ( factory() ) {
return ( ( QMenu* ) factory() ->container( name, this ) );
}
debugPlan<<"No factory";
return 0L;
}
void View::slotUpdate()
{
//debugPlan<<"calculate="<<calculate;
// m_updateResourceview = true;
m_updateResourceAssignmentView = true;
m_updatePertEditor = true;
updateView( m_tab->currentWidget() );
}
void View::slotGuiActivated( ViewBase *view, bool activate )
{
//FIXME: Avoid unplug if possible, it flashes the gui
// always unplug, in case they already are plugged
foreach( const QString &name, view->actionListNames() ) {
unplugActionList( name );
}
if ( activate ) {
foreach( const QString &name, view->actionListNames() ) {
plugActionList( name, view->actionList( name ) );
}
foreach ( DockWidget *ds, view->dockers() ) {
m_dockers.append( ds );
ds->activate( mainWindow() );
}
if (!m_dockers.isEmpty()) {debugPlan<<"Added dockers:"<<view<<m_dockers;}
} else {
if (!m_dockers.isEmpty()) {debugPlan<<"Remove dockers:"<<view<<m_dockers;}
while ( ! m_dockers.isEmpty() ) {
m_dockers.takeLast()->deactivate( mainWindow() );
}
}
}
void View::guiActivateEvent( bool activated )
{
if ( activated ) {
// plug my own actionlists, they may be gone
slotPlugScheduleActions();
}
// propagate to sub-view
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v ) {
v->setGuiActive( activated );
}
}
void View::slotViewListItemRemoved( ViewListItem *item )
{
getPart()->removeViewListItem( this, item );
}
void View::removeViewListItem( const ViewListItem *item )
{
if ( item == 0 ) {
return;
}
ViewListItem *itm = m_viewlist->findItem( item->tag() );
if ( itm == 0 ) {
return;
}
m_viewlist->removeViewListItem( itm );
return;
}
void View::slotViewListItemInserted( ViewListItem *item, ViewListItem *parent, int index )
{
getPart()->insertViewListItem( this, item, parent, index );
}
void View::addViewListItem( const ViewListItem *item, const ViewListItem *parent, int index )
{
if ( item == 0 ) {
return;
}
if ( parent == 0 ) {
if ( item->type() != ViewListItem::ItemType_Category ) {
return;
}
m_viewlist->blockSignals( true );
ViewListItem *cat = m_viewlist->addCategory( item->tag(), item->text( 0 ) );
cat->setToolTip( 0, item->toolTip( 0 ) );
m_viewlist->blockSignals( false );
return;
}
ViewListItem *cat = m_viewlist->findCategory( parent->tag() );
if ( cat == 0 ) {
return;
}
m_viewlist->blockSignals( true );
createView( cat, item->viewType(), item->tag(), item->text( 0 ), item->toolTip( 0 ), index );
m_viewlist->blockSignals( false );
}
void View::createReportView(const QDomDocument &doc)
{
#ifdef PLAN_USE_KREPORT
QPointer<ViewListReportsDialog> vd = new ViewListReportsDialog( this, *m_viewlist, doc, this );
vd->exec(); // FIXME make non-crash
delete vd;
#else
Q_UNUSED(doc)
#endif
}
void View::slotOpenReportFile()
{
#ifdef PLAN_USE_KREPORT
QFileDialog *dlg = new QFileDialog(this);
connect(dlg, SIGNAL(finished(int)), SLOT(slotOpenReportFileFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
#endif
}
void View::slotOpenReportFileFinished( int result )
{
#ifdef PLAN_USE_KREPORT
QFileDialog *fdlg = qobject_cast<QFileDialog*>( sender() );
if ( fdlg == 0 || result != QDialog::Accepted ) {
return;
}
QString fn = fdlg->selectedFiles().value(0);
if ( fn.isEmpty() ) {
return;
}
QFile file( fn );
if ( ! file.open( QIODevice::ReadOnly | QIODevice::Text ) ) {
KMessageBox::sorry( this, xi18nc( "@info", "Cannot open file:<br/><filename>%1</filename>", fn ) );
return;
}
QDomDocument doc;
doc.setContent( &file );
createReportView(doc);
#else
Q_UNUSED(result)
#endif
}
void View::slotReportDesignFinished( int /*result */)
{
#ifdef PLAN_USE_KREPORT
if ( sender() ) {
sender()->deleteLater();
}
#endif
}
void View::slotCreateView()
{
ViewListDialog *dlg = new ViewListDialog( this, *m_viewlist, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotCreateViewFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void View::slotCreateViewFinished( int )
{
if ( sender() ) {
sender()->deleteLater();
}
}
void View::slotViewActivated( ViewListItem *item, ViewListItem *prev )
{
QApplication::setOverrideCursor( Qt::WaitCursor );
if ( prev && prev->type() == ViewListItem::ItemType_Category && m_viewlist->previousViewItem() ) {
// A view is shown anyway...
ViewBase *v = qobject_cast<ViewBase*>( m_viewlist->previousViewItem()->view() );
if ( v ) {
v->setGuiActive( false );
}
} else if ( prev && prev->type() == ViewListItem::ItemType_SubView ) {
ViewBase *v = qobject_cast<ViewBase*>( prev->view() );
if ( v ) {
v->setGuiActive( false );
}
}
if ( item && item->type() == ViewListItem::ItemType_SubView ) {
//debugPlan<<"Activate:"<<item;
m_tab->setCurrentWidget( item->view() );
// Add sub-view specific gui
ViewBase *v = dynamic_cast<ViewBase*>( m_tab->currentWidget() );
if ( v ) {
v->setGuiActive( true );
}
}
QApplication::restoreOverrideCursor();
}
QWidget *View::canvas() const
{
return m_tab->currentWidget();//KoView::canvas();
}
KoPageLayout View::pageLayout() const
{
return currentView()->pageLayout();
}
void View::setPageLayout(const KoPageLayout &pageLayout)
{
currentView()->setPageLayout(pageLayout);
}
QPrintDialog *View::createPrintDialog( KoPrintJob *printJob, QWidget *parent )
{
debugPlan<<printJob;
KoPrintingDialog *job = dynamic_cast<KoPrintingDialog*>( printJob );
if ( ! job ) {
return 0;
}
QPrintDialog *dia = KoView::createPrintDialog( job, parent );
PrintingDialog *j = dynamic_cast<PrintingDialog*>( job );
if ( j ) {
new PrintingControlPrivate( j, dia );
}
return dia;
}
void View::slotCurrentChanged( int view )
{
m_visitedViews << view;
ViewListItem *item = m_viewlist->findItem( qobject_cast<ViewBase*>( m_tab->currentWidget() ) );
m_viewlist->setCurrentItem( item );
}
void View::slotSelectDefaultView()
{
m_tab->setCurrentIndex(qMin(m_defaultView, m_tab->count()-1));
}
void View::updateView( QWidget * )
{
QApplication::setOverrideCursor( Qt::WaitCursor );
//setScheduleActionsEnabled();
QWidget *widget2;
widget2 = m_viewlist->findView( "ResourceAssignmentView" );
if ( widget2 && m_updateResourceAssignmentView )
static_cast<ViewBase*>( widget2 ) ->draw( getProject() );
m_updateResourceAssignmentView = false;
QApplication::restoreOverrideCursor();
}
void View::slotRenameNode( Node *node, const QString& name )
{
//debugPlan<<name;
if ( node ) {
KUndo2MagicString s = kundo2_i18n( "Modify name" );
switch( node->type() ) {
case Node::Type_Task: s = kundo2_i18n( "Modify task name" ); break;
case Node::Type_Milestone: s = kundo2_i18n( "Modify milestone name" ); break;
case Node::Type_Summarytask: s = kundo2_i18n( "Modify summarytask name" ); break;
case Node::Type_Project: s = kundo2_i18n( "Modify project name" ); break;
}
NodeModifyNameCmd * cmd = new NodeModifyNameCmd( *node, name, s );
getPart() ->addCommand( cmd );
}
}
void View::slotPopupMenu( const QString& menuname, const QPoint & pos )
{
QMenu * menu = this->popupMenu( menuname );
if ( menu ) {
//debugPlan<<menu<<":"<<menu->actions().count();
ViewBase *v = qobject_cast<ViewBase*>( m_tab->currentWidget() );
//debugPlan<<v<<menuname;
QList<QAction*> lst;
if ( v ) {
lst = v->contextActionList();
debugPlan<<lst;
if ( ! lst.isEmpty() ) {
menu->addSeparator();
foreach ( QAction *a, lst ) {
menu->addAction( a );
}
}
}
menu->exec( pos );
foreach ( QAction *a, lst ) {
menu->removeAction( a );
}
}
}
void View::slotPopupMenu( const QString& menuname, const QPoint &pos, ViewListItem *item )
{
//debugPlan<<menuname;
m_viewlistItem = item;
slotPopupMenu( menuname, pos );
}
bool View::loadContext()
{
Context *ctx = getPart()->context();
if ( ctx == 0 || ! ctx->isLoaded() ) {
return false;
}
KoXmlElement n = ctx->context();
QString cv = n.attribute( "current-view" );
if ( ! cv.isEmpty() ) {
m_viewlist->setSelected( m_viewlist->findItem( cv ) );
} else debugPlan<<"No current view";
long id = n.attribute( "current-schedule", "-1" ).toLong();
if ( id != -1 ) {
setActiveSchedule( id );
} else debugPlan<<"No current schedule";
return true;
}
void View::saveContext( QDomElement &me ) const
{
//debugPlan;
long id = activeScheduleId();
if ( id != -1 ) {
me.setAttribute( "current-schedule", QString::number((qlonglong)id) );
}
ViewListItem *item = m_viewlist->findItem( qobject_cast<ViewBase*>( m_tab->currentWidget() ) );
if ( item ) {
me.setAttribute("current-view", item->tag() );
}
m_viewlist->save( me );
}
bool View::loadWorkPackage( Project &project, const QUrl &url )
{
return getPart()->loadWorkPackage( project, url );
}
void View::setLabel( ScheduleManager *sm )
{
//debugPlan;
Schedule *s = sm == 0 ? 0 : sm->expected();
if ( s && !s->isDeleted() && s->isScheduled() ) {
m_estlabel->setText( sm->name() );
return;
}
m_estlabel->setText( xi18nc( "@info:status", "Not scheduled" ) );
}
void View::slotWorkPackageLoaded()
{
debugPlan<<getPart()->workPackages();
}
void View::slotMailWorkpackage( Node *node, Resource *resource )
{
debugPlan;
QTemporaryFile tmpfile(QDir::tempPath() + QLatin1String("/calligraplanwork_XXXXXX") + QLatin1String( ".planwork" ));
tmpfile.setAutoRemove( false );
if ( ! tmpfile.open() ) {
debugPlan<<"Failed to open file";
KMessageBox::error(0, i18n("Failed to open temporary file" ) );
return;
}
QUrl url = QUrl::fromLocalFile( tmpfile.fileName() );
if ( ! getPart()->saveWorkPackageUrl( url, node, activeScheduleId(), resource ) ) {
debugPlan<<"Failed to save to file";
KMessageBox::error(0, xi18nc( "@info", "Failed to save to temporary file:<br/> <filename>%1</filename>", url.url() ) );
return;
}
QStringList attachURLs;
attachURLs << url.url();
QString to = resource == 0 ? node->leader() : ( resource->name() + " <" + resource->email() + '>' );
QString cc;
QString bcc;
QString subject = i18n( "Work Package: %1", node->name() );
QString body = i18nc( "1=project name, 2=task name", "%1\n%2", getProject().name(), node->name() );
QString messageFile;
KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs );
}
void View::slotMailWorkpackages( const QList<Node*> &nodes, Resource *resource )
{
debugPlan;
if ( resource == 0 ) {
warnPlan<<"No resource, we don't handle node->leader() yet";
return;
}
QString to = resource->name() + " <" + resource->email() + '>';
QString subject = i18n( "Work Package for project: %1", getProject().name() );
QString body;
QStringList attachURLs;
foreach ( Node *n, nodes ) {
QTemporaryFile tmpfile(QDir::tempPath() + QLatin1String("/calligraplanwork_XXXXXX") + QLatin1String( ".planwork" ));
tmpfile.setAutoRemove( false );
if ( ! tmpfile.open() ) {
debugPlan<<"Failed to open file";
KMessageBox::error(0, i18n("Failed to open temporary file" ) );
return;
}
QUrl url = QUrl::fromLocalFile( tmpfile.fileName() );
if ( ! getPart()->saveWorkPackageUrl( url, n, activeScheduleId(), resource ) ) {
debugPlan<<"Failed to save to file";
KMessageBox::error(0, xi18nc( "@info", "Failed to save to temporary file:<br/><filename>%1</filename>", url.url() ) );
return;
}
attachURLs << url.url();
body += n->name() + '\n';
}
QString cc;
QString bcc;
QString messageFile;
KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs );
}
void View::slotCurrencyConfig()
{
LocaleConfigMoneyDialog *dlg = new LocaleConfigMoneyDialog( getProject().locale(), this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotCurrencyConfigFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void View::slotCurrencyConfigFinished( int result )
{
LocaleConfigMoneyDialog *dlg = qobject_cast<LocaleConfigMoneyDialog*>( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == QDialog::Accepted ) {
KUndo2Command *c = dlg->buildCommand( getProject() );
if ( c ) {
getPart()->addCommand( c );
}
}
dlg->deleteLater();
}
void View::saveTaskModule( const QUrl &url, Project *project )
{
// NOTE: workaround: KoResourcePaths::saveLocation( "calligraplan_taskmodules" ); does not work
const QString dir = KoResourcePaths::saveLocation( "appdata", "taskmodules/" );
debugPlan<<"dir="<<dir;
if ( ! dir.isEmpty() ) {
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->insertProject( *project, 0, 0 ); // FIXME: destroys project, find better way
doc->getProject().setName( project->name() );
doc->getProject().setLeader( project->leader() );
doc->getProject().setDescription( project->description() );
doc->saveNativeFormat( dir + url.fileName() );
part->deleteLater(); // also deletes document
debugPlan<<dir + url.fileName();
} else {
debugPlan<<"Could not find a location";
}
}
void View::removeTaskModule( const QUrl &url )
{
debugPlan<<url;
}
void View::taskModuleFileChanged(const QString &path)
{
if (!path.endsWith(".plan")) {
return;
}
m_dirwatch.stopScan();
int result = QMessageBox::question(this, xi18nc("@title", "Task module changed"), i18n("Do you want to reload task modules?"));
if (result == QMessageBox::Yes) {
QStringList modules = KoResourcePaths::findAllResources( "calligraplan_taskmodules", "*.plan", KoResourcePaths::NoDuplicates|KoResourcePaths::Recursive );
emit taskModulesChanged(modules);
}
m_dirwatch.startScan();
}
QString View::standardTaskStatusReport() const
{
QString s;
#ifdef PLAN_USE_KREPORT
s = QString::fromLatin1(
"<planreportdefinition version=\"1.0\" mime=\"application/x-vnd.kde.plan.report.definition\" editor=\"Plan\" >"
"<data-source select-from=\"taskstatus\" ></data-source>"
"<report:content xmlns:report=\"http://kexi-project.org/report/2.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" >"
"<report:title>%1</report:title>"
"<report:script report:script-interpreter=\"javascript\" ></report:script>"
"<report:grid report:grid-divisions=\"4\" report:grid-snap=\"1\" report:page-unit=\"cm\" report:grid-visible=\"1\" />"
"<report:page-style report:print-orientation=\"portrait\" fo:margin-bottom=\"1cm\" fo:margin-top=\"1cm\" fo:margin-left=\"1cm\" fo:margin-right=\"1cm\" report:page-size=\"A4\" >predefined</report:page-style>"
"<report:body>"
"<report:section svg:height=\"1.75cm\" fo:background-color=\"#ffffff\" report:section-type=\"header-page-any\" >"
"<report:field report:name=\"field16\" report:horizontal-align=\"left\" report:item-data-source=\"#project.manager\" svg:x=\"13cm\" svg:width=\"5.9714cm\" svg:y=\"0.4cm\" report:vertical-align=\"bottom\" svg:height=\"0.6cm\" report:z-index=\"0\" >"
"<report:text-style fo:font-weight=\"bold\" fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"10\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"<report:label report:name=\"label16\" report:horizontal-align=\"left\" svg:x=\"13cm\" svg:width=\"5.9714cm\" svg:y=\"0cm\" report:caption=\"%2\" report:vertical-align=\"center\" svg:height=\"0.4cm\" report:z-index=\"1\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:font-style=\"italic\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"<report:field report:name=\"field17\" report:horizontal-align=\"left\" report:item-data-source=\"#project.name\" svg:x=\"0cm\" svg:width=\"13cm\" svg:y=\"0.4cm\" report:vertical-align=\"bottom\" svg:height=\"0.6cm\" report:z-index=\"1\" >"
"<report:text-style fo:font-weight=\"bold\" fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"10\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"0%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"<report:label report:name=\"label18\" report:horizontal-align=\"left\" svg:x=\"0cm\" svg:width=\"13cm\" svg:y=\"0cm\" report:caption=\"%3\" report:vertical-align=\"center\" svg:height=\"0.4cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:font-style=\"italic\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"<report:line report:name=\"line15\" svg:y1=\"1.2229cm\" svg:x1=\"0cm\" svg:y2=\"1.2229cm\" svg:x2=\"18.9715cm\" report:z-index=\"0\" >"
"<report:line-style report:line-style=\"solid\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:line>"
"</report:section>"
"<report:section svg:height=\"1.50cm\" fo:background-color=\"#ffffff\" report:section-type=\"header-report\" >"
"<report:label report:name=\"label17\" report:horizontal-align=\"left\" svg:x=\"0cm\" svg:width=\"18.97cm\" svg:y=\"0cm\" report:caption=\"%4\" report:vertical-align=\"center\" svg:height=\"1.25cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"10\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"</report:section>"
"<report:section svg:height=\"2.50cm\" fo:background-color=\"#ffffff\" report:section-type=\"footer-page-any\" >"
"<report:field report:name=\"field10\" report:horizontal-align=\"right\" report:item-data-source=\"=constants.PageNumber()\" svg:x=\"6.75cm\" svg:width=\"0.75cm\" svg:y=\"0.25cm\" report:vertical-align=\"center\" svg:height=\"0.75cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"<report:field report:name=\"field11\" report:horizontal-align=\"left\" report:item-data-source=\"=constants.PageTotal()\" svg:x=\"8.25cm\" svg:width=\"3cm\" svg:y=\"0.25cm\" report:vertical-align=\"center\" svg:height=\"0.75cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"<report:label report:name=\"label12\" report:horizontal-align=\"center\" svg:x=\"7.5cm\" svg:width=\"0.75cm\" svg:y=\"0.25cm\" report:caption=\"%5\" report:vertical-align=\"center\" svg:height=\"0.75cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:font-style=\"italic\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"<report:label report:name=\"label13\" report:horizontal-align=\"right\" svg:x=\"5.75cm\" svg:width=\"1cm\" svg:y=\"0.25cm\" report:caption=\"%6\" report:vertical-align=\"center\" svg:height=\"0.75cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:font-style=\"italic\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"<report:line report:name=\"line14\" svg:y1=\"0.2195cm\" svg:x1=\"0cm\" svg:y2=\"0.2195cm\" svg:x2=\"18.9715cm\" report:z-index=\"0\" >"
"<report:line-style report:line-style=\"solid\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:line>"
"</report:section>"
"<report:detail>"
"<report:group report:group-sort=\"ascending\" report:group-column=\"Parent\" >"
"<report:section svg:height=\"2.50cm\" fo:background-color=\"#ffffff\" report:section-type=\"group-header\" >"
"<report:label report:name=\"label6\" report:horizontal-align=\"left\" svg:x=\"0.5cm\" svg:width=\"3.75cm\" svg:y=\"1.75cm\" report:caption=\"%7\" report:vertical-align=\"center\" svg:height=\"0.75cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"<report:field report:name=\"field8\" report:horizontal-align=\"left\" report:item-data-source=\"Parent\" svg:x=\"0.5cm\" svg:width=\"8cm\" svg:y=\"1cm\" report:vertical-align=\"center\" svg:height=\"0.689cm\" report:z-index=\"0\" >"
"<report:text-style fo:font-weight=\"bold\" fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"<report:label report:name=\"label8\" report:horizontal-align=\"center\" svg:x=\"4.25cm\" svg:width=\"4.25cm\" svg:y=\"1.75cm\" report:caption=\"%8\" report:vertical-align=\"center\" svg:height=\"0.75cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:label>"
"</report:section>"
"</report:group>"
"<report:section svg:height=\"0.50cm\" fo:background-color=\"#ffffff\" report:section-type=\"detail\" >"
"<report:field report:name=\"field7\" report:horizontal-align=\"left\" report:item-data-source=\"NodeName\" svg:x=\"0.5cm\" svg:width=\"3.75cm\" svg:y=\"0cm\" report:vertical-align=\"center\" svg:height=\"0.5cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"<report:field report:name=\"field9\" report:horizontal-align=\"center\" report:item-data-source=\"NodeCompleted\" svg:x=\"4.25cm\" svg:width=\"4.25cm\" svg:y=\"0cm\" report:vertical-align=\"center\" svg:height=\"0.5cm\" report:z-index=\"0\" >"
"<report:text-style fo:letter-spacing=\"0%\" style:letter-kerning=\"true\" fo:font-size=\"8\" fo:foreground-color=\"#000000\" fo:font-family=\"DejaVu Sans\" fo:background-color=\"#ffffff\" fo:background-opacity=\"100%\" />"
"<report:line-style report:line-style=\"nopen\" report:line-weight=\"1\" report:line-color=\"#000000\" />"
"</report:field>"
"</report:section>"
"</report:detail>"
"</report:body>"
"</report:content>"
"</planreportdefinition>")
.arg(
i18n( "Report" ),
i18nc( "Project manager", "Manager:" ),
i18n( "Project:" ),
i18n( "Task Status Report" ),
i18nc( "As in: Page 1 of 2", "of" ),
i18n( "Page" ),
i18nc( "Task name", "Name" ),
i18nc( "Task completion", "Completion (%)" )
);
#endif
return s;
}
} //KPlato namespace
diff --git a/src/kptviewlist.cpp b/src/kptviewlist.cpp
index 2220b931..3bcaf4f4 100644
--- a/src/kptviewlist.cpp
+++ b/src/kptviewlist.cpp
@@ -1,870 +1,871 @@
/* This file is part of the KDE project
Copyright (C) 2007 -2010 Dag Andersen <danders@get2net.dk>
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 "kptviewlist.h"
#include <QString>
#include <QStringList>
#include <QItemDelegate>
#include <QStyle>
#include <QBrush>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QMenu>
#include <kmessagebox.h>
#include <kcombobox.h>
#include <KoIcon.h>
#include "KoDocument.h"
#include "kptviewbase.h"
#include "kptmaindocument.h"
#include "kptviewlistdialog.h"
#include "kptviewlistdocker.h"
#include "kptschedulemodel.h"
#include "Help.h"
#include <kptdebug.h>
#include <assert.h>
namespace KPlato
{
// <Code mostly nicked from qt designer ;)>
class ViewCategoryDelegate : public QItemDelegate
{
public:
ViewCategoryDelegate( QObject *parent, QTreeView *view )
: QItemDelegate( parent ),
m_view( view )
{}
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
virtual void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
private:
QTreeView *m_view;
};
QSize ViewCategoryDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
const QAbstractItemModel * model = index.model();
Q_ASSERT( model );
if ( model->parent( index ).isValid() ) {
return QItemDelegate::sizeHint( option, index );
}
return QItemDelegate::sizeHint( option, index ).expandedTo( QSize( 0, 16 ) );
}
void ViewCategoryDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
const QAbstractItemModel * model = index.model();
Q_ASSERT( model );
if ( !model->parent( index ).isValid() ) {
// this is a top-level item.
QStyleOptionButton buttonOption;
buttonOption.state = option.state;
buttonOption.rect = option.rect;
buttonOption.palette = option.palette;
buttonOption.features = QStyleOptionButton::None;
m_view->style() ->drawControl( QStyle::CE_PushButton, &buttonOption, painter, m_view );
QStyleOption branchOption;
static const int i = 9; // ### hardcoded in qcommonstyle.cpp
QRect r = option.rect;
branchOption.rect = QRect( r.left() + i / 2, r.top() + ( r.height() - i ) / 2, i, i );
branchOption.palette = option.palette;
branchOption.state = QStyle::State_Children;
if ( m_view->isExpanded( index ) )
branchOption.state |= QStyle::State_Open;
m_view->style() ->drawPrimitive( QStyle::PE_IndicatorBranch, &branchOption, painter, m_view );
// draw text
QRect textrect = QRect( r.left() + i * 2, r.top(), r.width() - ( ( 5 * i ) / 2 ), r.height() );
QString text = elidedText( option.fontMetrics, textrect.width(), Qt::ElideMiddle,
model->data( index, Qt::DisplayRole ).toString() );
m_view->style() ->drawItemText( painter, textrect, Qt::AlignLeft|Qt::AlignVCenter,
option.palette, m_view->isEnabled(), text );
} else {
QItemDelegate::paint( painter, option, index );
}
}
ViewListItem::ViewListItem( const QString &tag, const QStringList &strings, int type )
: QTreeWidgetItem( strings, type ),
m_tag( tag )
{
}
ViewListItem::ViewListItem( QTreeWidget *parent, const QString &tag, const QStringList &strings, int type )
: QTreeWidgetItem( parent, strings, type ),
m_tag( tag )
{
}
ViewListItem::ViewListItem( QTreeWidgetItem *parent, const QString &tag, const QStringList &strings, int type )
: QTreeWidgetItem( parent, strings, type ),
m_tag( tag )
{
}
void ViewListItem::setReadWrite( bool rw )
{
if ( type() == ItemType_SubView ) {
static_cast<ViewBase*>( view() )->updateReadWrite( rw );
}
}
void ViewListItem::setView( ViewBase *view )
{
setData( 0, ViewListItem::DataRole_View, qVariantFromValue(static_cast<QObject*>( view ) ) );
}
ViewBase *ViewListItem::view() const
{
if ( data(0, ViewListItem::DataRole_View ).isValid() ) {
return static_cast<ViewBase*>( data(0, ViewListItem::DataRole_View ).value<QObject*>() );
}
return 0;
}
void ViewListItem::setDocument( KoDocument *doc )
{
setData( 0, ViewListItem::DataRole_Document, qVariantFromValue(static_cast<QObject*>( doc ) ) );
}
KoDocument *ViewListItem::document() const
{
if ( data(0, ViewListItem::DataRole_Document ).isValid() ) {
return static_cast<KoDocument*>( data(0, ViewListItem::DataRole_Document ).value<QObject*>() );
}
return 0;
}
QString ViewListItem::viewType() const
{
if ( type() != ItemType_SubView ) {
return QString();
}
QString name = view()->metaObject()->className();
if ( name.contains( ':' ) ) {
name = name.remove( 0, name.lastIndexOf( ':' ) + 1 );
}
return name;
}
void ViewListItem::save( QDomElement &element ) const
{
element.setAttribute( "itemtype", QString::number(type()) );
element.setAttribute( "tag", tag() );
if ( type() == ItemType_SubView ) {
element.setAttribute( "viewtype", viewType() );
element.setAttribute( "name", m_viewinfo.name == text( 0 ) ? "" : text( 0 ) );
element.setAttribute( "tooltip", m_viewinfo.tip == toolTip( 0 ) ? TIP_USE_DEFAULT_TEXT : toolTip( 0 ) );
} else if ( type() == ItemType_Category ) {
debugPlan<<text(0)<<m_viewinfo.name;
element.setAttribute( "name", text( 0 ) == m_viewinfo.name ? "" : text( 0 ) );
element.setAttribute( "tooltip", toolTip( 0 ).isEmpty() ? TIP_USE_DEFAULT_TEXT : toolTip( 0 ) );
}
}
ViewListTreeWidget::ViewListTreeWidget( QWidget *parent )
: QTreeWidget( parent )
{
header() ->hide();
setRootIsDecorated( false );
setItemDelegate( new ViewCategoryDelegate( this, this ) );
setItemsExpandable( true );
setSelectionMode( QAbstractItemView::SingleSelection );
setDragDropMode( QAbstractItemView::InternalMove );
//setContextMenuPolicy( Qt::ActionsContextMenu );
connect( this, &QTreeWidget::itemPressed, this, &ViewListTreeWidget::handleMousePress );
}
void ViewListTreeWidget::drawRow( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
QTreeWidget::drawRow( painter, option, index );
}
void ViewListTreeWidget::handleMousePress( QTreeWidgetItem *item )
{
//debugPlan;
if ( item == 0 )
return ;
if ( item->parent() == 0 ) {
setItemExpanded( item, !isItemExpanded( item ) );
return ;
}
}
void ViewListTreeWidget::mousePressEvent ( QMouseEvent *event )
{
if ( event->button() == Qt::RightButton ) {
QTreeWidgetItem *item = itemAt( event->pos() );
if ( item && item->type() == ViewListItem::ItemType_Category ) {
setCurrentItem( item );
emit customContextMenuRequested( event->pos() );
event->accept();
return;
}
}
QTreeWidget::mousePressEvent( event );
}
void ViewListTreeWidget::save( QDomElement &element ) const
{
int cnt = topLevelItemCount();
if ( cnt == 0 ) {
return;
}
QDomElement cs = element.ownerDocument().createElement( "categories" );
element.appendChild( cs );
for ( int i = 0; i < cnt; ++i ) {
ViewListItem *itm = static_cast<ViewListItem*>( topLevelItem( i ) );
if ( itm->type() != ViewListItem::ItemType_Category ) {
continue;
}
QDomElement c = cs.ownerDocument().createElement( "category" );
cs.appendChild( c );
emit const_cast<ViewListTreeWidget*>( this )->updateViewInfo( itm );
itm->save( c );
for ( int j = 0; j < itm->childCount(); ++j ) {
ViewListItem *vi = static_cast<ViewListItem*>( itm->child( j ) );
if ( vi->type() != ViewListItem::ItemType_SubView ) {
continue;
}
QDomElement el = c.ownerDocument().createElement( "view" );
c.appendChild( el );
emit const_cast<ViewListTreeWidget*>( this )->updateViewInfo( vi );
vi->save( el );
QDomElement elm = el.ownerDocument().createElement( "settings" );
el.appendChild( elm );
static_cast<ViewBase*>( vi->view() )->saveContext( elm );
}
}
}
// </Code mostly nicked from qt designer ;)>
void ViewListTreeWidget::startDrag( Qt::DropActions supportedActions )
{
QModelIndexList indexes = selectedIndexes();
if ( indexes.count() == 1 ) {
ViewListItem *item = static_cast<ViewListItem*>( itemFromIndex( indexes.at( 0 ) ) );
Q_ASSERT( item );
QTreeWidgetItem *root = invisibleRootItem();
int count = root->childCount();
if ( item && item->type() == ViewListItem::ItemType_Category ) {
root->setFlags( root->flags() | Qt::ItemIsDropEnabled );
for ( int i = 0; i < count; ++i ) {
QTreeWidgetItem * ch = root->child( i );
ch->setFlags( ch->flags() & ~Qt::ItemIsDropEnabled );
}
} else if ( item ) {
root->setFlags( root->flags() & ~Qt::ItemIsDropEnabled );
for ( int i = 0; i < count; ++i ) {
QTreeWidgetItem * ch = root->child( i );
ch->setFlags( ch->flags() | Qt::ItemIsDropEnabled );
}
}
}
QTreeWidget::startDrag( supportedActions );
}
void ViewListTreeWidget::dropEvent( QDropEvent *event )
{
QTreeWidget::dropEvent( event );
if ( event->isAccepted() ) {
emit modified();
}
}
ViewListItem *ViewListTreeWidget::findCategory( const QString &cat )
{
QTreeWidgetItem * item;
int cnt = topLevelItemCount();
for ( int i = 0; i < cnt; ++i ) {
item = topLevelItem( i );
if ( static_cast<ViewListItem*>(item)->tag() == cat )
return static_cast<ViewListItem*>(item);
}
return 0;
}
ViewListItem *ViewListTreeWidget::category( const KoView *view ) const
{
QTreeWidgetItem * item;
int cnt = topLevelItemCount();
for ( int i = 0; i < cnt; ++i ) {
item = topLevelItem( i );
for ( int c = 0; c < item->childCount(); ++c ) {
if ( view == static_cast<ViewListItem*>( item->child( c ) )->view() ) {
return static_cast<ViewListItem*>( item );
}
}
}
return 0;
}
//-----------------------
ViewListWidget::ViewListWidget( MainDocument *part, QWidget *parent )//QString name, KXmlGuiWindow *parent )
: QWidget( parent ),
m_part( part ),
m_prev( 0 ),
m_temp( 0 )
{
setObjectName("ViewListWidget");
Help::add(this,
xi18nc("@info:whatsthis",
"<title>View Selector</title>"
"<para>This is the list of views and editors.</para>"
"<para>You can configure the list by using the context menu:"
"<list>"
"<item>Rename categories or views</item>"
"<item>Configure. Move, remove, rename or edit tool tip for categories or views</item>"
"<item>Insert categories and views</item>"
"</list>"
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/View_Selector")));
m_viewlist = new ViewListTreeWidget( this );
m_viewlist->setEditTriggers( QAbstractItemView::NoEditTriggers );
connect(m_viewlist, &ViewListTreeWidget::modified, this, &ViewListWidget::modified);
m_currentSchedule = new KComboBox( this );
m_model.setFlat( true );
m_sfModel.setFilterKeyColumn ( ScheduleModel::ScheduleScheduled );
m_sfModel.setFilterRole( Qt::EditRole );
m_sfModel.setFilterFixedString( "true" );
m_sfModel.setDynamicSortFilter ( true );
m_sfModel.setSourceModel( &m_model );
m_currentSchedule->setModel( &m_sfModel );
Help::add(m_currentSchedule,
xi18nc("@info:whatsthis",
"<title>Schedule selector</title>"
"<para>"
"Selects the schedule to be used when displaying schedule dependent data."
"<nl/><note>Unscheduled tasks are only shown in editors.</note>"
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Main_Work_Space#Schedule_Selector")));
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin( 0 );
l->addWidget( m_viewlist );
l->addWidget( m_currentSchedule );
connect( m_viewlist, &QTreeWidget::currentItemChanged, this, &ViewListWidget::slotActivated );
connect( m_viewlist, &QTreeWidget::itemChanged, this, &ViewListWidget::slotItemChanged );
setupContextMenus();
connect( m_currentSchedule, SIGNAL(activated(int)), SLOT(slotCurrentScheduleChanged(int)) );
connect( &m_model, &ScheduleItemModel::scheduleManagerAdded, this, &ViewListWidget::slotScheduleManagerAdded );
connect( m_viewlist, &ViewListTreeWidget::updateViewInfo, this, &ViewListWidget::updateViewInfo );
}
ViewListWidget::~ViewListWidget()
{
}
void ViewListWidget::setReadWrite( bool rw )
{
foreach ( ViewListItem *c, categories() ) {
for ( int i = 0; i < c->childCount(); ++i ) {
static_cast<ViewListItem*>( c->child( i ) )->setReadWrite( rw );
}
}
}
void ViewListWidget::slotItemChanged( QTreeWidgetItem */*item*/, int /*col */)
{
//debugPlan;
}
void ViewListWidget::slotActivated( QTreeWidgetItem *item, QTreeWidgetItem *prev )
{
if ( m_prev ) {
m_prev->setData( 0, Qt::BackgroundRole, QVariant() );
}
if ( item && item->type() == ViewListItem::ItemType_Category ) {
return ;
}
emit activated( static_cast<ViewListItem*>( item ), static_cast<ViewListItem*>( prev ) );
if ( item ) {
QVariant v = QBrush( QColor( Qt::yellow ) );
item->setData( 0, Qt::BackgroundRole, v );
m_prev = static_cast<ViewListItem*>( item );
}
}
ViewListItem *ViewListWidget::addCategory( const QString &tag, const QString& name )
{
//debugPlan ;
ViewListItem *item = m_viewlist->findCategory( tag );
if ( item == 0 ) {
item = new ViewListItem( m_viewlist, tag, QStringList( name ), ViewListItem::ItemType_Category );
item->setExpanded( true );
item->setFlags( item->flags() | Qt::ItemIsEditable );
}
return item;
}
QList<ViewListItem*> ViewListWidget::categories() const
{
QList<ViewListItem*> lst;
QTreeWidgetItem *item;
int cnt = m_viewlist->topLevelItemCount();
for ( int i = 0; i < cnt; ++i ) {
item = m_viewlist->topLevelItem( i );
if ( item->type() == ViewListItem::ItemType_Category )
lst << static_cast<ViewListItem*>( item );
}
return lst;
}
ViewListItem *ViewListWidget::findCategory( const QString &tag ) const
{
return m_viewlist->findCategory( tag );
}
ViewListItem *ViewListWidget::category( const KoView *view ) const
{
return m_viewlist->category( view );
}
QString ViewListWidget::uniqueTag( const QString &seed ) const
{
QString tag = seed;
for ( int i = 1; findItem( tag ); ++i ) {
tag = QString("%1-%2").arg( seed ).arg( i );
}
return tag;
}
ViewListItem *ViewListWidget::addView(QTreeWidgetItem *category, const QString &tag, const QString &name, ViewBase *view, KoDocument *doc, const QString &iconName, int index)
{
ViewListItem * item = new ViewListItem( uniqueTag( tag ), QStringList( name ), ViewListItem::ItemType_SubView );
item->setView( view );
item->setDocument( doc );
if (! iconName.isEmpty()) {
item->setData(0, Qt::DecorationRole, QIcon::fromTheme(iconName));
}
item->setFlags( ( item->flags() | Qt::ItemIsEditable ) & ~Qt::ItemIsDropEnabled );
insertViewListItem( item, category, index );
connect(view, &ViewBase::optionsModified, this, &ViewListWidget::setModified);
return item;
}
void ViewListWidget::setSelected( QTreeWidgetItem *item )
{
//debugPlan<<item<<","<<m_viewlist->currentItem();
if ( item == 0 && m_viewlist->currentItem() ) {
m_viewlist->currentItem()->setSelected( false );
if ( m_prev ) {
m_prev->setData( 0, Qt::BackgroundRole, QVariant() );
}
}
m_viewlist->setCurrentItem( item );
//debugPlan<<item<<","<<m_viewlist->currentItem();
}
void ViewListWidget::setCurrentItem( QTreeWidgetItem *item )
{
m_viewlist->setCurrentItem( item );
//debugPlan<<item<<","<<m_viewlist->currentItem();
}
ViewListItem *ViewListWidget::currentItem() const
{
return static_cast<ViewListItem*>( m_viewlist->currentItem() );
}
ViewListItem *ViewListWidget::currentCategory() const
{
ViewListItem *item = static_cast<ViewListItem*>( m_viewlist->currentItem() );
if ( item == 0 ) {
return 0;
}
if ( item->type() == ViewListItem::ItemType_Category ) {
return item;
}
return static_cast<ViewListItem*>( item->parent() );
}
KoView *ViewListWidget::findView( const QString &tag ) const
{
ViewListItem *i = findItem( tag );
if ( i == 0 ) {
return 0;
}
return i->view();
}
ViewListItem *ViewListWidget::findItem( const QString &tag ) const
{
ViewListItem *item = findItem( tag, m_viewlist->invisibleRootItem() );
if ( item == 0 ) {
QTreeWidgetItem *parent = m_viewlist->invisibleRootItem();
for (int i = 0; i < parent->childCount(); ++i ) {
item = findItem( tag, parent->child( i ) );
if ( item != 0 ) {
break;
}
}
}
return item;
}
ViewListItem *ViewListWidget::findItem( const QString &tag, QTreeWidgetItem *parent ) const
{
if ( parent == 0 ) {
return findItem( tag, m_viewlist->invisibleRootItem() );
}
for (int i = 0; i < parent->childCount(); ++i ) {
ViewListItem * ch = static_cast<ViewListItem*>( parent->child( i ) );
if ( ch->tag() == tag ) {
//debugPlan<<ch<<","<<view;
return ch;
}
ch = findItem( tag, ch );
if ( ch ) {
return ch;
}
}
return 0;
}
ViewListItem *ViewListWidget::findItem( const ViewBase *view, QTreeWidgetItem *parent ) const
{
if ( parent == 0 ) {
return findItem( view, m_viewlist->invisibleRootItem() );
}
for (int i = 0; i < parent->childCount(); ++i ) {
ViewListItem * ch = static_cast<ViewListItem*>( parent->child( i ) );
if ( ch->view() == view ) {
//debugPlan<<ch<<","<<view;
return ch;
}
ch = findItem( view, ch );
if ( ch ) {
return ch;
}
}
return 0;
}
void ViewListWidget::slotAddView()
{
emit createView();
}
void ViewListWidget::slotRemoveCategory()
{
if ( m_contextitem == 0 ) {
return;
}
if ( m_contextitem->type() != ViewListItem::ItemType_Category ) {
return;
}
debugPlan<<m_contextitem<<":"<<m_contextitem->type();
if ( m_contextitem->childCount() > 0 ) {
if ( KMessageBox::warningContinueCancel( this, i18n( "Removing this category will also remove all its views." ) ) == KMessageBox::Cancel ) {
return;
}
}
// first remove all views in this category
while ( m_contextitem->childCount() > 0 ) {
ViewListItem *itm = static_cast<ViewListItem*>( m_contextitem->child( 0 ) );
takeViewListItem( itm );
delete itm->view();
delete itm;
}
takeViewListItem( m_contextitem );
delete m_contextitem;
m_contextitem = 0;
emit modified();
}
void ViewListWidget::slotRemoveView()
{
if ( m_contextitem ) {
takeViewListItem( m_contextitem );
delete m_contextitem->view();
delete m_contextitem;
emit modified();
}
}
void ViewListWidget::slotEditViewTitle()
{
//QTreeWidgetItem *item = m_viewlist->currentItem();
if ( m_contextitem ) {
debugPlan<<m_contextitem<<":"<<m_contextitem->type();
QString title = m_contextitem->text( 0 );
m_viewlist->editItem( m_contextitem );
if ( title != m_contextitem->text( 0 ) ) {
emit modified();
}
}
}
void ViewListWidget::slotConfigureItem()
{
if ( m_contextitem == 0 ) {
return;
}
KoDialog *dlg = 0;
if ( m_contextitem->type() == ViewListItem::ItemType_Category ) {
debugPlan<<m_contextitem<<":"<<m_contextitem->type();
dlg = new ViewListEditCategoryDialog( *this, m_contextitem, this );
} else if ( m_contextitem->type() == ViewListItem::ItemType_SubView ) {
dlg = new ViewListEditViewDialog( *this, m_contextitem, this );
}
if ( dlg ) {
connect(dlg, SIGNAL(finished(int)), SLOT(slotDialogFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
}
void ViewListWidget::slotDialogFinished( int result )
{
if ( result == QDialog::Accepted ) {
emit modified();
}
if ( sender() ) {
sender()->deleteLater();
}
}
void ViewListWidget::slotEditDocumentTitle()
{
//QTreeWidgetItem *item = m_viewlist->currentItem();
if ( m_contextitem ) {
debugPlan<<m_contextitem<<":"<<m_contextitem->type();
QString title = m_contextitem->text( 0 );
m_viewlist->editItem( m_contextitem );
}
}
int ViewListWidget::removeViewListItem( ViewListItem *item )
{
QTreeWidgetItem *p = item->parent();
if ( p == 0 ) {
p = m_viewlist->invisibleRootItem();
}
int i = p->indexOfChild( item );
if ( i != -1 ) {
p->takeChild( i );
emit modified();
}
return i;
}
void ViewListWidget::addViewListItem( ViewListItem *item, QTreeWidgetItem *parent, int index )
{
QTreeWidgetItem *p = parent;
if ( p == 0 ) {
p = m_viewlist->invisibleRootItem();
}
if ( index == -1 ) {
index = p->childCount();
}
p->insertChild( index, item );
emit modified();
}
int ViewListWidget::takeViewListItem( ViewListItem *item )
{
while ( item->childCount() > 0 ) {
takeViewListItem( static_cast<ViewListItem*>( item->child( 0 ) ) );
}
int pos = removeViewListItem( item );
if ( pos != -1 ) {
emit viewListItemRemoved( item );
if ( item == m_prev ) {
m_prev = 0;
}
if ( m_prev ) {
setCurrentItem( m_prev );
}
}
return pos;
}
void ViewListWidget::insertViewListItem( ViewListItem *item, QTreeWidgetItem *parent, int index )
{
addViewListItem( item, parent, index );
emit viewListItemInserted( item, static_cast<ViewListItem*>( parent ), index );
}
void ViewListWidget::setupContextMenus()
{
// NOTE: can't use xml file as there may not be a factory()
QAction *action;
// view actions
action = new QAction(koIcon("edit-rename"), xi18nc("@action:inmenu rename view", "Rename"), this);
connect( action, &QAction::triggered, this, &ViewListWidget::slotEditViewTitle );
m_viewactions.append( action );
action = new QAction(koIcon("configure"), xi18nc("@action:inmenu configure view", "Configure..."), this );
connect( action, &QAction::triggered, this, &ViewListWidget::slotConfigureItem );
m_viewactions.append( action );
action = new QAction(koIcon("list-remove"), xi18nc("@action:inmenu remove view", "Remove"), this);
connect( action, &QAction::triggered, this, &ViewListWidget::slotRemoveView );
m_viewactions.append( action );
action = new QAction( this );
action->setSeparator( true );
m_viewactions.append( action );
// Category actions
action = new QAction(koIcon("edit-rename"), xi18nc("@action:inmenu rename view category", "Rename"), this);
connect( action, &QAction::triggered, this, &ViewListWidget::renameCategory );
m_categoryactions.append( action );
action = new QAction(koIcon("configure"), xi18nc("@action:inmenu configure view category", "Configure..."), this);
connect( action, &QAction::triggered, this, &ViewListWidget::slotConfigureItem );
m_categoryactions.append( action );
action = new QAction(koIcon("list-remove"), xi18nc("@action:inmenu Remove view category", "Remove"),this);
connect( action, &QAction::triggered, this, &ViewListWidget::slotRemoveCategory );
m_categoryactions.append( action );
action = new QAction( this );
action->setSeparator( true );
m_categoryactions.append( action );
// list actions
action = new QAction(koIcon("list-add"), xi18nc("@action:inmenu Insert View", "Insert..."), this);
connect( action, &QAction::triggered, this, &ViewListWidget::slotAddView );
m_listactions.append( action );
}
void ViewListWidget::renameCategory()
{
if ( m_contextitem ) {
QString title = m_contextitem->text( 0 );
m_viewlist->editItem( m_contextitem, 0 );
}
}
void ViewListWidget::contextMenuEvent ( QContextMenuEvent *event )
{
QMenu menu;
QList<QAction*> lst;
m_contextitem = static_cast<ViewListItem*>(m_viewlist->itemAt( event->pos() ) );
if ( m_contextitem == 0 ) {
lst += m_listactions;
} else {
if ( m_contextitem->type() == ViewListItem::ItemType_Category ) {
lst += m_categoryactions;
} else if ( m_contextitem->type() == ViewListItem::ItemType_SubView ) {
lst += m_viewactions;
ViewBase *v = dynamic_cast<ViewBase*>( m_contextitem->view() );
if ( v ) {
lst += v->viewlistActionList();
}
}
lst += m_listactions;
}
if ( ! lst.isEmpty() ) {
//menu.addTitle( i18n( "Edit" ) );
foreach ( QAction *a, lst ) {
menu.addAction( a );
}
}
if ( ! menu.actions().isEmpty() ) {
menu.exec( event->globalPos() );
}
}
void ViewListWidget::save( QDomElement &element ) const
{
m_viewlist->save( element );
}
void ViewListWidget::setProject( Project *project )
{
debugPlan<<project;
m_model.setProject( project );
}
void ViewListWidget::slotCurrentScheduleChanged( int idx )
{
debugPlan<<idx<<selectedSchedule();
emit selectionChanged( selectedSchedule() );
}
ScheduleManager *ViewListWidget::selectedSchedule() const
{
QModelIndex idx = m_sfModel.index( m_currentSchedule->currentIndex(), m_currentSchedule->modelColumn() );
debugPlan<<idx;
return m_sfModel.manager( idx );
}
void ViewListWidget::setSelectedSchedule( ScheduleManager *sm )
{
debugPlan<<sm<<m_model.index( sm );
QModelIndex idx = m_sfModel.mapFromSource( m_model.index( sm ) );
if ( sm && ! idx.isValid()) {
m_temp = sm;
return;
}
m_currentSchedule->setCurrentIndex( idx.row() );
debugPlan<<sm<<idx;
m_temp = 0;
}
void ViewListWidget::slotScheduleManagerAdded( ScheduleManager *sm )
{
if ( m_temp && m_temp == sm ) {
setSelectedSchedule( sm );
m_temp = 0;
}
}
void ViewListWidget::setModified()
{
emit modified();
}
} //KPlato namespace
diff --git a/src/kptviewlistdialog.cpp b/src/kptviewlistdialog.cpp
index af885576..2947270d 100644
--- a/src/kptviewlistdialog.cpp
+++ b/src/kptviewlistdialog.cpp
@@ -1,676 +1,677 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2010 Dag Andersen <danders@get2net.dk>
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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 "kptviewlistdialog.h"
#include "kptviewlist.h"
#include "kptview.h"
#ifdef PLAN_USE_KREPORT
#include "reports/reportview.h"
#endif
#include <kptdebug.h>
#include <KLocalizedString>
#include <QTextEdit>
namespace KPlato
{
ViewListDialog::ViewListDialog( View *view, ViewListWidget &viewlist, QWidget *parent )
: KoDialog(parent)
{
setCaption( i18nc( "@title:window", "Add View") );
setButtons( KoDialog::Ok | KoDialog::Cancel );
setDefaultButton( Ok );
m_panel = new AddViewPanel( view, viewlist, this );
setMainWidget( m_panel );
enableButtonOk(false);
connect(this,&KoDialog::okClicked,this,&ViewListDialog::slotOk);
connect( m_panel, &AddViewPanel::enableButtonOk, this, &KoDialog::enableButtonOk );
connect( m_panel, &AddViewPanel::viewCreated, this, &ViewListDialog::viewCreated );
connect(&viewlist, &ViewListWidget::viewListItemRemoved, this, &ViewListDialog::slotViewListItemRemoved);
}
void ViewListDialog::slotViewListItemRemoved( ViewListItem * )
{
reject();
}
void ViewListDialog::slotOk() {
if ( m_panel->ok() ) {
accept();
}
}
//------------------------
AddViewPanel::AddViewPanel( View *view, ViewListWidget &viewlist, QWidget *parent )
: QWidget( parent ),
m_view( view ),
m_viewlist( viewlist ),
m_viewnameChanged( false ),
m_viewtipChanged( false )
{
widget.setupUi( this );
// NOTE: these lists must match switch in ok() FIXME: refactor
m_viewtypes << "ResourceEditor"
<< "TaskEditor"
<< "CalendarEditor"
<< "AccountsEditor"
<< "DependencyEditor"
<< "PertEditor"
<< "ScheduleHandlerView"
<< "TaskStatusView"
<< "TaskView"
<< "TaskWorkPackageView"
<< "GanttView"
<< "MilestoneGanttView"
<< "ResourceAppointmentsView"
<< "ResourceAppointmentsGanttView"
<< "AccountsView"
<< "ProjectStatusView"
<< "PerformanceStatusView"
<< "ReportsGeneratorView";
#ifdef PLAN_USE_KREPORT
m_viewtypes << "ReportView";
#endif
QStringList lst;
lst << i18n( "Resource Editor" )
<< i18n( "Task Editor" )
<< i18n( "Work & Vacation Editor" )
<< i18n( "Accounts Editor" )
<< i18n( "Dependency Editor (Graphic)" )
<< i18n( "Dependency Editor (List)" )
<< i18n( "Schedule Handler" )
<< i18n( "Task Status" )
<< i18n( "Task View" )
<< i18n( "Work Package View" )
<< i18n( "Gantt View" )
<< i18n( "Milestone Gantt View" )
<< i18n( "Resource Assignments" )
<< i18n( "Resource Assignments (Gantt)" )
<< i18n( "Cost Breakdown" )
<< i18n( "Project Performance Chart" )
<< i18n( "Tasks Performance Chart" )
<< i18n("Reports generator");
#ifdef PLAN_USE_KREPORT
lst << i18n( "Report" );
#endif
widget.viewtype->addItems( lst );
foreach ( ViewListItem *item, m_viewlist.categories() ) {
m_categories.insert( item->text( 0 ), item );
}
widget.category->addItems( m_categories.keys() );
ViewListItem *curr = m_viewlist.currentCategory();
if ( curr ) {
const QList<ViewListItem*> &items = m_categories.values();
widget.category->setCurrentIndex(items.indexOf(curr));
}
fillAfter( m_categories.value( widget.category->currentText() ) );
viewtypeChanged( widget.viewtype->currentIndex() );
connect( widget.viewname, &QLineEdit::textChanged, this, &AddViewPanel::changed );
connect( widget.tooltip, &QLineEdit::textChanged, this, &AddViewPanel::changed );
connect( widget.viewname, &QLineEdit::textChanged, this, &AddViewPanel::viewnameChanged );
connect( widget.tooltip, &QLineEdit::textChanged, this, &AddViewPanel::viewtipChanged );
connect( widget.insertAfter, SIGNAL(currentIndexChanged(int)), SLOT(changed()) );
connect( widget.viewtype, SIGNAL(currentIndexChanged(int)), SLOT(viewtypeChanged(int)) );
connect( widget.category, &QComboBox::editTextChanged, this, &AddViewPanel::categoryChanged );
QString categoryWhatsThis = xi18nc("@info:whatsthis",
"<title>The category of the view</title><nl/>"
"The view is placed under this category in the view selector.<nl/>"
"You can edit the category name to create a new category.");
widget.categoryLabel->setWhatsThis(categoryWhatsThis);
widget.category->setWhatsThis(categoryWhatsThis);
}
void AddViewPanel::viewnameChanged( const QString &text )
{
m_viewnameChanged = ! text.isEmpty();
}
void AddViewPanel::viewtipChanged( const QString &text )
{
m_viewtipChanged = ! text.isEmpty();
}
void AddViewPanel::viewtypeChanged( int idx )
{
ViewInfo vi = m_view->defaultViewInfo( m_viewtypes.value( idx ) );
if ( widget.viewname->text().isEmpty() ) {
m_viewnameChanged = false;
}
if ( ! m_viewnameChanged ) {
widget.viewname->setText( vi.name );
m_viewnameChanged = false;
}
if ( widget.tooltip->text().isEmpty() ) {
m_viewtipChanged = false;
}
if ( ! m_viewtipChanged ) {
QTextEdit e;
e.setText(vi.tip);
widget.tooltip->setText(e.toPlainText());
m_viewtipChanged = false;
}
}
void AddViewPanel::categoryChanged()
{
debugPlan<<widget.category->currentText();
fillAfter( m_categories.value( widget.category->currentText() ) );
changed();
}
void AddViewPanel::fillAfter( ViewListItem *cat )
{
debugPlan<<cat;
widget.insertAfter->clear();
if ( cat ) {
widget.insertAfter->addItem( i18n( "Top" ) );
// int idx = 0;
for ( int i = 0; i < cat->childCount(); ++i ) {
ViewListItem *itm = static_cast<ViewListItem*>( cat->child( i ) );
widget.insertAfter->addItem( itm->text( 0 ), QVariant::fromValue( (void*)itm ) );
}
if ( cat == m_viewlist.currentCategory() ) {
ViewListItem *v = m_viewlist.currentItem();
if ( v && v->type() != ViewListItem::ItemType_Category ) {
widget.insertAfter->setCurrentIndex( cat->indexOfChild( v ) + 1 );
}
}
}
}
bool AddViewPanel::ok()
{
QString n = widget.category->currentText();
ViewListItem *curr = m_categories.value( n );
QString c = curr == 0 ? n : curr->tag();
ViewListItem *cat = m_viewlist.addCategory( c, n );
if ( cat == 0 ) {
return false;
}
ViewBase *v = 0;
int index = widget.insertAfter->currentIndex();
int viewtype = widget.viewtype->currentIndex();
switch ( viewtype ) {
case 0: { // Resource editor
v = m_view->createResourceEditor( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 1: { // Task editor
v = m_view->createTaskEditor( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 2: { // Work & Vacation Editor
v = m_view->createCalendarEditor( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 3: { // Accounts Editor
v = m_view->createAccountsEditor( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 4: { // Dependency Editor (Graphic)
v = m_view->createDependencyEditor( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 5: { // Dependency Editor (List)
v = m_view->createPertEditor( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 6: { // Schedules Handler
v = m_view->createScheduleHandler( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 7: { // Task status
v = m_view->createTaskStatusView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 8: { // Task status
v = m_view->createTaskView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 9: { // Task work package
v = m_view->createTaskWorkPackageView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 10: { // Gantt View
v = m_view->createGanttView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 11: { // Milestone Gantt View
v = m_view->createMilestoneGanttView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 12: { // Resource Assignments
v = m_view->createResourceAppointmentsView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 13: { // Resource Assignments (Gantt)
v = m_view->createResourceAppointmentsGanttView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 14: { // Cost Breakdown
v = m_view->createAccountsView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 15: { // Project Performance Chart
v = m_view->createProjectStatusView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 16: { // Task Performance Chart
v = m_view->createPerformanceStatusView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
case 17: { // Reports generator
v = m_view->createReportsGeneratorView(cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index);
break; }
#ifdef PLAN_USE_KREPORT
case 18: { // Report view
v = m_view->createReportView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
break; }
#endif
default:
errorPlan<<"Unknown view type!";
break;
}
emit viewCreated( v );
return true;
}
void AddViewPanel::changed()
{
bool disable = widget.viewname->text().isEmpty() | widget.viewtype->currentText().isEmpty() | widget.category->currentText().isEmpty();
emit enableButtonOk( ! disable );
}
//------------------------
ViewListEditViewDialog::ViewListEditViewDialog( ViewListWidget &viewlist, ViewListItem *item, QWidget *parent )
: KoDialog(parent)
{
setCaption( i18nc( "@title:window", "Configure View" ) );
setButtons( KoDialog::Ok | KoDialog::Cancel );
setDefaultButton( Ok );
m_panel = new EditViewPanel( viewlist, item, this );
setMainWidget( m_panel );
enableButtonOk(false);
connect(this,&KoDialog::okClicked,this,&ViewListEditViewDialog::slotOk);
connect( m_panel, &EditViewPanel::enableButtonOk, this, &KoDialog::enableButtonOk );
connect(&viewlist, &ViewListWidget::viewListItemRemoved, this, &ViewListEditViewDialog::slotViewListItemRemoved);
}
void ViewListEditViewDialog::slotViewListItemRemoved( ViewListItem * )
{
reject();
}
void ViewListEditViewDialog::slotOk() {
if ( m_panel->ok() ) {
accept();
}
}
EditViewPanel::EditViewPanel( ViewListWidget &viewlist, ViewListItem *item, QWidget *parent )
: QWidget( parent ),
m_item( item ),
m_viewlist( viewlist )
{
widget.setupUi( this );
widget.viewname->setText( item->text( 0 ) );
QTextEdit e;
e.setText(item->toolTip(0));
widget.tooltip->setText(e.toPlainText());
foreach ( ViewListItem *item, m_viewlist.categories() ) {
m_categories.insert( item->text( 0 ), item );
}
widget.category->addItems( m_categories.keys() );
ViewListItem *curr = m_viewlist.currentCategory();
if ( curr ) {
const QList<ViewListItem*> &items = m_categories.values();
widget.category->setCurrentIndex(items.indexOf(curr));
}
categoryChanged();
connect( widget.viewname, &QLineEdit::textChanged, this, &EditViewPanel::changed );
connect( widget.tooltip, &QLineEdit::textChanged, this, &EditViewPanel::changed );
connect( widget.insertAfter, SIGNAL(currentIndexChanged(int)), SLOT(changed()) );
connect( widget.category, &QComboBox::editTextChanged, this, &EditViewPanel::categoryChanged );
QString categoryWhatsThis = xi18nc("@info:whatsthis",
"<title>The category of the view</title><nl/>"
"The view is placed under this category in the view selector.<nl/>"
"Selecting a different category will move the view to the new category.<nl/>"
"You can edit the category name to create a new category.");
widget.categoryLabel->setWhatsThis(categoryWhatsThis);
widget.category->setWhatsThis(categoryWhatsThis);
}
bool EditViewPanel::ok()
{
QString n = widget.category->currentText();
ViewListItem *curr = m_categories.value( n );
QString c = curr == 0 ? n : curr->tag();
ViewListItem *cat = m_viewlist.addCategory( c, n );
if ( cat == 0 ) {
warnPlan<<"No category";
return false;
}
if ( widget.viewname->text() != m_item->text( 0 ) ) {
m_item->setText( 0, widget.viewname->text() );
}
if ( widget.tooltip->text() != m_item->toolTip( 0 ) ) {
m_item->setToolTip( 0, widget.tooltip->text() );
}
m_viewlist.removeViewListItem( m_item );
int index = qMin( widget.insertAfter->currentIndex(), cat->childCount() );
m_viewlist.addViewListItem( m_item, cat, index );
return true;
}
void EditViewPanel::changed()
{
bool disable = widget.viewname->text().isEmpty() | widget.category->currentText().isEmpty();
emit enableButtonOk( ! disable );
}
void EditViewPanel::categoryChanged()
{
debugPlan<<widget.category->currentText();
fillAfter( m_categories.value( widget.category->currentText() ) );
changed();
}
void EditViewPanel::fillAfter( ViewListItem *cat )
{
debugPlan<<cat;
widget.insertAfter->clear();
if ( cat ) {
widget.insertAfter->addItem( i18n( "Top" ) );
// int idx = 0;
for ( int i = 0; i < cat->childCount(); ++i ) {
ViewListItem *itm = static_cast<ViewListItem*>( cat->child( i ) );
widget.insertAfter->addItem( itm->text( 0 ), QVariant::fromValue( (void*)itm ) );
}
if ( cat == m_viewlist.currentCategory() ) {
ViewListItem *v = m_viewlist.currentItem();
if ( v && v->type() != ViewListItem::ItemType_Category ) {
widget.insertAfter->setCurrentIndex( cat->indexOfChild( v ) + 1 );
}
}
}
}
//------------------------
ViewListEditCategoryDialog::ViewListEditCategoryDialog( ViewListWidget &viewlist, ViewListItem *item, QWidget *parent )
: KoDialog(parent)
{
setCaption( i18nc( "@title:window", "Configure Category" ) );
setButtons( KoDialog::Ok | KoDialog::Cancel );
setDefaultButton( Ok );
m_panel = new EditCategoryPanel( viewlist, item, this );
setMainWidget( m_panel );
enableButtonOk(false);
connect(this,&KoDialog::okClicked,this,&ViewListEditCategoryDialog::slotOk);
connect( m_panel, &EditCategoryPanel::enableButtonOk, this, &KoDialog::enableButtonOk );
connect(&viewlist, &ViewListWidget::viewListItemRemoved, this, &ViewListEditCategoryDialog::slotViewListItemRemoved);
}
void ViewListEditCategoryDialog::slotViewListItemRemoved( ViewListItem * )
{
reject();
}
void ViewListEditCategoryDialog::slotOk() {
if ( m_panel->ok() ) {
accept();
}
}
EditCategoryPanel::EditCategoryPanel( ViewListWidget &viewlist, ViewListItem *item, QWidget *parent )
: QWidget( parent ),
m_item( item ),
m_viewlist( viewlist )
{
widget.setupUi( this );
widget.viewname->setText( item->text( 0 ) );
QTextEdit e;
e.setText(item->toolTip(0));
widget.tooltip->setText(e.toPlainText());
fillAfter();
connect( widget.viewname, &QLineEdit::textChanged, this, &EditCategoryPanel::changed );
connect( widget.tooltip, &QLineEdit::textChanged, this, &EditCategoryPanel::changed );
connect( widget.insertAfter, SIGNAL(currentIndexChanged(int)), SLOT(changed()) );
}
bool EditCategoryPanel::ok()
{
if ( widget.viewname->text() != m_item->text( 0 ) ) {
m_item->setText( 0, widget.viewname->text() );
}
if ( widget.tooltip->text() != m_item->toolTip( 0 ) ) {
m_item->setToolTip( 0, widget.tooltip->text() );
}
bool ex = m_item->isExpanded();
m_viewlist.removeViewListItem( m_item );
int index = widget.insertAfter->currentIndex();
m_viewlist.addViewListItem( m_item, 0, index );
m_item->setExpanded( ex );
return true;
}
void EditCategoryPanel::changed()
{
bool disable = widget.viewname->text().isEmpty();
emit enableButtonOk( ! disable );
}
void EditCategoryPanel::fillAfter()
{
debugPlan;
widget.insertAfter->clear();
widget.insertAfter->addItem( i18n( "Top" ) );
int idx = 0;
QList<ViewListItem*> lst = m_viewlist.categories();
for ( int i = 0; i < lst.count(); ++i ) {
ViewListItem *itm = lst[ i ];
if ( m_item == itm ) {
idx = i;
} else {
widget.insertAfter->addItem( itm->text( 0 ), QVariant::fromValue( (void*)itm ) );
}
}
widget.insertAfter->setCurrentIndex( idx );
}
#ifdef PLAN_USE_KREPORT
//------ Reports
ViewListReportsDialog::ViewListReportsDialog( View *view, ViewListWidget &viewlist, const QDomDocument &doc, QWidget *parent )
: KoDialog(parent)
{
setCaption( i18nc( "@title:window", "Add Report" ) );
setButtons( KoDialog::Ok | KoDialog::Cancel );
setDefaultButton( Ok );
m_panel = new AddReportsViewPanel( view, viewlist, doc, this );
setMainWidget( m_panel );
enableButtonOk(true);
connect(this,SIGNAL(okClicked()),this,SLOT(slotOk()));
connect( m_panel, SIGNAL(enableButtonOk(bool)), SLOT(enableButtonOk(bool)) );
connect( m_panel, SIGNAL(viewCreated(KPlato::ViewBase*)), SIGNAL(viewCreated(KPlato::ViewBase*)));
connect(&viewlist, SIGNAL(viewListItemRemoved(KPlato::ViewListItem*)), SLOT(slotViewListItemRemoved(KPlato::ViewListItem*)));
}
void ViewListReportsDialog::slotViewListItemRemoved( ViewListItem * )
{
reject();
}
void ViewListReportsDialog::slotOk() {
if ( m_panel->ok() ) {
accept();
}
}
//------------------------
AddReportsViewPanel::AddReportsViewPanel( View *view, ViewListWidget &viewlist, const QDomDocument &doc, QWidget *parent )
: QWidget( parent ),
m_view( view ),
m_viewlist( viewlist ),
m_viewnameChanged( false ),
m_viewtipChanged( false ),
m_data(doc)
{
widget.setupUi( this );
// NOTE: these lists must match switch in ok() FIXME: refactor
m_viewtypes << "ReportView";
QStringList lst;
lst << i18n( "Report" );
widget.viewtype->addItems( lst );
foreach ( ViewListItem *item, m_viewlist.categories() ) {
m_categories.insert( item->text( 0 ), item );
}
widget.category->addItems( m_categories.keys() );
ViewListItem *curr = m_viewlist.currentCategory();
if ( curr ) {
widget.category->setCurrentIndex( m_categories.values().indexOf( curr ) );
}
fillAfter( m_categories.value( widget.category->currentText() ) );
viewtypeChanged( widget.viewtype->currentIndex() );
connect( widget.viewname, SIGNAL(textChanged(QString)), SLOT(changed()) );
connect( widget.tooltip, SIGNAL(textChanged(QString)), SLOT(changed()) );
connect( widget.viewname, SIGNAL(textChanged(QString)), SLOT(viewnameChanged(QString)) );
connect( widget.tooltip, SIGNAL(textChanged(QString)), SLOT(viewtipChanged(QString)) );
connect( widget.insertAfter, SIGNAL(currentIndexChanged(int)), SLOT(changed()) );
connect( widget.viewtype, SIGNAL(currentIndexChanged(int)), SLOT(viewtypeChanged(int)) );
connect( widget.category, SIGNAL(editTextChanged(QString)), SLOT(categoryChanged()) );
}
void AddReportsViewPanel::viewnameChanged( const QString &text )
{
m_viewnameChanged = ! text.isEmpty();
}
void AddReportsViewPanel::viewtipChanged( const QString &text )
{
m_viewtipChanged = ! text.isEmpty();
}
void AddReportsViewPanel::viewtypeChanged( int idx )
{
ViewInfo vi = m_view->defaultViewInfo( m_viewtypes.value( idx ) );
if ( widget.viewname->text().isEmpty() ) {
m_viewnameChanged = false;
}
if ( ! m_viewnameChanged ) {
widget.viewname->setText( vi.name );
m_viewnameChanged = false;
}
if ( widget.tooltip->text().isEmpty() ) {
m_viewtipChanged = false;
}
if ( ! m_viewtipChanged ) {
QTextEdit e;
e.setText(vi.tip);
widget.tooltip->setText(e.toPlainText());
m_viewtipChanged = false;
}
}
void AddReportsViewPanel::categoryChanged()
{
debugPlan<<widget.category->currentText();
fillAfter( m_categories.value( widget.category->currentText() ) );
changed();
}
void AddReportsViewPanel::fillAfter( ViewListItem *cat )
{
debugPlan<<cat;
widget.insertAfter->clear();
if ( cat ) {
widget.insertAfter->addItem( i18n( "Top" ) );
// int idx = 0;
for ( int i = 0; i < cat->childCount(); ++i ) {
ViewListItem *itm = static_cast<ViewListItem*>( cat->child( i ) );
widget.insertAfter->addItem( itm->text( 0 ), QVariant::fromValue( (void*)itm ) );
}
if ( cat == m_viewlist.currentCategory() ) {
ViewListItem *v = m_viewlist.currentItem();
if ( v && v->type() != ViewListItem::ItemType_Category ) {
widget.insertAfter->setCurrentIndex( cat->indexOfChild( v ) + 1 );
}
}
}
}
bool AddReportsViewPanel::ok()
{
QString n = widget.category->currentText();
ViewListItem *curr = m_categories.value( n );
QString c = curr == 0 ? n : curr->tag();
ViewListItem *cat = m_viewlist.addCategory( c, n );
if ( cat == 0 ) {
return false;
}
ViewBase *v = 0;
int index = widget.insertAfter->currentIndex();
int viewtype = widget.viewtype->currentIndex();
switch ( viewtype ) {
case 0: { // Report view
v = m_view->createReportView( cat, m_viewtypes.value( viewtype ), widget.viewname->text(), widget.tooltip->text(), index );
static_cast<ReportView*>(v)->loadXML(m_data);
break; }
default:
errorPlan<<"Unknown view type!";
break;
}
emit viewCreated( v );
return true;
}
void AddReportsViewPanel::changed()
{
bool disable = widget.viewname->text().isEmpty() | widget.viewtype->currentText().isEmpty() | widget.category->currentText().isEmpty();
emit enableButtonOk( ! disable );
}
#endif
} //KPlato namespace
diff --git a/src/kptviewlistdocker.cpp b/src/kptviewlistdocker.cpp
index 4c9c3987..b1d79427 100644
--- a/src/kptviewlistdocker.cpp
+++ b/src/kptviewlistdocker.cpp
@@ -1,98 +1,99 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Fredy Yanardi <fyanardi@gmail.com>
*
* 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 "kptviewlistdocker.h"
#include "kptviewlist.h"
#include "kptview.h"
#include "Help.h"
#include "kptdebug.h"
#include <KLocalizedString>
namespace KPlato
{
ViewListDocker::ViewListDocker(View *view)
{
updateWindowTitle( false );
setView(view);
}
ViewListDocker::~ViewListDocker()
{
}
View *ViewListDocker::view()
{
return m_view;
}
void ViewListDocker::setView(View *view)
{
m_view = view;
QWidget *wdg = widget();
if (wdg)
delete wdg;
m_viewlist = new ViewListWidget(view->getPart(), this);
setWhatsThis(m_viewlist->whatsThis());
setWidget(m_viewlist);
m_viewlist->setProject( &( view->getProject() ) );
connect( m_viewlist, SIGNAL(selectionChanged(ScheduleManager*)), view, SLOT(slotSelectionChanged(ScheduleManager*)) );
connect( view, &View::currentScheduleManagerChanged, m_viewlist, &ViewListWidget::setSelectedSchedule );
connect( m_viewlist, SIGNAL(updateViewInfo(ViewListItem*)), view, SLOT(slotUpdateViewInfo(ViewListItem*)) );
}
void ViewListDocker::slotModified()
{
setWindowTitle( xi18nc( "@title:window", "View Selector [modified]" ) );
}
void ViewListDocker::updateWindowTitle( bool modified )
{
if ( modified ) {
setWindowTitle( xi18nc( "@title:window", "View Selector [modified]" ) );
} else {
setWindowTitle(xi18nc( "@title:window", "View Selector"));
}
}
//----------
ViewListDockerFactory::ViewListDockerFactory(View *view)
{
m_view = view;
}
QString ViewListDockerFactory::id() const
{
return QString("KPlatoViewList");
}
QDockWidget* ViewListDockerFactory::createDockWidget()
{
ViewListDocker *widget = new ViewListDocker(m_view);
widget->setObjectName(id());
return widget;
}
} //namespace KPlato
diff --git a/src/kptworkpackageconfigpanel.cpp b/src/kptworkpackageconfigpanel.cpp
index 9c68192d..595695ff 100644
--- a/src/kptworkpackageconfigpanel.cpp
+++ b/src/kptworkpackageconfigpanel.cpp
@@ -1,39 +1,40 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "kptworkpackageconfigpanel.h"
#include "kptduration.h"
#include "kptmycombobox_p.h"
#include "calligraplansettings.h"
namespace KPlato
{
WorkPackageConfigPanel::WorkPackageConfigPanel(QWidget *p )
: QWidget(p)
{
setupUi(this);
kcfg_RetrieveUrl->setMode( KFile::Directory );
kcfg_SaveUrl->setMode( KFile::Directory );
}
} //KPlato namespace
diff --git a/src/libs/kernel/KPlatoXmlLoaderBase.cpp b/src/libs/kernel/KPlatoXmlLoaderBase.cpp
index 9fe83a49..fbfeb9f3 100644
--- a/src/libs/kernel/KPlatoXmlLoaderBase.cpp
+++ b/src/libs/kernel/KPlatoXmlLoaderBase.cpp
@@ -1,1361 +1,1362 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
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 "KPlatoXmlLoaderBase.h"
#include "kptlocale.h"
#include "kptxmlloaderobject.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptcalendar.h"
#include "kptschedule.h"
#include "kptrelation.h"
#include "kptresource.h"
#include "kptaccount.h"
#include "kptappointment.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QTimeZone>
using namespace KPlato;
KPlatoXmlLoaderBase::KPlatoXmlLoaderBase()
{
}
bool KPlatoXmlLoaderBase::load( Project *project, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"project";
// load locale first
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "locale" ) {
Locale *l = project->locale();
l->setCurrencySymbol( e.attribute( "currency-symbol", l->currencySymbol() ) );
//NOTE: decimalplaces missing
}
}
QList<Calendar*> cals;
QString s;
bool ok = false;
project->setName( element.attribute( "name" ) );
project->removeId( project->id() );
project->setId( element.attribute( "id" ) );
project->registerNodeId( project );
project->setLeader( element.attribute( "leader" ) );
project->setDescription( element.attribute( "description" ) );
QTimeZone tz( element.attribute( "timezone" ).toLatin1() );
if ( tz.isValid() ) {
project->setTimeZone( tz );
} else warnPlanXml<<"No timezone specified, using default (local)";
status.setProjectTimeZone( project->timeZone() );
// Allow for both numeric and text
s = element.attribute( "scheduling", "0" );
project->setConstraint( ( Node::ConstraintType ) ( s.toInt( &ok ) ) );
if ( ! ok )
project->setConstraint( s );
if ( project->constraint() != Node::MustStartOn && project->constraint() != Node::MustFinishOn ) {
errorPlanXml << "Illegal constraint: " << project->constraintToString();
project->setConstraint( Node::MustStartOn );
}
s = element.attribute( "start-time" );
if ( ! s.isEmpty() ) {
project->setConstraintStartTime( DateTime::fromString( s, project->timeZone() ) );
}
s = element.attribute( "end-time" );
if ( ! s.isEmpty() ) {
project->setConstraintEndTime( DateTime::fromString( s, project->timeZone() ) );
}
// Load the project children
// Do calendars first, they only reference other calendars
//debugPlanXml<<"Calendars--->";
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "calendar" ) {
// Load the calendar.
// Referenced by resources
Calendar * child = new Calendar();
child->setProject( project );
if ( load( child, e, status ) ) {
cals.append( child ); // temporary, reorder later
} else {
// TODO: Complain about this
errorPlanXml << "Failed to load calendar";
delete child;
}
} else if ( e.tagName() == "standard-worktime" ) {
// Load standard worktime
StandardWorktime * child = new StandardWorktime();
if ( load( child, e, status ) ) {
project->setStandardWorktime( child );
} else {
errorPlanXml << "Failed to load standard worktime";
delete child;
}
}
}
// calendars references calendars in arbritary saved order
bool added = false;
do {
added = false;
QList<Calendar*> lst;
while ( !cals.isEmpty() ) {
Calendar *c = cals.takeFirst();
if ( c->parentId().isEmpty() ) {
project->addCalendar( c, status.baseCalendar() ); // handle pre 0.6 version
added = true;
//debugPlanXml<<"added to project:"<<c->name();
} else {
Calendar *par = project->calendar( c->parentId() );
if ( par ) {
project->addCalendar( c, par );
added = true;
//debugPlanXml<<"added:"<<c->name()<<" to parent:"<<par->name();
} else {
lst.append( c ); // treat later
//debugPlanXml<<"treat later:"<<c->name();
}
}
}
cals = lst;
} while ( added );
if ( ! cals.isEmpty() ) {
errorPlanXml<<"All calendars not saved!";
}
//debugPlanXml<<"Calendars<---";
// Resource groups and resources, can reference calendars
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "resource-group" ) {
// Load the resources
// References calendars
ResourceGroup * child = new ResourceGroup();
if ( load( child, e, status ) ) {
project->addResourceGroup( child );
} else {
// TODO: Complain about this
delete child;
}
}
}
// The main stuff
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "project" ) {
//debugPlanXml<<"Sub project--->";
/* // Load the subproject
Project * child = new Project( this );
if ( child->load( e ) ) {
if ( !addTask( child, this ) ) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}*/
} else if ( e.tagName() == "task" ) {
//debugPlanXml<<"Task--->";
// Load the task (and resourcerequests).
// Depends on resources already loaded
Task *child = new Task( project );
if ( load( child, e, status ) ) {
if ( ! project->addTask( child, project ) ) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}
}
}
// These go last
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
debugPlanXml<<n.isElement();
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "accounts" ) {
//debugPlanXml<<"Accounts--->";
// Load accounts
// References tasks
if ( ! load( project->accounts(), e, status ) ) {
errorPlanXml << "Failed to load accounts";
}
} else if ( e.tagName() == "relation" ) {
//debugPlanXml<<"Relation--->";
// Load the relation
// References tasks
Relation * child = new Relation();
if ( ! load( child, e, status ) ) {
// TODO: Complain about this
errorPlanXml << "Failed to load relation";
delete child;
}
//debugPlanXml<<"Relation<---";
} else if ( e.tagName() == "schedules" ) {
debugPlanXml<<"Project schedules & task appointments--->";
// References tasks and resources
KoXmlNode sn = e.firstChild();
for ( ; ! sn.isNull(); sn = sn.nextSibling() ) {
if ( ! sn.isElement() ) {
continue;
}
KoXmlElement el = sn.toElement();
//debugPlanXml<<el.tagName()<<" Version="<<status.version();
ScheduleManager *sm = 0;
bool add = false;
if ( status.version() <= "0.5" ) {
if ( el.tagName() == "schedule" ) {
sm = project->findScheduleManagerByName( el.attribute( "name" ) );
if ( sm == 0 ) {
sm = new ScheduleManager( *project, el.attribute( "name" ) );
add = true;
}
}
} else if ( el.tagName() == "plan" ) {
sm = new ScheduleManager( *project );
add = true;
}
if ( sm ) {
if ( load( sm, el, status ) ) {
if ( add )
project->addScheduleManager( sm );
} else {
errorPlanXml << "Failed to load schedule manager";
delete sm;
}
} else {
debugPlanXml<<"No schedule manager ?!";
}
}
debugPlanXml<<"Project schedules & task appointments<---";
} else if ( e.tagName() == "resource-teams" ) {
//debugPlanXml<<"Resource teams--->";
// References other resources
KoXmlNode tn = e.firstChild();
for ( ; ! tn.isNull(); tn = tn.nextSibling() ) {
if ( ! tn.isElement() ) {
continue;
}
KoXmlElement el = tn.toElement();
if ( el.tagName() == "team" ) {
Resource *r = project->findResource( el.attribute( "team-id" ) );
Resource *tm = project->findResource( el.attribute( "member-id" ) );
if ( r == 0 || tm == 0 ) {
errorPlanXml<<"resource-teams: cannot find resources";
continue;
}
if ( r == tm ) {
errorPlanXml<<"resource-teams: a team cannot be a member of itself";
continue;
}
r->addTeamMemberId( tm->id() );
} else {
errorPlanXml<<"resource-teams: unhandled tag"<<el.tagName();
}
}
//debugPlanXml<<"Resource teams<---";
} else if ( e.tagName() == "wbs-definition" ) {
load( project->wbsDefinition(), e, status );
} else if ( e.tagName() == "locale" ) {
// handled earlier
} else if ( e.tagName() == "resource-group" ) {
// handled earlier
} else if ( e.tagName() == "calendar" ) {
// handled earlier
} else if ( e.tagName() == "standard-worktime" ) {
// handled earlier
} else if ( e.tagName() == "project" ) {
// handled earlier
} else if ( e.tagName() == "task" ) {
// handled earlier
} else {
warnPlanXml<<"Unhandled tag:"<<e.tagName();
}
}
// set schedule parent
foreach ( Schedule *s, project->schedules() ) {
project->setParentSchedule( s );
}
debugPlanXml<<"Project loaded:"<<project<<project->name()<<project->allNodes();
return true;
}
bool KPlatoXmlLoaderBase::load( Task *task, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"task";
QString s;
bool ok = false;
task->setId( element.attribute("id") );
task->setName( element.attribute("name") );
task->setLeader( element.attribute("leader") );
task->setDescription( element.attribute("description") );
//debugPlanXml<<m_name<<": id="<<m_id;
// Allow for both numeric and text
QString constraint = element.attribute("scheduling","0");
task->setConstraint( (Node::ConstraintType)constraint.toInt(&ok) );
if ( ! ok ) {
task->setConstraint( constraint );
}
s = element.attribute("constraint-starttime");
if ( ! s.isEmpty() ) {
task->setConstraintStartTime( DateTime::fromString( s, status.projectTimeZone() ) );
}
s = element.attribute("constraint-endtime");
if ( ! s.isEmpty() ) {
task->setConstraintEndTime( DateTime::fromString( s, status.projectTimeZone() ) );
}
task->setStartupCost( element.attribute("startup-cost", "0.0").toDouble() );
task->setShutdownCost( element.attribute("shutdown-cost", "0.0").toDouble() );
// Load the task children
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "project") {
// Load the subproject
/* Project *child = new Project(this, status);
if (child->load(e)) {
if (!project.addSubTask(child, this)) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}*/
} else if (e.tagName() == "task") {
// Load the task
Task *child = new Task( task );
if ( load( child, e, status) ) {
if ( ! status.project().addSubTask( child, task ) ) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}
} else if ( e.tagName() == "resource" ) {
// tasks don't have resources
} else if (e.tagName() == "estimate" || ( /*status.version() < "0.6" &&*/ e.tagName() == "effort" ) ) {
// Load the estimate
load( task->estimate(), e, status);
} else if ( e.tagName() == "resourcegroup-request" ) {
// Load the resource request
// Handle multiple requests to same group gracefully (Not really allowed)
ResourceGroupRequest *r = task->requests().findGroupRequestById( e.attribute( "group-id" ) );
if ( r ) {
warnPlanXml<<"Multiple requests to same group, loading into existing group";
if ( ! load( r, e, status ) ) {
errorPlanXml<<"Failed to load resource request";
}
} else {
r = new ResourceGroupRequest();
if ( load( r, e, status ) ) {
task->addRequest(r);
} else {
errorPlanXml<<"Failed to load resource request";
delete r;
}
}
} else if ( e.tagName() == "workpackage" ) {
load( task->workPackage(), e, status );
} else if ( e.tagName() == "progress" ) {
load( task->completion(), e, status );
} else if ( e.tagName() == "schedules" ) {
KoXmlNode n = e.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
if ( el.tagName() == "schedule" ) {
NodeSchedule *sch = new NodeSchedule();
if ( loadNodeSchedule( sch, el, status ) ) {
sch->setNode( task );
task->addSchedule( sch );
} else {
errorPlanXml<<"Failed to load schedule";
delete sch;
}
}
}
} else if (e.tagName() == "documents") {
load( task->documents(), e, status );
} else if ( e.tagName() == "workpackage-log" ) {
KoXmlNode n = e.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
if ( el.tagName() == "workpackage" ) {
WorkPackage *wp = new WorkPackage( task );
if ( loadWpLog( wp, el, status ) ) {
task->addWorkPackage( wp );
} else {
errorPlanXml<<"Failed to load logged workpackage";
delete wp;
}
}
}
}
}
//debugPlanXml<<m_name<<" loaded";
return true;
}
bool KPlatoXmlLoaderBase::load( Calendar *calendar, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"calendar"<<element.text();
//bool ok;
calendar->setId( element.attribute( "id" ) );
calendar->setParentId( element.attribute( "parent" ) );
calendar->setName( element.attribute( "name","" ) );
QTimeZone tz( element.attribute( "timezone" ).toLatin1() );
if ( tz.isValid() ) {
calendar->setTimeZone( tz );
} else warnPlanXml<<"No timezone specified, use default (local)";
bool dc = (bool)element.attribute( "default","0" ).toInt();
if ( dc ) {
status.project().setDefaultCalendar( calendar );
}
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "weekday") {
if ( ! load( calendar->weekdays(), e, status ) ) {
return false;
}
}
if ( e.tagName() == "day" ) {
CalendarDay *day = new CalendarDay();
if ( load( day, e, status ) ) {
if ( ! day->date().isValid() ) {
delete day;
errorPlanXml<<calendar->name()<<": Failed to load calendarDay - Invalid date";
} else {
CalendarDay *d = calendar->findDay( day->date() );
if ( d ) {
// already exists, keep the new
delete calendar->takeDay(d);
warnPlanXml<<calendar->name()<<" Load calendarDay - Date already exists";
}
calendar->addDay( day );
}
} else {
delete day;
errorPlanXml<<"Failed to load calendarDay";
return true; // don't throw away the whole calendar
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( CalendarDay *day, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"day";
bool ok=false;
day->setState( QString( element.attribute( "state", "-1" ) ).toInt( &ok ) );
if ( day->state() < 0 ) {
return false;
}
//debugPlanXml<<" state="<<m_state;
QString s = element.attribute( "date" );
if ( ! s.isEmpty() ) {
day->setDate( QDate::fromString( s, Qt::ISODate ) );
if ( ! day->date().isValid() ) {
day->setDate( QDate::fromString( s ) );
}
}
day->clearIntervals();
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "interval" ) {
//debugPlanXml<<"Interval start="<<e.attribute("start")<<" end="<<e.attribute("end");
QString st = e.attribute("start");
if (st.isEmpty() ) {
errorPlanXml<<"Empty interval";
continue;
}
QTime start = QTime::fromString( st );
int length = 0;
if ( status.version() <= "0.6.1" ) {
QString en = e.attribute( "end" );
if ( en.isEmpty() ) {
errorPlanXml<<"Invalid interval end";
continue;
}
QTime end = QTime::fromString( en );
length = start.msecsTo( end );
} else {
length = e.attribute("length", "0").toInt();
}
if ( length <= 0 ) {
errorPlanXml<<"Invalid interval length";
continue;
}
day->addInterval( new TimeInterval( start, length ) );
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( CalendarWeekdays *weekdays, const KoXmlElement& element, XMLLoaderObject& status)
{
debugPlanXml<<"weekdays";
bool ok;
int dayNo = QString( element.attribute( "day","-1" ) ).toInt( &ok );
if (dayNo < 0 || dayNo > 6) {
errorPlanXml<<"Illegal weekday: "<<dayNo;
return true; // we continue anyway
}
CalendarDay *day = weekdays->weekday( dayNo + 1 );
if ( day == 0 ) {
errorPlanXml<<"No weekday: "<<dayNo;
return false;
}
if ( ! load( day, element, status ) ) {
day->setState( CalendarDay::None );
}
return true;
}
bool KPlatoXmlLoaderBase::load( StandardWorktime *swt, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"swt";
swt->setYear( Duration::fromString( element.attribute( "year" ), Duration::Format_Hour ) );
swt->setMonth( Duration::fromString( element.attribute( "month" ), Duration::Format_Hour ) );
swt->setWeek( Duration::fromString( element.attribute( "week" ), Duration::Format_Hour ) );
swt->setDay( Duration::fromString( element.attribute( "day" ), Duration::Format_Hour ) );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "calendar" ) {
// pre 0.6 version stored base calendar in standard worktime
if ( status.version() >= "0.6" ) {
warnPlanXml<<"Old format, calendar in standard worktime";
warnPlanXml<<"Tries to load anyway";
}
// try to load anyway
Calendar *calendar = new Calendar;
if ( load( calendar, e, status ) ) {
status.project().addCalendar( calendar );
calendar->setDefault( true );
status.project().setDefaultCalendar( calendar ); // hmmm
status.setBaseCalendar( calendar );
} else {
delete calendar;
errorPlanXml<<"Failed to load calendar";
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Relation *relation, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"relation";
relation->setParent( status.project().findNode( element.attribute( "parent-id" ) ) );
if (relation->parent() == 0) {
warnPlanXml<<"Parent node == 0, cannot find id:"<<element.attribute( "parent-id" );
return false;
}
relation->setChild( status.project().findNode( element.attribute( "child-id" ) ) );
if ( relation->child() == 0 ) {
warnPlanXml<<"Child node == 0, cannot find id:"<<element.attribute( "child-id" );
return false;
}
if ( relation->child() == relation->parent() ) {
warnPlanXml<<"Parent node == child node";
return false;
}
if ( ! relation->parent()->legalToLink( relation->child() ) ) {
warnPlanXml<<"Realation is not legal:"<<relation->parent()->name()<<"->"<<relation->child()->name();
return false;
}
relation->setType( element.attribute("type") );
relation->setLag( Duration::fromString( element.attribute( "lag" ) ) );
if ( ! relation->parent()->addDependChildNode( relation ) ) {
errorPlanXml<<"Failed to add relation: Child="<<relation->child()->name()<<" parent="<<relation->parent()->name();
return false;
}
if ( ! relation->child()->addDependParentNode( relation ) ) {
relation->parent()->takeDependChildNode( relation );
errorPlanXml<<"Failed to add relation: Child="<<relation->child()->name()<<" parent="<<relation->parent()->name();
return false;
}
//debugPlanXml<<"Added relation: Child="<<relation->child()->name()<<" parent="<<relation->parent()->name();
return true;
}
bool KPlatoXmlLoaderBase::load( ResourceGroup *rg, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"resource-group";
rg->setId( element.attribute( "id" ) );
rg->setName( element.attribute( "name" ) );
rg->setType( element.attribute( "type" ) );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "resource") {
// Load the resource
Resource *child = new Resource();
if ( load( child, e, status ) ) {
status.project().addResource( rg, child );
} else {
// TODO: Complain about this
delete child;
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Resource *resource, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"resource";
const Locale *locale = status.project().locale();
QString s;
resource->setId( element.attribute( "id" ) );
resource->setName( element.attribute( "name" ) );
resource->setInitials( element.attribute( "initials" ) );
resource->setEmail( element.attribute( "email" ) );
resource->setType( element.attribute( "type" ) );
resource->setCalendar( status.project().findCalendar( element.attribute( "calendar-id" ) ) );
resource->setUnits( element.attribute( "units", "100" ).toInt() );
s = element.attribute( "available-from" );
if ( ! s.isEmpty() ) {
resource->setAvailableFrom( DateTime::fromString( s, status.projectTimeZone() ) );
}
s = element.attribute( "available-until" );
if ( ! s.isEmpty() ) {
resource->setAvailableUntil( DateTime::fromString( s, status.projectTimeZone() ) );
}
resource->setNormalRate( locale->readMoney( element.attribute( "normal-rate" ) ) );
resource->setOvertimeRate( locale->readMoney( element.attribute( "overtime-rate" ) ) );
resource->setAccount( status.project().accounts().findAccount( element.attribute( "account" ) ) );
KoXmlElement e;
KoXmlElement parent = element.namedItem( "required-resources" ).toElement();
forEachElement( e, parent ) {
if (e.nodeName() == "resource") {
QString id = e.attribute( "id" );
if ( id.isEmpty() ) {
warnPlanXml<<"Missing resource id";
continue;
}
resource->addRequiredId( id );
}
}
parent = element.namedItem( "external-appointments" ).toElement();
forEachElement( e, parent ) {
if ( e.nodeName() == "project" ) {
QString id = e.attribute( "id" );
if ( id.isEmpty() ) {
errorPlanXml<<"Missing project id";
continue;
}
resource->clearExternalAppointments( id ); // in case...
AppointmentIntervalList lst;
load( lst, e, status );
Appointment *a = new Appointment();
a->setIntervals( lst );
a->setAuxcilliaryInfo( e.attribute( "name", "Unknown" ) );
resource->addExternalAppointment( id, a );
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Accounts &accounts, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"accounts";
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "account") {
Account *child = new Account();
if ( load( child, e, status ) ) {
accounts.insert( child );
} else {
// TODO: Complain about this
warnPlanXml<<"Loading failed";
delete child;
}
}
}
if (element.hasAttribute("default-account")) {
accounts.setDefaultAccount( accounts.findAccount( element.attribute( "default-account" ) ) );
if ( accounts.defaultAccount() == 0) {
warnPlanXml<<"Could not find default account.";
}
}
return true;
}
bool KPlatoXmlLoaderBase::load(Account* account, const KoXmlElement& element, XMLLoaderObject& status)
{
debugPlanXml<<"account";
account->setName( element.attribute( "name" ) );
account->setDescription( element.attribute( "description" ) );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "costplace") {
Account::CostPlace *child = new Account::CostPlace( account );
if ( load( child, e, status ) ) {
account->append( child );
} else {
delete child;
}
} else if ( e.tagName() == "account" ) {
Account *child = new Account();
if ( load( child, e, status ) ) {
account->insert( child );
} else {
// TODO: Complain about this
warnPlanXml<<"Loading failed";
delete child;
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load(Account::CostPlace* cp, const KoXmlElement& element, XMLLoaderObject& status)
{
debugPlanXml<<"cost-place";
cp->setObjectId( element.attribute( "object-id" ) );
if ( cp->objectId().isEmpty() ) {
// check old format
cp->setObjectId( element.attribute( "node-id" ) );
if ( cp->objectId().isEmpty() ) {
errorPlanXml<<"No object id";
return false;
}
}
cp->setNode( status.project().findNode( cp->objectId() ) );
if ( cp->node() == 0) {
cp->setResource( status.project().findResource( cp->objectId() ) );
if ( cp->resource() == 0 ) {
errorPlanXml<<"Cannot find object with id: "<<cp->objectId();
return false;
}
}
bool on = (bool)(element.attribute("running-cost").toInt());
if ( on ) cp->setRunning( on );
on = (bool)(element.attribute("startup-cost").toInt());
if ( on ) cp->setStartup( on );
on = (bool)(element.attribute("shutdown-cost").toInt());
if ( on ) cp->setShutdown( on );
return true;
}
bool KPlatoXmlLoaderBase::load( ScheduleManager *manager, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"schedule-manager";
MainSchedule *sch = 0;
if ( status.version() <= "0.5" ) {
manager->setUsePert( false );
MainSchedule *sch = loadMainSchedule( manager, element, status );
if ( sch && sch->type() == Schedule::Expected ) {
sch->setManager( manager );
manager->setExpected( sch );
} else {
delete sch;
}
return true;
}
manager->setName( element.attribute( "name" ) );
manager->setManagerId( element.attribute( "id" ) );
manager->setUsePert( element.attribute( "distribution" ).toInt() == 1 );
manager->setAllowOverbooking( (bool)( element.attribute( "overbooking" ).toInt() ) );
manager->setCheckExternalAppointments( (bool)( element.attribute( "check-external-appointments" ).toInt() ) );
manager->setSchedulingDirection( (bool)(element.attribute( "scheduling-direction" ).toInt() ) );
manager->setBaselined( (bool)( element.attribute( "baselined" ).toInt() ) );
manager->setSchedulerPluginId( element.attribute( "scheduler-plugin-id" ) );
manager->setRecalculate( (bool)( element.attribute( "recalculate" ).toInt() ) );
manager->setRecalculateFrom( DateTime::fromString( element.attribute( "recalculate-from" ), status.projectTimeZone() ) );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
//debugPlanXml<<e.tagName();
if ( e.tagName() == "schedule" ) {
sch = loadMainSchedule( manager, e, status );
if ( sch && sch->type() == Schedule::Expected ) {
sch->setManager( manager );
manager->setExpected( sch ); break;
} else {
delete sch;
}
} else if ( e.tagName() == "plan" ) {
ScheduleManager *sm = new ScheduleManager( status.project() );
if ( load( sm, e, status ) ) {
status.project().addScheduleManager( sm, manager );
} else {
errorPlanXml<<"Failed to load schedule manager";
delete sm;
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Schedule *schedule, const KoXmlElement& element, XMLLoaderObject& /*status*/ )
{
debugPlanXml<<"schedule";
schedule->setName( element.attribute( "name" ) );
schedule->setType( element.attribute( "type" ) );
schedule->setId( element.attribute( "id" ).toLong() );
return true;
}
MainSchedule* KPlatoXmlLoaderBase::loadMainSchedule( ScheduleManager* /*manager*/, const KoXmlElement& element, XMLLoaderObject& status )
{
debugPlanXml<<"main-schedule";
MainSchedule *sch = new MainSchedule();
if ( loadMainSchedule( sch, element, status ) ) {
status.project().addSchedule( sch );
sch->setNode( &(status.project()) );
status.project().setParentSchedule( sch );
// If it's here, it's scheduled!
sch->setScheduled( true );
} else {
errorPlanXml << "Failed to load schedule";
delete sch;
sch = 0;
}
return sch;
}
bool KPlatoXmlLoaderBase::loadMainSchedule( MainSchedule *ms, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml;
QString s;
load( ms, element, status );
s = element.attribute( "start" );
if ( !s.isEmpty() ) {
ms->startTime = DateTime::fromString( s, status.projectTimeZone() );
}
s = element.attribute( "end" );
if ( !s.isEmpty() ) {
ms->endTime = DateTime::fromString( s, status.projectTimeZone() );
}
ms->duration = Duration::fromString( element.attribute( "duration" ) );
ms->constraintError = element.attribute( "scheduling-conflict", "0" ).toInt();
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
if ( el.tagName() == "appointment" ) {
// Load the appointments.
// Resources and tasks must already be loaded
Appointment * child = new Appointment();
if ( ! load( child, el, status, *ms ) ) {
// TODO: Complain about this
errorPlanXml << "Failed to load appointment" << endl;
delete child;
}
} else if ( el.tagName() == "criticalpath-list" ) {
// Tasks must already be loaded
for ( KoXmlNode n1 = el.firstChild(); ! n1.isNull(); n1 = n1.nextSibling() ) {
if ( ! n1.isElement() ) {
continue;
}
KoXmlElement e1 = n1.toElement();
if ( e1.tagName() != "criticalpath" ) {
continue;
}
QList<Node*> lst;
for ( KoXmlNode n2 = e1.firstChild(); ! n2.isNull(); n2 = n2.nextSibling() ) {
if ( ! n2.isElement() ) {
continue;
}
KoXmlElement e2 = n2.toElement();
if ( e2.tagName() != "node" ) {
continue;
}
QString s = e2.attribute( "id" );
Node *node = status.project().findNode( s );
if ( node ) {
lst.append( node );
} else {
errorPlanXml<<"Failed to find node id="<<s;
}
}
ms->m_pathlists.append( lst );
}
ms->criticalPathListCached = true;
}
}
return true;
}
bool KPlatoXmlLoaderBase::loadNodeSchedule(NodeSchedule* schedule, const KoXmlElement &element, XMLLoaderObject& status)
{
debugPlanXml<<"node-schedule";
QString s;
load( schedule, element, status );
s = element.attribute( "earlystart" );
if ( s.isEmpty() ) { // try version < 0.6
s = element.attribute( "earlieststart" );
}
if ( ! s.isEmpty() ) {
schedule->earlyStart = DateTime::fromString( s, status.projectTimeZone() );
}
s = element.attribute( "latefinish" );
if ( s.isEmpty() ) { // try version < 0.6
s = element.attribute( "latestfinish" );
}
if ( ! s.isEmpty() ) {
schedule->lateFinish = DateTime::fromString( s, status.projectTimeZone() );
}
s = element.attribute( "latestart" );
if ( ! s.isEmpty() ) {
schedule->lateStart = DateTime::fromString( s, status.projectTimeZone() );
}
s = element.attribute( "earlyfinish" );
if ( ! s.isEmpty() ) {
schedule->earlyFinish = DateTime::fromString( s, status.projectTimeZone() );
}
s = element.attribute( "start" );
if ( ! s.isEmpty() )
schedule->startTime = DateTime::fromString( s, status.projectTimeZone() );
s = element.attribute( "end" );
if ( !s.isEmpty() )
schedule->endTime = DateTime::fromString( s, status.projectTimeZone() );
s = element.attribute( "start-work" );
if ( !s.isEmpty() )
schedule->workStartTime = DateTime::fromString( s, status.projectTimeZone() );
s = element.attribute( "end-work" );
if ( !s.isEmpty() )
schedule->workEndTime = DateTime::fromString( s, status.projectTimeZone() );
schedule->duration = Duration::fromString( element.attribute( "duration" ) );
schedule->inCriticalPath = element.attribute( "in-critical-path", "0" ).toInt();
schedule->resourceError = element.attribute( "resource-error", "0" ).toInt();
schedule->resourceOverbooked = element.attribute( "resource-overbooked", "0" ).toInt();
schedule->resourceNotAvailable = element.attribute( "resource-not-available", "0" ).toInt();
schedule->constraintError = element.attribute( "scheduling-conflict", "0" ).toInt();
schedule->notScheduled = element.attribute( "not-scheduled", "1" ).toInt();
schedule->positiveFloat = Duration::fromString( element.attribute( "positive-float" ) );
schedule->negativeFloat = Duration::fromString( element.attribute( "negative-float" ) );
schedule->freeFloat = Duration::fromString( element.attribute( "free-float" ) );
return true;
}
bool KPlatoXmlLoaderBase::load( WBSDefinition &def, const KoXmlElement &element, XMLLoaderObject &/*status*/ )
{
debugPlanXml<<"wbs-def";
def.setProjectCode( element.attribute( "project-code" ) );
def.setProjectSeparator( element.attribute( "project-separator" ) );
def.setLevelsDefEnabled( (bool)element.attribute( "levels-enabled", "0" ).toInt() );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "default" ) {
def.defaultDef().code = e.attribute( "code", "Number" );
def.defaultDef().separator = e.attribute( "separator", "." );
} else if (e.tagName() == "levels") {
KoXmlNode n = e.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
WBSDefinition::CodeDef d;
d.code = el.attribute( "code" );
d.separator = el.attribute( "separator" );
int lvl = el.attribute( "level", "-1" ).toInt();
if ( lvl >= 0 ) {
def.setLevelsDef( lvl, d );
} else errorPlanXml<<"Invalid levels definition";
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Documents &documents, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"documents";
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "document") {
Document *doc = new Document();
if ( ! load( doc, e, status ) ) {
warnPlanXml<<"Failed to load document";
status.addMsg( XMLLoaderObject::Errors, "Failed to load document" );
delete doc;
} else {
documents.addDocument( doc );
status.addMsg( i18n( "Document loaded, URL=%1", doc->url().url() ) );
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Document *document, const KoXmlElement &element, XMLLoaderObject &status )
{
debugPlanXml<<"document";
Q_UNUSED(status);
document->setUrl( QUrl( element.attribute( "url" ) ) );
document->setType( ( Document::Type )( element.attribute( "type" ).toInt() ) );
document->setStatus( element.attribute( "status" ) );
document->setSendAs( ( Document::SendAs )( element.attribute( "sendas" ).toInt() ) );
return true;
}
bool KPlatoXmlLoaderBase::load( Estimate* estimate, const KoXmlElement& element, XMLLoaderObject& status )
{
debugPlanXml<<"estimate";
estimate->setType( element.attribute( "type") );
estimate->setRisktype( element.attribute( "risk" ) );
if ( status.version() <= "0.6" ) {
estimate->setUnit( ( Duration::Unit )( element.attribute( "display-unit", QString().number( Duration::Unit_h ) ).toInt()) );
QList<qint64> s = status.project().standardWorktime()->scales();
estimate->setExpectedEstimate( Estimate::scale( Duration::fromString( element.attribute("expected" ) ), estimate->unit(), s ) );
estimate->setOptimisticEstimate( Estimate::scale( Duration::fromString( element.attribute("optimistic" ) ), estimate->unit(), s ) );
estimate->setPessimisticEstimate( Estimate::scale( Duration::fromString( element.attribute("pessimistic") ), estimate->unit(), s ) );
} else {
if ( status.version() <= "0.6.2" ) {
// 0 pos in unit is now Unit_Y, so add 3 to get the correct new unit
estimate->setUnit( ( Duration::Unit )( element.attribute( "unit", QString().number( Duration::Unit_ms - 3 ) ).toInt() + 3 ) );
} else {
estimate->setUnit( Duration::unitFromString( element.attribute( "unit" ) ) );
}
estimate->setExpectedEstimate( element.attribute( "expected", "0.0" ).toDouble() );
estimate->setOptimisticEstimate( element.attribute( "optimistic", "0.0" ).toDouble() );
estimate->setPessimisticEstimate( element.attribute( "pessimistic", "0.0" ).toDouble() );
estimate->setCalendar( status.project().findCalendar( element.attribute("calendar-id" ) ) );
}
return true;
}
bool KPlatoXmlLoaderBase::load( ResourceGroupRequest* gr, const KoXmlElement& element, XMLLoaderObject& status )
{
debugPlanXml<<"resourcegroup-request";
gr->setGroup( status.project().findResourceGroup(element.attribute("group-id")) );
if ( gr->group() == 0 ) {
errorPlanXml<<"The referenced resource group does not exist: group id="<<element.attribute("group-id");
return false;
}
gr->group()->registerRequest( gr );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "resource-request") {
ResourceRequest *r = new ResourceRequest();
if ( load( r, e, status ) ) {
gr->addResourceRequest( r );
} else {
errorPlanXml<<"Failed to load resource request";
delete r;
}
}
}
// meaning of m_units changed
int x = element.attribute("units").toInt() -gr->count();
gr->setUnits( x > 0 ? x : 0 );
return true;
}
bool KPlatoXmlLoaderBase::load( ResourceRequest *rr, const KoXmlElement& element, XMLLoaderObject& status)
{
debugPlanXml<<"resource-request";
rr->setResource( status.project().resource(element.attribute("resource-id")) );
if (rr->resource() == 0) {
warnPlanXml<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id");
return false;
}
rr->setUnits( element.attribute("units").toInt() );
KoXmlElement parent = element.namedItem( "required-resources" ).toElement();
KoXmlElement e;
QList<Resource*> required;
forEachElement( e, parent ) {
if (e.nodeName() == "resource") {
QString id = e.attribute( "id" );
if ( id.isEmpty() ) {
errorPlanXml<<"Missing project id";
continue;
}
Resource *r = status.project().resource(id);
if (r == 0) {
warnPlanXml<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id");
} else {
if ( r != rr->resource() ) {
required << r;
}
}
}
}
rr->setRequiredResources( required );
return true;
}
bool KPlatoXmlLoaderBase::load( WorkPackage &wp, const KoXmlElement& element, XMLLoaderObject& status )
{
debugPlanXml<<"workpackage";
Q_UNUSED(status);
wp.setOwnerName( element.attribute( "owner" ) );
wp.setOwnerId( element.attribute( "owner-id" ) );
return true;
}
bool KPlatoXmlLoaderBase::loadWpLog( WorkPackage *wp, KoXmlElement& element, XMLLoaderObject& status )
{
debugPlanXml<<"wplog";
wp->setOwnerName( element.attribute( "owner" ) );
wp->setOwnerId( element.attribute( "owner-id" ) );
wp->setTransmitionStatus( wp->transmitionStatusFromString( element.attribute( "status" ) ) );
wp->setTransmitionTime( DateTime( QDateTime::fromString( element.attribute( "time" ), Qt::ISODate ) ) );
return load( wp->completion(), element, status );
}
bool KPlatoXmlLoaderBase::load( Completion &completion, const KoXmlElement& element, XMLLoaderObject& status )
{
debugPlanXml<<"completion";
QString s;
completion.setStarted( (bool)element.attribute("started", "0").toInt() );
completion.setFinished( (bool)element.attribute("finished", "0").toInt() );
s = element.attribute("startTime");
if (!s.isEmpty()) {
completion.setStartTime( DateTime::fromString(s, status.projectTimeZone()) );
}
s = element.attribute("finishTime");
if (!s.isEmpty()) {
completion.setFinishTime( DateTime::fromString(s, status.projectTimeZone() ) );
}
completion.setEntrymode( element.attribute( "entrymode" ) );
if (status.version() < "0.6") {
if ( completion.isStarted() ) {
Completion::Entry *entry = new Completion::Entry( element.attribute("percent-finished", "0").toInt(), Duration::fromString(element.attribute("remaining-effort")), Duration::fromString(element.attribute("performed-effort")) );
entry->note = element.attribute("note");
QDate date = completion.startTime().date();
if ( completion.isFinished() ) {
date = completion.finishTime().date();
}
// almost the best we can do ;)
completion.addEntry( date, entry );
}
} else {
KoXmlElement e;
forEachElement(e, element) {
if (e.tagName() == "completion-entry") {
QDate date;
s = e.attribute("date");
if ( !s.isEmpty() ) {
date = QDate::fromString( s, Qt::ISODate );
}
if ( !date.isValid() ) {
warnPlanXml<<"Invalid date: "<<date<<s;
continue;
}
Completion::Entry *entry = new Completion::Entry( e.attribute("percent-finished", "0").toInt(), Duration::fromString(e.attribute("remaining-effort")), Duration::fromString(e.attribute("performed-effort")) );
completion.addEntry( date, entry );
} else if (e.tagName() == "used-effort") {
KoXmlElement el;
forEachElement(el, e) {
if (el.tagName() == "resource") {
QString id = el.attribute( "id" );
Resource *r = status.project().resource( id );
if ( r == 0 ) {
warnPlanXml<<"Cannot find resource, id="<<id;
continue;
}
Completion::UsedEffort *ue = new Completion::UsedEffort();
completion.addUsedEffort( r, ue );
load( ue, el, status );
}
}
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Completion::UsedEffort* ue, const KoXmlElement& element, XMLLoaderObject& /*status*/)
{
debugPlanXml<<"used-effort";
KoXmlElement e;
forEachElement(e, element) {
if (e.tagName() == "actual-effort") {
QDate date = QDate::fromString( e.attribute("date"), Qt::ISODate );
if ( date.isValid() ) {
Completion::UsedEffort::ActualEffort a;
a.setNormalEffort( Duration::fromString( e.attribute( "normal-effort" ) ) );
a.setOvertimeEffort( Duration::fromString( e.attribute( "overtime-effort" ) ) );
ue->setEffort( date, a );
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load( Appointment *appointment, const KoXmlElement& element, XMLLoaderObject& status, Schedule &sch )
{
debugPlanXml<<"appointment";
Node *node = status.project().findNode(element.attribute("task-id"));
if (node == 0) {
errorPlanXml<<"The referenced task does not exists: "<<element.attribute("task-id");
return false;
}
Resource *res = status.project().resource(element.attribute("resource-id"));
if (res == 0) {
errorPlanXml<<"The referenced resource does not exists: resource id="<<element.attribute("resource-id");
return false;
}
if (!res->addAppointment( appointment, sch ) ) {
errorPlanXml<<"Failed to add appointment to resource: "<<res->name();
return false;
}
if ( ! node->addAppointment( appointment, sch ) ) {
errorPlanXml<<"Failed to add appointment to node: "<<node->name();
appointment->resource()->takeAppointment( appointment );
return false;
}
//debugPlanXml<<"res="<<m_resource<<" node="<<m_node;
AppointmentIntervalList lst = appointment->intervals();
load( lst, element, status );
if ( lst.isEmpty() ) {
errorPlanXml<<"Appointment interval list is empty (added anyway): "<<node->name()<<res->name();
return false;
}
appointment->setIntervals( lst );
return true;
}
bool KPlatoXmlLoaderBase::load(AppointmentIntervalList& lst, const KoXmlElement& element, XMLLoaderObject& status)
{
debugPlanXml<<"appointment-interval-list";
KoXmlElement e;
forEachElement(e, element) {
if (e.tagName() == "interval") {
AppointmentInterval a;
if ( load( a, e, status ) ) {
lst.add(a);
} else {
errorPlanXml<<"Could not load interval";
}
}
}
return true;
}
bool KPlatoXmlLoaderBase::load(AppointmentInterval& interval, const KoXmlElement& element, XMLLoaderObject& status)
{
bool ok;
QString s = element.attribute("start");
if (!s.isEmpty()) {
interval.setStartTime( DateTime::fromString(s, status.projectTimeZone()) );
}
s = element.attribute("end");
if (!s.isEmpty()) {
interval.setEndTime( DateTime::fromString(s, status.projectTimeZone()) );
}
double l = element.attribute("load", "100").toDouble(&ok);
if (ok) {
interval.setLoad( l );
}
debugPlanXml<<"interval:"<<interval;
return interval.isValid();
}
diff --git a/src/libs/kernel/kptaccount.cpp b/src/libs/kernel/kptaccount.cpp
index b4d32da7..ba856f01 100644
--- a/src/libs/kernel/kptaccount.cpp
+++ b/src/libs/kernel/kptaccount.cpp
@@ -1,957 +1,958 @@
/* This file is part of the KDE project
Copyright (C) 2005, 2006, 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptaccount.h"
#include "kptduration.h"
#include "kptproject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QDate>
namespace KPlato
{
Account::Account()
: m_name(),
m_description(),
m_list(0),
m_parent(0),
m_accountList(),
m_costPlaces() {
}
Account::Account(const QString& name, const QString& description)
: m_name(name),
m_description(description),
m_list(0),
m_parent(0),
m_accountList(),
m_costPlaces() {
}
Account::~Account() {
//debugPlan<<m_name;
if (m_list) {
m_list->accountDeleted(this); // default account
}
while (!m_accountList.isEmpty()) {
delete m_accountList.takeFirst();
}
while (!m_costPlaces.isEmpty()) {
delete m_costPlaces.takeFirst();
}
take(this);
}
bool Account::isDefaultAccount() const
{
return m_list == 0 ? false : m_list->defaultAccount() == this;
}
void Account::changed() {
if ( m_list ) {
m_list->accountChanged( this );
}
}
void Account::setName(const QString& name) {
if (findAccount() == this) {
removeId();
}
m_name = name;
insertId();
changed();
}
void Account::setDescription(const QString& desc)
{
m_description = desc;
changed();
}
void Account::insert(Account *account, int index) {
Q_ASSERT(account);
int i = index == -1 ? m_accountList.count() : index;
m_accountList.insert(i, account);
account->setList(m_list);
account->setParent(this);
insertId(account);
account->insertChildren();
}
void Account::insertChildren() {
foreach (Account *a, m_accountList) {
a->setList(m_list);
a->setParent(this);
insertId(a);
a->insertChildren();
}
}
void Account::take(Account *account) {
if (account == 0) {
return;
}
if (account->parent()) {
if (account->m_list) {
// emits remove signals,
// sets account->m_list = 0,
// calls us again
account->m_list->take(account);
} else {
// child account has been removed from Accounts
// so now we can remove child account from us
m_accountList.removeAt(m_accountList.indexOf(account));
}
} else if (account->m_list) {
// we are top level so just needs Accounts to remove us
account->m_list->take(account);
}
//debugPlan<<account->name();
}
bool Account::isChildOf( const Account *account) const
{
if ( m_parent == 0 ) {
return false;
}
if ( m_parent == account ) {
return true;
}
return m_parent->isChildOf( account );
}
bool Account::isBaselined( long id ) const
{
foreach ( CostPlace *p, m_costPlaces ) {
if ( p->isBaselined( id ) ) {
return true;
}
}
return false;
}
bool Account::load(KoXmlElement &element, Project &project) {
m_name = element.attribute("name");
m_description = element.attribute("description");
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "costplace") {
Account::CostPlace *child = new Account::CostPlace(this);
if (child->load(e, project)) {
append(child);
} else {
delete child;
}
} else if (e.tagName() == "account") {
Account *child = new Account();
if (child->load(e, project)) {
m_accountList.append(child);
} else {
// TODO: Complain about this
warnPlan<<"Loading failed";
delete child;
}
}
}
return true;
}
void Account::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("account");
element.appendChild(me);
me.setAttribute("name", m_name);
me.setAttribute("description", m_description);
foreach (Account::CostPlace *cp, m_costPlaces) {
cp->save(me);
}
foreach (Account *a, m_accountList) {
a->save(me);
}
}
Account::CostPlace *Account::findCostPlace(const Resource &resource) const
{
foreach (Account::CostPlace *cp, m_costPlaces) {
if (&resource == cp->resource()) {
return cp;
}
}
return 0;
}
Account::CostPlace *Account::findRunning(const Resource &resource) const {
foreach (Account::CostPlace *cp, m_costPlaces) {
if (&resource == cp->resource() && cp->running()) {
return cp;
}
}
return 0;
}
void Account::removeRunning(const Resource &resource) {
Account::CostPlace *cp = findRunning(resource);
if (cp && cp->running()) {
cp->setRunning(false);
if (cp->isEmpty()) {
deleteCostPlace(cp);
}
}
}
void Account::addRunning(Resource &resource) {
Account::CostPlace *cp = findRunning(resource);
if (cp) {
cp->setRunning(true);
changed();
return;
}
cp = new CostPlace(this, &resource, true);
append(cp);
changed();
}
Account::CostPlace *Account::findCostPlace(const Node &node) const
{
foreach (Account::CostPlace *cp, m_costPlaces) {
if (&node == cp->node()) {
return cp;
}
}
return 0;
}
Account::CostPlace *Account::findRunning(const Node &node) const
{
for (CostPlace *cp : m_costPlaces) {
if (cp->node() == &node && cp->running()) {
return cp;
}
}
return 0;
}
void Account::removeRunning(const Node &node) {
CostPlace *cp = findRunning(node);
if (cp) {
cp->setRunning(false);
if (cp->isEmpty()) {
deleteCostPlace(cp);
}
changed();
}
}
void Account::addRunning(Node &node) {
Account::CostPlace *cp = findCostPlace(node);
if (cp) {
cp->setRunning(true);
changed();
return;
}
cp = new CostPlace(this, &node, true);
append(cp);
changed();
}
Account::CostPlace *Account::findStartup(const Node &node) const {
for (CostPlace *cp : m_costPlaces) {
if (cp->node() == &node && cp->startup()) {
return cp;
}
}
return 0;
}
void Account::removeStartup(const Node &node) {
Account::CostPlace *cp = findStartup(node);
if (cp) {
cp->setStartup(false);
if (cp->isEmpty()) {
deleteCostPlace(cp);
}
changed();
}
}
void Account::addStartup(Node &node) {
Account::CostPlace *cp = findCostPlace(node);
if (cp) {
cp->setStartup(true);
changed();
return;
}
cp = new CostPlace(this, &node, false, true, false);
append(cp);
changed();
}
Account::CostPlace *Account::findShutdown(const Node &node) const {
for (CostPlace *cp : m_costPlaces) {
if (cp->node() == &node && cp->shutdown()) {
return cp;
}
}
return 0;
}
void Account::removeShutdown(const Node &node) {
Account::CostPlace *cp = findShutdown(node);
if (cp) {
cp->setShutdown(false);
if (cp->isEmpty()) {
deleteCostPlace(cp);
}
changed();
}
}
void Account::addShutdown(Node &node) {
Account::CostPlace *cp = findCostPlace(node);
if (cp) {
cp->setShutdown(true);
changed();
return;
}
cp = new CostPlace(this, &node, false, false, true);
append(cp);
changed();
}
Account *Account::findAccount(const QString &id) const {
if (m_list)
return m_list->findAccount(id);
return 0;
}
bool Account::removeId(const QString &id) {
return (m_list ? m_list->removeId(id) : false);
}
bool Account::insertId() {
return insertId(this);
}
bool Account::insertId(Account *account) {
return (m_list ? m_list->insertId(account) : false);
}
void Account::deleteCostPlace(CostPlace *cp) {
//debugPlan;
int i = m_costPlaces.indexOf(cp);
if (i != -1)
m_costPlaces.removeAt(i);
delete cp;
}
EffortCostMap Account::plannedCost(long id) const
{
return plannedCost( QDate(), QDate(), id );
}
EffortCostMap Account::plannedCost(const QDate &start, const QDate &end, long id) const {
EffortCostMap ec;
if ( ! isElement() ) {
foreach ( Account *a, m_accountList ) {
ec += a->plannedCost( start, end, id );
}
}
foreach (Account::CostPlace *cp, m_costPlaces) {
ec += plannedCost( *cp, start, end, id );
}
if (isDefaultAccount()) {
QList<Node*> list = m_list == 0 ? QList<Node*>() : m_list->allNodes();
foreach (Node *n, list) {
if ( n->numChildren() > 0 ) {
continue;
}
if (n->runningAccount() == 0) {
ec += n->plannedEffortCostPrDay(start, end, id);
}
if (n->startupAccount() == 0) {
if ( ( ! start.isValid() || n->startTime( id ).date() >= start ) &&
( ! end.isValid() || n->startTime( id ).date() <= end ) ) {
ec.add(n->startTime( id ).date(), EffortCost(Duration::zeroDuration, n->startupCost()));
}
}
if (n->shutdownAccount() == 0) {
if ( ( ! start.isValid() || n->endTime( id ).date() >= start ) &&
( ! end.isValid() || n->endTime( id ).date() <= end ) ) {
ec.add(n->endTime( id ).date(), EffortCost(Duration::zeroDuration, n->shutdownCost()));
}
}
}
}
return ec;
}
EffortCostMap Account::plannedCost( const Account::CostPlace &cp, const QDate &start, const QDate &end, long id) const
{
EffortCostMap ec;
if ( cp.node() ) {
Node &node = *(cp.node());
//debugPlan<<"n="<<n->name();
if (cp.running()) {
ec += node.plannedEffortCostPrDay(start, end, id);
}
if (cp.startup()) {
if ( ( ! start.isValid() || node.startTime( id ).date() >= start ) &&
( ! end.isValid() || node.startTime( id ).date() <= end ) ) {
ec.add(node.startTime( id ).date(), EffortCost(Duration::zeroDuration, node.startupCost()));
}
}
if (cp.shutdown()) {
if ( ( ! start.isValid() || node.endTime( id ).date() >= start ) &&
( ! end.isValid() || node.endTime( id ).date() <= end ) ) {
ec.add(node.endTime( id ).date(), EffortCost(Duration::zeroDuration, node.shutdownCost()));
}
}
} else if ( cp.resource() ) {
if ( cp.running() ) {
ec += cp.resource()->plannedEffortCostPrDay(start, end, id);
}
}
return ec;
}
EffortCostMap Account::actualCost(long id) const
{
return actualCost( QDate(), QDate(), id );
}
EffortCostMap Account::actualCost(const QDate &start, const QDate &end, long id) const
{
EffortCostMap ec;
if ( ! isElement() ) {
foreach ( Account *a, m_accountList ) {
ec += a->actualCost( start, end, id );
}
}
foreach (Account::CostPlace *cp, costPlaces()) {
ec += actualCost( *cp, start, end, id );
}
if (isDefaultAccount()) {
QList<Node*> list = m_list == 0 ? QList<Node*>() : m_list->allNodes();
foreach (Node *n, list) {
if ( n->numChildren() > 0 ) {
continue;
}
if (n->runningAccount() == 0) {
//debugPlan<<"default, running:"<<n->name();
ec += n->actualEffortCostPrDay(start, end, id);
}
Task *t = dynamic_cast<Task*>( n ); // only tasks have completion
if ( t ) {
if (n->startupAccount() == 0 && t->completion().isStarted()) {
const QDate startDate = t->completion().startTime().date();
if ( ( ! start.isValid() || startDate >= start ) &&
( ! end.isValid() || startDate <= end ) ) {
ec.add(startDate, EffortCost(Duration::zeroDuration, n->startupCost()));
}
}
if (n->shutdownAccount() == 0 && t->completion().isFinished()) {
//debugPlan<<"default, shutdown:"<<n->name();
const QDate finishDate = t->completion().finishTime().date();
if ( ( ! start.isValid() || finishDate >= start ) &&
( ! end.isValid() || finishDate <= end ) ) {
ec.add(finishDate, EffortCost(Duration::zeroDuration, n->shutdownCost()));
}
}
}
}
}
return ec;
}
EffortCostMap Account::actualCost(const Account::CostPlace &cp, const QDate &start, const QDate &end, long id) const
{
EffortCostMap ec;
if ( cp.node() ) {
Node &node = *(cp.node());
if (cp.running()) {
ec += node.actualEffortCostPrDay(start, end, id);
}
Task *t = dynamic_cast<Task*>( &node ); // only tasks have completion
if ( t ) {
if (cp.startup() && t->completion().isStarted()) {
const QDate startDate = t->completion().startTime().date();
if ( ( ! start.isValid() || startDate >= start ) &&
( ! end.isValid() || startDate <= end ) ) {
ec.add(startDate, EffortCost(Duration::zeroDuration, node.startupCost()));
}
}
if (cp.shutdown() && t->completion().isFinished()) {
const QDate finishDate = t->completion().finishTime().date();
if ( ( ! start.isValid() || finishDate >= start ) &&
( ! end.isValid() || finishDate <= end ) ) {
ec.add(finishDate, EffortCost(Duration::zeroDuration, node.shutdownCost()));
}
}
}
} else if ( cp.resource() && m_list ) {
foreach ( Node *n, m_list->allNodes() ) {
if ( n->type() == Node::Type_Task ) {
ec += n->actualEffortCostPrDay( cp.resource(), start, end, id );
}
}
}
return ec;
}
//------------------------------------
Account::CostPlace::CostPlace(Account *acc, Node *node, bool running, bool strtup, bool shutdown)
: m_account(acc),
m_objectId(node->id()),
m_node(node),
m_resource(0),
m_running( false ),
m_startup( false ),
m_shutdown( false )
{
if (node) {
if (running) setRunning(running);
if (strtup) setStartup(strtup);
if (shutdown) setShutdown(shutdown);
}
}
Account::CostPlace::CostPlace(Account *acc, Resource *resource, bool running)
: m_account(acc),
m_objectId(resource->id()),
m_node(0),
m_resource(resource),
m_running( false ),
m_startup( false ),
m_shutdown( false )
{
if (resource) {
if (running) setRunning(running);
}
}
Account::CostPlace::~CostPlace() {
if (m_node) {
if (m_running) {
m_running = false;
m_node->setRunningAccount(0);
}
if (m_startup) {
m_startup = false;
m_node->setStartupAccount(0);
}
if (m_shutdown) {
m_shutdown = false;
m_node->setShutdownAccount(0);
}
}
if (m_resource) {
if (m_running) {
m_running = false;
m_resource->setAccount(0);
}
}
}
bool Account::CostPlace::isBaselined( long id ) const
{
if ( m_node ) {
if ( m_running ) {
if ( m_node->isBaselined( id ) ) {
return true;
}
}
if ( m_startup ) {
if ( m_node->isBaselined( id ) ) {
return true;
}
}
if ( m_shutdown ) {
if ( m_node->isBaselined( id ) ) {
return true;
}
}
}
if ( m_resource ) {
if ( m_running ) {
if ( m_resource->isBaselined( id ) ) {
return true;
}
}
}
return false;
}
void Account::CostPlace::setNode(Node* node)
{
Q_ASSERT( ! m_node );
m_node = node;
}
void Account::CostPlace::setResource(Resource* resource)
{
Q_ASSERT( ! m_resource );
m_resource = resource;
}
void Account::CostPlace::setRunning(bool on )
{
if (m_running == on) {
return;
}
m_running = on;
if (m_node) {
m_node->setRunningAccount(on ? m_account : 0);
} else if ( m_resource ) {
m_resource->setAccount(on ? m_account : 0);
}
}
void Account::CostPlace::setStartup(bool on )
{
if (m_startup == on) {
return;
}
m_startup = on;
if (m_node)
m_node->setStartupAccount(on ? m_account : 0);
}
void Account::CostPlace::setShutdown(bool on )
{
if (m_shutdown == on) {
return;
}
m_shutdown = on;
if (m_node)
m_node->setShutdownAccount(on ? m_account : 0);
}
//TODO
bool Account::CostPlace::load(KoXmlElement &element, Project &project) {
//debugPlan;
m_objectId = element.attribute("object-id");
if (m_objectId.isEmpty()) {
// check old format
m_objectId = element.attribute("node-id");
if (m_objectId.isEmpty()) {
errorPlan<<"No object id";
return false;
}
}
m_node = project.findNode(m_objectId);
if (m_node == 0) {
m_resource = project.findResource(m_objectId);
if ( m_resource == 0 ) {
errorPlan<<"Cannot find object with id: "<<m_objectId;
return false;
}
}
bool on = (bool)(element.attribute("running-cost").toInt());
if ( on ) setRunning( on );
on = (bool)(element.attribute("startup-cost").toInt());
if ( on ) setStartup( on );
on = (bool)(element.attribute("shutdown-cost").toInt());
if ( on ) setShutdown( on );
return true;
}
void Account::CostPlace::save(QDomElement &element) const {
//debugPlan;
QDomElement me = element.ownerDocument().createElement("costplace");
element.appendChild(me);
me.setAttribute("object-id", m_objectId);
me.setAttribute("running-cost", QString::number(m_running));
me.setAttribute("startup-cost", QString::number(m_startup));
me.setAttribute("shutdown-cost", QString::number(m_shutdown));
}
void Account::CostPlace::setObjectId( const QString& id )
{
m_objectId = id;
}
QString Account::CostPlace::objectId() const
{
return m_objectId;
}
//---------------------------------
Accounts::Accounts(Project &project)
: m_project(project),
m_accountList(),
m_idDict(),
m_defaultAccount(0) {
}
Accounts::~Accounts() {
//debugPlan;
m_defaultAccount = 0;
while (!m_accountList.isEmpty()) {
delete m_accountList.takeFirst();
}
}
EffortCostMap Accounts::plannedCost(const Account &account, long id) const
{
return account.plannedCost( id );
}
EffortCostMap Accounts::plannedCost(const Account &account, const QDate &start, const QDate &end, long id) const
{
return account.plannedCost( start, end, id );
}
EffortCostMap Accounts::actualCost(const Account &account, long id) const
{
return account.actualCost( id );
}
EffortCostMap Accounts::actualCost(const Account &account, const QDate &start, const QDate &end, long id) const
{
return account.actualCost( start, end, id );
}
void Accounts::insert(Account *account, Account *parent, int index) {
Q_ASSERT(account);
if ( parent == 0 ) {
int i = index == -1 ? m_accountList.count() : index;
emit accountToBeAdded( parent, i );
m_accountList.insert(i, account);
account->setList(this);
account->setParent(0); // incase...
insertId(account);
account->insertChildren();
} else {
int i = index == -1 ? parent->accountList().count() : index;
emit accountToBeAdded( parent, i );
parent->insert( account, i );
}
//debugPlan<<account->name();
emit accountAdded( account );
}
void Accounts::take(Account *account){
if (account == 0) {
return;
}
account->m_list = 0;
removeId(account->name());
if (account->parent()) {
emit accountToBeRemoved( account );
account->parent()->take(account);
emit accountRemoved( account );
//debugPlan<<account->name();
return;
}
int i = m_accountList.indexOf(account);
if (i != -1) {
emit accountToBeRemoved( account );
m_accountList.removeAt(i);
emit accountRemoved( account );
}
//debugPlan<<account->name();
}
bool Accounts::load(KoXmlElement &element, Project &project) {
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "account") {
Account *child = new Account();
if (child->load(e, project)) {
insert(child);
} else {
// TODO: Complain about this
warnPlan<<"Loading failed";
delete child;
}
}
}
if (element.hasAttribute("default-account")) {
m_defaultAccount = findAccount(element.attribute("default-account"));
if (m_defaultAccount == 0) {
warnPlan<<"Could not find default account.";
}
}
return true;
}
void Accounts::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("accounts");
element.appendChild(me);
if (m_defaultAccount) {
me.setAttribute("default-account", m_defaultAccount->name());
}
foreach (Account *a, m_accountList) {
a->save(me);
}
}
QStringList Accounts::costElements() const {
QStringList l;
foreach (const QString &key, m_idDict.uniqueKeys()) {
if (m_idDict[key]->isElement())
l << key;
}
return l;
}
QStringList Accounts::nameList() const {
return m_idDict.uniqueKeys();
}
Account *Accounts::findRunningAccount(const Resource &resource) const {
foreach (Account *a, m_idDict) {
if (a->findRunning(resource)) {
return a;
}
}
return 0;
}
Account *Accounts::findRunningAccount(const Node &node) const {
foreach (Account *a, m_idDict) {
if (a->findRunning(node))
return a;
}
return 0;
}
Account *Accounts::findStartupAccount(const Node &node) const {
foreach (Account *a, m_idDict) {
if (a->findStartup(node))
return a;
}
return 0;
}
Account *Accounts::findShutdownAccount(const Node &node) const {
foreach (Account *a, m_idDict) {
if (a->findShutdown(node))
return a;
}
return 0;
}
Account *Accounts::findAccount(const QString &id) const {
return m_idDict.value(id);
}
bool Accounts::insertId(Account *account) {
Q_ASSERT(account);
Account *a = findAccount(account->name());
if (a == 0) {
//debugPlan<<"'"<<account->name()<<"' inserted";
m_idDict.insert(account->name(), account);
return true;
}
if (a == account) {
debugPlan<<"'"<<a->name()<<"' already exists";
return true;
}
//TODO: Create unique id?
warnPlan<<"Insert failed, creating unique id";
account->setName( uniqueId( account->name() ) ); // setName() calls insertId !!
return false;
}
bool Accounts::removeId(const QString &id) {
bool res = m_idDict.remove(id);
//debugPlan<<id<<": removed="<<res;
return res;
}
QString Accounts::uniqueId( const QString &seed ) const
{
QString s = seed.isEmpty() ? i18n( "Account" ) + ".%1" : seed + ".%1";
int i = 1;
QString n = s.arg( i );
while ( findAccount( n ) ) {
n = s.arg( ++i );
}
return n;
}
void Accounts::setDefaultAccount(Account *account)
{
Account *a = m_defaultAccount;
m_defaultAccount = account;
if ( a ) {
emit changed( a );
}
if ( account ) {
emit changed( account );
}
if ( a != account ) {
emit defaultAccountChanged();
}
}
void Accounts::accountDeleted(Account *account)
{
if (account == m_defaultAccount) {
m_defaultAccount = 0;
}
}
void Accounts::accountChanged( Account *account )
{
emit changed( account );
}
QList<Node*> Accounts::allNodes() const
{
return m_project.allNodes();
}
#ifndef NDEBUG
void Accounts::printDebug(const QString& indent) {
debugPlan<<indent<<"Accounts:"<<m_accountList.count()<<" children";
foreach( Account *a, m_accountList ) {
a->printDebug( indent + " !" );
}
}
void Account::printDebug(const QString& indent) {
debugPlan<<indent<<"--- Account:"<<m_name<<":"<<m_accountList.count()<<" children";
foreach( Account *a, m_accountList ) {
a->printDebug( indent + " !" );
}
}
#endif
} //namespace KPlato
diff --git a/src/libs/kernel/kptappointment.cpp b/src/libs/kernel/kptappointment.cpp
index 4e83e660..ba2d5e50 100644
--- a/src/libs/kernel/kptappointment.cpp
+++ b/src/libs/kernel/kptappointment.cpp
@@ -1,1063 +1,1064 @@
/* This file is part of the KDE project
Copyright (C) 2005 - 2011 Dag Andersen <danders@get2net.dk>
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptappointment.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptdatetime.h"
#include "kptcalendar.h"
#include "kpteffortcostmap.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
namespace KPlato
{
class Resource;
AppointmentInterval::AppointmentInterval()
: d( new AppointmentIntervalData() )
{
//debugPlan<<this;
}
AppointmentInterval::AppointmentInterval( const AppointmentInterval &interval )
: d( interval.d )
{
}
AppointmentInterval::AppointmentInterval( const DateTime &start, const DateTime &end, double load )
: d( new AppointmentIntervalData() )
{
setStartTime( start );
setEndTime( end );
setLoad( load );
#ifndef NDEBUG
if ( start.isValid() && end.isValid() && start.timeZone() != end.timeZone() ) {
warnPlan<<"Timezones not equal:"<<start.timeZone()<<end.timeZone();
}
#endif
}
AppointmentInterval::AppointmentInterval( QDate date, const TimeInterval& timeInterval, double load )
: d( new AppointmentIntervalData() )
{
Q_ASSERT( date.isValid() && timeInterval.isValid() );
DateTime s( date, timeInterval.startTime() );
DateTime e( date, timeInterval.endTime() );
if ( timeInterval.endsMidnight() ) {
e = e.addDays( 1 );
}
setStartTime( s );
setEndTime( e );
setLoad( load );
#ifndef NDEBUG
if (s.isValid() && e.isValid()) {
debugPlan<<*this;
Q_ASSERT(s.timeZone() == e.timeZone());
}
#endif
}
AppointmentInterval::~AppointmentInterval() {
//debugPlan<<this;
}
const DateTime &AppointmentInterval::startTime() const
{
return d->start;
}
void AppointmentInterval::setStartTime( const DateTime &time )
{
if ( d->start != time ) {
d->start = time;
}
}
const DateTime &AppointmentInterval::endTime() const
{
return d->end;
}
void AppointmentInterval::setEndTime( const DateTime &time )
{
if ( d->end != time ) {
d->end = time;
}
}
double AppointmentInterval::load() const
{
return d->load;
}
void AppointmentInterval::setLoad( double load )
{
if ( d->load != load ) {
d->load = load;
}
}
Duration AppointmentInterval::effort() const
{
return ( d->end - d->start ) * d->load / 100;
}
Duration AppointmentInterval::effort(const DateTime &start, const DateTime &end) const {
if (start >= d->end || end <= d->start) {
return Duration::zeroDuration;
}
DateTime s = (start > d->start ? start : d->start);
DateTime e = (end < d->end ? end : d->end);
return (e - s) * d->load / 100;
}
Duration AppointmentInterval::effort(QDate time, bool upto) const {
DateTime t( time );
//debugPlan<<time<<upto<<t<<d->start<<d->end;
if (upto) {
if (t <= d->start) {
return Duration::zeroDuration;
}
DateTime e = (t < d->end ? t : d->end);
Duration eff = (e - d->start) * d->load / 100;
//debugPlan<<d->toString();
return eff;
}
// from time till end
if (t >= d->end) {
return Duration::zeroDuration;
}
DateTime s = (t > d->start ? t : d->start);
return (d->end - s) * d->load / 100;
}
bool AppointmentInterval::loadXML(KoXmlElement &element, XMLLoaderObject &status) {
//debugPlan;
bool ok;
QString s = element.attribute(QStringLiteral("start"));
if (!s.isEmpty())
d->start = DateTime::fromString(s, status.projectTimeZone());
s = element.attribute(QStringLiteral("end"));
if (!s.isEmpty())
d->end = DateTime::fromString(s, status.projectTimeZone());
d->load = element.attribute(QStringLiteral("load"), QStringLiteral("100")).toDouble(&ok);
if (!ok) d->load = 100;
if ( ! isValid() ) {
errorPlan<<"AppointmentInterval::loadXML: Invalid interval:"<<*this<<element.attribute(QStringLiteral("start"))<<element.attribute(QStringLiteral("end"));
} else {
Q_ASSERT(d->start.timeZone() == d->end.timeZone());
}
return isValid();
}
void AppointmentInterval::saveXML(QDomElement &element) const
{
Q_ASSERT( isValid() );
QDomElement me = element.ownerDocument().createElement(QStringLiteral("interval"));
element.appendChild(me);
me.setAttribute(QStringLiteral("start"), d->start.toString( Qt::ISODate ));
me.setAttribute(QStringLiteral("end"), d->end.toString( Qt::ISODate ));
me.setAttribute(QStringLiteral("load"), QString::number(d->load));
}
bool AppointmentInterval::isValid() const {
return d->start.isValid() && d->end.isValid() && d->start < d->end && d->load >= 0.0;
}
AppointmentInterval AppointmentInterval::firstInterval(const AppointmentInterval &interval, const DateTime &from) const {
//debugPlan<<interval.startTime().toString()<<" -"<<interval.endTime().toString()<<" from="<<from.toString();
DateTime f = from;
DateTime s1 = d->start;
DateTime e1 = d->end;
DateTime s2 = interval.startTime();
DateTime e2 = interval.endTime();
AppointmentInterval a;
if (f.isValid() && f >= e1 && f >= e2) {
return a;
}
if (f.isValid()) {
if (s1 < f && f < e1) {
s1 = f;
}
if (s2 < f && f < e2) {
s2 = f;
}
} else {
f = s1 < s2 ? s1 : s2;
}
if (s1 < s2) {
a.setStartTime(s1);
if (e1 <= s2) {
a.setEndTime(e1);
} else {
a.setEndTime(s2);
}
a.setLoad(d->load);
} else if (s1 > s2) {
a.setStartTime(s2);
if (e2 <= s1) {
a.setEndTime(e2);
} else {
a.setEndTime(s1);
}
a.setLoad(interval.load());
} else {
a.setStartTime(s1);
if (e1 <= e2)
a.setEndTime(e1);
else
a.setEndTime(e2);
a.setLoad(d->load + interval.load());
}
//debugPlan<<a.startTime().toString()<<" -"<<a.endTime().toString()<<" load="<<a.load();
return a;
}
bool AppointmentInterval::operator==( const AppointmentInterval &interval ) const
{
return d->start == interval.d->start && d->end == interval.d->end && d->load == interval.d->load;
}
bool AppointmentInterval::operator<( const AppointmentInterval &other ) const
{
if ( d->start < other.d->start ) {
//debugPlan<<"this start"<<d->start<<" < "<<other.d->start;
return true;
} else if ( other.d->start < d->start ) {
//debugPlan<<"other start"<<other.d->start<<" < "<<d->start;
return false;
}
// Start is assumed equal
//debugPlan<<"this end"<<d->end<<" < "<<other.d->end;
return d->end < other.d->end;
}
bool AppointmentInterval::intersects( const AppointmentInterval &other ) const
{
return ( d->start < other.d->end && d->end > other.d->start );
}
AppointmentInterval AppointmentInterval::interval( const DateTime &start, const DateTime &end ) const
{
// TODO: Find and fix those that call with "wrong" timezone (should be local zone atm)
const DateTime s = start.toTimeZone( d->start.timeZone() );
const DateTime e = end.toTimeZone( d->end.timeZone() );
if ( s <= d->start && e >= d->end ) {
return *this;
}
return AppointmentInterval( qMax( s, d->start ), qMin( e, d->end ), d->load );
}
QString AppointmentInterval::toString() const
{
return QStringLiteral( "%1 - %2, %3%" ).arg( d->start.toString( Qt::ISODate ) ).arg( d->end.toString( Qt::ISODate ) ).arg( d->load );
}
QDebug operator<<( QDebug dbg, const KPlato::AppointmentInterval &i )
{
dbg<<"AppointmentInterval["<<i.startTime()<<i.endTime()<<i.load()<<"%"<<']';
return dbg;
}
//-----------------------
AppointmentIntervalList::AppointmentIntervalList()
{
}
AppointmentIntervalList::AppointmentIntervalList( const QMultiMap<QDate, AppointmentInterval> &other)
: m_map( other )
{
}
QMultiMap< QDate, AppointmentInterval > AppointmentIntervalList::map()
{
return m_map;
}
const QMultiMap< QDate, AppointmentInterval >& AppointmentIntervalList::map() const
{
return m_map;
}
AppointmentIntervalList &AppointmentIntervalList::operator=( const AppointmentIntervalList &lst )
{
m_map = lst.m_map;
return *this;
}
AppointmentIntervalList &AppointmentIntervalList::operator-=( const AppointmentIntervalList &lst )
{
if ( lst.m_map.isEmpty() ) {
return *this;
}
foreach ( const AppointmentInterval &ai, lst.map() ) {
subtract( ai );
}
return *this;
}
void AppointmentIntervalList::subtract( const DateTime &st, const DateTime &et, double load )
{
subtract( AppointmentInterval( st, et, load ) );
}
void AppointmentIntervalList::subtract( const AppointmentInterval &interval )
{
//debugPlan<<st<<et<<load;
if ( m_map.isEmpty() ) {
return;
}
if ( ! interval.isValid() ) {
return;
}
const DateTime st = interval.startTime();
const DateTime et = interval.endTime();
Q_ASSERT( st < et );
const double load = interval.load();
// debugPlan<<"subtract:"<<*this<<endl<<"minus"<<interval;
for ( QDate date = st.date(); date <= et.date(); date = date.addDays( 1 ) ) {
if ( ! m_map.contains( date ) ) {
continue;
}
QList<AppointmentInterval> l;
QList<AppointmentInterval> v = m_map.values( date );
m_map.remove( date );
foreach ( const AppointmentInterval &vi, v ) {
if ( ! vi.intersects( interval ) ) {
//debugPlan<<"subtract: not intersect:"<<vi<<interval;
l.insert( 0, vi );
//if ( ! l.at(0).isValid() ) { debugPlan<<vi<<interval<<l.at(0); qFatal( "Invalid interval" ); }
continue;
}
if ( vi < interval ) {
//debugPlan<<"subtract: vi<interval"<<vi<<interval;
if ( vi.startTime() < st ) {
l.insert( 0, AppointmentInterval( vi.startTime(), st, vi.load() ) );
//if ( ! l.at(0).isValid() ) { debugPlan<<vi<<interval<<l.at(0); qFatal( "Invalid interval" ); }
}
if ( vi.load() > load ) {
l.insert( 0, AppointmentInterval( st, qMin( vi.endTime(), et ), vi.load() - load ) );
//if ( ! l.at(0).isValid() ) { debugPlan<<vi<<interval<<l.at(0); qFatal( "Invalid interval" ); }
}
} else if ( interval < vi ) {
//debugPlan<<"subtract: interval<vi"<<vi<<interval;
if ( vi.load() > load ) {
//debugPlan<<"subtract: interval<vi vi.load > load"<<vi.load()<<load;
l.insert( 0, AppointmentInterval( vi.startTime(), qMin( vi.endTime(), et ), vi.load() - load ) );
//if ( ! l.at(0).isValid() ) { debugPlan<<vi<<interval<<l.at(0); qFatal( "Invalid interval" ); }
}
if ( et < vi.endTime() ) {
//debugPlan<<"subtract: interval<vi et < vi.endTime"<<et<<vi.endTime();
l.insert( 0, AppointmentInterval( et, vi.endTime(), vi.load() ) );
//if ( ! l.at(0).isValid() ) { debugPlan<<vi<<interval<<l.at(0); qFatal( "Invalid interval" ); }
}
} else if ( vi.load() > load ) {
//debugPlan<<"subtract: vi==interval"<<vi<<interval;
l.insert( 0, AppointmentInterval( st, et, vi.load() - load ) );
//if ( ! l.at(0).isValid() ) { debugPlan<<vi<<interval<<l.at(0); qFatal( "Invalid interval" ); }
}
}
foreach ( const AppointmentInterval &i, l ) {
//if ( ! i.isValid() ) { debugPlan<<interval<<i; qFatal( "Invalid interval" ); }
m_map.insert( date, i );
}
}
//debugPlan<<"subtract:"<<interval<<" result="<<endl<<*this;
}
AppointmentIntervalList &AppointmentIntervalList::operator+=( const AppointmentIntervalList &lst )
{
if ( lst.isEmpty() ) {
return *this;
}
foreach ( const AppointmentInterval &ai, lst.m_map ) {
add( ai );
}
return *this;
}
AppointmentIntervalList AppointmentIntervalList::extractIntervals( const DateTime &start, const DateTime &end ) const
{
if ( isEmpty() ) {
return AppointmentIntervalList();
}
QMultiMap<QDate, AppointmentInterval> lst;
QMultiMap<QDate, AppointmentInterval>::const_iterator it = m_map.lowerBound( start.date() );
for ( ; it != m_map.constEnd() && it.key() <= end.date(); ++it ) {
AppointmentInterval i = it.value().interval( start, end );
if ( i.isValid() ) {
lst.insert( it.key(), it.value().interval( start, end ) );
}
}
return AppointmentIntervalList( lst );
}
void AppointmentIntervalList::add( const DateTime &st, const DateTime &et, double load )
{
add( AppointmentInterval( st, et, load ) );
}
void AppointmentIntervalList::add( const AppointmentInterval &ai )
{
if ( ! ai.isValid() ) {
debugPlan<<ai;
Q_ASSERT( ai.isValid() );
return;
}
QDate date = ai.startTime().date();
QDate ed = ai.endTime().date();
int load = ai.load();
QList<AppointmentInterval> lst;
if ( date == ed ) {
lst << ai;
} else {
// split intervals into separate dates
QTime t1 = ai.startTime().time();
while ( date < ed ) {
lst << AppointmentInterval( DateTime( date, t1 ), DateTime( date.addDays( 1 ) ), load );
//debugPlan<<"split:"<<date<<lst.last();
Q_ASSERT_X(lst.last().isValid(), "Split", "Invalid interval");
date = date.addDays( 1 );
t1 = QTime();
}
if ( ai.endTime().time() != QTime( 0, 0, 0 ) ) {
lst << AppointmentInterval( DateTime( ed ), ai.endTime(), load );
Q_ASSERT_X(lst.last().isValid(), "Split", "Invalid interval");
}
}
foreach ( AppointmentInterval li, lst ) {
Q_ASSERT_X(lst.last().isValid(), "Add", "Invalid interval");
date = li.startTime().date();
if ( ! m_map.contains( date ) ) {
m_map.insert( date, li );
continue;
}
QList<AppointmentInterval> v = m_map.values( date );
m_map.remove( date );
QList<AppointmentInterval> l;
foreach ( const AppointmentInterval &vi, v ) {
if ( ! li.isValid() ) {
l.insert( 0, vi );
Q_ASSERT_X(l.at(0).isValid(), "Original", "Invalid interval");
continue;
}
if ( ! li.intersects( vi ) ) {
//debugPlan<<"not intersects:"<<li<<vi;
if ( li < vi ) {
if ( ! l.contains( li ) ) {
//debugPlan<<"li < vi:"<<"insert li"<<li;
l.insert( 0, li );
Q_ASSERT_X(l.at(0).isValid(), "No intersects", "Add Invalid interval");
li = AppointmentInterval();
}
//debugPlan<<"li < vi:"<<"insert vi"<<vi;
l.insert( 0, vi );
Q_ASSERT_X(l.at(0).isValid(), "No intersects", "Add Invalid interval");
} else if ( vi < li ) {
//debugPlan<<"vi < li:"<<"insert vi"<<vi;
l.insert( 0, vi );
Q_ASSERT_X(l.at(0).isValid(), "No intersects", "Add Invalid interval");
} else { Q_ASSERT( false ); }
} else {
//debugPlan<<"intersects, merge"<<li<<vi;
if ( li < vi ) {
//debugPlan<<"li < vi:";
if (li.startTime() < vi.startTime()) {
l.insert(0, AppointmentInterval(li.startTime(), vi.startTime(), li.load()));
Q_ASSERT_X(l.at(0).isValid(), "Intersects, start", "Add Invalid interval");
}
l.insert( 0, AppointmentInterval( vi.startTime(), qMin( vi.endTime(), li.endTime() ), vi.load() + li.load() ) );
Q_ASSERT_X(l.at(0).isValid(), "Intersects, middle", "Add Invalid interval");
li.setStartTime( l.at( 0 ).endTime() ); // if more of li, it may overlap with next vi
if ( l.at( 0 ).endTime() < vi.endTime() ) {
l.insert( 0, AppointmentInterval( l.at( 0 ).endTime(), vi.endTime(), vi.load() ) );
//debugPlan<<"li < vi: vi rest:"<<l.at( 0 );
Q_ASSERT_X(l.at(0).isValid(), "Intersects, end", "Add Invalid interval");
}
} else if ( vi < li ) {
//debugPlan<<"vi < li:";
if ( vi.startTime() < li.startTime() ) {
l.insert( 0, AppointmentInterval( vi.startTime(), li.startTime(), vi.load() ) );
Q_ASSERT_X(l.at(0).isValid(), "Intersects, start", "Add Invalid interval");
}
l.insert( 0, AppointmentInterval( li.startTime(), qMin( vi.endTime(), li.endTime() ), vi.load() + li.load() ) );
Q_ASSERT_X(l.at(0).isValid(), "Intersects, middle", "Add Invalid interval");
li.setStartTime( l.at( 0 ).endTime() ); // if more of li, it may overlap with next vi
if ( l.at( 0 ).endTime() < vi.endTime() ) {
l.insert( 0, AppointmentInterval( l.at( 0 ).endTime(), vi.endTime(), vi.load() ) );
//debugPlan<<"vi < li: vi rest:"<<l.at( 0 );
Q_ASSERT_X(l.at(0).isValid(), "Intersects, end", "Add Invalid interval");
}
} else {
//debugPlan<<"vi == li:";
li.setLoad( vi.load() + li.load() );
l.insert( 0, li );
Q_ASSERT_X(l.at(0).isValid(), "Equal", "Add Invalid interval");
li = AppointmentInterval();
}
}
}
// If there is a rest of li, it must be inserted
if ( li.isValid() ) {
//debugPlan<<"rest:"<<li;
l.insert( 0, li );
}
foreach( const AppointmentInterval &i, l ) {
Q_ASSERT(i.isValid());
m_map.insert( i.startTime().date(), i );
}
}
}
// Returns the total effort
Duration AppointmentIntervalList::effort() const
{
Duration d;
foreach ( const AppointmentInterval &i, m_map ) {
d += i.effort();
}
return d;
}
// Returns the effort from start to end
Duration AppointmentIntervalList::effort(const DateTime &start, const DateTime &end) const
{
Duration d;
QMultiMap<QDate, AppointmentInterval>::const_iterator it = m_map.lowerBound( start.date() );
for ( ; it != m_map.constEnd() && it.key() <= end.date(); ++it ) {
d += it.value().effort( start, end );
}
return d;
}
void AppointmentIntervalList::saveXML( QDomElement &element ) const
{
foreach ( const AppointmentInterval &i, m_map ) {
i.saveXML( element );
#ifndef NDEBUG
if ( !i.isValid() ) {
// NOTE: This should not happen, so hunt down cause if it does
warnPlan<<"Invalid interval:"<<i;
}
#endif
}
}
bool AppointmentIntervalList::loadXML( KoXmlElement &element, XMLLoaderObject &status )
{
KoXmlElement e;
forEachElement(e, element) {
if (e.tagName() == QLatin1String("interval")) {
AppointmentInterval a;
if (a.loadXML(e, status)) {
add(a);
} else {
errorPlan<<"AppointmentIntervalList::loadXML:"<<"Could not load interval"<<a;
}
}
}
return true;
}
QDebug operator<<( QDebug dbg, const KPlato::AppointmentIntervalList &i )
{
QMultiMap<QDate, AppointmentInterval>::const_iterator it = i.map().constBegin();
for ( ; it != i.map().constEnd(); ++it ) {
dbg<<endl<<it.key()<<":"<<it.value().startTime()<<it.value().endTime()<<it.value().load()<<"%";
}
return dbg;
}
////
Appointment::Appointment()
: m_extraRepeats(), m_skipRepeats() {
//debugPlan<<"("<<this<<")";
m_resource=0;
m_node=0;
m_calculationMode = Schedule::Scheduling;
m_repeatInterval=Duration();
m_repeatCount=0;
}
Appointment::Appointment(Schedule *resource, Schedule *node, const DateTime &start, const DateTime &end, double load)
: m_extraRepeats(),
m_skipRepeats() {
//debugPlan<<"("<<this<<")";
m_node = node;
m_resource = resource;
m_calculationMode = Schedule::Scheduling;
m_repeatInterval = Duration();
m_repeatCount = 0;
addInterval(start, end, load);
}
Appointment::Appointment(Schedule *resource, Schedule *node, const DateTime &start, Duration duration, double load)
: m_extraRepeats(),
m_skipRepeats() {
//debugPlan<<"("<<this<<")";
m_node = node;
m_resource = resource;
m_calculationMode = Schedule::Scheduling;
m_repeatInterval = Duration();
m_repeatCount = 0;
addInterval(start, duration, load);
}
Appointment::Appointment( const Appointment &app)
{
copy( app );
}
Appointment::~Appointment() {
//debugPlan<<"("<<this<<")";
detach();
}
void Appointment::clear()
{
m_intervals.clear();
}
AppointmentIntervalList Appointment::intervals( const DateTime &start, const DateTime &end ) const
{
//debugPlan<<start<<end;
AppointmentIntervalList lst;
QMultiMap<QDate, AppointmentInterval>::const_iterator it = m_intervals.map().lowerBound( start.date() );
for ( ; it != m_intervals.map().constEnd() && it.key() <= end.date(); ++it ) {
AppointmentInterval ai = it.value().interval( start, end );
if ( ai.isValid() ) {
lst.add( ai );
//debugPlan<<ai.startTime().toString()<<ai.endTime().toString();
}
}
return lst;
}
void Appointment::setIntervals(const AppointmentIntervalList &lst) {
m_intervals.clear();
foreach( const AppointmentInterval &i, lst.map() ) {
m_intervals.add( i );
}
}
void Appointment::addInterval(const AppointmentInterval &a) {
Q_ASSERT( a.isValid() );
m_intervals.add(a);
//if ( m_resource && m_resource->resource() && m_node && m_node->node() ) debugPlan<<"Mode="<<m_calculationMode<<":"<<m_resource->resource()->name()<<" to"<<m_node->node()->name()<<""<<a.startTime()<<a.endTime();
}
void Appointment::addInterval(const DateTime &start, const DateTime &end, double load) {
Q_ASSERT( start < end );
addInterval(AppointmentInterval(start, end, load));
}
void Appointment::addInterval(const DateTime &start, KPlato::Duration duration, double load) {
DateTime e = start+duration;
addInterval(start, e, load);
}
double Appointment::maxLoad() const {
double v = 0.0;
foreach (const AppointmentInterval &i, m_intervals.map() ) {
if (v < i.load())
v = i.load();
}
return v;
}
DateTime Appointment::startTime() const {
if ( isEmpty() ) {
//debugPlan<<"empty list";
return DateTime();
}
return m_intervals.map().values().first().startTime();
}
DateTime Appointment::endTime() const {
if ( isEmpty() ) {
//debugPlan<<"empty list";
return DateTime();
}
return m_intervals.map().values().last().endTime();
}
bool Appointment::isBusy(const DateTime &/*start*/, const DateTime &/*end*/) {
return false;
}
bool Appointment::loadXML(KoXmlElement &element, XMLLoaderObject &status, Schedule &sch) {
//debugPlan<<project.name();
Node *node = status.project().findNode(element.attribute(QStringLiteral("task-id")));
if (node == 0) {
errorPlan<<"The referenced task does not exists: "<<element.attribute(QStringLiteral("task-id"));
return false;
}
Resource *res = status.project().resource(element.attribute(QStringLiteral("resource-id")));
if (res == 0) {
errorPlan<<"The referenced resource does not exists: resource id="<<element.attribute(QStringLiteral("resource-id"));
return false;
}
if (!res->addAppointment(this, sch)) {
errorPlan<<"Failed to add appointment to resource: "<<res->name();
return false;
}
if (!node->addAppointment(this, sch)) {
errorPlan<<"Failed to add appointment to node: "<<node->name();
m_resource->takeAppointment(this);
return false;
}
//debugPlan<<"res="<<m_resource->resource()->name()<<" node="<<m_node->node()->name();
m_intervals.loadXML( element, status );
if (isEmpty()) {
errorPlan<<"Appointment is empty (added anyway): "<<node->name()<<res->name();
return false;
}
return true;
}
void Appointment::saveXML(QDomElement &element) const {
if (isEmpty()) {
errorPlan<<"Incomplete appointment data: No intervals";
}
if (m_resource == 0 || m_resource->resource() == 0) {
errorPlan<<"Incomplete appointment data: No resource";
return;
}
if (m_node == 0 || m_node->node() == 0) {
errorPlan<<"Incomplete appointment data: No node";
return; // shouldn't happen
}
//debugPlan;
QDomElement me = element.ownerDocument().createElement(QStringLiteral("appointment"));
element.appendChild(me);
me.setAttribute(QStringLiteral("resource-id"), m_resource->resource()->id());
me.setAttribute(QStringLiteral("task-id"), m_node->node()->id());
//debugPlan<<m_resource->resource()->name()<<m_node->node()->name();
m_intervals.saveXML( me );
}
// Returns the total planned effort for this appointment
Duration Appointment::plannedEffort( const Resource *resource, EffortCostCalculationType type) const {
if ( m_resource->resource() != resource ) {
return Duration::zeroDuration;
}
return plannedEffort( type );
}
// Returns the total planned effort for this appointment
Duration Appointment::plannedEffort(EffortCostCalculationType type) const {
Duration d;
if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) {
foreach (const AppointmentInterval &i, m_intervals.map() ) {
d += i.effort();
}
}
return d;
}
// Returns the planned effort on the date
Duration Appointment::plannedEffort(QDate date, EffortCostCalculationType type) const {
Duration d;
if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) {
QMultiMap<QDate, AppointmentInterval>::const_iterator it = m_intervals.map().constFind( date );
for ( ; it != m_intervals.map().constEnd() && it.key() == date; ++it ) {
d += it.value().effort();
}
}
return d;
}
// Returns the planned effort on the date
Duration Appointment::plannedEffort( const Resource *resource, QDate date, EffortCostCalculationType type ) const {
if ( resource != m_resource->resource() ) {
return Duration::zeroDuration;
}
return plannedEffort( date, type );
}
// Returns the planned effort upto and including the date
Duration Appointment::plannedEffortTo(QDate date, EffortCostCalculationType type) const {
Duration d;
QDate e(date.addDays(1));
if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) {
foreach (const AppointmentInterval &i, m_intervals.map() ) {
d += i.effort(e, true); // upto e, not including
}
}
//debugPlan<<date<<d.toString();
return d;
}
// Returns the planned effort upto and including the date
Duration Appointment::plannedEffortTo( const Resource *resource, QDate date, EffortCostCalculationType type ) const {
if ( resource != m_resource->resource() ) {
return Duration::zeroDuration;
}
return plannedEffortTo( date, type );
}
EffortCostMap Appointment::plannedPrDay(QDate pstart, QDate pend, EffortCostCalculationType type) const {
//debugPlan<<m_node->id()<<","<<m_resource->id();
EffortCostMap ec;
QDate start = pstart.isValid() ? pstart : startTime().date();
QDate end = pend.isValid() ? pend : endTime().date();
double rate = m_resource && m_resource->resource() ? m_resource->normalRatePrHour() : 0.0;
Resource::Type rt = m_resource && m_resource->resource() ? m_resource->resource()->type() : Resource::Type_Work;
Duration zero;
//debugPlan<<rate<<m_intervals.count();
QMultiMap<QDate, AppointmentInterval>::const_iterator it = m_intervals.map().lowerBound( start );
for ( ; it != m_intervals.map().constEnd() && it.key() <= end; ++it ) {
//debugPlan<<start<<end<<dt;
Duration eff;
switch ( type ) {
case ECCT_All:
eff = it.value().effort();
ec.add(it.key(), eff, eff.toDouble(Duration::Unit_h) * rate);
break;
case ECCT_EffortWork:
eff = it.value().effort();
ec.add(it.key(), (rt == Resource::Type_Work ? eff : zero), eff.toDouble(Duration::Unit_h) * rate);
break;
case ECCT_Work:
if ( rt == Resource::Type_Work ) {
eff = it.value().effort();
ec.add(it.key(), eff, eff.toDouble(Duration::Unit_h) * rate);
}
break;
}
}
return ec;
}
EffortCost Appointment::plannedCost(EffortCostCalculationType type) const {
EffortCost ec;
ec.setEffort( plannedEffort(type) );
if (m_resource && m_resource->resource()) {
switch ( type ) {
case ECCT_Work:
if ( m_resource->resource()->type() != Resource::Type_Work ) {
break;
}
// fall through
default:
ec.setCost( ec.hours() * m_resource->resource()->normalRate() ); //FIXME overtime
break;
}
}
return ec;
}
//Calculates the planned cost on date
double Appointment::plannedCost(QDate date, EffortCostCalculationType type) {
if (m_resource && m_resource->resource()) {
switch ( type ) {
case ECCT_Work:
if ( m_resource->resource()->type() != Resource::Type_Work ) {
break;
}
// fall through
default:
return plannedEffort(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime
}
}
return 0.0;
}
//Calculates the planned cost upto and including date
double Appointment::plannedCostTo(QDate date, EffortCostCalculationType type) {
if (m_resource && m_resource->resource()) {
switch ( type ) {
case ECCT_Work:
if ( m_resource->resource()->type() != Resource::Type_Work ) {
break;
}
// fall through
default:
return plannedEffortTo(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime
}
}
return 0.0;
}
bool Appointment::attach() {
//debugPlan<<"("<<this<<")";
if (m_resource && m_node) {
m_resource->attach(this);
m_node->attach(this);
return true;
}
warnPlan<<"Failed: "<<(m_resource ? "" : "resource=0 ")
<<(m_node ? "" : "node=0");
return false;
}
void Appointment::detach() {
//debugPlan<<"("<<this<<")"<<m_calculationMode<<":"<<m_resource<<","<<m_node;
if (m_resource) {
m_resource->takeAppointment(this, m_calculationMode); // takes from node also
}
if (m_node) {
m_node->takeAppointment(this, m_calculationMode); // to make it robust
}
}
// Returns the effort from start to end
Duration Appointment::effort(const DateTime &start, const DateTime &end, EffortCostCalculationType type) const {
Duration e;
if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) {
e = m_intervals.effort( start, end );
}
return e;
}
// Returns the effort from start for the duration
Duration Appointment::effort(const DateTime &start, KPlato::Duration duration, EffortCostCalculationType type) const {
Duration d;
if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) {
foreach (const AppointmentInterval &i, m_intervals.map() ) {
d += i.effort(start, start+duration);
}
}
return d;
}
Appointment &Appointment::operator=(const Appointment &app) {
copy( app );
return *this;
}
Appointment &Appointment::operator+=(const Appointment &app) {
merge( app );
return *this;
}
Appointment Appointment::operator+(const Appointment &app) {
Appointment a( *this );
a.merge(app);
return a;
}
Appointment &Appointment::operator-=(const Appointment &app) {
m_intervals -= app.m_intervals;
return *this;
}
void Appointment::copy(const Appointment &app) {
m_resource = 0; //app.resource(); // NOTE: Don't copy, the new appointment
m_node = 0; //app.node(); // NOTE: doesn't belong to anyone yet.
//TODO: incomplete but this is all we use atm
m_calculationMode = app.calculationMode();
//m_repeatInterval = app.repeatInterval();
//m_repeatCount = app.repeatCount();
m_intervals.clear();
foreach (const AppointmentInterval &i, app.intervals().map() ) {
addInterval(i);
}
}
void Appointment::merge(const Appointment &app) {
//debugPlan<<this<<(m_node ? m_node->node()->name() : "no node")<<(app.node() ? app.node()->node()->name() : "no node");
if ( app.isEmpty() ) {
return;
}
if ( isEmpty() ) {
setIntervals( app.intervals() );
return;
}
QList<AppointmentInterval> result;
QList<AppointmentInterval> lst1 = m_intervals.map().values();
AppointmentInterval i1;
QList<AppointmentInterval> lst2 = app.intervals().map().values();
//debugPlan<<"add"<<lst1.count()<<" intervals to"<<lst2.count()<<" intervals";
AppointmentInterval i2;
int index1 = 0, index2 = 0;
DateTime from;
while (index1 < lst1.size() || index2 < lst2.size()) {
if (index1 >= lst1.size()) {
i2 = lst2[index2];
if (!from.isValid() || from < i2.startTime())
from = i2.startTime();
result.append(AppointmentInterval(from, i2.endTime(), i2.load()));
//debugPlan<<"Interval+ (i2):"<<from<<" -"<<i2.endTime();
from = i2.endTime();
++index2;
continue;
}
if (index2 >= lst2.size()) {
i1 = lst1[index1];
if (!from.isValid() || from < i1.startTime())
from = i1.startTime();
result.append(AppointmentInterval(from, i1.endTime(), i1.load()));
//debugPlan<<"Interval+ (i1):"<<from<<" -"<<i1.endTime();
from = i1.endTime();
++index1;
continue;
}
i1 = lst1[index1];
i2 = lst2[index2];
AppointmentInterval i = i1.firstInterval(i2, from);
if (!i.isValid()) {
break;
}
result.append(AppointmentInterval(i));
from = i.endTime();
//debugPlan<<"Interval+ (i):"<<i.startTime()<<" -"<<i.endTime()<<" load="<<i.load();
if (i.endTime() >= i1.endTime()) {
++index1;
}
if (i.endTime() >= i2.endTime()) {
++index2;
}
}
m_intervals.clear();
foreach ( const AppointmentInterval &i, result ) {
m_intervals.add( i );
}
//debugPlan<<this<<":"<<m_intervals.count();
return;
}
Appointment Appointment::extractIntervals( const DateTimeInterval& interval ) const
{
Appointment a;
if ( interval.isValid() ) {
a.setIntervals( m_intervals.extractIntervals( interval.first, interval.second ) );
}
return a;
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptcalendar.cpp b/src/libs/kernel/kptcalendar.cpp
index 08c10ab4..ee200a80 100644
--- a/src/libs/kernel/kptcalendar.cpp
+++ b/src/libs/kernel/kptcalendar.cpp
@@ -1,1708 +1,1709 @@
/* This file is part of the KDE project
Copyright (C) 2003 - 2007, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "kptcalendar.h"
#include "kptappointment.h"
#include "kptmap.h"
#include "kptduration.h"
#include "kptdatetime.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include "kptcommand.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#ifdef HAVE_KHOLIDAYS
#include <KHolidays/HolidayRegion>
#include <KHolidays/Holiday>
#endif
#include <QTimeZone>
#include <QDate>
namespace KPlato
{
#ifdef HAVE_KHOLIDAYS
// start qt-copy (with some changes)
// Copyright (C) 2013 John Layt <jlayt@kde.org>
struct TzTimeZone {
QString country;
QByteArray comment;
};
// Define as a type as Q_GLOBAL_STATIC doesn't like it
typedef QHash<QByteArray, TzTimeZone> TzTimeZoneHash;
// Parse zone.tab table, assume lists all installed zones, if not will need to read directories
static TzTimeZoneHash loadTzTimeZones()
{
QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab");
if (!QFile::exists(path))
path = QStringLiteral("/usr/lib/zoneinfo/zone.tab");
QFile tzif(path);
if (!tzif.open(QIODevice::ReadOnly))
return TzTimeZoneHash();
TzTimeZoneHash zonesHash;
// TODO QTextStream inefficient, replace later
QTextStream ts(&tzif);
while (!ts.atEnd()) {
const QString line = ts.readLine();
// Comment lines are prefixed with a #
if (!line.isEmpty() && line.at(0) != '#') {
// Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments
QStringList parts = line.split(QLatin1Char('\t'));
TzTimeZone zone;
zone.country = parts.at(0);
if (parts.size() > 3)
zone.comment = parts.at(3).toUtf8();
zonesHash.insert(parts.at(2).toUtf8(), zone);
}
}
return zonesHash;
}
// Hash of available system tz files as loaded by loadTzTimeZones()
Q_GLOBAL_STATIC_WITH_ARGS(const TzTimeZoneHash, tzZones, (loadTzTimeZones()));
// end qt-copy
#endif
QString CalendarDay::stateToString( int st, bool trans )
{
return
( st == None ) ?
(trans ? i18n( "Undefined" ) : QStringLiteral( "Undefined" )) :
( st == NonWorking ) ?
(trans ? i18n( "Non-working" ) : QStringLiteral( "Non-working" )) :
( st == Working ) ?
(trans ? i18n( "Working" ) : QStringLiteral( "Working" )) :
QString();
}
QStringList CalendarDay::stateList( bool trans )
{
QStringList lst;
return trans
? lst << i18n( "Undefined" ) << i18n( "Non-working" ) << i18n( "Working" )
: lst << QStringLiteral("Undefined") << QStringLiteral("Non-working") << QStringLiteral("Working");
}
///// CalendarDay ////
CalendarDay::CalendarDay()
: m_date(),
m_state(Undefined),
m_calendar( 0 )
{
//debugPlan<<"("<<this<<")";
}
CalendarDay::CalendarDay(int state)
: m_date(),
m_state(state),
m_calendar( 0 )
{
//debugPlan<<"("<<this<<")";
}
CalendarDay::CalendarDay(QDate date, int state)
: m_date(date),
m_state(state),
m_calendar( 0 )
{
//debugPlan<<"("<<this<<")";
}
CalendarDay::CalendarDay(CalendarDay *day)
{
//debugPlan<<"("<<this<<") from ("<<day<<")";
copy(*day);
}
CalendarDay::~CalendarDay() {
//debugPlan<<"("<<this<<")";
while (!m_timeIntervals.isEmpty())
delete m_timeIntervals.takeFirst();
}
const CalendarDay &CalendarDay::copy(const CalendarDay &day) {
m_calendar = 0; // NOTE
//debugPlan<<"("<<&day<<") date="<<day.date().toString();
m_date = day.date();
m_state = day.state();
m_timeIntervals.clear();
foreach (TimeInterval *i, day.timeIntervals()) {
m_timeIntervals.append( new TimeInterval( *i ) );
}
return *this;
}
bool CalendarDay::load( KoXmlElement &element, XMLLoaderObject &status ) {
//debugPlan;
bool ok=false;
m_state = QString(element.attribute(QStringLiteral("state"), QStringLiteral("-1"))).toInt(&ok);
if (m_state < 0)
return false;
//debugPlan<<" state="<<m_state;
QString s = element.attribute(QStringLiteral("date"));
if (!s.isEmpty()) {
m_date = QDate::fromString(s, Qt::ISODate);
if (!m_date.isValid())
m_date = QDate::fromString(s);
}
clearIntervals();
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == QLatin1String("interval")) {
//debugPlan<<"Interval start="<<e.attribute("start")<<" end="<<e.attribute("end");
QString st = e.attribute(QStringLiteral("start"));
if (st.isEmpty() ) {
errorPlan<<"Empty interval";
continue;
}
QTime start = QTime::fromString(st);
int length = 0;
if ( status.version() <= QLatin1String("0.6.1") ) {
QString en = e.attribute(QStringLiteral("end"));
if ( en.isEmpty() ) {
errorPlan<<"Invalid interval end";
continue;
}
QTime end = QTime::fromString(en);
length = start.msecsTo( end );
} else {
length = e.attribute(QStringLiteral("length"), QStringLiteral("0")).toInt();
}
if ( length <= 0 ) {
errorPlan<<"Invalid interval length";
continue;
}
addInterval( new TimeInterval( start, length ) );
}
}
return true;
}
void CalendarDay::save(QDomElement &element) const {
//debugPlan<<m_date.toString();
if (m_state == None)
return;
if (m_date.isValid()) {
element.setAttribute(QStringLiteral("date"), m_date.toString(Qt::ISODate));
}
element.setAttribute(QStringLiteral("state"), QString::number(m_state));
if (m_timeIntervals.count() == 0)
return;
foreach (TimeInterval *i, m_timeIntervals) {
QDomElement me = element.ownerDocument().createElement(QStringLiteral("interval"));
element.appendChild(me);
me.setAttribute(QStringLiteral("length"), QString::number(i->second));
me.setAttribute(QStringLiteral("start"), i->first.toString());
}
}
void CalendarDay::addInterval(TimeInterval *interval) {
if (!interval) {
return;
}
// TODO: check for overlapping intervals and handle them for what makes sense
QList <TimeInterval*>::Iterator it;
const QList <TimeInterval*>::Iterator end = m_timeIntervals.end();
QList <TimeInterval*>::Iterator position = end;
for (it = m_timeIntervals.begin(); it != end; ++it) {
// first found that is later?
if ((*it)->startTime() > interval->startTime()) {
// insert before
position = it;
break;
}
}
m_timeIntervals.insert(position, interval);
}
bool CalendarDay::operator==(const CalendarDay *day) const {
return operator==(*day);
}
bool CalendarDay::operator==(const CalendarDay &day) const {
//debugPlan;
if (m_date.isValid() && day.date().isValid()) {
if (m_date != day.date()) {
//debugPlan<<m_date.toString()<<" !="<<day.date().toString();
return false;
}
} else if (m_date.isValid() != day.date().isValid()) {
//debugPlan<<"one of the dates is not valid";
return false;
}
if (m_state != day.state()) {
//debugPlan<<m_state<<" !="<<day.state();
return false;
}
if (m_timeIntervals.count() != day.timeIntervals().count()) {
//debugPlan<<m_timeIntervals.count()<<" !="<<day.timeIntervals().count();
return false;
}
foreach (TimeInterval *a, m_timeIntervals) {
bool res = false;
foreach (TimeInterval *b, day.timeIntervals()) {
if (a == b ) {
res = true;
break;
}
}
if (res == false) {
//debugPlan<<"interval mismatch"<<a->first.toString()<<"-"<<a->second.toString();
return false;
}
}
return true;
}
bool CalendarDay::operator!=(const CalendarDay *day) const {
return operator!=(*day);
}
bool CalendarDay::operator!=(const CalendarDay &day) const {
return !operator==(day);
}
Duration CalendarDay::effort(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) {
// debugPlan<<start<<" -"<<length;
return effort( m_date, start, length, timeZone, sch );
}
Duration CalendarDay::effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) {
// debugPlan<<date<<start<<length;
if ( !date.isValid() ) {
return Duration::zeroDuration;
}
//debugPlan<<start.toString()<<" -"<<end.toString();
Duration eff;
if (m_state != Working) {
//debugPlan<<"Non working day";
return eff;
}
int l = 0;
foreach (TimeInterval *i, m_timeIntervals) {
if ( ! i->endsMidnight() && start >= i->endTime() ) {
//debugPlan<<"Skip:"<<start<<">="<<i->first.addMSecs(i->second);
continue;
}
QTime t1 = start.addMSecs( length );
if ( t1 != QTime( 0, 0, 0 ) && t1 < i->first ) {
//debugPlan<<"Skip:"<<t1<<"<"<<i->first;
continue;
}
t1 = qMax( start, i->first );
if ( i->endsMidnight() ) {
l = t1.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1;
} else {
l = t1.msecsTo( i->endTime() );
}
l = qMin( l, length - start.msecsTo( t1 ) );
if ( l <= 0 ) {
continue;
}
//debugPlan<<"Interval:"<<t1<<"->"<<l;
DateTime dt1 = ! timeZone.isValid() ? DateTime( date, t1 ) : DateTime( date, t1, timeZone );
DateTimeInterval dti( dt1, dt1.addMSecs( l ) );
if ( sch ) {
dti = sch->available( dti ); //FIXME needs an effort method
//debugPlan<<"Checked sch:"<<dti.first<<" -"<<dti.second;
}
eff += dti.second - dti.first;
//debugPlan<<dti.first.toString()<<" -"<<dti.second.toString()<<", effort now"<<eff.toString();
}
// debugPlan<<(m_date.isValid()?m_date.toString(Qt::ISODate):"Weekday")<<":"<<start.toString()<<" -"<<start.addMSecs(length).toString()<<": total="<<eff.toDouble(Duration::Unit_h)<<"h";
return eff;
}
Duration CalendarDay::workDuration() const
{
Duration d;
if (m_state != Working) {
//debugPlan<<"Non working day";
return d;
}
foreach (TimeInterval *i, m_timeIntervals) {
//debugPlan<<"Interval:"<<i->first<<" -"<<i->second;
d += Duration( (qint64)i->second );
}
return d;
}
TimeInterval CalendarDay::interval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const {
//debugPlan;
return interval( m_date, start, length, timeZone, sch );
}
TimeInterval CalendarDay::interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const
{
//debugPlan<<"Inp:"<<date<<start<<"+"<<length<<"="<<QDateTime(date, start).addMSecs( length );
Q_ASSERT( length > 0 );
Q_ASSERT( QTime(0,0,0).msecsTo( start ) + length <= 1000*60*60*24 );
QTime t1;
int l = 0;
if ( ! hasInterval() ) {
return TimeInterval();
}
foreach (TimeInterval *i, m_timeIntervals) {
//debugPlan<<"Interval:"<<i->first<<i->second<<i->first.addMSecs(i->second);
if ( ! i->endsMidnight() && start >= i->endTime() ) {
//debugPlan<<"Skip:"<<start<<">="<<i->first.addMSecs(i->second);
continue;
}
QTime t1 = start.addMSecs( length );
if ( t1 != QTime( 0, 0, 0 ) && t1 < i->first ) {
//debugPlan<<"Skip:"<<t1<<"<"<<i->first;
continue;
}
t1 = qMax( start, i->first );
if ( i->endsMidnight() ) {
l = t1.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1;
} else {
l = t1.msecsTo( i->endTime() );
}
l = qMin( l, length - start.msecsTo( t1 ) );
if ( l <= 0 ) {
continue;
}
TimeInterval ti( t1, l );
//debugPlan<<"Day give:"<<date<<","<<t1<<"->"<<l;
if ( sch ) {
// check if booked
DateTime dt1 = timeZone.isValid() ? DateTime( date, t1, timeZone ) : DateTime( date, t1 );
QDate d2 = date;
QTime t2 = t1.addMSecs( l );
if ( t2 == QTime( 0, 0, 0 ) ) {
d2 = d2.addDays( 1 );
}
DateTime dt2 = timeZone.isValid() ? DateTime( d2, t2, timeZone ) : DateTime( d2, t2 );
DateTimeInterval dti( dt1, dt2 );
//debugPlan<<": Booked?"<<date<<","<<t1<<"+"<<l<<"="<<t1.addMSecs( l )<<endl<<dti;
dti = sch->available( dti );
//debugPlan<<"Checked sch:"<<ti.first<<","<<ti.second<<"="<<dti;
ti = TimeInterval( dti.first.time(), ( dti.second - dti.first ).milliseconds() );
//debugPlan<<"CalendarDay::interval:"<<ti;
}
if ( ti.isValid() ) {
//debugPlan<<"Return:"<<ti.first<<"+"<<ti.second<<"="<<ti.first.addMSecs( ti.second );
return ti;
}
}
return TimeInterval(t1, l);
}
bool CalendarDay::hasInterval() const
{
return m_state == Working && m_timeIntervals.count() > 0;
}
bool CalendarDay::hasInterval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const {
//debugPlan<<(m_date.isValid()?m_date.toString(Qt::ISODate):"Weekday")<<""<<start.toString()<<" -"<<end.toString();
return hasInterval( m_date, start, length, timeZone, sch );
}
bool CalendarDay::hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const
{
//debugPlan<<(m_date.isValid()?m_date.toString(Qt::ISODate):"Weekday")<<""<<start<<"->"<<length;
return interval( date, start, length, timeZone, sch ).first.isValid();
}
Duration CalendarDay::duration() const {
Duration dur;
foreach (TimeInterval *i, m_timeIntervals) {
dur += Duration( (qint64)i->second );
}
return dur;
}
void CalendarDay::removeInterval( TimeInterval *ti )
{
m_timeIntervals.removeOne(ti);
}
int CalendarDay::numIntervals() const
{
return m_state == Working ? m_timeIntervals.count() : 0;
}
bool CalendarDay::hasInterval(const TimeInterval* interval) const
{
return m_timeIntervals.contains(const_cast<TimeInterval*>(interval));
}
DateTime CalendarDay::start() const
{
if ( m_state != Working || m_timeIntervals.isEmpty() ) {
return DateTime();
}
QDate date = m_date;
if ( ! m_date.isValid() ) {
date = QDate::currentDate();
}
if ( m_calendar && m_calendar->timeZone().isValid() ) {
return DateTime( date, m_timeIntervals.first()->startTime(), m_calendar->timeZone() );
}
return DateTime( date, m_timeIntervals.first()->startTime() );
}
DateTime CalendarDay::end() const
{
if ( m_state != Working || m_timeIntervals.isEmpty() ) {
return DateTime();
}
QDate date;
if ( m_date.isValid() ) {
date = m_timeIntervals.last()->endsMidnight() ? m_date.addDays( 1 ) : m_date;
} else {
date = QDate::currentDate();
}
if ( m_calendar && m_calendar->timeZone().isValid() ) {
return DateTime( date, m_timeIntervals.last()->endTime(), m_calendar->timeZone() );
}
return DateTime( date, m_timeIntervals.last()->endTime() );
}
///// CalendarWeekdays ////
CalendarWeekdays::CalendarWeekdays()
: m_weekdays()
{
//debugPlan<<"--->";
for (int i=1; i <= 7; ++i) {
m_weekdays.insert( i, new CalendarDay() );
}
//debugPlan<<"<---";
}
CalendarWeekdays::CalendarWeekdays( const CalendarWeekdays *weekdays )
: m_weekdays() {
//debugPlan<<"--->";
copy(*weekdays);
//debugPlan<<"<---";
}
CalendarWeekdays::~CalendarWeekdays() {
qDeleteAll( m_weekdays );
//debugPlan;
}
const CalendarWeekdays &CalendarWeekdays::copy(const CalendarWeekdays &weekdays) {
//debugPlan;
qDeleteAll( m_weekdays );
m_weekdays.clear();
QMapIterator<int, CalendarDay*> i( weekdays.weekdayMap() );
while ( i.hasNext() ) {
i.next();
m_weekdays.insert( i.key(), new CalendarDay( i.value() ) );
}
return *this;
}
bool CalendarWeekdays::load( KoXmlElement &element, XMLLoaderObject &status ) {
//debugPlan;
bool ok;
int dayNo = QString(element.attribute(QStringLiteral("day"),QStringLiteral("-1"))).toInt(&ok);
if (dayNo < 0 || dayNo > 6) {
errorPlan<<"Illegal weekday: "<<dayNo;
return true; // we continue anyway
}
CalendarDay *day = m_weekdays.value( dayNo + 1 );
if ( day == 0 ) {
errorPlan<<"No weekday: "<<dayNo;
return false;
}
if (!day->load( element, status ) )
day->setState(CalendarDay::None);
return true;
}
void CalendarWeekdays::save(QDomElement &element) const {
//debugPlan;
QMapIterator<int, CalendarDay*> i( m_weekdays );
while ( i.hasNext() ) {
i.next();
QDomElement me = element.ownerDocument().createElement(QStringLiteral("weekday"));
element.appendChild(me);
me.setAttribute( QStringLiteral("day"), QString::number(i.key() - 1) ); // 0 (monday) .. 6 (sunday)
i.value()->save(me);
}
}
const QMap<int, CalendarDay*> &CalendarWeekdays::weekdayMap() const
{
return m_weekdays;
}
IntMap CalendarWeekdays::stateMap() const
{
IntMap days;
QMapIterator<int, CalendarDay*> i( m_weekdays );
while ( i.hasNext() ) {
i.next();
if ( i.value()->state() != CalendarDay::None )
days.insert( i.key(), i.value()->state() );
}
return days;
}
int CalendarWeekdays::state(QDate date) const {
return state( date.dayOfWeek() );
}
int CalendarWeekdays::state( int weekday ) const {
CalendarDay *day = m_weekdays.value( weekday );
return day ? day->state() : CalendarDay::None;
}
void CalendarWeekdays::setState(int weekday, int state) {
CalendarDay *day = m_weekdays.value( weekday );
if ( day == 0 )
return;
day->setState(state);
}
QList<TimeInterval*> CalendarWeekdays::intervals(int weekday) const {
CalendarDay *day = m_weekdays.value( weekday );
Q_ASSERT(day);
return day->timeIntervals();
}
void CalendarWeekdays::setIntervals(int weekday, const QList<TimeInterval*> &intervals) {
CalendarDay *day = m_weekdays.value( weekday );
if (day) {
day->setIntervals( intervals );
}
}
void CalendarWeekdays::clearIntervals(int weekday) {
CalendarDay *day = m_weekdays.value( weekday );
if (day) {
day->clearIntervals();
}
}
bool CalendarWeekdays::operator==(const CalendarWeekdays *wd) const {
if (m_weekdays.count() != wd->weekdays().count()) {
return false;
}
QMapIterator<int, CalendarDay*> i( wd->weekdayMap() );
while ( i.hasNext() ) {
i.next();
CalendarDay *day1 = i.value();
CalendarDay *day2 = m_weekdays.value( i.key() );
if (day1 != day2)
return false;
}
return true;
}
bool CalendarWeekdays::operator!=(const CalendarWeekdays *wd) const {
return operator==( wd ) == false;
}
Duration CalendarWeekdays::effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) {
// debugPlan<<"Day of week="<<date.dayOfWeek();
Q_ASSERT( QTime(0,0,0).msecsTo( start ) + length <= 1000*60*60*24 );
CalendarDay *day = weekday( date.dayOfWeek() );
if (day && day->state() == CalendarDay::Working) {
return day->effort(date, start, length, timeZone, sch);
}
return Duration::zeroDuration;
}
TimeInterval CalendarWeekdays::interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const
{
//debugPlan;
CalendarDay *day = weekday( date.dayOfWeek() );
if (day && day->state() == CalendarDay::Working) {
return day->interval(date, start, length, timeZone, sch);
}
return TimeInterval();
}
bool CalendarWeekdays::hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const
{
//debugPlan<<date<<":"<<start<<"+"<<length;
CalendarDay *day = weekday( date.dayOfWeek() );
return day && day->hasInterval(date, start, length, timeZone, sch);
}
bool CalendarWeekdays::hasInterval() const
{
//debugPlan;
foreach ( CalendarDay *d, m_weekdays ) {
if (d->hasInterval())
return true;
}
return false;
}
CalendarDay *CalendarWeekdays::weekday( int day ) const {
Q_ASSERT( day >= 1 && day <= 7 );
Q_ASSERT( m_weekdays.contains( day ) );
return m_weekdays.value( day );
}
//static
int CalendarWeekdays::dayOfWeek(const QString& name)
{
QStringList lst;
lst << QStringLiteral("Monday") << QStringLiteral("Tuesday") << QStringLiteral("Wednesday") << QStringLiteral("Thursday") << QStringLiteral("Friday") << QStringLiteral("Saturday") << QStringLiteral("Sunday");
int idx = -1;
if ( lst.contains( name ) ) {
idx = lst.indexOf( name ) + 1;
}
return idx;
}
Duration CalendarWeekdays::duration() const {
Duration dur;
foreach ( CalendarDay *d, m_weekdays ) {
dur += d->duration();
}
return dur;
}
Duration CalendarWeekdays::duration(int _weekday) const {
CalendarDay *day = weekday(_weekday);
if (day)
return day->duration();
return Duration();
}
int CalendarWeekdays::indexOf( const CalendarDay *day ) const
{
return m_weekdays.key(const_cast<CalendarDay*>(day));
}
///// Calendar ////
Calendar::Calendar()
: QObject( 0 ), // don't use parent
m_parent(0),
m_project(0),
m_default( false ),
m_shared(false)
{
init();
}
Calendar::Calendar(const QString& name, Calendar *parent)
: QObject( 0 ), // don't use parent
m_name(name),
m_parent(parent),
m_project(0),
m_days(),
m_default( false ),
m_shared(false)
{
init();
}
Calendar::~Calendar() {
//debugPlan<<"deleting"<<m_name;
removeId();
#ifdef HAVE_KHOLIDAYS
delete m_region;
#endif
delete m_weekdays;
while (!m_days.isEmpty())
delete m_days.takeFirst();
}
// Not allowed, QObject
// Calendar::Calendar(Calendar *calendar)
// : m_project(0),
// m_days() {
// copy(*calendar);
// }
const Calendar &Calendar::copy( const Calendar &calendar ) {
m_name = calendar.name();
m_timeZone = calendar.timeZone();
// m_parent = calendar.parentCal();
// m_id = calendar.id();
#ifdef HAVE_KHOLIDAYS
delete m_region;
m_region = new KHolidays::HolidayRegion(calendar.holidayRegion()->regionCode());
#endif
foreach (CalendarDay *d, calendar.days()) {
m_days.append(new CalendarDay(d));
}
delete m_weekdays;
m_weekdays = new CalendarWeekdays(calendar.weekdays());
return *this;
}
void Calendar::init() {
#ifdef HAVE_KHOLIDAYS
m_region = new KHolidays::HolidayRegion();
#endif
m_weekdays = new CalendarWeekdays();
m_timeZone = QTimeZone::systemTimeZone();
m_cacheversion = 0;
m_blockversion = false;
}
int Calendar::cacheVersion() const
{
return m_parent ? m_parent->cacheVersion() : m_cacheversion;
}
void Calendar::incCacheVersion()
{
if ( m_blockversion ) {
return;
}
if ( m_parent ) {
m_parent->incCacheVersion();
} else {
++m_cacheversion;
debugPlan<<m_name<<m_cacheversion;
}
}
void Calendar::setCacheVersion( int version )
{
if ( m_blockversion ) {
return;
}
if ( m_parent ) {
m_parent->setCacheVersion( version );
} else {
m_cacheversion = version;
debugPlan<<m_name<<m_cacheversion;
}
}
void Calendar::setName(const QString& name)
{
m_name = name;
if ( m_project ) {
m_project->changed( this );
}
}
void Calendar::setParentCal( Calendar *parent, int pos )
{
if ( m_parent ) {
m_parent->takeCalendar( this );
}
m_parent = parent;
if ( m_parent ) {
m_parent->addCalendar( this, pos );
}
}
bool Calendar::isChildOf( const Calendar *cal ) const
{
Calendar *p = parentCal();
for (; p != 0; p = p->parentCal() ) {
if ( cal == p ) {
return true;
}
}
return false;
}
void Calendar::setProject(Project *project) {
m_project = project;
}
void Calendar::setTimeZone( const QTimeZone &tz )
{
if (m_timeZone == tz) {
return;
}
//debugPlan<<tz->name();
m_timeZone = tz;
#ifdef HAVE_KHOLIDAYS
if (m_regionCode == QLatin1String("Default")) {
setHolidayRegion(QStringLiteral("Default"));
}
#endif
if ( m_project ) {
m_project->changed( this );
}
incCacheVersion();
}
QTimeZone Calendar::projectTimeZone() const
{
return m_project ? m_project->timeZone() : QTimeZone::systemTimeZone();
}
void Calendar::setDefault( bool on )
{
m_default = on;
if ( m_project ) {
m_project->changed( this );
}
incCacheVersion();
}
// Note: only project should do this
void Calendar::setId(const QString& id) {
//debugPlan<<id;
m_id = id;
}
void Calendar::addCalendar( Calendar *calendar, int pos )
{
pos == -1 ? m_calendars.append( calendar ) : m_calendars.insert( pos, calendar );
calendar->setTimeZone( m_timeZone );
}
void Calendar::takeCalendar( Calendar *calendar )
{
int i = indexOf( calendar );
if ( i != -1 ) {
m_calendars.removeAt( i );
}
}
int Calendar::indexOf( const Calendar *calendar ) const
{
return m_calendars.indexOf( const_cast<Calendar*>(calendar) );
}
bool Calendar::loadCacheVersion( KoXmlElement &element, XMLLoaderObject &status )
{
Q_UNUSED(status);
m_cacheversion = element.attribute( QStringLiteral("version"), 0 ).toInt();
debugPlan<<m_name<<m_cacheversion;
return true;
}
void Calendar::saveCacheVersion( QDomElement &element ) const
{
QDomElement me = element.ownerDocument().createElement(QStringLiteral("cache"));
element.appendChild(me);
me.setAttribute(QStringLiteral("version"), QString::number(m_cacheversion));
}
bool Calendar::load( KoXmlElement &element, XMLLoaderObject &status ) {
//debugPlan<<element.text();
//bool ok;
m_blockversion = true;
setId(element.attribute(QStringLiteral("id")));
m_parentId = element.attribute(QStringLiteral("parent"));
m_name = element.attribute(QStringLiteral("name"),QLatin1String(""));
QTimeZone tz( element.attribute( QStringLiteral("timezone") ).toLatin1() );
if ( tz.isValid() ) {
setTimeZone( tz );
} else warnPlan<<"No timezone specified, use default (local)";
bool m_default = (bool)element.attribute(QStringLiteral("default"),QStringLiteral("0")).toInt();
if ( m_default ) {
status.project().setDefaultCalendar( this );
}
m_shared = element.attribute(QStringLiteral("shared"), QStringLiteral("0")).toInt();
#ifdef HAVE_KHOLIDAYS
setHolidayRegion(element.attribute(QStringLiteral("holiday-region")));
#endif
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == QLatin1String("weekday")) {
if ( !m_weekdays->load( e, status ) )
return false;
}
if (e.tagName() == QLatin1String("day")) {
CalendarDay *day = new CalendarDay();
if ( day->load( e, status ) ) {
if (!day->date().isValid()) {
delete day;
errorPlan<<m_name<<": Failed to load calendarDay - Invalid date";
} else {
CalendarDay *d = findDay(day->date());
if (d) {
// already exists, keep the new
delete takeDay(d);
warnPlan<<m_name<<" Load calendarDay - Date already exists";
}
addDay(day);
}
} else {
delete day;
errorPlan<<"Failed to load calendarDay";
return true; //false; don't throw away the whole calendar
}
}
}
// this must go last
KoXmlElement e = element.namedItem( QStringLiteral("cache") ).toElement();
if ( ! e.isNull() ) {
loadCacheVersion( e, status );
}
m_blockversion = false;
return true;
}
void Calendar::save(QDomElement &element) const {
//debugPlan<<m_name;
QDomElement me = element.ownerDocument().createElement(QStringLiteral("calendar"));
element.appendChild(me);
if (m_parent) {
me.setAttribute(QStringLiteral("parent"), m_parent->id());
}
me.setAttribute(QStringLiteral("name"), m_name);
me.setAttribute(QStringLiteral("id"), m_id);
if ( m_default ) {
me.setAttribute(QStringLiteral("default"), QString::number(m_default));
}
me.setAttribute(QStringLiteral("timezone"), m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString());
m_weekdays->save(me);
foreach (CalendarDay *d, m_days) {
QDomElement e = me.ownerDocument().createElement(QStringLiteral("day"));
me.appendChild(e);
d->save(e);
}
me.setAttribute(QStringLiteral("shared"), m_shared);
#ifdef HAVE_KHOLIDAYS
me.setAttribute(QStringLiteral("holiday-region"), m_regionCode);
#endif
saveCacheVersion( me );
}
int Calendar::state(QDate date) const
{
CalendarDay *day = findDay( date );
if ( day && day->state() != CalendarDay::Undefined ) {
return day->state();
}
#ifdef HAVE_KHOLIDAYS
if (isHoliday(date)) {
return CalendarDay::NonWorking;
}
#endif
day = weekday( date.dayOfWeek() );
if ( day && day->state() != CalendarDay::Undefined ) {
return day->state();
}
return m_parent ? m_parent->state( date ) : CalendarDay::Undefined;
}
CalendarDay *Calendar::findDay(QDate date, bool skipUndefined) const {
//debugPlan<<date.toString();
foreach (CalendarDay *d, m_days) {
if (d->date() == date) {
if (skipUndefined && d->state() == CalendarDay::Undefined) {
continue; // hmmm, break?
}
return d;
}
}
//debugPlan<<date.toString()<<" not found";
return 0;
}
void Calendar::setState( CalendarDay *day, CalendarDay::State state )
{
day->setState( state );
emit changed( day );
incCacheVersion();
}
void Calendar::addWorkInterval( CalendarDay *day, TimeInterval *ti )
{
emit workIntervalToBeAdded( day, ti, day->numIntervals() );
day->addInterval( ti );
emit workIntervalAdded( day, ti );
incCacheVersion();
}
void Calendar::takeWorkInterval( CalendarDay *day, TimeInterval *ti )
{
if ( !day->hasInterval(ti) ) {
return;
}
emit workIntervalToBeRemoved( day, ti );
day->removeInterval( ti );
emit workIntervalRemoved( day, ti );
incCacheVersion();
return;
}
void Calendar::setWorkInterval( TimeInterval *ti, const TimeInterval &value )
{
*ti = value;
emit changed( ti );
incCacheVersion();
}
void Calendar::setDate( CalendarDay *day, QDate date )
{
day->setDate( date );
emit changed( day );
incCacheVersion();
}
CalendarDay *Calendar::day( QDate date ) const
{
foreach ( CalendarDay *d, m_days ) {
if ( d->date() == date ) {
return d;
}
}
return 0;
}
IntMap Calendar::weekdayStateMap() const
{
return m_weekdays->stateMap();
}
void Calendar::setWeekday( int dayno, const CalendarDay &day )
{
if ( dayno < 1 || dayno > 7 ) {
return;
}
CalendarDay *wd = weekday( dayno );
while ( ! wd->timeIntervals().isEmpty() ) {
TimeInterval *ti = wd->timeIntervals().constLast();
emit workIntervalToBeRemoved( wd, ti );
wd->removeInterval( ti );
emit workIntervalRemoved( wd, ti );
}
wd->setState( day.state() );
emit changed( wd );
foreach ( TimeInterval *ti, day.timeIntervals() ) {
TimeInterval *t = new TimeInterval( *ti );
emit workIntervalToBeAdded( wd, t, wd->numIntervals() ); // hmmmm
wd->addInterval( t );
emit workIntervalAdded( wd, t );
}
incCacheVersion();
}
bool Calendar::hasParent(Calendar *cal) {
//debugPlan;
if (!m_parent)
return false;
if (m_parent == cal)
return true;
return m_parent->hasParent(cal);
}
AppointmentIntervalList Calendar::workIntervals( const QDateTime &start, const QDateTime &end, double load ) const
{
//debugPlan<<start<<end<<load;
AppointmentIntervalList lst;
TimeInterval res;
QTime startTime = start.time();
int length = 0;
if ( start.date() == end.date() ) {
// Handle single day
length = startTime.msecsTo( end.time() );
if ( length <= 0 ) {
warnPlan<<"Invalid length"<<length;
return lst;
}
//debugPlan<<"Check single day:"<<s.date()<<s.time()<<length;
res = firstInterval(start.date(), startTime, length, 0);
while ( res.isValid() ) {
DateTime dt( start.date(), res.startTime(), m_timeZone );
lst.add( AppointmentInterval( dt.toTimeZone(projectTimeZone()), dt.addMSecs( res.second ).toTimeZone(projectTimeZone()), load ) );
length -= res.second;
if ( length <= 0 || res.endsMidnight() ) {
break;
}
res = firstInterval( start.date(), res.endTime(), length, 0 );
}
//debugPlan<<lst;
return lst;
}
//debugPlan<<"tospec:"<<s.toString()<<" -"<<e.toString();
// Multiple days
for ( QDate date = start.date(); date <= end.date(); date = date.addDays(1) ) {
if (date > start.date()) {
startTime = QTime(0, 0, 0);
}
if (date < end.date()) {
length = startTime.msecsTo( QTime(23, 59, 59, 999) ) + 1;
} else {
length = startTime.msecsTo( end.time() );
}
if ( length <= 0 ) {
break;
}
res = firstInterval( date, startTime, length );
while ( res.isValid() ) {
//debugPlan<<"interval:"<<date<<startTime<<'='<<res.first<<res.second;
DateTime dt1( date, res.startTime(), m_timeZone );
DateTime dt2( date, res.endTime(), m_timeZone );
if ( res.endsMidnight() ) {
dt2 = dt2.addDays( 1 );
}
AppointmentInterval i( dt1.toTimeZone(projectTimeZone()), dt2.toTimeZone(projectTimeZone()), load );
lst.add( i );
debugPlan<<dt1<<dt2<<lst;
length -= startTime.msecsTo( res.endTime() );
if ( length <= 0 || res.endsMidnight() ) {
break;
}
startTime = res.endTime();
res = firstInterval( date, startTime, length, 0 );
}
}
//debugPlan<<lst;
return lst;
}
AppointmentIntervalList Calendar::workIntervals( const DateTime &start, const DateTime &end, double load ) const
{
// debugPlan<<start<<end<<load;
AppointmentIntervalList lst;
if (!start.isValid()) {
warnPlan<<"Invalid start time";
return lst;
}
if (!end.isValid()) {
warnPlan<<"Invalid end time";
return lst;
}
if ( start >= end ) {
warnPlan<<"Invalid interval";
return lst;
}
Q_ASSERT(m_timeZone.isValid());
QDateTime zonedStart = start.toTimeZone( m_timeZone );
QDateTime zonedEnd = end.toTimeZone( m_timeZone );
Q_ASSERT( zonedStart.isValid() && zonedEnd.isValid() );
return workIntervals( zonedStart, zonedEnd, load );
}
Duration Calendar::effort(QDate date, QTime start, int length, Schedule *sch) const {
// debugPlan<<m_name<<":"<<date<<""<<start<<"->"<<length;
if (length <= 0) {
return Duration::zeroDuration;
}
// first, check my own day
CalendarDay *day = findDay(date, true);
if (day) {
if (day->state() == CalendarDay::Working) {
return day->effort(start, length, m_timeZone, sch);
} else if (day->state() == CalendarDay::NonWorking) {
return Duration::zeroDuration;
} else {
errorPlan<<"Invalid state: "<<day->state();
return Duration::zeroDuration;
}
}
#ifdef HAVE_KHOLIDAYS
if (isHoliday(date)) {
return Duration::zeroDuration;
}
#endif
// check my own weekdays
if (m_weekdays) {
if (m_weekdays->state(date) == CalendarDay::Working) {
return m_weekdays->effort(date, start, length, m_timeZone, sch);
}
if (m_weekdays->state(date) == CalendarDay::NonWorking) {
return Duration::zeroDuration;
}
}
if (m_parent) {
return m_parent->effort(date, start, length, sch);
}
return Duration::zeroDuration;
}
Duration Calendar::effort(const QDateTime &start, const QDateTime &end, Schedule *sch) const {
// debugPlan<<m_name<<":"<<start<<"to"<<end;
Duration eff;
QDate date = start.date();
QTime startTime = start.time();
QTime endTime = end.time();
int length = 0;
if ( date == end.date() ) {
// single day
length = startTime.msecsTo( endTime );
return effort( date, startTime, length, sch );
}
length = startTime.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1;
QTime t0(0, 0, 0);
int aday = t0.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1;
eff = effort(date, startTime, length, sch); // first day
// Now get all the rest of the days
for (date = date.addDays(1); date <= end.date(); date = date.addDays(1)) {
if (date < end.date()) {
eff += effort(date, t0, aday, sch); // whole days
} else if ( endTime > t0 ) {
eff += effort(date, t0, t0.msecsTo( endTime ), sch); // last day
}
//debugPlan<<": eff now="<<eff.toString(Duration::Format_Day);
}
//debugPlan<<start<<"-"<<end<<": total="<<eff.toString();
return eff;
}
Duration Calendar::effort(const DateTime &start, const DateTime &end, Schedule *sch) const {
// debugPlan<<m_name<<":"<<start<<start.timeSpec()<<"to"<<end<<end.timeSpec();
Duration eff;
if (!start.isValid() || !end.isValid() || end < start) {
if ( sch && sch->resource() ) debugPlan<<sch->resource()->name()<<sch->name()<<"Available:"<<sch->resource()->availableFrom()<<sch->resource()->availableUntil();
errorPlan<<"Illegal datetime: "<<start<<", "<<end;
return eff;
}
if ( start == end ) {
//debugPlan<<"start == end";
return eff;
}
Q_ASSERT(m_timeZone.isValid());
QDateTime zonedStart = start.toTimeZone( m_timeZone );
QDateTime zonedEnd = end.toTimeZone( m_timeZone );
Q_ASSERT( zonedStart.isValid() && zonedEnd.isValid() );
return effort( zonedStart, zonedEnd, sch );
}
TimeInterval Calendar::firstInterval(QDate date, QTime startTime, int length, Schedule *sch) const {
//debugPlan;
CalendarDay *day = findDay(date, true);
if (day) {
return day->interval(startTime, length, m_timeZone, sch);
}
#ifdef HAVE_KHOLIDAYS
if (isHoliday(date)) {
return TimeInterval();
}
#endif
if (m_weekdays) {
if (m_weekdays->state(date) == CalendarDay::Working) {
//debugPlan<<"Check weekday";
TimeInterval i = m_weekdays->interval(date, startTime, length, m_timeZone, sch);
//debugPlan<<"Checked weekday, got"<<i;
return i;
}
if (m_weekdays->state(date) == CalendarDay::NonWorking) {
return TimeInterval();
}
}
if (m_parent) {
//debugPlan<<"Check parent";
return m_parent->firstInterval(date, startTime, length, sch);
}
return TimeInterval();
}
DateTimeInterval Calendar::firstInterval( const QDateTime &start, const QDateTime &end, Schedule *sch) const
{
TimeInterval res;
QTime startTime = start.time();
int length = 0;
if ( start.date() == end.date() ) {
// Handle single day
length = startTime.msecsTo( end.time() );
if ( length <= 0 ) {
warnPlan<<"Invalid length"<<length;
return DateTimeInterval();
}
//debugPlan<<"Check single day:"<<s.date()<<s.time()<<length;
res = firstInterval(start.date(), startTime, length, sch);
if ( ! res.isValid() ) {
return DateTimeInterval();
}
DateTime dt1( start.date(), res.first, m_timeZone );
DateTimeInterval dti( dt1, DateTime( dt1.addMSecs( res.second ) ).toTimeZone( m_timeZone ) );
return dti;
}
//debugPlan<<"tospec:"<<s.toString()<<" -"<<e.toString();
// Multiple days
for ( QDate date = start.date(); date <= end.date(); date = date.addDays(1) ) {
if (date > start.date()) {
startTime = QTime(0, 0, 0);
}
if (date < end.date()) {
length = startTime.msecsTo( QTime(23, 59, 59, 999) ) + 1;
} else {
length = startTime.msecsTo( end.time() );
}
if ( length <= 0 ) {
break;
}
//debugPlan<<"Check:"<<date<<startTime<<"+"<<length<<"="<<startTime.addMSecs( length );
res = firstInterval(date, startTime, length, sch);
if ( res.isValid() ) {
//debugPlan<<"inp:"<<start<<"-"<<end;
//debugPlan<<"Found an interval ("<<date<<","<<res.first<<","<<res.second<<")";
// return result in callers timezone
DateTime dt1( date, res.first, m_timeZone );
DateTimeInterval dti( DateTime( dt1 ), dt1.addMSecs( res.second ).toTimeZone( m_timeZone ) );
//debugPlan<<"Result firstInterval:"<<dti.first.toString()<<","<<dti.second.toString();
return dti;
}
}
//warnPlan<<"Didn't find an interval ("<<start<<", "<<end<<")";
return DateTimeInterval();
}
DateTimeInterval Calendar::firstInterval(const DateTime &start, const DateTime &end, Schedule *sch) const
{
//debugPlan<<"inp:"<<start.toString()<<" -"<<end.toString();
if (!start.isValid()) {
warnPlan<<"Invalid start time";
return DateTimeInterval(DateTime(), DateTime());
}
if (!end.isValid()) {
warnPlan<<"Invalid end time";
return DateTimeInterval(DateTime(), DateTime());
}
if ( start >= end ) {
warnPlan<<"Invalid interval"<<start<<end<<":"<<start<<end;
return DateTimeInterval();
}
Q_ASSERT( m_timeZone.isValid() );
QDateTime zonedStart = start.toTimeZone( m_timeZone );
QDateTime zonedEnd = end.toTimeZone( m_timeZone );
Q_ASSERT( zonedStart.isValid() && zonedEnd.isValid() );
return firstInterval( zonedStart, zonedEnd, sch );
}
bool Calendar::hasInterval(QDate date, QTime startTime, int length, Schedule *sch) const
{
//debugPlan;
return ! firstInterval( date, startTime, length, sch ).first.isNull();
}
bool Calendar::hasInterval(const DateTime &start, const DateTime &end, Schedule *sch) const {
//debugPlan;
return ! firstInterval( start, end, sch ).first.isNull();
}
DateTime Calendar::firstAvailableAfter(const DateTime &time, const DateTime &limit, Schedule *sch ) {
//debugPlan<<m_name<<": check from"<<time<<" limit="<<limit;
if (!time.isValid() || !limit.isValid() || time > limit) {
errorPlan<<"Invalid input: "<<(time.isValid()?"":"(time invalid) ")<<(limit.isValid()?"":"(limit invalid) ")<<(time>limit?"":"(time>limit)");
return DateTime();
}
if ( time == limit ) {
return DateTime();
}
Q_ASSERT( m_timeZone.isValid() );
QDateTime zonedTime = time.toTimeZone( m_timeZone );
QDateTime zonedLimit = limit.toTimeZone( m_timeZone );
Q_ASSERT( zonedTime.isValid() && zonedLimit.isValid() );
return firstInterval( zonedTime, zonedLimit, sch ).first;
}
DateTime Calendar::firstAvailableBefore(const QDateTime &time, const QDateTime &limit, Schedule *sch) {
debugPlan<<m_name<<"check from"<<time<<"limit="<<limit;
if ( !time.isValid() || !limit.isValid() ) {
warnPlan<<"Calendar::firstAvailableBefore:"<<"Invalid datetimes";
return DateTime();
}
Q_ASSERT(time.timeZone() == m_timeZone);
QDateTime lmt = time;
QDateTime t = QDateTime( time.date(), QTime( 0, 0, 0 ), m_timeZone ); // start of first day
if ( t == lmt ) {
t = t.addDays(-1); // in case time == start of day
}
if ( t < limit ) {
t = limit; // always stop at limit (lower boundary)
}
//debugPlan<<m_name<<":"<<time<<limit<<t<<lmt;
QDateTime res;
//debugPlan<<m_name<<": t="<<t<<","<<lmt<<" limit="<<limit;
while (!res.isValid() && t >= limit) {
// check intervals for 1 day
QDateTime r = firstInterval( t, lmt, sch ).second.toTimeZone( m_timeZone );
res = r;
// Find the last interval
while(r.isValid() && r < lmt) {
r = firstInterval(r, lmt, sch).second.toTimeZone( m_timeZone );
if (r.isValid() ) {
res = r;
}
//debugPlan<<m_name<<": r="<<r<<","<<lmt<<" res="<<res;
}
if (!res.isValid()) {
if (t == limit) {
break;
}
lmt = t;
t = t.addDays(-1);
if (t < limit) {
t = limit;
}
if (t == lmt)
break;
}
}
DateTime result( res.toTimeZone(projectTimeZone()) );
//debugPlan<<m_name<<res<<res.dateTime().timeSpec()<<result<<result.timeSpec();
return result; // return in local timezone
}
DateTime Calendar::firstAvailableBefore(const DateTime &time, const DateTime &limit, Schedule *sch) {
//debugPlan<<m_name<<"check from"<<time<<time.timeSpec()<<" limit="<<limit<<limit.timeSpec();
if (!time.isValid() || !limit.isValid() || time < limit) {
errorPlan<<"Invalid input: "<<(time.isValid()?"":"(time invalid) ")<<(limit.isValid()?"":"(limit invalid) ")<<(time<limit?"":"(time<limit)");
return DateTime();
}
if ( time == limit ) {
return DateTime();
}
Q_ASSERT(m_timeZone.isValid());
return firstAvailableBefore( time.toTimeZone(m_timeZone), limit.toTimeZone(m_timeZone), sch );
}
Calendar *Calendar::findCalendar(const QString &id) const {
return (m_project ? m_project->findCalendar(id) : 0);
}
bool Calendar::removeId(const QString &id) {
return (m_project ? m_project->removeCalendarId(id) : false);
}
void Calendar::insertId(const QString &id){
if (m_project)
m_project->insertCalendarId(id, this);
}
void Calendar::addDay( CalendarDay *day )
{
emit dayToBeAdded( day, 0 );
m_days.insert(0, day);
emit dayAdded( day );
incCacheVersion();
}
CalendarDay *Calendar::takeDay(CalendarDay *day)
{
int i = m_days.indexOf(day);
if (i == -1) {
return 0;
}
emit dayToBeRemoved( day );
m_days.removeAt(i);
emit dayRemoved( day );
incCacheVersion();
return day;
}
QList<std::pair<CalendarDay*, CalendarDay*> > Calendar::consecutiveVacationDays() const
{
QList<std::pair<CalendarDay*, CalendarDay*> > lst;
std::pair<CalendarDay*, CalendarDay*> interval( 0, 0 );
foreach ( CalendarDay* day, m_days ) {
if ( day->state() == CalendarDay::NonWorking ) {
if ( interval.first == 0 ) {
interval.first = day;
}
interval.second = day;
} else {
if ( interval.first != 0 ) {
lst << std::pair<CalendarDay*, CalendarDay*>( interval );
}
interval.first = interval.second = 0;
}
}
return lst;
}
QList<CalendarDay*> Calendar::workingDays() const
{
QList<CalendarDay*> lst;
foreach ( CalendarDay* day, m_days ) {
if ( day->state() == CalendarDay::Working ) {
lst << day;
}
}
return lst;
}
bool Calendar::isShared() const
{
return m_shared;
}
void Calendar::setShared(bool on)
{
m_shared = on;
}
#ifdef HAVE_KHOLIDAYS
bool Calendar::isHoliday(QDate date) const
{
if (m_region->isValid()) {
KHolidays::Holiday::List lst = m_region->holidays(date);
if (!lst.isEmpty() && lst.first().dayType() != KHolidays::Holiday::Workday) {
return true;
}
}
return false;
}
KHolidays::HolidayRegion *Calendar::holidayRegion() const
{
return m_region;
}
void Calendar::setHolidayRegion(const QString &code)
{
delete m_region;
m_regionCode = code;
if (code == QLatin1String("Default")) {
QString country;
if (m_timeZone.isValid()) {
// TODO be more accurate when country has multiple timezones/regions
country = tzZones->value(m_timeZone.id()).country;
}
m_region = new KHolidays::HolidayRegion(KHolidays::HolidayRegion::defaultRegionCode(country));
} else {
m_region = new KHolidays::HolidayRegion(code);
}
debugPlan<<code<<"->"<<m_regionCode<<m_region->isValid();
emit changed(static_cast<CalendarDay*>(0));
if (m_project) {
m_project->changed(this);
}
}
QString Calendar::holidayRegionCode() const
{
return m_regionCode;
}
QStringList Calendar::holidayRegionCodes() const
{
QStringList lst = KHolidays::HolidayRegion::regionCodes();
lst.removeDuplicates();
return lst;
}
#endif
/////////////
StandardWorktime::StandardWorktime( Project *project )
: m_project( project )
{
init();
}
StandardWorktime::StandardWorktime(StandardWorktime *worktime) {
if (worktime) {
m_year = worktime->durationYear();
m_month = worktime->durationMonth();
m_week = worktime->durationWeek();
m_day = worktime->durationDay();
} else {
init();
}
}
StandardWorktime::~StandardWorktime()
{
//debugPlan<<"("<<this<<")";
}
void StandardWorktime::init() {
// Some sane default values
m_year = Duration(0, 1760, 0);
m_month = Duration(0, 176, 0);
m_week = Duration(0, 40, 0);
m_day = Duration(0, 8, 0);
}
void StandardWorktime::changed()
{
if ( m_project ) {
m_project->changed( this );
}
}
QList<qint64> StandardWorktime::scales() const
{
return QList<qint64>() << m_year.milliseconds() << m_month.milliseconds() << m_week.milliseconds() << m_day.milliseconds() << 60*60*1000 << 60*1000 << 1000 << 1;
}
bool StandardWorktime::load( KoXmlElement &element, XMLLoaderObject &status ) {
//debugPlan;
m_year = Duration::fromString(element.attribute(QStringLiteral("year")), Duration::Format_Hour);
m_month = Duration::fromString(element.attribute(QStringLiteral("month")), Duration::Format_Hour);
m_week = Duration::fromString(element.attribute(QStringLiteral("week")), Duration::Format_Hour);
m_day = Duration::fromString(element.attribute(QStringLiteral("day")), Duration::Format_Hour);
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == QLatin1String("calendar")) {
// pre 0.6 version stored base calendar in standard worktime
if ( status.version() >= QLatin1String("0.6") ) {
warnPlan<<"Old format, calendar in standard worktime";
warnPlan<<"Tries to load anyway";
}
// try to load anyway
Calendar *calendar = new Calendar;
if ( calendar->load( e, status ) ) {
status.project().addCalendar( calendar );
calendar->setDefault( true );
status.project().setDefaultCalendar( calendar ); // hmmm
status.setBaseCalendar( calendar );
} else {
delete calendar;
errorPlan<<"Failed to load calendar";
}
}
}
return true;
}
void StandardWorktime::save(QDomElement &element) const {
//debugPlan;
QDomElement me = element.ownerDocument().createElement(QStringLiteral("standard-worktime"));
element.appendChild(me);
me.setAttribute(QStringLiteral("year"), m_year.toString(Duration::Format_Hour));
me.setAttribute(QStringLiteral("month"), m_month.toString(Duration::Format_Hour));
me.setAttribute(QStringLiteral("week"), m_week.toString(Duration::Format_Hour));
me.setAttribute(QStringLiteral("day"), m_day.toString(Duration::Format_Hour));
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptcommand.cpp b/src/libs/kernel/kptcommand.cpp
index b0e004c9..6e4ddd1f 100644
--- a/src/libs/kernel/kptcommand.cpp
+++ b/src/libs/kernel/kptcommand.cpp
@@ -1,3759 +1,3760 @@
/* This file is part of the KDE project
Copyright (C) 2004 - 2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2011 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptcommand.h"
#include "kptaccount.h"
#include "kptappointment.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptcalendar.h"
#include "kptrelation.h"
#include "kptresource.h"
#include "kptdocuments.h"
#include "kptlocale.h"
#include "kptdebug.h"
#include <QApplication>
const QLoggingCategory &PLANCMDINSPROJECT_LOG()
{
static const QLoggingCategory category("calligra.plan.command.insertProject");
return category;
}
#define debugPlanInsertProject qCDebug(PLANCMDINSPROJECT_LOG)
#define warnPlanInsertProject qCWarning(PLANCMDINSPROJECT_LOG)
#define errorPlanInsertProject qCCritical(PLANCMDINSPROJECT_LOG)
namespace KPlato
{
void NamedCommand::setSchScheduled()
{
QHash<Schedule*, bool>::ConstIterator it;
for ( it = m_schedules.constBegin(); it != m_schedules.constEnd(); ++it ) {
//debugPlan << it.key() ->name() <<":" << it.value();
it.key() ->setScheduled( it.value() );
}
}
void NamedCommand::setSchScheduled( bool state )
{
QHash<Schedule*, bool>::ConstIterator it;
for ( it = m_schedules.constBegin(); it != m_schedules.constEnd(); ++it ) {
//debugPlan << it.key() ->name() <<":" << state;
it.key() ->setScheduled( state );
}
}
void NamedCommand::addSchScheduled( Schedule *sch )
{
//debugPlan << sch->id() <<":" << sch->isScheduled();
m_schedules.insert( sch, sch->isScheduled() );
foreach ( Appointment * a, sch->appointments() ) {
if ( a->node() == sch ) {
m_schedules.insert( a->resource(), a->resource() ->isScheduled() );
} else if ( a->resource() == sch ) {
m_schedules.insert( a->node(), a->node() ->isScheduled() );
}
}
}
//---------
MacroCommand::~MacroCommand()
{
while ( ! cmds.isEmpty() ) {
delete cmds.takeLast();
}
}
void MacroCommand::addCommand( KUndo2Command *cmd )
{
cmds.append( cmd );
}
void MacroCommand::execute()
{
foreach ( KUndo2Command *c, cmds ) {
c->redo();
}
}
void MacroCommand::unexecute()
{
for (int i = cmds.count() - 1; i >= 0; --i) {
cmds.at( i )->undo();
}
}
//-------------------------------------------------
CalendarAddCmd::CalendarAddCmd( Project *project, Calendar *cal, int pos, Calendar *parent, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_cal( cal ),
m_pos( pos ),
m_parent( parent ),
m_mine( true )
{
//debugPlan<<cal->name();
Q_ASSERT( project != 0 );
}
CalendarAddCmd::~CalendarAddCmd()
{
if ( m_mine )
delete m_cal;
}
void CalendarAddCmd::execute()
{
if ( m_project ) {
m_project->addCalendar( m_cal, m_parent, m_pos );
m_mine = false;
}
//debugPlan<<m_cal->name()<<" added to:"<<m_project->name();
}
void CalendarAddCmd::unexecute()
{
if ( m_project ) {
m_project->takeCalendar( m_cal );
m_mine = true;
}
//debugPlan<<m_cal->name();
}
CalendarRemoveCmd::CalendarRemoveCmd( Project *project, Calendar *cal, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_parent( cal->parentCal() ),
m_cal( cal ),
m_index( -1 ),
m_mine( false ),
m_cmd( new MacroCommand(KUndo2MagicString()) )
{
Q_ASSERT( project != 0 );
m_index = m_parent ? m_parent->indexOf( cal ) : project->indexOf( cal );
foreach ( Resource *r, project->resourceList() ) {
if ( r->calendar( true ) == cal ) {
m_cmd->addCommand( new ModifyResourceCalendarCmd( r, 0 ) );
}
}
if ( project->defaultCalendar() == cal ) {
m_cmd->addCommand( new ProjectModifyDefaultCalendarCmd( project, 0 ) );
}
foreach ( Calendar *c, cal->calendars() ) {
m_cmd->addCommand( new CalendarRemoveCmd( project, c ) );
}
}
CalendarRemoveCmd::~CalendarRemoveCmd()
{
delete m_cmd;
if ( m_mine )
delete m_cal;
}
void CalendarRemoveCmd::execute()
{
m_cmd->execute();
m_project->takeCalendar( m_cal );
m_mine = true;
}
void CalendarRemoveCmd::unexecute()
{
m_project->addCalendar( m_cal, m_parent, m_index );
m_cmd->unexecute();
m_mine = false;
}
CalendarMoveCmd::CalendarMoveCmd( Project *project, Calendar *cal, int position, Calendar *parent, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_cal( cal ),
m_newpos( position ),
m_newparent( parent ),
m_oldparent( cal->parentCal() )
{
//debugPlan<<cal->name();
Q_ASSERT( project != 0 );
m_oldpos = m_oldparent ? m_oldparent->indexOf( cal ) : project->indexOf( cal );
}
void CalendarMoveCmd::execute()
{
m_project->takeCalendar( m_cal );
m_project->addCalendar( m_cal, m_newparent, m_newpos );
}
void CalendarMoveCmd::unexecute()
{
m_project->takeCalendar( m_cal );
m_project->addCalendar( m_cal, m_oldparent, m_oldpos );
}
CalendarModifyNameCmd::CalendarModifyNameCmd( Calendar *cal, const QString& newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal )
{
m_oldvalue = cal->name();
m_newvalue = newvalue;
//debugPlan<<cal->name();
}
void CalendarModifyNameCmd::execute()
{
m_cal->setName( m_newvalue );
//debugPlan<<m_cal->name();
}
void CalendarModifyNameCmd::unexecute()
{
m_cal->setName( m_oldvalue );
//debugPlan<<m_cal->name();
}
CalendarModifyParentCmd::CalendarModifyParentCmd( Project *project, Calendar *cal, Calendar *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_cal( cal ),
m_cmd( new MacroCommand( KUndo2MagicString() ) ),
m_oldindex( -1 ),
m_newindex( -1 )
{
m_oldvalue = cal->parentCal();
m_newvalue = newvalue;
m_oldindex = m_oldvalue ? m_oldvalue->indexOf( cal ) : m_project->indexOf( cal );
if ( newvalue ) {
m_cmd->addCommand( new CalendarModifyTimeZoneCmd( cal, newvalue->timeZone() ) );
}
//debugPlan<<cal->name();
}
CalendarModifyParentCmd::~CalendarModifyParentCmd()
{
delete m_cmd;
}
void CalendarModifyParentCmd::execute()
{
m_project->takeCalendar( m_cal );
m_project->addCalendar( m_cal, m_newvalue, m_newindex );
m_cmd->execute();
}
void CalendarModifyParentCmd::unexecute()
{
m_cmd->unexecute();
m_project->takeCalendar( m_cal );
m_project->addCalendar( m_cal, m_oldvalue, m_oldindex );
}
CalendarModifyTimeZoneCmd::CalendarModifyTimeZoneCmd( Calendar *cal, const QTimeZone &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_newvalue( value ),
m_cmd( new MacroCommand( KUndo2MagicString() ) )
{
m_oldvalue = cal->timeZone();
foreach ( Calendar *c, cal->calendars() ) {
m_cmd->addCommand( new CalendarModifyTimeZoneCmd( c, value ) );
}
//debugPlan<<cal->name();
}
CalendarModifyTimeZoneCmd::~CalendarModifyTimeZoneCmd()
{
delete m_cmd;
}
void CalendarModifyTimeZoneCmd::execute()
{
m_cmd->execute();
m_cal->setTimeZone( m_newvalue );
}
void CalendarModifyTimeZoneCmd::unexecute()
{
m_cal->setTimeZone( m_oldvalue );
m_cmd->unexecute();
}
#ifdef HAVE_KHOLIDAYS
CalendarModifyHolidayRegionCmd::CalendarModifyHolidayRegionCmd( Calendar *cal, const QString &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_newvalue( value )
{
m_oldvalue = cal->holidayRegionCode();
}
CalendarModifyHolidayRegionCmd::~CalendarModifyHolidayRegionCmd()
{
}
void CalendarModifyHolidayRegionCmd::execute()
{
m_cal->setHolidayRegion( m_newvalue );
}
void CalendarModifyHolidayRegionCmd::unexecute()
{
m_cal->setHolidayRegion( m_oldvalue );
}
#endif
CalendarAddDayCmd::CalendarAddDayCmd( Calendar *cal, CalendarDay *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_mine( true )
{
m_newvalue = newvalue;
//debugPlan<<cal->name();
}
CalendarAddDayCmd::~CalendarAddDayCmd()
{
//debugPlan;
if ( m_mine )
delete m_newvalue;
}
void CalendarAddDayCmd::execute()
{
//debugPlan<<m_cal->name();
m_cal->addDay( m_newvalue );
m_mine = false;
}
void CalendarAddDayCmd::unexecute()
{
//debugPlan<<m_cal->name();
m_cal->takeDay( m_newvalue );
m_mine = true;
}
CalendarRemoveDayCmd::CalendarRemoveDayCmd( Calendar *cal,CalendarDay *day, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_value( day ),
m_mine( false )
{
//debugPlan<<cal->name();
// TODO check if any resources uses this calendar
init();
}
CalendarRemoveDayCmd::CalendarRemoveDayCmd( Calendar *cal, const QDate &day, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_mine( false )
{
m_value = cal->findDay( day );
//debugPlan<<cal->name();
// TODO check if any resources uses this calendar
init();
}
void CalendarRemoveDayCmd::init()
{
}
void CalendarRemoveDayCmd::execute()
{
//debugPlan<<m_cal->name();
m_cal->takeDay( m_value );
m_mine = true;
}
void CalendarRemoveDayCmd::unexecute()
{
//debugPlan<<m_cal->name();
m_cal->addDay( m_value );
m_mine = false;
}
CalendarModifyDayCmd::CalendarModifyDayCmd( Calendar *cal, CalendarDay *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_mine( true )
{
m_newvalue = value;
m_oldvalue = cal->findDay( value->date() );
//debugPlan<<cal->name()<<" old:("<<m_oldvalue<<") new:("<<m_newvalue<<")";
}
CalendarModifyDayCmd::~CalendarModifyDayCmd()
{
//debugPlan;
if ( m_mine ) {
delete m_newvalue;
} else {
delete m_oldvalue;
}
}
void CalendarModifyDayCmd::execute()
{
//debugPlan;
if ( m_oldvalue ) {
m_cal->takeDay( m_oldvalue );
}
m_cal->addDay( m_newvalue );
m_mine = false;
}
void CalendarModifyDayCmd::unexecute()
{
//debugPlan;
m_cal->takeDay( m_newvalue );
if ( m_oldvalue ) {
m_cal->addDay( m_oldvalue );
}
m_mine = true;
}
CalendarModifyStateCmd::CalendarModifyStateCmd( Calendar *calendar, CalendarDay *day, CalendarDay::State value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_calendar( calendar ),
m_day( day ),
m_cmd( new MacroCommand( KUndo2MagicString() ) )
{
m_newvalue = value;
m_oldvalue = (CalendarDay::State)day->state();
if ( value != CalendarDay::Working ) {
foreach ( TimeInterval *ti, day->timeIntervals() ) {
m_cmd->addCommand( new CalendarRemoveTimeIntervalCmd( calendar, day, ti ) );
}
}
}
CalendarModifyStateCmd::~CalendarModifyStateCmd()
{
delete m_cmd;
}
void CalendarModifyStateCmd::execute()
{
//debugPlan;
m_cmd->execute();
m_calendar->setState( m_day, m_newvalue );
}
void CalendarModifyStateCmd::unexecute()
{
//debugPlan;
m_calendar->setState( m_day, m_oldvalue );
m_cmd->unexecute();
}
CalendarModifyTimeIntervalCmd::CalendarModifyTimeIntervalCmd( Calendar *calendar, TimeInterval &newvalue, TimeInterval *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_calendar( calendar )
{
m_value = value; // keep pointer
m_oldvalue = *value; // save value
m_newvalue = newvalue;
}
void CalendarModifyTimeIntervalCmd::execute()
{
//debugPlan;
m_calendar->setWorkInterval( m_value, m_newvalue );
}
void CalendarModifyTimeIntervalCmd::unexecute()
{
//debugPlan;
m_calendar->setWorkInterval( m_value, m_oldvalue );
}
CalendarAddTimeIntervalCmd::CalendarAddTimeIntervalCmd( Calendar *calendar, CalendarDay *day, TimeInterval *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_calendar( calendar ),
m_day( day ),
m_value( value ),
m_mine( true )
{
}
CalendarAddTimeIntervalCmd::~CalendarAddTimeIntervalCmd()
{
if ( m_mine )
delete m_value;
}
void CalendarAddTimeIntervalCmd::execute()
{
//debugPlan;
m_calendar->addWorkInterval( m_day, m_value );
m_mine = false;
}
void CalendarAddTimeIntervalCmd::unexecute()
{
//debugPlan;
m_calendar->takeWorkInterval( m_day, m_value );
m_mine = true;
}
CalendarRemoveTimeIntervalCmd::CalendarRemoveTimeIntervalCmd( Calendar *calendar, CalendarDay *day, TimeInterval *value, const KUndo2MagicString& name )
: CalendarAddTimeIntervalCmd( calendar, day, value, name )
{
m_mine = false ;
}
void CalendarRemoveTimeIntervalCmd::execute()
{
CalendarAddTimeIntervalCmd::unexecute();
}
void CalendarRemoveTimeIntervalCmd::unexecute()
{
CalendarAddTimeIntervalCmd::execute();
}
CalendarModifyWeekdayCmd::CalendarModifyWeekdayCmd( Calendar *cal, int weekday, CalendarDay *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_weekday( weekday ),
m_cal( cal ),
m_value( value ),
m_orig( *( cal->weekday( weekday ) ) )
{
//debugPlan << cal->name() <<" (" << value <<")";
}
CalendarModifyWeekdayCmd::~CalendarModifyWeekdayCmd()
{
//debugPlan << m_weekday <<":" << m_value;
delete m_value;
}
void CalendarModifyWeekdayCmd::execute()
{
m_cal->setWeekday( m_weekday, *m_value );
}
void CalendarModifyWeekdayCmd::unexecute()
{
m_cal->setWeekday( m_weekday, m_orig );
}
CalendarModifyDateCmd::CalendarModifyDateCmd( Calendar *cal, CalendarDay *day, const QDate &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_cal( cal ),
m_day( day ),
m_newvalue( value ),
m_oldvalue( day->date() )
{
//debugPlan << cal->name() <<" (" << value <<")";
}
void CalendarModifyDateCmd::execute()
{
m_cal->setDate( m_day, m_newvalue );
}
void CalendarModifyDateCmd::unexecute()
{
m_cal->setDate( m_day, m_oldvalue );
}
ProjectModifyDefaultCalendarCmd::ProjectModifyDefaultCalendarCmd( Project *project, Calendar *cal, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_newvalue( cal ),
m_oldvalue( project->defaultCalendar() )
{
//debugPlan << cal->name() <<" (" << value <<")";
}
void ProjectModifyDefaultCalendarCmd::execute()
{
m_project->setDefaultCalendar( m_newvalue );
}
void ProjectModifyDefaultCalendarCmd::unexecute()
{
m_project->setDefaultCalendar( m_oldvalue );
}
NodeDeleteCmd::NodeDeleteCmd( Node *node, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_index( -1 ),
m_relCmd(0)
{
m_parent = node->parentNode();
m_mine = false;
m_project = static_cast<Project*>( node->projectNode() );
if ( m_project ) {
foreach ( Schedule * s, m_project->schedules() ) {
if ( s && s->isScheduled() ) {
// Only invalidate schedules this node is part of
Schedule *ns = node->findSchedule( s->id() );
if ( ns && ! ns->isDeleted() ) {
addSchScheduled( s );
}
}
}
}
m_cmd = new MacroCommand( KUndo2MagicString() );
QList<Node*> lst = node->childNodeIterator();
for ( int i = lst.count(); i > 0; --i ) {
m_cmd->addCommand( new NodeDeleteCmd( lst[ i - 1 ] ) );
}
if ( node->runningAccount() ) {
m_cmd->addCommand( new NodeModifyRunningAccountCmd( *node, node->runningAccount(), 0 ) );
}
if ( node->startupAccount() ) {
m_cmd->addCommand( new NodeModifyRunningAccountCmd( *node, node->startupAccount(), 0 ) );
}
if ( node->shutdownAccount() ) {
m_cmd->addCommand( new NodeModifyRunningAccountCmd( *node, node->shutdownAccount(), 0 ) );
}
}
NodeDeleteCmd::~NodeDeleteCmd()
{
delete m_relCmd; // before node
if ( m_mine ) {
delete m_node;
}
delete m_cmd;
while ( !m_appointments.isEmpty() )
delete m_appointments.takeFirst();
}
void NodeDeleteCmd::execute()
{
if ( m_parent && m_project ) {
m_index = m_parent->findChildNode( m_node );
//debugPlan<<m_node->name()<<""<<m_index;
if ( !m_relCmd ) {
m_relCmd = new MacroCommand();
// Only add delete relation commands if we (still) have relations
// The other node might have deleted them...
foreach ( Relation * r, m_node->dependChildNodes() ) {
m_relCmd->addCommand( new DeleteRelationCmd( *m_project, r ) );
}
foreach ( Relation * r, m_node->dependParentNodes() ) {
m_relCmd->addCommand( new DeleteRelationCmd( *m_project, r ) );
}
}
m_relCmd->execute();
if ( m_cmd ) {
m_cmd->execute();
}
m_project->takeTask( m_node );
m_mine = true;
setSchScheduled( false );
}
}
void NodeDeleteCmd::unexecute()
{
if ( m_parent && m_project ) {
//debugPlan<<m_node->name()<<""<<m_index;
m_project->addSubTask( m_node, m_index, m_parent );
if ( m_cmd ) {
m_cmd->unexecute();
}
m_relCmd->unexecute();
m_mine = false;
setSchScheduled();
}
}
TaskAddCmd::TaskAddCmd( Project *project, Node *node, Node *after, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_node( node ),
m_after( after ),
m_added( false )
{
// set some reasonable defaults for normally calculated values
if ( after && after->parentNode() && after->parentNode() != project ) {
node->setStartTime( after->parentNode() ->startTime() );
node->setEndTime( node->startTime() + node->duration() );
} else {
if ( project->constraint() == Node::MustFinishOn ) {
node->setEndTime( project->endTime() );
node->setStartTime( node->endTime() - node->duration() );
} else {
node->setStartTime( project->startTime() );
node->setEndTime( node->startTime() + node->duration() );
}
}
node->setEarlyStart( node->startTime() );
node->setLateFinish( node->endTime() );
node->setWorkStartTime( node->startTime() );
node->setWorkEndTime( node->endTime() );
}
TaskAddCmd::~TaskAddCmd()
{
if ( !m_added )
delete m_node;
}
void TaskAddCmd::execute()
{
//debugPlan<<m_node->name();
m_project->addTask( m_node, m_after );
m_added = true;
}
void TaskAddCmd::unexecute()
{
m_project->takeTask( m_node );
m_added = false;
}
SubtaskAddCmd::SubtaskAddCmd( Project *project, Node *node, Node *parent, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_node( node ),
m_parent( parent ),
m_added( false ),
m_cmd( 0 )
{
// set some reasonable defaults for normally calculated values
node->setStartTime( parent->startTime() );
node->setEndTime( node->startTime() + node->duration() );
node->setEarlyStart( node->startTime() );
node->setLateFinish( node->endTime() );
node->setWorkStartTime( node->startTime() );
node->setWorkEndTime( node->endTime() );
// Summarytasks can't have resources, so remove resource requests from the new parent
foreach ( ResourceGroupRequest *r, parent->requests().requests() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new RemoveResourceGroupRequestCmd( r ) );
}
// Also remove accounts
if ( parent->runningAccount() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new NodeModifyRunningAccountCmd( *parent, parent->runningAccount(), 0 ) );
}
if ( parent->startupAccount() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new NodeModifyStartupAccountCmd( *parent, parent->startupAccount(), 0 ) );
}
if ( parent->shutdownAccount() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new NodeModifyShutdownAccountCmd( *parent, parent->shutdownAccount(), 0 ) );
}
}
SubtaskAddCmd::~SubtaskAddCmd()
{
delete m_cmd;
if ( !m_added )
delete m_node;
}
void SubtaskAddCmd::execute()
{
m_project->addSubTask( m_node, m_parent );
if ( m_cmd ) {
m_cmd->execute();
}
m_added = true;
}
void SubtaskAddCmd::unexecute()
{
m_project->takeTask( m_node );
if ( m_cmd ) {
m_cmd->unexecute();
}
m_added = false;
}
NodeModifyNameCmd::NodeModifyNameCmd( Node &node, const QString& nodename, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newName( nodename ),
oldName( node.name() )
{
}
void NodeModifyNameCmd::execute()
{
m_node.setName( newName );
}
void NodeModifyNameCmd::unexecute()
{
m_node.setName( oldName );
}
NodeModifyLeaderCmd::NodeModifyLeaderCmd( Node &node, const QString& leader, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newLeader( leader ),
oldLeader( node.leader() )
{
}
void NodeModifyLeaderCmd::execute()
{
m_node.setLeader( newLeader );
}
void NodeModifyLeaderCmd::unexecute()
{
m_node.setLeader( oldLeader );
}
NodeModifyDescriptionCmd::NodeModifyDescriptionCmd( Node &node, const QString& description, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newDescription( description ),
oldDescription( node.description() )
{
}
void NodeModifyDescriptionCmd::execute()
{
m_node.setDescription( newDescription );
}
void NodeModifyDescriptionCmd::unexecute()
{
m_node.setDescription( oldDescription );
}
NodeModifyConstraintCmd::NodeModifyConstraintCmd( Node &node, Node::ConstraintType c, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newConstraint( c ),
oldConstraint( static_cast<Node::ConstraintType>( node.constraint() ) )
{
}
void NodeModifyConstraintCmd::execute()
{
m_node.setConstraint( newConstraint );
}
void NodeModifyConstraintCmd::unexecute()
{
m_node.setConstraint( oldConstraint );
}
NodeModifyConstraintStartTimeCmd::NodeModifyConstraintStartTimeCmd( Node &node, const QDateTime& dt, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newTime( dt ),
oldTime( node.constraintStartTime() )
{
if ( node.projectNode() ) {
m_timeZone = static_cast<Project*>( node.projectNode() )->timeZone();
}
}
void NodeModifyConstraintStartTimeCmd::execute()
{
m_node.setConstraintStartTime( DateTime( newTime, m_timeZone ) );
}
void NodeModifyConstraintStartTimeCmd::unexecute()
{
m_node.setConstraintStartTime( oldTime );
}
NodeModifyConstraintEndTimeCmd::NodeModifyConstraintEndTimeCmd( Node &node, const QDateTime& dt, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newTime( dt ),
oldTime( node.constraintEndTime() )
{
if ( node.projectNode() ) {
m_timeZone = static_cast<Project*>( node.projectNode() )->timeZone();
}
}
void NodeModifyConstraintEndTimeCmd::execute()
{
m_node.setConstraintEndTime( DateTime( newTime, m_timeZone ) );
}
void NodeModifyConstraintEndTimeCmd::unexecute()
{
m_node.setConstraintEndTime( oldTime );
}
NodeModifyStartTimeCmd::NodeModifyStartTimeCmd( Node &node, const QDateTime& dt, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newTime( dt ),
oldTime( node.startTime() )
{
m_timeZone = static_cast<Project*>( node.projectNode() )->timeZone();
}
void NodeModifyStartTimeCmd::execute()
{
m_node.setStartTime( DateTime( newTime, m_timeZone ) );
}
void NodeModifyStartTimeCmd::unexecute()
{
m_node.setStartTime( oldTime );
}
NodeModifyEndTimeCmd::NodeModifyEndTimeCmd( Node &node, const QDateTime& dt, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newTime( dt ),
oldTime( node.endTime() )
{
m_timeZone = static_cast<Project*>( node.projectNode() )->timeZone();
}
void NodeModifyEndTimeCmd::execute()
{
m_node.setEndTime( DateTime( newTime, m_timeZone ) );
}
void NodeModifyEndTimeCmd::unexecute()
{
m_node.setEndTime( oldTime );
}
NodeModifyIdCmd::NodeModifyIdCmd( Node &node, const QString& id, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newId( id ),
oldId( node.id() )
{
}
void NodeModifyIdCmd::execute()
{
m_node.setId( newId );
}
void NodeModifyIdCmd::unexecute()
{
m_node.setId( oldId );
}
NodeIndentCmd::NodeIndentCmd( Node &node, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_newparent( 0 ),
m_newindex( -1 ),
m_cmd( 0 )
{
}
NodeIndentCmd::~NodeIndentCmd()
{
delete m_cmd;
}
void NodeIndentCmd::execute()
{
m_oldparent = m_node.parentNode();
m_oldindex = m_oldparent->findChildNode( &m_node );
Project *p = dynamic_cast<Project *>( m_node.projectNode() );
if ( p && p->indentTask( &m_node, m_newindex ) ) {
m_newparent = m_node.parentNode();
m_newindex = m_newparent->findChildNode( &m_node );
// Summarytasks can't have resources, so remove resource requests from the new parent
if ( m_cmd == 0 ) {
foreach ( ResourceGroupRequest *r, m_newparent->requests().requests() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new RemoveResourceGroupRequestCmd( r ) );
}
// Also remove accounts
if ( m_newparent->runningAccount() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new NodeModifyRunningAccountCmd( *m_newparent, m_newparent->runningAccount(), 0 ) );
}
if ( m_newparent->startupAccount() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new NodeModifyStartupAccountCmd( *m_newparent, m_newparent->startupAccount(), 0 ) );
}
if ( m_newparent->shutdownAccount() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new NodeModifyShutdownAccountCmd( *m_newparent, m_newparent->shutdownAccount(), 0 ) );
}
}
if ( m_cmd ) {
m_cmd->execute();
}
}
}
void NodeIndentCmd::unexecute()
{
Project * p = dynamic_cast<Project *>( m_node.projectNode() );
if ( m_newindex != -1 && p && p->unindentTask( &m_node ) ) {
m_newindex = -1;
if ( m_cmd ) {
m_cmd->unexecute();
}
}
}
NodeUnindentCmd::NodeUnindentCmd( Node &node, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_newparent( 0 ),
m_newindex( -1 )
{}
void NodeUnindentCmd::execute()
{
m_oldparent = m_node.parentNode();
m_oldindex = m_oldparent->findChildNode( &m_node );
Project *p = dynamic_cast<Project *>( m_node.projectNode() );
if ( p && p->unindentTask( &m_node ) ) {
m_newparent = m_node.parentNode();
m_newindex = m_newparent->findChildNode( &m_node );
}
}
void NodeUnindentCmd::unexecute()
{
Project * p = dynamic_cast<Project *>( m_node.projectNode() );
if ( m_newindex != -1 && p && p->indentTask( &m_node, m_oldindex ) ) {
m_newindex = -1;
}
}
NodeMoveUpCmd::NodeMoveUpCmd( Node &node, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_moved( false )
{
m_project = static_cast<Project *>( m_node.projectNode() );
}
void NodeMoveUpCmd::execute()
{
if ( m_project ) {
m_moved = m_project->moveTaskUp( &m_node );
}
}
void NodeMoveUpCmd::unexecute()
{
if ( m_project && m_moved ) {
m_project->moveTaskDown( &m_node );
}
m_moved = false;
}
NodeMoveDownCmd::NodeMoveDownCmd( Node &node, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_moved( false )
{
m_project = static_cast<Project *>( m_node.projectNode() );
}
void NodeMoveDownCmd::execute()
{
if ( m_project ) {
m_moved = m_project->moveTaskDown( &m_node );
}
}
void NodeMoveDownCmd::unexecute()
{
if ( m_project && m_moved ) {
m_project->moveTaskUp( &m_node );
}
m_moved = false;
}
NodeMoveCmd::NodeMoveCmd( Project *project, Node *node, Node *newParent, int newPos, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_node( node ),
m_newparent( newParent ),
m_newpos( newPos ),
m_moved( false )
{
m_oldparent = node->parentNode();
Q_ASSERT( m_oldparent );
}
void NodeMoveCmd::execute()
{
if ( m_project ) {
m_oldpos = m_oldparent->indexOf( m_node );
m_moved = m_project->moveTask( m_node, m_newparent, m_newpos );
if ( m_moved ) {
if ( m_cmd.isEmpty() ) {
// Summarytasks can't have resources, so remove resource requests from the new parent
foreach ( ResourceGroupRequest *r, m_newparent->requests().requests() ) {
m_cmd.addCommand( new RemoveResourceGroupRequestCmd( r ) );
}
// TODO appointments ??
}
m_cmd.execute();
}
}
}
void NodeMoveCmd::unexecute()
{
if ( m_project && m_moved ) {
m_moved = m_project->moveTask( m_node, m_oldparent, m_oldpos );
m_cmd.unexecute();
}
m_moved = false;
}
AddRelationCmd::AddRelationCmd( Project &project, Relation *rel, const KUndo2MagicString& name )
: NamedCommand( name ),
m_rel( rel ),
m_project( project )
{
m_taken = true;
}
AddRelationCmd::~AddRelationCmd()
{
if ( m_taken )
delete m_rel;
}
void AddRelationCmd::execute()
{
//debugPlan<<m_rel->parent()<<" to"<<m_rel->child();
m_taken = false;
m_project.addRelation( m_rel, false );
}
void AddRelationCmd::unexecute()
{
m_taken = true;
m_project.takeRelation( m_rel );
}
DeleteRelationCmd::DeleteRelationCmd( Project &project, Relation *rel, const KUndo2MagicString& name )
: NamedCommand( name ),
m_rel( rel ),
m_project( project )
{
m_taken = false;
}
DeleteRelationCmd::~DeleteRelationCmd()
{
if ( m_taken ) {
// do not access nodes, the may already be deleted
m_rel->setParent(0);
m_rel->setChild(0);
delete m_rel;
}
}
void DeleteRelationCmd::execute()
{
//debugPlan<<m_rel->parent()<<" to"<<m_rel->child();
m_taken = true;
m_project.takeRelation( m_rel );
}
void DeleteRelationCmd::unexecute()
{
m_taken = false;
m_project.addRelation( m_rel, false );
}
ModifyRelationTypeCmd::ModifyRelationTypeCmd( Relation *rel, Relation::Type type, const KUndo2MagicString& name )
: NamedCommand( name ),
m_rel( rel ),
m_newtype( type )
{
m_oldtype = rel->type();
m_project = dynamic_cast<Project*>( rel->parent() ->projectNode() );
}
void ModifyRelationTypeCmd::execute()
{
if ( m_project ) {
m_project->setRelationType( m_rel, m_newtype );
}
}
void ModifyRelationTypeCmd::unexecute()
{
if ( m_project ) {
m_project->setRelationType( m_rel, m_oldtype );
}
}
ModifyRelationLagCmd::ModifyRelationLagCmd( Relation *rel, Duration lag, const KUndo2MagicString& name )
: NamedCommand( name ),
m_rel( rel ),
m_newlag( lag )
{
m_oldlag = rel->lag();
m_project = dynamic_cast<Project*>( rel->parent() ->projectNode() );
}
void ModifyRelationLagCmd::execute()
{
if ( m_project ) {
m_project->setRelationLag( m_rel, m_newlag );
}
}
void ModifyRelationLagCmd::unexecute()
{
if ( m_project ) {
m_project->setRelationLag( m_rel, m_oldlag );
}
}
AddResourceRequestCmd::AddResourceRequestCmd( ResourceGroupRequest *group, ResourceRequest *request, const KUndo2MagicString& name )
: NamedCommand( name ),
m_group( group ),
m_request( request )
{
m_mine = true;
}
AddResourceRequestCmd::~AddResourceRequestCmd()
{
if ( m_mine )
delete m_request;
}
void AddResourceRequestCmd::execute()
{
//debugPlan<<"group="<<m_group<<" req="<<m_request;
m_group->addResourceRequest( m_request );
m_mine = false;
}
void AddResourceRequestCmd::unexecute()
{
//debugPlan<<"group="<<m_group<<" req="<<m_request;
m_group->takeResourceRequest( m_request );
m_mine = true;
}
RemoveResourceRequestCmd::RemoveResourceRequestCmd( ResourceGroupRequest *group, ResourceRequest *request, const KUndo2MagicString& name )
: NamedCommand( name ),
m_group( group ),
m_request( request )
{
m_mine = false;
//debugPlan<<"group req="<<group<<" req="<<request<<" to gr="<<m_group->group();
}
RemoveResourceRequestCmd::~RemoveResourceRequestCmd()
{
if ( m_mine )
delete m_request;
}
void RemoveResourceRequestCmd::execute()
{
m_group->takeResourceRequest( m_request );
m_mine = true;
}
void RemoveResourceRequestCmd::unexecute()
{
m_group->addResourceRequest( m_request );
m_mine = false;
}
ModifyResourceRequestUnitsCmd::ModifyResourceRequestUnitsCmd( ResourceRequest *request, int oldvalue, int newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_request( request ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyResourceRequestUnitsCmd::execute()
{
m_request->setUnits( m_newvalue );
}
void ModifyResourceRequestUnitsCmd::unexecute()
{
m_request->setUnits( m_oldvalue );
}
ModifyResourceRequestRequiredCmd::ModifyResourceRequestRequiredCmd( ResourceRequest *request, const QList<Resource*> &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_request( request ),
m_newvalue( value )
{
m_oldvalue = request->requiredResources();
}
void ModifyResourceRequestRequiredCmd::execute()
{
m_request->setRequiredResources( m_newvalue );
}
void ModifyResourceRequestRequiredCmd::unexecute()
{
m_request->setRequiredResources( m_oldvalue );
}
ModifyResourceGroupRequestUnitsCmd::ModifyResourceGroupRequestUnitsCmd( ResourceGroupRequest *request, int oldvalue, int newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_request( request ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyResourceGroupRequestUnitsCmd::execute()
{
m_request->setUnits( m_newvalue );
}
void ModifyResourceGroupRequestUnitsCmd::unexecute()
{
m_request->setUnits( m_oldvalue );
}
ModifyEstimateCmd::ModifyEstimateCmd( Node &node, double oldvalue, double newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue ),
m_optimistic( node.estimate()->optimisticRatio() ),
m_pessimistic( node.estimate()->pessimisticRatio() ),
m_cmd( 0 )
{
if ( newvalue == 0.0 ) {
// Milestones can't have resources, so remove resource requests
foreach ( ResourceGroupRequest *r, node.requests().requests() ) {
if ( m_cmd == 0 ) m_cmd = new MacroCommand( KUndo2MagicString() );
m_cmd->addCommand( new RemoveResourceGroupRequestCmd( r ) );
}
}
}
ModifyEstimateCmd::~ModifyEstimateCmd()
{
delete m_cmd;
}
void ModifyEstimateCmd::execute()
{
m_estimate->setExpectedEstimate( m_newvalue );
if ( m_cmd ) {
m_cmd->execute();
}
m_estimate->setPessimisticRatio( m_pessimistic );
m_estimate->setOptimisticRatio( m_optimistic );
}
void ModifyEstimateCmd::unexecute()
{
m_estimate->setExpectedEstimate( m_oldvalue );
if ( m_cmd ) {
m_cmd->unexecute();
}
m_estimate->setPessimisticRatio( m_pessimistic );
m_estimate->setOptimisticRatio( m_optimistic );
}
EstimateModifyOptimisticRatioCmd::EstimateModifyOptimisticRatioCmd( Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void EstimateModifyOptimisticRatioCmd::execute()
{
m_estimate->setOptimisticRatio( m_newvalue );
}
void EstimateModifyOptimisticRatioCmd::unexecute()
{
m_estimate->setOptimisticRatio( m_oldvalue );
}
EstimateModifyPessimisticRatioCmd::EstimateModifyPessimisticRatioCmd( Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void EstimateModifyPessimisticRatioCmd::execute()
{
m_estimate->setPessimisticRatio( m_newvalue );
}
void EstimateModifyPessimisticRatioCmd::unexecute()
{
m_estimate->setPessimisticRatio( m_oldvalue );
}
ModifyEstimateTypeCmd::ModifyEstimateTypeCmd( Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyEstimateTypeCmd::execute()
{
m_estimate->setType( static_cast<Estimate::Type>( m_newvalue ) );
}
void ModifyEstimateTypeCmd::unexecute()
{
m_estimate->setType( static_cast<Estimate::Type>( m_oldvalue ) );
}
ModifyEstimateCalendarCmd::ModifyEstimateCalendarCmd( Node &node, Calendar *oldvalue, Calendar *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyEstimateCalendarCmd::execute()
{
m_estimate->setCalendar( m_newvalue );
}
void ModifyEstimateCalendarCmd::unexecute()
{
m_estimate->setCalendar( m_oldvalue );
}
ModifyEstimateUnitCmd::ModifyEstimateUnitCmd( Node &node, Duration::Unit oldvalue, Duration::Unit newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyEstimateUnitCmd::execute()
{
m_estimate->setUnit( m_newvalue );
}
void ModifyEstimateUnitCmd::unexecute()
{
m_estimate->setUnit( m_oldvalue );
}
EstimateModifyRiskCmd::EstimateModifyRiskCmd( Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_estimate( node.estimate() ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void EstimateModifyRiskCmd::execute()
{
m_estimate->setRisktype( static_cast<Estimate::Risktype>( m_newvalue ) );
}
void EstimateModifyRiskCmd::unexecute()
{
m_estimate->setRisktype( static_cast<Estimate::Risktype>( m_oldvalue ) );
}
AddResourceGroupRequestCmd::AddResourceGroupRequestCmd( Task &task, ResourceGroupRequest *request, const KUndo2MagicString& name )
: NamedCommand( name ),
m_task( task ),
m_request( request )
{
m_mine = true;
}
void AddResourceGroupRequestCmd::execute()
{
//debugPlan<<"group="<<m_request;
m_task.addRequest( m_request );
m_mine = false;
}
void AddResourceGroupRequestCmd::unexecute()
{
//debugPlan<<"group="<<m_request;
m_task.takeRequest( m_request ); // group should now be empty of resourceRequests
m_mine = true;
}
RemoveResourceGroupRequestCmd::RemoveResourceGroupRequestCmd( ResourceGroupRequest *request, const KUndo2MagicString& name )
: NamedCommand( name ),
m_task( *(request->parent() ->task()) ),
m_request( request )
{
m_mine = false;
}
RemoveResourceGroupRequestCmd::RemoveResourceGroupRequestCmd( Task &task, ResourceGroupRequest *request, const KUndo2MagicString& name )
: NamedCommand( name ),
m_task( task ),
m_request( request )
{
m_mine = false;
}
void RemoveResourceGroupRequestCmd::execute()
{
//debugPlan<<"group="<<m_request;
m_task.takeRequest( m_request ); // group should now be empty of resourceRequests
m_mine = true;
}
void RemoveResourceGroupRequestCmd::unexecute()
{
//debugPlan<<"group="<<m_request;
m_task.addRequest( m_request );
m_mine = false;
}
AddResourceCmd::AddResourceCmd( ResourceGroup *group, Resource *resource, const KUndo2MagicString& name )
: NamedCommand( name ),
m_group( group ),
m_resource( resource )
{
m_index = group->indexOf( resource );
m_mine = true;
}
AddResourceCmd::~AddResourceCmd()
{
if ( m_mine ) {
//debugPlan<<"delete:"<<m_resource;
delete m_resource;
}
}
void AddResourceCmd::execute()
{
Q_ASSERT( m_group->project() );
if ( m_group->project() ) {
m_group->project()->addResource( m_group, m_resource, m_index );
m_mine = false;
//debugPlan<<"added:"<<m_resource;
}
}
void AddResourceCmd::unexecute()
{
Q_ASSERT( m_group->project() );
if ( m_group->project() ) {
m_group->project()->takeResource( m_group, m_resource );
//debugPlan<<"removed:"<<m_resource;
m_mine = true;
}
Q_ASSERT( m_group->project() );
}
RemoveResourceCmd::RemoveResourceCmd( ResourceGroup *group, Resource *resource, const KUndo2MagicString& name )
: AddResourceCmd( group, resource, name )
{
//debugPlan<<resource;
m_mine = false;
m_requests = m_resource->requests();
if ( group->project() ) {
foreach ( Schedule * s, group->project()->schedules() ) {
Schedule *rs = resource->findSchedule( s->id() );
if ( rs && ! rs->isDeleted() ) {
debugPlan<<s->name();
addSchScheduled( s );
}
}
}
if ( resource->account() ) {
m_cmd.addCommand( new ResourceModifyAccountCmd( *resource, resource->account(), 0 ) );
}
}
RemoveResourceCmd::~RemoveResourceCmd()
{
while ( !m_appointments.isEmpty() )
delete m_appointments.takeFirst();
}
void RemoveResourceCmd::execute()
{
foreach ( ResourceRequest * r, m_requests ) {
r->parent() ->takeResourceRequest( r );
//debugPlan<<"Remove request for"<<r->resource()->name();
}
AddResourceCmd::unexecute();
m_cmd.execute();
setSchScheduled( false );
}
void RemoveResourceCmd::unexecute()
{
foreach ( ResourceRequest * r, m_requests ) {
r->parent() ->addResourceRequest( r );
//debugPlan<<"Add request for"<<r->resource()->name();
}
m_cmd.unexecute();
AddResourceCmd::execute();
setSchScheduled();
}
MoveResourceCmd::MoveResourceCmd( ResourceGroup *group, Resource *resource, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( *(group->project()) ),
m_resource( resource ),
m_oldvalue( resource->parentGroup() ),
m_newvalue( group )
{
foreach ( ResourceRequest * r, resource->requests() ) {
cmd.addCommand( new RemoveResourceRequestCmd( r->parent(), r ) );
}
}
void MoveResourceCmd::execute()
{
cmd.execute();
m_project.moveResource( m_newvalue, m_resource );
}
void MoveResourceCmd::unexecute()
{
m_project.moveResource( m_oldvalue, m_resource );
cmd.unexecute();
}
ModifyResourceNameCmd::ModifyResourceNameCmd( Resource *resource, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->name();
}
void ModifyResourceNameCmd::execute()
{
m_resource->setName( m_newvalue );
}
void ModifyResourceNameCmd::unexecute()
{
m_resource->setName( m_oldvalue );
}
ModifyResourceInitialsCmd::ModifyResourceInitialsCmd( Resource *resource, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->initials();
}
void ModifyResourceInitialsCmd::execute()
{
m_resource->setInitials( m_newvalue );
}
void ModifyResourceInitialsCmd::unexecute()
{
m_resource->setInitials( m_oldvalue );
}
ModifyResourceEmailCmd::ModifyResourceEmailCmd( Resource *resource, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->email();
}
void ModifyResourceEmailCmd::execute()
{
m_resource->setEmail( m_newvalue );
}
void ModifyResourceEmailCmd::unexecute()
{
m_resource->setEmail( m_oldvalue );
}
ModifyResourceAutoAllocateCmd::ModifyResourceAutoAllocateCmd( Resource *resource,bool value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->autoAllocate();
}
void ModifyResourceAutoAllocateCmd::execute()
{
m_resource->setAutoAllocate( m_newvalue );
}
void ModifyResourceAutoAllocateCmd::unexecute()
{
m_resource->setAutoAllocate( m_oldvalue );
}
ModifyResourceTypeCmd::ModifyResourceTypeCmd( Resource *resource, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->type();
}
void ModifyResourceTypeCmd::execute()
{
m_resource->setType( ( Resource::Type ) m_newvalue );
}
void ModifyResourceTypeCmd::unexecute()
{
m_resource->setType( ( Resource::Type ) m_oldvalue );
}
ModifyResourceUnitsCmd::ModifyResourceUnitsCmd( Resource *resource, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->units();
}
void ModifyResourceUnitsCmd::execute()
{
m_resource->setUnits( m_newvalue );
}
void ModifyResourceUnitsCmd::unexecute()
{
m_resource->setUnits( m_oldvalue );
}
ModifyResourceAvailableFromCmd::ModifyResourceAvailableFromCmd( Resource *resource, const QDateTime& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->availableFrom();
m_timeZone = resource->timeZone();
}
void ModifyResourceAvailableFromCmd::execute()
{
m_resource->setAvailableFrom( DateTime( m_newvalue, m_timeZone ) );
}
void ModifyResourceAvailableFromCmd::unexecute()
{
m_resource->setAvailableFrom( m_oldvalue );
}
ModifyResourceAvailableUntilCmd::ModifyResourceAvailableUntilCmd( Resource *resource, const QDateTime& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->availableUntil();
m_timeZone = resource->timeZone();
}
void ModifyResourceAvailableUntilCmd::execute()
{
m_resource->setAvailableUntil( DateTime( m_newvalue, m_timeZone ) );
}
void ModifyResourceAvailableUntilCmd::unexecute()
{
m_resource->setAvailableUntil( m_oldvalue );
}
ModifyResourceNormalRateCmd::ModifyResourceNormalRateCmd( Resource *resource, double value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->normalRate();
}
void ModifyResourceNormalRateCmd::execute()
{
m_resource->setNormalRate( m_newvalue );
}
void ModifyResourceNormalRateCmd::unexecute()
{
m_resource->setNormalRate( m_oldvalue );
}
ModifyResourceOvertimeRateCmd::ModifyResourceOvertimeRateCmd( Resource *resource, double value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->overtimeRate();
}
void ModifyResourceOvertimeRateCmd::execute()
{
m_resource->setOvertimeRate( m_newvalue );
}
void ModifyResourceOvertimeRateCmd::unexecute()
{
m_resource->setOvertimeRate( m_oldvalue );
}
ModifyResourceCalendarCmd::ModifyResourceCalendarCmd( Resource *resource, Calendar *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->calendar( true );
}
void ModifyResourceCalendarCmd::execute()
{
m_resource->setCalendar( m_newvalue );
}
void ModifyResourceCalendarCmd::unexecute()
{
m_resource->setCalendar( m_oldvalue );
}
ModifyRequiredResourcesCmd::ModifyRequiredResourcesCmd( Resource *resource, const QStringList &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_newvalue( value )
{
m_oldvalue = resource->requiredIds();
}
void ModifyRequiredResourcesCmd::execute()
{
m_resource->setRequiredIds( m_newvalue );
}
void ModifyRequiredResourcesCmd::unexecute()
{
m_resource->setRequiredIds( m_oldvalue );
}
AddResourceTeamCmd::AddResourceTeamCmd( Resource *team, const QString &member, const KUndo2MagicString& name )
: NamedCommand( name ),
m_team( team ),
m_member( member )
{
}
void AddResourceTeamCmd::execute()
{
m_team->addTeamMemberId( m_member );
}
void AddResourceTeamCmd::unexecute()
{
m_team->removeTeamMemberId( m_member );
}
RemoveResourceTeamCmd::RemoveResourceTeamCmd( Resource *team, const QString &member, const KUndo2MagicString& name )
: NamedCommand( name ),
m_team( team ),
m_member( member )
{
}
void RemoveResourceTeamCmd::execute()
{
m_team->removeTeamMemberId( m_member );
}
void RemoveResourceTeamCmd::unexecute()
{
m_team->addTeamMemberId( m_member );
}
RemoveResourceGroupCmd::RemoveResourceGroupCmd( Project *project, ResourceGroup *group, const KUndo2MagicString& name )
: NamedCommand( name ),
m_group( group ),
m_project( project ),
m_cmd( 0 )
{
m_index = project->indexOf( group );
m_mine = false;
if ( !m_group->requests().isEmpty() ) {
m_cmd = new MacroCommand(KUndo2MagicString());
foreach( ResourceGroupRequest * r, m_group->requests() ) {
m_cmd->addCommand( new RemoveResourceGroupRequestCmd( r ) );
}
}
}
RemoveResourceGroupCmd::~RemoveResourceGroupCmd()
{
delete m_cmd;
if ( m_mine )
delete m_group;
}
void RemoveResourceGroupCmd::execute()
{
// remove all requests to this group
if ( m_cmd ) {
m_cmd->execute();
}
if ( m_project )
m_project->takeResourceGroup( m_group );
m_mine = true;
}
void RemoveResourceGroupCmd::unexecute()
{
if ( m_project )
m_project->addResourceGroup( m_group, m_index );
m_mine = false;
// add all requests
if ( m_cmd ) {
m_cmd->unexecute();
}
}
AddResourceGroupCmd::AddResourceGroupCmd( Project *project, ResourceGroup *group, const KUndo2MagicString& name )
: RemoveResourceGroupCmd( project, group, name )
{
m_mine = true;
}
void AddResourceGroupCmd::execute()
{
RemoveResourceGroupCmd::unexecute();
}
void AddResourceGroupCmd::unexecute()
{
RemoveResourceGroupCmd::execute();
}
ModifyResourceGroupNameCmd::ModifyResourceGroupNameCmd( ResourceGroup *group, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_group( group ),
m_newvalue( value )
{
m_oldvalue = group->name();
}
void ModifyResourceGroupNameCmd::execute()
{
m_group->setName( m_newvalue );
}
void ModifyResourceGroupNameCmd::unexecute()
{
m_group->setName( m_oldvalue );
}
ModifyResourceGroupTypeCmd::ModifyResourceGroupTypeCmd( ResourceGroup *group, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_group( group ),
m_newvalue( value )
{
m_oldvalue = group->type();
}
void ModifyResourceGroupTypeCmd::execute()
{
m_group->setType( static_cast<ResourceGroup::Type>( m_newvalue) );
}
void ModifyResourceGroupTypeCmd::unexecute()
{
m_group->setType( static_cast<ResourceGroup::Type>( m_oldvalue ) );
}
ModifyCompletionEntrymodeCmd::ModifyCompletionEntrymodeCmd( Completion &completion, Completion::Entrymode value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
oldvalue( m_completion.entrymode() ),
newvalue( value )
{
}
void ModifyCompletionEntrymodeCmd::execute()
{
m_completion.setEntrymode( newvalue );
}
void ModifyCompletionEntrymodeCmd::unexecute()
{
m_completion.setEntrymode( oldvalue );
}
ModifyCompletionPercentFinishedCmd::ModifyCompletionPercentFinishedCmd( Completion &completion, const QDate &date, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
m_date( date ),
m_newvalue( value ),
m_oldvalue( completion.percentFinished( date ) )
{
if ( ! completion.entries().contains( date ) ) {
Completion::Entry *e = new Completion::Entry();
Completion::Entry *latest = completion.entry( completion.entryDate() );
if ( latest ) {
*e = *latest;
}
cmd.addCommand( new AddCompletionEntryCmd( completion, date, e ) );
}
}
void ModifyCompletionPercentFinishedCmd::execute()
{
cmd.execute();
m_completion.setPercentFinished( m_date, m_newvalue );
}
void ModifyCompletionPercentFinishedCmd::unexecute()
{
m_completion.setPercentFinished( m_date, m_oldvalue );
cmd.unexecute();
}
ModifyCompletionRemainingEffortCmd::ModifyCompletionRemainingEffortCmd( Completion &completion, const QDate &date, const Duration &value, const KUndo2MagicString &name )
: NamedCommand( name ),
m_completion( completion ),
m_date( date ),
m_newvalue( value ),
m_oldvalue( completion.remainingEffort( date ) )
{
if ( ! completion.entries().contains( date ) ) {
Completion::Entry *e = new Completion::Entry();
Completion::Entry *latest = completion.entry( completion.entryDate() );
if ( latest ) {
*e = *latest;
}
cmd.addCommand( new AddCompletionEntryCmd( completion, date, e ) );
}
}
void ModifyCompletionRemainingEffortCmd::execute()
{
cmd.execute();
m_completion.setRemainingEffort( m_date, m_newvalue );
}
void ModifyCompletionRemainingEffortCmd::unexecute()
{
m_completion.setRemainingEffort( m_date, m_oldvalue );
cmd.unexecute();
}
ModifyCompletionActualEffortCmd::ModifyCompletionActualEffortCmd( Completion &completion, const QDate &date, const Duration &value, const KUndo2MagicString &name )
: NamedCommand( name ),
m_completion( completion ),
m_date( date ),
m_newvalue( value ),
m_oldvalue( completion.actualEffort( date ) )
{
if ( ! completion.entries().contains( date ) ) {
Completion::Entry *e = new Completion::Entry();
Completion::Entry *latest = completion.entry( completion.entryDate() );
if ( latest ) {
*e = *latest;
}
cmd.addCommand( new AddCompletionEntryCmd( completion, date, e ) );
}
}
void ModifyCompletionActualEffortCmd::execute()
{
cmd.execute();
m_completion.setActualEffort( m_date, m_newvalue );
}
void ModifyCompletionActualEffortCmd::unexecute()
{
m_completion.setActualEffort( m_date, m_oldvalue );
cmd.unexecute();
}
ModifyCompletionStartedCmd::ModifyCompletionStartedCmd( Completion &completion, bool value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
oldvalue( m_completion.isStarted() ),
newvalue( value )
{
}
void ModifyCompletionStartedCmd::execute()
{
m_completion.setStarted( newvalue );
}
void ModifyCompletionStartedCmd::unexecute()
{
m_completion.setStarted( oldvalue );
}
ModifyCompletionFinishedCmd::ModifyCompletionFinishedCmd( Completion &completion, bool value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
oldvalue( m_completion.isFinished() ),
newvalue( value )
{
}
void ModifyCompletionFinishedCmd::execute()
{
m_completion.setFinished( newvalue );
}
void ModifyCompletionFinishedCmd::unexecute()
{
m_completion.setFinished( oldvalue );
}
ModifyCompletionStartTimeCmd::ModifyCompletionStartTimeCmd( Completion &completion, const QDateTime &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
oldvalue( m_completion.startTime() ),
newvalue( value )
{
m_timeZone = static_cast<Project*>( completion.node()->projectNode() )->timeZone();
}
void ModifyCompletionStartTimeCmd::execute()
{
m_completion.setStartTime( DateTime( newvalue, m_timeZone ) );
}
void ModifyCompletionStartTimeCmd::unexecute()
{
m_completion.setStartTime( oldvalue );
}
ModifyCompletionFinishTimeCmd::ModifyCompletionFinishTimeCmd( Completion &completion, const QDateTime &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
oldvalue( m_completion.finishTime() ),
newvalue( value )
{
m_timeZone = static_cast<Project*>( completion.node()->projectNode() )->timeZone();
}
void ModifyCompletionFinishTimeCmd::execute()
{
m_completion.setFinishTime( DateTime( newvalue, m_timeZone ) );
}
void ModifyCompletionFinishTimeCmd::unexecute()
{
m_completion.setFinishTime( oldvalue );
}
AddCompletionEntryCmd::AddCompletionEntryCmd( Completion &completion, const QDate &date, Completion::Entry *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
m_date( date ),
newvalue( value ),
m_newmine( true )
{
}
AddCompletionEntryCmd::~AddCompletionEntryCmd()
{
if ( m_newmine )
delete newvalue;
}
void AddCompletionEntryCmd::execute()
{
Q_ASSERT( ! m_completion.entries().contains( m_date ) );
m_completion.addEntry( m_date, newvalue );
m_newmine = false;
}
void AddCompletionEntryCmd::unexecute()
{
m_completion.takeEntry( m_date );
m_newmine = true;
}
RemoveCompletionEntryCmd::RemoveCompletionEntryCmd( Completion &completion, const QDate &date, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
m_date( date ),
m_mine( false )
{
value = m_completion.entry( date );
}
RemoveCompletionEntryCmd::~RemoveCompletionEntryCmd()
{
debugPlan<<m_mine<<value;
if ( m_mine )
delete value;
}
void RemoveCompletionEntryCmd::execute()
{
if ( ! m_completion.entries().contains( m_date ) ) {
warnPlan<<"Completion entries does not contain date:"<<m_date;
}
if ( value ) {
m_completion.takeEntry( m_date );
m_mine = true;
}
}
void RemoveCompletionEntryCmd::unexecute()
{
if ( value ) {
m_completion.addEntry( m_date, value );
}
m_mine = false;
}
ModifyCompletionEntryCmd::ModifyCompletionEntryCmd( Completion &completion, const QDate &date, Completion::Entry *value, const KUndo2MagicString& name )
: NamedCommand( name )
{
cmd = new MacroCommand(KUndo2MagicString());
cmd->addCommand( new RemoveCompletionEntryCmd( completion, date ) );
cmd->addCommand( new AddCompletionEntryCmd( completion, date, value ) );
}
ModifyCompletionEntryCmd::~ModifyCompletionEntryCmd()
{
delete cmd;
}
void ModifyCompletionEntryCmd::execute()
{
cmd->execute();
}
void ModifyCompletionEntryCmd::unexecute()
{
cmd->unexecute();
}
AddCompletionUsedEffortCmd::AddCompletionUsedEffortCmd( Completion &completion, const Resource *resource, Completion::UsedEffort *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_completion( completion ),
m_resource( resource ),
newvalue( value ),
m_newmine( true ),
m_oldmine( false)
{
oldvalue = m_completion.usedEffort( resource );
}
AddCompletionUsedEffortCmd::~AddCompletionUsedEffortCmd()
{
if ( m_oldmine )
delete oldvalue;
if ( m_newmine )
delete newvalue;
}
void AddCompletionUsedEffortCmd::execute()
{
if ( oldvalue ) {
m_completion.takeUsedEffort( m_resource );
m_oldmine = true;
}
m_completion.addUsedEffort( m_resource, newvalue );
m_newmine = false;
}
void AddCompletionUsedEffortCmd::unexecute()
{
m_completion.takeUsedEffort( m_resource );
if ( oldvalue ) {
m_completion.addUsedEffort( m_resource, oldvalue );
}
m_newmine = true;
m_oldmine = false;
}
AddCompletionActualEffortCmd::AddCompletionActualEffortCmd( Completion::UsedEffort &ue, const QDate &date, const Completion::UsedEffort::ActualEffort &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_usedEffort( ue ),
m_date( date ),
newvalue( value )
{
oldvalue = ue.effort( date );
}
AddCompletionActualEffortCmd::~AddCompletionActualEffortCmd()
{
}
void AddCompletionActualEffortCmd::execute()
{
m_usedEffort.takeEffort( m_date );
if ( newvalue.effort() > 0 ) {
m_usedEffort.setEffort( m_date, newvalue );
}
}
void AddCompletionActualEffortCmd::unexecute()
{
m_usedEffort.takeEffort( m_date );
if ( oldvalue.effort() > 0 ) {
m_usedEffort.setEffort( m_date, oldvalue );
}
}
AddAccountCmd::AddAccountCmd( Project &project, Account *account, const QString& parent, int index, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_account( account ),
m_parent( 0 ),
m_index( index ),
m_parentName( parent )
{
m_mine = true;
}
AddAccountCmd::AddAccountCmd( Project &project, Account *account, Account *parent, int index, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_account( account ),
m_parent( parent ),
m_index( index )
{
m_mine = true;
}
AddAccountCmd::~AddAccountCmd()
{
if ( m_mine )
delete m_account;
}
void AddAccountCmd::execute()
{
if ( m_parent == 0 && !m_parentName.isEmpty() ) {
m_parent = m_project.accounts().findAccount( m_parentName );
}
m_project.accounts().insert( m_account, m_parent, m_index );
m_mine = false;
}
void AddAccountCmd::unexecute()
{
m_project.accounts().take( m_account );
m_mine = true;
}
RemoveAccountCmd::RemoveAccountCmd( Project &project, Account *account, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_account( account ),
m_parent( account->parent() )
{
if ( m_parent ) {
m_index = m_parent->accountList().indexOf( account );
} else {
m_index = project.accounts().accountList().indexOf( account );
}
m_mine = false;
m_isDefault = account == project.accounts().defaultAccount();
for (Account::CostPlace *cp : m_account->costPlaces()) {
if (cp->node()) {
if (cp->running()) {
m_cmd.addCommand(new NodeModifyRunningAccountCmd(*cp->node(), cp->node()->runningAccount(), 0));
}
if (cp->startup()) {
m_cmd.addCommand(new NodeModifyStartupAccountCmd(*cp->node(), cp->node()->startupAccount(), 0));
}
if (cp->shutdown()) {
m_cmd.addCommand(new NodeModifyShutdownAccountCmd(*cp->node(), cp->node()->shutdownAccount(), 0));
}
} else if (cp->resource()) {
m_cmd.addCommand(new ResourceModifyAccountCmd(*cp->resource(), cp->resource()->account(), 0));
}
}
for (int i = account->accountList().count()-1; i >= 0; --i) {
m_cmd.addCommand(new RemoveAccountCmd(project, account->accountList().at(i)));
}
}
RemoveAccountCmd::~RemoveAccountCmd()
{
if ( m_mine )
delete m_account;
}
void RemoveAccountCmd::execute()
{
if ( m_isDefault ) {
m_project.accounts().setDefaultAccount( 0 );
}
m_cmd.execute(); // remove costplaces and children
m_project.accounts().take( m_account );
m_mine = true;
}
void RemoveAccountCmd::unexecute()
{
m_project.accounts().insert( m_account, m_parent, m_index );
m_cmd.unexecute(); // add costplaces && children
if ( m_isDefault ) {
m_project.accounts().setDefaultAccount( m_account );
}
m_mine = false;
}
RenameAccountCmd::RenameAccountCmd( Account *account, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_account( account )
{
m_oldvalue = account->name();
m_newvalue = value;
}
void RenameAccountCmd::execute()
{
m_account->setName( m_newvalue );
}
void RenameAccountCmd::unexecute()
{
m_account->setName( m_oldvalue );
}
ModifyAccountDescriptionCmd::ModifyAccountDescriptionCmd( Account *account, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_account( account )
{
m_oldvalue = account->description();
m_newvalue = value;
}
void ModifyAccountDescriptionCmd::execute()
{
m_account->setDescription( m_newvalue );
}
void ModifyAccountDescriptionCmd::unexecute()
{
m_account->setDescription( m_oldvalue );
}
NodeModifyStartupCostCmd::NodeModifyStartupCostCmd( Node &node, double value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node )
{
m_oldvalue = node.startupCost();
m_newvalue = value;
}
void NodeModifyStartupCostCmd::execute()
{
m_node.setStartupCost( m_newvalue );
}
void NodeModifyStartupCostCmd::unexecute()
{
m_node.setStartupCost( m_oldvalue );
}
NodeModifyShutdownCostCmd::NodeModifyShutdownCostCmd( Node &node, double value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node )
{
m_oldvalue = node.shutdownCost();
m_newvalue = value;
}
void NodeModifyShutdownCostCmd::execute()
{
m_node.setShutdownCost( m_newvalue );
}
void NodeModifyShutdownCostCmd::unexecute()
{
m_node.setShutdownCost( m_oldvalue );
}
NodeModifyRunningAccountCmd::NodeModifyRunningAccountCmd( Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node )
{
m_oldvalue = oldvalue;
m_newvalue = newvalue;
//debugPlan;
}
void NodeModifyRunningAccountCmd::execute()
{
//debugPlan;
if ( m_oldvalue ) {
m_oldvalue->removeRunning( m_node );
}
if ( m_newvalue ) {
m_newvalue->addRunning( m_node );
}
}
void NodeModifyRunningAccountCmd::unexecute()
{
//debugPlan;
if ( m_newvalue ) {
m_newvalue->removeRunning( m_node );
}
if ( m_oldvalue ) {
m_oldvalue->addRunning( m_node );
}
}
NodeModifyStartupAccountCmd::NodeModifyStartupAccountCmd( Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node )
{
m_oldvalue = oldvalue;
m_newvalue = newvalue;
//debugPlan;
}
void NodeModifyStartupAccountCmd::execute()
{
//debugPlan;
if ( m_oldvalue ) {
m_oldvalue->removeStartup( m_node );
}
if ( m_newvalue ) {
m_newvalue->addStartup( m_node );
}
}
void NodeModifyStartupAccountCmd::unexecute()
{
//debugPlan;
if ( m_newvalue ) {
m_newvalue->removeStartup( m_node );
}
if ( m_oldvalue ) {
m_oldvalue->addStartup( m_node );
}
}
NodeModifyShutdownAccountCmd::NodeModifyShutdownAccountCmd( Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node )
{
m_oldvalue = oldvalue;
m_newvalue = newvalue;
//debugPlan;
}
void NodeModifyShutdownAccountCmd::execute()
{
//debugPlan;
if ( m_oldvalue ) {
m_oldvalue->removeShutdown( m_node );
}
if ( m_newvalue ) {
m_newvalue->addShutdown( m_node );
}
}
void NodeModifyShutdownAccountCmd::unexecute()
{
//debugPlan;
if ( m_newvalue ) {
m_newvalue->removeShutdown( m_node );
}
if ( m_oldvalue ) {
m_oldvalue->addShutdown( m_node );
}
}
ModifyDefaultAccountCmd::ModifyDefaultAccountCmd( Accounts &acc, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_accounts( acc )
{
m_oldvalue = oldvalue;
m_newvalue = newvalue;
//debugPlan;
}
void ModifyDefaultAccountCmd::execute()
{
//debugPlan;
m_accounts.setDefaultAccount( m_newvalue );
}
void ModifyDefaultAccountCmd::unexecute()
{
//debugPlan;
m_accounts.setDefaultAccount( m_oldvalue );
}
ResourceModifyAccountCmd::ResourceModifyAccountCmd( Resource &resource, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource )
{
m_oldvalue = oldvalue;
m_newvalue = newvalue;
}
void ResourceModifyAccountCmd::execute()
{
//debugPlan;
if ( m_oldvalue ) {
m_oldvalue->removeRunning( m_resource );
}
if ( m_newvalue ) {
m_newvalue->addRunning( m_resource );
}
}
void ResourceModifyAccountCmd::unexecute()
{
//debugPlan;
if ( m_newvalue ) {
m_newvalue->removeRunning( m_resource );
}
if ( m_oldvalue ) {
m_oldvalue->addRunning( m_resource );
}
}
ProjectModifyConstraintCmd::ProjectModifyConstraintCmd( Project &node, Node::ConstraintType c, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newConstraint( c ),
oldConstraint( static_cast<Node::ConstraintType>( node.constraint() ) )
{
}
void ProjectModifyConstraintCmd::execute()
{
m_node.setConstraint( newConstraint );
}
void ProjectModifyConstraintCmd::unexecute()
{
m_node.setConstraint( oldConstraint );
}
ProjectModifyStartTimeCmd::ProjectModifyStartTimeCmd( Project &node, const QDateTime& dt, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newTime( dt ),
oldTime( node.startTime() )
{
m_timeZone = node.timeZone();
}
void ProjectModifyStartTimeCmd::execute()
{
m_node.setConstraintStartTime( DateTime( newTime, m_timeZone ) );
}
void ProjectModifyStartTimeCmd::unexecute()
{
m_node.setConstraintStartTime( oldTime );
}
ProjectModifyEndTimeCmd::ProjectModifyEndTimeCmd( Project &node, const QDateTime& dt, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
newTime( dt ),
oldTime( node.endTime() )
{
m_timeZone = node.timeZone();
}
void ProjectModifyEndTimeCmd::execute()
{
m_node.setEndTime( DateTime( newTime, m_timeZone ) );
m_node.setConstraintEndTime( DateTime( newTime, m_timeZone ) );
}
void ProjectModifyEndTimeCmd::unexecute()
{
m_node.setConstraintEndTime( oldTime );
}
//----------------------------
AddScheduleManagerCmd::AddScheduleManagerCmd( Project &node, ScheduleManager *sm, int index, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_parent( sm->parentManager() ),
m_sm( sm ),
m_index( index ),
m_exp( sm->expected() ),
m_mine( true)
{
}
AddScheduleManagerCmd::AddScheduleManagerCmd( ScheduleManager *parent, ScheduleManager *sm, int index, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( parent->project() ),
m_parent( parent ),
m_sm( sm ),
m_index( index ),
m_exp( sm->expected() ),
m_mine( true)
{
}
AddScheduleManagerCmd::~AddScheduleManagerCmd()
{
if ( m_mine ) {
m_sm->setParentManager( 0 );
delete m_sm;
}
}
void AddScheduleManagerCmd::execute()
{
m_node.addScheduleManager( m_sm, m_parent, m_index );
m_sm->setExpected( m_exp );
m_mine = false;
}
void AddScheduleManagerCmd::unexecute()
{
m_node.takeScheduleManager( m_sm );
m_sm->setExpected( 0 );
m_mine = true;
}
DeleteScheduleManagerCmd::DeleteScheduleManagerCmd( Project &node, ScheduleManager *sm, const KUndo2MagicString& name )
: AddScheduleManagerCmd( node, sm, -1, name )
{
m_mine = false;
m_index = m_parent ? m_parent->indexOf( sm ) : node.indexOf( sm );
foreach ( ScheduleManager *s, sm->children() ) {
cmd.addCommand( new DeleteScheduleManagerCmd( node, s ) );
}
}
void DeleteScheduleManagerCmd::execute()
{
cmd.execute();
AddScheduleManagerCmd::unexecute();
}
void DeleteScheduleManagerCmd::unexecute()
{
AddScheduleManagerCmd::execute();
cmd.unexecute();
}
MoveScheduleManagerCmd::MoveScheduleManagerCmd( ScheduleManager *sm, ScheduleManager *newparent, int newindex, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
m_oldparent( sm->parentManager() ),
m_newparent( newparent ),
m_newindex( newindex )
{
m_oldindex = sm->parentManager() ? sm->parentManager()->indexOf( sm ) : sm->project().indexOf( sm );
}
void MoveScheduleManagerCmd::execute()
{
m_sm->project().moveScheduleManager( m_sm, m_newparent, m_newindex );
}
void MoveScheduleManagerCmd::unexecute()
{
m_sm->project().moveScheduleManager( m_sm, m_oldparent, m_oldindex );
}
ModifyScheduleManagerNameCmd::ModifyScheduleManagerNameCmd( ScheduleManager &sm, const QString& value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
oldvalue( sm.name() ),
newvalue( value )
{
}
void ModifyScheduleManagerNameCmd::execute()
{
m_sm.setName( newvalue );
}
void ModifyScheduleManagerNameCmd::unexecute()
{
m_sm.setName( oldvalue );
}
ModifyScheduleManagerAllowOverbookingCmd::ModifyScheduleManagerAllowOverbookingCmd( ScheduleManager &sm, bool value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
oldvalue( sm.allowOverbooking() ),
newvalue( value )
{
}
void ModifyScheduleManagerAllowOverbookingCmd::execute()
{
m_sm.setAllowOverbooking( newvalue );
}
void ModifyScheduleManagerAllowOverbookingCmd::unexecute()
{
m_sm.setAllowOverbooking( oldvalue );
}
ModifyScheduleManagerDistributionCmd::ModifyScheduleManagerDistributionCmd( ScheduleManager &sm, bool value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
oldvalue( sm.usePert() ),
newvalue( value )
{
}
void ModifyScheduleManagerDistributionCmd::execute()
{
m_sm.setUsePert( newvalue );
}
void ModifyScheduleManagerDistributionCmd::unexecute()
{
m_sm.setUsePert( oldvalue );
}
ModifyScheduleManagerSchedulingDirectionCmd::ModifyScheduleManagerSchedulingDirectionCmd( ScheduleManager &sm, bool value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
oldvalue( sm.schedulingDirection() ),
newvalue( value )
{
}
void ModifyScheduleManagerSchedulingDirectionCmd::execute()
{
m_sm.setSchedulingDirection( newvalue );
}
void ModifyScheduleManagerSchedulingDirectionCmd::unexecute()
{
m_sm.setSchedulingDirection( oldvalue );
}
ModifyScheduleManagerSchedulerCmd::ModifyScheduleManagerSchedulerCmd( ScheduleManager &sm, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
oldvalue( sm.schedulerPluginIndex() ),
newvalue( value )
{
}
void ModifyScheduleManagerSchedulerCmd::execute()
{
m_sm.setSchedulerPlugin( newvalue );
}
void ModifyScheduleManagerSchedulerCmd::unexecute()
{
m_sm.setSchedulerPlugin( oldvalue );
}
ModifyScheduleManagerSchedulingGranularityCmd::ModifyScheduleManagerSchedulingGranularityCmd( ScheduleManager &sm, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm ),
oldvalue( sm.granularity() ),
newvalue( value )
{
}
void ModifyScheduleManagerSchedulingGranularityCmd::execute()
{
m_sm.setGranularity( newvalue );
}
void ModifyScheduleManagerSchedulingGranularityCmd::unexecute()
{
m_sm.setGranularity( oldvalue );
}
CalculateScheduleCmd::CalculateScheduleCmd( Project &node, ScheduleManager *sm, const KUndo2MagicString& name )
: NamedCommand( name ),
m_node( node ),
m_sm( sm ),
m_first( true ),
m_oldexpected( m_sm->expected() ),
m_newexpected( 0 )
{
}
void CalculateScheduleCmd::execute()
{
Q_ASSERT( m_sm );
if ( m_first ) {
m_sm->calculateSchedule();
if ( m_sm->calculationResult() != ScheduleManager::CalculationCanceled ) {
m_first = false;
}
m_newexpected = m_sm->expected();
} else {
m_sm->setExpected( m_newexpected );
}
}
void CalculateScheduleCmd::unexecute()
{
if ( m_sm->scheduling() ) {
// terminate scheduling
QApplication::setOverrideCursor( Qt::WaitCursor );
m_sm->haltCalculation();
m_first = true;
QApplication::restoreOverrideCursor();
}
m_sm->setExpected( m_oldexpected );
}
//------------------------
BaselineScheduleCmd::BaselineScheduleCmd( ScheduleManager &sm, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm )
{
}
void BaselineScheduleCmd::execute()
{
m_sm.setBaselined( true );
}
void BaselineScheduleCmd::unexecute()
{
m_sm.setBaselined( false );
}
ResetBaselineScheduleCmd::ResetBaselineScheduleCmd( ScheduleManager &sm, const KUndo2MagicString& name )
: NamedCommand( name ),
m_sm( sm )
{
}
void ResetBaselineScheduleCmd::execute()
{
m_sm.setBaselined( false );
}
void ResetBaselineScheduleCmd::unexecute()
{
m_sm.setBaselined( true );
}
//------------------------
ModifyStandardWorktimeYearCmd::ModifyStandardWorktimeYearCmd( StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
swt( wt ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyStandardWorktimeYearCmd::execute()
{
swt->setYear( m_newvalue );
}
void ModifyStandardWorktimeYearCmd::unexecute()
{
swt->setYear( m_oldvalue );
}
ModifyStandardWorktimeMonthCmd::ModifyStandardWorktimeMonthCmd( StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
swt( wt ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyStandardWorktimeMonthCmd::execute()
{
swt->setMonth( m_newvalue );
}
void ModifyStandardWorktimeMonthCmd::unexecute()
{
swt->setMonth( m_oldvalue );
}
ModifyStandardWorktimeWeekCmd::ModifyStandardWorktimeWeekCmd( StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
swt( wt ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyStandardWorktimeWeekCmd::execute()
{
swt->setWeek( m_newvalue );
}
void ModifyStandardWorktimeWeekCmd::unexecute()
{
swt->setWeek( m_oldvalue );
}
ModifyStandardWorktimeDayCmd::ModifyStandardWorktimeDayCmd( StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name )
: NamedCommand( name ),
swt( wt ),
m_oldvalue( oldvalue ),
m_newvalue( newvalue )
{
}
void ModifyStandardWorktimeDayCmd::execute()
{
swt->setDay( m_newvalue );
}
void ModifyStandardWorktimeDayCmd::unexecute()
{
swt->setDay( m_oldvalue );
}
//----------------
DocumentAddCmd::DocumentAddCmd( Documents &docs, Document *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_docs( docs ),
m_mine( true )
{
Q_ASSERT( value );
m_value = value;
}
DocumentAddCmd::~DocumentAddCmd()
{
//debugPlan;
if ( m_mine )
delete m_value;
}
void DocumentAddCmd::execute()
{
m_docs.addDocument( m_value );
m_mine = false;
}
void DocumentAddCmd::unexecute()
{
m_docs.takeDocument( m_value );
m_mine = true;
}
//----------------
DocumentRemoveCmd::DocumentRemoveCmd( Documents &docs, Document *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_docs( docs ),
m_mine( false )
{
Q_ASSERT( value );
m_value = value;
}
DocumentRemoveCmd::~DocumentRemoveCmd()
{
//debugPlan;
if ( m_mine )
delete m_value;
}
void DocumentRemoveCmd::execute()
{
m_docs.takeDocument( m_value );
m_mine = true;
}
void DocumentRemoveCmd::unexecute()
{
m_docs.addDocument( m_value );
m_mine = false;
}
//----------------
DocumentModifyUrlCmd::DocumentModifyUrlCmd( Document *doc, const QUrl &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_doc( doc )
{
Q_ASSERT( doc );
m_value = value;
m_oldvalue = doc->url();
}
void DocumentModifyUrlCmd::execute()
{
m_doc->setUrl( m_value );
}
void DocumentModifyUrlCmd::unexecute()
{
m_doc->setUrl( m_oldvalue );
}
//----------------
DocumentModifyNameCmd::DocumentModifyNameCmd( Document *doc, const QString &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_doc( doc )
{
Q_ASSERT( doc );
m_value = value;
m_oldvalue = doc->name();
}
void DocumentModifyNameCmd::execute()
{
m_doc->setName( m_value );
}
void DocumentModifyNameCmd::unexecute()
{
m_doc->setName( m_oldvalue );
}
//----------------
DocumentModifyTypeCmd::DocumentModifyTypeCmd( Document *doc, Document::Type value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_doc( doc )
{
Q_ASSERT( doc );
m_value = value;
m_oldvalue = doc->type();
}
void DocumentModifyTypeCmd::execute()
{
m_doc->setType( m_value );
}
void DocumentModifyTypeCmd::unexecute()
{
m_doc->setType( m_oldvalue );
}
//----------------
DocumentModifyStatusCmd::DocumentModifyStatusCmd( Document *doc, const QString &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_doc( doc )
{
Q_ASSERT( doc );
m_value = value;
m_oldvalue = doc->type();
}
void DocumentModifyStatusCmd::execute()
{
m_doc->setStatus( m_value );
}
void DocumentModifyStatusCmd::unexecute()
{
m_doc->setStatus( m_oldvalue );
}
//----------------
DocumentModifySendAsCmd::DocumentModifySendAsCmd( Document *doc, const Document::SendAs value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_doc( doc )
{
Q_ASSERT( doc );
m_value = value;
m_oldvalue = doc->sendAs();
}
void DocumentModifySendAsCmd::execute()
{
m_doc->setSendAs( m_value );
}
void DocumentModifySendAsCmd::unexecute()
{
m_doc->setSendAs( m_oldvalue );
}
//----------------
WBSDefinitionModifyCmd::WBSDefinitionModifyCmd( Project &project, const WBSDefinition value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project )
{
m_newvalue = value;
m_oldvalue = m_project.wbsDefinition();
}
void WBSDefinitionModifyCmd::execute()
{
m_project.setWbsDefinition( m_newvalue );
}
void WBSDefinitionModifyCmd::unexecute()
{
m_project.setWbsDefinition( m_oldvalue );
}
//----------------
InsertProjectCmd::InsertProjectCmd( Project &project, Node *parent, Node *after, const KUndo2MagicString& name )
: MacroCommand( name ),
m_project( static_cast<Project*>( parent->projectNode() ) ),
m_parent( parent )
{
Q_ASSERT( &project != m_project );
if ( m_project->defaultCalendar() ) {
project.setDefaultCalendar( 0 ); // or else m_project default calendar may be overwitten
}
QString defaultAccount;
if ( ! m_project->accounts().defaultAccount() && project.accounts().defaultAccount() ) {
defaultAccount = project.accounts().defaultAccount()->name();
}
QHash<Node*, QString> startupaccountmap;
QHash<Node*, QString> shutdownaccountmap;
QHash<Node*, QString> runningaccountmap;
QHash<Node*, QString> nodecalendarmap;
// remove unhandled info in tasks and get accounts and calendars
foreach ( Node *n, project.allNodes() ) {
if ( n->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( n );
t->workPackage().clear();
while ( ! t->workPackageLog().isEmpty() ) {
WorkPackage *wp = t->workPackageLog().at( 0 );
t->removeWorkPackage( wp );
delete wp;
}
}
if ( n->startupAccount() ) {
startupaccountmap.insert( n, n->startupAccount()->name() );
n->setStartupAccount( 0 );
}
if ( n->shutdownAccount() ) {
shutdownaccountmap.insert( n, n->shutdownAccount()->name() );
n->setShutdownAccount( 0 );
}
if ( n->runningAccount() ) {
runningaccountmap.insert( n, n->runningAccount()->name() );
n->setRunningAccount( 0 );
}
if ( n->estimate()->calendar() ) {
nodecalendarmap.insert( n, n->estimate()->calendar()->id() );
n->estimate()->setCalendar( 0 );
}
}
// get resources pointing to calendars and accounts
QHash<Resource*, QString> resaccountmap;
QHash<Resource*, QString> rescalendarmap;
foreach ( Resource *r, project.resourceList() ) {
if ( r->account() ) {
resaccountmap.insert( r, r->account()->name() );
r->setAccount( 0 );
}
if ( r->calendar() ) {
rescalendarmap.insert( r, r->calendar()->id() );
r->setCalendar( 0 );
}
}
// create add account commands and keep track of used and unused accounts
QList<Account*> unusedAccounts;
QMap<QString, Account*> accountsmap;
foreach ( Account *a, m_project->accounts().allAccounts() ) {
accountsmap.insert( a->name(), a );
}
foreach ( Account *a, project.accounts().accountList() ) {
addAccounts( a, 0, unusedAccounts, accountsmap );
}
// create add calendar commands and keep track of used and unused calendars
QList<Calendar*> unusedCalendars;
QMap<QString, Calendar*> calendarsmap;
foreach ( Calendar *c, m_project->allCalendars() ) {
calendarsmap.insert( c->id(), c );
}
foreach ( Calendar *c, project.calendars() ) {
addCalendars( c, 0, unusedCalendars, calendarsmap );
}
// get all requests before resources are merged
QHash<ResourceGroupRequest*, QPair<Node *, ResourceGroup*> > greqs;
QHash<ResourceGroupRequest*, QPair<ResourceRequest*, Resource*> > rreqs;
foreach ( Node *n, project.allNodes() ) {
QList<ResourceRequest*> resReq;
if ( n->type() != (int)Node::Type_Task || n->requests().isEmpty() ) {
continue;
}
while ( ResourceGroupRequest *gr = n->requests().requests().value( 0 ) ) {
while ( ResourceRequest *rr = gr->resourceRequests( false ).value( 0 ) ) {
debugPlanInsertProject<<"Get resource request:"<<rr;
rreqs.insertMulti( gr, QPair<ResourceRequest*, Resource*>( rr, rr->resource() ) );
// all resource requests shall be reinserted
rr->unregisterRequest();
gr->takeResourceRequest( rr );
}
// all group requests shall be reinserted
greqs[ gr ] = QPair<Node*, ResourceGroup*>( n, gr->group() );
gr->group()->unregisterRequest( gr );
int i = n->requests().takeRequest( gr );
Q_ASSERT( i >= 0 );
#ifdef NDEBUG
Q_UNUSED(i);
#endif
}
}
QList<ResourceGroup*> allGroups;
QList<Resource*> allResources;
QList<Resource*> newResources;
QHash<ResourceGroup*, ResourceGroup*> existingGroups;
QHash<Resource*, Resource*> existingResources;
foreach ( ResourceGroup *g, project.resourceGroups() ) {
ResourceGroup *gr = m_project->findResourceGroup( g->id() );
if ( gr == 0 ) {
addCommand( new AddResourceGroupCmd( m_project, g, kundo2_noi18n("ResourceGroup") ) );
gr = g;
debugPlanInsertProject<<"AddResourceGroupCmd:"<<gr->name();
} else {
existingGroups[ gr ] = g;
}
allGroups << gr;
foreach ( Resource *r, g->resources() ) {
while ( Schedule *s = r->schedules().values().value( 0 ) ) {
r->deleteSchedule( s ); // schedules not handled
}
Resource *res = m_project->findResource( r->id() );
if ( res == 0 ) {
addCommand( new AddResourceCmd( gr, r, kundo2_noi18n("Resource") ) );
allResources << r;
newResources << r;
debugPlanInsertProject<<"AddResourceCmd:"<<gr->name()<<r->name();
} else {
existingResources[ res ] = r;
allResources << res;
}
}
}
// Update resource account
{QHash<Resource*, QString>::const_iterator it = resaccountmap.constBegin();
QHash<Resource*, QString>::const_iterator end = resaccountmap.constEnd();
for ( ; it != end; ++it ) {
Resource *r = it.key();
if ( newResources.contains( r ) ) {
Q_ASSERT( allResources.contains( r ) );
addCommand( new ResourceModifyAccountCmd( *r, 0, accountsmap.value( it.value() ) ) );
}
}}
// Update resource calendar
{QHash<Resource*, QString>::const_iterator it = rescalendarmap.constBegin();
QHash<Resource*, QString>::const_iterator end = rescalendarmap.constEnd();
for ( ; it != end; ++it ) {
Resource *r = it.key();
if ( newResources.contains( r ) ) {
Q_ASSERT( allResources.contains( r ) );
addCommand( new ModifyResourceCalendarCmd( r, calendarsmap.value( it.value() ) ) );
}
}}
// Requests: clean up requests to resources already in m_project
int gi = 0;
int ri = 0;
QHash<ResourceGroupRequest*, QPair<Node *, ResourceGroup*> >::const_iterator gregsIt;
for (gregsIt = greqs.constBegin(); gregsIt != greqs.constEnd(); ++gregsIt) {
ResourceGroupRequest *gr = gregsIt.key();
QPair<Node*, ResourceGroup*> pair = gregsIt.value();
Node *n = pair.first;
ResourceGroup *newGroup = pair.second;
if (ResourceGroup *ng = existingGroups.key(newGroup)) {
newGroup = ng;
}
Q_ASSERT( allGroups.contains( newGroup ) );
gr->setGroup( newGroup );
addCommand( new AddResourceGroupRequestCmd( static_cast<Task&>( *n ), gr, kundo2_noi18n("Group %1", ++gi) ) );
debugPlanInsertProject<<"Add resource group request:"<<n->name()<<":"<<newGroup->name();
QHash<ResourceGroupRequest*, QPair<ResourceRequest*, Resource*> >::const_iterator i = rreqs.constFind( gr );
for ( ; i != rreqs.constEnd() && i.key() == gr; ++i ) {
ResourceRequest *rr = i.value().first;
Resource *newRes = i.value().second;
if (Resource *nr = existingResources.key(newRes)) {
newRes = nr;
}
debugPlanInsertProject<<"Add resource request:"<<n->name()<<":"<<newGroup->name()<<":"<<newRes->name();
if ( ! rr->requiredResources().isEmpty() ) {
// the resource request may have required resources that needs mapping
QList<Resource*> required;
foreach ( Resource *r, rr->requiredResources() ) {
if ( newResources.contains( r ) ) {
required << r;
debugPlanInsertProject<<"Request: required (new)"<<r->name();
continue;
}
Resource *r2 = existingResources.key( r );
Q_ASSERT( allResources.contains( r2 ) );
if ( r2 ) {
debugPlanInsertProject<<"Request: required (existing)"<<r2->name();
required << r2;
}
}
rr->setRequiredResources( required );
}
Q_ASSERT( allResources.contains( newRes ) );
// all resource requests shall be reinserted
rr->setResource( newRes );
addCommand( new AddResourceRequestCmd( gr, rr, kundo2_noi18n("Resource %1", ++ri) ) );
}
}
// Add nodes ( ids are unique, no need to check )
Node *node_after = after;
for ( int i = 0; i < project.numChildren(); ++i ) {
Node *n = project.childNode( i );
Q_ASSERT( n );
while ( Schedule *s = n->schedules().values().value( 0 ) ) {
n->takeSchedule( s ); // schedules not handled
delete s;
}
n->setParentNode( 0 );
if ( node_after ) {
addCommand( new TaskAddCmd( m_project, n, node_after, kundo2_noi18n("Task") ) );
node_after = n;
} else {
addCommand( new SubtaskAddCmd( m_project, n, parent, kundo2_noi18n("Subtask") ) );
}
addChildNodes( n );
}
// Dependencies:
foreach ( Node *n, project.allNodes() ) {
while ( n->numDependChildNodes() > 0 ) {
Relation *r = n->dependChildNodes().at( 0 );
n->takeDependChildNode( r );
r->child()->takeDependParentNode( r );
addCommand( new AddRelationCmd( *m_project, r ) );
}
}
// node calendar
{QHash<Node*, QString>::const_iterator it = nodecalendarmap.constBegin();
QHash<Node*, QString>::const_iterator end = nodecalendarmap.constEnd();
for ( ; it != end; ++it ) {
addCommand( new ModifyEstimateCalendarCmd( *(it.key()), 0, calendarsmap.value( it.value() ) ) );
}}
// node startup account
{QHash<Node*, QString>::const_iterator it = startupaccountmap.constBegin();
QHash<Node*, QString>::const_iterator end = startupaccountmap.constEnd();
for ( ; it != end; ++it ) {
addCommand( new NodeModifyStartupAccountCmd( *(it.key()), 0, accountsmap.value( it.value() ) ) );
}}
// node shutdown account
{QHash<Node*, QString>::const_iterator it = shutdownaccountmap.constBegin();
QHash<Node*, QString>::const_iterator end = shutdownaccountmap.constEnd();
for ( ; it != end; ++it ) {
addCommand( new NodeModifyShutdownAccountCmd( *(it.key()), 0, accountsmap.value( it.value() ) ) );
}}
// node running account
{QHash<Node*, QString>::const_iterator it = runningaccountmap.constBegin();
QHash<Node*, QString>::const_iterator end = runningaccountmap.constEnd();
for ( ; it != end; ++it ) {
addCommand( new NodeModifyRunningAccountCmd( *(it.key()), 0, accountsmap.value( it.value() ) ) );
}}
if ( ! defaultAccount.isEmpty() ) {
Account *a = accountsmap.value( defaultAccount );
if ( a && a->list() ) {
addCommand( new ModifyDefaultAccountCmd( m_project->accounts(), 0, a ) );
}
}
// Cleanup
// Remove nodes from project so they are not deleted
while ( Node *ch = project.childNode( 0 ) ) {
project.takeChildNode( ch );
}
foreach ( Node *n, project.allNodes() ) {
project.removeId( n->id() );
}
// Remove calendars from project
while ( project.calendarCount() > 0 ) {
project.takeCalendar( project.calendarAt( 0 ) );
}
qDeleteAll( unusedCalendars );
// Remove accounts from project
while ( project.accounts().accountCount() > 0 ) {
project.accounts().take( project.accounts().accountAt( 0 ) );
}
qDeleteAll( unusedAccounts );
while ( project.numResourceGroups() > 0 ) {
ResourceGroup *g = project.resourceGroupAt( 0 );
while ( g->numResources() > 0 ) {
g->takeResource( g->resourceAt( 0 ) );
}
project.takeResourceGroup( g );
}
qDeleteAll( existingResources ); // deletes unused resources
qDeleteAll( existingGroups ); // deletes unused resource groups
}
void InsertProjectCmd::addCalendars( Calendar *calendar, Calendar *parent, QList<Calendar*> &unused, QMap<QString, Calendar*> &calendarsmap ) {
Calendar *par = 0;
if ( parent ) {
par = calendarsmap.value( parent->id() );
}
if ( par == 0 ) {
par = parent;
}
Calendar *cal = calendarsmap.value( calendar->id() );
if ( cal == 0 ) {
calendarsmap.insert( calendar->id(), calendar );
addCommand( new CalendarAddCmd( m_project, calendar, -1, par ) );
} else {
unused << calendar;
}
foreach ( Calendar *c, calendar->calendars() ) {
addCalendars( c, calendar, unused, calendarsmap );
}
}
void InsertProjectCmd::addAccounts( Account *account, Account *parent, QList<Account*> &unused, QMap<QString, Account*> &accountsmap ) {
Account *par = 0;
if ( parent ) {
par = accountsmap.value( parent->name() );
}
if ( par == 0 ) {
par = parent;
}
Account *acc = accountsmap.value( account->name() );
if ( acc == 0 ) {
debugPlanInsertProject<<"Move to new project:"<<account<<account->name();
accountsmap.insert( account->name(), account );
addCommand( new AddAccountCmd( *m_project, account, par, -1, kundo2_noi18n( "Add account %1", account->name() ) ) );
} else {
debugPlanInsertProject<<"Already exists:"<<account<<account->name();
unused << account;
}
while ( ! account->accountList().isEmpty() ) {
Account *a = account->accountList().first();
account->list()->take( a );
addAccounts( a, account, unused, accountsmap );
}
}
void InsertProjectCmd::addChildNodes( Node *node ) {
// schedules not handled
while ( Schedule *s = node->schedules().values().value( 0 ) ) {
node->takeSchedule( s ); // schedules not handled
delete s;
}
foreach ( Node *n, node->childNodeIterator() ) {
n->setParentNode( 0 );
addCommand( new SubtaskAddCmd( m_project, n, node, kundo2_noi18n("Subtask") ) );
addChildNodes( n );
}
// Remove child nodes so they are not added twice
while ( Node *ch = node->childNode( 0 ) ) {
node->takeChildNode( ch );
}
}
void InsertProjectCmd::execute()
{
QApplication::setOverrideCursor( Qt::WaitCursor );
MacroCommand::execute();
QApplication::restoreOverrideCursor();
}
void InsertProjectCmd::unexecute()
{
QApplication::setOverrideCursor( Qt::WaitCursor );
MacroCommand::unexecute();
QApplication::restoreOverrideCursor();
}
WorkPackageAddCmd::WorkPackageAddCmd( Project *project, Node *node, WorkPackage *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_project( project ),
m_node( node ),
m_wp( value ),
m_mine( true )
{
}
WorkPackageAddCmd::~WorkPackageAddCmd()
{
if ( m_mine ) {
delete m_wp;
}
}
void WorkPackageAddCmd::execute()
{
// FIXME use project
//m_project->addWorkPackage( m_node, m_wp );
static_cast<Task*>( m_node )->addWorkPackage( m_wp );
}
void WorkPackageAddCmd::unexecute()
{
// FIXME use project
//m_project->removeWorkPackage( m_node, m_wp );
static_cast<Task*>( m_node )->removeWorkPackage( m_wp );
}
ModifyProjectLocaleCmd::ModifyProjectLocaleCmd( Project &project, const KUndo2MagicString& name )
: MacroCommand( name ),
m_project( project )
{
};
void ModifyProjectLocaleCmd::execute()
{
MacroCommand::execute();
m_project.emitLocaleChanged();
}
void ModifyProjectLocaleCmd::unexecute()
{
MacroCommand::unexecute();
m_project.emitLocaleChanged();
}
ModifyCurrencySymolCmd::ModifyCurrencySymolCmd( Locale *locale, const QString &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_locale( locale ),
m_newvalue( value ),
m_oldvalue( locale->currencySymbol() )
{
};
void ModifyCurrencySymolCmd::execute()
{
m_locale->setCurrencySymbol( m_newvalue );
}
void ModifyCurrencySymolCmd::unexecute()
{
m_locale->setCurrencySymbol( m_oldvalue );
}
ModifyCurrencyFractionalDigitsCmd::ModifyCurrencyFractionalDigitsCmd( Locale *locale, int value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_locale( locale ),
m_newvalue( value ),
m_oldvalue(locale->monetaryDecimalPlaces())
{
};
void ModifyCurrencyFractionalDigitsCmd::execute()
{
m_locale->setMonetaryDecimalPlaces(m_newvalue);
}
void ModifyCurrencyFractionalDigitsCmd::unexecute()
{
m_locale->setMonetaryDecimalPlaces(m_oldvalue);
}
AddExternalAppointmentCmd::AddExternalAppointmentCmd( Resource *resource, const QString &pid, const QString &pname, const QDateTime &start, const QDateTime &end, double load, const KUndo2MagicString& name )
: NamedCommand( name ),
m_resource( resource ),
m_pid( pid ),
m_pname( pname ),
m_start( start ),
m_end( end ),
m_load( load )
{
}
void AddExternalAppointmentCmd::execute()
{
m_resource->addExternalAppointment( m_pid, m_pname, m_start, m_end, m_load );
}
void AddExternalAppointmentCmd::unexecute()
{
m_resource->subtractExternalAppointment( m_pid, m_start, m_end, m_load );
// FIXME do this smarter
if ( ! m_resource->externalAppointments( m_pid ).isEmpty() ) {
m_resource->takeExternalAppointment( m_pid );
}
}
ClearExternalAppointmentCmd::ClearExternalAppointmentCmd( Resource *resource, const QString &pid, const KUndo2MagicString &name )
: NamedCommand( name ),
m_resource( resource ),
m_pid( pid ),
m_appointments( 0 )
{
}
ClearExternalAppointmentCmd::~ClearExternalAppointmentCmd()
{
delete m_appointments;
}
void ClearExternalAppointmentCmd::execute()
{
// debugPlan<<text()<<":"<<m_resource->name()<<m_pid;
m_appointments = m_resource->takeExternalAppointment( m_pid );
}
void ClearExternalAppointmentCmd::unexecute()
{
// debugPlan<<text()<<":"<<m_resource->name()<<m_pid;
if ( m_appointments ) {
m_resource->addExternalAppointment( m_pid, m_appointments );
}
m_appointments = 0;
}
ClearAllExternalAppointmentsCmd::ClearAllExternalAppointmentsCmd( Project *project, const KUndo2MagicString &name )
: NamedCommand( name ),
m_project( project )
{
foreach ( Resource *r, project->resourceList() ) {
const QMap<QString, QString> map = r->externalProjects();
QMap<QString, QString>::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
m_cmd.addCommand(new ClearExternalAppointmentCmd(r, it.key()));
}
}
}
void ClearAllExternalAppointmentsCmd::execute()
{
m_cmd.redo();
}
void ClearAllExternalAppointmentsCmd::unexecute()
{
m_cmd.undo();
}
SharedResourcesFileCmd::SharedResourcesFileCmd(Project *project, const QString &newValue, const KUndo2MagicString& name)
: NamedCommand(name)
, m_project(project)
, m_oldValue(project->sharedResourcesFile())
, m_newValue(newValue)
{
}
void SharedResourcesFileCmd::execute()
{
m_project->setSharedResourcesFile(m_newValue);
}
void SharedResourcesFileCmd::unexecute()
{
m_project->setSharedResourcesFile(m_oldValue);
}
UseSharedResourcesCmd::UseSharedResourcesCmd(Project *project, bool newValue, const KUndo2MagicString& name)
: NamedCommand(name)
, m_project(project)
, m_oldValue(project->useSharedResources())
, m_newValue(newValue)
{
}
void UseSharedResourcesCmd::execute()
{
m_project->setUseSharedResources(m_newValue);
}
void UseSharedResourcesCmd::unexecute()
{
m_project->setUseSharedResources(m_oldValue);
}
SharedProjectsUrlCmd::SharedProjectsUrlCmd(Project *project, const QUrl &newValue, const KUndo2MagicString& name)
: NamedCommand(name)
, m_project(project)
, m_oldValue(project->sharedProjectsUrl())
, m_newValue(newValue)
{
}
void SharedProjectsUrlCmd::execute()
{
m_project->setSharedProjectsUrl(m_newValue);
}
void SharedProjectsUrlCmd::unexecute()
{
m_project->setSharedProjectsUrl(m_oldValue);
}
LoadProjectsAtStartupCmd::LoadProjectsAtStartupCmd(Project *project, bool newValue, const KUndo2MagicString& name)
: NamedCommand(name)
, m_project(project)
, m_oldValue(project->loadProjectsAtStartup())
, m_newValue(newValue)
{
}
void LoadProjectsAtStartupCmd::execute()
{
m_project->setLoadProjectsAtStartup(m_newValue);
}
void LoadProjectsAtStartupCmd::unexecute()
{
m_project->setLoadProjectsAtStartup(m_oldValue);
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptconfigbase.cpp b/src/libs/kernel/kptconfigbase.cpp
index 673ceb99..002c24e2 100644
--- a/src/libs/kernel/kptconfigbase.cpp
+++ b/src/libs/kernel/kptconfigbase.cpp
@@ -1,162 +1,163 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "kptconfigbase.h"
#include "kptlocale.h"
#include <QApplication>
#include <QBrush>
#include <QColor>
#include <QFontMetrics>
namespace KPlato
{
ConfigBase::ConfigBase()
: m_taskDefaults( new Task() )
{
m_locale = new Locale();
m_readWrite = true;
// set some reasonable defaults
m_taskDefaults->estimate()->setType( Estimate::Type_Effort );
m_taskDefaults->estimate()->setUnit( Duration::Unit_h );
m_taskDefaults->estimate()->setExpectedEstimate( 1.0 );
m_taskDefaults->estimate()->setPessimisticRatio( 0 );
m_taskDefaults->estimate()->setOptimisticRatio( 0 );
}
ConfigBase::~ConfigBase()
{
delete m_taskDefaults;
delete m_locale;
}
void ConfigBase::setTaskDefaults( Task *task )
{
if ( m_taskDefaults != task ) {
delete m_taskDefaults;
m_taskDefaults = task;
}
}
QBrush ConfigBase::summaryTaskLevelColor( int level ) const
{
if ( summaryTaskLevelColorsEnabled() ) {
switch ( level ) {
case 1: return summaryTaskLevelColor_1();
case 2: return summaryTaskLevelColor_2();
case 3: return summaryTaskLevelColor_3();
case 4: return summaryTaskLevelColor_4();
default: break;
}
}
return summaryTaskDefaultColor();
}
bool ConfigBase::summaryTaskLevelColorsEnabled() const
{
return false;
}
QBrush ConfigBase::summaryTaskDefaultColor() const
{
return gradientBrush( Qt::blue );
}
QBrush ConfigBase::summaryTaskLevelColor_1() const
{
return gradientBrush( Qt::blue );
}
QBrush ConfigBase::summaryTaskLevelColor_2() const
{
return gradientBrush( Qt::blue );
}
QBrush ConfigBase::summaryTaskLevelColor_3() const
{
return gradientBrush( Qt::blue );
}
QBrush ConfigBase::summaryTaskLevelColor_4() const
{
return gradientBrush( Qt::blue );
}
QBrush ConfigBase::taskNormalColor() const
{
return gradientBrush( Qt::green );
}
QBrush ConfigBase::taskErrorColor() const
{
return gradientBrush( Qt::yellow );
}
QBrush ConfigBase::taskCriticalColor() const
{
return gradientBrush( Qt::red );
}
QBrush ConfigBase::taskFinishedColor() const
{
return gradientBrush( Qt::gray );
}
QBrush ConfigBase::milestoneNormalColor() const
{
return gradientBrush( Qt::blue );
}
QBrush ConfigBase::milestoneErrorColor() const
{
return gradientBrush( Qt::yellow );
}
QBrush ConfigBase::milestoneCriticalColor() const
{
return gradientBrush( Qt::red );
}
QBrush ConfigBase::milestoneFinishedColor() const
{
return gradientBrush( Qt::gray );
}
const Locale *ConfigBase::locale() const
{
return m_locale;
}
Locale *ConfigBase::locale()
{
return m_locale;
}
//static
QBrush ConfigBase::gradientBrush( const QColor &c )
{
QLinearGradient b( 0., 0., 0., QApplication::fontMetrics().height() );
b.setColorAt( 0., c );
b.setColorAt( 1., c.darker() );
return QBrush( b );
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptdatetime.cpp b/src/libs/kernel/kptdatetime.cpp
index fa395635..f3470aa8 100644
--- a/src/libs/kernel/kptdatetime.cpp
+++ b/src/libs/kernel/kptdatetime.cpp
@@ -1,177 +1,178 @@
/* This file is part of the KDE project
Copyright (C) 2003-2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2011 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptdatetime.h"
#include "kptdebug.h"
namespace KPlato
{
DateTime::DateTime()
: QDateTime()
{
}
DateTime::DateTime( QDate date )
: QDateTime( date )
{
}
DateTime::DateTime( QDate date, QTime time)
: QDateTime( date, time, Qt::LocalTime)
{
if (!isValid() && this->date().isValid() && this->time().isValid()) {
QTime t = this->time();
warnPlan<<"Invalid DateTime, try to compensate for DST"<<this->date()<<t;
setTime( QTime( t.hour() + 1, 0, 0));
Q_ASSERT(isValid());
}
}
DateTime::DateTime(QDate date, QTime time, const QTimeZone &timeZone)
: QDateTime( timeZone.isValid() ? QDateTime(date, time, timeZone) : QDateTime(date, time, Qt::LocalTime) )
{
// If we ended inside DST, DateTime is not valid, but date(), time() and timeSpec() should be valid
if (!isValid() && this->date().isValid() && this->time().isValid()) {
QTime t = this->time();
warnPlan<<"Invalid DateTime, try to compensate for DST"<<this->date()<<t<<timeSpec();
setTime( QTime( t.hour() + 1, 0, 0 ) );
Q_ASSERT(isValid());
}
}
DateTime::DateTime( const QDateTime& other )
: QDateTime( other )
{
}
static QDateTime fromTimeZone(const QDateTime &dt, const QTimeZone &timeZone)
{
Q_ASSERT(dt.timeSpec() == Qt::LocalTime);
QDateTime result(dt);
if (timeZone.isValid()) {
result.setTimeZone(timeZone);
result = result.toLocalTime();
}
return result;
}
DateTime::DateTime( const QDateTime &dt, const QTimeZone &timeZone )
: QDateTime( fromTimeZone(dt, timeZone) )
{
// may come from a TZ wo DST, into one with
if (!isValid() && dt.isValid()) {
warnPlan<<"Invalid DateTime, try to compensate for DST"<<dt;
setTime( QTime( dt.time().hour() + 1, 0, 0 ) );
Q_ASSERT(isValid());
}
}
void DateTime::add(const Duration &duration) {
if (isValid() && duration.m_ms) {
if (timeSpec() == Qt::TimeZone ) {
QTimeZone tz = timeZone();
toUTC();
QDateTime x = addMSecs(duration.m_ms);
x.toUTC();
setDate( x.date() );
setTime( x.time() );
toTimeZone(tz);
} else {
*this = addMSecs(duration.m_ms);
}
//debugPlan<<toString();
}
}
void DateTime::subtract(const Duration &duration) {
if (isValid() && duration.m_ms) {
if (timeSpec() == Qt::TimeZone ) {
QTimeZone tz = timeZone();
QDateTime x = addMSecs(-duration.m_ms);
x.toUTC();
setDate( x.date() );
setTime( x.time() );
toTimeZone(tz);
} else {
*this = addMSecs(-duration.m_ms);
}
//debugPlan<<toString();
}
}
Duration DateTime::duration(const DateTime &dt) const {
Duration dur;
if (isValid() && dt.isValid()) {
qint64 x = msecsTo( dt ); //NOTE: this does conversion to UTC (expensive)
dur.m_ms = x < 0 ? -x : x;
}
//debugPlan<<dur.milliseconds();
return dur;
}
DateTime DateTime::operator+(const Duration &duration) const {
DateTime d(*this);
d.add(duration);
return d;
}
DateTime& DateTime::operator+=(const Duration &duration) {
add(duration);
return *this;
}
DateTime DateTime::operator-(const Duration &duration) const {
DateTime d(*this);
d.subtract(duration);
return d;
}
DateTime& DateTime::operator-=(const Duration &duration) {
subtract(duration);
return *this;
}
DateTime DateTime::fromString( const QString &dts, const QTimeZone &timeZone )
{
if (dts.isEmpty()) {
return DateTime();
}
QDateTime dt = QDateTime::fromString(dts, Qt::ISODate);
return DateTime(dt.date(), dt.time(), timeZone);
}
} //KPlato namespace
QDebug operator<<( QDebug dbg, const KPlato::DateTime &dt )
{
dbg.nospace()<<" DateTime[" << dt.toString( Qt::ISODate );
switch ( dt.timeSpec() ) {
case Qt::LocalTime: dbg << "LocalTime"; break;
case Qt::UTC: dbg << "UTC"; break;
case Qt::OffsetFromUTC: dbg << "OffsetFromUTC"; break;
case Qt::TimeZone: dbg << dt.timeZone(); break;
}
dbg.nospace() << "] ";
return dbg;
}
diff --git a/src/libs/kernel/kptdebug.cpp b/src/libs/kernel/kptdebug.cpp
index 232f2c0b..b9a69e26 100644
--- a/src/libs/kernel/kptdebug.cpp
+++ b/src/libs/kernel/kptdebug.cpp
@@ -1,44 +1,45 @@
/* This file is part of the KDE project
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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 "kptdebug.h"
const QLoggingCategory &PLANSHARED_LOG()
{
static const QLoggingCategory category("calligra.plan.shared");
return category;
}
const QLoggingCategory &PLANDEPEDITOR_LOG()
{
static const QLoggingCategory category("calligra.plan.dependencyeditor");
return category;
}
const QLoggingCategory &PLANXML_LOG()
{
static const QLoggingCategory category("calligra.plan.xml");
return category;
}
const QLoggingCategory &PLAN_LOG()
{
static const QLoggingCategory category("calligra.plan");
return category;
}
diff --git a/src/libs/kernel/kptdocuments.cpp b/src/libs/kernel/kptdocuments.cpp
index cec24060..a21f3980 100644
--- a/src/libs/kernel/kptdocuments.cpp
+++ b/src/libs/kernel/kptdocuments.cpp
@@ -1,333 +1,334 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptdocuments.h"
#include "kptnode.h"
#include "kptxmlloaderobject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KoStore.h>
#include <KLocalizedString>
namespace KPlato
{
Document::Document()
: m_type( Type_None ),
m_sendAs( SendAs_None ),
parent ( 0 )
{
//debugPlan<<this;
}
Document::Document( const QUrl &url, Document::Type type, Document::SendAs sendAs )
: m_type( type ),
m_sendAs( sendAs ),
parent ( 0 )
{
setUrl( url );
//debugPlan<<this;
}
Document::~Document()
{
//debugPlan<<this;
}
bool Document::operator==( const Document &doc ) const
{
bool res = ( m_url == doc.url() &&
m_name == doc.m_name &&
m_type == doc.type() &&
m_status == doc.status() &&
m_sendAs == doc.sendAs()
);
return res;
}
bool Document::isValid() const
{
return m_url.isValid();
}
QStringList Document::typeList( bool trans )
{
return QStringList()
<< ( trans ? xi18nc( "@item", "Unknown" ) : "Unknown" )
<< ( trans ? xi18nc( "@item The produced document", "Product" ) : "Product" )
<< ( trans ? xi18nc( "@item Document is used for reference", "Reference" ) : "Reference" );
}
QString Document::typeToString( Document::Type type, bool trans )
{
return typeList( trans ).at( type );
}
QStringList Document::sendAsList( bool trans )
{
return QStringList()
<< ( trans ? xi18nc( "@item", "Unknown" ) : "Unknown" )
<< ( trans ? xi18nc( "@item Send a copy of the document", "Copy" ) : "Copy" )
<< ( trans ? xi18nc( "@item Send the reference (url) of the document", "Reference" ) : "Reference" );
}
QString Document::sendAsToString( Document::SendAs snd, bool trans )
{
return sendAsList( trans ).at( snd );
}
void Document::setName( const QString &name )
{
if ( m_name != name ) {
m_name = name;
if ( parent ) {
parent->documentChanged( this );
}
}
}
void Document::setType( Type type )
{
if ( type != m_type ) {
m_type = type;
if ( parent ) {
parent->documentChanged( this );
}
}
}
void Document::setSendAs( SendAs snd )
{
if ( m_sendAs != snd ) {
m_sendAs = snd;
if ( parent ) {
parent->documentChanged( this );
}
}
}
void Document::setUrl( const QUrl &url )
{
if ( m_url != url ) {
m_url = url;
if ( m_name.isEmpty() ) {
m_name = url.fileName();
}
if ( parent ) {
parent->documentChanged( this );
}
}
}
void Document::setStatus( const QString &sts )
{
if ( m_status != sts ) {
m_status = sts;
if ( parent ) {
parent->documentChanged( this );
}
}
}
bool Document::load( KoXmlElement &element, XMLLoaderObject &status )
{
Q_UNUSED(status);
m_url = QUrl( element.attribute( "url" ) );
m_name = element.attribute( "name", m_url.fileName() );
m_type = ( Type )( element.attribute( "type" ).toInt() );
m_status = element.attribute( "status" );
m_sendAs = ( SendAs )( element.attribute( "sendas" ).toInt() );
return true;
}
void Document::save(QDomElement &element) const
{
element.setAttribute("url", m_url.url() );
element.setAttribute("name", m_name );
element.setAttribute("type", QString::number(m_type) );
element.setAttribute("status", m_status );
element.setAttribute("sendas", QString::number(m_sendAs) );
}
//----------------
Documents::Documents()
: node( 0 )
{
//debugPlan<<this;
}
Documents::Documents( const Documents &docs )
: node( 0 )
{
//debugPlan<<this;
foreach ( Document *doc, docs.documents() ) {
m_docs.append( new Document( *doc ) );
}
}
Documents::~Documents()
{
//debugPlan<<this;
deleteAll();
}
bool Documents::operator==( const Documents &docs ) const
{
int cnt = m_docs.count();
if ( cnt != docs.count() ) {
return false;
}
for ( int i = 0; i < cnt; ++i ) {
if ( *(m_docs.at( i ) ) != *( docs.at( i ) ) ) {
return false;
}
}
return true;
}
void Documents::deleteAll()
{
while ( ! m_docs.isEmpty() ) {
delete m_docs.takeFirst();
}
}
void Documents::addDocument( Document *doc )
{
Q_ASSERT( doc );
m_docs.append( doc );
doc->parent = this;
if ( node ) {
node->emitDocumentAdded( node, doc, m_docs.count() - 1 );
}
}
void Documents::addDocument( const QUrl &url, Document::Type type )
{
addDocument( new Document( url, type ) );
}
Document *Documents::takeDocument( int index )
{
if ( index >= 0 && index < m_docs.count() ) {
Document *doc = m_docs.takeAt( index );
if ( doc ) {
doc->parent = 0;
if ( node ) {
node->emitDocumentRemoved( node, doc, index );
}
}
return doc;
}
return 0;
}
Document *Documents::takeDocument( Document *doc )
{
Q_ASSERT( m_docs.contains( doc ) );
int idx = m_docs.indexOf( doc );
if ( idx >= 0 ) {
takeDocument( idx );
doc->parent = 0;
if ( node ) {
node->emitDocumentRemoved( node, doc, idx );
}
return doc;
}
return 0;
}
Document *Documents::findDocument( const Document *doc ) const
{
return findDocument( doc->url() );
}
Document *Documents::findDocument( const QUrl &url ) const
{
for ( int i = 0; i < m_docs.count(); ++i ) {
if ( m_docs.at( i )->url() == url ) {
return m_docs.at( i );
}
}
return 0;
}
bool Documents::load( KoXmlElement &element, XMLLoaderObject &status )
{
debugPlan;
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "document") {
Document *doc = new Document();
if ( !doc->load( e, status ) ) {
warnPlan<<"Failed to load document";
status.addMsg( XMLLoaderObject::Errors, "Failed to load document" );
delete doc;
} else {
addDocument( doc );
status.addMsg( i18n( "Document loaded, URL=%1", doc->url().url() ) );
}
}
}
return true;
}
void Documents::save(QDomElement &element) const
{
if ( m_docs.isEmpty() ) {
return;
}
QDomElement e = element.ownerDocument().createElement("documents");
element.appendChild(e);
foreach ( Document *d, m_docs) {
QDomElement me = element.ownerDocument().createElement("document");
e.appendChild(me);
d->save( me );
}
}
void Documents::saveToStore( KoStore *store ) const
{
foreach ( Document *doc, m_docs ) {
if ( doc->sendAs() == Document::SendAs_Copy ) {
QString path = doc->url().url();
if ( doc->url().isLocalFile() ) {
path = doc->url().toLocalFile();
}
debugPlan<<"Copy file to store: "<<path<<doc->url().fileName();
store->addLocalFile( path, doc->url().fileName() );
}
}
}
void Documents::documentChanged( Document *doc )
{
if ( node ) {
node->emitDocumentChanged( node, doc, indexOf( doc ) );
}
}
} //namespace KPlato
diff --git a/src/libs/kernel/kptduration.cpp b/src/libs/kernel/kptduration.cpp
index 08398dc2..f8a742f5 100644
--- a/src/libs/kernel/kptduration.cpp
+++ b/src/libs/kernel/kptduration.cpp
@@ -1,337 +1,338 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas Zander zander@kde.org
Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptduration.h"
#include "kptdatetime.h"
#include "kptdebug.h"
#include <KFormat>
#include <KLocalizedString>
#include <QLocale>
#include <QRegExp>
#include <QStringList>
namespace KPlato
{
// Set the value of Duration::zeroDuration to zero.
const Duration Duration::zeroDuration( 0, 0, 0 );
Duration::Duration() {
m_ms = 0;
}
Duration::Duration( double value, Duration::Unit unit ) {
if (unit == Unit_ms) m_ms = (qint64)value;
else if (unit == Unit_s) m_ms = (qint64)(value * 1000);
else if (unit == Unit_m) m_ms = (qint64)(value * ( 1000 * 60 ));
else if (unit == Unit_h) m_ms = (qint64)(value * ( 1000 * 60 * 60 ));
else if (unit == Unit_d) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 ));
else if (unit == Unit_w) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 * 7 ));
else if (unit == Unit_M) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 30 ));
else if (unit == Unit_Y) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 365 ));
}
Duration::Duration(unsigned d, unsigned h, unsigned m, unsigned s, unsigned ms) {
m_ms = ms;
m_ms += static_cast<qint64>(s) * 1000; // cast to avoid potential overflow problem
m_ms += static_cast<qint64>(m) * 60 * 1000;
m_ms += static_cast<qint64>(h) * 60 * 60 * 1000;
m_ms += static_cast<qint64>(d) * 24 * 60 * 60 * 1000;
}
Duration::Duration(const qint64 value, Duration::Unit unit) {
if (unit == Unit_ms) m_ms = value;
else if (unit == Unit_s) m_ms = (qint64)(value * 1000);
else if (unit == Unit_m) m_ms = (qint64)(value * ( 1000 * 60 ));
else if (unit == Unit_h) m_ms = (qint64)(value * ( 1000 * 60 * 60 ));
else if (unit == Unit_d) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 ));
else if (unit == Unit_w) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 * 7 ));
else if (unit == Unit_M) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 30 ));
else if (unit == Unit_Y) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 365 ));
else errorPlan<<"Unknown unit: "<<unit;
}
void Duration::add(KPlato::Duration delta) {
m_ms += delta.m_ms;
}
void Duration::add(qint64 delta) {
qint64 tmp = m_ms + delta;
if (tmp < 0) {
debugPlan<<"Underflow"<<(long int)delta<<" from"<<this->toString();
m_ms = 0;
return;
}
m_ms = tmp;
}
void Duration::subtract(KPlato::Duration delta) {
if (m_ms < delta.m_ms) {
debugPlan<<"Underflow"<<delta.toString()<<" from"<<this->toString();
m_ms = 0;
return;
}
m_ms -= delta.m_ms;
}
Duration Duration::operator*(int value) const {
Duration dur(*this);
if (value < 0) {
debugPlan<<"Underflow"<<value<<" from"<<this->toString();
}
else {
dur.m_ms = m_ms * value; //FIXME
}
return dur;
}
Duration Duration::operator/(int value) const {
Duration dur(*this);
if (value <= 0) {
debugPlan<<"Underflow"<<value<<" from"<<this->toString();
}
else {
dur.m_ms = m_ms / value; //FIXME
}
return dur;
}
Duration Duration::operator*(const double value) const {
Duration dur(*this);
dur.m_ms = qAbs(m_ms * (qint64)value);
return dur;
}
Duration Duration::operator*(const Duration value) const {
Duration dur(*this);
dur.m_ms = m_ms * value.m_ms;
return dur;
}
double Duration::operator/(KPlato::Duration d) const {
if (d == zeroDuration) {
debugPlan<<"Divide by zero:"<<this->toString();
return 0.0;
}
return (double)(m_ms) / (double)(d.m_ms);
}
QString Duration::format(Unit unit, int pres) const
{
/* FIXME if necessary
return i18nc( "<duration><unit>", "%1%2", QLocale().toString(toDouble(unit), 'f', pres), unitToString(unit) );*/
return QLocale().toString( toDouble( unit ), 'f', pres ) + unitToString( unit );
}
QString Duration::toString(Format format) const {
qint64 ms;
double days;
unsigned hours;
unsigned minutes;
unsigned seconds;
QString result;
switch (format) {
case Format_Hour:
ms = m_ms;
hours = ms / (1000 * 60 * 60);
ms -= (qint64)hours * (1000 * 60 * 60);
minutes = ms / (1000 * 60);
result = QStringLiteral("%1h%2m").arg(hours).arg(minutes);
break;
case Format_Day:
days = m_ms / (1000 * 60 * 60 * 24.0);
result = QStringLiteral("%1d").arg(QString::number(days, 'f', 4));
break;
case Format_DayTime:
ms = m_ms;
days = m_ms / (1000 * 60 * 60 * 24);
ms -= (qint64)days * (1000 * 60 * 60 * 24);
hours = ms / (1000 * 60 * 60);
ms -= (qint64)hours * (1000 * 60 * 60);
minutes = ms / (1000 * 60);
ms -= minutes * (1000 * 60);
seconds = ms / (1000);
ms -= seconds * (1000);
result.sprintf("%u %02u:%02u:%02u.%u", (unsigned)days, hours, minutes, seconds, (unsigned)ms);
break;
case Format_HourFraction:
result = QLocale().toString(toDouble(Unit_h), 'f', 2);
break;
// i18n
case Format_i18nHour:
ms = m_ms;
hours = ms / (1000 * 60 * 60);
ms -= (qint64)hours * (1000 * 60 * 60);
minutes = ms / (1000 * 60);
result = i18nc("<hours>h:<minutes>m", "%1h:%2m", hours, minutes);
break;
case Format_i18nDay:
result = KFormat().formatSpelloutDuration( m_ms );
break;
case Format_i18nWeek:
result = this->format( Unit_w, 2 );
break;
case Format_i18nMonth:
result = this->format( Unit_M, 2 );
break;
case Format_i18nYear:
result = this->format( Unit_Y, 2 );
break;
case Format_i18nDayTime:
ms = m_ms;
days = m_ms / (1000 * 60 * 60 * 24);
ms -= (qint64)days * (1000 * 60 * 60 * 24);
hours = ms / (1000 * 60 * 60);
ms -= (qint64)hours * (1000 * 60 * 60);
minutes = ms / (1000 * 60);
ms -= minutes * (1000 * 60);
seconds = ms / (1000);
ms -= seconds * (1000);
if (days == 0) {
result = toString(Format_i18nHour);
} else {
result = i18nc("<days>d <hours>h:<minutes>m", "%1d %2h:%3m", days, hours, minutes);
}
break;
case Format_i18nHourFraction:
result = QLocale().toString(toDouble(Unit_h), 'f', 2);
break;
default:
qFatal("Unknown format");
break;
}
return result;
}
Duration Duration::fromString(const QString &s, Format format, bool *ok) {
if (ok) *ok = false;
QRegExp matcher;
Duration tmp;
switch (format) {
case Format_Hour: {
matcher.setPattern(QStringLiteral("^(\\d*)h(\\d*)m$") );
int pos = matcher.indexIn(s);
if (pos > -1) {
tmp.addHours(matcher.cap(1).toUInt());
tmp.addMinutes(matcher.cap(2).toUInt());
if (ok) *ok = true;
}
break;
}
case Format_DayTime: {
matcher.setPattern(QStringLiteral("^(\\d*) (\\d*):(\\d*):(\\d*)\\.(\\d*)$") );
int pos = matcher.indexIn(s);
if (pos > -1) {
tmp.addDays(matcher.cap(1).toUInt());
tmp.addHours(matcher.cap(2).toUInt());
tmp.addMinutes(matcher.cap(3).toUInt());
tmp.addSeconds(matcher.cap(4).toUInt());
tmp.addMilliseconds(matcher.cap(5).toUInt());
if (ok) *ok = true;
}
break;
}
case Format_HourFraction: {
// should be in double format
bool res;
double f = QLocale().toDouble(s, &res);
if (ok) *ok = res;
if (res) {
return Duration((qint64)(f)*3600*1000);
}
break;
}
default:
qFatal("Unknown format");
break;
}
return tmp;
}
QStringList Duration::unitList( bool trans )
{
QStringList lst;
lst << ( trans ? i18nc( "Year. Note: Letter(s) only!", "Y" ) : QStringLiteral("Y") )
<< ( trans ? i18nc( "Month. Note: Letter(s) only!", "M" ) : QStringLiteral("M") )
<< ( trans ? i18nc( "Week. Note: Letter(s) only!", "w" ) : QStringLiteral("w") )
<< ( trans ? i18nc( "Day. Note: Letter(s) only!", "d" ) : QStringLiteral("d") )
<< ( trans ? i18nc( "Hour. Note: Letter(s) only!", "h" ) : QStringLiteral("h") )
<< ( trans ? i18nc( "Minute. Note: Letter(s) only!", "m" ) : QStringLiteral("m") )
<< ( trans ? i18nc( "Second. Note: Letter(s) only!", "s" ) : QStringLiteral("s") )
<< ( trans ? i18nc( "Millisecond. Note: Letter(s) only!", "ms" ) : QStringLiteral("ms") );
return lst;
}
QString Duration::unitToString( Duration::Unit unit, bool trans )
{
return unitList( trans ).at( unit );
}
Duration::Unit Duration::unitFromString( const QString &u )
{
int i = unitList().indexOf( u );
if ( i < 0 ) {
errorPlan<<"Illegal unit: "<<u;
return Unit_ms;
}
return (Duration::Unit)( i );
}
bool Duration::valueFromString( const QString &value, double &rv, Unit &unit ) {
QStringList lst = Duration::unitList();
foreach ( const QString &s, lst ) {
int pos = value.lastIndexOf( s );
if ( pos != -1 ) {
unit = Duration::unitFromString( s );
QString v = value;
v.remove( s );
bool ok;
rv = v.toDouble( &ok );
errorPlan<<value<<" -> "<<v<<", "<<s<<" = "<<ok<<endl;
return ok;
}
}
errorPlan<<"Illegal format, no unit: "<<value<<endl;
return false;
}
double Duration::toHours() const
{
return toDouble( Unit_h );
}
double Duration::toDouble( Unit u ) const
{
if (u == Unit_ms) return (double)m_ms;
else if (u == Unit_s) return (double)m_ms/1000.0;
else if (u == Unit_m) return (double)m_ms/(1000.0*60.0);
else if (u == Unit_h) return (double)m_ms/(1000.0*60.0*60.0);
else if (u == Unit_d) return (double)m_ms/(1000.0*60.0*60.0*24.0);
else if (u == Unit_w) return (double)m_ms/(1000.0*60.0*60.0*24.0*7.0);
else if (u == Unit_M) return (double)m_ms/(1000.0*60.0*60.0*24.0*30); //Month
else if (u == Unit_Y) return (double)m_ms/(1000.0*60.0*60.0*24.0*365); // Year
return (double)m_ms;
}
} //KPlato namespace
diff --git a/src/libs/kernel/kpteffortcostmap.cpp b/src/libs/kernel/kpteffortcostmap.cpp
index 2d01720f..ddf5e31e 100644
--- a/src/libs/kernel/kpteffortcostmap.cpp
+++ b/src/libs/kernel/kpteffortcostmap.cpp
@@ -1,158 +1,159 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
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;
version 2 of the License.
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 "kpteffortcostmap.h"
namespace KPlato
{
void EffortCost::add(const Duration &effort, double cost, double bcwpEffort, double bcwpCost )
{
m_effort += effort;
m_cost += cost;
m_bcwpEffort += bcwpEffort;
m_bcwpCost += bcwpCost;
}
//-----------------------
EffortCostMap::EffortCostMap( const EffortCostMap &map )
{
m_days = map.m_days;
}
void EffortCostMap::insert(const QDate &date, const EffortCost &ec )
{
Q_ASSERT( date.isValid() );
m_days[ date ] = ec;
}
EffortCostMap &EffortCostMap::operator=( const EffortCostMap &ec )
{
m_days = ec.m_days;
return *this;
}
EffortCostMap &EffortCostMap::operator+=(const EffortCostMap &ec) {
//debugPlan<<"me="<<m_days.count()<<" ec="<<ec.days().count();
if (ec.isEmpty()) {
return *this;
}
if (isEmpty()) {
m_days = ec.days();
return *this;
}
EffortCostMap other = ec;
QDate oed = other.endDate();
QDate ed = endDate();
// get bcwp of the last entries
EffortCost last_oec = other.m_days[oed];
last_oec.setEffort( Duration::zeroDuration );
last_oec.setCost( 0.0 );
EffortCost last_ec = m_days[ed];
last_ec.setEffort( Duration::zeroDuration );
last_ec.setCost( 0.0 );
if ( oed > ed ) {
// expand my last entry to match other
for ( QDate d = ed.addDays( 1 ); d <= oed; d = d.addDays( 1 ) ) {
m_days[ d ] = last_ec ;
}
}
EffortCostDayMap::const_iterator it;
for(it = ec.days().constBegin(); it != ec.days().constEnd(); ++it) {
add(it.key(), it.value());
}
if ( oed < ed ) {
// add others last entry to my trailing entries
for ( QDate d = oed.addDays( 1 ); d <= ed; d = d.addDays( 1 ) ) {
m_days[ d ] += last_oec;
}
}
return *this;
}
void EffortCostMap::addBcwpCost( const QDate &date, double cost )
{
EffortCost ec = m_days[ date ];
ec.setBcwpCost( ec.bcwpCost() + cost );
m_days[ date ] = ec;
}
double EffortCostMap::bcwpCost( const QDate &date ) const
{
double v = 0.0;
for ( EffortCostDayMap::const_iterator it = m_days.constBegin(); it != m_days.constEnd(); ++it ) {
if ( it.key() > date ) {
break;
}
v = it.value().bcwpCost();
}
return v;
}
double EffortCostMap::bcwpEffort( const QDate &date ) const
{
double v = 0.0;
for ( EffortCostDayMap::const_iterator it = m_days.constBegin(); it != m_days.constEnd(); ++it ) {
if ( it.key() > date ) {
break;
}
v = it.value().bcwpEffort();
}
return v;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug EffortCostMap::debug( QDebug dbg ) const
{
dbg.nospace()<<"EffortCostMap[";
if ( ! m_days.isEmpty() ) {
dbg<<startDate().toString(Qt::ISODate)<<" "<<endDate().toString(Qt::ISODate)
<<" total bcws="<<totalEffort().toDouble( Duration::Unit_h )<<", "<<totalCost()<<" bcwp="<<bcwpTotalEffort()<<" "<<bcwpTotalCost();
}
dbg.nospace()<<']';
if ( ! m_days.isEmpty() ) {
QMap<QDate, KPlato::EffortCost>::ConstIterator it = days().constBegin();
for ( ; it != days().constEnd(); ++it ) {
dbg<<endl;
dbg<<" "<<it.key().toString(Qt::ISODate)<<" "<<it.value();
}
dbg<<endl;
}
return dbg;
}
#endif
} //namespace KPlato
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<( QDebug dbg, const KPlato::EffortCost &ec )
{
dbg.nospace()<<"EffortCost[ bcws effort="<<ec.hours()<<" cost="<<ec.cost()<<" : bcwp effort="<<ec.bcwpEffort()<<" cost="<<ec.bcwpCost()<<"]";
return dbg;
}
QDebug operator<<( QDebug dbg, const KPlato::EffortCost *ec )
{
return operator<<( dbg, *ec );
}
QDebug operator<<( QDebug dbg, const KPlato::EffortCostMap &i )
{
return i.debug( dbg );
}
#endif
diff --git a/src/libs/kernel/kptglobal.cpp b/src/libs/kernel/kptglobal.cpp
index 54386750..ab089072 100644
--- a/src/libs/kernel/kptglobal.cpp
+++ b/src/libs/kernel/kptglobal.cpp
@@ -1,51 +1,52 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptglobal.h"
#include <KLocalizedString>
namespace KPlato
{
// namespace SchedulingState
// {
QString SchedulingState::deleted( bool trans )
{ return trans ? i18n( "Deleted" ) : QString( "Deleted" ); }
QString SchedulingState::notScheduled( bool trans )
{ return trans ? i18n( "Not scheduled" ) : QString( "Not scheduled" ); }
QString SchedulingState::scheduled( bool trans )
{ return trans ? i18n( "Scheduled" ) : QString( "Scheduled" ); }
QString SchedulingState::resourceOverbooked( bool trans )
{ return trans ? i18n( "Resource overbooked" ) : QString( "Resource overbooked" ); }
QString SchedulingState::resourceNotAvailable( bool trans )
{ return trans ? i18n( "Resource not available" ) : QString( "Resource not available" ); }
QString SchedulingState::resourceNotAllocated( bool trans )
{ return trans ? i18n( "No resource allocated" ) : QString( "No resource allocated" ); }
QString SchedulingState::constraintsNotMet( bool trans )
{ return trans ? i18n( "Cannot fulfill constraints" ) : QString( "Cannot fulfill constraints" ); }
QString SchedulingState::effortNotMet( bool trans )
{ return trans ? i18n( "Effort not met" ) : QString( "Effort not met" ); }
QString SchedulingState::schedulingError( bool trans )
{ return trans ? i18n( "Scheduling error" ) : QString( "Scheduling error" ); }
//} namespace SchedulingState
} //namespace KPlato
diff --git a/src/libs/kernel/kptlocale.cpp b/src/libs/kernel/kptlocale.cpp
index 1339949e..612d6c2f 100644
--- a/src/libs/kernel/kptlocale.cpp
+++ b/src/libs/kernel/kptlocale.cpp
@@ -1,120 +1,121 @@
/* This file is part of the KDE project
Copyright (C) 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptlocale.h"
#include "kptdebug.h"
#include <QLocale>
namespace KPlato
{
Locale::Locale()
{
QLocale locale;
m_language = locale.language();
m_country = locale.country();
m_decimalPlaces = 2;
}
Locale::~Locale()
{
}
void Locale::setCurrencyLocale(QLocale::Language language, QLocale::Country country)
{
m_language = language;
m_country = country;
}
void Locale::setCurrencySymbol(const QString &symbol)
{
m_currencySymbol = symbol;
}
QString Locale::currencySymbol() const
{
QString s = m_currencySymbol;
if (s.isEmpty()) {
QLocale locale(m_language, m_country);
s = locale.currencySymbol(QLocale::CurrencySymbol);
}
return s;
}
void Locale::setMonetaryDecimalPlaces(int digits)
{
m_decimalPlaces = digits;
}
int Locale::monetaryDecimalPlaces() const
{
return m_decimalPlaces;
}
QString Locale::formatMoney(double num, const QString &currency, int precision) const
{
QString c = currency;
if (c.isEmpty()) {
c = currencySymbol();
}
int p = precision;
if (p < 0) {
p = m_decimalPlaces;
}
QLocale l;
QString s = l.toCurrencyString(num, c, p);
return s;
}
double Locale::readMoney(const QString &numStr, bool *ok) const
{
QLocale l;
QString s = numStr;
bool okk = false;
s.remove(currencySymbol());
double v = l.toDouble(s, &okk);
if (ok) {
*ok = okk;
}
if (!okk) {
errorPlan<<"Failed to read money:"<<numStr;
}
return v;
}
QString Locale::currencySymbolExplicit() const
{
return m_currencySymbol;
}
QLocale::Language Locale::currencyLanguage() const
{
return m_language;
}
QLocale::Country Locale::currencyCountry() const
{
return m_country;
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptnode.cpp b/src/libs/kernel/kptnode.cpp
index 4670e901..d5e22681 100644
--- a/src/libs/kernel/kptnode.cpp
+++ b/src/libs/kernel/kptnode.cpp
@@ -1,1883 +1,1884 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
Copyright (C) 2002 - 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptnode.h"
#include "kptappointment.h"
#include "kptaccount.h"
#include "kptwbsdefinition.h"
#include "kptresource.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QListIterator>
namespace KPlato
{
Node::Node(Node *parent)
: QObject( 0 ), // We don't use qobjects parent
m_nodes(), m_dependChildNodes(), m_dependParentNodes(),
m_estimate( 0 ),
m_blockChanged(false)
{
//debugPlan<<"("<<this<<")";
m_parent = parent;
init();
m_id.clear(); // Not mapped
}
Node::Node(const Node &node, Node *parent)
: QObject( 0 ), // Don't set parent, we handle parent/child ourselves
m_nodes(),
m_dependChildNodes(),
m_dependParentNodes(),
m_estimate( 0 ),
m_blockChanged(false)
{
//debugPlan<<"("<<this<<")";
m_parent = parent;
init();
m_name = node.name();
m_leader = node.leader();
m_description = node.description();
m_constraint = (ConstraintType) node.constraint();
m_constraintStartTime = node.constraintStartTime();
m_constraintEndTime = node.constraintEndTime();
m_runningAccount = node.runningAccount();
m_startupAccount = node.startupAccount();
m_shutdownAccount = node.shutdownAccount();
m_startupCost = node.startupCost();
m_shutdownCost = node.shutdownCost();
setObjectName( node.objectName() );
}
Node::~Node() {
//debugPlan<<"("<<this<<")"<<m_name;
delete m_estimate;
while (!m_nodes.isEmpty())
delete m_nodes.takeFirst();
if (findNode() == this) {
removeId(); // only remove myself (I may be just a working copy)
}
while (!m_dependParentNodes.isEmpty()) {
delete m_dependParentNodes.value(0);
}
while (!m_dependChildNodes.isEmpty()) {
delete m_dependChildNodes.value(0);
}
if (m_runningAccount)
m_runningAccount->removeRunning(*this);
if (m_startupAccount)
m_startupAccount->removeStartup(*this);
if (m_shutdownAccount)
m_shutdownAccount->removeShutdown(*this);
foreach (Schedule *s, m_schedules) {
delete s;
}
m_schedules.clear();
m_parent = 0; //safety
}
void Node::init() {
m_documents.node = this;
m_currentSchedule = 0;
m_name="";
m_constraint = Node::ASAP;
m_estimate = 0;
m_visitedForward = false;
m_visitedBackward = false;
m_runningAccount = 0;
m_startupAccount = 0;
m_shutdownAccount = 0;
m_startupCost = 0.0;
m_shutdownCost = 0.0;
}
QString Node::typeToString( bool trans ) const
{
return typeToString( (Node::NodeTypes)type(), trans );
}
// static
QString Node::typeToString( Node::NodeTypes typ, bool trans )
{
return typeToStringList( trans ).at( typ );
}
// static
QStringList Node::typeToStringList( bool trans )
{
return QStringList()
<< ( trans ? i18n( "None" ) : QString( "None" ) )
<< ( trans ? i18n( "Project" ) : QString( "Project" ) )
<< ( trans ? i18n( "Sub-Project" ) : QString( "Sub-Project" ) )
<< ( trans ? i18n( "Task" ) : QString( "Task" ) )
<< ( trans ? i18n( "Milestone" ) : QString( "Milestone" ) )
<< ( trans ? i18n( "Periodic" ) : QString( "Periodic" ) )
<< ( trans ? i18n( "Summary" ) : QString( "Summary-Task" ) );
}
void Node::setName(const QString &n)
{
#ifndef NDEBUG
setObjectName( n );
#endif
m_name = n;
changed(this);
}
void Node::setLeader(const QString &l)
{
m_leader = l;
changed(this);
}
void Node::setDescription(const QString &d)
{
m_description = d;
changed(this);
}
Node *Node::projectNode() {
if ((type() == Type_Project) || (type() == Type_Subproject)) {
return this;
}
if (m_parent)
return m_parent->projectNode();
// This happens for default tasks
return 0;
}
const Node *Node::projectNode() const {
if ((type() == Type_Project) || (type() == Type_Subproject)) {
return this;
}
if (m_parent)
return m_parent->projectNode();
// This happens for default tasks
return 0;
}
void Node::takeChildNode( Node *node) {
//debugPlan<<"find="<<m_nodes.indexOf(node);
int t = type();
int i = m_nodes.indexOf(node);
if ( i != -1 ) {
m_nodes.removeAt(i);
}
node->setParentNode(0);
if ( t != type() ) {
changed( Type );
}
}
void Node::takeChildNode( int number ) {
int t = type();
if (number >= 0 && number < m_nodes.size()) {
Node *n = m_nodes.takeAt(number);
//debugPlan<<(n?n->id():"null")<<" :"<<(n?n->name():"");
if (n) {
n->setParentNode( 0 );
}
}
if ( t != type() ) {
changed( Type );
}
}
void Node::insertChildNode( int index, Node *node ) {
int t = type();
if (index == -1)
m_nodes.append(node);
else
m_nodes.insert(index,node);
node->setParentNode( this );
if ( t != type() ) {
changed( Type );
}
}
void Node::addChildNode( Node *node, Node *after) {
int t = type();
int index = m_nodes.indexOf(after);
if (index == -1) {
m_nodes.append(node);
node->setParentNode( this );
if ( t != type() ) {
changed( Type );
}
return;
}
m_nodes.insert(index+1, node);
node->setParentNode(this);
if ( t != type() ) {
changed( Type );
}
}
int Node::findChildNode( const Node* node ) const
{
return m_nodes.indexOf( const_cast<Node*>( node ) );
}
bool Node::isChildOf( const Node* node ) const
{
if ( node == 0 || m_parent == 0 ) {
return false;
}
if ( node == m_parent ) {
return true;
}
return m_parent->isChildOf( node );
}
Node* Node::childNode(int number)
{
//debugPlan<<number;
return m_nodes.value( number );
}
const Node* Node::childNode(int number) const
{
if ( number < 0 || number >= m_nodes.count() ) {
return 0;
}
return m_nodes.at( number );
}
int Node::indexOf( const Node *node ) const
{
return m_nodes.indexOf( const_cast<Node*>(node) );
}
Duration *Node::getDelay() {
/* TODO
Calculate the delay of this node. Use the calculated startTime and the set startTime.
*/
return 0L;
}
void Node::addDependChildNode( Node *node, Relation::Type p) {
addDependChildNode(node,p,Duration());
}
void Node::addDependChildNode( Node *node, Relation::Type p, Duration lag) {
Relation *relation = new Relation(this, node, p, lag);
if (node->addDependParentNode(relation))
m_dependChildNodes.append(relation);
else
delete relation;
}
void Node::insertDependChildNode( unsigned int index, Node *node, Relation::Type p) {
Relation *relation = new Relation(this, node, p, Duration());
if (node->addDependParentNode(relation))
m_dependChildNodes.insert(index, relation);
else
delete relation;
}
bool Node::addDependChildNode( Relation *relation) {
if(m_dependChildNodes.indexOf(relation) != -1)
return false;
m_dependChildNodes.append(relation);
return true;
}
void Node::takeDependChildNode( Relation *rel ) {
int i = m_dependChildNodes.indexOf(rel);
if ( i != -1 ) {
//debugPlan<<m_name<<": ("<<rel<<")";
m_dependChildNodes.removeAt(i);
}
}
void Node::addDependParentNode( Node *node, Relation::Type p) {
addDependParentNode(node,p,Duration());
}
void Node::addDependParentNode( Node *node, Relation::Type p, Duration lag) {
Relation *relation = new Relation(node, this, p, lag);
if (node->addDependChildNode(relation))
m_dependParentNodes.append(relation);
else
delete relation;
}
void Node::insertDependParentNode( unsigned int index, Node *node, Relation::Type p) {
Relation *relation = new Relation(this, node, p, Duration());
if (node->addDependChildNode(relation))
m_dependParentNodes.insert(index,relation);
else
delete relation;
}
bool Node::addDependParentNode( Relation *relation) {
if(m_dependParentNodes.indexOf(relation) != -1)
return false;
m_dependParentNodes.append(relation);
return true;
}
void Node::takeDependParentNode( Relation *rel ) {
int i = m_dependParentNodes.indexOf(rel);
if ( i != -1 ) {
//debugPlan<<m_name<<": ("<<rel<<")";
m_dependParentNodes.removeAt(i);
}
}
bool Node::isParentOf( const Node *node ) const
{
if (m_nodes.indexOf( const_cast<Node*>( node ) ) != -1)
return true;
QListIterator<Node*> nit(childNodeIterator());
while (nit.hasNext()) {
if (nit.next()->isParentOf(node))
return true;
}
return false;
}
Relation *Node::findParentRelation( const Node *node ) const
{
for (int i=0; i<numDependParentNodes(); i++) {
Relation *rel = getDependParentNode(i);
if (rel->parent() == node)
return rel;
}
return (Relation *)0;
}
Relation *Node::findChildRelation( const Node *node) const
{
for (int i=0; i<numDependChildNodes(); i++) {
Relation *rel = getDependChildNode(i);
if (rel->child() == node)
return rel;
}
return (Relation *)0;
}
Relation *Node::findRelation( const Node *node ) const
{
Relation *rel = findParentRelation(node);
if (!rel)
rel = findChildRelation(node);
return rel;
}
bool Node::isDependChildOf( const Node *node ) const
{
//debugPlan<<" '"<<m_name<<"' checking against '"<<node->name()<<"'";
for (int i=0; i<numDependParentNodes(); i++) {
Relation *rel = getDependParentNode(i);
if (rel->parent() == node)
return true;
if (rel->parent()->isDependChildOf(node))
return true;
}
return false;
}
QList<Node*> Node::getParentNodes()
{
this->m_parentNodes.clear();
foreach(Relation * currentRelation, this->dependParentNodes())
{
if (!this->m_parentNodes.contains(currentRelation->parent()))
{
this->m_parentNodes.append(currentRelation->parent());
}
}
return this->m_parentNodes;
}
bool Node::canMoveTo( const Node *newParent ) const
{
if ( m_parent == newParent ) {
return true;
}
if ( newParent->isChildOf( this ) ) {
return false;
}
if ( isDependChildOf( newParent ) || newParent->isDependChildOf( this ) ) {
debugPlan<<"Can't move, node is dependent on new parent";
return false;
}
foreach ( Node *n, m_nodes ) {
if ( !n->canMoveTo( newParent ) ) {
return false;
}
}
return true;
}
void Node::makeAppointments() {
QListIterator<Node*> nit(m_nodes);
while (nit.hasNext()) {
nit.next()->makeAppointments();
}
}
void Node::calcResourceOverbooked() {
QListIterator<Node*> nit(m_nodes);
while (nit.hasNext()) {
nit.next()->calcResourceOverbooked();
}
}
// Returns the (previously) calculated duration
Duration Node::duration( long id ) const
{
Schedule *s = schedule( id );
return s ? s->duration : Duration::zeroDuration;
}
double Node::variance( long id, Duration::Unit unit ) const
{
double d = deviation( id, unit );
return d * d;
}
double Node::deviation( long id, Duration::Unit unit ) const
{
Schedule *s = schedule( id );
double d = 0.0;
if ( s && m_estimate ) {
d = s->duration.toDouble( unit );
double o = ( d * ( 100 + m_estimate->optimisticRatio() ) ) / 100;
double p = ( d * ( 100 + m_estimate->pessimisticRatio() ) ) / 100;
d = ( p - o ) / 6;
}
return d;
}
DateTime Node::startTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->startTime : DateTime();
}
DateTime Node::endTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->endTime : DateTime();
}
DateTime Node::appointmentStartTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->appointmentStartTime() : DateTime();
}
DateTime Node::appointmentEndTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->appointmentEndTime() : DateTime();
}
void Node::setDuration(const Duration &duration, long id )
{
Schedule *s = schedule( id );
if ( s ) {
s->duration = duration;
}
}
void Node::setEarlyStart(const DateTime &dt, long id )
{
Schedule *s = schedule( id );
if ( s ) s->earlyStart = dt;
}
DateTime Node::earlyStart( long id ) const
{
Schedule *s = schedule( id );
return s ? s->earlyStart : DateTime();
}
void Node::setLateStart(const DateTime &dt, long id )
{
Schedule *s = schedule( id );
if ( s ) s->lateStart = dt;
}
DateTime Node::lateStart( long id ) const
{
Schedule *s = schedule( id );
return s ? s->lateStart : DateTime();
}
void Node::setEarlyFinish(const DateTime &dt, long id )
{
Schedule *s = schedule( id );
if ( s ) s->earlyFinish = dt;
}
DateTime Node::earlyFinish( long id ) const
{
Schedule *s = schedule( id );
return s ? s->earlyFinish : DateTime();
}
void Node::setLateFinish(const DateTime &dt, long id )
{
Schedule *s = schedule( id );
if ( s ) s->lateFinish = dt;
}
DateTime Node::lateFinish( long id ) const
{
Schedule *s = schedule( id );
return s ? s->lateFinish : DateTime();
}
DateTime Node::workStartTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->workStartTime : DateTime();
}
void Node::setWorkStartTime(const DateTime &dt, long id )
{
Schedule *s = schedule( id );
if ( s ) s->workStartTime = dt;
}
DateTime Node::workEndTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->workEndTime : DateTime();
}
void Node::setWorkEndTime(const DateTime &dt, long id )
{
Schedule *s = schedule( id );
if ( s ) s->workEndTime = dt;
}
bool Node::inCriticalPath( long id ) const
{
Schedule *s = schedule( id );
return s ? s->inCriticalPath : false;
}
QStringList Node::schedulingStatus( long id, bool trans ) const
{
Schedule *s = schedule( id );
QStringList lst;
if ( s ) {
lst = s->state();
}
if ( lst.isEmpty() ) {
lst.append( trans ? i18n( "Not scheduled" ) : QString( "Not scheduled" ) );
}
return lst;
}
bool Node::resourceError( long id ) const
{
Schedule *s = schedule( id );
return s ? s->resourceError : false;
}
bool Node::resourceOverbooked( long id ) const
{
Schedule *s = schedule( id );
return s ? s->resourceOverbooked : false;
}
bool Node::resourceNotAvailable( long id ) const
{
Schedule *s = schedule( id );
return s ? s->resourceNotAvailable : false;
}
bool Node::constraintError( long id ) const
{
Schedule *s = schedule( id );
return s ? s->constraintError : false;
}
bool Node::schedulingError( long id ) const
{
Schedule *s = schedule( id );
return s ? s->schedulingError : false;
}
bool Node::notScheduled( long id ) const
{
if ( type() == Type_Summarytask ) {
// i am scheduled if al least on child is scheduled
foreach ( Node *n, m_nodes ) {
if ( ! n->notScheduled( id ) ) {
return false;
}
}
return true;
}
Schedule *s = schedule( id );
return s == 0 || s->isDeleted() || s->notScheduled;
}
QStringList Node::overbookedResources( long id ) const
{
Schedule *s = schedule( id );
return s ? s->overbookedResources() : QStringList();
}
void Node::saveWorkPackageXML( QDomElement &, long ) const
{
return;
}
void Node::saveRelations(QDomElement &element) const
{
QListIterator<Relation*> it(m_dependChildNodes);
while (it.hasNext()) {
it.next()->save(element);
}
QListIterator<Node*> nodes(m_nodes);
while (nodes.hasNext()) {
nodes.next()->saveRelations(element);
}
}
void Node::setConstraint(Node::ConstraintType type)
{
m_constraint = type;
changed( this );
}
void Node::setConstraint(const QString &type) {
// Do not i18n these, they are used in load()
if (type == "ASAP")
setConstraint(ASAP);
else if (type == "ALAP")
setConstraint(ALAP);
else if (type == "MustStartOn")
setConstraint(MustStartOn);
else if (type == "MustFinishOn")
setConstraint(MustFinishOn);
else if (type == "StartNotEarlier")
setConstraint(StartNotEarlier);
else if (type == "FinishNotLater")
setConstraint(FinishNotLater);
else if (type == "FixedInterval")
setConstraint(FixedInterval);
else
setConstraint(ASAP); // default
}
QString Node::constraintToString( bool trans ) const {
return constraintList( trans ).at( m_constraint );
}
QStringList Node::constraintList( bool trans ) {
// keep theses in the same order as the enum!
return QStringList()
<< (trans ? i18n("As Soon As Possible") : QString("ASAP"))
<< (trans ? i18n("As Late As Possible") : QString("ALAP"))
<< (trans ? i18n("Must Start On") : QString("MustStartOn"))
<< (trans ? i18n("Must Finish On") : QString("MustFinishOn"))
<< (trans ? i18n("Start Not Earlier") : QString("StartNotEarlier"))
<< (trans ? i18n("Finish Not Later") : QString("FinishNotLater"))
<< (trans ? i18n("Fixed Interval") : QString("FixedInterval"));
}
void Node::propagateEarliestStart(DateTime &time) {
if (m_currentSchedule == 0) {
return;
}
if ( type() != Type_Project ) {
m_currentSchedule->earlyStart = time;
if ( m_currentSchedule->lateStart.isValid() && m_currentSchedule->lateStart < time ) {
m_currentSchedule->lateStart = time;
}
//m_currentSchedule->logDebug( "propagateEarliestStart: " + time.toString() );
switch ( m_constraint ) {
case FinishNotLater:
case MustFinishOn:
if ( m_constraintEndTime < time ) {
m_currentSchedule->logWarning("Task constraint outside project constraint");
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug(QString("%1: end constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString()));
#endif
}
break;
case MustStartOn:
case FixedInterval:
if ( m_constraintStartTime < time ) {
m_currentSchedule->logWarning("Task constraint outside project constraint");
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug(QString("%1: start constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString()));
#endif
}
break;
default:
break;
}
}
//debugPlan<<m_name<<":"<<m_currentSchedule->earlyStart;
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->propagateEarliestStart(time);
}
}
void Node::propagateLatestFinish(DateTime &time) {
if (m_currentSchedule == 0) {
return;
}
if ( type() != Type_Project ) {
m_currentSchedule->lateFinish = time;
if ( m_currentSchedule->earlyFinish.isValid() && m_currentSchedule->earlyFinish > time ) {
m_currentSchedule->earlyFinish = time;
}
switch ( m_constraint ) {
case StartNotEarlier:
case MustStartOn:
if ( m_constraintStartTime > time ) {
m_currentSchedule->logWarning("Task constraint outside project constraint");
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug(QString("%1: start constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString()));
#endif
}
break;
case MustFinishOn:
case FixedInterval:
if ( m_constraintEndTime > time ) {
m_currentSchedule->logWarning("Task constraint outside project constraint");
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug(QString("%1: end constraint %2 > %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString()));
#endif
}
break;
default:
break;
}
}
//debugPlan<<m_name<<":"<<m_currentSchedule->lateFinish;
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->propagateLatestFinish(time);
}
}
void Node::moveEarliestStart(DateTime &time) {
if (m_currentSchedule == 0)
return;
if (m_currentSchedule->earlyStart < time) {
//m_currentSchedule->logDebug( "moveEarliestStart: " + m_currentSchedule->earlyStart.toString() + " -> " + time.toString() );
m_currentSchedule->earlyStart = time;
}
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->moveEarliestStart(time);
}
}
void Node::moveLatestFinish(DateTime &time) {
if (m_currentSchedule == 0)
return;
if (m_currentSchedule->lateFinish > time)
m_currentSchedule->lateFinish = time;
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->moveLatestFinish(time);
}
}
void Node::initiateCalculation(MainSchedule &sch) {
m_visitedForward = false;
m_visitedBackward = false;
m_durationForward = Duration::zeroDuration;
m_durationBackward = Duration::zeroDuration;
m_earlyStart = DateTime();
m_earlyFinish = DateTime();
m_lateFinish = DateTime();
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->initiateCalculation(sch);
}
}
void Node::resetVisited() {
m_visitedForward = false;
m_visitedBackward = false;
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->resetVisited();
}
}
Node *Node::siblingBefore() {
//debugPlan;
if (parentNode())
return parentNode()->childBefore(this);
return 0;
}
Node *Node::childBefore(Node *node) {
//debugPlan;
int index = m_nodes.indexOf(node);
if (index > 0){
return m_nodes.at(index-1);
}
return 0;
}
Node *Node::siblingAfter() {
//debugPlan;
if (parentNode())
return parentNode()->childAfter(this);
return 0;
}
Node *Node::childAfter(Node *node)
{
//debugPlan;
Q_ASSERT( m_nodes.contains( node ) );
int index = m_nodes.indexOf(node);
if (index < m_nodes.count()-1) {
return m_nodes.at(index+1);
}
return 0;
}
bool Node::moveChildUp(Node* node)
{
if (findChildNode(node) == -1)
return false; // not my node!
Node *sib = node->siblingBefore();
if (!sib)
return false;
sib = sib->siblingBefore();
takeChildNode(node);
if (sib) {
addChildNode(node, sib);
} else {
insertChildNode(0, node);
}
return true;
}
bool Node::moveChildDown(Node* node)
{
if (findChildNode(node) == -1)
return false; // not my node!
Node *sib = node->siblingAfter();
if (!sib)
return false;
takeChildNode(node);
addChildNode(node, sib);
return true;
}
bool Node::legalToLink( const Node *node ) const
{
Node *p = const_cast<Node*>(this)->projectNode();
if (p)
return p->legalToLink(this, node);
return false;
}
bool Node::isEndNode() const {
return m_dependChildNodes.isEmpty();
}
bool Node::isStartNode() const {
return m_dependParentNodes.isEmpty();
}
void Node::setId(const QString& id) {
//debugPlan<<id;
m_id = id;
}
void Node::setStartTime(const DateTime &startTime, long id )
{
Schedule *s = schedule( id );
if ( s )
s->startTime = startTime;
}
void Node::setEndTime(const DateTime &endTime, long id )
{
Schedule *s = schedule( id );
if ( s )
s->endTime = endTime;
}
void Node::saveAppointments(QDomElement &element, long id) const {
//debugPlan<<m_name<<" id="<<id;
QListIterator<Node*> it(m_nodes);
while (it.hasNext()) {
it.next()->saveAppointments(element, id);
}
}
QList<Appointment*> Node::appointments( long id )
{
Schedule *s = schedule( id );
QList<Appointment*> lst;
if ( s )
lst = s->appointments();
return lst;
}
QList<Resource*> Node::assignedResources( long id ) const {
Schedule *s = schedule( id );
QList<Resource*> res;
if ( s ) {
foreach ( Appointment *a, s->appointments() ) {
res << a->resource()->resource();
}
}
return res;
}
// Appointment *Node::findAppointment(Resource *resource) {
// if (m_currentSchedule)
// return m_currentSchedule->findAppointment(resource);
// return 0;
// }
// bool Node::addAppointment(Appointment *appointment) {
// if ( m_currentSchedule )
// return m_currentSchedule->add(appointment);
// return false;
// }
//
// called from resource side when resource adds appointment
bool Node::addAppointment(Appointment *appointment, Schedule &main) {
Schedule *s = findSchedule(main.id());
if (s == 0) {
s = createSchedule(&main);
}
appointment->setNode(s);
//debugPlan<<this<<":"<<appointment<<","<<s<<","<<s->id()<<","<<main.id();
return s->add(appointment);
}
void Node::addAppointment(ResourceSchedule *resource, const DateTime &start, const DateTime &end, double load) {
Schedule *node = findSchedule(resource->id());
if (node == 0) {
node = createSchedule(resource->parent());
}
node->setCalculationMode( resource->calculationMode() );
node->addAppointment(resource, start, end, load);
}
bool Node::isBaselined( long id ) const
{
Schedule *s = schedule( id );
return s ? s->isBaselined() : false;
}
void Node::takeSchedule(const Schedule *schedule) {
if (schedule == 0)
return;
if (m_currentSchedule == schedule)
m_currentSchedule = 0;
m_schedules.take(schedule->id());
}
void Node::addSchedule(Schedule *schedule) {
if (schedule == 0)
return;
m_schedules.insert(schedule->id(), schedule);
}
Schedule *Node::createSchedule(const QString& name, Schedule::Type type, long id) {
//debugPlan<<name<<" type="<<type<<" id="<<(int)id;
NodeSchedule *sch = new NodeSchedule(this, name, type, id);
addSchedule(sch);
return sch;
}
Schedule *Node::createSchedule(Schedule *parent) {
//debugPlan<<name<<" type="<<type<<" id="<<(int)id;
NodeSchedule *sch = new NodeSchedule(parent, this);
addSchedule(sch);
return sch;
}
Schedule *Node::schedule( long id ) const
{
switch ( id ) {
case ANYSCHEDULED: {
foreach ( Schedule *s, m_schedules ) {
if ( s->isScheduled() ) {
return s;
}
}
return 0;
}
case CURRENTSCHEDULE:
return m_currentSchedule;
case NOTSCHEDULED:
return 0;
case BASELINESCHEDULE: {
foreach ( Schedule *s, m_schedules ) {
if ( s->isBaselined() ) {
return s;
}
}
return 0;
}
default:
break;
}
return findSchedule( id );
}
Schedule *Node::findSchedule( long id ) const
{
return m_schedules.value( id );
}
Schedule *Node::findSchedule(const QString &name, const Schedule::Type type) {
QHash<long, Schedule*> it;
foreach (Schedule *sch, it) {
if (!sch->isDeleted() &&
sch->name() == name && sch->type() == type)
return sch;
}
return 0;
}
Schedule *Node::findSchedule(const QString &name) {
foreach (Schedule *sch, m_schedules) {
if (!sch->isDeleted() && sch->name() == name)
return sch;
}
return 0;
}
Schedule *Node::findSchedule(const Schedule::Type type) {
//debugPlan<<m_name<<" find type="<<type<<" nr="<<m_schedules.count();
QHash<long, Schedule*> hash;
foreach (Schedule *sch, hash) {
if (!sch->isDeleted() && sch->type() == type) {
return sch;
}
}
return 0;
}
void Node::setScheduleDeleted(long id, bool on) {
Schedule *ns = findSchedule(id);
if (ns == 0) {
errorPlan<<m_name<<" Could not find schedule with id="<<id;
} else {
ns->setDeleted(on);
}
}
void Node::setParentSchedule(Schedule *sch) {
Schedule *s = findSchedule(sch->id());
if (s) {
s->setParent(sch);
}
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->setParentSchedule(sch);
}
}
bool Node::calcCriticalPath(bool fromEnd) {
if (m_currentSchedule == 0)
return false;
//debugPlan<<m_name;
if (!isCritical()) {
return false;
}
if (!fromEnd && isStartNode()) {
m_currentSchedule->inCriticalPath = true;
return true;
}
if (fromEnd && isEndNode()) {
m_currentSchedule->inCriticalPath = true;
return true;
}
QListIterator<Relation*> pit(m_dependParentNodes);
while (pit.hasNext()) {
if (pit.next()->parent()->calcCriticalPath(fromEnd)) {
m_currentSchedule->inCriticalPath = true;
}
}
return m_currentSchedule->inCriticalPath;
}
void Node::calcFreeFloat() {
foreach ( Node *n, m_nodes ) {
n->calcFreeFloat();
}
return;
}
int Node::level() const {
const Node *n = parentNode();
return n ? n->level() + 1 : 0;
}
QString Node::generateWBSCode( QList<int> &indexes, bool sortable ) const {
//debugPlan<<m_name<<indexes;
if ( m_parent == 0 ) {
return QString();
}
indexes.insert( 0, m_parent->indexOf( this ) );
return m_parent->generateWBSCode( indexes, sortable );
}
QString Node::wbsCode(bool sortable) const {
//debugPlan<<m_name;
QList<int> indexes;
return generateWBSCode( indexes, sortable );
}
bool Node::isScheduled( long id ) const
{
Schedule *s = schedule( id );
return s != 0 && s->isScheduled();
}
void Node::setCurrentSchedule(long id) {
QListIterator<Node*> it = m_nodes;
while (it.hasNext()) {
it.next()->setCurrentSchedule(id);
}
//debugPlan<<m_name<<" id:"<<id<<"="<<m_currentSchedule;
}
void Node::setStartupCost(double cost)
{
m_startupCost = cost;
changed(StartupCost);
}
void Node::setStartupAccount(Account *acc)
{
//debugPlan<<m_name<<"="<<acc;
if ( m_startupAccount ) {
m_startupAccount->removeStartup( *this );
}
m_startupAccount = acc;
changed();
}
void Node::setShutdownCost(double cost)
{
m_shutdownCost = cost;
changed(ShutdownCost);
}
void Node::setShutdownAccount(Account *acc)
{
//debugPlan<<m_name<<"="<<acc;
if ( m_shutdownAccount ) {
m_shutdownAccount->removeShutdown( *this );
}
m_shutdownAccount = acc;
changed();
}
void Node::setRunningAccount(Account *acc)
{
//debugPlan<<m_name<<"="<<acc;
if ( m_runningAccount ) {
m_runningAccount->removeRunning( *this );
}
m_runningAccount = acc;
changed();
}
void Node::blockChanged(bool on)
{
m_blockChanged = on;
}
void Node::changed(Node *node, int property) {
if (m_blockChanged) {
return;
}
switch ( property) {
case Type:
case StartupCost:
case ShutdownCost:
case CompletionEntry:
case CompletionStarted:
case CompletionFinished:
case CompletionStartTime:
case CompletionFinishTime:
case CompletionPercentage:
case CompletionRemainingEffort:
case CompletionActualEffort:
case CompletionUsedEffort:
foreach ( Schedule *s, m_schedules ) {
s->clearPerformanceCache();
}
break;
default: break;
}
if (m_parent) {
m_parent->changed(node, property);
}
}
Duration Node::plannedEffort( const Resource *resource, long id, EffortCostCalculationType type ) const
{
Duration e;
foreach ( Node *n, m_nodes ) {
e += n->plannedEffort( resource, id, type );
}
return e;
}
Duration Node::plannedEffort( const Resource *resource, QDate date, long id, EffortCostCalculationType type ) const
{
Duration e;
foreach ( Node *n, m_nodes ) {
e += n->plannedEffort( resource, date, id, type );
}
return e;
}
Duration Node::plannedEffortTo( const Resource *resource, QDate date, long id, EffortCostCalculationType type ) const
{
Duration e;
foreach ( Node *n, m_nodes ) {
e += n->plannedEffortTo( resource, date, id, type );
}
return e;
}
EffortCost Node::plannedCost( long id, EffortCostCalculationType type ) const
{
EffortCost ec;
foreach ( Node *n, m_nodes ) {
ec += n->plannedCost( id, type );
}
return ec;
}
EffortCostMap Node::bcwsPrDay( long int id, EffortCostCalculationType type ) const
{
return const_cast<Node*>( this )->bcwsPrDay( id, type );
}
EffortCostMap Node::bcwsPrDay( long int id, EffortCostCalculationType type )
{
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostCache &ec = s->bcwsPrDayCache( type );
if ( ! ec.cached ) {
ec.effortcostmap = EffortCostMap();
foreach ( Node *n, m_nodes ) {
ec.effortcostmap += n->bcwsPrDay( id, type );
}
ec.cached = true;
}
return ec.effortcostmap;
}
EffortCostMap Node::bcwpPrDay( long int id, EffortCostCalculationType type ) const
{
return const_cast<Node*>( this )->bcwpPrDay( id, type);
}
EffortCostMap Node::bcwpPrDay( long int id, EffortCostCalculationType type )
{
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostCache &ec = s->bcwpPrDayCache( type );
if ( ! ec.cached ) {
ec.effortcostmap = EffortCostMap();
foreach ( Node *n, m_nodes ) {
ec.effortcostmap += n->bcwpPrDay( id, type );
}
ec.cached = true;
}
return ec.effortcostmap;
}
EffortCostMap Node::acwp( long id, EffortCostCalculationType type ) const
{
return const_cast<Node*>( this )->acwp( id, type );
}
EffortCostMap Node::acwp( long id, EffortCostCalculationType type )
{
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostCache &ec = s->acwpCache( type );
if ( ! ec.cached ) {
ec.effortcostmap = EffortCostMap();
foreach ( Node *n, m_nodes ) {
ec.effortcostmap += n->acwp( id, type );
}
ec.cached = true;
}
return ec.effortcostmap;
}
EffortCost Node::acwp( QDate date, long id ) const
{
EffortCost ec;
foreach ( Node *n, m_nodes ) {
ec += n->acwp( date, id );
}
return ec;
}
void Node::slotStandardWorktimeChanged(KPlato::StandardWorktime*)
{
//debugPlan<<m_estimate;
if ( m_estimate ) {
m_estimate->m_expectedCached = false;
m_estimate->m_optimisticCached = false;
m_estimate->m_pessimisticCached = false;
}
}
void Node::emitDocumentAdded( Node *node, Document *doc, int idx )
{
if ( m_parent ) {
m_parent->emitDocumentAdded( node, doc, idx );
}
}
void Node::emitDocumentRemoved( Node *node, Document *doc, int idx )
{
if ( m_parent ) {
m_parent->emitDocumentRemoved( node, doc, idx );
}
}
void Node::emitDocumentChanged( Node *node, Document *doc, int idx )
{
if ( m_parent ) {
m_parent->emitDocumentChanged( node, doc, idx );
}
}
////////////////////////// Estimate /////////////////////////////////
Estimate::Estimate( Node *parent )
: m_parent( parent )
{
m_pertCached = false;
setUnit( Duration::Unit_h );
setExpectedEstimate( 8.0 );
setPessimisticEstimate( 8.0 );
setOptimisticEstimate( 8.0 );
m_type = Type_Effort;
m_calendar = 0;
m_risktype = Risk_None;
}
Estimate::Estimate(const Estimate &estimate, Node *parent)
: m_parent( parent )
{
copy( estimate );
}
Estimate::~Estimate()
{
}
void Estimate::clear()
{
m_pertCached = false;
setExpectedEstimate( 0.0 );
setPessimisticEstimate( 0.0 );
setOptimisticEstimate( 0.0 );
m_type = Type_Effort;
m_calendar = 0;
m_risktype = Risk_None;
m_unit = Duration::Unit_h;
changed();
}
Estimate &Estimate::operator=( const Estimate &estimate )
{
copy( estimate );
return *this;
}
void Estimate::copy( const Estimate &estimate )
{
//m_parent = 0; // don't touch
m_expectedEstimate = estimate.m_expectedEstimate;
m_optimisticEstimate = estimate.m_optimisticEstimate;
m_pessimisticEstimate = estimate.m_pessimisticEstimate;
m_expectedValue = estimate.m_expectedValue;
m_optimisticValue = estimate.m_optimisticValue;
m_pessimisticValue = estimate.m_pessimisticValue;
m_expectedCached = estimate.m_expectedCached;
m_optimisticCached = estimate.m_optimisticCached;
m_pessimisticCached = estimate.m_pessimisticCached;
m_pertExpected = estimate.m_pertExpected;
m_pertCached = estimate.m_pertCached;
m_type = estimate.m_type;
m_calendar = estimate.m_calendar;
m_risktype = estimate.m_risktype;
m_unit = estimate.m_unit;
changed();
}
double Estimate::variance() const
{
double d = deviation();
return d * d;
}
double Estimate::variance( Duration::Unit unit ) const
{
double d = deviation( unit );
return d * d;
}
double Estimate::deviation() const
{
return ( m_pessimisticEstimate - m_optimisticEstimate ) / 6;
}
double Estimate::deviation( Duration::Unit unit ) const
{
if ( unit == m_unit ) {
return deviation();
}
double p = pessimisticValue().toDouble( unit );
double o = optimisticValue().toDouble( unit );
double v = ( p - o ) / 6;
return v;
}
Duration Estimate::pertExpected() const {
if (m_risktype == Risk_Low) {
if ( ! m_pertCached ) {
m_pertExpected = (optimisticValue() + pessimisticValue() + (expectedValue()*4))/6;
m_pertCached = true;
}
return m_pertExpected;
} else if (m_risktype == Risk_High) {
if ( ! m_pertCached ) {
m_pertExpected = (optimisticValue() + (pessimisticValue()*2) + (expectedValue()*4))/7;
m_pertCached = true;
}
return m_pertExpected;
}
return expectedValue(); // risk==none
}
Duration Estimate::pertOptimistic() const {
if (m_risktype != Risk_None) {
return pertExpected() - Duration( variance( Duration::Unit_ms ) );
}
return optimisticValue();
}
Duration Estimate::pertPessimistic() const {
if (m_risktype != Risk_None) {
return pertExpected() + Duration( variance( Duration::Unit_ms ) );
}
return pessimisticValue();
}
Duration Estimate::value(int valueType, bool pert) const {
if (valueType == Estimate::Use_Expected) {
return pert ? pertExpected() : expectedValue();
} else if (valueType == Estimate::Use_Optimistic) {
return pert ? pertOptimistic() : optimisticValue();
} else if (valueType == Estimate::Use_Pessimistic) {
return pert ? pertPessimistic() : pessimisticValue();
}
return expectedValue();
}
void Estimate::setUnit( Duration::Unit unit )
{
m_unit = unit;
m_expectedCached = false;
m_optimisticCached = false;
m_pessimisticCached = false;
m_pertCached = false;
changed();
}
bool Estimate::load(KoXmlElement &element, XMLLoaderObject &status) {
setType(element.attribute("type"));
setRisktype(element.attribute("risk"));
if ( status.version() <= "0.6" ) {
m_unit = (Duration::Unit)(element.attribute("display-unit", QString().number(Duration::Unit_h) ).toInt());
QList<qint64> s = status.project().standardWorktime()->scales();
m_expectedEstimate = scale( Duration::fromString(element.attribute("expected")), m_unit, s );
m_optimisticEstimate = scale( Duration::fromString(element.attribute("optimistic")), m_unit, s );
m_pessimisticEstimate = scale( Duration::fromString(element.attribute("pessimistic")), m_unit, s );
} else {
if ( status.version() <= "0.6.2" ) {
// 0 pos in unit is now Unit_Y, so add 3 to get the correct new unit
m_unit = (Duration::Unit)(element.attribute("unit", QString().number(Duration::Unit_ms - 3) ).toInt() + 3);
} else {
m_unit = Duration::unitFromString( element.attribute( "unit" ) );
}
m_expectedEstimate = element.attribute("expected", "0.0").toDouble();
m_optimisticEstimate = element.attribute("optimistic", "0.0").toDouble();
m_pessimisticEstimate = element.attribute("pessimistic", "0.0").toDouble();
m_calendar = status.project().findCalendar(element.attribute("calendar-id"));
}
return true;
}
void Estimate::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("estimate");
element.appendChild(me);
me.setAttribute("expected", QString::number(m_expectedEstimate));
me.setAttribute("optimistic", QString::number(m_optimisticEstimate));
me.setAttribute("pessimistic", QString::number(m_pessimisticEstimate));
me.setAttribute("type", typeToString());
if ( m_calendar ) {
me.setAttribute("calendar-id", m_calendar->id() );
}
me.setAttribute("risk", risktypeToString());
me.setAttribute("unit", Duration::unitToString( m_unit ) );
}
QString Estimate::typeToString( bool trans ) const {
return typeToStringList( trans ).at( m_type );
}
QString Estimate::typeToString( Estimate::Type typ, bool trans )
{
return typeToStringList( trans ).value( typ );
}
QStringList Estimate::typeToStringList( bool trans ) {
return QStringList()
<< (trans ? i18n("Effort") : QString("Effort"))
<< (trans ? i18n("Duration") : QString("Duration"));
}
void Estimate::setType(Type type)
{
m_type = type;
m_expectedCached = false;
m_optimisticCached = false;
m_pessimisticCached = false;
m_pertCached = false;
changed();
}
void Estimate::setType(const QString& type) {
if (type == "Effort")
setType(Type_Effort);
else if (type == "Duration" || /*old format*/ type == "FixedDuration")
setType(Type_Duration);
else if (/*old format*/type == "Length")
setType(Type_Duration);
else if (type == "Type_FixedDuration") // Typo, keep old xml files working
setType(Type_Duration);
else
setType(Type_Effort); // default
}
QString Estimate::risktypeToString( bool trans ) const {
return risktypeToStringList( trans ).at( m_risktype );
}
QStringList Estimate::risktypeToStringList( bool trans ) {
return QStringList()
<< (trans ? i18n("None") : QString("None"))
<< (trans ? i18n("Low") : QString("Low"))
<< (trans ? i18n("High") : QString("High"));
}
void Estimate::setRisktype(const QString& type) {
if (type == "High")
setRisktype(Risk_High);
else if (type == "Low")
setRisktype(Risk_Low);
else
setRisktype(Risk_None); // default
}
void Estimate::setRisktype(Risktype type)
{
m_pertCached = false;
m_risktype = type;
changed();
}
void Estimate::setCalendar( Calendar *calendar )
{
m_calendar = calendar;
m_expectedCached = false;
m_optimisticCached = false;
m_pessimisticCached = false;
m_pertCached = false;
changed();
}
void Estimate::setExpectedEstimate( double value)
{
m_expectedEstimate = value;
m_expectedCached = false;
m_pertCached = false;
changed();
}
void Estimate::setOptimisticEstimate( double value )
{
m_optimisticEstimate = value;
m_optimisticCached = false;
m_pertCached = false;
changed();
}
void Estimate::setPessimisticEstimate( double value )
{
m_pessimisticEstimate = value;
m_pessimisticCached = false;
m_pertCached = false;
changed();
}
void Estimate::setOptimisticRatio(int percent)
{
int p = percent>0 ? -percent : percent;
m_optimisticValue = expectedValue()*(100+p)/100;
m_optimisticEstimate = scale( m_optimisticValue, m_unit, scales() );
m_optimisticCached = true;
m_pertCached = false;
changed();
}
int Estimate::optimisticRatio() const {
if (m_expectedEstimate == 0.0)
return 0;
return (int)((optimisticValue()*100)/expectedValue())-100;
}
void Estimate::setPessimisticRatio(int percent)
{
int p = percent<0 ? -percent : percent;
m_pessimisticValue = expectedValue()*(100+p)/100;
m_pessimisticEstimate = scale( m_pessimisticValue, m_unit, scales() );
m_pessimisticCached = true;
m_pertCached = false;
changed();
}
int Estimate::pessimisticRatio() const {
if (m_expectedEstimate == 0.0)
return 0;
return (int)((pessimisticValue()*100)/expectedValue())-100;
}
// internal
void Estimate::setOptimisticValue()
{
m_optimisticValue = scale( m_optimisticEstimate, m_unit, scales() );
m_optimisticCached = true;
m_pertCached = false;
}
// internal
void Estimate::setExpectedValue()
{
m_expectedValue = scale( m_expectedEstimate, m_unit, scales() );
m_expectedCached = true;
m_pertCached = false;
}
// internal
void Estimate::setPessimisticValue()
{
m_pessimisticValue = scale( m_pessimisticEstimate, m_unit, scales() );
m_pessimisticCached = true;
m_pertCached = false;
}
Duration Estimate::optimisticValue() const
{
if ( ! m_optimisticCached ) {
const_cast<Estimate*>(this)->setOptimisticValue();
}
return m_optimisticValue;
}
Duration Estimate::pessimisticValue() const
{
if ( ! m_pessimisticCached ) {
const_cast<Estimate*>(this)->setPessimisticValue();
}
return m_pessimisticValue;
}
Duration Estimate::expectedValue() const
{
if ( ! m_expectedCached ) {
const_cast<Estimate*>(this)->setExpectedValue();
}
return m_expectedValue;
}
double Estimate::scale( const Duration &value, Duration::Unit unit, const QList<qint64> &scales )
{
//debugPlan<<value.toDouble( unit )<<","<<unit<<scales;
QList<qint64> lst = scales;
switch ( lst.count() ) {
case Duration::Unit_Y:
lst << (qint64)(365 * 24) * 60 * 60 * 1000; // add milliseconds in a year
Q_FALLTHROUGH();
case Duration::Unit_M:
lst << (qint64)(30 * 24) * 60 * 60 * 1000; // add milliseconds in a month
Q_FALLTHROUGH();
case Duration::Unit_w:
lst << (qint64)(7 * 24) * 60 * 60 * 1000; // add milliseconds in a week
Q_FALLTHROUGH();
case Duration::Unit_d:
lst << 24 * 60 * 60 * 1000; // add milliseconds in day
Q_FALLTHROUGH();
case Duration::Unit_h:
lst << 60 * 60 * 1000; // add milliseconds in hour
case Duration::Unit_m:
lst << 60 * 1000; // add milliseconds in minute
Q_FALLTHROUGH();
case Duration::Unit_s:
lst << 1000; // add milliseconds in second
Q_FALLTHROUGH();
case Duration::Unit_ms:
lst << 1; // add milliseconds in a millisecond
Q_FALLTHROUGH();
default:
break;
}
double v = ( double )( value.milliseconds() );
v /= lst[ unit ];
//debugPlan<<value.toString()<<","<<unit<<"="<<v;
return v;
}
Duration Estimate::scale( double value, Duration::Unit unit, const QList<qint64> &scales )
{
//debugPlan<<value<<","<<unit<<scales;
QList<qint64> lst = scales;
switch ( lst.count() ) {
case Duration::Unit_Y:
lst << (qint64)(365 * 24) * 60 * 60 * 1000; // add milliseconds in a year
Q_FALLTHROUGH();
case Duration::Unit_M:
lst << (qint64)(30 * 24) * 60 * 60 * 1000; // add milliseconds in a month
Q_FALLTHROUGH();
case Duration::Unit_w:
lst << (qint64)(7 * 24) * 60 * 60 * 1000; // add milliseconds in a week
Q_FALLTHROUGH();
case Duration::Unit_d:
lst << 24 * 60 * 60 * 1000; // add milliseconds in day
Q_FALLTHROUGH();
case Duration::Unit_h:
lst << 60 * 60 * 1000; // add milliseconds in hour
Q_FALLTHROUGH();
case Duration::Unit_m:
lst << 60 * 1000; // add milliseconds in minute
Q_FALLTHROUGH();
case Duration::Unit_s:
lst << 1000; // add milliseconds in second
Q_FALLTHROUGH();
case Duration::Unit_ms:
lst << 1; // add milliseconds in a millisecond
Q_FALLTHROUGH();
default:
break;
}
qint64 v = ( qint64 )( value * lst[ unit ] );
//debugPlan<<value<<","<<unit<<"="<<v;
return Duration( v, Duration::Unit_ms );
}
//static
QList<qint64> Estimate::defaultScales()
{
QList<qint64> lst;
lst << (qint64)(365 * 24) * 60 * 60 * 1000 // add milliseconds in a year
<< (qint64)(30 * 24) * 60 * 60 * 1000 // add milliseconds in a month
<< (qint64)(7 * 24) * 60 * 60 * 1000 // add milliseconds in a week
<< 24 * 60 * 60 * 1000 // add milliseconds in day
<< 60 * 60 * 1000 // add milliseconds in hour
<< 60 * 1000 // add milliseconds in minute
<< 1000 // add milliseconds in second
<< 1; // add milliseconds in a millisecond
return lst;
}
QList<qint64> Estimate::scales() const
{
QList<qint64> s;
if ( m_type == Type_Duration && m_calendar == 0 ) {
return s; // Use default scaling ( 24h a day...)
}
if ( m_parent == 0 ) {
return s;
}
Project *p = static_cast<Project*>( m_parent->projectNode() );
if ( p == 0 ) {
return s;
}
s << p->standardWorktime()->scales();
return s;
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptpackage.cpp b/src/libs/kernel/kptpackage.cpp
index 3a67ffe6..2f545000 100644
--- a/src/libs/kernel/kptpackage.cpp
+++ b/src/libs/kernel/kptpackage.cpp
@@ -1,29 +1,30 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+// clazy:excludeall=qstring-arg
#include "kptpackage.h"
using namespace KPlato;
Package::Package()
: task( 0 ),
toTask( 0 )
{
}
diff --git a/src/libs/kernel/kptproject.cpp b/src/libs/kernel/kptproject.cpp
index 88f97541..793f02d9 100644
--- a/src/libs/kernel/kptproject.cpp
+++ b/src/libs/kernel/kptproject.cpp
@@ -1,2940 +1,2941 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
Copyright (C) 2004 - 2010, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2007 Florian Piquemal <flotueur@yahoo.fr>
Copyright (C) 2007 Alexis Ménard <darktears31@gmail.com>
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 "kptproject.h"
#include "kptlocale.h"
#include "kptappointment.h"
#include "kpttask.h"
#include "kptdatetime.h"
#include "kpteffortcostmap.h"
#include "kptschedule.h"
#include "kptwbsdefinition.h"
#include "kptxmlloaderobject.h"
#include "kptschedulerplugin.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <krandom.h>
#include <KFormat>
#include <KLocalizedString>
#include <QDateTime>
#include <QLocale>
namespace KPlato
{
Project::Project( Node *parent )
: Node( parent ),
m_accounts( *this ),
m_defaultCalendar( 0 ),
m_config( &emptyConfig ),
m_schedulerPlugins(),
m_useSharedResources(false),
m_sharedResourcesLoaded(false),
m_loadProjectsAtStartup(false)
{
//debugPlan<<"("<<this<<")";
init();
}
Project::Project( ConfigBase &config, Node *parent )
: Node( parent ),
m_accounts( *this ),
m_defaultCalendar( 0 ),
m_config( &config ),
m_schedulerPlugins(),
m_useSharedResources(false),
m_sharedResourcesLoaded(false),
m_loadProjectsAtStartup(false)
{
debugPlan<<"("<<this<<")";
init();
m_config->setDefaultValues(*this);
}
Project::Project( ConfigBase &config, bool useDefaultValues, Node *parent )
: Node( parent ),
m_accounts( *this ),
m_defaultCalendar( 0 ),
m_config( &config ),
m_schedulerPlugins(),
m_useSharedResources(false),
m_sharedResourcesLoaded(false),
m_loadProjectsAtStartup(false)
{
debugPlan<<"("<<this<<")";
init();
if (useDefaultValues) {
m_config->setDefaultValues(*this);
}
}
void Project::init()
{
m_refCount = 1; // always used by creator
m_constraint = Node::MustStartOn;
m_standardWorktime = new StandardWorktime();
m_timeZone = QTimeZone::systemTimeZone(); // local timezone as default
//debugPlan<<m_timeZone;
if ( m_parent == 0 ) {
// set sensible defaults for a project wo parent
m_constraintStartTime = DateTime( QDate::currentDate() );
m_constraintEndTime = m_constraintStartTime.addYears( 2 );
}
}
void Project::deref()
{
--m_refCount;
Q_ASSERT( m_refCount >= 0 );
if ( m_refCount <= 0 ) {
emit aboutToBeDeleted();
deleteLater();
}
}
Project::~Project()
{
debugPlan<<"("<<this<<")";
disconnect();
for(Node *n : qAsConst(nodeIdDict)) {
n->blockChanged();
}
for (Resource *r : qAsConst(resourceIdDict)) {
r->blockChanged();
}
for (ResourceGroup *g : qAsConst(resourceGroupIdDict)) {
g->blockChanged();
}
delete m_standardWorktime;
while ( !m_resourceGroups.isEmpty() )
delete m_resourceGroups.takeFirst();
while ( !m_calendars.isEmpty() )
delete m_calendars.takeFirst();
while ( !m_managers.isEmpty() )
delete m_managers.takeFirst();
m_config = 0; //not mine, don't delete
}
int Project::type() const { return Node::Type_Project; }
void Project::generateUniqueNodeIds()
{
foreach ( Node *n, nodeIdDict ) {
debugPlan<<n->name()<<"old"<<n->id();
QString uid = uniqueNodeId();
nodeIdDict.remove( n->id() );
n->setId( uid );
nodeIdDict[ uid ] = n;
debugPlan<<n->name()<<"new"<<n->id();
}
}
void Project::generateUniqueIds()
{
generateUniqueNodeIds();
foreach ( ResourceGroup *g, resourceGroupIdDict ) {
if (g->isShared()) {
continue;
}
resourceGroupIdDict.remove( g->id() );
g->setId( uniqueResourceGroupId() );
resourceGroupIdDict[ g->id() ] = g;
}
foreach ( Resource *r, resourceIdDict ) {
if (r->isShared()) {
continue;
}
resourceIdDict.remove( r->id() );
r->setId( uniqueResourceId() );
resourceIdDict[ r->id() ] = r;
}
foreach ( Calendar *c, calendarIdDict ) {
if (c->isShared()) {
continue;
}
calendarIdDict.remove( c->id() );
c->setId( uniqueCalendarId() );
calendarIdDict[ c->id() ] = c;
}
}
void Project::calculate( Schedule *schedule, const DateTime &dt )
{
if ( schedule == 0 ) {
errorPlan << "Schedule == 0, cannot calculate";
return ;
}
m_currentSchedule = schedule;
calculate( dt );
}
void Project::calculate( const DateTime &dt )
{
if ( m_currentSchedule == 0 ) {
errorPlan << "No current schedule to calculate";
return ;
}
stopcalculation = false;
QLocale locale;
DateTime time = dt.isValid() ? dt : DateTime( QDateTime::currentDateTime() );
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
Estimate::Use estType = ( Estimate::Use ) cs->type();
if ( type() == Type_Project ) {
cs->setPhaseName( 0, i18n( "Init" ) );
cs->logInfo( i18n( "Schedule project from: %1", locale.toString(dt, QLocale::ShortFormat) ), 0 );
initiateCalculation( *cs );
initiateCalculationLists( *cs ); // must be after initiateCalculation() !!
propagateEarliestStart( time );
// Calculate lateFinish from time. If a task has started, remainingEffort is used.
cs->setPhaseName( 1, i18nc( "Schedule project forward", "Forward" ) );
cs->logInfo( i18n( "Calculate finish" ), 1 );
cs->lateFinish = calculateForward( estType );
cs->lateFinish = checkEndConstraints( cs->lateFinish );
propagateLatestFinish( cs->lateFinish );
// Calculate earlyFinish. If a task has started, remainingEffort is used.
cs->setPhaseName( 2, i18nc( "Schedule project backward","Backward" ) );
cs->logInfo( i18n( "Calculate start" ), 2 );
calculateBackward( estType );
// Schedule. If a task has started, remainingEffort is used and appointments are copied from parent
cs->setPhaseName( 3, i18n( "Schedule" ) );
cs->logInfo( i18n( "Schedule tasks forward" ), 3 );
cs->endTime = scheduleForward( cs->startTime, estType );
cs->logInfo( i18n( "Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat) ), 3 );
if ( cs->endTime > m_constraintEndTime ) {
cs->logError( i18n( "Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 );
} else if ( cs->endTime == m_constraintEndTime ) {
cs->logWarning( i18n( "Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 );
} else {
cs->logInfo( i18n( "Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 );
}
calcCriticalPath( false );
calcResourceOverbooked();
cs->notScheduled = false;
calcFreeFloat();
emit scheduleChanged( cs );
emit projectChanged();
} else if ( type() == Type_Subproject ) {
warnPlan << "Subprojects not implemented";
} else {
errorPlan << "Illegal project type: " << type();
}
}
void Project::calculate( ScheduleManager &sm )
{
emit sigCalculationStarted( this, &sm );
sm.setScheduling( true );
m_progress = 0;
int nodes = 0;
foreach ( Node *n, nodeIdDict ) {
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
nodes++;
}
}
int maxprogress = nodes * 3;
if ( sm.recalculate() ) {
emit maxProgress( maxprogress );
sm.setMaxProgress( maxprogress );
incProgress();
if ( sm.parentManager() ) {
sm.expected()->startTime = sm.parentManager()->expected()->startTime;
sm.expected()->earlyStart = sm.parentManager()->expected()->earlyStart;
}
incProgress();
calculate( sm.expected(), sm.recalculateFrom() );
} else {
emit maxProgress( maxprogress );
sm.setMaxProgress( maxprogress );
calculate( sm.expected() );
emit scheduleChanged( sm.expected() );
setCurrentSchedule( sm.expected()->id() );
}
emit sigProgress( maxprogress );
emit sigCalculationFinished( this, &sm );
emit scheduleManagerChanged( &sm );
emit projectCalculated( &sm );
emit projectChanged();
sm.setScheduling( false );
}
void Project::calculate( Schedule *schedule )
{
if ( schedule == 0 ) {
errorPlan << "Schedule == 0, cannot calculate";
return ;
}
m_currentSchedule = schedule;
calculate();
}
void Project::calculate()
{
if ( m_currentSchedule == 0 ) {
errorPlan << "No current schedule to calculate";
return ;
}
stopcalculation = false;
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
bool backwards = false;
if ( cs->manager() ) {
backwards = cs->manager()->schedulingDirection();
}
QLocale locale;
Estimate::Use estType = ( Estimate::Use ) cs->type();
if ( type() == Type_Project ) {
QTime timer; timer.start();
initiateCalculation( *cs );
initiateCalculationLists( *cs ); // must be after initiateCalculation() !!
if ( ! backwards ) {
cs->setPhaseName( 0, i18n( "Init" ) );
cs->logInfo( i18n( "Schedule project forward from: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 0 );
cs->startTime = m_constraintStartTime;
cs->earlyStart = m_constraintStartTime;
// Calculate from start time
propagateEarliestStart( cs->earlyStart );
cs->setPhaseName( 1, i18nc( "Schedule project forward", "Forward" ) );
cs->logInfo( i18n( "Calculate late finish" ), 1 );
cs->lateFinish = calculateForward( estType );
// cs->lateFinish = checkEndConstraints( cs->lateFinish );
cs->logInfo( i18n( "Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat) ), 1 );
propagateLatestFinish( cs->lateFinish );
cs->setPhaseName( 2, i18nc( "Schedule project backward", "Backward" ) );
cs->logInfo( i18n( "Calculate early start" ), 2 );
calculateBackward( estType );
cs->setPhaseName( 3, i18n( "Schedule" ) );
cs->logInfo( i18n( "Schedule tasks forward" ), 3 );
cs->endTime = scheduleForward( cs->startTime, estType );
cs->duration = cs->endTime - cs->startTime;
cs->logInfo( i18n( "Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat) ), 3 );
if ( cs->endTime > m_constraintEndTime ) {
cs->constraintError = true;
cs->logError( i18n( "Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 );
} else if ( cs->endTime == m_constraintEndTime ) {
cs->logWarning( i18n( "Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 );
} else {
cs->logInfo( i18n( "Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 3 );
}
calcCriticalPath( false );
} else {
cs->setPhaseName( 0, i18n( "Init" ) );
cs->logInfo( i18n( "Schedule project backward from: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat) ), 0 );
// Calculate from end time
propagateLatestFinish( m_constraintEndTime );
cs->setPhaseName( 1, i18nc( "Schedule project backward", "Backward" ) );
cs->logInfo( i18n( "Calculate early start" ), 1 );
cs->earlyStart = calculateBackward( estType );
// cs->earlyStart = checkStartConstraints( cs->earlyStart );
cs->logInfo( i18n( "Early start calculated: %1", locale.toString(cs->earlyStart, QLocale::ShortFormat) ), 1 );
propagateEarliestStart( cs->earlyStart );
cs->setPhaseName( 2, i18nc( "Schedule project forward", "Forward" ) );
cs->logInfo( i18n( "Calculate late finish" ), 2 );
cs->lateFinish = qMax( m_constraintEndTime, calculateForward( estType ) );
cs->logInfo( i18n( "Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat) ), 2 );
cs->setPhaseName( 3, i18n( "Schedule" ) );
cs->logInfo( i18n( "Schedule tasks backward" ), 3 );
cs->startTime = scheduleBackward( cs->lateFinish, estType );
cs->endTime = cs->startTime;
foreach ( Node *n, allNodes() ) {
if ( n->type() == Type_Task || n->type() == Type_Milestone ) {
DateTime e = n->endTime( cs->id() );
if ( cs->endTime < e ) {
cs->endTime = e;
}
}
}
if ( cs->endTime > m_constraintEndTime ) {
cs->constraintError = true;
cs->logError( i18n( "Failed to finish project within target time" ), 3 );
}
cs->duration = cs->endTime - cs->startTime;
cs->logInfo( i18n( "Scheduled start: %1, target time: %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 );
if ( cs->startTime < m_constraintStartTime ) {
cs->constraintError = true;
cs->logError( i18n( "Must start project early in order to finish in time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 );
} else if ( cs->startTime == m_constraintStartTime ) {
cs->logWarning( i18n( "Start project exactly on time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 );
} else {
cs->logInfo( i18n( "Can start project later than time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat) ), 3 );
}
calcCriticalPath( true );
}
cs->logInfo( i18n( "Calculation took: %1", KFormat().formatDuration( timer.elapsed() ) ) );
// TODO: fix this uncertainty, manager should *always* be available
if (cs->manager()) {
finishCalculation(*(cs->manager()));
}
} else if ( type() == Type_Subproject ) {
warnPlan << "Subprojects not implemented";
} else {
errorPlan << "Illegal project type: " << type();
}
}
void Project::finishCalculation( ScheduleManager &sm )
{
MainSchedule *cs = sm.expected();
if (nodeIdDict.count() > 1) {
// calculate project duration
cs->startTime = m_constraintEndTime;
cs->endTime = m_constraintStartTime;
for (const Node *n : qAsConst(nodeIdDict)) {
cs->startTime = qMin(cs->startTime, n->startTime(cs->id()));
cs->endTime = qMax(cs->endTime, n->endTime(cs->id()));
}
cs->duration = cs->endTime - cs->startTime;
}
calcCriticalPath( false );
calcResourceOverbooked();
cs->notScheduled = false;
calcFreeFloat();
emit scheduleChanged( cs );
emit projectChanged();
debugPlan<<cs->startTime<<cs->endTime<<"-------------------------";
}
void Project::setProgress( int progress, ScheduleManager *sm )
{
m_progress = progress;
if ( sm ) {
sm->setProgress( progress );
}
emit sigProgress( progress );
}
void Project::setMaxProgress( int max, ScheduleManager *sm )
{
if ( sm ) {
sm->setMaxProgress( max );
}
emitMaxProgress( max );
}
void Project::incProgress()
{
m_progress += 1;
emit sigProgress( m_progress );
}
void Project::emitMaxProgress( int value )
{
emit maxProgress( value );
}
bool Project::calcCriticalPath( bool fromEnd )
{
//debugPlan;
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
if ( cs == 0 ) {
return false;
}
if ( fromEnd ) {
QListIterator<Node*> startnodes = cs->startNodes();
while ( startnodes.hasNext() ) {
startnodes.next() ->calcCriticalPath( fromEnd );
}
} else {
QListIterator<Node*> endnodes = cs->endNodes();
while ( endnodes.hasNext() ) {
endnodes.next() ->calcCriticalPath( fromEnd );
}
}
calcCriticalPathList( cs );
return false;
}
void Project::calcCriticalPathList( MainSchedule *cs )
{
//debugPlan<<m_name<<", "<<cs->name();
cs->clearCriticalPathList();
foreach ( Node *n, allNodes() ) {
if ( n->numDependParentNodes() == 0 && n->inCriticalPath( cs->id() ) ) {
cs->addCriticalPath();
cs->addCriticalPathNode( n );
calcCriticalPathList( cs, n );
}
}
cs->criticalPathListCached = true;
//debugPlan<<*(criticalPathList( cs->id() ));
}
void Project::calcCriticalPathList( MainSchedule *cs, Node *node )
{
//debugPlan<<node->name()<<", "<<cs->id();
bool newPath = false;
QList<Node*> lst = *( cs->currentCriticalPath() );
foreach ( Relation *r, node->dependChildNodes() ) {
if ( r->child()->inCriticalPath( cs->id() ) ) {
if ( newPath ) {
cs->addCriticalPath( &lst );
//debugPlan<<node->name()<<" new path";
}
cs->addCriticalPathNode( r->child() );
calcCriticalPathList( cs, r->child() );
newPath = true;
}
}
}
const QList< QList<Node*> > *Project::criticalPathList( long id )
{
Schedule *s = schedule( id );
if ( s == 0 ) {
//debugPlan<<"No schedule with id="<<id;
return 0;
}
MainSchedule *ms = static_cast<MainSchedule*>( s );
if ( ! ms->criticalPathListCached ) {
initiateCalculationLists( *ms );
calcCriticalPathList( ms );
}
return ms->criticalPathList();
}
QList<Node*> Project::criticalPath( long id, int index )
{
Schedule *s = schedule( id );
if ( s == 0 ) {
//debugPlan<<"No schedule with id="<<id;
return QList<Node*>();
}
MainSchedule *ms = static_cast<MainSchedule*>( s );
if ( ! ms->criticalPathListCached ) {
initiateCalculationLists( *ms );
calcCriticalPathList( ms );
}
return ms->criticalPath( index );
}
DateTime Project::startTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->startTime : m_constraintStartTime;
}
DateTime Project::endTime( long id ) const
{
Schedule *s = schedule( id );
return s ? s->endTime : m_constraintEndTime;
}
Duration Project::duration( long id ) const
{
Schedule *s = schedule( id );
return s ? s->duration : Duration::zeroDuration;
}
Duration *Project::getRandomDuration()
{
return 0L;
}
DateTime Project::checkStartConstraints( const DateTime &dt ) const
{
DateTime t = dt;
foreach ( Node *n, nodeIdDict ) {
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
switch ( n->constraint() ) {
case Node::FixedInterval:
case Node::StartNotEarlier:
case Node::MustStartOn:
t = qMin( t, qMax( n->constraintStartTime(), m_constraintStartTime ) );
break;
default: break;
}
}
}
return t;
}
DateTime Project::checkEndConstraints( const DateTime &dt ) const
{
DateTime t = dt;
foreach ( Node *n, nodeIdDict ) {
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
switch ( n->constraint() ) {
case Node::FixedInterval:
case Node::FinishNotLater:
case Node::MustFinishOn:
t = qMax( t, qMin( n->constraintEndTime(), m_constraintEndTime ) );
break;
default: break;
}
}
}
return t;
}
#ifndef PLAN_NLOGDEBUG
bool Project::checkParent( Node *n, const QList<Node*> &list, QList<Relation*> &checked )
{
if ( n->isStartNode() ) {
debugPlan<<n<<"start node"<<list;
return true;
}
debugPlan<<"Check:"<<n<<":"<<checked.count()<<":"<<list;
if ( list.contains( n ) ) {
debugPlan<<"Failed:"<<n<<":"<<list;
return false;
}
QList<Node*> lst = list;
lst << n;
foreach ( Relation *r, n->dependParentNodes() ) {
if ( checked.contains( r ) ) {
debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
continue;
}
checked << r;
if ( ! checkParent( r->parent(), lst, checked ) ) {
return false;
}
}
Task *t = static_cast<Task*>( n );
foreach ( Relation *r, t->parentProxyRelations() ) {
if ( checked.contains( r ) ) {
debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
continue;
}
checked << r;
debugPlan<<"Proxy:"<<n<<":"<<r->parent()<<":"<<lst;
if ( ! checkParent( r->parent(), lst, checked ) ) {
return false;
}
}
return true;
}
bool Project::checkChildren( Node *n, const QList<Node*> &list, QList<Relation*> &checked )
{
if ( n->isEndNode() ) {
debugPlan<<n<<"end node"<<list;
return true;
}
debugPlan<<"Check:"<<n<<":"<<checked.count()<<":"<<list;
if ( list.contains( n ) ) {
debugPlan<<"Failed:"<<n<<":"<<list;
return false;
}
QList<Node*> lst = list;
lst << n;
foreach ( Relation *r, n->dependChildNodes() ) {
if ( checked.contains( r ) ) {
debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
continue;
}
checked << r;
if ( ! checkChildren( r->child(), lst, checked ) ) {
return false;
}
}
Task *t = static_cast<Task*>( n );
foreach ( Relation *r, t->childProxyRelations() ) {
if ( checked.contains( r ) ) {
debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
continue;
}
debugPlan<<"Proxy:"<<n<<":"<<r->parent()<<":"<<lst;
checked << r;
if ( ! checkChildren( r->child(), lst, checked ) ) {
return false;
}
}
return true;
}
#endif
void Project::tasksForward()
{
m_hardConstraints.clear();
m_softConstraints.clear();
m_terminalNodes.clear();
foreach ( Task *t, allTasks() ) {
switch ( t->constraint() ) {
case Node::MustStartOn:
case Node::MustFinishOn:
case Node::FixedInterval:
m_hardConstraints.append( t );
break;
case Node::StartNotEarlier:
case Node::FinishNotLater:
m_softConstraints.append( t );
break;
default:
if ( t->isEndNode() ) {
m_terminalNodes.append( t );
}
break;
}
}
#ifndef PLAN_NLOGDEBUG
debugPlan<<"End nodes:"<<m_terminalNodes;
foreach ( Node* n, m_terminalNodes ) {
QList<Node*> lst;
QList<Relation*> rel;
Q_ASSERT( checkParent( n, lst, rel ) ); Q_UNUSED( n );
}
#endif
}
void Project::tasksBackward()
{
m_hardConstraints.clear();
m_softConstraints.clear();
m_terminalNodes.clear();
foreach ( Task *t, allTasks() ) {
switch ( t->constraint() ) {
case Node::MustStartOn:
case Node::MustFinishOn:
case Node::FixedInterval:
m_hardConstraints.append( t );
break;
case Node::StartNotEarlier:
case Node::FinishNotLater:
m_softConstraints.append( t );
break;
default:
if ( t->isStartNode() ) {
m_terminalNodes.append( t );
}
break;
}
}
#ifndef PLAN_NLOGDEBUG
debugPlan<<"Start nodes:"<<m_terminalNodes;
foreach ( Node* n, m_terminalNodes ) {
QList<Node*> lst;
QList<Relation*> rel;
Q_ASSERT( checkChildren( n, lst, rel ) ); Q_UNUSED( n );
}
#endif
}
DateTime Project::calculateForward( int use )
{
//debugPlan<<m_name;
DateTime finish;
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
if ( cs == 0 ) {
return finish;
}
if ( type() == Node::Type_Project ) {
QTime timer;
timer.start();
cs->logInfo( i18n( "Start calculating forward" ) );
m_visitedForward = true;
if ( ! m_visitedBackward ) {
// setup tasks
tasksForward();
// Do all hard constrained first
foreach ( Node *n, m_hardConstraints ) {
cs->logDebug( "Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateEarlyFinish( use ); // do not do predeccessors
if ( time > finish ) {
finish = time;
}
}
// do the predeccessors
foreach ( Node *n, m_hardConstraints ) {
cs->logDebug( "Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateForward( use );
if ( time > finish ) {
finish = time;
}
}
// now try to schedule soft constrained *with* predeccessors
foreach ( Node *n, m_softConstraints ) {
cs->logDebug( "Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateForward( use );
if ( time > finish ) {
finish = time;
}
}
// and then the rest using the end nodes to calculate everything (remaining)
foreach ( Task *n, m_terminalNodes ) {
cs->logDebug( "Calculate using end task:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateForward( use );
if ( time > finish ) {
finish = time;
}
}
} else {
// tasks have been calculated backwards in this order
foreach ( Node *n, cs->backwardNodes() ) {
DateTime time = n->calculateForward( use );
if ( time > finish ) {
finish = time;
}
}
}
cs->logInfo( i18n( "Finished calculating forward: %1 ms", timer.elapsed() ) );
} else {
//TODO: subproject
}
return finish;
}
DateTime Project::calculateBackward( int use )
{
//debugPlan<<m_name;
DateTime start;
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
if ( cs == 0 ) {
return start;
}
if ( type() == Node::Type_Project ) {
QTime timer;
timer.start();
cs->logInfo( i18n( "Start calculating backward" ) );
m_visitedBackward = true;
if ( ! m_visitedForward ) {
// setup tasks
tasksBackward();
// Do all hard constrained first
foreach ( Task *n, m_hardConstraints ) {
cs->logDebug( "Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateLateStart( use ); // do not do predeccessors
if ( ! start.isValid() || time < start ) {
start = time;
}
}
// then do the predeccessors
foreach ( Task *n, m_hardConstraints ) {
cs->logDebug( "Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateBackward( use );
if ( ! start.isValid() || time < start ) {
start = time;
}
}
// now try to schedule soft constrained *with* predeccessors
foreach ( Task *n, m_softConstraints ) {
cs->logDebug( "Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateBackward( use );
if ( ! start.isValid() || time < start ) {
start = time;
}
}
// and then the rest using the start nodes to calculate everything (remaining)
foreach ( Task *n, m_terminalNodes ) {
cs->logDebug( "Calculate using start task:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->calculateBackward( use );
if ( ! start.isValid() || time < start ) {
start = time;
}
}
} else {
// tasks have been calculated forwards in this order
foreach ( Node *n, cs->forwardNodes() ) {
DateTime time = n->calculateBackward( use );
if ( ! start.isValid() || time < start ) {
start = time;
}
}
}
cs->logInfo( i18n( "Finished calculating backward: %1 ms", timer.elapsed() ) );
} else {
//TODO: subproject
}
return start;
}
DateTime Project::scheduleForward( const DateTime &earliest, int use )
{
DateTime end;
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
if ( cs == 0 || stopcalculation ) {
return DateTime();
}
QTime timer;
timer.start();
cs->logInfo( i18n( "Start scheduling forward" ) );
resetVisited();
// Schedule in the same order as calculated forward
// Do all hard constrained first
foreach ( Node *n, m_hardConstraints ) {
cs->logDebug( "Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->scheduleFromStartTime( use ); // do not do predeccessors
if ( time > end ) {
end = time;
}
}
foreach ( Node *n, cs->forwardNodes() ) {
cs->logDebug( "Schedule task:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->scheduleForward( earliest, use );
if ( time > end ) {
end = time;
}
}
// Fix summarytasks
adjustSummarytask();
cs->logInfo( i18n( "Finished scheduling forward: %1 ms", timer.elapsed() ) );
foreach ( Node *n, allNodes() ) {
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
Q_ASSERT( n->isScheduled() );
}
}
return end;
}
DateTime Project::scheduleBackward( const DateTime &latest, int use )
{
DateTime start;
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
if ( cs == 0 || stopcalculation ) {
return start;
}
QTime timer;
timer.start();
cs->logInfo( i18n( "Start scheduling backward" ) );
resetVisited();
// Schedule in the same order as calculated backward
// Do all hard constrained first
foreach ( Node *n, m_hardConstraints ) {
cs->logDebug( "Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->scheduleFromEndTime( use ); // do not do predeccessors
if ( ! start.isValid() || time < start ) {
start = time;
}
}
foreach ( Node *n, cs->backwardNodes() ) {
cs->logDebug( "Schedule task:" + n->name() + " : " + n->constraintToString() );
DateTime time = n->scheduleBackward( latest, use );
if ( ! start.isValid() || time < start ) {
start = time;
}
}
// Fix summarytasks
adjustSummarytask();
cs->logInfo( i18n( "Finished scheduling backward: %1 ms", timer.elapsed() ) );
foreach ( Node *n, allNodes() ) {
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
Q_ASSERT( n->isScheduled() );
}
}
return start;
}
void Project::adjustSummarytask()
{
MainSchedule *cs = static_cast<MainSchedule*>( m_currentSchedule );
if ( cs == 0 || stopcalculation ) {
return;
}
QListIterator<Node*> it( cs->summaryTasks() );
while ( it.hasNext() ) {
it.next() ->adjustSummarytask();
}
}
void Project::initiateCalculation( MainSchedule &sch )
{
//debugPlan<<m_name;
// clear all resource appointments
m_visitedForward = false;
m_visitedBackward = false;
QListIterator<ResourceGroup*> git( m_resourceGroups );
while ( git.hasNext() ) {
git.next() ->initiateCalculation( sch );
}
Node::initiateCalculation( sch );
}
void Project::initiateCalculationLists( MainSchedule &sch )
{
//debugPlan<<m_name;
sch.clearNodes();
if ( type() == Node::Type_Project ) {
QListIterator<Node*> it = childNodeIterator();
while ( it.hasNext() ) {
it.next() ->initiateCalculationLists( sch );
}
} else {
//TODO: subproject
}
}
bool Project::load( KoXmlElement &element, XMLLoaderObject &status )
{
//debugPlan<<"--->";
m_useSharedResources = false; // default should off in case old project
// load locale first
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "locale" ) {
Locale *l = locale();
l->setCurrencySymbol(e.attribute( "currency-symbol", ""));
if ( e.hasAttribute( "currency-digits" ) ) {
l->setMonetaryDecimalPlaces(e.attribute("currency-digits").toInt());
}
QLocale::Language language = QLocale::AnyLanguage;
QLocale::Country country = QLocale::AnyCountry;
if (e.hasAttribute("language")) {
language = static_cast<QLocale::Language>(e.attribute("language").toInt());
}
if (e.hasAttribute("country")) {
country = static_cast<QLocale::Country>(e.attribute("country").toInt());
}
l->setCurrencyLocale(language, country);
} else if (e.tagName() == "shared-resources") {
m_useSharedResources = e.attribute("use", "0").toInt();
m_sharedResourcesFile = e.attribute("file");
m_sharedProjectsUrl = QUrl(e.attribute("projects-url"));
m_loadProjectsAtStartup = (bool)e.attribute("projects-loadatstartup", "0").toInt();
}
}
QList<Calendar*> cals;
QString s;
bool ok = false;
setName( element.attribute( "name" ) );
removeId( m_id );
m_id = element.attribute( "id" );
registerNodeId( this );
m_leader = element.attribute( "leader" );
m_description = element.attribute( "description" );
QTimeZone tz( element.attribute( "timezone" ).toLatin1() );
if ( tz.isValid() ) {
m_timeZone = tz;
} else warnPlan<<"No timezone specified, using default (local)";
status.setProjectTimeZone( m_timeZone );
// Allow for both numeric and text
s = element.attribute( "scheduling", "0" );
m_constraint = ( Node::ConstraintType ) s.toInt( &ok );
if ( !ok )
setConstraint( s );
if ( m_constraint != Node::MustStartOn &&
m_constraint != Node::MustFinishOn ) {
errorPlan << "Illegal constraint: " << constraintToString();
setConstraint( Node::MustStartOn );
}
s = element.attribute( "start-time" );
if ( !s.isEmpty() )
m_constraintStartTime = DateTime::fromString( s, m_timeZone );
s = element.attribute( "end-time" );
if ( !s.isEmpty() )
m_constraintEndTime = DateTime::fromString( s, m_timeZone );
status.setProgress( 10 );
// Load the project children
// Do calendars first, they only reference other calendars
//debugPlan<<"Calendars--->";
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "calendar" ) {
// Load the calendar.
// Referenced by resources
Calendar * child = new Calendar();
child->setProject( this );
if ( child->load( e, status ) ) {
cals.append( child ); // temporary, reorder later
} else {
// TODO: Complain about this
errorPlan << "Failed to load calendar";
delete child;
}
} else if ( e.tagName() == "standard-worktime" ) {
// Load standard worktime
StandardWorktime * child = new StandardWorktime();
if ( child->load( e, status ) ) {
setStandardWorktime( child );
} else {
errorPlan << "Failed to load standard worktime";
delete child;
}
}
}
// calendars references calendars in arbritary saved order
bool added = false;
do {
added = false;
QList<Calendar*> lst;
while ( !cals.isEmpty() ) {
Calendar *c = cals.takeFirst();
c->m_blockversion = true;
if ( c->parentId().isEmpty() ) {
addCalendar( c, status.baseCalendar() ); // handle pre 0.6 version
added = true;
//debugPlan<<"added to project:"<<c->name();
} else {
Calendar *par = calendar( c->parentId() );
if ( par ) {
par->m_blockversion = true;
addCalendar( c, par );
added = true;
//debugPlan<<"added:"<<c->name()<<" to parent:"<<par->name();
par->m_blockversion = false;
} else {
lst.append( c ); // treat later
//debugPlan<<"treat later:"<<c->name();
}
}
c->m_blockversion = false;
}
cals = lst;
} while ( added );
if ( ! cals.isEmpty() ) {
errorPlan<<"All calendars not saved!";
}
//debugPlan<<"Calendars<---";
status.setProgress( 15 );
// Resource groups and resources, can reference calendars
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "resource-group" ) {
// Load the resources
// References calendars
ResourceGroup * child = new ResourceGroup();
if ( child->load( e, status ) ) {
addResourceGroup( child );
} else {
// TODO: Complain about this
delete child;
}
}
}
status.setProgress( 20 );
// The main stuff
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "project" ) {
//debugPlan<<"Sub project--->";
/* // Load the subproject
Project * child = new Project( this );
if ( child->load( e ) ) {
if ( !addTask( child, this ) ) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}*/
} else if ( e.tagName() == "task" ) {
//debugPlan<<"Task--->";
// Load the task (and resourcerequests).
// Depends on resources already loaded
Task * child = new Task( this );
if ( child->load( e, status ) ) {
if ( !addTask( child, this ) ) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}
}
}
status.setProgress( 70 );
// These go last
n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
debugPlan<<n.isElement();
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "accounts" ) {
//debugPlan<<"Accounts--->";
// Load accounts
// References tasks
if ( !m_accounts.load( e, *this ) ) {
errorPlan << "Failed to load accounts";
}
} else if ( e.tagName() == "relation" ) {
//debugPlan<<"Relation--->";
// Load the relation
// References tasks
Relation * child = new Relation();
if ( !child->load( e, *this ) ) {
// TODO: Complain about this
errorPlan << "Failed to load relation";
delete child;
}
//debugPlan<<"Relation<---";
} else if ( e.tagName() == "schedules" ) {
//debugPlan<<"Project schedules & task appointments--->";
// References tasks and resources
KoXmlNode sn = e.firstChild();
for ( ; ! sn.isNull(); sn = sn.nextSibling() ) {
if ( ! sn.isElement() ) {
continue;
}
KoXmlElement el = sn.toElement();
//debugPlan<<el.tagName()<<" Version="<<status.version();
ScheduleManager *sm = 0;
bool add = false;
if ( status.version() <= "0.5" ) {
if ( el.tagName() == "schedule" ) {
sm = findScheduleManagerByName( el.attribute( "name" ) );
if ( sm == 0 ) {
sm = new ScheduleManager( *this, el.attribute( "name" ) );
add = true;
}
}
} else if ( el.tagName() == "plan" ) {
sm = new ScheduleManager( *this );
add = true;
}
if ( sm ) {
debugPlan<<"load schedule manager";
if ( sm->loadXML( el, status ) ) {
if ( add )
addScheduleManager( sm );
} else {
errorPlan << "Failed to load schedule manager";
delete sm;
}
} else {
debugPlan<<"No schedule manager ?!";
}
}
//debugPlan<<"Node schedules<---";
} else if ( e.tagName() == "resource-teams" ) {
//debugPlan<<"Resource teams--->";
// References other resources
KoXmlNode tn = e.firstChild();
for ( ; ! tn.isNull(); tn = tn.nextSibling() ) {
if ( ! tn.isElement() ) {
continue;
}
KoXmlElement el = tn.toElement();
if ( el.tagName() == "team" ) {
Resource *r = findResource( el.attribute( "team-id" ) );
Resource *tm = findResource( el.attribute( "member-id" ) );
if ( r == 0 || tm == 0 ) {
errorPlan<<"resource-teams: cannot find resources";
continue;
}
if ( r == tm ) {
errorPlan<<"resource-teams: a team cannot be a member of itself";
continue;
}
r->addTeamMemberId( tm->id() );
} else {
errorPlan<<"resource-teams: unhandled tag"<<el.tagName();
}
}
//debugPlan<<"Resource teams<---";
} else if ( e.tagName() == "wbs-definition" ) {
m_wbsDefinition.loadXML( e, status );
} else if ( e.tagName() == "locale" ) {
// handled earlier
} else if ( e.tagName() == "resource-group" ) {
// handled earlier
} else if ( e.tagName() == "calendar" ) {
// handled earlier
} else if ( e.tagName() == "standard-worktime" ) {
// handled earlier
} else if ( e.tagName() == "project" ) {
// handled earlier
} else if ( e.tagName() == "task" ) {
// handled earlier
} else if ( e.tagName() == "shared-resources" ) {
// handled earlier
} else {
warnPlan<<"Unhandled tag:"<<e.tagName();
}
}
//debugPlan<<"<---";
status.setProgress( 90 );
return true;
}
void Project::save( QDomElement &element ) const
{
QDomElement me = element.ownerDocument().createElement( "project" );
element.appendChild( me );
me.setAttribute( "name", m_name );
me.setAttribute( "leader", m_leader );
me.setAttribute( "id", m_id );
me.setAttribute( "description", m_description );
me.setAttribute( "timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString() );
me.setAttribute( "scheduling", constraintToString() );
me.setAttribute( "start-time", m_constraintStartTime.toString( Qt::ISODate ) );
me.setAttribute( "end-time", m_constraintEndTime.toString( Qt::ISODate ) );
m_wbsDefinition.saveXML( me );
QDomElement loc = me.ownerDocument().createElement( "locale" );
me.appendChild( loc );
const Locale *l = locale();
if (!l->currencySymbolExplicit().isEmpty()) {
loc.setAttribute("currency-symbol", l->currencySymbolExplicit());
}
loc.setAttribute("currency-digits", l->monetaryDecimalPlaces());
loc.setAttribute("language", l->currencyLanguage());
loc.setAttribute("country", l->currencyCountry());
QDomElement share = me.ownerDocument().createElement( "shared-resources" );
me.appendChild(share);
share.setAttribute("use", m_useSharedResources);
share.setAttribute("file", m_sharedResourcesFile);
share.setAttribute("projects-url", QString(m_sharedProjectsUrl.toEncoded()));
share.setAttribute("projects-loadatstartup", m_loadProjectsAtStartup);
m_accounts.save( me );
// save calendars
foreach ( Calendar *c, calendarIdDict ) {
c->save( me );
}
// save standard worktime
if ( m_standardWorktime )
m_standardWorktime->save( me );
// save project resources, must be after calendars
QListIterator<ResourceGroup*> git( m_resourceGroups );
while ( git.hasNext() ) {
git.next() ->save( me );
}
// Only save parent relations
QListIterator<Relation*> it( m_dependParentNodes );
while ( it.hasNext() ) {
it.next() ->save( me );
}
for ( int i = 0; i < numChildren(); i++ )
// Save all children
childNode( i ) ->save( me );
// Now we can save relations assuming no tasks have relations outside the project
QListIterator<Node*> nodes( m_nodes );
while ( nodes.hasNext() ) {
nodes.next() ->saveRelations( me );
}
if ( !m_managers.isEmpty() ) {
QDomElement el = me.ownerDocument().createElement( "schedules" );
me.appendChild( el );
foreach ( ScheduleManager *sm, m_managers ) {
sm->saveXML( el );
}
}
// save resource teams
QDomElement el = me.ownerDocument().createElement( "resource-teams" );
me.appendChild( el );
foreach ( Resource *r, resourceIdDict ) {
if ( r->type() != Resource::Type_Team ) {
continue;
}
foreach ( const QString &id, r->teamMemberIds() ) {
QDomElement e = el.ownerDocument().createElement( "team" );
el.appendChild( e );
e.setAttribute( "team-id", r->id() );
e.setAttribute( "member-id", id );
}
}
}
void Project::saveWorkPackageXML( QDomElement &element, const Node *node, long id ) const
{
QDomElement me = element.ownerDocument().createElement( "project" );
element.appendChild( me );
me.setAttribute( "name", m_name );
me.setAttribute( "leader", m_leader );
me.setAttribute( "id", m_id );
me.setAttribute( "description", m_description );
me.setAttribute( "timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString() );
me.setAttribute( "scheduling", constraintToString() );
me.setAttribute( "start-time", m_constraintStartTime.toString( Qt::ISODate ) );
me.setAttribute( "end-time", m_constraintEndTime.toString( Qt::ISODate ) );
QListIterator<ResourceGroup*> git( m_resourceGroups );
while ( git.hasNext() ) {
git.next() ->saveWorkPackageXML( me, node->assignedResources( id ) );
}
if ( node == 0 ) {
return;
}
node->saveWorkPackageXML( me, id );
foreach ( ScheduleManager *sm, m_managerIdMap ) {
if ( sm->scheduleId() == id ) {
QDomElement el = me.ownerDocument().createElement( "schedules" );
me.appendChild( el );
sm->saveWorkPackageXML( el, *node );
break;
}
}
}
void Project::setParentSchedule( Schedule *sch )
{
QListIterator<Node*> it = m_nodes;
while ( it.hasNext() ) {
it.next() ->setParentSchedule( sch );
}
}
void Project::addResourceGroup( ResourceGroup *group, int index )
{
int i = index == -1 ? m_resourceGroups.count() : index;
emit resourceGroupToBeAdded( group, i );
m_resourceGroups.insert( i, group );
setResourceGroupId( group );
group->setProject( this );
foreach ( Resource *r, group->resources() ) {
setResourceId( r );
r->setProject( this );
}
emit resourceGroupAdded( group );
emit projectChanged();
}
ResourceGroup *Project::takeResourceGroup( ResourceGroup *group )
{
int i = m_resourceGroups.indexOf( group );
Q_ASSERT( i != -1 );
if ( i == -1 ) {
return 0;
}
emit resourceGroupToBeRemoved( group );
ResourceGroup *g = m_resourceGroups.takeAt( i );
Q_ASSERT( group == g );
g->setProject( 0 );
removeResourceGroupId( g->id() );
foreach ( Resource *r, g->resources() ) {
r->setProject( 0 );
removeResourceId( r->id() );
}
emit resourceGroupRemoved( g );
emit projectChanged();
return g;
}
QList<ResourceGroup*> &Project::resourceGroups()
{
return m_resourceGroups;
}
void Project::addResource( ResourceGroup *group, Resource *resource, int index )
{
int i = index == -1 ? group->numResources() : index;
emit resourceToBeAdded( group, i );
group->addResource( i, resource, 0 );
setResourceId( resource );
emit resourceAdded( resource );
emit projectChanged();
}
Resource *Project::takeResource( ResourceGroup *group, Resource *resource )
{
emit resourceToBeRemoved( resource );
bool result = removeResourceId( resource->id() );
Q_ASSERT( result == true );
if (!result) {
warnPlan << "Could not remove resource with id" << resource->id();
}
resource->removeRequests(); // not valid anymore
Resource *r = group->takeResource( resource );
Q_ASSERT( resource == r );
if (resource != r) {
warnPlan << "Could not take resource from group";
}
emit resourceRemoved( resource );
emit projectChanged();
return r;
}
void Project::moveResource( ResourceGroup *group, Resource *resource )
{
if ( group == resource->parentGroup() ) {
return;
}
takeResource( resource->parentGroup(), resource );
addResource( group, resource );
return;
}
QMap< QString, QString > Project::externalProjects() const
{
QMap< QString, QString > map;
foreach ( Resource *r, resourceList() ) {
for( QMapIterator<QString, QString> it( r->externalProjects() ); it.hasNext(); ) {
it.next();
if ( ! map.contains( it.key() ) ) {
map[ it.key() ] = it.value();
}
}
}
return map;
}
bool Project::addTask( Node* task, Node* position )
{
// we want to add a task at the given position. => the new node will
// become next sibling right after position.
if ( 0 == position ) {
return addSubTask( task, this );
}
//debugPlan<<"Add"<<task->name()<<" after"<<position->name();
// in case we want to add to the main project, we make it child element
// of the root element.
if ( Node::Type_Project == position->type() ) {
return addSubTask( task, position );
}
// find the position
// we have to tell the parent that we want to delete one of its children
Node* parentNode = position->parentNode();
if ( !parentNode ) {
debugPlan <<"parent node not found???";
return false;
}
int index = parentNode->findChildNode( position );
if ( -1 == index ) {
// ok, it does not exist
debugPlan <<"Task not found???";
return false;
}
return addSubTask( task, index + 1, parentNode );
}
bool Project::addSubTask( Node* task, Node* parent )
{
// append task to parent
return addSubTask( task, -1, parent );
}
bool Project::addSubTask( Node* task, int index, Node* parent, bool emitSignal )
{
// we want to add a subtask to the node "parent" at the given index.
// If parent is 0, add to this
Node *p = parent;
if ( 0 == p ) {
p = this;
}
if ( !registerNodeId( task ) ) {
errorPlan << "Failed to register node id, can not add subtask: " << task->name();
return false;
}
int i = index == -1 ? p->numChildren() : index;
if ( emitSignal ) emit nodeToBeAdded( p, i );
p->insertChildNode( i, task );
connect( this, &Project::standardWorktimeChanged, task, &Node::slotStandardWorktimeChanged );
if ( emitSignal ) {
emit nodeAdded( task );
emit projectChanged();
if ( p != this && p->numChildren() == 1 ) {
emit nodeChanged( p );
}
}
return true;
}
void Project::takeTask( Node *node, bool emitSignal )
{
//debugPlan<<node->name();
Node * parent = node->parentNode();
if ( parent == 0 ) {
debugPlan <<"Node must have a parent!";
return;
}
removeId( node->id() );
if ( emitSignal ) emit nodeToBeRemoved( node );
disconnect( this, &Project::standardWorktimeChanged, node, &Node::slotStandardWorktimeChanged );
parent->takeChildNode( node );
if ( emitSignal ) {
emit nodeRemoved( node );
emit projectChanged();
if ( parent != this && parent->type() != Node::Type_Summarytask ) {
emit nodeChanged( parent );
}
}
}
bool Project::canMoveTask( Node* node, Node *newParent )
{
//debugPlan<<node->name()<<" to"<<newParent->name();
if ( node == this ) {
return false;
}
Node *p = newParent;
while ( p && p != this ) {
if ( ! node->canMoveTo( p ) ) {
return false;
}
p = p->parentNode();
}
return true;
}
bool Project::moveTask( Node* node, Node *newParent, int newPos )
{
//debugPlan<<node->name()<<" to"<<newParent->name()<<","<<newPos;
if ( ! canMoveTask( node, newParent ) ) {
return false;
}
Node *oldParent = node->parentNode();
int oldPos = oldParent->indexOf( node );
int i = newPos < 0 ? newParent->numChildren() : newPos;
if ( oldParent == newParent && i == oldPos ) {
// no need to move to where it already is
return false;
}
int newRow = i;
if ( oldParent == newParent && newPos > oldPos ) {
++newRow; // itemmodels wants new row *before* node is removed from old position
}
debugPlan<<node->name()<<"at"<<oldParent->indexOf( node )<<"to"<<newParent->name()<<i<<newRow<<"("<<newPos<<")";
emit nodeToBeMoved( node, oldPos, newParent, newRow );
takeTask( node, false );
addSubTask( node, i, newParent, false );
emit nodeMoved( node );
if ( oldParent != this && oldParent->numChildren() == 0 ) {
emit nodeChanged( oldParent );
}
if ( newParent != this && newParent->numChildren() == 1 ) {
emit nodeChanged( newParent );
}
return true;
}
bool Project::canIndentTask( Node* node )
{
if ( 0 == node ) {
// should always be != 0. At least we would get the Project,
// but you never know who might change that, so better be careful
return false;
}
if ( node->type() == Node::Type_Project ) {
//debugPlan<<"The root node cannot be indented";
return false;
}
// we have to find the parent of task to manipulate its list of children
Node* parentNode = node->parentNode();
if ( !parentNode ) {
return false;
}
if ( parentNode->findChildNode( node ) == -1 ) {
errorPlan << "Tasknot found???";
return false;
}
Node *sib = node->siblingBefore();
if ( !sib ) {
//debugPlan<<"new parent node not found";
return false;
}
if ( node->findParentRelation( sib ) || node->findChildRelation( sib ) ) {
//debugPlan<<"Cannot have relations to parent";
return false;
}
return true;
}
bool Project::indentTask( Node* node, int index )
{
if ( canIndentTask( node ) ) {
Node * newParent = node->siblingBefore();
int i = index == -1 ? newParent->numChildren() : index;
moveTask( node, newParent, i );
//debugPlan;
return true;
}
return false;
}
bool Project::canUnindentTask( Node* node )
{
if ( 0 == node ) {
// is always != 0. At least we would get the Project, but you
// never know who might change that, so better be careful
return false;
}
if ( Node::Type_Project == node->type() ) {
//debugPlan<<"The root node cannot be unindented";
return false;
}
// we have to find the parent of task to manipulate its list of children
// and we need the parent's parent too
Node* parentNode = node->parentNode();
if ( !parentNode ) {
return false;
}
Node* grandParentNode = parentNode->parentNode();
if ( !grandParentNode ) {
//debugPlan<<"This node already is at the top level";
return false;
}
int index = parentNode->findChildNode( node );
if ( -1 == index ) {
errorPlan << "Tasknot found???";
return false;
}
return true;
}
bool Project::unindentTask( Node* node )
{
if ( canUnindentTask( node ) ) {
Node * parentNode = node->parentNode();
Node *grandParentNode = parentNode->parentNode();
int i = grandParentNode->indexOf( parentNode ) + 1;
if ( i == 0 ) {
i = grandParentNode->numChildren();
}
moveTask( node, grandParentNode, i );
//debugPlan;
return true;
}
return false;
}
bool Project::canMoveTaskUp( Node* node )
{
if ( node == 0 )
return false; // safety
// we have to find the parent of task to manipulate its list of children
Node* parentNode = node->parentNode();
if ( !parentNode ) {
//debugPlan<<"No parent found";
return false;
}
if ( parentNode->findChildNode( node ) == -1 ) {
errorPlan << "Tasknot found???";
return false;
}
if ( node->siblingBefore() ) {
return true;
}
return false;
}
bool Project::moveTaskUp( Node* node )
{
if ( canMoveTaskUp( node ) ) {
moveTask( node, node->parentNode(), node->parentNode()->indexOf( node ) - 1 );
return true;
}
return false;
}
bool Project::canMoveTaskDown( Node* node )
{
if ( node == 0 )
return false; // safety
// we have to find the parent of task to manipulate its list of children
Node* parentNode = node->parentNode();
if ( !parentNode ) {
return false;
}
if ( parentNode->findChildNode( node ) == -1 ) {
errorPlan << "Tasknot found???";
return false;
}
if ( node->siblingAfter() ) {
return true;
}
return false;
}
bool Project::moveTaskDown( Node* node )
{
if ( canMoveTaskDown( node ) ) {
moveTask( node, node->parentNode(), node->parentNode()->indexOf( node ) + 1 );
return true;
}
return false;
}
Task *Project::createTask()
{
Task * node = new Task();
node->setId( uniqueNodeId() );
reserveId( node->id(), node );
return node;
}
Task *Project::createTask( const Task &def )
{
Task * node = new Task( def );
node->setId( uniqueNodeId() );
reserveId( node->id(), node );
return node;
}
Node *Project::findNode( const QString &id ) const
{
if ( m_parent == 0 ) {
if ( nodeIdDict.contains( id ) ) {
return nodeIdDict[ id ];
}
return 0;
}
return m_parent->findNode( id );
}
bool Project::nodeIdentExists( const QString &id ) const
{
return nodeIdDict.contains( id ) || nodeIdReserved.contains( id );
}
QString Project::uniqueNodeId( int seed ) const
{
Q_UNUSED(seed);
QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' ';
QString ident = s + KRandom::randomString( 10 );
// int i = seed;
while ( nodeIdentExists( ident ) ) {
ident = s + KRandom::randomString( 10 );
}
return ident;
}
QString Project::uniqueNodeId( const QList<QString> &existingIds, int seed )
{
QString id = uniqueNodeId( seed );
while ( existingIds.contains( id ) ) {
id = uniqueNodeId( seed );
}
return id;
}
bool Project::removeId( const QString &id )
{
//debugPlan <<"id=" << id;
if ( m_parent ) {
return m_parent->removeId( id );
}
//debugPlan << "id=" << id<< nodeIdDict.contains(id);
return nodeIdDict.remove( id );
}
void Project::reserveId( const QString &id, Node *node )
{
//debugPlan <<"id=" << id << node->name();
nodeIdReserved.insert( id, node );
}
bool Project::registerNodeId( Node *node )
{
nodeIdReserved.remove( node->id() );
if ( node->id().isEmpty() ) {
warnPlan << "Node id is empty, cannot register it";
return false;
}
Node *rn = findNode( node->id() );
if ( rn == 0 ) {
//debugPlan <<"id=" << node->id() << node->name();
nodeIdDict.insert( node->id(), node );
return true;
}
if ( rn != node ) {
errorPlan << "Id already exists for different task: " << node->id();
return false;
}
//debugPlan<<"Already exists" <<"id=" << node->id() << node->name();
return true;
}
QList<Node*> Project::allNodes() const
{
QList<Node*> lst = nodeIdDict.values();
int me = lst.indexOf( const_cast<Project*>( this ) );
if ( me != -1 ) {
lst.removeAt( me );
}
return lst;
}
QList<Task*> Project::allTasks( const Node *parent ) const
{
QList<Task*> lst;
const Node *p = parent ? parent : this;
foreach ( Node *n, p->childNodeIterator() ) {
if ( n->type() == Node::Type_Task || n->type() == Type_Milestone ) {
lst << static_cast<Task*>( n );
}
lst += allTasks( n );
}
return lst;
}
bool Project::setResourceGroupId( ResourceGroup *group )
{
if ( group == 0 ) {
return false;
}
if ( ! group->id().isEmpty() ) {
ResourceGroup *g = findResourceGroup( group->id() );
if ( group == g ) {
return true;
} else if ( g == 0 ) {
insertResourceGroupId( group->id(), group );
return true;
}
}
QString id = uniqueResourceGroupId();
group->setId( id );
if ( id.isEmpty() ) {
return false;
}
insertResourceGroupId( id, group );
return true;
}
QString Project::uniqueResourceGroupId() const {
QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' ';
QString id = s + KRandom::randomString( 10 );
while ( resourceGroupIdDict.contains( id ) ) {
id = s + KRandom::randomString( 10 );
}
return id;
}
ResourceGroup *Project::group( const QString& id )
{
return findResourceGroup( id );
}
ResourceGroup *Project::groupByName( const QString& name ) const
{
foreach ( ResourceGroup *g, resourceGroupIdDict ) {
if ( g->name() == name ) {
return g;
}
}
return 0;
}
QList<Resource*> Project::autoAllocateResources() const
{
QList<Resource*> lst;
foreach ( Resource *r, resourceIdDict ) {
if ( r->autoAllocate() ) {
lst << r;
}
}
return lst;
}
void Project::insertResourceId( const QString &id, Resource *resource )
{
resourceIdDict.insert( id, resource );
}
bool Project::removeResourceId( const QString &id )
{
return resourceIdDict.remove( id );
}
bool Project::setResourceId( Resource *resource )
{
if ( resource == 0 ) {
return false;
}
if ( ! resource->id().isEmpty() ) {
Resource *r = findResource( resource->id() );
if ( resource == r ) {
return true;
} else if ( r == 0 ) {
insertResourceId( resource->id(), resource );
return true;
}
}
QString id = uniqueResourceId();
resource->setId( id );
if ( id.isEmpty() ) {
return false;
}
insertResourceId( id, resource );
return true;
}
QString Project::uniqueResourceId() const {
QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' ';
QString id = s + KRandom::randomString( 10 );
while ( resourceIdDict.contains( id ) ) {
id = s + KRandom::randomString( 10 );
}
return id;
}
Resource *Project::resource( const QString& id )
{
return findResource( id );
}
Resource *Project::resourceByName( const QString& name ) const
{
QHash<QString, Resource*>::const_iterator it;
for (it = resourceIdDict.constBegin(); it != resourceIdDict.constEnd(); ++it) {
Resource *r = it.value();
if ( r->name() == name ) {
Q_ASSERT( it.key() == r->id() );
return r;
}
}
return 0;
}
QStringList Project::resourceNameList() const
{
QStringList lst;
foreach ( Resource *r, resourceIdDict ) {
lst << r->name();
}
return lst;
}
EffortCostMap Project::plannedEffortCostPrDay( QDate start, QDate end, long id, EffortCostCalculationType typ ) const
{
//debugPlan<<start<<end<<id;
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->plannedEffortCostPrDay( start, end, id, typ );
}
return ec;
}
EffortCostMap Project::plannedEffortCostPrDay( const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ ) const
{
//debugPlan<<start<<end<<id;
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->plannedEffortCostPrDay( resource, start, end, id, typ );
}
return ec;
}
EffortCostMap Project::actualEffortCostPrDay( QDate start, QDate end, long id, EffortCostCalculationType typ ) const
{
//debugPlan<<start<<end<<id;
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->actualEffortCostPrDay( start, end, id, typ );
}
return ec;
}
EffortCostMap Project::actualEffortCostPrDay( const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ ) const
{
//debugPlan<<start<<end<<id;
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->actualEffortCostPrDay( resource, start, end, id, typ );
}
return ec;
}
// Returns the total planned effort for this project (or subproject)
Duration Project::plannedEffort( long id, EffortCostCalculationType typ ) const
{
//debugPlan;
Duration eff;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffort( id, typ );
}
return eff;
}
// Returns the total planned effort for this project (or subproject) on date
Duration Project::plannedEffort( QDate date, long id, EffortCostCalculationType typ ) const
{
//debugPlan;
Duration eff;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffort( date, id, typ );
}
return eff;
}
// Returns the total planned effort for this project (or subproject) upto and including date
Duration Project::plannedEffortTo( QDate date, long id, EffortCostCalculationType typ ) const
{
//debugPlan;
Duration eff;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffortTo( date, id, typ );
}
return eff;
}
// Returns the total actual effort for this project (or subproject) upto and including date
Duration Project::actualEffortTo( QDate date ) const
{
//debugPlan;
Duration eff;
QListIterator
<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
eff += it.next() ->actualEffortTo( date );
}
return eff;
}
// Returns the total planned effort for this project (or subproject) upto and including date
double Project::plannedCostTo( QDate date, long id, EffortCostCalculationType typ ) const
{
//debugPlan;
double c = 0;
QListIterator
<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
c += it.next() ->plannedCostTo( date, id, typ );
}
return c;
}
// Returns the total actual cost for this project (or subproject) upto and including date
EffortCost Project::actualCostTo( long int id, QDate date ) const
{
//debugPlan;
EffortCost c;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
c += it.next() ->actualCostTo( id, date );
}
return c;
}
Duration Project::budgetedWorkPerformed( QDate date, long id ) const
{
//debugPlan;
Duration e;
foreach (Node *n, childNodeIterator()) {
e += n->budgetedWorkPerformed( date, id );
}
return e;
}
double Project::budgetedCostPerformed( QDate date, long id ) const
{
//debugPlan;
double c = 0.0;
foreach (Node *n, childNodeIterator()) {
c += n->budgetedCostPerformed( date, id );
}
return c;
}
double Project::effortPerformanceIndex( QDate date, long id ) const
{
//debugPlan;
debugPlan<<date<<id;
Duration b = budgetedWorkPerformed( date, id );
if ( b == Duration::zeroDuration ) {
return 1.0;
}
Duration a = actualEffortTo( date );
if ( b == Duration::zeroDuration ) {
return 1.0;
}
return b.toDouble() / a.toDouble();
}
double Project::schedulePerformanceIndex( QDate date, long id ) const
{
//debugPlan;
double r = 1.0;
double s = bcws( date, id );
double p = bcwp( date, id );
if ( s > 0.0 ) {
r = p / s;
}
debugPlan<<s<<p<<r;
return r;
}
double Project::bcws( QDate date, long id ) const
{
//debugPlan;
double c = plannedCostTo( date, id, ECCT_EffortWork );
debugPlan<<c;
return c;
}
double Project::bcwp( long id ) const
{
QDate date = QDate::currentDate();
return bcwp( date, id );
}
double Project::bcwp( QDate date, long id ) const
{
debugPlan<<date<<id;
QDate start = startTime( id ).date();
QDate end = endTime( id ).date();
EffortCostMap plan = plannedEffortCostPrDay( start, end, id, ECCT_EffortWork );
EffortCostMap actual = actualEffortCostPrDay( start, (end > date ? end : date), id );
double budgetAtCompletion;
double plannedCompleted;
double budgetedCompleted;
bool useEffort = false; //FIXME
if ( useEffort ) {
budgetAtCompletion = plan.totalEffort().toDouble( Duration::Unit_h );
plannedCompleted = plan.effortTo( date ).toDouble( Duration::Unit_h );
//actualCompleted = actual.effortTo( date ).toDouble( Duration::Unit_h );
budgetedCompleted = budgetedWorkPerformed( date, id ).toDouble( Duration::Unit_h );
} else {
budgetAtCompletion = plan.totalCost();
plannedCompleted = plan.costTo( date );
budgetedCompleted = budgetedCostPerformed( date, id );
}
double c = 0.0;
if ( budgetAtCompletion > 0.0 ) {
double percentageCompletion = budgetedCompleted / budgetAtCompletion;
c = budgetAtCompletion * percentageCompletion; //??
debugPlan<<percentageCompletion<<budgetAtCompletion<<budgetedCompleted<<plannedCompleted;
}
return c;
}
void Project::addCalendar( Calendar *calendar, Calendar *parent, int index )
{
Q_ASSERT( calendar != 0 );
//debugPlan<<calendar->name()<<","<<(parent?parent->name():"No parent");
int row = parent == 0 ? m_calendars.count() : parent->calendars().count();
if ( index >= 0 && index < row ) {
row = index;
}
emit calendarToBeAdded( parent, row );
calendar->setProject( this );
if ( parent == 0 ) {
calendar->setParentCal( 0 ); // in case
m_calendars.insert( row, calendar );
} else {
calendar->setParentCal( parent, row );
}
if ( calendar->isDefault() ) {
setDefaultCalendar( calendar );
}
setCalendarId( calendar );
emit calendarAdded( calendar );
emit projectChanged();
}
void Project::takeCalendar( Calendar *calendar )
{
emit calendarToBeRemoved( calendar );
removeCalendarId( calendar->id() );
if ( calendar == m_defaultCalendar ) {
m_defaultCalendar = 0;
}
if ( calendar->parentCal() == 0 ) {
int i = indexOf( calendar );
if ( i != -1 ) {
m_calendars.removeAt( i );
}
} else {
calendar->setParentCal( 0 );
}
emit calendarRemoved( calendar );
calendar->setProject( 0 );
emit projectChanged();
}
int Project::indexOf( const Calendar *calendar ) const
{
return m_calendars.indexOf( const_cast<Calendar*>(calendar) );
}
Calendar *Project::calendar( const QString& id ) const
{
return findCalendar( id );
}
Calendar *Project::calendarByName( const QString& name ) const
{
foreach( Calendar *c, calendarIdDict ) {
if ( c->name() == name ) {
return c;
}
}
return 0;
}
const QList<Calendar*> &Project::calendars() const
{
return m_calendars;
}
QList<Calendar*> Project::allCalendars() const
{
return calendarIdDict.values();
}
QStringList Project::calendarNames() const
{
QStringList lst;
foreach( Calendar *c, calendarIdDict ) {
lst << c->name();
}
return lst;
}
bool Project::setCalendarId( Calendar *calendar )
{
if ( calendar == 0 ) {
return false;
}
if ( ! calendar->id().isEmpty() ) {
Calendar *c = findCalendar( calendar->id() );
if ( calendar == c ) {
return true;
} else if ( c == 0 ) {
insertCalendarId( calendar->id(), calendar );
return true;
}
}
QString id = uniqueCalendarId();
calendar->setId( id );
if ( id.isEmpty() ) {
return false;
}
insertCalendarId( id, calendar );
return true;
}
QString Project::uniqueCalendarId() const {
QString s = QDateTime::currentDateTime().toString( Qt::ISODate ) + ' ';
QString id = s + KRandom::randomString( 10 );
while ( calendarIdDict.contains( id ) ) {
id = s + KRandom::randomString( 10 );
}
return id;
}
void Project::setDefaultCalendar( Calendar *cal )
{
if ( m_defaultCalendar ) {
m_defaultCalendar->setDefault( false );
}
m_defaultCalendar = cal;
if ( cal ) {
cal->setDefault( true );
}
emit defaultCalendarChanged( cal );
emit projectChanged();
}
void Project::setStandardWorktime( StandardWorktime * worktime )
{
if ( m_standardWorktime != worktime ) {
delete m_standardWorktime;
m_standardWorktime = worktime;
m_standardWorktime->setProject( this );
emit standardWorktimeChanged( worktime );
}
}
void Project::emitDocumentAdded( Node *node , Document *doc , int index )
{
emit documentAdded( node, doc, index );
}
void Project::emitDocumentRemoved( Node *node , Document *doc , int index )
{
emit documentRemoved( node, doc, index );
}
void Project::emitDocumentChanged( Node *node , Document *doc , int index )
{
emit documentChanged( node, doc, index );
}
bool Project::linkExists( const Node *par, const Node *child ) const
{
if ( par == 0 || child == 0 || par == child || par->isDependChildOf( child ) ) {
return false;
}
foreach ( Relation *r, par->dependChildNodes() ) {
if ( r->child() == child ) {
return true;
}
}
return false;
}
bool Project::legalToLink( const Node *par, const Node *child ) const
{
//debugPlan<<par.name()<<" ("<<par.numDependParentNodes()<<" parents)"<<child.name()<<" ("<<child.numDependChildNodes()<<" children)";
if ( par == 0 || child == 0 || par == child || par->isDependChildOf( child ) ) {
return false;
}
if ( linkExists( par, child ) ) {
return false;
}
bool legal = true;
// see if par/child is related
if ( legal && ( par->isParentOf( child ) || child->isParentOf( par ) ) ) {
legal = false;
}
if ( legal )
legal = legalChildren( par, child );
if ( legal )
legal = legalParents( par, child );
if ( legal ) {
foreach ( Node *p, par->childNodeIterator() ) {
if ( ! legalToLink( p, child ) ) {
return false;
}
}
}
return legal;
}
bool Project::legalParents( const Node *par, const Node *child ) const
{
bool legal = true;
//debugPlan<<par->name()<<" ("<<par->numDependParentNodes()<<" parents)"<<child->name()<<" ("<<child->numDependChildNodes()<<" children)";
for ( int i = 0; i < par->numDependParentNodes() && legal; ++i ) {
Node *pNode = par->getDependParentNode( i ) ->parent();
if ( child->isParentOf( pNode ) || pNode->isParentOf( child ) ) {
//debugPlan<<"Found:"<<pNode->name()<<" is related to"<<child->name();
legal = false;
} else {
legal = legalChildren( pNode, child );
}
if ( legal )
legal = legalParents( pNode, child );
}
return legal;
}
bool Project::legalChildren( const Node *par, const Node *child ) const
{
bool legal = true;
//debugPlan<<par->name()<<" ("<<par->numDependParentNodes()<<" parents)"<<child->name()<<" ("<<child->numDependChildNodes()<<" children)";
for ( int j = 0; j < child->numDependChildNodes() && legal; ++j ) {
Node *cNode = child->getDependChildNode( j ) ->child();
if ( par->isParentOf( cNode ) || cNode->isParentOf( par ) ) {
//debugPlan<<"Found:"<<par->name()<<" is related to"<<cNode->name();
legal = false;
} else {
legal = legalChildren( par, cNode );
}
}
return legal;
}
WBSDefinition &Project::wbsDefinition()
{
return m_wbsDefinition;
}
void Project::setWbsDefinition( const WBSDefinition &def )
{
//debugPlan;
m_wbsDefinition = def;
emit wbsDefinitionChanged();
emit projectChanged();
}
QString Project::generateWBSCode( QList<int> &indexes, bool sortable ) const
{
QString code = m_wbsDefinition.projectCode();
if (sortable) {
int fw = (nodeIdDict.count() / 10) + 1;
QLatin1Char fc('0');
foreach ( int index, indexes ) {
code += ".%1";
code = code.arg(QString::number(index), fw, fc);
}
debugPlan<<code<<"------------------";
} else {
if ( ! code.isEmpty() && ! indexes.isEmpty() ) {
code += m_wbsDefinition.projectSeparator();
}
int level = 1;
foreach ( int index, indexes ) {
code += m_wbsDefinition.code( index + 1, level );
if ( level < indexes.count() ) {
// not last level, add separator also
code += m_wbsDefinition.separator( level );
}
++level;
}
}
//debugPlan<<code;
return code;
}
void Project::setCurrentSchedule( long id )
{
//debugPlan;
setCurrentSchedulePtr( findSchedule( id ) );
Node::setCurrentSchedule( id );
QHash<QString, Resource*> hash = resourceIdDict;
foreach ( Resource * r, hash ) {
r->setCurrentSchedule( id );
}
emit currentScheduleChanged();
emit projectChanged();
}
ScheduleManager *Project::scheduleManager( long id ) const
{
foreach ( ScheduleManager *sm, m_managers ) {
if ( sm->scheduleId() == id ) {
return sm;
}
}
return 0;
}
ScheduleManager *Project::scheduleManager( const QString &id ) const
{
return m_managerIdMap.value( id );
}
ScheduleManager *Project::findScheduleManagerByName( const QString &name ) const
{
//debugPlan;
ScheduleManager *m = 0;
foreach( ScheduleManager *sm, m_managers ) {
m = sm->findManager( name );
if ( m ) {
break;
}
}
return m;
}
QList<ScheduleManager*> Project::allScheduleManagers() const
{
QList<ScheduleManager*> lst;
foreach ( ScheduleManager *sm, m_managers ) {
lst << sm;
lst << sm->allChildren();
}
return lst;
}
QString Project::uniqueScheduleName() const {
//debugPlan;
QString n = i18n( "Plan" );
bool unique = findScheduleManagerByName( n ) == 0;
if ( unique ) {
return n;
}
n += " %1";
int i = 1;
for ( ; true; ++i ) {
unique = findScheduleManagerByName( n.arg( i ) ) == 0;
if ( unique ) {
break;
}
}
return n.arg( i );
}
void Project::addScheduleManager( ScheduleManager *sm, ScheduleManager *parent, int index )
{
int row = parent == 0 ? m_managers.count() : parent->childCount();
if ( index >= 0 && index < row ) {
row = index;
}
if ( parent == 0 ) {
emit scheduleManagerToBeAdded( parent, row );
m_managers.insert( row, sm );
} else {
emit scheduleManagerToBeAdded( parent, row );
sm->setParentManager( parent, row );
}
if ( sm->managerId().isEmpty() ) {
sm->setManagerId( uniqueScheduleManagerId() );
}
Q_ASSERT( ! m_managerIdMap.contains( sm->managerId() ) );
m_managerIdMap.insert( sm->managerId(), sm );
emit scheduleManagerAdded( sm );
emit projectChanged();
//debugPlan<<"Added:"<<sm->name()<<", now"<<m_managers.count();
}
int Project::takeScheduleManager( ScheduleManager *sm )
{
foreach ( ScheduleManager *s, sm->children() ) {
takeScheduleManager( s );
}
if ( sm->scheduling() ) {
sm->stopCalculation();
}
int index = -1;
if ( sm->parentManager() ) {
int index = sm->parentManager()->indexOf( sm );
if ( index >= 0 ) {
emit scheduleManagerToBeRemoved( sm );
sm->setParentManager( 0 );
m_managerIdMap.remove( sm->managerId() );
emit scheduleManagerRemoved( sm );
emit projectChanged();
}
} else {
index = indexOf( sm );
if ( index >= 0 ) {
emit scheduleManagerToBeRemoved( sm );
m_managers.removeAt( indexOf( sm ) );
m_managerIdMap.remove( sm->managerId() );
emit scheduleManagerRemoved( sm );
emit projectChanged();
}
}
return index;
}
void Project::moveScheduleManager( ScheduleManager *sm, ScheduleManager *newparent, int newindex )
{
//debugPlan<<sm->name()<<newparent<<newindex;
emit scheduleManagerToBeMoved( sm );
if ( ! sm->parentManager() ) {
m_managers.removeAt( indexOf( sm ) );
}
sm->setParentManager( newparent, newindex );
if ( ! newparent ) {
m_managers.insert( newindex, sm );
}
emit scheduleManagerMoved( sm, newindex );
}
bool Project::isScheduleManager( void *ptr ) const
{
const ScheduleManager *sm = static_cast<ScheduleManager*>( ptr );
if ( indexOf( sm ) >= 0 ) {
return true;
}
foreach ( ScheduleManager *p, m_managers ) {
if ( p->isParentOf( sm ) ) {
return true;
}
}
return false;
}
ScheduleManager *Project::createScheduleManager( const QString &name )
{
//debugPlan<<name;
ScheduleManager *sm = new ScheduleManager( *this, name );
return sm;
}
ScheduleManager *Project::createScheduleManager()
{
//debugPlan;
return createScheduleManager( uniqueScheduleName() );
}
QString Project::uniqueScheduleManagerId() const
{
QString ident = KRandom::randomString( 10 );
while ( m_managerIdMap.contains( ident ) ) {
ident = KRandom::randomString( 10 );
}
return ident;
}
bool Project::isBaselined( long id ) const
{
if ( id == ANYSCHEDULED ) {
foreach ( ScheduleManager *sm, allScheduleManagers() ) {
if ( sm->isBaselined() ) {
return true;
}
}
return false;
}
Schedule *s = schedule( id );
return s == 0 ? false : s->isBaselined();
}
MainSchedule *Project::createSchedule( const QString& name, Schedule::Type type )
{
//debugPlan<<"No of schedules:"<<m_schedules.count();
MainSchedule *sch = new MainSchedule();
sch->setName( name );
sch->setType( type );
addMainSchedule( sch );
return sch;
}
void Project::addMainSchedule( MainSchedule *sch )
{
if ( sch == 0 ) {
return;
}
//debugPlan<<"No of schedules:"<<m_schedules.count();
long i = 1; // keep this positive (negative values are special...)
while ( m_schedules.contains( i ) ) {
++i;
}
sch->setId( i );
sch->setNode( this );
addSchedule( sch );
}
bool Project::removeCalendarId( const QString &id )
{
//debugPlan <<"id=" << id;
return calendarIdDict.remove( id );
}
void Project::insertCalendarId( const QString &id, Calendar *calendar )
{
//debugPlan <<"id=" << id <<":" << calendar->name();
calendarIdDict.insert( id, calendar );
}
void Project::changed( Node *node, int property )
{
if ( m_parent == 0 ) {
Node::changed( node, property ); // reset cache
if ( property != Node::Type ) {
// add/remove node is handled elsewhere
emit nodeChanged( node );
emit projectChanged();
}
return;
}
Node::changed( node, property );
}
void Project::changed( ResourceGroup *group )
{
//debugPlan;
emit resourceGroupChanged( group );
emit projectChanged();
}
void Project::changed( ScheduleManager *sm )
{
emit scheduleManagerChanged( sm );
emit projectChanged();
}
void Project::changed( MainSchedule *sch )
{
//debugPlan<<sch->id();
emit scheduleChanged( sch );
emit projectChanged();
}
void Project::sendScheduleToBeAdded( const ScheduleManager *sm, int row )
{
emit scheduleToBeAdded( sm, row );
}
void Project::sendScheduleAdded( const MainSchedule *sch )
{
//debugPlan<<sch->id();
emit scheduleAdded( sch );
emit projectChanged();
}
void Project::sendScheduleToBeRemoved( const MainSchedule *sch )
{
//debugPlan<<sch->id();
emit scheduleToBeRemoved( sch );
}
void Project::sendScheduleRemoved( const MainSchedule *sch )
{
//debugPlan<<sch->id();
emit scheduleRemoved( sch );
emit projectChanged();
}
void Project::changed( Resource *resource )
{
emit resourceChanged( resource );
emit projectChanged();
}
void Project::changed( Calendar *cal )
{
emit calendarChanged( cal );
emit projectChanged();
}
void Project::changed( StandardWorktime *w )
{
emit standardWorktimeChanged( w );
emit projectChanged();
}
bool Project::addRelation( Relation *rel, bool check )
{
if ( rel->parent() == 0 || rel->child() == 0 ) {
return false;
}
if ( check && !legalToLink( rel->parent(), rel->child() ) ) {
return false;
}
emit relationToBeAdded( rel, rel->parent()->numDependChildNodes(), rel->child()->numDependParentNodes() );
rel->parent()->addDependChildNode( rel );
rel->child()->addDependParentNode( rel );
emit relationAdded( rel );
emit projectChanged();
return true;
}
void Project::takeRelation( Relation *rel )
{
emit relationToBeRemoved( rel );
rel->parent() ->takeDependChildNode( rel );
rel->child() ->takeDependParentNode( rel );
emit relationRemoved( rel );
emit projectChanged();
}
void Project::setRelationType( Relation *rel, Relation::Type type )
{
emit relationToBeModified( rel );
rel->setType( type );
emit relationModified( rel );
emit projectChanged();
}
void Project::setRelationLag( Relation *rel, const Duration &lag )
{
emit relationToBeModified( rel );
rel->setLag( lag );
emit relationModified( rel );
emit projectChanged();
}
QList<Node*> Project::flatNodeList( Node *parent )
{
QList<Node*> lst;
Node *p = parent == 0 ? this : parent;
//debugPlan<<p->name()<<lst.count();
foreach ( Node *n, p->childNodeIterator() ) {
lst.append( n );
if ( n->numChildren() > 0 ) {
lst += flatNodeList( n );
}
}
return lst;
}
void Project::setSchedulerPlugins( const QMap<QString, SchedulerPlugin*> &plugins )
{
m_schedulerPlugins = plugins;
debugPlan<<m_schedulerPlugins;
}
void Project::emitLocaleChanged()
{
emit localeChanged();
}
bool Project::useSharedResources() const
{
return m_useSharedResources;
}
void Project::setUseSharedResources(bool on)
{
m_useSharedResources = on;
}
bool Project::isSharedResourcesLoaded() const
{
return m_sharedResourcesLoaded;
}
void Project::setSharedResourcesLoaded(bool on)
{
m_sharedResourcesLoaded = on;
}
void Project::setSharedResourcesFile(const QString &file)
{
m_sharedResourcesFile = file;
}
QString Project::sharedResourcesFile() const
{
return m_sharedResourcesFile;
}
void Project::setSharedProjectsUrl(const QUrl &url)
{
m_sharedProjectsUrl = url;
}
QUrl Project::sharedProjectsUrl() const
{
return m_sharedProjectsUrl;
}
void Project::setLoadProjectsAtStartup(bool value)
{
m_loadProjectsAtStartup = value;
}
bool Project::loadProjectsAtStartup() const
{
return m_loadProjectsAtStartup;
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptrelation.cpp b/src/libs/kernel/kptrelation.cpp
index cfb6d938..60dc6382 100644
--- a/src/libs/kernel/kptrelation.cpp
+++ b/src/libs/kernel/kptrelation.cpp
@@ -1,206 +1,207 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
Copyright (C) 2004, 2012 Dag Andersen <danders@get2net.dk>
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 "kptrelation.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QStringList>
namespace KPlato
{
Relation::Relation(Node *parent, Node *child, Type type, Duration lag) {
m_parent=parent;
m_child=child;
m_type=type;
m_lag=lag;
//debugPlan<<this;
}
Relation::Relation(Node *parent, Node *child, Type type) {
m_parent=parent;
m_child=child;
m_type=type;
m_lag=Duration();
//debugPlan<<this;
}
Relation::Relation(Relation *rel) {
m_parent=rel->parent();
m_child=rel->child();
m_type=rel->type();
m_lag=rel->lag();
//debugPlan<<this;
}
Relation::~Relation() {
//debugPlan<<"("<<this<<") parent:"<<(m_parent ? m_parent->name():"none")<<" child:"<<(m_child ? m_child->name():"None");
if (m_parent)
m_parent->takeDependChildNode(this);
if (m_child)
m_child->takeDependParentNode(this);
}
void Relation::setType(Type type) {
m_type=type;
}
void Relation::setType( const QString &type )
{
int t = typeList().indexOf( type );
if ( t == -1 ) {
t = FinishStart;
}
m_type = static_cast<Type>( t );
}
QString Relation::typeToString( bool trans ) const
{
return typeList( trans ).at( m_type );
}
QStringList Relation::typeList( bool trans )
{
//NOTE: must match enum
QStringList lst;
lst << ( trans ? i18n( "Finish-Start" ) : "Finish-Start" );
lst << ( trans ? i18n( "Finish-Finish" ) : "Finish-Finish" );
lst << ( trans ? i18n( "Start-Start" ) : "Start-Start" );
return lst;
}
void Relation::setParent( Node* node )
{
m_parent = node;
}
void Relation::setChild( Node* node )
{
m_child = node;
}
bool Relation::load(KoXmlElement &element, Project &project) {
m_parent = project.findNode(element.attribute("parent-id"));
if (m_parent == 0) {
return false;
}
m_child = project.findNode(element.attribute("child-id"));
if (m_child == 0) {
return false;
}
if (m_child == m_parent) {
debugPlan<<"child == parent";
return false;
}
if (m_child == m_parent) {
debugPlan<<"child == parent";
return false;
}
if (!m_parent->legalToLink(m_child))
return false;
setType( element.attribute("type") );
m_lag = Duration::fromString(element.attribute("lag"));
if (!m_parent->addDependChildNode(this)) {
errorPlan<<"Failed to add relation: Child="<<m_child->name()<<" parent="<<m_parent->name()<<endl;
return false;
}
if (!m_child->addDependParentNode(this)) {
m_parent->takeDependChildNode(this);
errorPlan<<"Failed to add relation: Child="<<m_child->name()<<" parent="<<m_parent->name()<<endl;
return false;
}
//debugPlan<<"Added relation: Child="<<m_child->name()<<" parent="<<m_parent->name();
return true;
}
void Relation::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("relation");
element.appendChild(me);
me.setAttribute("parent-id", m_parent->id());
me.setAttribute("child-id", m_child->id());
QString type = "Finish-Start";
switch (m_type) {
case FinishStart:
type = "Finish-Start";
break;
case FinishFinish:
type = "Finish-Finish";
break;
case StartStart:
type = "Start-Start";
break;
default:
break;
}
me.setAttribute("type", type);
me.setAttribute("lag", m_lag.toString());
}
#ifndef NDEBUG
void Relation::printDebug(const QByteArray& _indent) {
QString indent = _indent;
indent += " ";
debugPlan<<indent<<" Parent:"<<m_parent->name();
debugPlan<<indent<<" Child:"<<m_child->name();
debugPlan<<indent<<" Type:"<<m_type;
}
#endif
} //KPlato namespace
QDebug operator<<( QDebug dbg, const KPlato::Relation *r )
{
return dbg<<(*r);
}
QDebug operator<<( QDebug dbg, const KPlato::Relation &r )
{
KPlato::Node *parent = r.parent();
KPlato::Node *child = r.child();
QString type = "FS";
switch ( r.type() ) {
case KPlato::Relation::StartStart: type = "SS"; break;
case KPlato::Relation::FinishFinish: type = "FF"; break;
default: break;
}
KPlato::Duration lag = r.lag();
dbg<<"Relation["<<parent->name()<<"->"<<child->name()<<type;
if ( lag != 0 ) {
dbg<<lag.toString( KPlato::Duration::Format_HourFraction );
}
dbg <<']';
return dbg;
}
diff --git a/src/libs/kernel/kptresource.cpp b/src/libs/kernel/kptresource.cpp
index 8a1b98ca..12f5809a 100644
--- a/src/libs/kernel/kptresource.cpp
+++ b/src/libs/kernel/kptresource.cpp
@@ -1,2655 +1,2656 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
Copyright (C) 2004-2007, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptresource.h"
#include "kptlocale.h"
#include "kptaccount.h"
#include "kptappointment.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptdatetime.h"
#include "kptcalendar.h"
#include "kpteffortcostmap.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QLocale>
namespace KPlato
{
ResourceGroup::ResourceGroup()
: QObject( 0 ),
m_blockChanged(false),
m_shared(false)
{
m_project = 0;
m_type = Type_Work;
//debugPlan<<"("<<this<<")";
}
ResourceGroup::ResourceGroup( const ResourceGroup *group )
: QObject( 0 )
{
m_project = 0;
copy( group );
}
ResourceGroup::~ResourceGroup() {
//debugPlan<<"("<<this<<")";
if (findId() == this) {
removeId(); // only remove myself (I may be just a working copy)
}
foreach ( ResourceGroupRequest* r, m_requests ) {
r->unregister( this );
}
while (!m_resources.isEmpty()) {
delete m_resources.takeFirst();
}
//debugPlan<<"("<<this<<")";
}
void ResourceGroup::copy( const ResourceGroup *group )
{
//m_project = group->m_project; //Don't copy
m_id = group->m_id;
m_type = group->m_type;
m_name = group->m_name;
}
void ResourceGroup::blockChanged(bool on)
{
m_blockChanged = on;
}
void ResourceGroup::changed() {
if (m_project && !m_blockChanged) {
m_project->changed( this );
}
}
void ResourceGroup::setId(const QString& id) {
//debugPlan<<id;
m_id = id;
}
void ResourceGroup::setName( const QString& n )
{
m_name = n.trimmed();
changed();
}
void ResourceGroup::setType( Type type )
{
m_type = type;
changed();
}
void ResourceGroup::setType(const QString &type)
{
if (type == "Work")
setType( Type_Work );
else if (type == "Material")
setType( Type_Material );
else
setType( Type_Work );
}
QString ResourceGroup::typeToString( bool trans ) const {
return typeToStringList( trans ).at( m_type );
}
QStringList ResourceGroup::typeToStringList( bool trans ) {
// keep these in the same order as the enum!
return QStringList()
<< (trans ? i18n("Work") : QString("Work"))
<< (trans ? i18n("Material") : QString("Material"));
}
void ResourceGroup::setProject( Project *project )
{
if ( project != m_project ) {
if ( m_project ) {
removeId();
}
}
m_project = project;
foreach ( Resource *r, m_resources ) {
r->setProject( project );
}
}
bool ResourceGroup::isScheduled() const
{
foreach ( Resource *r, m_resources ) {
if ( r->isScheduled() ) {
return true;
}
}
return false;
}
bool ResourceGroup::isBaselined( long id ) const
{
Q_UNUSED(id);
foreach ( const Resource *r, m_resources ) {
if ( r->isBaselined() ) {
return true;
}
}
return false;
}
void ResourceGroup::addResource(int index, Resource* resource, Risk*) {
int i = index == -1 ? m_resources.count() : index;
resource->setParentGroup( this );
resource->setProject( m_project );
m_resources.insert(i, resource );
}
Resource *ResourceGroup::takeResource(Resource *resource) {
Resource *r = 0;
int i = m_resources.indexOf(resource);
if (i != -1) {
r = m_resources.takeAt(i);
r->setParentGroup( 0 );
r->setProject( 0 );
}
return r;
}
int ResourceGroup::indexOf( const Resource *resource ) const
{
return m_resources.indexOf( const_cast<Resource*>( resource ) ); //???
}
Risk* ResourceGroup::getRisk(int) {
return 0L;
}
void ResourceGroup::addRequiredResource(ResourceGroup*) {
}
ResourceGroup* ResourceGroup::getRequiredResource(int) {
return 0L;
}
void ResourceGroup::deleteRequiredResource(int) {
}
bool ResourceGroup::load(KoXmlElement &element, XMLLoaderObject &status ) {
//debugPlan;
setId(element.attribute("id"));
m_name = element.attribute("name");
setType(element.attribute("type"));
m_shared = element.attribute("shared", "0").toInt();
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "resource") {
// Load the resource
Resource *child = new Resource();
if (child->load(e, status)) {
addResource( -1, child, 0 );
} else {
// TODO: Complain about this
delete child;
}
}
}
return true;
}
void ResourceGroup::save(QDomElement &element) const {
//debugPlan;
QDomElement me = element.ownerDocument().createElement("resource-group");
element.appendChild(me);
me.setAttribute("id", m_id);
me.setAttribute("name", m_name);
me.setAttribute("type", typeToString());
me.setAttribute("shared", m_shared);
foreach (Resource *r, m_resources) {
r->save(me);
}
}
void ResourceGroup::saveWorkPackageXML( QDomElement &element, const QList<Resource*> &lst ) const
{
QDomElement me = element.ownerDocument().createElement( "resource-group" );
element.appendChild( me );
me.setAttribute( "id", m_id );
me.setAttribute( "name", m_name );
foreach ( Resource *r, m_resources ) {
if ( lst.contains( r ) ) {
r->save( me );
}
}
}
void ResourceGroup::initiateCalculation(Schedule &sch) {
foreach (Resource *r, m_resources) {
r->initiateCalculation(sch);
}
clearNodes();
}
int ResourceGroup::units() const {
int u = 0;
foreach ( const Resource *r, m_resources) {
u += r->units();
}
return u;
}
ResourceGroup *ResourceGroup::findId(const QString &id) const {
return m_project ? m_project->findResourceGroup(id) : 0;
}
bool ResourceGroup::removeId(const QString &id) {
return m_project ? m_project->removeResourceGroupId(id): false;
}
void ResourceGroup::insertId(const QString &id) {
//debugPlan;
if (m_project)
m_project->insertResourceGroupId(id, this);
}
Appointment ResourceGroup::appointmentIntervals() const {
Appointment a;
foreach (Resource *r, m_resources) {
a += r->appointmentIntervals();
}
return a;
}
DateTime ResourceGroup::startTime( long id ) const
{
DateTime dt;
foreach ( Resource *r, m_resources ) {
DateTime t = r->startTime( id );
if ( ! dt.isValid() || t < dt ) {
dt = t;
}
}
return dt;
}
DateTime ResourceGroup::endTime( long id ) const
{
DateTime dt;
foreach ( Resource *r, m_resources ) {
DateTime t = r->endTime( id );
if ( ! dt.isValid() || t > dt ) {
dt = t;
}
}
return dt;
}
bool ResourceGroup::isShared() const
{
return m_shared;
}
void ResourceGroup::setShared(bool on)
{
m_shared = on;
}
Resource::Resource()
: QObject( 0 ), // atm QObject is only for casting
m_project(0),
m_parent( 0 ),
m_autoAllocate( false ),
m_currentSchedule( 0 ),
m_blockChanged(false),
m_shared(false)
{
m_type = Type_Work;
m_units = 100; // %
// m_availableFrom = DateTime( QDate::currentDate(), QTime( 0, 0, 0 ) );
// m_availableUntil = m_availableFrom.addYears(2);
cost.normalRate = 100;
cost.overtimeRate = 0;
cost.fixed = 0;
cost.account = 0;
m_calendar = 0;
m_currentSchedule = 0;
//debugPlan<<"("<<this<<")";
// material: by default material is always available
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd = m_materialCalendar.weekday( i );
wd->setState( CalendarDay::Working );
wd->addInterval( TimeInterval( QTime( 0, 0, 0 ), 24*60*60*1000 ) );
}
}
Resource::Resource(Resource *resource)
: QObject( 0 ), // atm QObject is only for casting
m_project( 0 ),
m_parent( 0 ),
m_currentSchedule( 0 ),
m_shared(false)
{
//debugPlan<<"("<<this<<") from ("<<resource<<")";
copy(resource);
}
Resource::~Resource() {
//debugPlan<<"("<<this<<")";
if (findId() == this) {
removeId(); // only remove myself (I may be just a working copy)
}
removeRequests();
foreach ( Schedule *s, m_schedules ) {
delete s;
}
clearExternalAppointments();
if (cost.account) {
cost.account->removeRunning(*this);
}
}
void Resource::removeRequests() {
foreach (ResourceRequest *r, m_requests) {
r->setResource(0); // avoid the request to mess with my list
r->parent()->deleteResourceRequest(r);
}
m_requests.clear();
}
void Resource::setId(const QString& id) {
//debugPlan<<id;
m_id = id;
}
void Resource::copy(Resource *resource) {
m_project = 0; // NOTE: Don't copy, will be set when added to a project
//m_appointments = resource->appointments(); // Note
m_id = resource->id();
m_name = resource->name();
m_initials = resource->initials();
m_email = resource->email();
m_autoAllocate = resource->m_autoAllocate;
m_availableFrom = resource->availableFrom();
m_availableUntil = resource->availableUntil();
m_units = resource->units(); // available units in percent
m_type = resource->type();
cost.normalRate = resource->normalRate();
cost.overtimeRate = resource->overtimeRate();
cost.account = resource->account();
m_calendar = resource->m_calendar;
m_requiredIds = resource->requiredIds();
m_teamMembers = resource->m_teamMembers;
// hmmmm
//m_externalAppointments = resource->m_externalAppointments;
//m_externalNames = resource->m_externalNames;
}
void Resource::blockChanged(bool on)
{
m_blockChanged = on;
}
void Resource::changed()
{
if (m_project && !m_blockChanged) {
m_project->changed( this );
}
}
void Resource::setType( Type type )
{
m_type = type;
changed();
}
void Resource::setType(const QString &type)
{
if (type == "Work")
setType( Type_Work );
else if (type == "Material")
setType( Type_Material );
else if (type == "Team")
setType( Type_Team );
else
setType( Type_Work );
}
QString Resource::typeToString( bool trans ) const {
return typeToStringList( trans ).at( m_type );
}
QStringList Resource::typeToStringList( bool trans ) {
// keep these in the same order as the enum!
return QStringList()
<< (trans ? xi18nc( "@item:inlistbox resource type", "Work" ) : QString( "Work") )
<< (trans ? xi18nc( "@item:inlistbox resource type", "Material" ) : QString( "Material" ) )
<< (trans ? xi18nc( "@item:inlistbox resource type", "Team" ) : QString( "Team" ) );
}
void Resource::setName( const QString &n )
{
m_name = n.trimmed();
changed();
}
void Resource::setInitials( const QString &initials )
{
m_initials = initials.trimmed();
changed();
}
void Resource::setEmail( const QString &email )
{
m_email = email;
changed();
}
bool Resource::autoAllocate() const
{
return m_autoAllocate;
}
void Resource::setAutoAllocate( bool on )
{
if ( m_autoAllocate != on ) {
m_autoAllocate = on;
changed();
}
}
void Resource::setUnits( int units )
{
m_units = units;
m_workinfocache.clear();
changed();
}
Calendar *Resource::calendar( bool local ) const {
if ( local || m_calendar ) {
return m_calendar;
}
// No calendar is set, try default calendar
Calendar *c = 0;
if ( m_type == Type_Work && project() ) {
c = project()->defaultCalendar();
} else if ( m_type == Type_Material ) {
c = const_cast<Calendar*>( &m_materialCalendar );
}
return c;
}
void Resource::setCalendar( Calendar *calendar )
{
m_calendar = calendar;
m_workinfocache.clear();
changed();
}
DateTime Resource::firstAvailableAfter(const DateTime &, const DateTime & ) const {
return DateTime();
}
DateTime Resource::getBestAvailableTime(const Duration &/*duration*/) {
return DateTime();
}
DateTime Resource::getBestAvailableTime(const DateTime &/*after*/, const Duration &/*duration*/) {
return DateTime();
}
bool Resource::load(KoXmlElement &element, XMLLoaderObject &status) {
//debugPlan;
const Locale *locale = status.project().locale();
QString s;
setId(element.attribute("id"));
m_name = element.attribute("name");
m_initials = element.attribute("initials");
m_email = element.attribute("email");
m_autoAllocate = (bool)(element.attribute( "auto-allocate", "0" ).toInt());
setType(element.attribute("type"));
m_shared = element.attribute("shared", "0").toInt();
m_calendar = status.project().findCalendar(element.attribute("calendar-id"));
m_units = element.attribute("units", "100").toInt();
s = element.attribute("available-from");
if (!s.isEmpty())
m_availableFrom = DateTime::fromString(s, status.projectTimeZone());
s = element.attribute("available-until");
if (!s.isEmpty())
m_availableUntil = DateTime::fromString(s, status.projectTimeZone());
// NOTE: money was earlier (2.x) saved with symbol so we need to handle that
QString money = element.attribute("normal-rate");
bool ok = false;
cost.normalRate = money.toDouble(&ok);
if (!ok) {
cost.normalRate = locale->readMoney(money);
debugPlan<<"normal-rate failed, tried readMoney()"<<money<<"->"<<cost.normalRate;;
}
money = element.attribute("overtime-rate");
cost.overtimeRate = money.toDouble(&ok);
if (!ok) {
cost.overtimeRate = locale->readMoney(money);
debugPlan<<"overtime-rate failed, tried readMoney()"<<money<<"->"<<cost.overtimeRate;;
}
cost.account = status.project().accounts().findAccount(element.attribute("account"));
KoXmlElement e;
KoXmlElement parent = element.namedItem( "required-resources" ).toElement();
forEachElement( e, parent ) {
if (e.nodeName() == "resource") {
QString id = e.attribute( "id" );
if ( id.isEmpty() ) {
warnPlan<<"Missing resource id";
continue;
}
addRequiredId( id );
}
}
parent = element.namedItem( "external-appointments" ).toElement();
forEachElement( e, parent ) {
if ( e.nodeName() == "project" ) {
QString id = e.attribute( "id" );
if ( id.isEmpty() ) {
errorPlan<<"Missing project id";
continue;
}
clearExternalAppointments( id ); // in case...
AppointmentIntervalList lst;
lst.loadXML( e, status );
Appointment *a = new Appointment();
a->setIntervals( lst );
a->setAuxcilliaryInfo( e.attribute( "name", "Unknown" ) );
m_externalAppointments[ id ] = a;
}
}
loadCalendarIntervalsCache( element, status );
return true;
}
QList<Resource*> Resource::requiredResources() const
{
QList<Resource*> lst;
foreach ( const QString &s, m_requiredIds ) {
Resource *r = findId( s );
if ( r ) {
lst << r;
}
}
return lst;
}
void Resource::setRequiredIds( const QStringList &ids )
{
debugPlan<<ids;
m_requiredIds = ids;
}
void Resource::addRequiredId( const QString &id )
{
if ( ! id.isEmpty() && ! m_requiredIds.contains( id ) ) {
m_requiredIds << id;
}
}
void Resource::setAccount( Account *account )
{
if ( cost.account ) {
cost.account->removeRunning(*this);
}
cost.account = account;
changed();
}
void Resource::save(QDomElement &element) const {
//debugPlan;
QDomElement me = element.ownerDocument().createElement("resource");
element.appendChild(me);
if (calendar(true))
me.setAttribute("calendar-id", m_calendar->id());
me.setAttribute("id", m_id);
me.setAttribute("name", m_name);
me.setAttribute("initials", m_initials);
me.setAttribute("email", m_email);
me.setAttribute("auto-allocate", m_autoAllocate );
me.setAttribute("type", typeToString());
me.setAttribute("shared", m_shared);
me.setAttribute("units", QString::number(m_units));
if ( m_availableFrom.isValid() ) {
me.setAttribute("available-from", m_availableFrom.toString( Qt::ISODate ));
}
if ( m_availableUntil.isValid() ) {
me.setAttribute("available-until", m_availableUntil.toString( Qt::ISODate ));
}
QString money;
me.setAttribute("normal-rate", money.setNum(cost.normalRate));
me.setAttribute("overtime-rate", money.setNum(cost.overtimeRate));
if ( cost.account ) {
me.setAttribute("account", cost.account->name());
}
if ( ! m_requiredIds.isEmpty() ) {
QDomElement e = me.ownerDocument().createElement("required-resources");
me.appendChild(e);
foreach ( const QString &id, m_requiredIds ) {
QDomElement el = e.ownerDocument().createElement("resource");
e.appendChild( el );
el.setAttribute( "id", id );
}
}
if ( ! m_externalAppointments.isEmpty() ) {
QDomElement e = me.ownerDocument().createElement("external-appointments");
me.appendChild(e);
foreach ( const QString &id, m_externalAppointments.uniqueKeys() ) {
QDomElement el = e.ownerDocument().createElement("project");
e.appendChild( el );
el.setAttribute( "id", id );
el.setAttribute( "name", m_externalAppointments[ id ]->auxcilliaryInfo() );
m_externalAppointments[ id ]->intervals().saveXML( el );
}
}
saveCalendarIntervalsCache( me );
}
bool Resource::isAvailable(Task * /*task*/) {
bool busy = false;
/*
foreach (Appointment *a, m_appointments) {
if (a->isBusy(task->startTime(), task->endTime())) {
busy = true;
break;
}
}*/
return !busy;
}
QList<Appointment*> Resource::appointments( long id ) const {
Schedule *s = schedule( id );
if ( s == 0 ) {
return QList<Appointment*>();
}
return s->appointments();
}
bool Resource::addAppointment(Appointment *appointment) {
if (m_currentSchedule)
return m_currentSchedule->add(appointment);
return false;
}
bool Resource::addAppointment(Appointment *appointment, Schedule &main) {
Schedule *s = findSchedule(main.id());
if (s == 0) {
s = createSchedule(&main);
}
appointment->setResource(s);
return s->add(appointment);
}
// called from makeAppointment
void Resource::addAppointment( Schedule *node, const DateTime &start, const DateTime &end, double load )
{
Q_ASSERT( start < end );
Schedule *s = findSchedule(node->id());
if (s == 0) {
s = createSchedule(node->parent());
}
s->setCalculationMode( node->calculationMode() );
//debugPlan<<"id="<<node->id()<<" Mode="<<node->calculationMode()<<""<<start<<end;
s->addAppointment(node, start, end, load);
}
void Resource::initiateCalculation(Schedule &sch) {
m_currentSchedule = createSchedule(&sch);
}
Schedule *Resource::schedule( long id ) const
{
return id == -1 ? m_currentSchedule : findSchedule( id );
}
bool Resource::isBaselined( long id ) const
{
if ( m_type == Resource::Type_Team ) {
foreach ( const Resource *r, teamMembers() ) {
if ( r->isBaselined( id ) ) {
return true;
}
}
return false;
}
Schedule *s = schedule( id );
return s ? s->isBaselined() : false;
}
Schedule *Resource::findSchedule( long id ) const
{
if ( m_schedules.contains( id ) ) {
return m_schedules[ id ];
}
if ( id == CURRENTSCHEDULE ) {
return m_currentSchedule;
}
if ( id == BASELINESCHEDULE || id == ANYSCHEDULED ) {
foreach ( Schedule *s, m_schedules ) {
if ( s->isBaselined() ) {
return s;
}
}
}
if ( id == ANYSCHEDULED ) {
foreach ( Schedule *s, m_schedules ) {
if ( s->isScheduled() ) {
return s;
}
}
}
return 0;
}
bool Resource::isScheduled() const
{
foreach ( Schedule *s, m_schedules ) {
if ( s->isScheduled() ) {
return true;
}
}
return false;
}
void Resource::deleteSchedule(Schedule *schedule) {
takeSchedule(schedule);
delete schedule;
}
void Resource::takeSchedule(const Schedule *schedule) {
if (schedule == 0)
return;
if (m_currentSchedule == schedule)
m_currentSchedule = 0;
m_schedules.take(schedule->id());
}
void Resource::addSchedule(Schedule *schedule) {
if (schedule == 0)
return;
m_schedules.remove(schedule->id());
m_schedules.insert(schedule->id(), schedule);
}
ResourceSchedule *Resource::createSchedule(const QString& name, int type, long id) {
ResourceSchedule *sch = new ResourceSchedule(this, name, (Schedule::Type)type, id);
addSchedule(sch);
return sch;
}
ResourceSchedule *Resource::createSchedule(Schedule *parent) {
ResourceSchedule *sch = new ResourceSchedule(parent, this);
//debugPlan<<"id="<<sch->id();
addSchedule(sch);
return sch;
}
QTimeZone Resource::timeZone() const
{
Calendar *cal = calendar();
return
cal ? cal->timeZone() :
m_project ? m_project->timeZone() :
/* else */ QTimeZone();
}
DateTimeInterval Resource::requiredAvailable(Schedule *node, const DateTime &start, const DateTime &end ) const
{
Q_ASSERT( m_currentSchedule );
DateTimeInterval interval( start, end );
#ifndef PLAN_NLOGDEBUG
if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required available in interval: %1" ).arg( interval.toString() ) );
#endif
DateTime availableFrom = m_availableFrom.isValid() ? m_availableFrom : ( m_project ? m_project->constraintStartTime() : DateTime() );
DateTime availableUntil = m_availableUntil.isValid() ? m_availableUntil : ( m_project ? m_project->constraintEndTime() : DateTime() );
DateTimeInterval x = interval.limitedTo( availableFrom, availableUntil );
if ( calendar() == 0 ) {
#ifndef PLAN_NLOGDEBUG
if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required available: no calendar, %1" ).arg( x.toString() ) );
#endif
return x;
}
DateTimeInterval i = m_currentSchedule->firstBookedInterval( x, node );
if ( i.isValid() ) {
#ifndef PLAN_NLOGDEBUG
if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required available: booked, %1" ).arg( i.toString() ) );
#endif
return i;
}
i = calendar()->firstInterval(x.first, x.second, m_currentSchedule);
#ifndef PLAN_NLOGDEBUG
if (m_currentSchedule) m_currentSchedule->logDebug( QString( "Required first available in %1: %2" ).arg( x.toString() ).arg( i.toString() ) );
#endif
return i;
}
void Resource::makeAppointment(Schedule *node, const DateTime &from, const DateTime &end, int load, const QList<Resource*> &required ) {
//debugPlan<<"node id="<<node->id()<<" mode="<<node->calculationMode()<<""<<from<<" -"<<end;
if (!from.isValid() || !end.isValid()) {
m_currentSchedule->logWarning( i18n( "Make appointments: Invalid time" ) );
return;
}
Calendar *cal = calendar();
if (cal == 0) {
m_currentSchedule->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) );
return;
}
#ifndef PLAN_NLOGDEBUG
if ( m_currentSchedule ) {
QStringList lst; foreach ( Resource *r, required ) { lst << r->name(); }
m_currentSchedule->logDebug( QString( "Make appointments from %1 to %2 load=%4, required: %3" ).arg( from.toString() ).arg( end.toString() ).arg( lst.join(",") ).arg( load ) );
}
#endif
AppointmentIntervalList lst = workIntervals( from, end, m_currentSchedule );
foreach ( const AppointmentInterval &i, lst.map() ) {
m_currentSchedule->addAppointment( node, i.startTime(), i.endTime(), load );
foreach ( Resource *r, required ) {
r->addAppointment( node, i.startTime(), i.endTime(), r->units() ); //FIXME: units may not be correct
}
}
}
void Resource::makeAppointment(Schedule *node, int load, const QList<Resource*> &required) {
//debugPlan<<m_name<<": id="<<m_currentSchedule->id()<<" mode="<<m_currentSchedule->calculationMode()<<node->node()->name()<<": id="<<node->id()<<" mode="<<node->calculationMode()<<""<<node->startTime;
QLocale locale;
if (!node->startTime.isValid()) {
m_currentSchedule->logWarning( i18n( "Make appointments: Node start time is not valid" ) );
return;
}
if (!node->endTime.isValid()) {
m_currentSchedule->logWarning( i18n( "Make appointments: Node end time is not valid" ) );
return;
}
if ( m_type == Type_Team ) {
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug( "Make appointments to team " + m_name );
#endif
Duration e;
foreach ( Resource *r, teamMembers() ) {
r->makeAppointment( node, load, required );
}
return;
}
node->resourceNotAvailable = false;
node->workStartTime = DateTime();
node->workEndTime = DateTime();
Calendar *cal = calendar();
if (m_type == Type_Material) {
DateTime from = availableAfter(node->startTime, node->endTime);
DateTime end = availableBefore(node->endTime, node->startTime);
if (!from.isValid() || !end.isValid()) {
return;
}
if (cal == 0) {
// Allocate the whole period
addAppointment(node, from, end, m_units);
return;
}
makeAppointment(node, from, end, load);
return;
}
if (!cal) {
m_currentSchedule->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) );
return;
}
DateTime time = node->startTime;
DateTime end = node->endTime;
time = availableAfter(time, end);
if (!time.isValid()) {
m_currentSchedule->logWarning( i18n( "Resource %1 not available in interval: %2 to %3", m_name, locale.toString(node->startTime, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat) ) );
node->resourceNotAvailable = true;
return;
}
end = availableBefore(end, time);
foreach ( Resource *r, required ) {
time = r->availableAfter( time, end );
end = r->availableBefore( end, time );
if ( ! ( time.isValid() && end.isValid() ) ) {
#ifndef PLAN_NLOGDEBUG
if ( m_currentSchedule ) m_currentSchedule->logDebug( "The required resource '" + r->name() + "'is not available in interval:" + node->startTime.toString() + ',' + node->endTime.toString() );
#endif
break;
}
}
if (!end.isValid()) {
m_currentSchedule->logWarning( i18n( "Resource %1 not available in interval: %2 to %3", m_name, locale.toString(time, QLocale::ShortFormat), locale.toString(node->endTime, QLocale::ShortFormat) ) );
node->resourceNotAvailable = true;
return;
}
//debugPlan<<time.toString()<<" to"<<end.toString();
makeAppointment(node, time, end, load, required);
}
AppointmentIntervalList Resource::workIntervals( const DateTime &from, const DateTime &until ) const
{
return workIntervals( from, until, 0 );
}
AppointmentIntervalList Resource::workIntervals( const DateTime &from, const DateTime &until, Schedule *sch ) const
{
Calendar *cal = calendar();
if ( cal == 0 ) {
return AppointmentIntervalList();
}
// update cache
calendarIntervals( from, until );
AppointmentIntervalList work = m_workinfocache.intervals.extractIntervals( from, until );
if ( sch && ! sch->allowOverbooking() ) {
foreach ( const Appointment *a, sch->appointments( sch->calculationMode() ) ) {
work -= a->intervals();
}
foreach ( const Appointment *a, m_externalAppointments ) {
work -= a->intervals();
}
}
return work;
}
void Resource::calendarIntervals( const DateTime &from, const DateTime &until ) const
{
Calendar *cal = calendar();
if ( cal == 0 ) {
m_workinfocache.clear();
return;
}
if ( cal->cacheVersion() != m_workinfocache.version ) {
m_workinfocache.clear();
m_workinfocache.version = cal->cacheVersion();
}
if ( ! m_workinfocache.isValid() ) {
// First time
// debugPlan<<"First time:"<<from<<until;
m_workinfocache.start = from;
m_workinfocache.end = until;
m_workinfocache.intervals = cal->workIntervals( from, until, m_units );
// debugPlan<<"calendarIntervals (first):"<<m_workinfocache.intervals;
} else {
if ( from < m_workinfocache.start ) {
// debugPlan<<"Add to start:"<<from<<m_workinfocache.start;
m_workinfocache.intervals += cal->workIntervals( from, m_workinfocache.start, m_units );
m_workinfocache.start = from;
debugPlan<<"calendarIntervals (start):"<<m_workinfocache.intervals;
}
if ( until > m_workinfocache.end ) {
// debugPlan<<"Add to end:"<<m_workinfocache.end<<until;
m_workinfocache.intervals += cal->workIntervals( m_workinfocache.end, until, m_units );
m_workinfocache.end = until;
debugPlan<<"calendarIntervals: (end)"<<m_workinfocache.intervals;
}
}
}
bool Resource::loadCalendarIntervalsCache( const KoXmlElement &element, XMLLoaderObject &status )
{
KoXmlElement e = element.namedItem( "work-intervals-cache" ).toElement();
if ( e.isNull() ) {
errorPlan<<"No 'work-intervals-cache' element";
return true;
}
m_workinfocache.load( e, status );
return true;
}
void Resource::saveCalendarIntervalsCache( QDomElement &element ) const
{
QDomElement me = element.ownerDocument().createElement("work-intervals-cache");
element.appendChild(me);
m_workinfocache.save( me );
}
DateTime Resource::WorkInfoCache::firstAvailableAfter( const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch ) const
{
QMultiMap<QDate, AppointmentInterval>::const_iterator it = intervals.map().constEnd();
if ( start.isValid() && start <= time ) {
// possibly useful cache
it = intervals.map().lowerBound( time.date() );
}
if ( it == intervals.map().constEnd() ) {
// nothing cached, check the old way
DateTime t = cal ? cal->firstAvailableAfter( time, limit, sch ) : DateTime();
return t;
}
AppointmentInterval inp( time, limit );
for ( ; it != intervals.map().constEnd() && it.key() <= limit.date(); ++it ) {
if ( ! it.value().intersects( inp ) && it.value() < inp ) {
continue;
}
if ( sch ) {
DateTimeInterval ti = sch->available( DateTimeInterval( it.value().startTime(), it.value().endTime() ) );
if ( ti.isValid() && ti.first < limit ) {
ti.first = qMax( ti.first, time );
return ti.first;
}
} else {
DateTime t = qMax( it.value().startTime(), time );
return t;
}
}
if ( it == intervals.map().constEnd() ) {
// ran out of cache, check the old way
DateTime t = cal ? cal->firstAvailableAfter( time, limit, sch ) : DateTime();
return t;
}
return DateTime();
}
DateTime Resource::WorkInfoCache::firstAvailableBefore( const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch ) const
{
if ( time <= limit ) {
return DateTime();
}
QMultiMap<QDate, AppointmentInterval>::const_iterator it = intervals.map().constBegin();
if ( time.isValid() && limit.isValid() && end.isValid() && end >= time && ! intervals.isEmpty() ) {
// possibly useful cache
it = intervals.map().upperBound( time.date() );
}
if ( it == intervals.map().constBegin() ) {
// nothing cached, check the old way
DateTime t = cal ? cal->firstAvailableBefore( time, limit, sch ) : DateTime();
return t;
}
AppointmentInterval inp( limit, time );
for ( --it; it != intervals.map().constBegin() && it.key() >= limit.date(); --it ) {
if ( ! it.value().intersects( inp ) && inp < it.value() ) {
continue;
}
if ( sch ) {
DateTimeInterval ti = sch->available( DateTimeInterval( it.value().startTime(), it.value().endTime() ) );
if ( ti.isValid() && ti.second > limit ) {
ti.second = qMin( ti.second, time );
return ti.second;
}
} else {
DateTime t = qMin( it.value().endTime(), time );
return t;
}
}
if ( it == intervals.map().constBegin() ) {
// ran out of cache, check the old way
DateTime t = cal ? cal->firstAvailableBefore( time, limit, sch ) : DateTime();
return t;
}
return DateTime();
}
bool Resource::WorkInfoCache::load( const KoXmlElement &element, XMLLoaderObject &status )
{
clear();
version = element.attribute( "version" ).toInt();
effort = Duration::fromString( element.attribute( "effort" ) );
start = DateTime::fromString( element.attribute( "start" ) );
end = DateTime::fromString( element.attribute( "end" ) );
KoXmlElement e = element.namedItem( "intervals" ).toElement();
if ( ! e.isNull() ) {
intervals.loadXML( e, status );
}
//debugPlan<<*this;
return true;
}
void Resource::WorkInfoCache::save( QDomElement &element ) const
{
element.setAttribute( "version", QString::number(version) );
element.setAttribute( "effort", effort.toString() );
element.setAttribute( "start", start.toString( Qt::ISODate ) );
element.setAttribute( "end", end.toString( Qt::ISODate ) );
QDomElement me = element.ownerDocument().createElement("intervals");
element.appendChild(me);
intervals.saveXML( me );
}
Duration Resource::effort( const DateTime& start, const Duration& duration, int units, bool backward, const QList< Resource* >& required ) const
{
return effort( m_currentSchedule, start, duration, units, backward, required );
}
// the amount of effort we can do within the duration
Duration Resource::effort( Schedule *sch, const DateTime &start, const Duration &duration, int units, bool backward, const QList<Resource*> &required ) const
{
//debugPlan<<m_name<<": ("<<(backward?"B )":"F )")<<start<<" for duration"<<duration.toString(Duration::Format_Day);
#if 0
if ( sch ) sch->logDebug( QString( "Check effort in interval %1: %2, %3").arg(backward?"backward":"forward").arg( start.toString() ).arg( (backward?start-duration:start+duration).toString() ) );
#endif
Duration e;
if ( duration == 0 || m_units == 0 || units == 0 ) {
warnPlan<<"zero duration or zero units";
return e;
}
if ( m_type == Type_Team ) {
errorPlan<<"A team resource cannot deliver any effort";
return e;
}
Calendar *cal = calendar();
if ( cal == 0 ) {
if ( sch ) sch->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) );
return e;
}
DateTime from;
DateTime until;
if ( backward ) {
from = availableAfter( start - duration, start, sch );
until = availableBefore( start, start - duration, sch );
} else {
from = availableAfter( start, start + duration, sch );
until = availableBefore( start + duration, start, sch );
}
if ( ! ( from.isValid() && until.isValid() ) ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Resource not available in interval:" + start.toString() + ',' + (start+duration).toString() );
#endif
} else {
foreach ( Resource *r, required ) {
from = r->availableAfter( from, until );
until = r->availableBefore( until, from );
if ( ! ( from.isValid() && until.isValid() ) ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "The required resource '" + r->name() + "'is not available in interval:" + start.toString() + ',' + (start+duration).toString() );
#endif
break;
}
}
}
if ( from.isValid() && until.isValid() ) {
#ifndef PLAN_NLOGDEBUG
if ( sch && until < from ) sch->logDebug( " until < from: until=" + until.toString() + " from=" + from.toString() );
#endif
e = workIntervals( from, until ).effort( from, until ) * units / 100;
if ( sch && ( ! sch->allowOverbooking() || sch->allowOverbookingState() == Schedule::OBS_Deny ) ) {
Duration avail = workIntervals( from, until, sch ).effort( from, until );
if ( avail < e ) {
e = avail;
}
}
// e = ( cal->effort( from, until, sch ) ) * m_units / 100;
}
//debugPlan<<m_name<<start<<" e="<<e.toString(Duration::Format_Day)<<" ("<<m_units<<")";
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( QString( "effort: %1 for %2 hours = %3" ).arg( start.toString() ).arg( duration.toString( Duration::Format_HourFraction ) ).arg( e.toString( Duration::Format_HourFraction ) ) );
#endif
return e;
}
DateTime Resource::availableAfter(const DateTime &time, const DateTime &limit ) const {
return availableAfter( time, limit, m_currentSchedule );
}
DateTime Resource::availableBefore(const DateTime &time, const DateTime &limit) const {
return availableBefore( time, limit, m_currentSchedule );
}
DateTime Resource::availableAfter(const DateTime &time, const DateTime &limit, Schedule *sch) const {
debugPlan<<time<<limit;
DateTime t;
if (m_units == 0) {
debugPlan<<this<<"zero units";
return t;
}
DateTime lmt = m_availableUntil.isValid() ? m_availableUntil : ( m_project ? m_project->constraintEndTime() : DateTime() );
if (limit.isValid() && limit < lmt) {
lmt = limit;
}
if (time >= lmt) {
debugPlan<<this<<"time >= limit"<<time<<lmt<<m_project;
return t;
}
Calendar *cal = calendar();
if (cal == 0) {
if ( sch ) sch->logWarning( i18n( "Resource %1 has no calendar defined", m_name ) );
debugPlan<<this<<"No calendar";
return t;
}
DateTime availableFrom = m_availableFrom.isValid() ? m_availableFrom : ( m_project ? m_project->constraintStartTime() : DateTime() );
t = availableFrom > time ? availableFrom : time;
if ( t >= lmt ) {
debugPlan<<this<<t<<lmt;
return DateTime();
}
QTimeZone tz = cal->timeZone();
t = t.toTimeZone( tz );
lmt = lmt.toTimeZone( tz );
t = m_workinfocache.firstAvailableAfter( t, lmt, cal, sch );
// t = cal->firstAvailableAfter(t, lmt, sch);
//debugPlan<<m_currentSchedule<<""<<m_name<<" id="<<m_currentSchedule->id()<<" mode="<<m_currentSchedule->calculationMode()<<" returns:"<<time.toString()<<"="<<t.toString()<<""<<lmt.toString();
return t;
}
DateTime Resource::availableBefore(const DateTime &time, const DateTime &limit, Schedule *sch) const {
DateTime t;
if (m_units == 0) {
return t;
}
DateTime lmt = m_availableFrom.isValid() ? m_availableFrom : ( m_project ? m_project->constraintStartTime() : DateTime() );
if (limit.isValid() && limit > lmt) {
lmt = limit;
}
if (time <= lmt) {
return t;
}
Calendar *cal = calendar();
if (cal == 0) {
return t;
}
DateTime availableUntil = m_availableUntil.isValid() ? m_availableUntil : ( m_project ? m_project->constraintEndTime() : DateTime() );
if ( ! availableUntil.isValid() ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "availableUntil is invalid" );
#endif
t = time;
} else {
t = availableUntil < time ? availableUntil : time;
}
#ifndef PLAN_NLOGDEBUG
if ( sch && t < lmt ) sch->logDebug( "t < lmt: " + t.toString() + " < " + lmt.toString() );
#endif
QTimeZone tz = cal->timeZone();
t = t.toTimeZone( tz );
lmt = lmt.toTimeZone( tz );
t = m_workinfocache.firstAvailableBefore( t, lmt, cal, sch );
// t = cal->firstAvailableBefore(t, lmt, sch );
#ifndef PLAN_NLOGDEBUG
if ( sch && t.isValid() && t < lmt ) sch->logDebug( " t < lmt: t=" + t.toString() + " lmt=" + lmt.toString() );
#endif
return t;
}
Resource *Resource::findId(const QString &id) const {
return m_project ? m_project->findResource(id) : 0;
}
bool Resource::removeId(const QString &id) {
return m_project ? m_project->removeResourceId(id) : false;
}
void Resource::insertId(const QString &id) {
//debugPlan;
if (m_project)
m_project->insertResourceId(id, this);
}
Calendar *Resource::findCalendar(const QString &id) const {
return (m_project ? m_project->findCalendar(id) : 0);
}
bool Resource::isOverbooked() const {
return isOverbooked( DateTime(), DateTime() );
}
bool Resource::isOverbooked(const QDate &date) const {
return isOverbooked( DateTime( date ), DateTime( date.addDays(1 ) ) );
}
bool Resource::isOverbooked(const DateTime &start, const DateTime &end) const {
//debugPlan<<m_name<<":"<<start.toString()<<" -"<<end.toString()<<" cs=("<<m_currentSchedule<<")";
return m_currentSchedule ? m_currentSchedule->isOverbooked(start, end) : false;
}
Appointment Resource::appointmentIntervals( long id ) const {
Appointment a;
Schedule *s = findSchedule( id );
if ( s == 0 ) {
return a;
}
foreach (Appointment *app, static_cast<ResourceSchedule*>( s )->appointments()) {
a += *app;
}
return a;
}
Appointment Resource::appointmentIntervals() const {
Appointment a;
if (m_currentSchedule == 0)
return a;
foreach (Appointment *app, m_currentSchedule->appointments()) {
a += *app;
}
return a;
}
EffortCostMap Resource::plannedEffortCostPrDay( const QDate &start, const QDate &end, long id, EffortCostCalculationType typ )
{
EffortCostMap ec;
Schedule *s = findSchedule( id );
if ( s == 0 ) {
return ec;
}
ec = s->plannedEffortCostPrDay( start, end, typ );
return ec;
}
Duration Resource::plannedEffort( const QDate &date, EffortCostCalculationType typ ) const
{
return m_currentSchedule ? m_currentSchedule->plannedEffort( date, typ ) : Duration::zeroDuration;
}
void Resource::setProject( Project *project )
{
if ( project != m_project ) {
if ( m_project ) {
removeId();
}
}
m_project = project;
}
void Resource::addExternalAppointment(const QString& id, Appointment* a)
{
int row = -1;
if ( m_externalAppointments.contains( id ) ) {
int row = m_externalAppointments.keys().indexOf( id ); // clazy:exclude=container-anti-pattern
emit externalAppointmentToBeRemoved( this, row );
delete m_externalAppointments.take( id );
emit externalAppointmentRemoved();
}
if ( row == -1 ) {
m_externalAppointments[ id ] = a;
row = m_externalAppointments.keys().indexOf( id ); // clazy:exclude=container-anti-pattern
m_externalAppointments.remove( id );
}
emit externalAppointmentToBeAdded( this, row );
m_externalAppointments[ id ] = a;
emit externalAppointmentAdded( this, a );
}
void Resource::addExternalAppointment( const QString &id, const QString &name, const DateTime &from, const DateTime &end, double load )
{
Appointment *a = m_externalAppointments.value( id );
if ( a == 0 ) {
a = new Appointment();
a->setAuxcilliaryInfo( name );
a->addInterval( from, end, load );
//debugPlan<<m_name<<name<<"new appointment:"<<a<<from<<end<<load;
m_externalAppointments[ id ] = a;
int row = m_externalAppointments.keys().indexOf( id ); // clazy:exclude=container-anti-pattern
m_externalAppointments.remove( id );
emit externalAppointmentToBeAdded( this, row );
m_externalAppointments[ id ] = a;
emit externalAppointmentAdded( this, a );
} else {
//debugPlan<<m_name<<name<<"new interval:"<<a<<from<<end<<load;
a->addInterval( from, end, load );
emit externalAppointmentChanged( this, a );
}
}
void Resource::subtractExternalAppointment( const QString &id, const DateTime &start, const DateTime &end, double load )
{
Appointment *a = m_externalAppointments.value( id );
if ( a ) {
//debugPlan<<m_name<<name<<"new interval:"<<a<<from<<end<<load;
Appointment app;
app.addInterval( start, end, load );
*a -= app;
emit externalAppointmentChanged( this, a );
}
}
void Resource::clearExternalAppointments()
{
const QStringList keys = m_externalAppointments.keys();
foreach (const QString &id, keys) {
clearExternalAppointments( id );
}
}
void Resource::clearExternalAppointments( const QString &projectId )
{
while ( m_externalAppointments.contains( projectId ) ) {
int row = m_externalAppointments.keys().indexOf( projectId ); // clazy:exclude=container-anti-pattern
emit externalAppointmentToBeRemoved( this, row );
Appointment *a = m_externalAppointments.take( projectId );
emit externalAppointmentRemoved();
delete a;
}
}
Appointment *Resource::takeExternalAppointment( const QString &id )
{
Appointment *a = 0;
if ( m_externalAppointments.contains( id ) ) {
int row = m_externalAppointments.keys().indexOf( id ); // clazy:exclude=container-anti-pattern
emit externalAppointmentToBeRemoved( this, row );
a = m_externalAppointments.take( id );
emit externalAppointmentRemoved();
}
return a;
}
AppointmentIntervalList Resource::externalAppointments( const QString &id )
{
if ( ! m_externalAppointments.contains( id ) ) {
return AppointmentIntervalList();
}
return m_externalAppointments[ id ]->intervals();
}
AppointmentIntervalList Resource::externalAppointments( const DateTimeInterval &interval ) const
{
//debugPlan<<m_externalAppointments;
Appointment app;
foreach ( Appointment *a, m_externalAppointments ) {
app += interval.isValid() ? a->extractIntervals( interval ) : *a;
}
return app.intervals();
}
QMap<QString, QString> Resource::externalProjects() const
{
QMap<QString, QString> map;
for ( QMapIterator<QString, Appointment*> it( m_externalAppointments ); it.hasNext(); ) {
it.next();
if ( ! map.contains( it.key() ) ) {
map[ it.key() ] = it.value()->auxcilliaryInfo();
}
}
// debugPlan<<map;
return map;
}
long Resource::allocationSuitability( const DateTime &time, const Duration &duration, bool backward )
{
// FIXME: This is not *very* intelligent...
Duration e;
if ( m_type == Type_Team ) {
foreach ( Resource *r, teamMembers() ) {
e += r->effort( time, duration, 100, backward );
}
} else {
e = effort( time, duration, 100, backward );
}
return e.minutes();
}
DateTime Resource::startTime( long id ) const
{
DateTime dt;
Schedule *s = schedule( id );
if ( s == 0 ) {
return dt;
}
foreach ( Appointment *a, s->appointments() ) {
DateTime t = a->startTime();
if ( ! dt.isValid() || t < dt ) {
dt = t;
}
}
return dt;
}
DateTime Resource::endTime( long id ) const
{
DateTime dt;
Schedule *s = schedule( id );
if ( s == 0 ) {
return dt;
}
foreach ( Appointment *a, s->appointments() ) {
DateTime t = a->endTime();
if ( ! dt.isValid() || t > dt ) {
dt = t;
}
}
return dt;
}
QList<Resource*> Resource::teamMembers() const
{
QList<Resource*> lst;
foreach ( const QString &s, m_teamMembers ) {
Resource *r = findId( s );
if ( r ) {
lst << r;
}
}
return lst;
}
QStringList Resource::teamMemberIds() const
{
return m_teamMembers;
}
void Resource::addTeamMemberId( const QString &id )
{
if ( ! id.isEmpty() && ! m_teamMembers.contains( id ) ) {
m_teamMembers.append( id );
}
}
void Resource::removeTeamMemberId( const QString &id )
{
if ( m_teamMembers.contains( id ) ) {
m_teamMembers.removeAt( m_teamMembers.indexOf( id ) );
}
}
void Resource::setTeamMemberIds(const QStringList &ids)
{
m_teamMembers = ids;
}
bool Resource::isShared() const
{
return m_shared;
}
void Resource::setShared(bool on)
{
m_shared = on;
}
QDebug operator<<( QDebug dbg, const KPlato::Resource::WorkInfoCache &c )
{
dbg.nospace()<<"WorkInfoCache: ["<<" version="<<c.version<<" start="<<c.start.toString( Qt::ISODate )<<" end="<<c.end.toString( Qt::ISODate )<<" intervals="<<c.intervals.map().count();
if ( ! c.intervals.isEmpty() ) {
foreach ( const AppointmentInterval &i, c.intervals.map() ) {
dbg<<endl<<" "<<i;
}
}
dbg<<"]";
return dbg;
}
///////// Risk /////////
Risk::Risk(Node *n, Resource *r, RiskType rt) {
m_node=n;
m_resource=r;
m_riskType=rt;
}
Risk::~Risk() {
}
ResourceRequest::ResourceRequest(Resource *resource, int units)
: m_resource(resource),
m_units(units),
m_parent(0),
m_dynamic(false)
{
if ( resource ) {
m_required = resource->requiredResources();
}
//debugPlan<<"("<<this<<") Request to:"<<(resource ? resource->name() : QString("None"));
}
ResourceRequest::ResourceRequest(const ResourceRequest &r)
: m_resource(r.m_resource),
m_units(r.m_units),
m_parent(0),
m_dynamic(r.m_dynamic),
m_required(r.m_required)
{
}
ResourceRequest::~ResourceRequest() {
//debugPlan<<"("<<this<<") resource:"<<(m_resource ? m_resource->name() : QString("None"));
if (m_resource)
m_resource->unregisterRequest(this);
m_resource = 0;
qDeleteAll( m_teamMembers );
}
bool ResourceRequest::load(KoXmlElement &element, Project &project) {
//debugPlan;
m_resource = project.resource(element.attribute("resource-id"));
if (m_resource == 0) {
warnPlan<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id");
return false;
}
m_units = element.attribute("units").toInt();
KoXmlElement parent = element.namedItem( "required-resources" ).toElement();
KoXmlElement e;
forEachElement( e, parent ) {
if (e.nodeName() == "resource") {
QString id = e.attribute( "id" );
if ( id.isEmpty() ) {
errorPlan<<"Missing project id";
continue;
}
Resource *r = project.resource(id);
if (r == 0) {
warnPlan<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id");
} else {
if ( r != m_resource ) {
m_required << r;
}
}
}
}
return true;
}
void ResourceRequest::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("resource-request");
element.appendChild(me);
me.setAttribute("resource-id", m_resource->id());
me.setAttribute("units", QString::number(m_units));
if ( ! m_required.isEmpty() ) {
QDomElement e = me.ownerDocument().createElement("required-resources");
me.appendChild(e);
foreach ( Resource *r, m_required ) {
QDomElement el = e.ownerDocument().createElement("resource");
e.appendChild( el );
el.setAttribute( "id", r->id() );
}
}
}
int ResourceRequest::units() const {
//debugPlan<<m_resource->name()<<": units="<<m_units;
return m_units;
}
void ResourceRequest::setUnits( int value )
{
m_units = value; changed();
}
Task *ResourceRequest::task() const {
return m_parent ? m_parent->task() : 0;
}
void ResourceRequest::changed()
{
if ( task() ) {
task()->changed();
}
}
void ResourceRequest::setCurrentSchedulePtr( Schedule *ns )
{
setCurrentSchedulePtr( m_resource, ns );
}
void ResourceRequest::setCurrentSchedulePtr( Resource *resource, Schedule *ns )
{
resource->setCurrentSchedulePtr( resourceSchedule( ns, resource ) );
if( resource->type() == Resource::Type_Team ) {
foreach ( Resource *member, resource->teamMembers() ) {
member->setCurrentSchedulePtr( resourceSchedule( ns, member ) );
}
}
foreach ( Resource *r, m_required ) {
r->setCurrentSchedulePtr( resourceSchedule( ns, r ) );
}
}
Schedule *ResourceRequest::resourceSchedule( Schedule *ns, Resource *res )
{
if ( ns == 0 ) {
return 0;
}
Resource *r = res == 0 ? resource() : res;
Schedule *s = r->findSchedule(ns->id());
if (s == 0) {
s = r->createSchedule(ns->parent());
}
s->setCalculationMode( ns->calculationMode() );
s->setAllowOverbookingState( ns->allowOverbookingState() );
static_cast<ResourceSchedule*>( s )->setNodeSchedule( ns );
//debugPlan<<s->name()<<": id="<<s->id()<<" mode="<<s->calculationMode();
return s;
}
DateTime ResourceRequest::workTimeAfter(const DateTime &dt, Schedule *ns) {
if ( m_resource->type() == Resource::Type_Work ) {
DateTime t = availableAfter( dt, ns );
foreach ( Resource *r, m_required ) {
if ( ! t.isValid() ) {
break;
}
t = r->availableAfter( t, DateTime(), resourceSchedule( ns, r ) );
}
return t;
} else if ( m_resource->type() == Resource::Type_Team ) {
return availableAfter( dt, ns );
}
return DateTime();
}
DateTime ResourceRequest::workTimeBefore(const DateTime &dt, Schedule *ns) {
if ( m_resource->type() == Resource::Type_Work ) {
DateTime t = availableBefore( dt, ns );
foreach ( Resource *r, m_required ) {
if ( ! t.isValid() ) {
break;
}
t = r->availableBefore( t, DateTime(), resourceSchedule( ns, r ) );
}
return t;
} else if ( m_resource->type() == Resource::Type_Team ) {
return availableBefore( dt, ns );
}
return DateTime();
}
DateTime ResourceRequest::availableFrom()
{
DateTime dt = m_resource->availableFrom();
if ( ! dt.isValid() ) {
dt = m_resource->project()->constraintStartTime();
}
return dt;
}
DateTime ResourceRequest::availableUntil()
{
DateTime dt = m_resource->availableUntil();
if ( ! dt.isValid() ) {
dt = m_resource->project()->constraintEndTime();
}
return dt;
}
DateTime ResourceRequest::availableAfter(const DateTime &time, Schedule *ns) {
if ( m_resource->type() == Resource::Type_Team ) {
DateTime t;// = m_resource->availableFrom();
foreach ( Resource *r, m_resource->teamMembers() ) {
setCurrentSchedulePtr( r, ns );
DateTime x = r->availableAfter( time );
if ( x.isValid() ) {
t = t.isValid() ? qMin( t, x ) : x;
}
}
return t;
}
setCurrentSchedulePtr( ns );
return m_resource->availableAfter( time );
}
DateTime ResourceRequest::availableBefore(const DateTime &time, Schedule *ns) {
if ( m_resource->type() == Resource::Type_Team ) {
DateTime t;
foreach ( Resource *r, m_resource->teamMembers() ) {
setCurrentSchedulePtr( r, ns );
DateTime x = r->availableBefore( time );
if ( x.isValid() ) {
t = t.isValid() ? qMax( t, x ) : x;
}
}
return t;
}
setCurrentSchedulePtr( ns );
return resource()->availableBefore( time );
}
Duration ResourceRequest::effort( const DateTime &time, const Duration &duration, Schedule *ns, bool backward )
{
setCurrentSchedulePtr( ns );
Duration e = m_resource->effort( time, duration, m_units, backward, m_required );
//debugPlan<<m_resource->name()<<time<<duration.toString()<<"delivers:"<<e.toString()<<"request:"<<(m_units/100)<<"parts";
return e;
}
void ResourceRequest::makeAppointment( Schedule *ns )
{
if ( m_resource ) {
setCurrentSchedulePtr( ns );
m_resource->makeAppointment( ns, ( m_resource->units() * m_units / 100 ), m_required );
}
}
void ResourceRequest::makeAppointment( Schedule *ns, int amount )
{
if ( m_resource ) {
setCurrentSchedulePtr( ns );
m_resource->makeAppointment( ns, amount, m_required );
}
}
long ResourceRequest::allocationSuitability( const DateTime &time, const Duration &duration, Schedule *ns, bool backward )
{
setCurrentSchedulePtr( ns );
return resource()->allocationSuitability( time, duration, backward );
}
QList<ResourceRequest*> ResourceRequest::teamMembers() const
{
qDeleteAll( m_teamMembers );
m_teamMembers.clear();
if ( m_resource->type() == Resource::Type_Team ) {
foreach ( Resource *r, m_resource->teamMembers() ) {
m_teamMembers << new ResourceRequest( r, m_units );
}
}
return m_teamMembers;
}
QDebug &operator<<( QDebug &dbg, const KPlato::ResourceRequest *rr )
{
if (rr) {
dbg<<*rr;
} else {
dbg<<(void*)rr;
}
return dbg;
}
QDebug &operator<<( QDebug &dbg, const KPlato::ResourceRequest &rr )
{
if (rr.resource()) {
dbg<<"ResourceRequest["<<rr.resource()->name()<<']';
} else {
dbg<<"ResourceRequest[No resource]";
}
return dbg;
}
/////////
ResourceGroupRequest::ResourceGroupRequest(ResourceGroup *group, int units)
: m_group(group), m_units(units), m_parent(0) {
//debugPlan<<"Request to:"<<(group ? group->name() : QString("None"));
if (group)
group->registerRequest(this);
}
ResourceGroupRequest::ResourceGroupRequest(const ResourceGroupRequest &g)
: m_group(g.m_group), m_units(g.m_units), m_parent(0)
{
}
ResourceGroupRequest::~ResourceGroupRequest() {
//debugPlan;
if (m_group)
m_group->unregisterRequest(this);
while (!m_resourceRequests.isEmpty()) {
delete m_resourceRequests.takeFirst();
}
}
void ResourceGroupRequest::addResourceRequest(ResourceRequest *request) {
//debugPlan<<"("<<request<<") to Group:"<<(void*)m_group;
request->setParent(this);
m_resourceRequests.append(request);
request->registerRequest();
changed();
}
ResourceRequest *ResourceGroupRequest::takeResourceRequest(ResourceRequest *request) {
if (request)
request->unregisterRequest();
ResourceRequest *r = 0;
int i = m_resourceRequests.indexOf(request);
if (i != -1) {
r = m_resourceRequests.takeAt(i);
}
changed();
return r;
}
ResourceRequest *ResourceGroupRequest::find(const Resource *resource) const {
foreach (ResourceRequest *gr, m_resourceRequests) {
if (gr->resource() == resource) {
return gr;
}
}
return 0;
}
ResourceRequest *ResourceGroupRequest::resourceRequest( const QString &name ) {
foreach (ResourceRequest *r, m_resourceRequests) {
if (r->resource()->name() == name )
return r;
}
return 0;
}
QStringList ResourceGroupRequest::requestNameList( bool includeGroup ) const {
QStringList lst;
if ( includeGroup && m_units > 0 && m_group ) {
lst << m_group->name();
}
foreach ( ResourceRequest *r, m_resourceRequests ) {
if ( ! r->isDynamicallyAllocated() ) {
Q_ASSERT( r->resource() );
lst << r->resource()->name();
}
}
return lst;
}
QList<Resource*> ResourceGroupRequest::requestedResources() const
{
QList<Resource*> lst;
foreach ( ResourceRequest *r, m_resourceRequests ) {
if ( ! r->isDynamicallyAllocated() ) {
Q_ASSERT( r->resource() );
lst << r->resource();
}
}
return lst;
}
QList<ResourceRequest*> ResourceGroupRequest::resourceRequests( bool resolveTeam ) const
{
QList<ResourceRequest*> lst;
foreach ( ResourceRequest *rr, m_resourceRequests ) {
if ( resolveTeam && rr->resource()->type() == Resource::Type_Team ) {
lst += rr->teamMembers();
} else {
lst << rr;
}
}
return lst;
}
bool ResourceGroupRequest::load(KoXmlElement &element, XMLLoaderObject &status) {
//debugPlan;
m_group = status.project().findResourceGroup(element.attribute("group-id"));
if (m_group == 0) {
errorPlan<<"The referenced resource group does not exist: group id="<<element.attribute("group-id");
return false;
}
m_group->registerRequest(this);
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "resource-request") {
ResourceRequest *r = new ResourceRequest();
if (r->load(e, status.project()))
addResourceRequest(r);
else {
errorPlan<<"Failed to load resource request";
delete r;
}
}
}
// meaning of m_units changed
// Pre 0.6.6 the number *included* all requests, now it is in *addition* to resource requests
m_units = element.attribute("units").toInt();
if ( status.version() < "0.6.6" ) {
int x = m_units - m_resourceRequests.count();
m_units = x > 0 ? x : 0;
}
return true;
}
void ResourceGroupRequest::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("resourcegroup-request");
element.appendChild(me);
me.setAttribute("group-id", m_group->id());
me.setAttribute("units", QString::number(m_units));
foreach (ResourceRequest *r, m_resourceRequests)
r->save(me);
}
int ResourceGroupRequest::units() const {
return m_units;
}
Duration ResourceGroupRequest::duration(const DateTime &time, const Duration &_effort, Schedule *ns, bool backward) {
Duration dur;
if ( m_parent ) {
dur = m_parent->duration( m_resourceRequests, time, _effort, ns, backward );
}
return dur;
}
DateTime ResourceGroupRequest::workTimeAfter(const DateTime &time, Schedule *ns) {
DateTime start;
if ( m_resourceRequests.isEmpty() ) {
return start;
}
foreach (ResourceRequest *r, m_resourceRequests) {
DateTime t = r->workTimeAfter( time, ns );
if (t.isValid() && (!start.isValid() || t < start))
start = t;
}
if (start.isValid() && start < time)
start = time;
//debugPlan<<time.toString()<<"="<<start.toString();
return start;
}
DateTime ResourceGroupRequest::workTimeBefore(const DateTime &time, Schedule *ns) {
DateTime end;
if ( m_resourceRequests.isEmpty() ) {
return end;
}
foreach (ResourceRequest *r, m_resourceRequests) {
DateTime t = r->workTimeBefore( time, ns );
if (t.isValid() && (!end.isValid() ||t > end))
end = t;
}
if (!end.isValid() || end > time)
end = time;
return end;
}
DateTime ResourceGroupRequest::availableAfter(const DateTime &time, Schedule *ns) {
DateTime start;
if ( m_resourceRequests.isEmpty() ) {
return start;
}
foreach (ResourceRequest *r, m_resourceRequests) {
DateTime t = r->availableAfter(time, ns);
if (t.isValid() && (!start.isValid() || t < start))
start = t;
}
if (start.isValid() && start < time)
start = time;
//debugPlan<<time.toString()<<"="<<start.toString()<<""<<m_group->name();
return start;
}
DateTime ResourceGroupRequest::availableBefore(const DateTime &time, Schedule *ns) {
DateTime end;
if ( m_resourceRequests.isEmpty() ) {
return end;
}
foreach (ResourceRequest *r, m_resourceRequests) {
DateTime t = r->availableBefore(time, ns);
if (t.isValid() && (!end.isValid() || t > end))
end = t;
}
if (!end.isValid() || end > time)
end = time;
//debugPlan<<time.toString()<<"="<<end.toString()<<""<<m_group->name();
return end;
}
void ResourceGroupRequest::makeAppointments(Schedule *schedule) {
//debugPlan;
foreach (ResourceRequest *r, m_resourceRequests) {
r->makeAppointment(schedule);
}
}
void ResourceGroupRequest::reserve(const DateTime &start, const Duration &duration) {
m_start = start;
m_duration = duration;
}
bool ResourceGroupRequest::isEmpty() const {
return m_resourceRequests.isEmpty() && m_units == 0;
}
Task *ResourceGroupRequest::task() const {
return m_parent ? m_parent->task() : 0;
}
void ResourceGroupRequest::changed()
{
if ( m_parent )
m_parent->changed();
}
void ResourceGroupRequest::deleteResourceRequest( ResourceRequest *request )
{
int i = m_resourceRequests.indexOf( request );
if ( i != -1 ) {
m_resourceRequests.removeAt( i );
}
delete request;
changed();
}
void ResourceGroupRequest::resetDynamicAllocations()
{
QList<ResourceRequest*> lst;
foreach ( ResourceRequest *r, m_resourceRequests ) {
if ( r->isDynamicallyAllocated() ) {
lst << r;
}
}
while ( ! lst.isEmpty() ) {
deleteResourceRequest( lst.takeFirst() );
}
}
void ResourceGroupRequest::allocateDynamicRequests( const DateTime &time, const Duration &effort, Schedule *ns, bool backward )
{
int num = m_units;
if ( num <= 0 ) {
return;
}
if ( num == m_group->numResources() ) {
// TODO: allocate all
}
Duration e = effort / m_units;
QMap<long, ResourceRequest*> map;
foreach ( Resource *r, m_group->resources() ) {
if ( r->type() == Resource::Type_Team ) {
continue;
}
ResourceRequest *rr = find( r );
if ( rr ) {
if ( rr->isDynamicallyAllocated() ) {
--num; // already allocated
}
continue;
}
rr = new ResourceRequest( r, r->units() );
long s = rr->allocationSuitability( time, e, ns, backward );
if ( s == 0 ) {
// not suitable at all
delete rr;
} else {
map.insertMulti( s, rr );
}
}
for ( --num; num >= 0 && ! map.isEmpty(); --num ) {
long key = map.lastKey();
ResourceRequest *r = map.take( key );
r->setAllocatedDynaically( true );
addResourceRequest( r );
debugPlan<<key<<r;
}
qDeleteAll( map ); // delete the unused
}
/////////
ResourceRequestCollection::ResourceRequestCollection(Task *task)
: m_task(task) {
//debugPlan<<this<<(void*)(&task);
}
ResourceRequestCollection::~ResourceRequestCollection() {
//debugPlan<<this;
while (!m_requests.empty()) {
delete m_requests.takeFirst();
}
}
void ResourceRequestCollection::addRequest( ResourceGroupRequest *request )
{
foreach ( ResourceGroupRequest *r, m_requests ) {
if ( r->group() == request->group() ) {
errorPlan<<"Request to this group already exists";
errorPlan<<"Task:"<<m_task->name()<<"Group:"<<request->group()->name();
Q_ASSERT( false );
}
}
m_requests.append( request );
request->setParent( this );
changed();
}
ResourceGroupRequest *ResourceRequestCollection::find(const ResourceGroup *group) const {
foreach (ResourceGroupRequest *r, m_requests) {
if (r->group() == group)
return r; // we assume only one request to the same group
}
return 0;
}
ResourceRequest *ResourceRequestCollection::find(const Resource *resource) const {
ResourceRequest *req = 0;
QListIterator<ResourceGroupRequest*> it(m_requests);
while (req == 0 && it.hasNext()) {
req = it.next()->find(resource);
}
return req;
}
ResourceRequest *ResourceRequestCollection::resourceRequest( const QString &name ) const {
ResourceRequest *req = 0;
QListIterator<ResourceGroupRequest*> it(m_requests);
while (req == 0 && it.hasNext()) {
req = it.next()->resourceRequest( name );
}
return req;
}
QStringList ResourceRequestCollection::requestNameList( bool includeGroup ) const {
QStringList lst;
foreach ( ResourceGroupRequest *r, m_requests ) {
lst << r->requestNameList( includeGroup );
}
return lst;
}
QList<Resource*> ResourceRequestCollection::requestedResources() const {
QList<Resource*> lst;
foreach ( ResourceGroupRequest *g, m_requests ) {
lst += g->requestedResources();
}
return lst;
}
QList<ResourceRequest*> ResourceRequestCollection::resourceRequests( bool resolveTeam ) const {
QList<ResourceRequest*> lst;
foreach ( ResourceGroupRequest *g, m_requests ) {
foreach ( ResourceRequest *r, g->resourceRequests( resolveTeam ) ) {
lst << r;
}
}
return lst;
}
bool ResourceRequestCollection::contains( const QString &identity ) const {
QStringList lst = requestNameList();
return lst.indexOf( QRegExp( identity, Qt::CaseSensitive, QRegExp::FixedString ) ) != -1;
}
ResourceGroupRequest *ResourceRequestCollection::findGroupRequestById( const QString &id ) const
{
foreach ( ResourceGroupRequest *r, m_requests ) {
if ( r->group()->id() == id ) {
return r;
}
}
return 0;
}
// bool ResourceRequestCollection::load(KoXmlElement &element, Project &project) {
// //debugPlan;
// return true;
// }
void ResourceRequestCollection::save(QDomElement &element) const {
//debugPlan;
foreach (ResourceGroupRequest *r, m_requests) {
r->save(element);
}
}
// Returns the duration needed by the working resources
// "Material type" of resourcegroups does not (atm) affect the duration.
Duration ResourceRequestCollection::duration(const DateTime &time, const Duration &effort, Schedule *ns, bool backward) {
//debugPlan<<"time="<<time.toString()<<" effort="<<effort.toString(Duration::Format_Day)<<" backward="<<backward;
if (isEmpty()) {
return effort;
}
Duration dur = effort;
QList<ResourceRequest*> lst;
foreach (ResourceGroupRequest *r, m_requests) {
r->allocateDynamicRequests( time, effort, ns, backward );
if ( r->group()->type() == ResourceGroup::Type_Work ) {
lst << r->resourceRequests();
} else if (r->group()->type() == ResourceGroup::Type_Material) {
//TODO
}
}
if ( ! lst.isEmpty() ) {
dur = duration( lst, time, effort, ns, backward );
}
return dur;
}
DateTime ResourceRequestCollection::workTimeAfter(const DateTime &time, Schedule *ns) const {
DateTime start;
foreach (ResourceGroupRequest *r, m_requests) {
DateTime t = r->workTimeAfter( time, ns );
if (t.isValid() && (!start.isValid() || t < start))
start = t;
}
if (start.isValid() && start < time)
start = time;
//debugPlan<<time.toString()<<"="<<start.toString();
return start;
}
DateTime ResourceRequestCollection::workTimeBefore(const DateTime &time, Schedule *ns) const {
DateTime end;
foreach (ResourceGroupRequest *r, m_requests) {
DateTime t = r->workTimeBefore( time, ns );
if (t.isValid() && (!end.isValid() ||t > end))
end = t;
}
if (!end.isValid() || end > time)
end = time;
return end;
}
DateTime ResourceRequestCollection::availableAfter(const DateTime &time, Schedule *ns) {
DateTime start;
foreach (ResourceGroupRequest *r, m_requests) {
DateTime t = r->availableAfter(time, ns);
if (t.isValid() && (!start.isValid() || t < start))
start = t;
}
if (start.isValid() && start < time)
start = time;
//debugPlan<<time.toString()<<"="<<start.toString();
return start;
}
DateTime ResourceRequestCollection::availableBefore(const DateTime &time, Schedule *ns) {
DateTime end;
foreach (ResourceGroupRequest *r, m_requests) {
DateTime t = r->availableBefore(time, ns);
if (t.isValid() && (!end.isValid() ||t > end))
end = t;
}
if (!end.isValid() || end > time)
end = time;
return end;
}
DateTime ResourceRequestCollection::workStartAfter(const DateTime &time, Schedule *ns) {
DateTime start;
foreach (ResourceGroupRequest *r, m_requests) {
if ( r->group()->type() != ResourceGroup::Type_Work ) {
continue;
}
DateTime t = r->availableAfter(time, ns);
if (t.isValid() && (!start.isValid() || t < start))
start = t;
}
if (start.isValid() && start < time)
start = time;
//debugPlan<<time.toString()<<"="<<start.toString();
return start;
}
DateTime ResourceRequestCollection::workFinishBefore(const DateTime &time, Schedule *ns) {
DateTime end;
foreach (ResourceGroupRequest *r, m_requests) {
if ( r->group()->type() != ResourceGroup::Type_Work ) {
continue;
}
DateTime t = r->availableBefore(time, ns);
if (t.isValid() && (!end.isValid() ||t > end))
end = t;
}
if (!end.isValid() || end > time)
end = time;
return end;
}
void ResourceRequestCollection::makeAppointments(Schedule *schedule) {
//debugPlan;
foreach (ResourceGroupRequest *r, m_requests) {
r->makeAppointments(schedule);
}
}
void ResourceRequestCollection::reserve(const DateTime &start, const Duration &duration) {
//debugPlan;
foreach (ResourceGroupRequest *r, m_requests) {
r->reserve(start, duration);
}
}
bool ResourceRequestCollection::isEmpty() const {
foreach (ResourceGroupRequest *r, m_requests) {
if (!r->isEmpty())
return false;
}
return true;
}
void ResourceRequestCollection::changed()
{
//debugPlan<<m_task;
if ( m_task ) {
m_task->changed();
}
}
void ResourceRequestCollection::resetDynamicAllocations()
{
foreach ( ResourceGroupRequest *g, m_requests ) {
g->resetDynamicAllocations();
}
}
Duration ResourceRequestCollection::effort( const QList<ResourceRequest*> &lst, const DateTime &time, const Duration &duration, Schedule *ns, bool backward ) const {
Duration e;
foreach (ResourceRequest *r, lst) {
e += r->effort( time, duration, ns, backward );
//debugPlan<<(backward?"(B)":"(F)" )<<r<<": time="<<time<<" dur="<<duration.toString()<<"gave e="<<e.toString();
}
//debugPlan<<time.toString()<<"d="<<duration.toString()<<": e="<<e.toString();
return e;
}
int ResourceRequestCollection::numDays(const QList<ResourceRequest*> &lst, const DateTime &time, bool backward) const {
DateTime t1, t2 = time;
if (backward) {
foreach (ResourceRequest *r, lst) {
t1 = r->availableFrom();
if (!t2.isValid() || t2 > t1)
t2 = t1;
}
//debugPlan<<"bw"<<time.toString()<<":"<<t2.daysTo(time);
return t2.daysTo(time);
}
foreach (ResourceRequest *r, lst) {
t1 = r->availableUntil();
if (!t2.isValid() || t2 < t1)
t2 = t1;
}
//debugPlan<<"fw"<<time.toString()<<":"<<time.daysTo(t2);
return time.daysTo(t2);
}
Duration ResourceRequestCollection::duration(const QList<ResourceRequest*> &lst, const DateTime &time, const Duration &_effort, Schedule *ns, bool backward) {
//debugPlan<<"--->"<<(backward?"(B)":"(F)")<<time.toString()<<": effort:"<<_effort.toString(Duration::Format_Day)<<" ("<<_effort.milliseconds()<<")";
#if 0
if ( ns ) {
QStringList nl;
foreach ( ResourceRequest *r, lst ) { nl << r->resource()->name(); }
ns->logDebug( "Match effort:" + time.toString() + "," + _effort.toString() );
ns->logDebug( "Resources: " + ( nl.isEmpty() ? QString( "None" ) : nl.join( ", " ) ) );
}
#endif
QLocale locale;
Duration e;
if (_effort == Duration::zeroDuration) {
return e;
}
DateTime logtime = time;
bool match = false;
DateTime start = time;
int inc = backward ? -1 : 1;
DateTime end = start;
Duration e1;
int nDays = numDays(lst, time, backward) + 1;
int day = 0;
for (day=0; !match && day <= nDays; ++day) {
// days
end = end.addDays(inc);
e1 = effort( lst, start, backward ? start - end : end - start, ns, backward );
//debugPlan<<"["<<i<<"of"<<nDays<<"]"<<(backward?"(B)":"(F):")<<" start="<<start<<" e+e1="<<(e+e1).toString()<<" match"<<_effort.toString();
if (e + e1 < _effort) {
e += e1;
start = end;
} else if (e + e1 == _effort) {
e += e1;
match = true;
} else {
end = start;
break;
}
}
if ( ! match && day <= nDays ) {
#ifndef PLAN_NLOGDEBUG
if ( ns ) ns->logDebug( "Days: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' );
#endif
logtime = start;
for (int i=0; !match && i < 24; ++i) {
// hours
end = end.addSecs(inc*60*60);
e1 = effort( lst, start, backward ? start - end : end - start, ns, backward );
if (e + e1 < _effort) {
e += e1;
start = end;
} else if (e + e1 == _effort) {
e += e1;
match = true;
} else {
if ( false/*roundToHour*/ && ( _effort - e ) < ( e + e1 - _effort ) ) {
end = start;
match = true;
} else {
end = start;
}
break;
}
//debugPlan<<"duration(h)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
}
//debugPlan<<"duration"<<(backward?"backward":"forward:")<<start.toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<") match="<<match<<" sts="<<sts;
}
if ( ! match && day <= nDays ) {
#ifndef PLAN_NLOGDEBUG
if ( ns ) ns->logDebug( "Hours: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' );
#endif
logtime = start;
for (int i=0; !match && i < 60; ++i) {
//minutes
end = end.addSecs(inc*60);
e1 = effort( lst, start, backward ? start - end : end - start, ns, backward );
if (e + e1 < _effort) {
e += e1;
start = end;
} else if (e + e1 == _effort) {
e += e1;
match = true;
} else if (e + e1 > _effort) {
end = start;
break;
}
//debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
}
//debugPlan<<"duration"<<(backward?"backward":"forward:")<<" start="<<start.toString()<<" e="<<e.toString()<<" match="<<match<<" sts="<<sts;
}
// FIXME: better solution
// If effort to match is reasonably large, accept a match if deviation <= 1 min
if ( ! match && _effort > 5 * 60000 ) {
if ( ( _effort - e ) <= 60000 ){
match = true;
#ifndef PLAN_NLOGDEBUG
if ( ns ) ns->logDebug( "Deviation match:" + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' );
#endif
}
}
if ( ! match && day <= nDays ) {
#ifndef PLAN_NLOGDEBUG
if ( ns ) ns->logDebug( "Minutes: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' );
#endif
logtime = start;
for (int i=0; !match && i < 60; ++i) {
//seconds
end = end.addSecs(inc);
e1 = effort( lst, start, backward ? start - end : end - start, ns, backward );
if (e + e1 < _effort) {
e += e1;
start = end;
} else if (e + e1 == _effort) {
e += e1;
match = true;
} else if (e + e1 > _effort) {
end = start;
break;
}
//debugPlan<<"duration(s)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
}
}
if ( ! match && day <= nDays ) {
#ifndef PLAN_NLOGDEBUG
if ( ns ) ns->logDebug( "Seconds: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')' );
#endif
for (int i=0; !match && i < 1000; ++i) {
//milliseconds
end.setTime(end.time().addMSecs(inc));
e1 = effort( lst, start, backward ? start - end : end - start, ns, backward );
if (e + e1 < _effort) {
e += e1;
start = end;
} else if (e + e1 == _effort) {
e += e1;
match = true;
} else if (e + e1 > _effort) {
break;
}
//debugPlan<<"duration(ms)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
}
}
if (!match && ns) {
ns->logError( i18n( "Could not match effort. Want: %1 got: %2", _effort.toString( Duration::Format_Hour ), e.toString( Duration::Format_Hour ) ) );
foreach (ResourceRequest *r, lst) {
Resource *res = r->resource();
ns->logInfo( i18n( "Resource %1 available from %2 to %3", res->name(), locale.toString(r->availableFrom(), QLocale::ShortFormat), locale.toString(r->availableUntil(), QLocale::ShortFormat) ) );
}
}
DateTime t;
if (e != Duration::zeroDuration) {
foreach ( ResourceRequest *r, lst ) {
DateTime tt;
if ( backward ) {
tt = r->availableAfter(end, ns);
if ( tt.isValid() && ( ! t.isValid() || tt < t ) ) {
t = tt;
}
} else {
tt = r->availableBefore(end, ns);
if ( tt.isValid() && ( ! t.isValid() || tt > t ) ) {
t = tt;
}
}
}
}
end = t.isValid() ? t : time;
//debugPlan<<"<---"<<(backward?"(B)":"(F)")<<":"<<end.toString()<<"-"<<time.toString()<<"="<<(end - time).toString()<<" effort:"<<_effort.toString(Duration::Format_Day);
return (end>time?end-time:time-end);
}
} //KPlato namespace
diff --git a/src/libs/kernel/kptschedule.cpp b/src/libs/kernel/kptschedule.cpp
index 5e7d9a20..bc69fc76 100644
--- a/src/libs/kernel/kptschedule.cpp
+++ b/src/libs/kernel/kptschedule.cpp
@@ -1,2071 +1,2072 @@
/* This file is part of the KDE project
Copyright (C) 2005 - 2011, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptschedule.h"
#include "kptappointment.h"
#include "kptdatetime.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptxmlloaderobject.h"
#include "kptschedulerplugin.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QStringList>
namespace KPlato
{
class ScheduleManager;
Schedule::Log::Log( const Node *n, int sev, const QString &msg, int ph )
: node( n ), resource( 0 ), message( msg ), severity( sev ), phase( ph )
{
Q_ASSERT( n );
// debugPlan<<*this<<nodeId;
}
Schedule::Log::Log( const Node *n, const Resource *r, int sev, const QString &msg, int ph )
: node( n ), resource( r ), message( msg ), severity( sev ), phase( ph )
{
Q_ASSERT( r );
// debugPlan<<*this<<resourceId;
}
Schedule::Log::Log( const Log &other )
{
node = other.node;
resource = other.resource;
message = other.message;
severity = other.severity;
phase = other.phase;
}
Schedule::Log &Schedule::Log::operator=( const Schedule::Log &other )
{
node = other.node;
resource = other.resource;
message = other.message;
severity = other.severity;
phase = other.phase;
return *this;
}
Schedule::Schedule()
: m_type( Expected ),
m_id( 0 ),
m_deleted( false ),
m_parent( 0 ),
m_obstate( OBS_Parent ),
m_calculationMode( Schedule::Scheduling ),
notScheduled( true )
{
initiateCalculation();
}
Schedule::Schedule( Schedule *parent )
: m_type( Expected ),
m_id( 0 ),
m_deleted( false ),
m_parent( parent ),
m_obstate( OBS_Parent ),
m_calculationMode( Schedule::Scheduling ),
notScheduled( true )
{
if ( parent ) {
m_name = parent->name();
m_type = parent->type();
m_id = parent->id();
}
initiateCalculation();
//debugPlan<<"("<<this<<") Name: '"<<name<<"' Type="<<type<<" id="<<id;
}
Schedule::Schedule( const QString& name, Type type, long id )
: m_name( name ),
m_type( type ),
m_id( id ),
m_deleted( false ),
m_parent( 0 ),
m_obstate( OBS_Parent ),
m_calculationMode( Schedule::Scheduling ),
notScheduled( true )
{
//debugPlan<<"("<<this<<") Name: '"<<name<<"' Type="<<type<<" id="<<id;
initiateCalculation();
}
Schedule::~Schedule()
{
}
void Schedule::setParent( Schedule *parent )
{
m_parent = parent;
}
void Schedule::setDeleted( bool on )
{
//debugPlan<<"deleted="<<on;
m_deleted = on;
//changed( this ); don't do this!
}
bool Schedule::isDeleted() const
{
return m_parent == 0 ? m_deleted : m_parent->isDeleted();
}
void Schedule::setType( const QString& type )
{
m_type = Expected;
if ( type == "Expected" )
m_type = Expected;
}
QString Schedule::typeToString( bool translate ) const
{
if ( translate ) {
return i18n( "Expected" );
} else {
return "Expected";
}
}
QStringList Schedule::state() const
{
QStringList lst;
if ( m_deleted )
lst << SchedulingState::deleted();
if ( notScheduled )
lst << SchedulingState::notScheduled();
if ( constraintError )
lst << SchedulingState::constraintsNotMet();
if ( resourceError )
lst << SchedulingState::resourceNotAllocated();
if ( resourceNotAvailable )
lst << SchedulingState::resourceNotAvailable();
if ( resourceOverbooked )
lst << SchedulingState::resourceOverbooked();
if ( effortNotMet )
lst << SchedulingState::effortNotMet();
if ( schedulingError )
lst << SchedulingState::schedulingError();
if ( lst.isEmpty() )
lst << SchedulingState::scheduled();
return lst;
}
bool Schedule::isBaselined() const
{
if ( m_parent ) {
return m_parent->isBaselined();
}
return false;
}
bool Schedule::usePert() const
{
if ( m_parent ) {
return m_parent->usePert();
}
return false;
}
void Schedule::setAllowOverbookingState( Schedule::OBState state )
{
m_obstate = state;
}
Schedule::OBState Schedule::allowOverbookingState() const
{
return m_obstate;
}
bool Schedule::allowOverbooking() const
{
if ( m_obstate == OBS_Parent && m_parent ) {
return m_parent->allowOverbooking();
}
return m_obstate == OBS_Allow;
}
bool Schedule::checkExternalAppointments() const
{
if ( m_parent ) {
return m_parent->checkExternalAppointments();
}
return false;
}
void Schedule::setScheduled( bool on )
{
notScheduled = !on;
changed( this );
}
Duration Schedule::effort( const DateTimeInterval &interval ) const
{
return interval.second - interval.first;
}
DateTimeInterval Schedule::available( const DateTimeInterval &interval ) const
{
return DateTimeInterval( interval.first, interval.second );
}
void Schedule::initiateCalculation()
{
resourceError = false;
resourceOverbooked = false;
resourceNotAvailable = false;
constraintError = false;
schedulingError = false;
inCriticalPath = false;
effortNotMet = false;
workStartTime = DateTime();
workEndTime = DateTime();
}
void Schedule::calcResourceOverbooked()
{
resourceOverbooked = false;
foreach( Appointment *a, m_appointments ) {
if ( a->resource() ->isOverbooked( a->startTime(), a->endTime() ) ) {
resourceOverbooked = true;
break;
}
}
}
DateTimeInterval Schedule::firstBookedInterval( const DateTimeInterval &interval, const Schedule *node ) const
{
QList<Appointment*> lst = m_appointments;
switch ( m_calculationMode ) {
case CalculateForward: lst = m_forward; break;
case CalculateBackward: lst = m_backward; break;
default: break;
}
foreach ( Appointment *a, lst ) {
if ( a->node() == node ) {
AppointmentIntervalList i = a->intervals( interval.first, interval.second );
if ( i.isEmpty() ) {
break;
}
return DateTimeInterval( i.map().values().first().startTime(), i.map().values().first().endTime() );
}
}
return DateTimeInterval();
}
QStringList Schedule::overbookedResources() const
{
QStringList rl;
foreach( Appointment *a, m_appointments ) {
if ( a->resource() ->isOverbooked( a->startTime(), a->endTime() ) ) {
rl += a->resource() ->resource() ->name();
}
}
return rl;
}
QStringList Schedule::resourceNameList() const
{
return QStringList();
}
QList<Resource*> Schedule::resources() const
{
return QList<Resource*>();
}
bool Schedule::loadXML( const KoXmlElement &sch, XMLLoaderObject & )
{
m_name = sch.attribute( "name" );
setType( sch.attribute( "type" ) );
m_id = sch.attribute( "id" ).toLong();
return true;
}
void Schedule::saveXML( QDomElement &element ) const
{
QDomElement sch = element.ownerDocument().createElement( "schedule" );
element.appendChild( sch );
saveCommonXML( sch );
}
void Schedule::saveCommonXML( QDomElement &element ) const
{
//debugPlan<<m_name<<" save schedule";
element.setAttribute( "name", m_name );
element.setAttribute( "type", typeToString() );
element.setAttribute( "id", QString::number(qlonglong( m_id )) );
}
void Schedule::saveAppointments( QDomElement &element ) const
{
//debugPlan;
QListIterator<Appointment*> it = m_appointments;
while ( it.hasNext() ) {
it.next() ->saveXML( element );
}
}
void Schedule::insertForwardNode( Node *node )
{
if ( m_parent ) {
m_parent->insertForwardNode( node );
}
}
void Schedule::insertBackwardNode( Node *node )
{
if ( m_parent ) {
m_parent->insertBackwardNode( node );
}
}
// used (directly) when appointment wants to attach itself again
bool Schedule::attach( Appointment *appointment )
{
int mode = appointment->calculationMode();
//debugPlan<<appointment<<mode;
if ( mode == Scheduling ) {
if ( m_appointments.indexOf( appointment ) != -1 ) {
errorPlan << "Appointment already exists" << endl;
return false;
}
m_appointments.append( appointment );
//if (resource()) debugPlan<<appointment<<" For resource '"<<resource()->name()<<"'"<<" count="<<m_appointments.count();
//if (node()) debugPlan<<"("<<this<<")"<<appointment<<" For node '"<<node()->name()<<"'"<<" count="<<m_appointments.count();
return true;
}
if ( mode == CalculateForward ) {
if ( m_forward.indexOf( appointment ) != -1 ) {
errorPlan << "Appointment already exists" << endl;
return false;
}
m_forward.append( appointment );
//if (resource()) debugPlan<<"For resource '"<<resource()->name()<<"'";
//if (node()) debugPlan<<"For node '"<<node()->name()<<"'";
return true;
}
if ( mode == CalculateBackward ) {
if ( m_backward.indexOf( appointment ) != -1 ) {
errorPlan << "Appointment already exists" << endl;
return false;
}
m_backward.append( appointment );
//if (resource()) debugPlan<<"For resource '"<<resource()->name()<<"'";
//if (node()) debugPlan<<"For node '"<<node()->name()<<"'";
return true;
}
errorPlan<<"Unknown mode: "<<m_calculationMode<<endl;
return false;
}
// used to add new schedules
bool Schedule::add( Appointment *appointment )
{
//debugPlan<<this;
appointment->setCalculationMode( m_calculationMode );
return attach( appointment );
}
void Schedule::takeAppointment( Appointment *appointment, int mode )
{
Q_UNUSED(mode);
//debugPlan<<"("<<this<<")"<<mode<<":"<<appointment<<","<<appointment->calculationMode();
int i = m_forward.indexOf( appointment );
if ( i != -1 ) {
m_forward.removeAt( i );
Q_ASSERT( mode == CalculateForward );
}
i = m_backward.indexOf( appointment );
if ( i != -1 ) {
m_backward.removeAt( i );
Q_ASSERT( mode == CalculateBackward );
}
i = m_appointments.indexOf( appointment );
if ( i != -1 ) {
m_appointments.removeAt( i );
Q_ASSERT( mode == Scheduling );
}
}
Appointment *Schedule::findAppointment( Schedule *resource, Schedule *node, int mode )
{
//debugPlan<<this<<" ("<<resourceError<<","<<node<<")"<<mode;
if ( mode == Scheduling ) {
foreach( Appointment *a, m_appointments ) {
if ( a->node() == node && a->resource() == resource ) {
return a;
}
}
return 0;
} else if ( mode == CalculateForward ) {
foreach( Appointment *a, m_forward ) {
if ( a->node() == node && a->resource() == resource ) {
return a;
}
}
} else if ( mode == CalculateBackward ) {
foreach( Appointment *a, m_backward ) {
if ( a->node() == node && a->resource() == resource ) {
Q_ASSERT( mode == CalculateBackward );
return a;
}
}
} else {
Q_ASSERT( false ); // unknown mode
}
return 0;
}
DateTime Schedule::appointmentStartTime() const
{
DateTime dt;
foreach ( const Appointment *a, m_appointments ) {
if ( ! dt.isValid() || dt > a->startTime() ) {
dt = a->startTime();
}
}
return dt;
}
DateTime Schedule::appointmentEndTime() const
{
DateTime dt;
foreach ( const Appointment *a, m_appointments ) {
if ( ! dt.isValid() || dt > a->endTime() ) {
dt = a->endTime();
}
}
return dt;
}
bool Schedule::hasAppointments( int which ) const
{
if ( which == CalculateForward ) {
return m_forward.isEmpty();
} else if ( which == CalculateBackward ) {
return m_backward.isEmpty();
}
return m_appointments.isEmpty();
}
QList<Appointment*> Schedule::appointments( int which ) const
{
if ( which == CalculateForward ) {
return m_forward;
} else if ( which == CalculateBackward ) {
return m_backward;
}
return m_appointments;
}
Appointment Schedule::appointmentIntervals( int which, const DateTimeInterval &interval ) const
{
Appointment app;
if ( which == Schedule::CalculateForward ) {
//debugPlan<<"list == CalculateForward";
foreach ( Appointment *a, m_forward ) {
app += interval.isValid() ? a->extractIntervals( interval ) : *a;
}
return app;
} else if ( which == Schedule::CalculateBackward ) {
//debugPlan<<"list == CalculateBackward";
foreach ( Appointment *a, m_backward ) {
app += interval.isValid() ? a->extractIntervals( interval ) : *a;
}
//debugPlan<<"list == CalculateBackward:"<<m_backward.count();
return app;
}
foreach ( Appointment *a, m_appointments ) {
app += interval.isValid() ? a->extractIntervals( interval ) : *a;
}
return app;
}
void Schedule::copyAppointments( Schedule::CalculationMode from, Schedule::CalculationMode to )
{
switch ( to ) {
case Scheduling:
m_appointments.clear();
switch( from ) {
case CalculateForward:
m_appointments = m_forward;
break;
case CalculateBackward:
m_appointments = m_backward;
break;
default:
break;
}
break;
case CalculateForward: break;
case CalculateBackward: break;
}
}
EffortCostMap Schedule::bcwsPrDay( EffortCostCalculationType type ) const
{
return const_cast<Schedule*>( this )->bcwsPrDay( type );
}
EffortCostMap Schedule::bcwsPrDay( EffortCostCalculationType type )
{
//debugPlan<<m_name<<m_appointments;
EffortCostCache &ec = m_bcwsPrDay[ (int)type ];
if ( ! ec.cached ) {
foreach ( Appointment *a, m_appointments ) {
ec.effortcostmap += a->plannedPrDay( a->startTime().date(), a->endTime().date(), type );
}
}
return ec.effortcostmap;
}
EffortCostMap Schedule::plannedEffortCostPrDay( const QDate &start, const QDate &end, EffortCostCalculationType type ) const
{
//debugPlan<<m_name<<m_appointments;
EffortCostMap ec;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
//debugPlan<<m_name;
ec += it.next() ->plannedPrDay( start, end, type );
}
return ec;
}
EffortCostMap Schedule::plannedEffortCostPrDay( const Resource *resource, const QDate &start, const QDate &end, EffortCostCalculationType type ) const
{
//debugPlan<<m_name<<m_appointments;
EffortCostMap ec;
foreach ( Appointment *a, m_appointments ) {
if ( a->resource() && a->resource()->resource() == resource ) {
ec += a->plannedPrDay( start, end, type );
break;
}
}
return ec;
}
Duration Schedule::plannedEffort( const Resource *resource, EffortCostCalculationType type ) const
{
//debugPlan;
Duration eff;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffort( resource, type );
}
return eff;
}
Duration Schedule::plannedEffort( EffortCostCalculationType type ) const
{
//debugPlan;
Duration eff;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffort( type );
}
return eff;
}
Duration Schedule::plannedEffort( const QDate &date, EffortCostCalculationType type ) const
{
//debugPlan;
Duration eff;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffort( date, type );
}
return eff;
}
Duration Schedule::plannedEffort( const Resource *resource, const QDate &date, EffortCostCalculationType type ) const
{
//debugPlan;
Duration eff;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffort( resource, date, type );
}
return eff;
}
Duration Schedule::plannedEffortTo( const QDate &date, EffortCostCalculationType type ) const
{
//debugPlan;
Duration eff;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffortTo( date, type );
}
return eff;
}
Duration Schedule::plannedEffortTo( const Resource *resource, const QDate &date, EffortCostCalculationType type ) const
{
//debugPlan;
Duration eff;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
eff += it.next() ->plannedEffortTo( resource, date, type );
}
return eff;
}
EffortCost Schedule::plannedCost( EffortCostCalculationType type ) const
{
//debugPlan;
EffortCost c;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
c += it.next() ->plannedCost( type );
}
return c;
}
double Schedule::plannedCost( const QDate &date, EffortCostCalculationType type ) const
{
//debugPlan;
double c = 0;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
c += it.next() ->plannedCost( date, type );
}
return c;
}
double Schedule::plannedCostTo( const QDate &date, EffortCostCalculationType type ) const
{
//debugPlan;
double c = 0;
QListIterator<Appointment*> it( m_appointments );
while ( it.hasNext() ) {
c += it.next() ->plannedCostTo( date, type );
}
return c;
}
void Schedule::addLog( const Schedule::Log &log )
{
if ( m_parent ) {
m_parent->addLog( log );
}
}
QString Schedule::Log::formatMsg() const
{
QString s;
s += node ? QString( "%1 " ).arg( node->name(), -8 ) : "";
s += resource ? QString( "%1 ").arg(resource->name(), -8 ) : "";
s += message;
return s;
}
void Schedule::clearPerformanceCache()
{
m_bcwsPrDay.clear();
m_bcwpPrDay.clear();
m_acwp.clear();
}
//-------------------------------------------------
NodeSchedule::NodeSchedule()
: Schedule(),
m_node( 0 )
{
//debugPlan<<"("<<this<<")";
init();
}
NodeSchedule::NodeSchedule( Node *node, const QString& name, Schedule::Type type, long id )
: Schedule( name, type, id ),
m_node( node )
{
//debugPlan<<"node name:"<<node->name();
init();
}
NodeSchedule::NodeSchedule( Schedule *parent, Node *node )
: Schedule( parent ),
m_node( node )
{
//debugPlan<<"node name:"<<node->name();
init();
}
NodeSchedule::~NodeSchedule()
{
//debugPlan<<this<<""<<m_appointments.count();
while ( !m_appointments.isEmpty() ) {
Appointment *a = m_appointments.takeFirst();
a->setNode( 0 );
delete a;
}
//debugPlan<<"forw"<<m_forward.count();
while ( !m_forward.isEmpty() ) {
Appointment *a = m_forward.takeFirst();
a->setNode( 0 );
delete a;
}
//debugPlan<<"backw"<<m_backward.count();
while ( !m_backward.isEmpty() ) {
Appointment *a = m_backward.takeFirst();
a->setNode( 0 );
delete a;
}
}
void NodeSchedule::init()
{
resourceError = false;
resourceOverbooked = false;
resourceNotAvailable = false;
constraintError = false;
schedulingError = false;
notScheduled = true;
inCriticalPath = false;
m_calculationMode = Schedule::Scheduling;
positiveFloat = Duration::zeroDuration;
negativeFloat = Duration::zeroDuration;
freeFloat = Duration::zeroDuration;
}
void NodeSchedule::setDeleted( bool on )
{
//debugPlan<<"deleted="<<on;
m_deleted = on;
// set deleted also for possible resource schedules
QListIterator<Appointment*> it = m_appointments;
while ( it.hasNext() ) {
Appointment * a = it.next();
if ( a->resource() ) {
a->resource() ->setDeleted( on );
}
}
}
bool NodeSchedule::loadXML( const KoXmlElement &sch, XMLLoaderObject &status )
{
//debugPlan;
QString s;
Schedule::loadXML( sch, status );
s = sch.attribute( "earlystart" );
if ( s.isEmpty() ) { // try version < 0.6
s = sch.attribute( "earlieststart" );
}
if ( !s.isEmpty() ) {
earlyStart = DateTime::fromString( s, status.projectTimeZone() );
}
s = sch.attribute( "latefinish" );
if ( s.isEmpty() ) { // try version < 0.6
s = sch.attribute( "latestfinish" );
}
if ( !s.isEmpty() ) {
lateFinish = DateTime::fromString( s, status.projectTimeZone() );
}
s = sch.attribute( "latestart" );
if ( !s.isEmpty() ) {
lateStart = DateTime::fromString( s, status.projectTimeZone() );
}
s = sch.attribute( "earlyfinish" );
if ( !s.isEmpty() ) {
earlyFinish = DateTime::fromString( s, status.projectTimeZone() );
}
s = sch.attribute( "start" );
if ( !s.isEmpty() )
startTime = DateTime::fromString( s, status.projectTimeZone() );
s = sch.attribute( "end" );
if ( !s.isEmpty() )
endTime = DateTime::fromString( s, status.projectTimeZone() );
s = sch.attribute( "start-work" );
if ( !s.isEmpty() )
workStartTime = DateTime::fromString( s, status.projectTimeZone() );
s = sch.attribute( "end-work" );
if ( !s.isEmpty() )
workEndTime = DateTime::fromString( s, status.projectTimeZone() );
duration = Duration::fromString( sch.attribute( "duration" ) );
inCriticalPath = sch.attribute( "in-critical-path", "0" ).toInt();
resourceError = sch.attribute( "resource-error", "0" ).toInt();
resourceOverbooked = sch.attribute( "resource-overbooked", "0" ).toInt();
resourceNotAvailable = sch.attribute( "resource-not-available", "0" ).toInt();
constraintError = sch.attribute( "scheduling-conflict", "0" ).toInt();
schedulingError = sch.attribute( "scheduling-error", "0" ).toInt();
notScheduled = sch.attribute( "not-scheduled", "1" ).toInt();
positiveFloat = Duration::fromString( sch.attribute( "positive-float" ) );
negativeFloat = Duration::fromString( sch.attribute( "negative-float" ) );
freeFloat = Duration::fromString( sch.attribute( "free-float" ) );
return true;
}
void NodeSchedule::saveXML( QDomElement &element ) const
{
//debugPlan;
QDomElement sch = element.ownerDocument().createElement( "schedule" );
element.appendChild( sch );
saveCommonXML( sch );
if ( earlyStart.isValid() ) {
sch.setAttribute( "earlystart", earlyStart.toString( Qt::ISODate ) );
}
if ( lateStart.isValid() ) {
sch.setAttribute( "latestart", lateStart.toString( Qt::ISODate ) );
}
if ( earlyFinish.isValid() ) {
sch.setAttribute( "earlyfinish", earlyFinish.toString( Qt::ISODate ) );
}
if ( lateFinish.isValid() ) {
sch.setAttribute( "latefinish", lateFinish.toString( Qt::ISODate ) );
}
if ( startTime.isValid() )
sch.setAttribute( "start", startTime.toString( Qt::ISODate ) );
if ( endTime.isValid() )
sch.setAttribute( "end", endTime.toString( Qt::ISODate ) );
if ( workStartTime.isValid() )
sch.setAttribute( "start-work", workStartTime.toString( Qt::ISODate ) );
if ( workEndTime.isValid() )
sch.setAttribute( "end-work", workEndTime.toString( Qt::ISODate ) );
sch.setAttribute( "duration", duration.toString() );
sch.setAttribute( "in-critical-path", QString::number(inCriticalPath) );
sch.setAttribute( "resource-error", QString::number(resourceError) );
sch.setAttribute( "resource-overbooked", QString::number(resourceOverbooked) );
sch.setAttribute( "resource-not-available", QString::number(resourceNotAvailable) );
sch.setAttribute( "scheduling-conflict", QString::number(constraintError) );
sch.setAttribute( "scheduling-error", QString::number(schedulingError) );
sch.setAttribute( "not-scheduled", QString::number(notScheduled) );
sch.setAttribute( "positive-float", positiveFloat.toString() );
sch.setAttribute( "negative-float", negativeFloat.toString() );
sch.setAttribute( "free-float", freeFloat.toString() );
}
void NodeSchedule::addAppointment( Schedule *resource, const DateTime &start, const DateTime &end, double load )
{
//debugPlan;
Appointment * a = findAppointment( resource, this, m_calculationMode );
if ( a != 0 ) {
//debugPlan<<"Add interval to existing"<<a;
a->addInterval( start, end, load );
return ;
}
a = new Appointment( resource, this, start, end, load );
bool result = add( a );
Q_ASSERT ( result );
result = resource->add( a );
Q_ASSERT ( result );
Q_UNUSED ( result ); // cheating the compiler in release mode to not warn about unused-but-set-variable
//debugPlan<<"Added interval to new"<<a;
}
void NodeSchedule::takeAppointment( Appointment *appointment, int mode )
{
Schedule::takeAppointment( appointment, mode );
appointment->setNode( 0 ); // not my appointment anymore
//debugPlan<<"Taken:"<<appointment;
if ( appointment->resource() )
appointment->resource() ->takeAppointment( appointment );
}
QList<Resource*> NodeSchedule::resources() const
{
QList<Resource*> rl;
foreach( Appointment *a, m_appointments ) {
rl += a->resource() ->resource();
}
return rl;
}
QStringList NodeSchedule::resourceNameList() const
{
QStringList rl;
foreach( Appointment *a, m_appointments ) {
rl += a->resource() ->resource() ->name();
}
return rl;
}
void NodeSchedule::logError( const QString &msg, int phase )
{
Schedule::Log log( m_node, Log::Type_Error, msg, phase );
if ( m_parent ) {
m_parent->addLog( log );
} else {
addLog( log );
}
}
void NodeSchedule::logWarning( const QString &msg, int phase )
{
Schedule::Log log( m_node, Log::Type_Warning, msg, phase );
if ( m_parent ) {
m_parent->addLog( log );
} else {
addLog( log );
}
}
void NodeSchedule::logInfo( const QString &msg, int phase )
{
Schedule::Log log( m_node, Log::Type_Info, msg, phase );
if ( m_parent ) {
m_parent->addLog( log );
} else {
addLog( log );
}
}
void NodeSchedule::logDebug( const QString &msg, int phase )
{
Schedule::Log log( m_node, Log::Type_Debug, msg, phase );
if ( m_parent ) {
m_parent->addLog( log );
} else {
addLog( log );
}
}
//-----------------------------------------------
ResourceSchedule::ResourceSchedule()
: Schedule(),
m_resource( 0 )
{
//debugPlan<<"("<<this<<")";
}
ResourceSchedule::ResourceSchedule( Resource *resource, const QString& name, Schedule::Type type, long id )
: Schedule( name, type, id ),
m_resource( resource ),
m_parent( 0 ),
m_nodeSchedule( 0 )
{
//debugPlan<<"resource:"<<resource->name();
}
ResourceSchedule::ResourceSchedule( Schedule *parent, Resource *resource )
: Schedule( parent ),
m_resource( resource ),
m_parent( parent ),
m_nodeSchedule( 0 )
{
//debugPlan<<"resource:"<<resource->name();
}
ResourceSchedule::~ResourceSchedule()
{
//debugPlan<<this<<""<<m_appointments.count();
while ( !m_appointments.isEmpty() ) {
Appointment *a = m_appointments.takeFirst();
a->setResource( 0 );
delete a;
}
//debugPlan<<"forw"<<m_forward.count();
while ( !m_forward.isEmpty() ) {
Appointment *a = m_forward.takeFirst();
a->setResource( 0 );
delete a;
}
//debugPlan<<"backw"<<m_backward.count();
while ( !m_backward.isEmpty() ) {
Appointment *a = m_backward.takeFirst();
a->setResource( 0 );
delete a;
}
}
// called from the resource
void ResourceSchedule::addAppointment( Schedule *node, const DateTime &start, const DateTime &end, double load )
{
Q_ASSERT( start < end );
//debugPlan<<"("<<this<<")"<<node<<","<<m_calculationMode;
Appointment * a = findAppointment( this, node, m_calculationMode );
if ( a != 0 ) {
//debugPlan<<"Add interval to existing"<<a;
a->addInterval( start, end, load );
return ;
}
a = new Appointment( this, node, start, end, load );
bool result = add( a );
Q_ASSERT ( result == true );
result = node->add( a );
Q_ASSERT ( result == true );
Q_UNUSED ( result ); //don't warn about unused-but-set-variable in release mode
//debugPlan<<"Added interval to new"<<a;
}
void ResourceSchedule::takeAppointment( Appointment *appointment, int mode )
{
Schedule::takeAppointment( appointment, mode );
appointment->setResource( 0 );
//debugPlan<<"Taken:"<<appointment;
if ( appointment->node() )
appointment->node() ->takeAppointment( appointment );
}
bool ResourceSchedule::isOverbooked() const
{
return false;
}
bool ResourceSchedule::isOverbooked( const DateTime &start, const DateTime &end ) const
{
if ( m_resource == 0 )
return false;
//debugPlan<<start.toString()<<" -"<<end.toString();
Appointment a = appointmentIntervals();
foreach ( const AppointmentInterval &i, a.intervals().map() ) {
if ( ( !end.isValid() || i.startTime() < end ) &&
( !start.isValid() || i.endTime() > start ) ) {
if ( i.load() > m_resource->units() ) {
//debugPlan<<m_name<<" overbooked";
return true;
}
}
if ( i.startTime() >= end )
break;
}
//debugPlan<<m_name<<" not overbooked";
return false;
}
double ResourceSchedule::normalRatePrHour() const
{
return m_resource ? m_resource->normalRate() : 0.0;
}
//TODO change to return the booked effort
Duration ResourceSchedule::effort( const DateTimeInterval &interval ) const
{
Duration eff = interval.second - interval.first;
if ( allowOverbooking() ) {
return eff;
}
Appointment a;
if ( checkExternalAppointments() ) {
a.setIntervals( m_resource->externalAppointments() );
}
a.merge( appointmentIntervals( m_calculationMode ) );
if ( a.isEmpty() || a.startTime() >= interval.second || a.endTime() <= interval.first ) {
return eff;
}
foreach ( const AppointmentInterval &i, a.intervals().map() ) {
if ( interval.second <= i.startTime() ) {
break;
}
if ( interval.first >= i.startTime() ) {
DateTime et = i.endTime() < interval.second ? i.endTime() : interval.second;
eff -= ( et - interval.first ) * ((double)i.load()/100.0 );
} else {
DateTime et = i.endTime() < interval.second ? i.endTime() : interval.second;
eff -= ( et - i.startTime() ) * ((double)i.load()/100.0 );
}
}
return eff;
}
DateTimeInterval ResourceSchedule::available( const DateTimeInterval &interval ) const
{
//const_cast<ResourceSchedule*>(this)->logDebug( QString( "Schedule available id=%1, Mode=%2: interval=%3 - %4" ).arg(m_id).arg(m_calculationMode).arg(interval.first.toString()).arg(interval.second.toString()) );
if ( allowOverbooking() ) {
return DateTimeInterval( interval.first, interval.second );
}
QTimeZone projectTimeZone = QTimeZone::systemTimeZone();
if (m_resource) {
projectTimeZone = m_resource->project()->timeZone();
}
DateTimeInterval ci(interval.first.toTimeZone(projectTimeZone), interval.second.toTimeZone(projectTimeZone));
Appointment a;
if ( checkExternalAppointments() ) {
a.setIntervals( m_resource->externalAppointments( ci ) );
}
a.merge( appointmentIntervals( m_calculationMode, ci ) );
if ( a.isEmpty() || a.startTime() >= ci.second || a.endTime() <= ci.first ) {
//debugPlan<<this<<"id="<<m_id<<"Mode="<<m_calculationMode<<""<<interval.first<<","<<interval.second<<" FREE";
return DateTimeInterval( interval.first, interval.second ); // just return the interval
}
//debugPlan<<"available:"<<interval<<endl<<a.intervals();
DateTimeInterval res;
int units = m_resource ? m_resource->units() : 100;
foreach ( const AppointmentInterval &i, a.intervals().map() ) {
//const_cast<ResourceSchedule*>(this)->logDebug( QString( "Schedule available check interval=%1 - %2" ).arg(i.startTime().toString()).arg(i.endTime().toString()) );
if ( i.startTime() < ci.second && i.endTime() > ci.first ) {
// interval intersects appointment
if ( ci.first >= i.startTime() && ci.second <= i.endTime() ) {
// interval within appointment
if ( i.load() < units ) {
if ( ! res.first.isValid() ) {
res.first = qMax( ci.first, i.startTime() );
}
res.second = qMin( ci.second, i.endTime() );
} else {
// fully booked
if ( res.first.isValid() ) {
res.second = i.startTime();
if ( res.first >= res.second ) {
res = DateTimeInterval();
}
}
}
//debugPlan<<"available within:"<<interval<<i<<":"<<ci<<res;
break;
}
DateTime t = i.startTime();
if ( ci.first < t ) {
// Interval starts before appointment, so free from interval start
//debugPlan<<"available before:"<<interval<<i<<":"<<ci<<res;
//const_cast<ResourceSchedule*>(this)->logDebug( QString( "Schedule available t>first: returns interval=%1 - %2" ).arg(ci.first.toString()).arg(t.toString()) );
if ( ! res.first.isValid() ) {
res.first = ci.first;
}
res.second = t;
if ( i.load() < units ) {
res.second = qMin( ci.second, i.endTime() );
if ( ci.second > i.endTime() ) {
ci.first = i.endTime();
//debugPlan<<"available next 1:"<<interval<<i<<":"<<ci<<res;
continue; // check next appointment
}
}
//debugPlan<<"available:"<<interval<<i<<":"<<ci<<res;
break;
}
// interval start >= appointment start
t = i.endTime();
if ( t < ci.second ) {
// check if rest of appointment is free
if ( units <= i.load() ) {
ci.first = t; // fully booked, so move forward to appointment end
}
res = ci;
//debugPlan<<"available next 2:"<<interval<<i<<":"<<ci<<res;
continue;
}
//debugPlan<<"available:"<<interval<<i<<":"<<ci<<res;
Q_ASSERT( false );
} else if ( i.startTime() >= interval.second ) {
// no more overlaps
break;
}
}
//debugPlan<<"available: result="<<interval<<":"<<res;
return DateTimeInterval(res.first.toTimeZone(interval.first.timeZone()), res.second.toTimeZone(interval.second.timeZone()));
}
void ResourceSchedule::logError( const QString &msg, int phase )
{
if ( m_parent ) {
Schedule::Log log( m_nodeSchedule ? m_nodeSchedule->node() : 0, m_resource, Log::Type_Error, msg, phase );
m_parent->addLog( log );
}
}
void ResourceSchedule::logWarning( const QString &msg, int phase )
{
if ( m_parent ) {
Schedule::Log log( m_nodeSchedule ? m_nodeSchedule->node() : 0, m_resource, Log::Type_Warning, msg, phase );
m_parent->addLog( log );
}
}
void ResourceSchedule::logInfo( const QString &msg, int phase )
{
if ( m_parent ) {
Schedule::Log log( m_nodeSchedule ? m_nodeSchedule->node() : 0, m_resource, Log::Type_Info, msg, phase );
m_parent->addLog( log );
}
}
void ResourceSchedule::logDebug( const QString &msg, int phase )
{
if ( m_parent ) {
Schedule::Log log( m_nodeSchedule ? m_nodeSchedule->node() : 0, m_resource, Log::Type_Debug, msg, phase );
m_parent->addLog( log );
}
}
//--------------------------------------
MainSchedule::MainSchedule()
: NodeSchedule(),
m_manager( 0 )
{
//debugPlan<<"("<<this<<")";
init();
}
MainSchedule::MainSchedule( Node *node, const QString& name, Schedule::Type type, long id )
: NodeSchedule( node, name, type, id ),
criticalPathListCached( false ),
m_manager( 0 ),
m_currentCriticalPath( 0 )
{
//debugPlan<<"node name:"<<node->name();
init();
}
MainSchedule::~MainSchedule()
{
//debugPlan<<"("<<this<<")";
}
void MainSchedule::incProgress()
{
if ( m_manager ) m_manager->incProgress();
}
bool MainSchedule::isBaselined() const
{
return m_manager == 0 ? false : m_manager->isBaselined();
}
bool MainSchedule::usePert() const
{
return m_manager == 0 ? false : m_manager->usePert();
}
bool MainSchedule::allowOverbooking() const
{
return m_manager == 0 ? false : m_manager->allowOverbooking();
}
bool MainSchedule::checkExternalAppointments() const
{
return m_manager == 0 ? false : m_manager->checkExternalAppointments();
}
void MainSchedule::changed( Schedule *sch )
{
if ( m_manager ) {
m_manager->scheduleChanged( static_cast<MainSchedule*>( sch ) );
}
}
bool MainSchedule::loadXML( const KoXmlElement &sch, XMLLoaderObject &status )
{
//debugPlan;
QString s;
Schedule::loadXML( sch, status );
s = sch.attribute( "start" );
if ( !s.isEmpty() )
startTime = DateTime::fromString( s, status.projectTimeZone() );
s = sch.attribute( "end" );
if ( !s.isEmpty() )
endTime = DateTime::fromString( s, status.projectTimeZone() );
duration = Duration::fromString( sch.attribute( "duration" ) );
constraintError = sch.attribute( "scheduling-conflict", "0" ).toInt();
schedulingError = sch.attribute( "scheduling-error", "0" ).toInt();
//NOTE: we use "scheduled" as default to match old format without "not-scheduled" element
notScheduled = sch.attribute( "not-scheduled", "0" ).toInt();
KoXmlNode n = sch.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
if ( el.tagName() == "appointment" ) {
// Load the appointments.
// Resources and tasks must already be loaded
Appointment * child = new Appointment();
if ( !child->loadXML( el, status, *this ) ) {
// TODO: Complain about this
errorPlan << "Failed to load appointment" << endl;
delete child;
}
} else if ( el.tagName() == "criticalpath-list" ) {
// Tasks must already be loaded
for ( KoXmlNode n1 = el.firstChild(); ! n1.isNull(); n1 = n1.nextSibling() ) {
if ( ! n1.isElement() ) {
continue;
}
KoXmlElement e1 = n1.toElement();
if ( e1.tagName() != "criticalpath" ) {
continue;
}
QList<Node*> lst;
for ( KoXmlNode n2 = e1.firstChild(); ! n2.isNull(); n2 = n2.nextSibling() ) {
if ( ! n2.isElement() ) {
continue;
}
KoXmlElement e2 = n2.toElement();
if ( e2.tagName() != "node" ) {
continue;
}
QString s = e2.attribute( "id" );
Node *node = status.project().findNode( s );
if ( node ) {
lst.append( node );
} else {
errorPlan<<"Failed to find node id="<<s;
}
}
m_pathlists.append( lst );
}
criticalPathListCached = true;
}
}
return true;
}
void MainSchedule::saveXML( QDomElement &element ) const
{
saveCommonXML( element );
element.setAttribute( "start", startTime.toString( Qt::ISODate ) );
element.setAttribute( "end", endTime.toString( Qt::ISODate ) );
element.setAttribute( "duration", duration.toString() );
element.setAttribute( "scheduling-conflict", QString::number(constraintError) );
element.setAttribute( "scheduling-error", QString::number(schedulingError) );
element.setAttribute( "not-scheduled", QString::number(notScheduled) );
if ( ! m_pathlists.isEmpty() ) {
QDomElement lists = element.ownerDocument().createElement( "criticalpath-list" );
element.appendChild( lists );
foreach ( const QList<Node*> &l, m_pathlists ) {
if ( l.isEmpty() ) {
continue;
}
QDomElement list = lists.ownerDocument().createElement( "criticalpath" );
lists.appendChild( list );
foreach ( Node *n, l ) {
QDomElement el = list.ownerDocument().createElement( "node" );
list.appendChild( el );
el.setAttribute( "id", n->id() );
}
}
}
}
DateTime MainSchedule::calculateForward( int use )
{
DateTime late;
foreach( Node *n, m_backwardnodes ) {
DateTime t = n->calculateForward( use );
if ( !late.isValid() || late < t ) {
late = t;
}
}
return late;
}
DateTime MainSchedule::calculateBackward( int use )
{
DateTime early;
foreach( Node *n, m_forwardnodes ) {
DateTime t = n->calculateBackward( use );
if ( !early.isValid() || early > t ) {
early = t;
}
}
return early;
}
DateTime MainSchedule::scheduleForward( const DateTime &earliest, int use )
{
DateTime end;
foreach( Node *n, m_forwardnodes ) {
DateTime t = n->scheduleForward( earliest, use );
if ( !end.isValid() || end < t ) {
end = t;
}
}
return end;
}
DateTime MainSchedule::scheduleBackward( const DateTime &latest, int use )
{
DateTime start;
foreach( Node *n, m_backwardnodes ) {
DateTime t = n->scheduleBackward( latest, use );
if ( !start.isValid() || start > t ) {
start = t;
}
}
return start;
}
bool MainSchedule::recalculate() const
{
return m_manager == 0 ? false : m_manager->recalculate();
}
DateTime MainSchedule::recalculateFrom() const
{
return m_manager == 0 ? DateTime() : m_manager->recalculateFrom();
}
long MainSchedule::parentScheduleId() const
{
return m_manager == 0 ? -2 : m_manager->parentScheduleId();
}
void MainSchedule::clearCriticalPathList()
{
m_pathlists.clear();
m_currentCriticalPath = 0;
criticalPathListCached = false;
}
QList<Node*> *MainSchedule::currentCriticalPath() const
{
return m_currentCriticalPath;
}
void MainSchedule::addCriticalPath( QList<Node*> *lst )
{
QList<Node*> l;
if ( lst ) {
l = *lst;
}
m_pathlists.append( l );
m_currentCriticalPath = &( m_pathlists.last() );
}
void MainSchedule::addCriticalPathNode( Node *node )
{
if ( m_currentCriticalPath == 0 ) {
errorPlan<<"No currentCriticalPath"<<endl;
return;
}
m_currentCriticalPath->append( node );
}
QVector<Schedule::Log> MainSchedule::logs() const
{
return m_log;
}
void MainSchedule::addLog( const KPlato::Schedule::Log &log )
{
Q_ASSERT( log.resource || log.node );
#ifndef NDEBUG
if ( log.resource ) {
Q_ASSERT( manager()->project().findResource( log.resource->id() ) == log.resource );
} else if ( log.node ) {
Q_ASSERT( manager()->project().findNode( log.node->id() ) == log.node );
}
#endif
const int phaseToSet = ( log.phase == -1 && ! m_log.isEmpty() ) ? m_log.last().phase : -1;
m_log.append( log );
if ( phaseToSet != -1 ) {
m_log.last().phase = phaseToSet;
}
if ( m_manager ) {
m_manager->logAdded(m_log.last());
}
}
//static
QString MainSchedule::logSeverity( int severity )
{
switch ( severity ) {
case Log::Type_Debug: return "Debug";//FIXME i18n( "Debug" );
case Log::Type_Info: return i18n( "Info" );
case Log::Type_Warning: return i18n( "Warning" );
case Log::Type_Error: return i18n( "Error" );
default: break;
}
return QString( "Severity %1" ).arg( severity );
}
QStringList MainSchedule::logMessages() const
{
QStringList lst;
foreach ( const Schedule::Log &l, m_log ) {
lst << l.formatMsg();
}
return lst;
}
//-----------------------------------------
ScheduleManager::ScheduleManager( Project &project, const QString name )
: m_project( project),
m_parent( 0 ),
m_name( name ),
m_baselined( false ),
m_allowOverbooking( false ),
m_checkExternalAppointments( true ),
m_usePert( false ),
m_recalculate( false ),
m_schedulingDirection( false ),
m_scheduling( false ),
m_progress( 0 ),
m_maxprogress( 0 ),
m_expected( 0 )
{
//debugPlan<<name;
}
ScheduleManager::~ScheduleManager()
{
qDeleteAll( m_children );
setParentManager( 0 );
}
void ScheduleManager::setParentManager( ScheduleManager *sm, int index )
{
if ( m_parent ) {
m_parent->removeChild( this );
}
m_parent = sm;
if ( sm ) {
sm->insertChild( this, index );
}
}
int ScheduleManager::removeChild( const ScheduleManager *sm )
{
int i = m_children.indexOf( const_cast<ScheduleManager*>( sm ) );
if ( i != -1 ) {
m_children.removeAt( i );
}
return i;
}
void ScheduleManager::insertChild( ScheduleManager *sm, int index )
{
//debugPlan<<m_name<<", insert"<<sm->name()<<","<<index;
if ( index == -1 ) {
m_children.append( sm );
} else {
m_children.insert( index, sm );
}
}
void ScheduleManager::createSchedules()
{
setExpected( m_project.createSchedule( m_name, Schedule::Expected ) );
}
int ScheduleManager::indexOf( const ScheduleManager *child ) const
{
//debugPlan<<this<<","<<child;
return m_children.indexOf( const_cast<ScheduleManager*>( child ) );
}
ScheduleManager *ScheduleManager::findManager( const QString& name ) const
{
if ( m_name == name ) {
return const_cast<ScheduleManager*>( this );
}
foreach ( ScheduleManager *sm, m_children ) {
ScheduleManager *m = sm->findManager( name );
if ( m ) {
return m;
}
}
return 0;
}
QList<ScheduleManager*> ScheduleManager::allChildren() const
{
QList<ScheduleManager*> lst;
foreach ( ScheduleManager *sm, m_children ) {
lst << sm;
lst << sm->allChildren();
}
return lst;
}
bool ScheduleManager::isParentOf( const ScheduleManager *sm ) const
{
if ( indexOf( sm ) >= 0 ) {
return true;
}
foreach ( ScheduleManager *p, m_children ) {
if ( p->isParentOf( sm ) ) {
return true;
}
}
return false;
}
void ScheduleManager::setName( const QString& name )
{
m_name = name;
#ifndef NDEBUG
setObjectName( name );
#endif
if ( m_expected ) {
m_expected->setName( name );
m_project.changed( m_expected );
}
m_project.changed( this );
}
bool ScheduleManager::isChildBaselined() const
{
//debugPlan<<on;
foreach ( ScheduleManager *sm, m_children ) {
if ( sm->isBaselined() || sm->isChildBaselined() ) {
return true;
}
}
return false;
}
void ScheduleManager::setBaselined( bool on )
{
//debugPlan<<on;
m_baselined = on;
m_project.changed( this );
}
void ScheduleManager::setAllowOverbooking( bool on )
{
//debugPlan<<on;
m_allowOverbooking = on;
m_project.changed( this );
}
bool ScheduleManager::allowOverbooking() const
{
//debugPlan<<m_name<<"="<<m_allowOverbooking;
return m_allowOverbooking;
}
bool ScheduleManager::checkExternalAppointments() const
{
//debugPlan<<m_name<<"="<<m_allowOverbooking;
return m_checkExternalAppointments;
}
void ScheduleManager::setCheckExternalAppointments( bool on )
{
//debugPlan<<m_name<<"="<<m_checkExternalAppointments;
m_checkExternalAppointments = on;
}
void ScheduleManager::scheduleChanged( MainSchedule *sch )
{
m_project.changed( sch );
m_project.changed( this ); //hmmm, due to aggregated info
}
void ScheduleManager::setUsePert( bool on )
{
m_usePert = on;
m_project.changed( this );
}
void ScheduleManager::setSchedulingDirection( bool on )
{
//debugPlan<<on;
m_schedulingDirection = on;
m_project.changed( this );
}
void ScheduleManager::setScheduling( bool on )
{
m_scheduling = on;
if ( ! on ) {
m_project.setProgress( 0, this );
}
m_project.changed( this );
}
const QList<SchedulerPlugin*> ScheduleManager::schedulerPlugins() const
{
return m_project.schedulerPlugins().values();
}
QString ScheduleManager::schedulerPluginId() const
{
return m_schedulerPluginId;
}
void ScheduleManager::setSchedulerPluginId( const QString &id )
{
m_schedulerPluginId = id;
m_project.changed( this );
}
SchedulerPlugin *ScheduleManager::schedulerPlugin() const
{
if ( m_schedulerPluginId.isEmpty() || !m_project.schedulerPlugins().contains( m_schedulerPluginId ) ) {
// try to avoid crash
return m_project.schedulerPlugins().value( m_project.schedulerPlugins().keys().value( 0 ) );
}
return m_project.schedulerPlugins().value( m_schedulerPluginId );
}
QStringList ScheduleManager::schedulerPluginNames() const
{
QStringList lst;
QMap<QString, SchedulerPlugin*>::const_iterator it = m_project.schedulerPlugins().constBegin();
QMap<QString, SchedulerPlugin*>::const_iterator end = m_project.schedulerPlugins().constEnd();
for ( ; it != end; ++it ) {
lst << it.value()->name();
}
return lst;
}
int ScheduleManager::schedulerPluginIndex() const
{
if ( m_schedulerPluginId.isEmpty() ) {
return 0;
}
return m_project.schedulerPlugins().keys().indexOf( m_schedulerPluginId );
}
void ScheduleManager::setSchedulerPlugin( int index )
{
if ( schedulerPlugin() ) {
schedulerPlugin()->stopCalculation( this ); // in case...
}
m_schedulerPluginId = m_project.schedulerPlugins().keys().value( index );
debugPlan<<index<<m_schedulerPluginId;
m_project.changed( this );
}
void ScheduleManager::calculateSchedule()
{
m_calculationresult = CalculationRunning;
if ( schedulerPlugin() ) {
schedulerPlugin()->calculate( m_project, this );
}
}
void ScheduleManager::stopCalculation()
{
if ( schedulerPlugin() ) {
schedulerPlugin()->stopCalculation( this );
}
}
void ScheduleManager::haltCalculation()
{
if ( schedulerPlugin() ) {
schedulerPlugin()->haltCalculation( this );
}
}
void ScheduleManager::setMaxProgress( int value )
{
m_maxprogress = value;
emit maxProgressChanged( value );
m_project.changed( this );
}
void ScheduleManager::setProgress( int value )
{
m_progress = value;
emit progressChanged( value );
m_project.changed( this );
}
void ScheduleManager::setDeleted( bool on )
{
if ( m_expected ) {
m_expected->setDeleted( on );
}
m_project.changed( this );
}
void ScheduleManager::setExpected( MainSchedule *sch )
{
//debugPlan<<m_expected<<","<<sch;
if ( m_expected ) {
m_project.sendScheduleToBeRemoved( m_expected );
m_expected->setDeleted( true );
m_project.sendScheduleRemoved( m_expected );
}
m_expected = sch;
if ( sch ) {
m_project.sendScheduleToBeAdded( this, 0 );
sch->setManager( this );
m_expected->setDeleted( false );
m_project.sendScheduleAdded( sch );
}
m_project.changed( this );
}
QStringList ScheduleManager::state() const
{
QStringList lst;
if ( isBaselined() ) {
return lst << i18n( "Baselined" );
}
if ( m_scheduling ) {
return lst << i18n( "Scheduling" );
}
if ( m_expected == 0 ) {
return lst << i18n( "Not scheduled" );
}
if ( Schedule *s = m_expected ) {
if ( s->resourceError || s->resourceOverbooked || s->resourceNotAvailable || s->constraintError || s->schedulingError ) {
return lst << i18n( "Error" );
}
return s->state();
}
return lst;
}
QList<long unsigned int> ScheduleManager::supportedGranularities() const
{
QList<long unsigned int> lst;
if ( schedulerPlugin() ) {
lst = schedulerPlugin()->granularities();
}
return lst;
}
int ScheduleManager::granularity() const
{
if ( schedulerPlugin() ) {
return schedulerPlugin()->granularity();
}
return 0;
}
void ScheduleManager::setGranularity( int duration )
{
if ( schedulerPlugin() ) {
schedulerPlugin()->setGranularity( duration );
}
m_project.changed( this );
}
void ScheduleManager::incProgress()
{
m_project.incProgress();
}
void ScheduleManager::logAdded( const Schedule::Log &log )
{
emit sigLogAdded( log );
int row = expected()->logs().count() - 1;
emit logInserted( expected(), row, row );
}
void ScheduleManager::slotAddLog( const QVector<KPlato::Schedule::Log> &log )
{
if ( expected() && ! log.isEmpty() ) {
int first = expected()->logs().count();
int last = first + log.count() - 1;
Q_UNUSED(last);
foreach ( const KPlato::Schedule::Log &l, log ) {
expected()->addLog( l );
}
}
}
QMap< int, QString > ScheduleManager::phaseNames() const
{
if ( expected() ) {
return expected()->phaseNames();
}
return QMap<int, QString>();
}
void ScheduleManager::setPhaseNames( const QMap<int, QString> &phasenames )
{
if ( expected() ) {
expected()->setPhaseNames( phasenames );
}
}
bool ScheduleManager::loadXML( KoXmlElement &element, XMLLoaderObject &status )
{
MainSchedule *sch = 0;
if ( status.version() <= "0.5" ) {
m_usePert = false;
sch = loadMainSchedule( element, status );
if ( sch ) {
sch->setManager( this );
switch ( sch->type() ) {
case Schedule::Expected: setExpected( sch ); break;
}
}
return true;
}
setName( element.attribute( "name" ) );
m_id = element.attribute( "id" );
m_usePert = (element.attribute( "distribution" ).toInt()) == 1;
m_allowOverbooking = (bool)(element.attribute( "overbooking" ).toInt());
m_checkExternalAppointments = (bool)(element.attribute( "check-external-appointments" ).toInt());
m_schedulingDirection = (bool)(element.attribute( "scheduling-direction" ).toInt());
m_baselined = (bool)(element.attribute( "baselined" ).toInt());
m_schedulerPluginId = element.attribute( "scheduler-plugin-id" );
if ( status.project().schedulerPlugins().contains( m_schedulerPluginId ) ) {
// atm we only load for current plugin
int g = element.attribute( "granularity", "0" ).toInt();
status.project().schedulerPlugins().value( m_schedulerPluginId )->setGranularity( g );
}
m_recalculate = (bool)(element.attribute( "recalculate" ).toInt());
m_recalculateFrom = DateTime::fromString( element.attribute( "recalculate-from" ), status.projectTimeZone() );
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
//debugPlan<<e.tagName();
if ( e.tagName() == "schedule" ) {
sch = loadMainSchedule( e, status );
if ( sch ) {
sch->setManager( this );
switch ( sch->type() ) {
case Schedule::Expected: setExpected( sch ); break;
}
}
} else if ( e.tagName() == "plan" ) {
ScheduleManager *sm = new ScheduleManager( status.project() );
if ( sm->loadXML( e, status ) ) {
m_project.addScheduleManager( sm, this );
} else {
errorPlan<<"Failed to load schedule manager"<<endl;
delete sm;
}
}
}
return true;
}
MainSchedule *ScheduleManager::loadMainSchedule( KoXmlElement &element, XMLLoaderObject &status ) {
MainSchedule *sch = new MainSchedule();
if ( sch->loadXML( element, status ) ) {
status.project().addSchedule( sch );
sch->setNode( &(status.project()) );
status.project().setParentSchedule( sch );
} else {
errorPlan << "Failed to load schedule" << endl;
delete sch;
sch = 0;
}
return sch;
}
bool ScheduleManager::loadMainSchedule( MainSchedule *schedule, KoXmlElement &element, XMLLoaderObject &status ) {
long sid = schedule->id();
if ( schedule->loadXML( element, status ) ) {
if ( sid != schedule->id() && status.project().findSchedule( sid ) ) {
status.project().takeSchedule( schedule );
}
if ( ! status.project().findSchedule( schedule->id() ) ) {
status.project().addSchedule( schedule );
}
schedule->setNode( &(status.project()) );
status.project().setParentSchedule( schedule );
return true;
}
return false;
}
void ScheduleManager::saveXML( QDomElement &element ) const
{
QDomElement el = element.ownerDocument().createElement( "plan" );
element.appendChild( el );
el.setAttribute( "name", m_name );
el.setAttribute( "id", m_id );
el.setAttribute( "distribution", QString::number(m_usePert ? 1 : 0) );
el.setAttribute( "overbooking", QString::number(m_allowOverbooking) );
el.setAttribute( "check-external-appointments", QString::number(m_checkExternalAppointments) );
el.setAttribute( "scheduling-direction", QString::number(m_schedulingDirection) );
el.setAttribute( "baselined", QString::number(m_baselined) );
el.setAttribute( "scheduler-plugin-id", m_schedulerPluginId );
if ( schedulerPlugin() ) {
// atm we only save for current plugin
el.setAttribute( "granularity", QString::number(schedulerPlugin()->granularity()) );
}
el.setAttribute( "recalculate", QString::number(m_recalculate) );
el.setAttribute( "recalculate-from", m_recalculateFrom.toString( Qt::ISODate ) );
if ( m_expected && ! m_expected->isDeleted() ) {
QDomElement schs = el.ownerDocument().createElement( "schedule" );
el.appendChild( schs );
m_expected->saveXML( schs );
m_project.saveAppointments( schs, m_expected->id() );
}
foreach ( ScheduleManager *sm, m_children ) {
sm->saveXML( el );
}
}
void ScheduleManager::saveWorkPackageXML( QDomElement &element, const Node &node ) const
{
QDomElement el = element.ownerDocument().createElement( "plan" );
element.appendChild( el );
el.setAttribute( "name", m_name );
el.setAttribute( "id", m_id );
el.setAttribute( "distribution", QString::number(m_usePert ? 1 : 0) );
el.setAttribute( "overbooking", QString::number(m_allowOverbooking) );
el.setAttribute( "check-external-appointments", QString::number(m_checkExternalAppointments) );
el.setAttribute( "scheduling-direction", QString::number(m_schedulingDirection) );
el.setAttribute( "baselined", QString::number(m_baselined) );
if ( m_expected && ! m_expected->isDeleted() ) { // TODO: should we check isScheduled() ?
QDomElement schs = el.ownerDocument().createElement( "schedule" );
el.appendChild( schs );
m_expected->saveXML( schs );
Schedule *s = node.findSchedule( m_expected->id() );
if ( s && ! s->isDeleted() ) {
s->saveAppointments( schs );
}
}
}
} //namespace KPlato
QDebug operator<<( QDebug dbg, const KPlato::Schedule *s )
{
if ( s ) {
return dbg<<(*s);
}
return dbg<<"Schedule(0x0)";
}
QDebug operator<<( QDebug dbg, const KPlato::Schedule &s )
{
dbg.nospace()<<"Schedule["<<s.id();
if (s.isDeleted()) {
dbg.nospace()<<": Deleted";
} else {
dbg.nospace()<<": "<<s.name();
}
dbg.nospace()<<"]";
return dbg.space();
}
QDebug operator<<( QDebug dbg, const KPlato::Schedule::Log &log )
{
dbg.nospace()<<"Schedule::Log: "<<log.formatMsg();
return dbg.space();
}
diff --git a/src/libs/kernel/kptschedulerplugin.cpp b/src/libs/kernel/kptschedulerplugin.cpp
index e33f59c7..0ccee100 100644
--- a/src/libs/kernel/kptschedulerplugin.cpp
+++ b/src/libs/kernel/kptschedulerplugin.cpp
@@ -1,523 +1,524 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptschedulerplugin.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include "kptdebug.h"
#include "KoXmlReader.h"
namespace KPlato
{
class Q_DECL_HIDDEN SchedulerPlugin::Private
{
public:
Private() {}
QString name;
QString comment;
};
SchedulerPlugin::SchedulerPlugin(QObject *parent)
: QObject(parent),
d( new SchedulerPlugin::Private() ),
m_granularity( 0 )
{
// register Schedule::Log so it can be used in queued connections
qRegisterMetaType<Schedule::Log>("Schedule::Log");
m_synctimer.setInterval( 500 );
connect(&m_synctimer, &QTimer::timeout, this, &SchedulerPlugin::slotSyncData);
}
SchedulerPlugin::~SchedulerPlugin()
{
foreach ( SchedulerThread *s, m_jobs ) {
s->haltScheduling();
}
delete d;
}
void SchedulerPlugin::setName( const QString &name )
{
d->name = name;
}
QString SchedulerPlugin::name() const
{
return d->name;
}
void SchedulerPlugin::setComment( const QString &comment )
{
d->comment = comment;
}
QString SchedulerPlugin::comment() const
{
return d->comment;
}
int SchedulerPlugin::capabilities() const
{
return AvoidOverbooking | AllowOverbooking | ScheduleForward | ScheduleBackward;
}
void SchedulerPlugin::stopCalculation( ScheduleManager *sm )
{
foreach ( SchedulerThread *j, m_jobs ) {
if ( sm == j->mainManager() ) {
j->stopScheduling();
}
}
}
void SchedulerPlugin::haltCalculation( ScheduleManager *sm )
{
debugPlan<<"SchedulerPlugin::haltCalculation:"<<sm;
foreach ( SchedulerThread *j, m_jobs ) {
if ( sm == j->mainManager() ) {
haltCalculation( j );
break;
}
}
sm->setCalculationResult( ScheduleManager::CalculationCanceled );
sm->setScheduling( false );
}
void SchedulerPlugin::stopCalculation( SchedulerThread *job )
{
job->stopScheduling();
}
void SchedulerPlugin::haltCalculation( SchedulerThread *job )
{
debugPlan<<"SchedulerPlugin::haltCalculation:"<<job<<m_jobs.contains( job );
disconnect(this, 0, job, 0 );
job->haltScheduling();
if ( m_jobs.contains( job ) ) {
debugPlan<<"SchedulerPlugin::haltCalculation: remove"<<job;
m_jobs.removeAt( m_jobs.indexOf( job ) );
}
}
QList<long unsigned int> SchedulerPlugin::granularities() const
{
return m_granularities;
}
int SchedulerPlugin::granularity() const
{
return m_granularity;
}
void SchedulerPlugin::setGranularity( int index )
{
m_granularity = qMin( index, m_granularities.count() - 1 );
}
void SchedulerPlugin::slotSyncData()
{
updateProgress();
updateLog();
}
void SchedulerPlugin::updateProgress()
{
foreach ( SchedulerThread *j, m_jobs ) {
ScheduleManager *sm = j->mainManager();
if ( sm->maxProgress() != j->maxProgress() ) {
sm->setMaxProgress( j->maxProgress() );
}
sm->setProgress( j->progress() );
}
}
void SchedulerPlugin::updateLog()
{
foreach ( SchedulerThread *j, m_jobs ) {
updateLog( j );
}
}
void SchedulerPlugin::updateLog( SchedulerThread *j )
{
ScheduleManager *sm = j->mainManager();
Project *p = j->mainProject();
Q_ASSERT( p == &(sm->project()) );
#ifdef NDEBUG
Q_UNUSED(p)
#endif
if ( j->manager() ) {
sm->setPhaseNames( j->phaseNames() );
}
QVector<Schedule::Log> logs = j->takeLog();
QMutableVectorIterator<Schedule::Log> i(logs);
while (i.hasNext()) {
Schedule::Log &l = i.next();
// map log from temporary project to real project
if ( l.resource ) {
const Resource *r = l.resource;
l.resource = sm->project().findResource( l.resource->id() );
Q_ASSERT( r != l.resource );
#ifdef NDEBUG
Q_UNUSED(r)
#endif
Q_ASSERT( l.resource->project() == p );
}
if ( l.node ) {
const Node *n = l.node;
if ( l.node->type() == Node::Type_Project ) {
l.node = &( sm->project() );
} else {
l.node = sm->project().findNode( l.node->id() );
}
Q_ASSERT( n != l.node );
#ifdef NDEBUG
Q_UNUSED(n)
#endif
Q_ASSERT( l.node->projectNode() == p );
}
}
if ( ! logs.isEmpty() ) {
sm->slotAddLog( logs );
}
}
void SchedulerPlugin::updateProject( const Project *tp, const ScheduleManager *tm, Project *mp, ScheduleManager *sm ) const
{
Q_CHECK_PTR( tp );
Q_CHECK_PTR( tm );
Q_CHECK_PTR( mp );
Q_CHECK_PTR( sm );
//debugPlan<<"SchedulerPlugin::updateProject:"<<tp<<tp->name()<<"->"<<mp<<mp->name()<<sm;
Q_ASSERT( tp != mp && tm != sm );
long sid = tm->scheduleId();
Q_ASSERT( sid == sm->scheduleId() );
XMLLoaderObject status;
status.setVersion( PLAN_FILE_SYNTAX_VERSION );
status.setProject( mp );
status.setProjectTimeZone( mp->timeZone() );
foreach ( const Node *tn, tp->allNodes() ) {
Node *mn = mp->findNode( tn->id() );
Q_ASSERT( mn );
if ( mn ) {
updateNode( tn, mn, sid, status );
}
}
foreach ( const Resource *tr, tp->resourceList() ) {
Resource *r = mp->findResource( tr->id() );
Q_ASSERT( r );
if ( r ) {
updateResource( tr, r, status );
}
}
// update main schedule and appointments
updateAppointments( tp, tm, mp, sm, status );
sm->scheduleChanged( sm->expected() );
}
void SchedulerPlugin::updateNode( const Node *tn, Node *mn, long sid, XMLLoaderObject &status ) const
{
//debugPlan<<"SchedulerPlugin::updateNode:"<<tn<<tn->name()<<"->"<<mn<<mn->name();
NodeSchedule *s = static_cast<NodeSchedule*>( tn->schedule( sid ) );
if ( s == 0 ) {
warnPlan<<"SchedulerPlugin::updateNode:"<<"Task:"<<tn->name()<<"could not find schedule with id:"<<sid;
return;
}
QDomDocument doc( "tmp" );
QDomElement e = doc.createElement( "schedules" );
doc.appendChild( e );
s->saveXML( e );
Q_ASSERT( ! mn->findSchedule( sid ) );
s = static_cast<NodeSchedule*>( mn->schedule( sid ) );
Q_ASSERT( s == 0 );
s = new NodeSchedule();
KoXmlDocument xd;
xd.setContent( doc.toString() );
KoXmlElement se = xd.documentElement().namedItem( "schedule" ).toElement();
Q_ASSERT( ! se.isNull() );
s->loadXML( se, status );
s->setDeleted( false );
s->setNode( mn );
mn->addSchedule( s );
}
void SchedulerPlugin::updateResource( const Resource *tr, Resource *r, XMLLoaderObject &status ) const
{
QDomDocument doc( "tmp" );
QDomElement e = doc.createElement( "cache" );
doc.appendChild( e );
tr->saveCalendarIntervalsCache( e );
KoXmlDocument xd;
QString err;
xd.setContent( doc.toString(), &err );
KoXmlElement se = xd.documentElement();
Q_ASSERT( ! se.isNull() );
r->loadCalendarIntervalsCache( se, status );
Calendar *cr = tr->calendar();
Calendar *c = r->calendar();
if ( cr == 0 || c == 0 ) {
return;
}
debugPlan<<"cr:"<<cr->cacheVersion()<<"c"<<c->cacheVersion();
c->setCacheVersion( cr->cacheVersion() );
}
void SchedulerPlugin::updateAppointments( const Project *tp, const ScheduleManager *tm, Project *mp, ScheduleManager *sm, XMLLoaderObject &status ) const
{
MainSchedule *sch = tm->expected();
Q_ASSERT( sch );
Q_ASSERT( sch != sm->expected() );
Q_ASSERT( sch->id() == sm->expected()->id() );
QDomDocument doc( "tmp" );
QDomElement e = doc.createElement( "schedule" );
doc.appendChild( e );
sch->saveXML( e );
tp->saveAppointments( e, sch->id() );
KoXmlDocument xd;
xd.setContent( doc.toString() );
KoXmlElement se = xd.namedItem( "schedule" ).toElement();
Q_ASSERT( ! se.isNull() );
bool ret = sm->loadMainSchedule( sm->expected(), se, status ); // also loads appointments
Q_ASSERT( ret );
#ifdef NDEBUG
Q_UNUSED(ret)
#endif
mp->setCurrentSchedule( sch->id() );
sm->expected()->setPhaseNames( sch->phaseNames() );
mp->changed( sm );
}
//----------------------
SchedulerThread::SchedulerThread( Project *project, ScheduleManager *manager, QObject *parent )
: QThread( parent ),
m_mainproject( project ),
m_mainmanager( manager ),
m_mainmanagerId( manager->managerId() ),
m_project( 0 ),
m_manager( 0 ),
m_stopScheduling(false ),
m_haltScheduling( false ),
m_progress( 0 )
{
manager->createSchedules(); // creates expected() to get log messages during calculation
QDomDocument document( "kplato" );
saveProject( project, document );
m_pdoc.setContent( document.toString() );
connect( this, &QThread::started, this, &SchedulerThread::slotStarted);
connect( this, &QThread::finished, this, &SchedulerThread::slotFinished);
}
SchedulerThread::~SchedulerThread()
{
debugPlan<<"SchedulerThread::~SchedulerThread:"<<QThread::currentThreadId();
delete m_project;
m_project = 0;
}
void SchedulerThread::setMaxProgress( int value )
{
m_maxprogressMutex.lock();
m_maxprogress = value;
m_maxprogressMutex.unlock();
emit maxProgressChanged( value, m_mainmanager );
}
int SchedulerThread::maxProgress() const
{
QMutexLocker m( &m_maxprogressMutex );
return m_maxprogress;
}
void SchedulerThread::setProgress( int value )
{
m_progressMutex.lock();
m_progress = value;
m_progressMutex.unlock();
emit progressChanged( value, m_mainmanager );
}
int SchedulerThread::progress() const
{
QMutexLocker m( &m_progressMutex );
return m_progress;
}
void SchedulerThread::slotAddLog( const KPlato::Schedule::Log &log )
{
// debugPlan<<log;
QMutexLocker m( &m_logMutex );
m_logs << log;
}
QVector<Schedule::Log> SchedulerThread::takeLog()
{
QMutexLocker m( &m_logMutex );
const QVector<KPlato::Schedule::Log> l = m_logs;
m_logs.clear();
return l;
}
QMap<int, QString> SchedulerThread::phaseNames() const
{
QMutexLocker m( &m_managerMutex );
return m_manager->phaseNames();
}
void SchedulerThread::slotStarted()
{
emit jobStarted( this );
}
void SchedulerThread::slotFinished()
{
if ( m_haltScheduling ) {
deleteLater();
} else {
emit jobFinished( this );
}
}
void SchedulerThread::doRun()
{
slotStarted();
run();
slotFinished();
}
ScheduleManager *SchedulerThread::manager() const
{
QMutexLocker m( &m_managerMutex );
return m_manager;
}
Project *SchedulerThread::project() const
{
QMutexLocker m( &m_projectMutex );
return m_project;
}
void SchedulerThread::stopScheduling()
{
debugPlan<<"SchedulerThread::stopScheduling:";
m_stopScheduling = true;
}
void SchedulerThread::haltScheduling()
{
debugPlan<<"SchedulerThread::haltScheduling:";
m_haltScheduling = true;
}
void SchedulerThread::logError( Node *n, Resource *r, const QString &msg, int phase )
{
Schedule::Log log;
if ( r == 0 ) {
log = Schedule::Log( n, Schedule::Log::Type_Error, msg, phase );
} else {
log = Schedule::Log( n, r, Schedule::Log::Type_Error, msg, phase );
}
slotAddLog( log );
}
void SchedulerThread::logWarning( Node *n, Resource *r, const QString &msg, int phase )
{
Schedule::Log log;
if ( r == 0 ) {
log = Schedule::Log( n, Schedule::Log::Type_Warning, msg, phase );
} else {
log = Schedule::Log( n, r, Schedule::Log::Type_Warning, msg, phase );
}
slotAddLog( log );
}
void SchedulerThread::logInfo( Node *n, Resource *r, const QString &msg, int phase )
{
Schedule::Log log;
if ( r == 0 ) {
log = Schedule::Log( n, Schedule::Log::Type_Info, msg, phase );
} else {
log = Schedule::Log( n, r, Schedule::Log::Type_Info, msg, phase );
}
slotAddLog( log );
}
void SchedulerThread::logDebug( Node *n, Resource *r, const QString &msg, int phase )
{
Schedule::Log log;
if ( r == 0 ) {
log = Schedule::Log( n, Schedule::Log::Type_Debug, msg, phase );
} else {
log = Schedule::Log( n, r, Schedule::Log::Type_Debug, msg, phase );
}
slotAddLog( log );
}
//static
void SchedulerThread::saveProject( Project *project, QDomDocument &document )
{
document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "kplato" );
doc.setAttribute( "editor", "Plan" );
doc.setAttribute( "mime", "application/x-vnd.kde.plan" );
doc.setAttribute( "version", PLAN_FILE_SYNTAX_VERSION );
document.appendChild( doc );
project->save( doc );
}
//static
bool SchedulerThread::loadProject( Project *project, const KoXmlDocument &doc )
{
KoXmlElement pel = doc.documentElement().namedItem( "project" ).toElement();
if ( pel.isNull() ) {
return false;
}
XMLLoaderObject status;
status.setVersion( PLAN_FILE_SYNTAX_VERSION );
status.setProject( project );
return project->load( pel, status );
}
} //namespace KPlato
diff --git a/src/libs/kernel/kpttask.cpp b/src/libs/kernel/kpttask.cpp
index 0e497a67..b980ce10 100644
--- a/src/libs/kernel/kpttask.cpp
+++ b/src/libs/kernel/kpttask.cpp
@@ -1,3839 +1,3840 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
Copyright (C) 2004 - 2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2007 Florian Piquemal <flotueur@yahoo.fr>
Copyright (C) 2007 Alexis Ménard <darktears31@gmail.com>
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 "kpttask.h"
#include "kptappointment.h"
#include "kptproject.h"
#include "kptduration.h"
#include "kptrelation.h"
#include "kptdatetime.h"
#include "kptcalendar.h"
#include "kpteffortcostmap.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include <kptdebug.h>
#include <KoXmlReader.h>
#include <KLocalizedString>
namespace KPlato
{
Task::Task(Node *parent)
: Node(parent),
m_resource(),
m_workPackage( this )
{
//debugPlan<<"("<<this<<')';
m_requests.setTask( this );
Duration d(1, 0, 0);
m_estimate = new Estimate();
m_estimate->setOptimisticRatio(-10);
m_estimate->setPessimisticRatio(20);
m_estimate->setParentNode( this );
if (m_parent)
m_leader = m_parent->leader();
}
Task::Task(const Task &task, Node *parent)
: Node(task, parent),
m_resource(),
m_workPackage( this )
{
//debugPlan<<"("<<this<<')';
m_requests.setTask( this );
delete m_estimate;
if ( task.estimate() ) {
m_estimate = new Estimate( *( task.estimate() ) );
} else {
m_estimate = new Estimate();
}
m_estimate->setParentNode( this );
}
Task::~Task() {
while (!m_resource.isEmpty()) {
delete m_resource.takeFirst();
}
while (!m_parentProxyRelations.isEmpty()) {
delete m_parentProxyRelations.takeFirst();
}
while (!m_childProxyRelations.isEmpty()) {
delete m_childProxyRelations.takeFirst();
}
}
int Task::type() const {
if ( numChildren() > 0) {
return Node::Type_Summarytask;
} else if ( m_constraint == Node::FixedInterval ) {
if ( m_constraintEndTime == m_constraintStartTime ) {
return Node::Type_Milestone;
}
} else if ( m_estimate->expectedEstimate() == 0.0 ) {
return Node::Type_Milestone;
}
return Node::Type_Task;
}
Duration *Task::getRandomDuration() {
return 0L;
}
ResourceGroupRequest *Task::resourceGroupRequest(const ResourceGroup *group) const {
return m_requests.find(group);
}
void Task::clearResourceRequests() {
m_requests.clear();
changed( this );
}
void Task::addRequest(ResourceGroup *group, int numResources) {
addRequest(new ResourceGroupRequest(group, numResources));
}
void Task::addRequest(ResourceGroupRequest *request) {
//debugPlan<<m_name<<request<<request->group()<<request->group()->id()<<request->group()->name();
m_requests.addRequest(request);
changed( this );
}
void Task::takeRequest(ResourceGroupRequest *request) {
//debugPlan<<request;
m_requests.takeRequest(request);
changed( this );
}
QStringList Task::requestNameList() const {
return m_requests.requestNameList();
}
QList<Resource*> Task::requestedResources() const {
return m_requests.requestedResources();
}
bool Task::containsRequest( const QString &identity ) const {
return m_requests.contains( identity );
}
ResourceRequest *Task::resourceRequest( const QString &name ) const {
return m_requests.resourceRequest( name );
}
QStringList Task::assignedNameList( long id) const {
Schedule *s = schedule( id );
if ( s == 0 ) {
return QStringList();
}
return s->resourceNameList();
}
void Task::makeAppointments() {
if (m_currentSchedule == 0)
return;
if (type() == Node::Type_Task) {
//debugPlan<<m_name<<":"<<m_currentSchedule->startTime<<","<<m_currentSchedule->endTime<<";"<<m_currentSchedule->duration.toString();
m_requests.makeAppointments(m_currentSchedule);
//debugPlan<<m_name<<":"<<m_currentSchedule->startTime<<","<<m_currentSchedule->endTime<<";"<<m_currentSchedule->duration.toString();
} else if (type() == Node::Type_Summarytask) {
foreach (Node *n, m_nodes) {
n->makeAppointments();
}
} else if (type() == Node::Type_Milestone) {
//debugPlan<<"Milestone not implemented";
// Well, shouldn't have resources anyway...
}
}
void Task::copySchedule()
{
if ( m_currentSchedule == 0 || type() != Node::Type_Task ) {
return;
}
int id = m_currentSchedule->parentScheduleId();
NodeSchedule *ns = static_cast<NodeSchedule*>( findSchedule( id ) );
if ( ns == 0 ) {
return;
}
if ( type() == Node::Type_Task ) {
copyAppointments( ns->startTime, ns->endTime );
}
m_currentSchedule->startTime = ns->startTime;
m_currentSchedule->earlyStart = ns->earlyStart;
m_currentSchedule->endTime = ns->endTime;
m_currentSchedule->lateFinish = ns->lateFinish;
m_currentSchedule->duration = ns->duration;
// TODO: status flags, etc
//debugPlan;
}
void Task::copyAppointments()
{
copyAppointments( DateTime(), m_currentSchedule->startTime );
}
void Task::copyAppointments( const DateTime &start, const DateTime &end )
{
if ( m_currentSchedule == 0 || type() != Node::Type_Task ) {
return;
}
int id = m_currentSchedule->parentScheduleId();
NodeSchedule *ns = static_cast<NodeSchedule*>( findSchedule( id ) );
if ( ns == 0 ) {
return;
}
DateTime st = start.isValid() ? start : ns->startTime;
DateTime et = end.isValid() ? end : ns->endTime;
//debugPlan<<m_name<<st.toString()<<et.toString()<<m_currentSchedule->calculationMode();
foreach ( const Appointment *a, ns->appointments() ) {
Resource *r = a->resource() == 0 ? 0 : a->resource()->resource();
if ( r == 0 ) {
errorPlan<<"No resource";
continue;
}
AppointmentIntervalList lst = a->intervals( st, et );
if ( lst.isEmpty() ) {
//debugPlan<<"No intervals to copy from"<<a;
continue;
}
Appointment *curr = 0;
foreach ( Appointment *c, m_currentSchedule->appointments() ) {
if ( c->resource()->resource() == r ) {
//debugPlan<<"Found current appointment to"<<a->resource()->resource()->name()<<c;
curr = c;
break;
}
}
if ( curr == 0 ) {
curr = new Appointment();
m_currentSchedule->add( curr );
curr->setNode( m_currentSchedule );
//debugPlan<<"Created new appointment"<<curr;
}
ResourceSchedule *rs = static_cast<ResourceSchedule*>( r->findSchedule( m_currentSchedule->id() ) );
if ( rs == 0 ) {
rs = r->createSchedule( m_currentSchedule->parent() );
rs->setId( m_currentSchedule->id() );
rs->setName( m_currentSchedule->name() );
rs->setType( m_currentSchedule->type() );
//debugPlan<<"Resource schedule not found, id="<<m_currentSchedule->id();
}
rs->setCalculationMode( m_currentSchedule->calculationMode() );
if ( ! rs->appointments().contains( curr ) ) {
//debugPlan<<"add to resource"<<rs<<curr;
rs->add( curr );
curr->setResource( rs );
}
Appointment app;
app.setIntervals( lst );
//foreach ( AppointmentInterval *i, curr->intervals() ) { debugPlan<<i->startTime().toString()<<i->endTime().toString(); }
curr->merge( app );
//debugPlan<<"Appointments added";
}
m_currentSchedule->startTime = ns->startTime;
m_currentSchedule->earlyStart = ns->earlyStart;
}
void Task::calcResourceOverbooked() {
if (m_currentSchedule)
m_currentSchedule->calcResourceOverbooked();
}
bool Task::load(KoXmlElement &element, XMLLoaderObject &status ) {
QString s;
bool ok = false;
m_id = element.attribute(QStringLiteral("id"));
setName( element.attribute(QStringLiteral("name")) );
m_leader = element.attribute(QStringLiteral("leader"));
m_description = element.attribute(QStringLiteral("description"));
//debugPlan<<m_name<<": id="<<m_id;
// Allow for both numeric and text
QString constraint = element.attribute(QStringLiteral("scheduling"),QStringLiteral("0"));
m_constraint = (Node::ConstraintType)constraint.toInt(&ok);
if (!ok)
Node::setConstraint(constraint); // hmmm, why do I need Node::?
s = element.attribute(QStringLiteral("constraint-starttime"));
if (!s.isEmpty())
m_constraintStartTime = DateTime::fromString(s, status.projectTimeZone());
s = element.attribute(QStringLiteral("constraint-endtime"));
if (!s.isEmpty())
m_constraintEndTime = DateTime::fromString(s, status.projectTimeZone());
m_startupCost = element.attribute(QStringLiteral("startup-cost"), QStringLiteral("0.0")).toDouble();
m_shutdownCost = element.attribute(QStringLiteral("shutdown-cost"), QStringLiteral("0.0")).toDouble();
// Load the task children
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == QLatin1String("project")) {
// Load the subproject
/* Project *child = new Project(this, status);
if (child->load(e)) {
if (!project.addSubTask(child, this)) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}*/
} else if (e.tagName() == QLatin1String("task")) {
// Load the task
Task *child = new Task(this);
if (child->load(e, status)) {
if (!status.project().addSubTask(child, this)) {
delete child; // TODO: Complain about this
}
} else {
// TODO: Complain about this
delete child;
}
} else if (e.tagName() == QLatin1String("resource")) {
// TODO: Load the resource (projects don't have resources yet)
} else if (e.tagName() == QLatin1String("estimate") ||
( /*status.version() < "0.6" &&*/ e.tagName() == QLatin1String("effort") ) ) {
// Load the estimate
m_estimate->load(e, status);
} else if (e.tagName() == QLatin1String("resourcegroup-request")) {
// Load the resource request
// Handle multiple requests to same group gracefully (Not really allowed)
ResourceGroupRequest *r = m_requests.findGroupRequestById( e.attribute(QStringLiteral("group-id")) );
if ( r ) {
warnPlan<<"Multiple requests to same group, loading into existing group";
if ( ! r->load( e, status ) ) {
errorPlan<<"Failed to load resource request";
}
} else {
r = new ResourceGroupRequest();
if (r->load(e, status)) {
addRequest(r);
} else {
errorPlan<<"Failed to load resource request";
delete r;
}
}
} else if (e.tagName() == QLatin1String("workpackage")) {
m_workPackage.loadXML( e, status );
} else if (e.tagName() == QLatin1String("progress")) {
completion().loadXML( e, status );
} else if (e.tagName() == QLatin1String("schedules")) {
KoXmlNode n = e.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
if (el.tagName() == QLatin1String("schedule")) {
NodeSchedule *sch = new NodeSchedule();
if (sch->loadXML(el, status)) {
sch->setNode(this);
addSchedule(sch);
} else {
errorPlan<<"Failed to load schedule";
delete sch;
}
}
}
} else if (e.tagName() == QLatin1String("documents")) {
m_documents.load( e, status );
} else if (e.tagName() == QLatin1String("workpackage-log")) {
KoXmlNode n = e.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
if (el.tagName() == QLatin1String("workpackage")) {
WorkPackage *wp = new WorkPackage( this );
if ( wp->loadLoggedXML( el, status ) ) {
m_packageLog << wp;
} else {
errorPlan<<"Failed to load logged workpackage";
delete wp;
}
}
}
}
}
//debugPlan<<m_name<<" loaded";
return true;
}
void Task::save(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement(QStringLiteral("task"));
element.appendChild(me);
me.setAttribute(QStringLiteral("id"), m_id);
me.setAttribute(QStringLiteral("name"), m_name);
me.setAttribute(QStringLiteral("leader"), m_leader);
me.setAttribute(QStringLiteral("description"), m_description);
me.setAttribute(QStringLiteral("scheduling"),constraintToString());
me.setAttribute(QStringLiteral("constraint-starttime"),m_constraintStartTime.toString( Qt::ISODate ));
me.setAttribute(QStringLiteral("constraint-endtime"),m_constraintEndTime.toString( Qt::ISODate ));
me.setAttribute(QStringLiteral("startup-cost"), QString::number(m_startupCost));
me.setAttribute(QStringLiteral("shutdown-cost"), QString::number(m_shutdownCost));
me.setAttribute(QStringLiteral("wbs"), wbsCode()); //NOTE: included for information
m_estimate->save(me);
m_workPackage.saveXML(me);
completion().saveXML( me );
if (!m_schedules.isEmpty()) {
QDomElement schs = me.ownerDocument().createElement(QStringLiteral("schedules"));
me.appendChild(schs);
foreach (const Schedule *s, m_schedules) {
if (!s->isDeleted()) {
s->saveXML(schs);
}
}
}
if ( ! m_requests.isEmpty() ) {
m_requests.save(me);
}
m_documents.save( me );
// The workpackage log
if (!m_packageLog.isEmpty()) {
QDomElement log = me.ownerDocument().createElement(QStringLiteral("workpackage-log"));
me.appendChild(log);
foreach (const WorkPackage *wp, m_packageLog) {
wp->saveLoggedXML( log );
}
}
for (int i=0; i<numChildren(); i++) {
childNode(i)->save(me);
}
}
void Task::saveAppointments(QDomElement &element, long id) const {
//debugPlan<<m_name<<" id="<<id;
Schedule *sch = findSchedule(id);
if (sch) {
sch->saveAppointments(element);
}
foreach (const Node *n, m_nodes) {
n->saveAppointments(element, id);
}
}
void Task::saveWorkPackageXML(QDomElement &element, long id ) const
{
QDomElement me = element.ownerDocument().createElement(QStringLiteral("task"));
element.appendChild(me);
me.setAttribute(QStringLiteral("id"), m_id);
me.setAttribute(QStringLiteral("name"), m_name);
me.setAttribute(QStringLiteral("leader"), m_leader);
me.setAttribute(QStringLiteral("description"), m_description);
me.setAttribute(QStringLiteral("scheduling"),constraintToString());
me.setAttribute(QStringLiteral("constraint-starttime"),m_constraintStartTime.toString( Qt::ISODate ));
me.setAttribute(QStringLiteral("constraint-endtime"),m_constraintEndTime.toString( Qt::ISODate ));
me.setAttribute(QStringLiteral("startup-cost"), QString::number(m_startupCost));
me.setAttribute(QStringLiteral("shutdown-cost"), QString::number(m_shutdownCost));
me.setAttribute(QStringLiteral("wbs"), wbsCode()); // NOTE: included for information
m_estimate->save(me);
completion().saveXML( me );
if ( m_schedules.contains( id ) && ! m_schedules[ id ]->isDeleted() ) {
QDomElement schs = me.ownerDocument().createElement(QStringLiteral("schedules"));
me.appendChild(schs);
m_schedules[ id ]->saveXML( schs );
}
m_documents.save( me ); // TODO: copying documents
}
EffortCostMap Task::plannedEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ ) const {
//debugPlan<<m_name;
if ( type() == Node::Type_Summarytask ) {
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->plannedEffortCostPrDay( start, end, id, typ );
}
return ec;
}
Schedule *s = schedule( id );
if ( s ) {
return s->plannedEffortCostPrDay( start, end, typ );
}
return EffortCostMap();
}
EffortCostMap Task::plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ ) const {
//debugPlan<<m_name;
if ( type() == Node::Type_Summarytask ) {
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->plannedEffortCostPrDay( resource, start, end, id, typ );
}
return ec;
}
Schedule *s = schedule( id );
if ( s ) {
return s->plannedEffortCostPrDay( resource, start, end, typ );
}
return EffortCostMap();
}
EffortCostMap Task::actualEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ ) const {
//debugPlan<<m_name;
if ( type() == Node::Type_Summarytask ) {
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->actualEffortCostPrDay( start, end, id, typ );
}
return ec;
}
switch ( completion().entrymode() ) {
case Completion::FollowPlan:
return plannedEffortCostPrDay( start, end, id, typ );
default:
return completion().effortCostPrDay( start, end, id );
}
return EffortCostMap();
}
EffortCostMap Task::actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ ) const {
//debugPlan<<m_name;
if ( type() == Node::Type_Summarytask ) {
EffortCostMap ec;
QListIterator<Node*> it( childNodeIterator() );
while ( it.hasNext() ) {
ec += it.next() ->actualEffortCostPrDay( resource, start, end, id, typ );
}
return ec;
}
switch ( completion().entrymode() ) {
case Completion::FollowPlan:
return plannedEffortCostPrDay( resource, start, end, id, typ );
default:
return completion().effortCostPrDay( resource, start, end );
}
return EffortCostMap();
}
// Returns the total planned effort for this task (or subtasks)
Duration Task::plannedEffort( const Resource *resource, long id, EffortCostCalculationType typ ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->plannedEffort( resource, id, typ );
}
return eff;
}
Schedule *s = schedule( id );
if ( s ) {
eff = s->plannedEffort( resource, typ );
}
return eff;
}
// Returns the total planned effort for this task (or subtasks)
Duration Task::plannedEffort( long id, EffortCostCalculationType typ ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->plannedEffort( id, typ );
}
return eff;
}
Schedule *s = schedule( id );
if ( s ) {
eff = s->plannedEffort( typ );
}
return eff;
}
// Returns the total planned effort for this task (or subtasks) on date
Duration Task::plannedEffort( const Resource *resource, QDate date, long id, EffortCostCalculationType typ ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->plannedEffort( resource, date, id, typ );
}
return eff;
}
Schedule *s = schedule( id );
if ( s ) {
eff = s->plannedEffort( resource, date, typ );
}
return eff;
}
// Returns the total planned effort for this task (or subtasks) on date
Duration Task::plannedEffort(QDate date, long id, EffortCostCalculationType typ ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->plannedEffort( date, id, typ );
}
return eff;
}
Schedule *s = schedule( id );
if ( s ) {
eff = s->plannedEffort( date, typ );
}
return eff;
}
// Returns the total planned effort for this task (or subtasks) upto and including date
Duration Task::plannedEffortTo( QDate date, long id, EffortCostCalculationType typ ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->plannedEffortTo( date, id, typ );
}
return eff;
}
Schedule *s = schedule( id );
if ( s ) {
eff = s->plannedEffortTo( date, typ );
}
return eff;
}
// Returns the total planned effort for this task (or subtasks) upto and including date
Duration Task::plannedEffortTo( const Resource *resource, QDate date, long id, EffortCostCalculationType typ ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->plannedEffortTo( resource, date, id, typ );
}
return eff;
}
Schedule *s = schedule( id );
if ( s ) {
eff = s->plannedEffortTo( resource, date, typ );
}
return eff;
}
// Returns the total actual effort for this task (or subtasks)
Duration Task::actualEffort() const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->actualEffort();
}
}
return completion().actualEffort();
}
// Returns the total actual effort for this task (or subtasks) on date
Duration Task::actualEffort( QDate date ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->actualEffort( date );
}
return eff;
}
return completion().actualEffort( date );
}
// Returns the total actual effort for this task (or subtasks) to date
Duration Task::actualEffortTo( QDate date ) const {
//debugPlan;
Duration eff;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
eff += n->actualEffortTo( date );
}
return eff;
}
return completion().actualEffortTo( date );
}
EffortCost Task::plannedCost( long id, EffortCostCalculationType typ ) const {
//debugPlan;
if (type() == Node::Type_Summarytask) {
return Node::plannedCost( id, typ );
}
EffortCost c;
Schedule *s = schedule( id );
if ( s ) {
c = s->plannedCost( typ );
}
return c;
}
double Task::plannedCostTo( QDate date, long id, EffortCostCalculationType typ ) const {
//debugPlan;
double c = 0;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
c += n->plannedCostTo( date, id, typ );
}
return c;
}
Schedule *s = schedule( id );
if ( s == 0 ) {
return c;
}
c = s->plannedCostTo( date, typ );
if ( date >= s->startTime.date() ) {
c += m_startupCost;
}
if ( date >= s->endTime.date() ) {
c += m_shutdownCost;
}
return c;
}
EffortCost Task::actualCostTo( long int id, QDate date ) const {
//debugPlan;
EffortCostMap ecm = acwp( id );
return EffortCost( ecm.effortTo( date ), ecm.costTo( date ) );
}
double Task::bcws( QDate date, long id ) const
{
//debugPlan;
double c = plannedCostTo( date, id );
//debugPlan<<c;
return c;
}
EffortCostMap Task::bcwsPrDay( long int id, EffortCostCalculationType typ )
{
//debugPlan;
if (type() == Node::Type_Summarytask) {
return Node::bcwsPrDay( id );
}
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostCache &cache = s->bcwsPrDayCache( typ );
if ( ! cache.cached ) {
EffortCostMap ec = s->bcwsPrDay( typ );
if ( typ != ECCT_Work ) {
if ( m_startupCost > 0.0 ) {
ec.add( s->startTime.date(), Duration::zeroDuration, m_startupCost );
}
if ( m_shutdownCost > 0.0 ) {
ec.add( s->endTime.date(), Duration::zeroDuration, m_shutdownCost );
}
cache.effortcostmap = ec;
cache.cached = true;
}
}
return cache.effortcostmap;
}
EffortCostMap Task::bcwpPrDay( long int id, EffortCostCalculationType typ )
{
//debugPlan;
if ( type() == Node::Type_Summarytask ) {
return Node::bcwpPrDay( id, typ );
}
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostCache cache = s->bcwpPrDayCache( typ );
if ( ! cache.cached ) {
// do not use bcws cache, it includes startup/shutdown cost
EffortCostMap e = s->plannedEffortCostPrDay( s->appointmentStartTime().date(), s->appointmentEndTime().date(), typ );
if ( completion().isStarted() && ! e.isEmpty() ) {
// calculate bcwp on bases of bcws *without* startup/shutdown cost
double totEff = e.totalEffort().toDouble( Duration::Unit_h );
double totCost = e.totalCost();
QDate sd = completion().entries().keys().value( 0 );
if ( ! sd.isValid() || e.startDate() < sd ) {
sd = e.startDate();
}
QDate ed = qMax( e.endDate(), completion().entryDate() );
for ( QDate d = sd; d <= ed; d = d.addDays( 1 ) ) {
double p = (double)(completion().percentFinished( d )) / 100.0;
EffortCost ec = e.days()[ d ];
ec.setBcwpEffort( totEff * p );
ec.setBcwpCost( totCost * p );
e.insert( d, ec );
}
}
if ( typ != ECCT_Work ) {
// add bcws startup/shutdown cost
if ( m_startupCost > 0.0 ) {
e.add( s->startTime.date(), Duration::zeroDuration, m_startupCost );
}
if ( m_shutdownCost > 0.0 ) {
e.add( s->endTime.date(), Duration::zeroDuration, m_shutdownCost );
}
// add bcwp startup/shutdown cost
if ( m_shutdownCost > 0.0 && completion().finishIsValid() ) {
QDate finish = completion().finishTime().date();
e.addBcwpCost( finish, m_shutdownCost );
debugPlan<<"addBcwpCost:"<<finish<<m_shutdownCost;
// bcwp is cumulative so add to all entries after finish (in case task finished early)
for ( EffortCostDayMap::const_iterator it = e.days().constBegin(); it != e.days().constEnd(); ++it ) {
const QDate date = it.key();
if ( date > finish ) {
e.addBcwpCost( date, m_shutdownCost );
debugPlan<<"addBcwpCost:"<<date<<m_shutdownCost;
}
}
}
if ( m_startupCost > 0.0 && completion().startIsValid() ) {
QDate start = completion().startTime().date();
e.addBcwpCost( start, m_startupCost );
// bcwp is cumulative so add to all entries after start
for ( EffortCostDayMap::const_iterator it = e.days().constBegin(); it != e.days().constEnd(); ++it ) {
const QDate date = it.key();
if ( date > start ) {
e.addBcwpCost( date, m_startupCost );
}
}
}
}
cache.effortcostmap = e;
cache.cached = true;
}
return cache.effortcostmap;
}
Duration Task::budgetedWorkPerformed( QDate date, long id ) const
{
//debugPlan;
Duration e;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
e += n->budgetedWorkPerformed( date, id );
}
return e;
}
e = plannedEffort( id ) * (double)completion().percentFinished( date ) / 100.0;
//debugPlan<<m_name<<"("<<id<<")"<<date<<"="<<e.toString();
return e;
}
double Task::budgetedCostPerformed( QDate date, long id ) const
{
//debugPlan;
double c = 0.0;
if (type() == Node::Type_Summarytask) {
foreach (const Node *n, childNodeIterator()) {
c += n->budgetedCostPerformed( date, id );
}
return c;
}
c = plannedCost( id ).cost() * (double)completion().percentFinished( date ) / 100.0;
if ( completion().isStarted() && date >= completion().startTime().date() ) {
c += m_startupCost;
}
if ( completion().isFinished() && date >= completion().finishTime().date() ) {
c += m_shutdownCost;
}
//debugPlan<<m_name<<"("<<id<<")"<<date<<"="<<e.toString();
return c;
}
double Task::bcwp( long id ) const
{
return bcwp( QDate::currentDate(), id );
}
double Task::bcwp( QDate date, long id ) const
{
return budgetedCostPerformed( date, id );
}
EffortCostMap Task::acwp( long int id, KPlato::EffortCostCalculationType typ )
{
if ( type() == Node::Type_Summarytask ) {
return Node::acwp( id, typ );
}
Schedule *s = schedule( id );
if ( s == 0 ) {
return EffortCostMap();
}
EffortCostCache ec = s->acwpCache( typ );
if ( ! ec.cached ) {
//debugPlan<<m_name<<completion().entrymode();
EffortCostMap m;
switch ( completion().entrymode() ) {
case Completion::FollowPlan:
//TODO
break;
case Completion::EnterCompleted:
//hmmm
default: {
m = completion().actualEffortCost( id );
if ( completion().isStarted() ) {
EffortCost e;
e.setCost( m_startupCost );
m.add( completion().startTime().date(), e );
}
if ( completion().isFinished() ) {
EffortCost e;
e.setCost( m_shutdownCost );
m.add( completion().finishTime().date(), e );
}
}
}
ec.effortcostmap = m;
ec.cached = true;
}
return ec.effortcostmap;
}
EffortCost Task::acwp( QDate date, long id ) const
{
//debugPlan;
if (type() == Node::Type_Summarytask) {
return Node::acwp( date, id );
}
EffortCost c;
c = completion().actualCostTo( id, date );
if ( completion().isStarted() && date >= completion().startTime().date() ) {
c.add( Duration::zeroDuration, m_startupCost );
}
if ( completion().isFinished() && date >= completion().finishTime().date() ) {
c.add( Duration::zeroDuration, m_shutdownCost );
}
return c;
}
double Task::schedulePerformanceIndex( QDate date, long id ) const {
//debugPlan;
double r = 1.0;
double s = bcws( date, id );
double p = bcwp( date, id );
if ( s > 0.0 ) {
r = p / s;
}
return r;
}
double Task::effortPerformanceIndex( QDate date, long id ) const {
//debugPlan;
double r = 1.0;
Duration a, b;
if ( m_estimate->type() == Estimate::Type_Effort ) {
Duration b = budgetedWorkPerformed( date, id );
if ( b == Duration::zeroDuration ) {
return r;
}
Duration a = actualEffortTo( date );
if ( b == Duration::zeroDuration ) {
return 1.0;
}
r = b.toDouble() / a.toDouble();
} else if ( m_estimate->type() == Estimate::Type_Duration ) {
//TODO
}
return r;
}
//FIXME Handle summarytasks
double Task::costPerformanceIndex( long int id, QDate date, bool *error ) const
{
double res = 0.0;
double ac = actualCostTo( id, date ).cost();
bool e = ( ac == 0.0 || completion().percentFinished() == 0 );
if (error) {
*error = e;
}
if (!e) {
res = ( plannedCostTo( date, id ) * completion().percentFinished() ) / ( 100 * ac );
}
return res;
}
void Task::initiateCalculation(MainSchedule &sch) {
//debugPlan<<m_name<<" schedule:"<<sch.name()<<" id="<<sch.id();
m_currentSchedule = createSchedule(&sch);
m_currentSchedule->initiateCalculation();
clearProxyRelations();
Node::initiateCalculation(sch);
m_calculateForwardRun = false;
m_calculateBackwardRun = false;
m_scheduleForwardRun = false;
m_scheduleBackwardRun = false;
}
void Task::initiateCalculationLists(MainSchedule &sch) {
//debugPlan<<m_name;
if (type() == Node::Type_Summarytask) {
sch.insertSummaryTask(this);
// propagate my relations to my children and dependent nodes
foreach (Node *n, m_nodes) {
if (!dependParentNodes().isEmpty()) {
n->addParentProxyRelations( dependParentNodes() );
}
if (!dependChildNodes().isEmpty()) {
n->addChildProxyRelations( dependChildNodes() );
}
n->initiateCalculationLists(sch);
}
} else {
if (isEndNode()) {
sch.insertEndNode(this);
//debugPlan<<"endnodes append:"<<m_name;
}
if (isStartNode()) {
sch.insertStartNode(this);
//debugPlan<<"startnodes append:"<<m_name;
}
if ( ( m_constraint == Node::MustStartOn ) ||
( m_constraint == Node::MustFinishOn ) ||
( m_constraint == Node::FixedInterval ) )
{
sch.insertHardConstraint( this );
}
else if ( ( m_constraint == Node::StartNotEarlier ) ||
( m_constraint == Node::FinishNotLater ) )
{
sch.insertSoftConstraint( this );
}
}
}
DateTime Task::calculatePredeccessors(const QList<Relation*> &list, int use) {
DateTime time;
// do them forward
foreach (Relation *r, list) {
if (r->parent()->type() == Type_Summarytask) {
//debugPlan<<"Skip summarytask:"<<it.current()->parent()->name();
continue; // skip summarytasks
}
DateTime t = r->parent()->calculateForward(use); // early finish
switch (r->type()) {
case Relation::StartStart:
// I can't start earlier than my predesseccor
t = r->parent()->earlyStart() + r->lag();
break;
case Relation::FinishFinish: {
// I can't finish earlier than my predeccessor, so
// I can't start earlier than it's (earlyfinish+lag)- my duration
t += r->lag();
Schedule::OBState obs = m_currentSchedule->allowOverbookingState();
m_currentSchedule->setAllowOverbookingState( Schedule::OBS_Allow );
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug( QStringLiteral("FinishFinish: get duration to calculate early finish") );
#endif
t -= duration(t, use, true);
m_currentSchedule->setAllowOverbookingState( obs );
break;
}
default:
t += r->lag();
break;
}
if (!time.isValid() || t > time)
time = t;
}
//debugPlan<<time.toString()<<""<<m_name<<" calculatePredeccessors() ("<<list.count()<<")";
return time;
}
DateTime Task::calculateForward(int use)
{
if ( m_calculateForwardRun ) {
return m_currentSchedule->earlyFinish;
}
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
cs->setCalculationMode( Schedule::CalculateForward );
//cs->logDebug( "calculateForward: earlyStart=" + cs->earlyStart.toString() );
// calculate all predecessors
if (!dependParentNodes().isEmpty()) {
DateTime time = calculatePredeccessors(dependParentNodes(), use);
if (time.isValid() && time > cs->earlyStart) {
cs->earlyStart = time;
//cs->logDebug( QString( "calculate forward: early start moved to: %1" ).arg( cs->earlyStart.toString() ) );
}
}
if (!m_parentProxyRelations.isEmpty()) {
DateTime time = calculatePredeccessors(m_parentProxyRelations, use);
if (time.isValid() && time > cs->earlyStart) {
cs->earlyStart = time;
//cs->logDebug( QString( "calculate forward: early start moved to: %1" ).arg( cs->earlyStart.toString() ) );
}
}
m_calculateForwardRun = true;
//cs->logDebug( "calculateForward: earlyStart=" + cs->earlyStart.toString() );
return calculateEarlyFinish( use );
}
DateTime Task::calculateEarlyFinish(int use) {
//debugPlan<<m_name;
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
if (m_visitedForward) {
//debugPlan<<earliestStart.toString()<<" +"<<m_durationBackward.toString()<<""<<m_name<<" calculateForward() (visited)";
return m_earlyFinish;
}
bool pert = cs->usePert();
cs->setCalculationMode( Schedule::CalculateForward );
#ifndef PLAN_NLOGDEBUG
QTime timer;
timer.start();
cs->logDebug( QStringLiteral( "Start calculate forward: %1 " ).arg( constraintToString( true ) ) );
#endif
QLocale locale;
cs->logInfo( i18n( "Calculate early finish " ) );
//debugPlan<<"------>"<<m_name<<""<<cs->earlyStart;
if (type() == Node::Type_Task) {
m_durationForward = m_estimate->value(use, pert);
switch (constraint()) {
case Node::ASAP:
case Node::ALAP:
{
//debugPlan<<m_name<<" ASAP/ALAP:"<<cs->earlyStart;
cs->earlyStart = workTimeAfter( cs->earlyStart );
m_durationForward = duration(cs->earlyStart, use, false);
m_earlyFinish = cs->earlyStart + m_durationForward;
#ifndef PLAN_NLOGDEBUG
cs->logDebug("ASAP/ALAP: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + m_earlyFinish.toString() );
#endif
if ( !cs->allowOverbooking() ) {
cs->startTime = cs->earlyStart;
cs->endTime = m_earlyFinish;
makeAppointments();
// calculate duration wo checking booking = the earliest finish possible
Schedule::OBState obs = cs->allowOverbookingState();
cs->setAllowOverbookingState( Schedule::OBS_Allow );
m_durationForward = duration(cs->earlyStart, use, false);
cs->setAllowOverbookingState( obs );
#ifndef PLAN_NLOGDEBUG
cs->logDebug("ASAP/ALAP earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString() );
#endif
}
break;
}
case Node::MustFinishOn:
{
cs->earlyStart = workTimeAfter( cs->earlyStart );
m_durationForward = duration(cs->earlyStart, use, false);
cs->earlyFinish = cs->earlyStart + m_durationForward;
//debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->earlyStart<<cs->earlyFinish;
if (cs->earlyFinish > m_constraintEndTime) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
cs->earlyFinish = qMax( cs->earlyFinish, m_constraintEndTime );
if ( !cs->allowOverbooking() ) {
cs->endTime = cs->earlyFinish;
cs->startTime = cs->earlyFinish - duration( cs->earlyFinish, use, true );
makeAppointments();
}
m_earlyFinish = cs->earlyFinish;
m_durationForward = m_earlyFinish - cs->earlyStart;
break;
}
case Node::FinishNotLater:
{
m_durationForward = duration(cs->earlyStart, use, false);
cs->earlyFinish = cs->earlyStart + m_durationForward;
//debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->earlyStart<<cs->earlyFinish;
if (cs->earlyFinish > m_constraintEndTime) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
if ( !cs->allowOverbooking() ) {
cs->startTime = cs->earlyStart;
cs->endTime = cs->earlyFinish;
makeAppointments();
}
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
}
case Node::MustStartOn:
case Node::StartNotEarlier:
{
//debugPlan<<"MSO/SNE:"<<m_constraintStartTime<<cs->earlyStart;
cs->logDebug( constraintToString() + ": " + m_constraintStartTime.toString() + ' ' + cs->earlyStart.toString() );
cs->earlyStart = workTimeAfter( qMax( cs->earlyStart, m_constraintStartTime ) );
if ( cs->earlyStart < m_constraintStartTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
m_durationForward = duration( cs->earlyStart, use, false );
m_earlyFinish = cs->earlyStart + m_durationForward;
if ( !cs->allowOverbooking() ) {
cs->startTime = cs->earlyStart;
cs->endTime = m_earlyFinish;
makeAppointments();
// calculate duration wo checking booking = the earliest finish possible
Schedule::OBState obs = cs->allowOverbookingState();
cs->setAllowOverbookingState( Schedule::OBS_Allow );
m_durationForward = duration(cs->startTime, use, false);
cs->setAllowOverbookingState( obs );
m_earlyFinish = cs->earlyStart + m_durationForward;
#ifndef PLAN_NLOGDEBUG
cs->logDebug("MSO/SNE earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString() );
#endif
}
break;
}
case Node::FixedInterval: {
if ( cs->earlyStart > m_constraintStartTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
//cs->earlyStart = m_constraintStartTime;
m_durationForward = m_constraintEndTime - m_constraintStartTime;
if ( cs->earlyStart < m_constraintStartTime ) {
m_durationForward = m_constraintEndTime - cs->earlyStart;
}
if ( !cs->allowOverbooking() ) {
cs->startTime = m_constraintStartTime;
cs->endTime = m_constraintEndTime;
makeAppointments();
}
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
}
}
} else if (type() == Node::Type_Milestone) {
m_durationForward = Duration::zeroDuration;
switch (constraint()) {
case Node::MustFinishOn:
//debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->earlyStart;
//cs->logDebug( QString( "%1: %2, early start: %3" ).arg( constraintToString() ).arg( m_constraintEndTime.toString() ).arg( cs->earlyStart.toString() ) );
if ( cs->earlyStart < m_constraintEndTime ) {
m_durationForward = m_constraintEndTime - cs->earlyStart;
}
if ( cs->earlyStart > m_constraintEndTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
case Node::FinishNotLater:
//debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->earlyStart;
if ( cs->earlyStart > m_constraintEndTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
m_earlyFinish = cs->earlyStart;
break;
case Node::MustStartOn:
//debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->earlyStart;
if ( cs->earlyStart < m_constraintStartTime ) {
m_durationForward = m_constraintStartTime - cs->earlyStart;
}
if ( cs->earlyStart > m_constraintStartTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
case Node::StartNotEarlier:
//debugPlan<<"StartNotEarlier:"<<m_constraintStartTime<<cs->earlyStart;
if ( cs->earlyStart < m_constraintStartTime ) {
m_durationForward = m_constraintStartTime - cs->earlyStart;
}
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
case Node::FixedInterval:
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
default:
m_earlyFinish = cs->earlyStart + m_durationForward;
break;
}
//debugPlan<<m_name<<""<<earliestStart.toString();
} else if (type() == Node::Type_Summarytask) {
warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
} else { // ???
m_durationForward = Duration::zeroDuration;
}
m_visitedForward = true;
cs->insertForwardNode( this );
cs->earlyFinish = cs->earlyStart + m_durationForward;
foreach ( const Appointment *a, cs->appointments( Schedule::CalculateForward ) ) {
cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat) ) );
}
// clean up temporary usage
cs->startTime = DateTime();
cs->endTime = DateTime();
cs->duration = Duration::zeroDuration;
cs->logInfo( i18n( "Early finish calculated: %1", locale.toString(cs->earlyFinish, QLocale::ShortFormat) ) );
cs->incProgress();
#ifndef PLAN_NLOGDEBUG
cs->logDebug( QStringLiteral( "Finished calculate forward: %1 ms" ).arg( timer.elapsed() ) );
#endif
return m_earlyFinish;
}
DateTime Task::calculateSuccessors(const QList<Relation*> &list, int use) {
DateTime time;
foreach (Relation *r, list) {
if (r->child()->type() == Type_Summarytask) {
//debugPlan<<"Skip summarytask:"<<r->parent()->name();
continue; // skip summarytasks
}
DateTime t = r->child()->calculateBackward(use);
switch (r->type()) {
case Relation::StartStart: {
// I must start before my successor, so
// I can't finish later than it's (starttime-lag) + my duration
t -= r->lag();
Schedule::OBState obs = m_currentSchedule->allowOverbookingState();
m_currentSchedule->setAllowOverbookingState( Schedule::OBS_Allow );
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug( QStringLiteral("StartStart: get duration to calculate late start") );
#endif
t += duration(t, use, false);
m_currentSchedule->setAllowOverbookingState( obs );
break;
}
case Relation::FinishFinish:
// My successor cannot finish before me, so
// I can't finish later than it's latest finish - lag
t = r->child()->lateFinish() - r->lag();
break;
default:
t -= r->lag();
break;
}
if (!time.isValid() || t < time)
time = t;
}
//debugPlan<<time.toString()<<""<<m_name<<" calculateSuccessors() ("<<list.count()<<")";
return time;
}
DateTime Task::calculateBackward(int use) {
//debugPlan<<m_name;
if ( m_calculateBackwardRun ) {
return m_currentSchedule->lateStart;
}
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
cs->setCalculationMode( Schedule::CalculateBackward );
//cs->lateFinish = projectNode()->constraintEndTime();
// calculate all successors
if (!dependChildNodes().isEmpty()) {
DateTime time = calculateSuccessors(dependChildNodes(), use);
if (time.isValid() && time < cs->lateFinish) {
cs->lateFinish = time;
}
}
if (!m_childProxyRelations.isEmpty()) {
DateTime time = calculateSuccessors(m_childProxyRelations, use);
if (time.isValid() && time < cs->lateFinish) {
cs->lateFinish = time;
}
}
m_calculateBackwardRun = true;
return calculateLateStart( use );
}
DateTime Task::calculateLateStart(int use) {
//debugPlan<<m_name;
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
if (m_visitedBackward) {
//debugPlan<<latestFinish.toString()<<" -"<<m_durationBackward.toString()<<""<<m_name<<" calculateBackward() (visited)";
return cs->lateStart;
}
bool pert = cs->usePert();
cs->setCalculationMode( Schedule::CalculateBackward );
#ifndef PLAN_NLOGDEBUG
QTime timer;
timer.start();
cs->logDebug( QStringLiteral( "Start calculate backward: %1 " ).arg( constraintToString( true ) ) );
#endif
QLocale locale;
cs->logInfo( i18n( "Calculate late start" ) );
cs->logDebug( QStringLiteral( "%1: late finish= %2" ).arg( constraintToString() ).arg( cs->lateFinish.toString() ) );
//debugPlan<<m_name<<" id="<<cs->id()<<" mode="<<cs->calculationMode()<<": latestFinish="<<cs->lateFinish;
if (type() == Node::Type_Task) {
m_durationBackward = m_estimate->value(use, pert);
switch (constraint()) {
case Node::ASAP:
case Node::ALAP:
//debugPlan<<m_name<<" ASAP/ALAP:"<<cs->lateFinish;
cs->lateFinish = workTimeBefore( cs->lateFinish );
m_durationBackward = duration(cs->lateFinish, use, true);
cs->lateStart = cs->lateFinish - m_durationBackward;
#ifndef PLAN_NLOGDEBUG
cs->logDebug("ASAP/ALAP: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + cs->lateStart.toString() );
#endif
if ( !cs->allowOverbooking() ) {
cs->startTime = cs->lateStart;
cs->endTime = cs->lateFinish;
makeAppointments();
// calculate wo checking bookings = latest start possible
Schedule::OBState obs = cs->allowOverbookingState();
cs->setAllowOverbookingState( Schedule::OBS_Allow );
m_durationBackward = duration(cs->lateFinish, use, true);
cs->setAllowOverbookingState( obs );
#ifndef PLAN_NLOGDEBUG
cs->logDebug("ASAP/ALAP latest start possible: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + (cs->lateFinish-m_durationBackward).toString() );
#endif
}
break;
case Node::MustStartOn:
case Node::StartNotEarlier:
{
//debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->lateFinish;
cs->lateFinish = workTimeBefore( cs->lateFinish );
m_durationBackward = duration(cs->lateFinish, use, true);
cs->lateStart = cs->lateFinish - m_durationBackward;
if ( cs->lateStart < m_constraintStartTime) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
} else {
cs->lateStart = qMax( cs->earlyStart, m_constraintStartTime );
}
if ( !cs->allowOverbooking() ) {
if ( constraint() == MustStartOn ) {
cs->startTime = m_constraintStartTime;
cs->endTime = m_constraintStartTime + duration( m_constraintStartTime, use, false );
} else {
cs->startTime = qMax( cs->lateStart, m_constraintStartTime );
cs->endTime = qMax( cs->lateFinish, cs->startTime ); // safety
}
makeAppointments();
}
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
}
case Node::MustFinishOn:
case Node::FinishNotLater:
//debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->lateFinish;
cs->lateFinish = workTimeBefore( cs->lateFinish );
cs->endTime = cs->lateFinish;
if ( cs->lateFinish < m_constraintEndTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
} else {
cs->endTime = qMax( cs->earlyFinish, m_constraintEndTime );
}
m_durationBackward = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - m_durationBackward;
if ( !cs->allowOverbooking() ) {
makeAppointments();
}
m_durationBackward = cs->lateFinish - cs->startTime;
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
case Node::FixedInterval: {
//cs->lateFinish = m_constraintEndTime;
if ( cs->lateFinish < m_constraintEndTime ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
m_durationBackward = m_constraintEndTime - m_constraintStartTime;
if ( cs->lateFinish > m_constraintEndTime ) {
m_durationBackward = cs->lateFinish - m_constraintStartTime;
}
if ( !cs->allowOverbooking() ) {
cs->startTime = m_constraintStartTime;
cs->endTime = m_constraintEndTime;
makeAppointments();
}
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
}
}
} else if (type() == Node::Type_Milestone) {
m_durationBackward = Duration::zeroDuration;
switch (constraint()) {
case Node::MustFinishOn:
//debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->lateFinish;
if ( m_constraintEndTime < cs->lateFinish ) {
m_durationBackward = cs->lateFinish - m_constraintEndTime;
} else if ( m_constraintEndTime > cs->lateFinish ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
case Node::FinishNotLater:
//debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->lateFinish;
if ( m_constraintEndTime < cs->lateFinish ) {
m_durationBackward = cs->lateFinish - m_constraintEndTime;
} else if ( m_constraintEndTime > cs->lateFinish ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
case Node::MustStartOn:
//debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->lateFinish;
if ( m_constraintStartTime < cs->lateFinish ) {
m_durationBackward = cs->lateFinish - m_constraintStartTime;
} else if ( m_constraintStartTime > cs->lateFinish ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
cs->lateStart = cs->lateFinish - m_durationBackward;
//cs->logDebug( QString( "%1: constraint:%2, start=%3, finish=%4" ).arg( constraintToString() ).arg( m_constraintStartTime.toString() ).arg( cs->lateStart.toString() ).arg( cs->lateFinish.toString() ) );
break;
case Node::StartNotEarlier:
//debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->lateFinish;
if ( m_constraintStartTime > cs->lateFinish ) {
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to meet constraint", constraintToString( true ) ) );
}
cs->lateStart = cs->lateFinish;
break;
case Node::FixedInterval:
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
default:
cs->lateStart = cs->lateFinish - m_durationBackward;
break;
}
//debugPlan<<m_name<<""<<cs->lateFinish;
} else if (type() == Node::Type_Summarytask) {
warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
} else { // ???
m_durationBackward = Duration::zeroDuration;
}
m_visitedBackward = true;
cs->insertBackwardNode( this );
cs->lateStart = cs->lateFinish - m_durationBackward;
foreach ( const Appointment *a, cs->appointments( Schedule::CalculateBackward ) ) {
cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat) ) );
}
// clean up temporary usage
cs->startTime = DateTime();
cs->endTime = DateTime();
cs->duration = Duration::zeroDuration;
cs->logInfo( i18n( "Late start calculated: %1", locale.toString(cs->lateStart, QLocale::ShortFormat) ) );
cs->incProgress();
#ifndef PLAN_NLOGDEBUG
cs->logDebug( QStringLiteral( "Finished calculate backward: %1 ms" ).arg( timer.elapsed() ) );
#endif
return cs->lateStart;
}
DateTime Task::schedulePredeccessors(const QList<Relation*> &list, int use) {
DateTime time;
foreach (Relation *r, list) {
if (r->parent()->type() == Type_Summarytask) {
//debugPlan<<"Skip summarytask:"<<r->parent()->name();
continue; // skip summarytasks
}
// schedule the predecessors
DateTime earliest = r->parent()->earlyStart();
DateTime t = r->parent()->scheduleForward(earliest, use);
switch (r->type()) {
case Relation::StartStart:
// I can't start before my predesseccor
t = r->parent()->startTime() + r->lag();
break;
case Relation::FinishFinish:
// I can't end before my predecessor, so
// I can't start before it's endtime - my duration
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug( QStringLiteral("FinishFinish: get duration to calculate earliest start") );
#endif
t -= duration(t + r->lag(), use, true);
break;
default:
t += r->lag();
break;
}
if (!time.isValid() || t > time)
time = t;
}
//debugPlan<<time.toString()<<""<<m_name<<" schedulePredeccessors()";
return time;
}
DateTime Task::scheduleForward(const DateTime &earliest, int use) {
if ( m_scheduleForwardRun ) {
return m_currentSchedule->endTime;
}
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
//cs->logDebug( QString( "Schedule forward (early start: %1)" ).arg( cs->earlyStart.toString() ) );
cs->setCalculationMode( Schedule::Scheduling );
DateTime startTime = earliest > cs->earlyStart ? earliest : cs->earlyStart;
// First, calculate all my own predecessors
DateTime time = schedulePredeccessors(dependParentNodes(), use);
if ( time > startTime ) {
startTime = time;
//debugPlan<<m_name<<" new startime="<<cs->startTime;
}
// Then my parents
time = schedulePredeccessors(m_parentProxyRelations, use);
if ( time > startTime ) {
startTime = time;
}
if ( ! m_visitedForward ) {
cs->startTime = startTime;
}
m_scheduleForwardRun = true;
return scheduleFromStartTime( use );
}
DateTime Task::scheduleFromStartTime(int use) {
//debugPlan<<m_name;
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
cs->setCalculationMode( Schedule::Scheduling );
bool pert = cs->usePert();
if (m_visitedForward) {
return cs->endTime;
}
cs->notScheduled = false;
if ( !cs->startTime.isValid() ) {
//cs->logDebug( QString( "Schedule from start time: no start time use early start: %1" ).arg( cs->earlyStart.toString() ) );
cs->startTime = cs->earlyStart;
}
QTime timer;
timer.start();
cs->logInfo( i18n( "Start schedule forward: %1 ", constraintToString( true ) ) );
QLocale locale;
cs->logInfo( i18n( "Schedule from start %1", locale.toString(cs->startTime, QLocale::ShortFormat) ) );
//debugPlan<<m_name<<" startTime="<<cs->startTime;
if(type() == Node::Type_Task) {
if ( cs->recalculate() && completion().isFinished() ) {
copySchedule();
m_visitedForward = true;
return cs->endTime;
}
cs->duration = m_estimate->value(use, pert);
switch (m_constraint) {
case Node::ASAP:
// cs->startTime calculated above
//debugPlan<<m_name<<"ASAP:"<<cs->startTime<<"earliest:"<<cs->earlyStart;
if ( false/*useCalculateForwardAppointments*/
&& m_estimate->type() == Estimate::Type_Effort
&& ! cs->allowOverbooking()
&& cs->hasAppointments( Schedule::CalculateForward )
) {
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString() );
#endif
cs->copyAppointments( Schedule::CalculateForward, Schedule::Scheduling );
if ( cs->recalculate() && completion().isStarted() ) {
// copy start times + appointments from parent schedule
copyAppointments();
}
cs->startTime = cs->appointmentStartTime();
cs->endTime = cs->appointmentEndTime();
Q_ASSERT( cs->startTime.isValid() );
Q_ASSERT( cs->endTime.isValid() );
cs->duration = cs->endTime - cs->startTime;
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
cs->logInfo( i18n( "Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat) ) );
return cs->endTime;
}
cs->startTime = workTimeAfter( cs->startTime, cs );
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString() );
#endif
cs->duration = duration(cs->startTime, use, false);
cs->endTime = cs->startTime + cs->duration;
makeAppointments();
if ( cs->recalculate() && completion().isStarted() ) {
// copy start times + appointments from parent schedule
copyAppointments();
cs->duration = cs->endTime - cs->startTime;
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
break;
case Node::ALAP:
// cs->startTime calculated above
//debugPlan<<m_name<<"ALAP:"<<cs->startTime<<cs->endTime<<" latest="<<cs->lateFinish;
cs->endTime = workTimeBefore( cs->lateFinish, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
//debugPlan<<m_name<<" endTime="<<cs->endTime<<" latest="<<cs->lateFinish;
makeAppointments();
if ( cs->plannedEffort() == 0 && cs->lateFinish < cs->earlyFinish ) {
// the backward pass failed to calculate sane values, try to handle it
//TODO add an error indication
cs->logWarning( i18n( "%1: Scheduling failed using late finish, trying early finish instead.", constraintToString() ) );
cs->endTime = workTimeBefore( cs->earlyFinish, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
makeAppointments();
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
if ( cs->recalculate() && completion().isStarted() ) {
cs->earlyStart = cs->startTime = completion().startTime();
}
break;
case Node::StartNotEarlier:
// cs->startTime calculated above
//debugPlan<<"StartNotEarlier:"<<m_constraintStartTime<<cs->startTime<<cs->lateStart;
cs->startTime = workTimeAfter( qMax( cs->startTime, m_constraintStartTime ), cs );
cs->duration = duration(cs->startTime, use, false);
cs->endTime = cs->startTime + cs->duration;
makeAppointments();
if ( cs->recalculate() && completion().isStarted() ) {
// copy start times + appointments from parent schedule
copyAppointments();
cs->duration = cs->endTime - cs->startTime;
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
if (cs->startTime < m_constraintStartTime) {
cs->constraintError = true;
cs->negativeFloat = cs->startTime - m_constraintStartTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
break;
case Node::FinishNotLater:
// cs->startTime calculated above
//debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->startTime;
cs->startTime = workTimeAfter( cs->startTime, cs );
cs->duration = duration(cs->startTime, use, false);
cs->endTime = cs->startTime + cs->duration;
makeAppointments();
if ( cs->recalculate() && completion().isStarted() ) {
// copy start times + appointments from parent schedule
copyAppointments();
cs->duration = cs->endTime - cs->startTime;
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
if (cs->endTime > m_constraintEndTime) {
//warnPlan<<"cs->endTime > m_constraintEndTime";
cs->constraintError = true;
cs->negativeFloat = cs->endTime - m_constraintEndTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
break;
case Node::MustStartOn:
// Always try to put it on time
cs->startTime = workTimeAfter( m_constraintStartTime, cs );
//debugPlan<<"MustStartOn="<<m_constraintStartTime<<"<"<<cs->startTime;
cs->duration = duration(cs->startTime, use, false);
cs->endTime = cs->startTime + cs->duration;
#ifndef PLAN_NLOGDEBUG
cs->logDebug( QStringLiteral( "%1: Schedule from %2 to %3" ).arg( constraintToString() ).arg( cs->startTime.toString() ).arg( cs->endTime.toString() ) );
#endif
makeAppointments();
if ( cs->recalculate() && completion().isStarted() ) {
// copy start times + appointments from parent schedule
copyAppointments();
cs->duration = cs->endTime - cs->startTime;
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
if (m_constraintStartTime < cs->startTime ) {
cs->constraintError = true;
cs->negativeFloat = cs->startTime - m_constraintStartTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
break;
case Node::MustFinishOn:
// Just try to schedule on time
cs->endTime = workTimeBefore( m_constraintEndTime, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
//debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<","<<cs->lateFinish<<":"<<cs->startTime<<cs->endTime;
makeAppointments();
if ( cs->recalculate() && completion().isStarted() ) {
// copy start times + appointments from parent schedule
copyAppointments();
cs->duration = cs->endTime - cs->startTime;
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
if ( cs->endTime != m_constraintEndTime ) {
cs->constraintError = true;
cs->negativeFloat = cs->endTime - m_constraintEndTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
break;
case Node::FixedInterval: {
// cs->startTime calculated above
//debugPlan<<"FixedInterval="<<m_constraintStartTime<<""<<cs->startTime;
cs->duration = m_constraintEndTime - m_constraintStartTime;
if ( m_constraintStartTime >= cs->earlyStart ) {
cs->startTime = m_constraintStartTime;
cs->endTime = m_constraintEndTime;
} else {
cs->startTime = cs->earlyStart;
cs->endTime = cs->startTime + cs->duration;
cs->constraintError = true;
}
if ( m_constraintStartTime < cs->startTime ) {
cs->constraintError = true;
cs->negativeFloat = cs->startTime - m_constraintStartTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
} else {
cs->positiveFloat = Duration::zeroDuration;
}
cs->workStartTime = m_constraintStartTime;
cs->workEndTime = m_constraintEndTime;
//debugPlan<<"FixedInterval="<<cs->startTime<<","<<cs->endTime;
makeAppointments();
break;
}
default:
break;
}
if ( m_estimate->type() == Estimate::Type_Effort ) {
// HACK scheduling may accept deviation less than 5 mins to improve performance
cs->effortNotMet = ( m_estimate->value( use, cs->usePert() ) - cs->plannedEffort() ) > ( 5 * 60000 );
if ( cs->effortNotMet ) {
cs->logError( i18n( "Effort not met. Estimate: %1, planned: %2", estimate()->value( use, cs->usePert() ).toHours(), cs->plannedEffort().toHours() ) );
}
}
} else if (type() == Node::Type_Milestone) {
if ( cs->recalculate() && completion().isFinished() ) {
cs->startTime = completion().startTime();
cs->endTime = completion().finishTime();
m_visitedForward = true;
return cs->endTime;
}
switch (m_constraint) {
case Node::ASAP: {
cs->endTime = cs->startTime;
// TODO check, do we need to check succeccors earliestStart?
cs->positiveFloat = cs->lateFinish - cs->endTime;
break;
}
case Node::ALAP: {
cs->startTime = qMax( cs->lateFinish, cs->earlyFinish );
cs->endTime = cs->startTime;
cs->positiveFloat = Duration::zeroDuration;
break;
}
case Node::MustStartOn:
case Node::MustFinishOn:
case Node::FixedInterval: {
//debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->startTime;
DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime;
#ifndef PLAN_NLOGDEBUG
cs->logDebug( QStringLiteral( "%1: constraint time=%2, start time=%3" ).arg( constraintToString() ).arg( contime.toString() ).arg( cs->startTime.toString() ) );
#endif
if ( cs->startTime < contime ) {
if ( contime <= cs->lateFinish || contime <= cs->earlyFinish ) {
cs->startTime = contime;
}
}
cs->negativeFloat = cs->startTime > contime ? cs->startTime - contime : contime - cs->startTime;
if ( cs->negativeFloat != 0 ) {
cs->constraintError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
cs->endTime = cs->startTime;
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
break;
}
case Node::StartNotEarlier:
if ( cs->startTime < m_constraintStartTime ) {
if ( m_constraintStartTime <= cs->lateFinish || m_constraintStartTime <= cs->earlyFinish ) {
cs->startTime = m_constraintStartTime;
}
}
if ( cs->startTime < m_constraintStartTime ) {
cs->constraintError = true;
cs->negativeFloat = m_constraintStartTime - cs->startTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
cs->endTime = cs->startTime;
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
break;
case Node::FinishNotLater:
//debugPlan<<m_constraintEndTime<<cs->startTime;
if (cs->startTime > m_constraintEndTime) {
cs->constraintError = true;
cs->negativeFloat = cs->startTime - m_constraintEndTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
cs->endTime = cs->startTime;
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
break;
default:
break;
}
cs->duration = Duration::zeroDuration;
//debugPlan<<m_name<<":"<<cs->startTime<<","<<cs->endTime;
} else if (type() == Node::Type_Summarytask) {
//shouldn't come here
cs->endTime = cs->startTime;
cs->duration = cs->endTime - cs->startTime;
warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
}
//debugPlan<<cs->startTime<<" :"<<cs->endTime<<""<<m_name<<" scheduleForward()";
if ( cs->startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime() ) {
cs->logError( i18n( "Failed to schedule within project target time" ) );
}
foreach ( const Appointment *a, cs->appointments() ) {
cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat) ) );
}
if ( cs->startTime < cs->earlyStart ) {
cs->logWarning( i18n( "Starting earlier than early start" ) );
}
if ( cs->endTime > cs->lateFinish ) {
cs->logWarning( i18n( "Finishing later than late finish" ) );
}
cs->logInfo( i18n( "Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat) ) );
m_visitedForward = true;
cs->incProgress();
m_requests.resetDynamicAllocations();
cs->logInfo( i18n( "Finished schedule forward: %1 ms", timer.elapsed() ) );
return cs->endTime;
}
DateTime Task::scheduleSuccessors(const QList<Relation*> &list, int use) {
DateTime time;
foreach (Relation *r, list) {
if (r->child()->type() == Type_Summarytask) {
//debugPlan<<"Skip summarytask:"<<r->child()->name();
continue;
}
// get the successors starttime
DateTime latest = r->child()->lateFinish();
DateTime t = r->child()->scheduleBackward(latest, use);
switch (r->type()) {
case Relation::StartStart:
// I can't start before my successor, so
// I can't finish later than it's starttime + my duration
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug( QStringLiteral("StartStart: get duration to calculate late finish") );
#endif
t += duration(t - r->lag(), use, false);
break;
case Relation::FinishFinish:
t = r->child()->endTime() - r->lag();
break;
default:
t -= r->lag();
break;
}
if (!time.isValid() || t < time)
time = t;
}
return time;
}
DateTime Task::scheduleBackward(const DateTime &latest, int use) {
if ( m_scheduleBackwardRun ) {
return m_currentSchedule->startTime;
}
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
cs->setCalculationMode( Schedule::Scheduling );
DateTime endTime = latest < cs->lateFinish ? latest : cs->lateFinish;
// First, calculate all my own successors
DateTime time = scheduleSuccessors(dependChildNodes(), use);
if (time.isValid() && time < endTime) {
endTime = time;
}
// Then my parents
time = scheduleSuccessors(m_childProxyRelations, use);
if (time.isValid() && time < endTime) {
endTime = time;
}
if ( ! m_visitedBackward ) {
cs->endTime = endTime;
}
m_scheduleBackwardRun = true;
return scheduleFromEndTime( use );
}
DateTime Task::scheduleFromEndTime(int use) {
//debugPlan<<m_name;
if (m_currentSchedule == 0) {
return DateTime();
}
Schedule *cs = m_currentSchedule;
cs->setCalculationMode( Schedule::Scheduling );
bool pert = cs->usePert();
if (m_visitedBackward) {
return cs->startTime;
}
cs->notScheduled = false;
if ( !cs->endTime.isValid() ) {
cs->endTime = cs->lateFinish;
}
#ifndef PLAN_NLOGDEBUG
QTime timer;
timer.start();
cs->logDebug( QStringLiteral( "Start schedule backward: %1 " ).arg( constraintToString( true ) ) );
#endif
QLocale locale;
cs->logInfo( i18n( "Schedule from end time: %1", cs->endTime.toString() ) );
if (type() == Node::Type_Task) {
cs->duration = m_estimate->value(use, pert);
switch (m_constraint) {
case Node::ASAP: {
// cs->endTime calculated above
//debugPlan<<m_name<<": end="<<cs->endTime<<" early="<<cs->earlyStart;
//TODO: try to keep within projects constraint times
cs->endTime = workTimeBefore( cs->endTime, cs );
cs->startTime = workTimeAfter( cs->earlyStart, cs );
DateTime e;
if ( cs->startTime < cs->endTime ) {
cs->duration = duration( cs->startTime, use, false );
e = cs->startTime + cs->duration;
} else {
#ifndef PLAN_NLOGDEBUG
cs->logDebug( QStringLiteral( "%1: Latest allowed end time earlier than early start").arg( constraintToString() ) );
#endif
cs->duration = duration( cs->endTime, use, true );
e = cs->endTime;
cs->startTime = e - cs->duration;
}
if ( e > cs->lateFinish ) {
cs->schedulingError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to schedule within late finish.", constraintToString() ) );
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ASAP: late finish=" + cs->lateFinish.toString() + " end time=" + e.toString() );
#endif
} else if ( e > cs->endTime ) {
cs->schedulingError = true;
cs->logWarning( i18nc( "1=type of constraint", "%1: Failed to schedule within successors start time", constraintToString() ) );
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ASAP: succ. start=" + cs->endTime.toString() + " end time=" + e.toString() );
#endif
}
if ( cs->lateFinish > e ) {
DateTime w = workTimeBefore( cs->lateFinish );
if ( w > e ) {
cs->positiveFloat = w - e;
}
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ASAP: positiveFloat=" + cs->positiveFloat.toString() );
#endif
}
cs->endTime = e;
makeAppointments();
break;
}
case Node::ALAP:
{
// cs->endTime calculated above
//debugPlan<<m_name<<": end="<<cs->endTime<<" late="<<cs->lateFinish;
cs->endTime = workTimeBefore( cs->endTime, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
if ( cs->startTime < cs->earlyStart ) {
cs->schedulingError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to schedule after early start.", constraintToString() ) );
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ALAP: earlyStart=" + cs->earlyStart.toString() + " cs->startTime=" + cs->startTime.toString() );
#endif
} else if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
#ifndef PLAN_NLOGDEBUG
cs->logDebug( "ALAP: positiveFloat=" + cs->positiveFloat.toString() );
#endif
}
//debugPlan<<m_name<<": lateStart="<<cs->startTime;
makeAppointments();
break;
}
case Node::StartNotEarlier:
// cs->endTime calculated above
//debugPlan<<"StartNotEarlier:"<<m_constraintStartTime<<cs->endTime;
cs->endTime = workTimeBefore( cs->endTime, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
if ( cs->startTime < m_constraintStartTime ) {
//warnPlan<<"m_constraintStartTime > cs->lateStart";
cs->constraintError = true;
cs->negativeFloat = m_constraintStartTime - cs->startTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
makeAppointments();
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
}
break;
case Node::FinishNotLater:
// cs->endTime calculated above
//debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->endTime;
if (cs->endTime > m_constraintEndTime) {
cs->endTime = qMax( qMin( m_constraintEndTime, cs->lateFinish ), cs->earlyFinish );
}
cs->endTime = workTimeBefore( cs->endTime, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
if ( cs->endTime > m_constraintEndTime ) {
cs->negativeFloat = cs->endTime - m_constraintEndTime;
cs->constraintError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
makeAppointments();
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
}
break;
case Node::MustStartOn:
// Just try to schedule on time
//debugPlan<<"MustStartOn="<<m_constraintStartTime.toString()<<""<<cs->startTime.toString();
cs->startTime = workTimeAfter( m_constraintStartTime, cs );
cs->duration = duration(cs->startTime, use, false);
if ( cs->endTime >= cs->startTime + cs->duration ) {
cs->endTime = cs->startTime + cs->duration;
} else {
cs->endTime = workTimeBefore( cs->endTime );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
}
if (m_constraintStartTime != cs->startTime) {
cs->constraintError = true;
cs->negativeFloat = m_constraintStartTime - cs->startTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
makeAppointments();
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
}
break;
case Node::MustFinishOn:
// Just try to schedule on time
//debugPlan<<m_name<<"MustFinishOn:"<<m_constraintEndTime<<cs->endTime<<cs->earlyFinish;
cs->endTime = workTimeBefore( m_constraintEndTime, cs );
cs->duration = duration(cs->endTime, use, true);
cs->startTime = cs->endTime - cs->duration;
if (m_constraintEndTime != cs->endTime ) {
cs->negativeFloat = m_constraintEndTime - cs->endTime;
cs->constraintError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
//warnPlan<<"m_constraintEndTime > cs->endTime";
}
makeAppointments();
if ( cs->lateFinish > cs->endTime ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
}
break;
case Node::FixedInterval: {
// cs->endTime calculated above
//debugPlan<<m_name<<"FixedInterval="<<m_constraintEndTime<<""<<cs->endTime;
cs->duration = m_constraintEndTime - m_constraintStartTime;
if ( cs->endTime > m_constraintEndTime ) {
cs->endTime = qMax( m_constraintEndTime, cs->earlyFinish );
}
cs->startTime = cs->endTime - cs->duration;
if (m_constraintEndTime != cs->endTime) {
cs->negativeFloat = m_constraintEndTime - cs->endTime;
cs->constraintError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
cs->workStartTime = workTimeAfter( cs->startTime );
cs->workEndTime = workTimeBefore( cs->endTime );
makeAppointments();
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = workTimeBefore( cs->lateFinish ) - cs->endTime;
}
break;
}
default:
break;
}
m_requests.reserve(cs->startTime, cs->duration);
if ( m_estimate->type() == Estimate::Type_Effort ) {
// HACK scheduling may accept deviation less than 5 mins to improve performance
cs->effortNotMet = ( m_estimate->value( use, cs->usePert() ) - cs->plannedEffort() ) > ( 5 * 60000 );
if ( cs->effortNotMet ) {
cs->logError( i18n( "Effort not met. Estimate: %1, planned: %2", estimate()->value( use, cs->usePert() ).toHours(), cs->plannedEffort().toHours() ) );
}
}
} else if (type() == Node::Type_Milestone) {
switch (m_constraint) {
case Node::ASAP:
if ( cs->endTime < cs->earlyStart ) {
cs->schedulingError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to schedule after early start.", constraintToString() ) );
cs->endTime = cs->earlyStart;
} else {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
//cs->endTime = cs->earlyStart; FIXME need to follow predeccessors. Defer scheduling?
cs->startTime = cs->endTime;
break;
case Node::ALAP:
cs->startTime = cs->endTime;
cs->positiveFloat = cs->lateFinish - cs->endTime;
break;
case Node::MustStartOn:
case Node::MustFinishOn:
case Node::FixedInterval: {
DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime;
if ( contime < cs->earlyStart ) {
if ( cs->earlyStart < cs->endTime ) {
cs->endTime = cs->earlyStart;
}
} else if ( contime < cs->endTime ) {
cs->endTime = contime;
}
cs->negativeFloat = cs->endTime > contime ? cs->endTime - contime : contime - cs->endTime;
if ( cs->negativeFloat != 0 ) {
cs->constraintError = true;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
cs->startTime = cs->endTime;
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
break;
}
case Node::StartNotEarlier:
cs->startTime = cs->endTime;
if ( m_constraintStartTime > cs->startTime) {
cs->constraintError = true;
cs->negativeFloat = m_constraintStartTime - cs->startTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
break;
case Node::FinishNotLater:
if ( m_constraintEndTime < cs->earlyStart ) {
if ( cs->earlyStart < cs->endTime ) {
cs->endTime = cs->earlyStart;
}
} else if ( m_constraintEndTime < cs->endTime ) {
cs->endTime = m_constraintEndTime;
}
if ( m_constraintEndTime > cs->endTime ) {
cs->constraintError = true;
cs->negativeFloat = cs->endTime - m_constraintEndTime;
cs->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString( true ), cs->negativeFloat.toString( Duration::Format_i18nHour ) ) );
}
cs->startTime = cs->endTime;
if ( cs->negativeFloat == Duration::zeroDuration ) {
cs->positiveFloat = cs->lateFinish - cs->endTime;
}
break;
default:
break;
}
cs->duration = Duration::zeroDuration;
} else if (type() == Node::Type_Summarytask) {
//shouldn't come here
cs->startTime = cs->endTime;
cs->duration = cs->endTime - cs->startTime;
warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
}
if ( cs->startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime() ) {
cs->logError( i18n( "Failed to schedule within project target time" ) );
}
foreach ( const Appointment *a, cs->appointments() ) {
cs->logInfo( i18n( "Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat ), locale.toString(a->endTime(), QLocale::ShortFormat) ) );
}
if ( cs->startTime < cs->earlyStart ) {
cs->logWarning( i18n( "Starting earlier than early start" ) );
}
if ( cs->endTime > cs->lateFinish ) {
cs->logWarning( i18n( "Finishing later than late finish" ) );
}
cs->logInfo( i18n( "Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat) ) );
m_visitedBackward = true;
cs->incProgress();
m_requests.resetDynamicAllocations();
#ifndef PLAN_NLOGDEBUG
cs->logDebug( QStringLiteral( "Finished schedule backward: %1 ms" ).arg( timer.elapsed() ) );
#endif
return cs->startTime;
}
void Task::adjustSummarytask() {
if (m_currentSchedule == 0)
return;
if (type() == Type_Summarytask) {
DateTime start = m_currentSchedule->lateFinish;
DateTime end = m_currentSchedule->earlyStart;
foreach (Node *n, m_nodes) {
n->adjustSummarytask();
if (n->startTime() < start)
start = n->startTime();
if (n->endTime() > end)
end = n->endTime();
}
m_currentSchedule->startTime = start;
m_currentSchedule->endTime = end;
m_currentSchedule->duration = end - start;
m_currentSchedule->notScheduled = false;
//debugPlan<<cs->name<<":"<<m_currentSchedule->startTime.toString()<<" :"<<m_currentSchedule->endTime.toString();
}
}
Duration Task::duration(const DateTime &time, int use, bool backward) {
//debugPlan;
// TODO: handle risc
if (m_currentSchedule == 0) {
errorPlan<<"No current schedule";
return Duration::zeroDuration;
}
if (!time.isValid()) {
#ifndef PLAN_NLOGDEBUG
m_currentSchedule->logDebug( QStringLiteral("Calculate duration: Start time is not valid") );
#endif
return Duration::zeroDuration;
}
//debugPlan<<m_name<<": Use="<<use;
Duration eff;
if ( m_currentSchedule->recalculate() && completion().isStarted() ) {
eff = completion().remainingEffort();
//debugPlan<<m_name<<": recalculate, effort="<<eff.toDouble(Duration::Unit_h);
if ( eff == 0 || completion().isFinished() ) {
return eff;
}
} else {
eff = m_estimate->value(use, m_currentSchedule->usePert());
}
return calcDuration(time, eff, backward);
}
Duration Task::calcDuration(const DateTime &time, KPlato::Duration effort, bool backward) {
//debugPlan<<"--------> calcDuration"<<(backward?"(B)":"(F)")<<m_name<<" time="<<time<<" effort="<<effort.toString(Duration::Format_Day);
// Already checked: m_currentSchedule and time.
Duration dur = effort; // use effort as default duration
if (m_estimate->type() == Estimate::Type_Effort) {
if (m_requests.isEmpty()) {
m_currentSchedule->resourceError = true;
m_currentSchedule->logError( i18n( "No resource has been allocated") );
return effort;
}
dur = m_requests.duration(time, effort, m_currentSchedule, backward);
if (dur == Duration::zeroDuration) {
warnPlan<<"zero duration: Resource not available";
m_currentSchedule->resourceNotAvailable = true;
dur = effort; //???
}
return dur;
}
if (m_estimate->type() == Estimate::Type_Duration) {
return length( time, dur, backward );
}
errorPlan<<"Unsupported estimate type: "<<m_estimate->type();
return dur;
}
Duration Task::length(const DateTime &time, KPlato::Duration duration, bool backward)
{
return length( time, duration, m_currentSchedule, backward );
}
Duration Task::length(const DateTime &time, KPlato::Duration duration, Schedule *sch, bool backward) {
//debugPlan<<"--->"<<(backward?"(B)":"(F)")<<m_name<<""<<time.toString()<<": duration:"<<duration.toString(Duration::Format_Day)<<" ("<<duration.milliseconds()<<")";
Duration l;
if ( duration == Duration::zeroDuration ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( QStringLiteral("Calculate length: estimate == 0") );
#else
Q_UNUSED(sch)
#endif
return l;
}
Calendar *cal = m_estimate->calendar();
if ( cal == 0) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Calculate length: No calendar, return estimate " + duration.toString() );
#endif
return duration;
}
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Calculate length from: " + time.toString() );
#endif
DateTime logtime = time;
bool sts=true;
bool match = false;
DateTime start = time;
int inc = backward ? -1 : 1;
DateTime end = start;
Duration l1;
int nDays = backward ? projectNode()->constraintStartTime().daysTo( time ) : time.daysTo( projectNode()->constraintEndTime() );
for (int i=0; !match && i <= nDays; ++i) {
// days
end = end.addDays(inc);
l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
//debugPlan<<"["<<i<<"of"<<nDays<<"]"<<(backward?"(B)":"(F):")<<" start="<<start<<" l+l1="<<(l+l1).toString()<<" match"<<duration.toString();
if (l + l1 < duration) {
l += l1;
start = end;
} else if (l + l1 == duration) {
l += l1;
match = true;
} else {
end = start;
break;
}
}
if ( ! match ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Days: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')' );
#endif
logtime = start;
for (int i=0; !match && i < 24; ++i) {
// hours
end = end.addSecs(inc*60*60);
l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
if (l + l1 < duration) {
l += l1;
start = end;
} else if (l + l1 == duration) {
l += l1;
match = true;
} else {
end = start;
break;
}
//debugPlan<<"duration(h)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
}
//debugPlan<<"duration"<<(backward?"backward":"forward:")<<start.toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<") match="<<match<<" sts="<<sts;
}
if ( ! match ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Hours: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')' );
#endif
logtime = start;
for (int i=0; !match && i < 60; ++i) {
//minutes
end = end.addSecs(inc*60);
l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
if (l + l1 < duration) {
l += l1;
start = end;
} else if (l + l1 == duration) {
l += l1;
match = true;
} else if (l + l1 > duration) {
end = start;
break;
}
//debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
}
//debugPlan<<"duration"<<(backward?"backward":"forward:")<<" start="<<start.toString()<<" l="<<l.toString()<<" match="<<match<<" sts="<<sts;
}
if ( ! match ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Minutes: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')' );
#endif
logtime = start;
for (int i=0; !match && i < 60 && sts; ++i) {
//seconds
end = end.addSecs(inc);
l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
if (l + l1 < duration) {
l += l1;
start = end;
} else if (l + l1 == duration) {
l += l1;
match = true;
} else if (l + l1 > duration) {
end = start;
break;
}
//debugPlan<<"duration(s)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
}
}
if ( ! match ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Seconds: duration " + logtime.toString() + " - " + end.toString() + " l " + l.toString() + " (" + (duration - l).toString() + ')' );
#endif
for (int i=0; !match && i < 1000; ++i) {
//milliseconds
end.setTime(end.time().addMSecs(inc));
l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
if (l + l1 < duration) {
l += l1;
start = end;
} else if (l + l1 == duration) {
l += l1;
match = true;
} else {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Got more than asked for, should not happen! Want: " + duration.toString(Duration::Format_Hour) + " got: " + l.toString(Duration::Format_Hour) );
#endif
break;
}
//debugPlan<<"duration(ms)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
}
}
if (!match) {
m_currentSchedule->logError( i18n( "Could not match work duration. Want: %1 got: %2", l.toString( Duration::Format_i18nHour ), duration.toString( Duration::Format_i18nHour ) ) );
}
DateTime t = end;
if (l != Duration::zeroDuration) {
if ( backward ) {
if ( end < projectNode()->constraintEndTime() ) {
t = cal->firstAvailableAfter(end, projectNode()->constraintEndTime());
}
} else {
if ( end > projectNode()->constraintStartTime() ) {
t = cal->firstAvailableBefore(end, projectNode()->constraintStartTime());
}
}
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Moved end to work: " + end.toString() + " -> " + t.toString() );
#endif
}
end = t.isValid() ? t : time;
//debugPlan<<"<---"<<(backward?"(B)":"(F)")<<m_name<<":"<<end.toString()<<"-"<<time.toString()<<"="<<(end - time).toString()<<" duration:"<<duration.toString(Duration::Format_Day);
l = end>time ? end-time : time-end;
if ( match ) {
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( "Calculated length: " + time.toString() + " - " + end.toString() + " = " + l.toString() );
#endif
}
return l;
}
void Task::clearProxyRelations() {
m_parentProxyRelations.clear();
m_childProxyRelations.clear();
}
void Task::addParentProxyRelations( const QList<Relation*> &list )
{
//debugPlan<<m_name;
if (type() == Type_Summarytask) {
// propagate to my children
//debugPlan<<m_name<<" is summary task";
foreach (Node *n, m_nodes) {
n->addParentProxyRelations(list);
n->addParentProxyRelations(dependParentNodes());
}
} else {
// add 'this' as child relation to the relations parent
//debugPlan<<m_name<<" is not summary task";
foreach (Relation *r, list) {
r->parent()->addChildProxyRelation(this, r);
// add a parent relation to myself
addParentProxyRelation(r->parent(), r);
}
}
}
void Task::addChildProxyRelations( const QList<Relation*> &list) {
//debugPlan<<m_name;
if (type() == Type_Summarytask) {
// propagate to my children
//debugPlan<<m_name<<" is summary task";
foreach (Node *n, m_nodes) {
n->addChildProxyRelations(list);
n->addChildProxyRelations(dependChildNodes());
}
} else {
// add 'this' as parent relation to the relations child
//debugPlan<<m_name<<" is not summary task";
foreach (Relation *r, list) {
r->child()->addParentProxyRelation(this, r);
// add a child relation to myself
addChildProxyRelation(r->child(), r);
}
}
}
void Task::addParentProxyRelation(Node *node, const Relation *rel) {
if (node->type() != Type_Summarytask) {
if (type() == Type_Summarytask) {
//debugPlan<<"Add parent proxy from my children"<<m_name<<" to"<<node->name();
foreach (Node *n, m_nodes) {
n->addParentProxyRelation(node, rel);
}
} else {
//debugPlan<<"Add parent proxy from"<<node->name()<<" to (me)"<<m_name;
m_parentProxyRelations.append(new ProxyRelation(node, this, rel->type(), rel->lag()));
}
}
}
void Task::addChildProxyRelation(Node *node, const Relation *rel) {
if (node->type() != Type_Summarytask) {
if (type() == Type_Summarytask) {
//debugPlan<<"Add child proxy from my children"<<m_name<<" to"<<node->name();
foreach (Node *n, m_nodes) {
n->addChildProxyRelation(node, rel);
}
} else {
//debugPlan<<"Add child proxy from (me)"<<m_name<<" to"<<node->name();
m_childProxyRelations.append(new ProxyRelation(this, node, rel->type(), rel->lag()));
}
}
}
bool Task::isEndNode() const
{
return m_dependChildNodes.isEmpty() && m_childProxyRelations.isEmpty();
}
bool Task::isStartNode() const
{
return m_dependParentNodes.isEmpty() && m_parentProxyRelations.isEmpty();
}
DateTime Task::workTimeAfter(const DateTime &dt, Schedule *sch) const {
DateTime t;
if ( m_estimate->type() == Estimate::Type_Duration ) {
if ( m_estimate->calendar() ) {
t = m_estimate->calendar()->firstAvailableAfter( dt, projectNode()->constraintEndTime() );
}
} else {
t = m_requests.workTimeAfter(dt, sch);
#ifndef PLAN_NLOGDEBUG
if ( sch ) sch->logDebug( QStringLiteral( "workTimeAfter: %1 = %2" ).arg( dt.toString() ).arg( t.toString() ) );
#endif
}
return t.isValid() ? t : dt;
}
DateTime Task::workTimeBefore(const DateTime &dt, Schedule *sch) const {
DateTime t;
if ( m_estimate->type() == Estimate::Type_Duration ) {
if ( m_estimate->calendar() ) {
t = m_estimate->calendar()->firstAvailableBefore( dt, projectNode()->constraintStartTime() );
}
} else {
t = m_requests.workTimeBefore(dt, sch);
}
return t.isValid() ? t : dt;
}
Duration Task::positiveFloat( long id ) const
{
Schedule *s = schedule( id );
return s == 0 ? Duration::zeroDuration : s->positiveFloat;
}
void Task::setPositiveFloat( KPlato::Duration fl, long id ) const
{
Schedule *s = schedule( id );
if ( s )
s->positiveFloat = fl;
}
Duration Task::negativeFloat( long id ) const
{
Schedule *s = schedule( id );
return s == 0 ? Duration::zeroDuration : s->negativeFloat;
}
void Task::setNegativeFloat( KPlato::Duration fl, long id ) const
{
Schedule *s = schedule( id );
if ( s )
s->negativeFloat = fl;
}
Duration Task::freeFloat( long id ) const
{
Schedule *s = schedule( id );
return s == 0 ? Duration::zeroDuration : s->freeFloat;
}
void Task::setFreeFloat( KPlato::Duration fl, long id ) const
{
Schedule *s = schedule( id );
if ( s )
s->freeFloat = fl;
}
Duration Task::startFloat( long id ) const
{
Schedule *s = schedule( id );
return s == 0 || s->earlyStart > s->lateStart ? Duration::zeroDuration : ( s->earlyStart - s->lateStart );
}
Duration Task::finishFloat( long id ) const
{
Schedule *s = schedule( id );
return s == 0 || s->lateFinish < s->earlyFinish ? Duration::zeroDuration : ( s->lateFinish - s->earlyFinish );
}
bool Task::isCritical( long id ) const
{
Schedule *s = schedule( id );
return s == 0 ? false : s->isCritical();
}
bool Task::calcCriticalPath(bool fromEnd)
{
if (m_currentSchedule == 0)
return false;
//debugPlan<<m_name<<" fromEnd="<<fromEnd<<" cp="<<m_currentSchedule->inCriticalPath;
if (m_currentSchedule->inCriticalPath) {
return true; // path already calculated
}
if (!isCritical()) {
return false;
}
if (fromEnd) {
if (isEndNode() && startFloat() == 0 && finishFloat() == 0) {
m_currentSchedule->inCriticalPath = true;
//debugPlan<<m_name<<" end node";
return true;
}
foreach (Relation *r, m_childProxyRelations) {
if (r->child()->calcCriticalPath(fromEnd)) {
m_currentSchedule->inCriticalPath = true;
}
}
foreach (Relation *r, m_dependChildNodes) {
if (r->child()->calcCriticalPath(fromEnd)) {
m_currentSchedule->inCriticalPath = true;
}
}
} else {
if (isStartNode() && startFloat() == 0 && finishFloat() == 0) {
m_currentSchedule->inCriticalPath = true;
//debugPlan<<m_name<<" start node";
return true;
}
foreach (Relation *r, m_parentProxyRelations) {
if (r->parent()->calcCriticalPath(fromEnd)) {
m_currentSchedule->inCriticalPath = true;
}
}
foreach (Relation *r, m_dependParentNodes) {
if (r->parent()->calcCriticalPath(fromEnd)) {
m_currentSchedule->inCriticalPath = true;
}
}
}
//debugPlan<<m_name<<" return cp="<<m_currentSchedule->inCriticalPath;
return m_currentSchedule->inCriticalPath;
}
void Task::calcFreeFloat()
{
//debugPlan<<m_name;
if ( type() == Node::Type_Summarytask ) {
Node::calcFreeFloat();
return;
}
Schedule *cs = m_currentSchedule;
if ( cs == 0 ) {
return;
}
DateTime t;
foreach ( Relation *r, m_dependChildNodes ) {
DateTime c = r->child()->startTime();
if ( !t.isValid() || c < t ) {
t = c;
}
}
foreach ( Relation *r, m_childProxyRelations ) {
DateTime c = r->child()->startTime();
if ( !t.isValid() || c < t ) {
t = c;
}
}
if ( t.isValid() && t > cs->endTime ) {
cs->freeFloat = t - cs->endTime;
//debugPlan<<m_name<<": "<<cs->freeFloat.toString();
}
}
void Task::setCurrentSchedule(long id)
{
setCurrentSchedulePtr(findSchedule(id));
Node::setCurrentSchedule(id);
}
bool Task::effortMetError( long id ) const
{
Schedule *s = schedule( id );
if (s == 0 || s->notScheduled || m_estimate->type() != Estimate::Type_Effort) {
return false;
}
return s->effortNotMet;
}
uint Task::state( long id ) const
{
int st = Node::State_None;
if ( ! isScheduled( id ) ) {
st |= State_NotScheduled;
}
if ( completion().isFinished() ) {
st |= Node::State_Finished;
if ( completion().finishTime() > endTime( id ) ) {
st |= State_FinishedLate;
}
if ( completion().finishTime() < endTime( id ) ) {
st |= State_FinishedEarly;
}
} else if ( completion().isStarted() ) {
st |= Node::State_Started;
if ( completion().startTime() > startTime( id ) ) {
st |= State_StartedLate;
}
if ( completion().startTime() < startTime( id ) ) {
st |= State_StartedEarly;
}
if ( completion().percentFinished() > 0 ) {
st |= State_Running;
}
if ( endTime( id ) < QDateTime::currentDateTime() ) {
st |= State_Late;
}
} else if ( isScheduled( id ) ) {
if ( startTime( id ) < QDateTime::currentDateTime() ) {
st |= State_Late;
}
}
st |= State_ReadyToStart;
//TODO: check proxy relations
foreach ( const Relation *r, m_dependParentNodes ) {
if ( ! static_cast<Task*>( r->parent() )->completion().isFinished() ) {
st &= ~Node::State_ReadyToStart;
st |= Node::State_NotReadyToStart;
break;
}
}
return st;
}
void Task::addWorkPackage( WorkPackage *wp )
{
emit workPackageToBeAdded( this, m_packageLog.count() );
m_packageLog.append( wp );
emit workPackageAdded( this );
}
void Task::removeWorkPackage( WorkPackage *wp )
{
int index = m_packageLog.indexOf( wp );
if ( index < 0 ) {
return;
}
emit workPackageToBeRemoved( this, index );
m_packageLog.removeAt( index );
emit workPackageRemoved( this );
}
WorkPackage *Task::workPackageAt( int index ) const
{
Q_ASSERT ( index >= 0 && index < m_packageLog.count() );
return m_packageLog.at( index );
}
QString Task::wpOwnerName() const
{
if ( m_packageLog.isEmpty() ) {
return m_workPackage.ownerName();
}
return m_packageLog.last()->ownerName();
}
WorkPackage::WPTransmitionStatus Task::wpTransmitionStatus() const
{
if ( m_packageLog.isEmpty() ) {
return m_workPackage.transmitionStatus();
}
return m_packageLog.last()->transmitionStatus();
}
DateTime Task::wpTransmitionTime() const
{
if ( m_packageLog.isEmpty() ) {
return m_workPackage.transmitionTime();
}
return m_packageLog.last()->transmitionTime();
}
//------------------------------------------
Completion::Completion( Node *node )
: m_node( node ),
m_started( false ),
m_finished( false ),
m_entrymode( EnterCompleted )
{}
Completion::Completion( const Completion &c )
{
copy( c );
}
Completion::~Completion()
{
qDeleteAll( m_entries );
qDeleteAll( m_usedEffort );
}
void Completion::copy( const Completion &p )
{
m_node = 0; //NOTE
m_started = p.isStarted(); m_finished = p.isFinished();
m_startTime = p.startTime(); m_finishTime = p.finishTime();
m_entrymode = p.entrymode();
qDeleteAll( m_entries );
m_entries.clear();
Completion::EntryList::ConstIterator entriesIt = p.entries().constBegin();
const Completion::EntryList::ConstIterator entriesEnd = p.entries().constEnd();
for (; entriesIt != entriesEnd; ++entriesIt) {
addEntry( entriesIt.key(), new Entry( *entriesIt.value() ) );
}
qDeleteAll( m_usedEffort );
m_usedEffort.clear();
Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapIt = p.usedEffortMap().constBegin();
const Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapEnd = p.usedEffortMap().constEnd();
for (; usedEffortMapIt != usedEffortMapEnd; ++usedEffortMapIt) {
addUsedEffort( usedEffortMapIt.key(), new UsedEffort( *usedEffortMapIt.value() ) );
}
}
bool Completion::operator==( const Completion &p )
{
return m_started == p.isStarted() && m_finished == p.isFinished() &&
m_startTime == p.startTime() && m_finishTime == p.finishTime() &&
m_entries == p.entries() &&
m_usedEffort == p.usedEffortMap();
}
Completion &Completion::operator=( const Completion &p )
{
copy( p );
return *this;
}
void Completion::changed( int property)
{
if ( m_node ) {
m_node->changed(property);
}
}
void Completion::setStarted( bool on )
{
m_started = on;
changed(Node::CompletionStarted);
}
void Completion::setFinished( bool on )
{
m_finished = on;
changed(Node::CompletionFinished);
}
void Completion::setStartTime( const DateTime &dt )
{
m_startTime = dt;
changed(Node::CompletionStartTime);
}
void Completion::setFinishTime( const DateTime &dt )
{
m_finishTime = dt;
changed(Node::CompletionFinishTime);
}
void Completion::setPercentFinished( QDate date, int value )
{
Entry *e = 0;
if ( m_entries.contains( date ) ) {
e = m_entries[ date ];
} else {
e = new Entry();
m_entries[ date ] = e;
}
e->percentFinished = value;
changed(Node::CompletionPercentage);
}
void Completion::setRemainingEffort( QDate date, KPlato::Duration value )
{
Entry *e = 0;
if ( m_entries.contains( date ) ) {
e = m_entries[ date ];
} else {
e = new Entry();
m_entries[ date ] = e;
}
e->remainingEffort = value;
changed(Node::CompletionRemainingEffort);
}
void Completion::setActualEffort( QDate date, KPlato::Duration value )
{
Entry *e = 0;
if ( m_entries.contains( date ) ) {
e = m_entries[ date ];
} else {
e = new Entry();
m_entries[ date ] = e;
}
e->totalPerformed = value;
changed(Node::CompletionActualEffort);
}
void Completion::addEntry( QDate date, Entry *entry )
{
m_entries.insert( date, entry );
//debugPlan<<m_entries.count()<<" added:"<<date;
changed(Node::CompletionEntry);
}
QDate Completion::entryDate() const
{
return m_entries.isEmpty() ? QDate() : m_entries.lastKey();
}
int Completion::percentFinished() const
{
return m_entries.isEmpty() ? 0 : m_entries.last()->percentFinished;
}
int Completion::percentFinished( QDate date ) const
{
int x = 0;
EntryList::const_iterator it;
for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) {
x = it.value()->percentFinished;
}
return x;
}
Duration Completion::remainingEffort() const
{
return m_entries.isEmpty() ? Duration::zeroDuration : m_entries.last()->remainingEffort;
}
Duration Completion::remainingEffort( QDate date ) const
{
Duration x;
EntryList::const_iterator it;
for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) {
x = it.value()->remainingEffort;
}
return x;
}
Duration Completion::actualEffort() const
{
Duration eff;
if ( m_entrymode == EnterEffortPerResource ) {
foreach( const UsedEffort *ue, m_usedEffort ) {
const QMap<QDate, UsedEffort::ActualEffort> map = ue->actualEffortMap();
QMap<QDate, UsedEffort::ActualEffort>::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
eff += it.value().effort();
}
}
} else if ( ! m_entries.isEmpty() ) {
eff = m_entries.last()->totalPerformed;
}
return eff;
}
Duration Completion::actualEffort( const Resource *resource, QDate date ) const
{
UsedEffort *ue = usedEffort( resource );
if ( ue == 0 ) {
return Duration::zeroDuration;
}
UsedEffort::ActualEffort ae = ue->effort( date );
return ae.effort();
}
Duration Completion::actualEffort( QDate date ) const
{
Duration eff;
if ( m_entrymode == EnterEffortPerResource ) {
foreach( const UsedEffort *ue, m_usedEffort ) {
if ( ue && ue->actualEffortMap().contains( date ) ) {
eff += ue->actualEffortMap().value( date ).effort();
}
}
} else {
// Hmmm: How to really know a specific date?
if ( m_entries.contains( date ) ) {
eff = m_entries[ date ]->totalPerformed;
}
}
return eff;
}
Duration Completion::actualEffortTo( QDate date ) const
{
//debugPlan<<date;
Duration eff;
if ( m_entrymode == EnterEffortPerResource ) {
foreach( const UsedEffort *ue, m_usedEffort ) {
eff += ue->effortTo( date );
}
} else {
QListIterator<QDate> it( m_entries.uniqueKeys() );
it.toBack();
while ( it.hasPrevious() ) {
QDate d = it.previous();
if ( d <= date ) {
eff = m_entries[ d ]->totalPerformed;
break;
}
}
}
return eff;
}
double Completion::averageCostPrHour( QDate date, long id ) const
{
Schedule *s = m_node->schedule( id );
if ( s == 0 ) {
return 0.0;
}
double cost = 0.0;
double eff = 0.0;
QList<double> cl;
foreach ( const Appointment *a, s->appointments() ) {
cl << a->resource()->resource()->normalRate();
double e = a->plannedEffort( date ).toDouble( Duration::Unit_h );
if ( e > 0.0 ) {
eff += e;
cost += e * cl.last();
}
}
if ( eff > 0.0 ) {
cost /= eff;
} else {
foreach ( double c, cl ) {
cost += c;
}
cost /= cl.count();
}
return cost;
}
EffortCostMap Completion::effortCostPrDay(QDate start, QDate end, long id ) const
{
//debugPlan<<m_node->name()<<start<<end;
EffortCostMap ec;
if ( ! isStarted() ) {
return ec;
}
switch ( m_entrymode ) {
case FollowPlan:
break;
case EnterCompleted:
case EnterEffortPerTask: {
QDate st = start.isValid() ? start : m_startTime.date();
QDate et = end.isValid() ? end : m_finishTime.date();
Duration last;
foreach ( const QDate &d, m_entries.uniqueKeys() ) {
if ( d < st ) {
continue;
}
if ( et.isValid() && d > et ) {
break;
}
Duration e = m_entries[ d ]->totalPerformed;
if ( e != Duration::zeroDuration && e != last ) {
Duration eff = e - last;
ec.insert( d, eff, eff.toDouble( Duration::Unit_h ) * averageCostPrHour( d, id ) );
last = e;
}
}
break;
}
case EnterEffortPerResource: {
std::pair<QDate, QDate> dates = actualStartEndDates();
if ( ! dates.first.isValid() ) {
// no data, so just break
break;
}
QDate st = start.isValid() ? start : dates.first;
QDate et = end.isValid() ? end : dates.second;
for ( QDate d = st; d <= et; d = d.addDays( 1 ) ) {
ec.add( d, actualEffort( d ), actualCost( d ) );
}
break;
}
}
return ec;
}
EffortCostMap Completion::effortCostPrDay(const Resource *resource, QDate start, QDate end, long id ) const
{
Q_UNUSED(id);
//debugPlan<<m_node->name()<<start<<end;
EffortCostMap ec;
if ( ! isStarted() ) {
return ec;
}
switch ( m_entrymode ) {
case FollowPlan:
break;
case EnterCompleted:
case EnterEffortPerTask: {
//TODO but what todo?
break;
}
case EnterEffortPerResource: {
std::pair<QDate, QDate> dates = actualStartEndDates();
if ( ! dates.first.isValid() ) {
// no data, so just break
break;
}
QDate st = start.isValid() ? start : dates.first;
QDate et = end.isValid() ? end : dates.second;
for ( QDate d = st; d <= et; d = d.addDays( 1 ) ) {
ec.add( d, actualEffort( resource, d ), actualCost( resource, d ) );
}
break;
}
}
return ec;
}
void Completion::addUsedEffort( const Resource *resource, Completion::UsedEffort *value )
{
UsedEffort *v = value == 0 ? new UsedEffort() : value;
if ( m_usedEffort.contains( resource ) ) {
m_usedEffort[ resource ]->mergeEffort( *v );
delete v;
} else {
m_usedEffort.insert( resource, v );
}
changed(Node::CompletionUsedEffort);
}
QString Completion::note() const
{
return m_entries.isEmpty() ? QString() : m_entries.last()->note;
}
void Completion::setNote( const QString &str )
{
if ( ! m_entries.isEmpty() ) {
m_entries.last()->note = str;
changed();
}
}
std::pair<QDate, QDate> Completion::actualStartEndDates() const
{
std::pair<QDate, QDate> p;
ResourceUsedEffortMap::const_iterator it;
for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
if (!it.value()->actualEffortMap().isEmpty()) {
QDate d = it.value()->firstDate();
if (!p.first.isValid() || d < p.first) {
p.first = d;
}
d = it.value()->lastDate();
if (!p.second.isValid() || d > p.second ) {
p.second = d;
}
}
}
return p;
}
double Completion::actualCost( QDate date ) const
{
//debugPlan<<date;
double c = 0.0;
ResourceUsedEffortMap::const_iterator it;
for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
double nc = it.key()->normalRate();
double oc = it.key()->overtimeRate();
if (it.value()->actualEffortMap().contains(date)) {
UsedEffort::ActualEffort a = it.value()->effort(date);
c += a.normalEffort().toDouble( Duration::Unit_h ) * nc;
c += a.overtimeEffort().toDouble( Duration::Unit_h ) * oc;
}
}
return c;
}
double Completion::actualCost( const Resource *resource ) const
{
UsedEffort *ue = usedEffort( resource );
if ( ue == 0 ) {
return 0.0;
}
double c = 0.0;
double nc = resource->normalRate();
double oc = resource->overtimeRate();
foreach ( const UsedEffort::ActualEffort &a, ue->actualEffortMap() ) {
c += a.normalEffort().toDouble( Duration::Unit_h ) * nc;
c += a.overtimeEffort().toDouble( Duration::Unit_h ) * oc;
}
return c;
}
double Completion::actualCost() const
{
double c = 0.0;
ResourceUsedEffortMap::const_iterator it;
for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
c += actualCost(it.key());
}
return c;
}
double Completion::actualCost( const Resource *resource, QDate date ) const
{
UsedEffort *ue = usedEffort( resource );
if ( ue == 0 ) {
return 0.0;
}
UsedEffort::ActualEffort a = ue->actualEffortMap().value( date );
double c = a.normalEffort().toDouble( Duration::Unit_h ) * resource->normalRate();
c += a.overtimeEffort().toDouble( Duration::Unit_h ) * resource->overtimeRate();
return c;
}
EffortCostMap Completion::actualEffortCost( long int id, KPlato::EffortCostCalculationType type ) const
{
//debugPlan;
EffortCostMap map;
if ( ! isStarted() ) {
return map;
}
QList< QMap<QDate, UsedEffort::ActualEffort> > lst;
QList< double > rate;
QDate start, end;
ResourceUsedEffortMap::const_iterator it;
for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
const Resource *r = it.key();
//debugPlan<<m_node->name()<<r->name();
lst << usedEffort(r)->actualEffortMap();
if ( lst.last().isEmpty() ) {
lst.takeLast();
continue;
}
if ( r->type() == Resource::Type_Material ) {
if ( type == ECCT_All ) {
rate.append( r->normalRate() );
} else if ( type == ECCT_EffortWork ) {
rate.append( 0.0 );
} else {
lst.takeLast();
continue;
}
} else {
rate.append( r->normalRate() );
}
if ( ! start.isValid() || start > lst.last().firstKey() ) {
start = lst.last().firstKey();
}
if ( ! end.isValid() || end < lst.last().lastKey() ) {
end = lst.last().lastKey();
}
}
if ( ! lst.isEmpty() && start.isValid() && end.isValid() ) {
for ( QDate d = start; d <= end; d = d.addDays( 1 ) ) {
EffortCost c;
for ( int i = 0; i < lst.count(); ++i ) {
UsedEffort::ActualEffort a = lst.at( i ).value( d );
double nc = rate.value( i );
Duration eff = a.normalEffort();
double cost = eff.toDouble( Duration::Unit_h ) * nc;
c.add( eff, cost );
}
if ( c.effort() != Duration::zeroDuration || c.cost() != 0.0 ) {
map.add( d, c );
}
}
} else if ( ! m_entries.isEmpty() ) {
QDate st = start.isValid() ? start : m_startTime.date();
QDate et = end.isValid() ? end : m_finishTime.date();
Duration last;
foreach ( const QDate &d, m_entries.uniqueKeys() ) {
if ( d < st ) {
continue;
}
Duration e = m_entries[ d ]->totalPerformed;
if ( e != Duration::zeroDuration && e != last ) {
//debugPlan<<m_node->name()<<d<<(e - last).toDouble(Duration::Unit_h);
double eff = ( e - last ).toDouble( Duration::Unit_h );
map.insert( d, e - last, eff * averageCostPrHour( d, id ) ); // try to guess cost
last = e;
}
if ( et.isValid() && d > et ) {
break;
}
}
}
return map;
}
EffortCost Completion::actualCostTo( long int id, QDate date ) const
{
//debugPlan<<date;
EffortCostMap ecm = actualEffortCost( id );
return EffortCost( ecm.effortTo( date ), ecm.costTo( date ) );
}
QStringList Completion::entrymodeList() const
{
return QStringList()
<< QStringLiteral("FollowPlan")
<< QStringLiteral("EnterCompleted")
<< QStringLiteral("EnterEffortPerTask")
<< QStringLiteral("EnterEffortPerResource");
}
void Completion::setEntrymode( const QString &mode )
{
int m = entrymodeList().indexOf( mode );
if ( m == -1 ) {
m = EnterCompleted;
}
m_entrymode = static_cast<Entrymode>( m );
}
QString Completion::entryModeToString() const
{
return entrymodeList().value( m_entrymode );
}
bool Completion::loadXML( KoXmlElement &element, XMLLoaderObject &status )
{
//debugPlan;
QString s;
m_started = (bool)element.attribute(QStringLiteral("started"), QStringLiteral("0")).toInt();
m_finished = (bool)element.attribute(QStringLiteral("finished"), QStringLiteral("0")).toInt();
s = element.attribute(QStringLiteral("startTime"));
if (!s.isEmpty()) {
m_startTime = DateTime::fromString(s, status.projectTimeZone());
}
s = element.attribute(QStringLiteral("finishTime"));
if (!s.isEmpty()) {
m_finishTime = DateTime::fromString(s, status.projectTimeZone());
}
setEntrymode( element.attribute( QStringLiteral("entrymode") ) );
if (status.version() < QLatin1String("0.6")) {
if ( m_started ) {
Entry *entry = new Entry( element.attribute(QStringLiteral("percent-finished"), QStringLiteral("0")).toInt(), Duration::fromString(element.attribute(QStringLiteral("remaining-effort"))), Duration::fromString(element.attribute(QStringLiteral("performed-effort"))) );
entry->note = element.attribute(QStringLiteral("note"));
QDate date = m_startTime.date();
if ( m_finished ) {
date = m_finishTime.date();
}
// almost the best we can do ;)
addEntry( date, entry );
}
} else {
KoXmlElement e;
forEachElement(e, element) {
if (e.tagName() == QLatin1String("completion-entry")) {
QDate date;
s = e.attribute(QStringLiteral("date"));
if ( !s.isEmpty() ) {
date = QDate::fromString( s, Qt::ISODate );
}
if ( !date.isValid() ) {
warnPlan<<"Invalid date: "<<date<<s;
continue;
}
Entry *entry = new Entry( e.attribute(QStringLiteral("percent-finished"), QStringLiteral("0")).toInt(), Duration::fromString(e.attribute(QStringLiteral("remaining-effort"))), Duration::fromString(e.attribute(QStringLiteral("performed-effort"))) );
addEntry( date, entry );
} else if (e.tagName() == QLatin1String("used-effort")) {
KoXmlElement el;
forEachElement(el, e) {
if (el.tagName() == QLatin1String("resource")) {
QString id = el.attribute( QStringLiteral("id") );
Resource *r = status.project().resource( id );
if ( r == 0 ) {
warnPlan<<"Cannot find resource, id="<<id;
continue;
}
UsedEffort *ue = new UsedEffort();
addUsedEffort( r, ue );
ue->loadXML( el, status );
}
}
}
}
}
return true;
}
void Completion::saveXML(QDomElement &element ) const
{
QDomElement el = element.ownerDocument().createElement(QStringLiteral("progress"));
element.appendChild(el);
el.setAttribute(QStringLiteral("started"), QString::number(m_started));
el.setAttribute(QStringLiteral("finished"), QString::number(m_finished));
el.setAttribute(QStringLiteral("startTime"), m_startTime.toString( Qt::ISODate ));
el.setAttribute(QStringLiteral("finishTime"), m_finishTime.toString( Qt::ISODate ));
el.setAttribute(QStringLiteral("entrymode"), entryModeToString());
foreach( const QDate &date, m_entries.uniqueKeys() ) {
QDomElement elm = el.ownerDocument().createElement(QStringLiteral("completion-entry"));
el.appendChild(elm);
Entry *e = m_entries[ date ];
elm.setAttribute( QStringLiteral("date"), date.toString( Qt::ISODate ) );
elm.setAttribute( QStringLiteral("percent-finished"), e->percentFinished );
elm.setAttribute( QStringLiteral("remaining-effort"), e->remainingEffort.toString() );
elm.setAttribute( QStringLiteral("performed-effort"), e->totalPerformed.toString() );
elm.setAttribute( QStringLiteral("note"), e->note );
}
if ( ! m_usedEffort.isEmpty() ) {
QDomElement elm = el.ownerDocument().createElement(QStringLiteral("used-effort"));
el.appendChild(elm);
ResourceUsedEffortMap::ConstIterator i = m_usedEffort.constBegin();
for ( ; i != m_usedEffort.constEnd(); ++i ) {
if ( i.value() == 0 ) {
continue;
}
QDomElement e = elm.ownerDocument().createElement(QStringLiteral("resource"));
elm.appendChild(e);
e.setAttribute( QStringLiteral("id"), i.key()->id() );
i.value()->saveXML( e );
}
}
}
//--------------
Completion::UsedEffort::UsedEffort()
{
}
Completion::UsedEffort::UsedEffort( const UsedEffort &e )
{
mergeEffort( e );
}
Completion::UsedEffort::~UsedEffort()
{
}
void Completion::UsedEffort::mergeEffort( const Completion::UsedEffort &value )
{
const QMap<QDate, ActualEffort> map = value.actualEffortMap();
QMap<QDate, ActualEffort>::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
setEffort(it.key(), it.value());
}
}
void Completion::UsedEffort::setEffort( QDate date, const ActualEffort &value )
{
m_actual.insert( date, value );
}
Duration Completion::UsedEffort::effortTo( QDate date ) const
{
Duration eff;
QMap<QDate, ActualEffort>::const_iterator it;
for (it = m_actual.constBegin(); it != m_actual.constEnd() && it.key() <= date; ++it) {
eff += it.value().effort();
}
return eff;
}
Duration Completion::UsedEffort::effort() const
{
Duration eff;
foreach ( const ActualEffort &e, m_actual ) {
eff += e.effort();
}
return eff;
}
bool Completion::UsedEffort::operator==( const Completion::UsedEffort &e ) const
{
return m_actual == e.actualEffortMap();
}
bool Completion::UsedEffort::loadXML(KoXmlElement &element, XMLLoaderObject & )
{
//debugPlan;
KoXmlElement e;
forEachElement(e, element) {
if (e.tagName() == QLatin1String("actual-effort")) {
QDate date = QDate::fromString( e.attribute(QStringLiteral("date")), Qt::ISODate );
if ( date.isValid() ) {
ActualEffort a;
a.setNormalEffort( Duration::fromString( e.attribute( QStringLiteral("normal-effort") ) ) );
a.setOvertimeEffort( Duration::fromString( e.attribute( QStringLiteral("overtime-effort") ) ) );
setEffort( date, a );
}
}
}
return true;
}
void Completion::UsedEffort::saveXML(QDomElement &element ) const
{
if ( m_actual.isEmpty() ) {
return;
}
DateUsedEffortMap::ConstIterator i = m_actual.constBegin();
for ( ; i != m_actual.constEnd(); ++i ) {
QDomElement el = element.ownerDocument().createElement(QStringLiteral("actual-effort"));
element.appendChild( el );
el.setAttribute( QStringLiteral("overtime-effort"), i.value().overtimeEffort().toString() );
el.setAttribute( QStringLiteral("normal-effort"), i.value().normalEffort().toString() );
el.setAttribute( QStringLiteral("date"), i.key().toString( Qt::ISODate ) );
}
}
//----------------------------------
WorkPackage::WorkPackage( Task *task )
: m_task( task ),
m_manager( 0 ),
m_transmitionStatus( TS_None )
{
m_completion.setNode( task );
}
WorkPackage::WorkPackage( const WorkPackage &wp )
: m_task( 0 ),
m_manager( 0 ),
m_completion( wp.m_completion ),
m_ownerName( wp.m_ownerName ),
m_ownerId( wp.m_ownerId ),
m_transmitionStatus( wp.m_transmitionStatus ),
m_transmitionTime( wp.m_transmitionTime )
{
}
WorkPackage::~WorkPackage()
{
}
bool WorkPackage::loadXML(KoXmlElement &element, XMLLoaderObject &status )
{
Q_UNUSED(status);
m_ownerName = element.attribute( QStringLiteral("owner") );
m_ownerId = element.attribute( QStringLiteral("owner-id") );
return true;
}
void WorkPackage::saveXML(QDomElement &element) const
{
QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage"));
element.appendChild(el);
el.setAttribute( QStringLiteral("owner"), m_ownerName );
el.setAttribute( QStringLiteral("owner-id"), m_ownerId );
}
bool WorkPackage::loadLoggedXML(KoXmlElement &element, XMLLoaderObject &status )
{
m_ownerName = element.attribute( QStringLiteral("owner") );
m_ownerId = element.attribute( QStringLiteral("owner-id") );
m_transmitionStatus = transmitionStatusFromString( element.attribute( QStringLiteral("status") ) );
m_transmitionTime = DateTime( QDateTime::fromString( element.attribute( QStringLiteral("time") ), Qt::ISODate ) );
return m_completion.loadXML( element, status );
}
void WorkPackage::saveLoggedXML(QDomElement &element) const
{
QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage"));
element.appendChild(el);
el.setAttribute( QStringLiteral("owner"), m_ownerName );
el.setAttribute( QStringLiteral("owner-id"), m_ownerId );
el.setAttribute( QStringLiteral("status"), transmitionStatusToString( m_transmitionStatus ) );
el.setAttribute( QStringLiteral("time"), m_transmitionTime.toString( Qt::ISODate ) );
m_completion.saveXML( el );
}
QList<Resource*> WorkPackage::fetchResources()
{
return fetchResources( id() );
}
QList<Resource*> WorkPackage::fetchResources( long id )
{
//debugPlan<<m_task.name();
QList<Resource*> lst;
if ( id == NOTSCHEDULED ) {
if ( m_task ) {
lst << m_task->requestedResources();
}
} else {
if ( m_task ) lst = m_task->assignedResources( id );
foreach ( const Resource *r, m_completion.resources() ) {
if ( ! lst.contains( const_cast<Resource*>( r ) ) ) {
lst << const_cast<Resource*>( r );
}
}
}
return lst;
}
Completion &WorkPackage::completion()
{
return m_completion;
}
const Completion &WorkPackage::completion() const
{
return m_completion;
}
void WorkPackage::setScheduleManager( ScheduleManager *sm )
{
m_manager = sm;
}
QString WorkPackage::transmitionStatusToString( WorkPackage::WPTransmitionStatus sts, bool trans )
{
QString s = trans ? i18n( "None" ) : QStringLiteral("None");
switch ( sts ) {
case TS_Send:
s = trans ? i18n( "Send" ) : QStringLiteral("Send");
break;
case TS_Receive:
s = trans ? i18n( "Receive" ) : QStringLiteral("Receive");
break;
default:
break;
}
return s;
}
WorkPackage::WPTransmitionStatus WorkPackage::transmitionStatusFromString( const QString &sts )
{
QStringList lst;
lst << QStringLiteral("None") << QStringLiteral("Send") << QStringLiteral("Receive");
int s = lst.indexOf( sts );
return s < 0 ? TS_None : static_cast<WPTransmitionStatus>( s );
}
void WorkPackage::clear()
{
//m_task = 0;
m_manager = 0;
m_ownerName.clear();
m_ownerId.clear();
m_transmitionStatus = TS_None;
m_transmitionTime = DateTime();
m_log.clear();
m_completion = Completion();
m_completion.setNode( m_task );
}
//--------------------------------
WorkPackageSettings::WorkPackageSettings()
: usedEffort( true ),
progress( false ),
documents( true )
{
}
void WorkPackageSettings::saveXML( QDomElement &element ) const
{
QDomElement el = element.ownerDocument().createElement(QStringLiteral("settings"));
element.appendChild( el );
el.setAttribute( QStringLiteral("used-effort"), QString::number(usedEffort) );
el.setAttribute( QStringLiteral("progress"), QString::number(progress) );
el.setAttribute( QStringLiteral("documents"), QString::number(documents) );
}
bool WorkPackageSettings::loadXML( const KoXmlElement &element )
{
usedEffort = (bool)element.attribute( QStringLiteral("used-effort") ).toInt();
progress = (bool)element.attribute( QStringLiteral("progress") ).toInt();
documents = (bool)element.attribute( QStringLiteral("documents") ).toInt();
return true;
}
bool WorkPackageSettings::operator==( KPlato::WorkPackageSettings s ) const
{
return usedEffort == s.usedEffort &&
progress == s.progress &&
documents == s.documents;
}
bool WorkPackageSettings::operator!=( KPlato::WorkPackageSettings s ) const
{
return ! operator==( s );
}
} //KPlato namespace
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<( QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae )
{
dbg << QStringLiteral( "%1" ).arg( ae.normalEffort().toDouble( KPlato::Duration::Unit_h ), 1 );
return dbg;
}
#endif
diff --git a/src/libs/kernel/kptwbsdefinition.cpp b/src/libs/kernel/kptwbsdefinition.cpp
index 3f60fbeb..8f1a713c 100644
--- a/src/libs/kernel/kptwbsdefinition.cpp
+++ b/src/libs/kernel/kptwbsdefinition.cpp
@@ -1,259 +1,260 @@
/* This file is part of the KDE project
Copyright (C) 2005 Dag Andersen <danders@get2net.dk>
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 "kptwbsdefinition.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QStringList>
namespace KPlato
{
WBSDefinition::WBSDefinition() {
m_levelsEnabled = false;
m_defaultDef.code = "Number";
m_defaultDef.separator = '.';
m_codeLists.append(qMakePair(QString("Number"), i18n("Number")));
m_codeLists.append(qMakePair(QString("Roman, upper case"), i18n("Roman, upper case")));
m_codeLists.append(qMakePair(QString("Roman, lower case"), i18n("Roman, lower case")));
m_codeLists.append(qMakePair(QString("Letter, upper case"), i18n("Letter, upper case")));
m_codeLists.append(qMakePair(QString("Letter, lower case"), i18n("Letter, lower case")));
}
WBSDefinition::WBSDefinition( const WBSDefinition &def ) {
(void)this->operator=( def );
}
WBSDefinition::~WBSDefinition() {
}
WBSDefinition &WBSDefinition::operator=( const WBSDefinition &def ) {
m_projectCode = def.m_projectCode;
m_projectSeparator = def.m_projectSeparator;
m_defaultDef.code = def.m_defaultDef.code;
m_defaultDef.separator = def.m_defaultDef.separator;
m_levelsEnabled = def.m_levelsEnabled;
m_levelsDef = def.m_levelsDef;
m_codeLists = def.m_codeLists;
return *this;
}
void WBSDefinition::clear() {
m_defaultDef.clear();
m_levelsDef.clear();
}
QString WBSDefinition::wbs(uint index, int level) const {
if (isLevelsDefEnabled()) {
CodeDef def = levelsDef(level);
if (!def.isEmpty()) {
return code(def, index) + def.separator;
}
}
return code(m_defaultDef, index) + m_defaultDef.separator;
}
QString WBSDefinition::code(uint index, int level) const {
if (isLevelsDefEnabled()) {
CodeDef def = levelsDef(level);
if (!def.isEmpty()) {
return code(def, index);
}
}
return code(m_defaultDef, index);
}
QString WBSDefinition::separator(int level) const {
if (isLevelsDefEnabled()) {
CodeDef def = levelsDef(level);
if (!def.isEmpty()) {
return def.separator;
}
}
return m_defaultDef.separator;
}
void WBSDefinition::setLevelsDef(const QMap<int, CodeDef> &def) {
m_levelsDef.clear();
m_levelsDef = def;
}
WBSDefinition::CodeDef WBSDefinition::levelsDef(int level) const {
return m_levelsDef.contains(level) ? m_levelsDef[level] : CodeDef();
}
void WBSDefinition::setLevelsDef(int level, const CodeDef &def) {
m_levelsDef.insert(level, def);
}
void WBSDefinition::setLevelsDef(int level, const QString& c, const QString& s) {
m_levelsDef.insert(level, CodeDef(c, s));
}
bool WBSDefinition::level0Enabled() const {
return m_levelsEnabled && !levelsDef(0).isEmpty();
}
const QChar Letters[] = { '?','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' };
QString WBSDefinition::code(const CodeDef &def, uint index) const {
if (def.code == "Number") {
return QString("%1").arg(index);
}
if (def.code == "Roman, lower case") {
return QString("%1").arg(toRoman(index));
}
if (def.code == "Roman, upper case") {
return QString("%1").arg(toRoman(index, true));
}
if (def.code == "Letter, lower case") {
if (index > 26) {
index = 0;
}
return QString("%1").arg(Letters[index]);
}
if (def.code == "Letter, upper case") {
if (index > 26) {
index = 0;
}
return QString("%1").arg(Letters[index].toUpper());
}
return QString();
}
// Nicked from koparagcounter.cc
QString WBSDefinition::toRoman( int n, bool upper ) const
{
static const QString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
static const QString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
static const QString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
static const QString RNThousands[] = {"", "m", "mm", "mmm"};
if (n < 0) { // should never happen, but better not crash if it does
warnPlan << "intToRoman called with negative number: n=" << n;
return QString::number(n);
}
QString s = RNThousands[ ( n / 1000 ) ] +
RNHundreds[ ( n / 100 ) % 10 ] +
RNTens[ ( n / 10 ) % 10 ] +
RNUnits[ ( n ) % 10 ];
return upper ? s.toUpper() : s;
}
QStringList WBSDefinition::codeList() const {
QStringList cl;
QList<QPair<QString, QString> >::ConstIterator it;
for (it = m_codeLists.constBegin(); it != m_codeLists.constEnd(); ++it) {
cl.append((*it).second);
}
return cl;
}
int WBSDefinition::defaultCodeIndex() const {
int index = -1;
for(int i = 0; i < m_codeLists.count(); ++i) {
if (m_defaultDef.code == m_codeLists.at(i).first) {
index = i;
break;
}
}
return index;
}
bool WBSDefinition::setDefaultCode(uint index) {
if ((int)index >= m_codeLists.size()) {
return false;
}
m_defaultDef.code = m_codeLists[index].first;
return true;
}
void WBSDefinition::setDefaultSeparator(const QString& s) {
m_defaultDef.separator = s;
}
bool WBSDefinition::loadXML(KoXmlElement &element, XMLLoaderObject & ) {
m_projectCode = element.attribute( "project-code" );
m_projectSeparator = element.attribute( "project-separator" );
m_levelsEnabled = (bool)element.attribute( "levels-enabled", "0" ).toInt();
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() == "default") {
m_defaultDef.code = e.attribute( "code", "Number" );
m_defaultDef.separator = e.attribute( "separator", "." );
} else if (e.tagName() == "levels") {
KoXmlNode n = e.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement el = n.toElement();
CodeDef d;
d.code = el.attribute( "code" );
d.separator = el.attribute( "separator" );
int lvl = el.attribute( "level", "-1" ).toInt();
if ( lvl >= 0 ) {
setLevelsDef( lvl, d );
} else errorPlan<<"Invalid levels definition";
}
}
}
return true;
}
void WBSDefinition::saveXML(QDomElement &element) const {
QDomElement me = element.ownerDocument().createElement("wbs-definition");
element.appendChild(me);
me.setAttribute( "project-code", m_projectCode );
me.setAttribute( "project-separator", m_projectSeparator );
me.setAttribute( "levels-enabled", QString::number(m_levelsEnabled) );
if ( ! m_levelsDef.isEmpty() ) {
QDomElement ld = element.ownerDocument().createElement("levels");
me.appendChild(ld);
QMap<int, CodeDef>::ConstIterator it;
for (it = m_levelsDef.constBegin(); it != m_levelsDef.constEnd(); ++it) {
QDomElement l = element.ownerDocument().createElement("level");
ld.appendChild(l);
l.setAttribute( "level", QString::number(it.key()) );
l.setAttribute( "code", it.value().code );
l.setAttribute( "separator", it.value().separator );
}
}
QDomElement cd = element.ownerDocument().createElement("default");
me.appendChild(cd);
cd.setAttribute("code", m_defaultDef.code);
cd.setAttribute("separator", m_defaultDef.separator);
}
} //namespace KPlato
diff --git a/src/libs/kernel/tests/AccountsCommandTester.cpp b/src/libs/kernel/tests/AccountsCommandTester.cpp
index 26f45d4e..fd5bec17 100644
--- a/src/libs/kernel/tests/AccountsCommandTester.cpp
+++ b/src/libs/kernel/tests/AccountsCommandTester.cpp
@@ -1,322 +1,323 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "AccountsCommandTester.h"
#include "kptdatetime.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptcalendar.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include <QTest>
#include <kundo2stack.h>
namespace QTest
{
template<>
char *toString(const KPlato::DateTime &dt)
{
return toString( dt.toString() );
}
}
namespace KPlato
{
void AccountsCommandTester::printDebug( long id ) const {
Project *p = m_project;
Resource *r = m_resource;
qDebug()<<"Debug info -------------------------------------";
qDebug()<<"project start time:"<<p->startTime().toString();
qDebug()<<"project end time :"<<p->endTime().toString();
qDebug()<<"Resource start:"<<r->startTime( id ).toString();
qDebug()<<"Resource end :"<<r->endTime( id ).toString();
qDebug()<<"Appointments:"<<r->numAppointments( id )<<"(internal)";
foreach ( Appointment *a, r->appointments( id ) ) {
foreach ( const AppointmentInterval &i, a->intervals().map() ) {
qDebug()<<" "<<i.startTime().toString()<<"-"<<i.endTime().toString()<<";"<<i.load();
}
}
qDebug()<<"Appointments:"<<r->numExternalAppointments()<<"(external)";
foreach ( Appointment *a, r->externalAppointmentList() ) {
foreach ( const AppointmentInterval &i, a->intervals().map() ) {
qDebug()<<" "<<i.startTime().toString()<<"-"<<i.endTime().toString()<<";"<<i.load();
}
}
}
void AccountsCommandTester::printSchedulingLog( const ScheduleManager &sm ) const
{
qDebug()<<"Scheduling log ---------------------------------";
foreach ( const QString &s, sm.expected()->logMessages() ) {
qDebug()<<s;
}
}
void AccountsCommandTester::init()
{
m_project = new Project();
m_project->setName( "P1" );
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId( m_project );
DateTime targetstart = DateTime( QDate::currentDate(), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 3 ) );
m_project->setConstraintStartTime( targetstart );
m_project->setConstraintEndTime( targetend);
// standard worktime defines 8 hour day as default
QVERIFY( m_project->standardWorktime() );
QCOMPARE( m_project->standardWorktime()->day(), 8.0 );
m_calendar = new Calendar( "Test" );
m_calendar->setDefault( true );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = m_calendar->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
m_project->addCalendar( m_calendar );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
m_project->addResourceGroup( g );
m_resource = new Resource();
m_resource->setName( "R1" );
m_resource->setCalendar( m_calendar );
m_project->addResource( g, m_resource );
m_task = m_project->createTask();
m_task->setName( "T1" );
m_project->addTask( m_task, m_project );
m_task->estimate()->setUnit( Duration::Unit_h );
m_task->estimate()->setExpectedEstimate( 8.0 );
m_task->estimate()->setType( Estimate::Type_Effort );
}
void AccountsCommandTester::cleanup()
{
delete m_project;
}
void AccountsCommandTester::addAccount()
{
Account *a1 = new Account();
a1->setName("a1");
AddAccountCmd *cmd1 = new AddAccountCmd(*m_project, a1);
cmd1->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
cmd1->undo();
QCOMPARE(m_project->accounts().allAccounts().count(), 0);
delete cmd1;
a1 = new Account();
a1->setName("a1");
cmd1 = new AddAccountCmd(*m_project, a1);
cmd1->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
Account *a2 = new Account();
a2->setName("a2");
AddAccountCmd *cmd2 = new AddAccountCmd(*m_project, a2, a1);
cmd2->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 2);
cmd2->undo();
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
cmd1->undo();
QCOMPARE(m_project->accounts().allAccounts().count(), 0);
delete cmd2;
delete cmd1;
}
void AccountsCommandTester::removeAccount()
{
Account *a1 = new Account();
a1->setName("a1");
m_project->accounts().insert(a1);
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
RemoveAccountCmd *cmd1 = new RemoveAccountCmd(*m_project, a1);
cmd1->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 0);
cmd1->undo();
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
cmd1->redo();
delete cmd1;
QCOMPARE(m_project->accounts().allAccounts().count(), 0);
a1 = new Account();
a1->setName("a1");
m_project->accounts().insert(a1);
Account *a2 = new Account();
a2->setName("a2");
m_project->accounts().insert(a2, a1);
QCOMPARE(m_project->accounts().allAccounts().count(), 2);
cmd1 = new RemoveAccountCmd(*m_project, a1);
cmd1->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 0);
cmd1->undo();
QCOMPARE(m_project->accounts().allAccounts().count(), 2);
RemoveAccountCmd *cmd2 = new RemoveAccountCmd(*m_project, a2);
cmd2->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
cmd2->undo();
QCOMPARE(m_project->accounts().allAccounts().count(), 2);
cmd2->redo();
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
delete cmd2; // should delete a2
delete cmd1; // should not delete a1
}
void AccountsCommandTester::costPlace()
{
KUndo2QStack cmds;
Account *a1 = new Account();
a1->setName("a1");
cmds.push(new AddAccountCmd(*m_project, a1));
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
Account *a2 = new Account();
a2->setName("a2");
cmds.push(new AddAccountCmd(*m_project, a2));
QCOMPARE(m_project->accounts().allAccounts().count(), 2);
Account *a3 = new Account();
a3->setName("a3");
cmds.push(new AddAccountCmd(*m_project, a3));
QCOMPARE(m_project->accounts().allAccounts().count(), 3);
cmds.push(new NodeModifyRunningAccountCmd(*m_task, 0, a1));
QCOMPARE(m_task->runningAccount(), a1);
cmds.push(new NodeModifyStartupAccountCmd(*m_task, 0, a2));
QCOMPARE(m_task->startupAccount(), a2);
cmds.push(new NodeModifyShutdownAccountCmd(*m_task, 0, a3));
QCOMPARE(m_task->shutdownAccount(), a3);
cmds.push(new RemoveAccountCmd(*m_project, a1));
QVERIFY(m_task->runningAccount() == 0);
QCOMPARE(m_task->startupAccount(), a2);
QCOMPARE(m_task->shutdownAccount(), a3);
cmds.undo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a2);
QCOMPARE(m_task->shutdownAccount(), a3);
cmds.push(new RemoveAccountCmd(*m_project, a2));
QVERIFY(m_task->startupAccount() == 0);
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->shutdownAccount(), a3);
cmds.undo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a2);
QCOMPARE(m_task->shutdownAccount(), a3);
cmds.push(new RemoveAccountCmd(*m_project, a3));
QVERIFY(m_task->shutdownAccount() == 0);
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a2);
cmds.undo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a2);
QCOMPARE(m_task->shutdownAccount(), a3);
cmds.push(new ResourceModifyAccountCmd(*m_resource, 0, a1));
QCOMPARE(m_resource->account(), a1);
cmds.push(new RemoveAccountCmd(*m_project, a1));
QVERIFY(m_task->runningAccount() == 0);
QVERIFY(m_resource->account() == 0);
cmds.undo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_resource->account(), a1);
// test when same account is used for running/startup/shutdown
while (cmds.canUndo()) {
cmds.undo();
}
a1 = new Account();
a1->setName("a1");
cmds.push(new AddAccountCmd(*m_project, a1));
QCOMPARE(m_project->accounts().allAccounts().count(), 1);
cmds.push(new NodeModifyRunningAccountCmd(*m_task, 0, a1));
QCOMPARE(m_task->runningAccount(), a1);
cmds.push(new NodeModifyStartupAccountCmd(*m_task, 0, a1));
QCOMPARE(m_task->startupAccount(), a1);
cmds.push(new NodeModifyShutdownAccountCmd(*m_task, 0, a1));
QCOMPARE(m_task->shutdownAccount(), a1);
cmds.undo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a1);
QVERIFY(m_task->shutdownAccount() == 0);
cmds.undo();
QCOMPARE(m_task->runningAccount(), a1);
QVERIFY(m_task->startupAccount() == 0);
QVERIFY(m_task->shutdownAccount() == 0);
cmds.undo();
QVERIFY(m_task->runningAccount() == 0);
QVERIFY(m_task->startupAccount() == 0);
QVERIFY(m_task->shutdownAccount() == 0);
cmds.redo();
QCOMPARE(m_task->runningAccount(), a1);
QVERIFY(m_task->startupAccount() == 0);
QVERIFY(m_task->shutdownAccount() == 0);
cmds.redo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a1);
QVERIFY(m_task->shutdownAccount() == 0);
cmds.redo();
QCOMPARE(m_task->runningAccount(), a1);
QCOMPARE(m_task->startupAccount(), a1);
QCOMPARE(m_task->shutdownAccount(), a1);
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::AccountsCommandTester )
diff --git a/src/libs/kernel/tests/AccountsTester.cpp b/src/libs/kernel/tests/AccountsTester.cpp
index 641abaec..ab5498bc 100644
--- a/src/libs/kernel/tests/AccountsTester.cpp
+++ b/src/libs/kernel/tests/AccountsTester.cpp
@@ -1,622 +1,623 @@
/* This file is part of the KDE project
Copyright (C) 2008 Dag Andersen <danders@get2net.dk>
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 "AccountsTester.h"
#include "kptaccount.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kpttask.h"
#include "debug.cpp"
#include <QTest>
namespace KPlato
{
void AccountsTester::init()
{
project = new Project();
project->setId( project->uniqueCalendarId() );
project->registerNodeId( project );
today = QDate::currentDate();
tomorrow = today.addDays( 1 );
yesterday = today.addDays( -1 );
nextweek = today.addDays( 7 );
t1 = QTime( 9, 0, 0 );
t2 = QTime( 17, 0, 0 );
length = t1.msecsTo( t2 );
t = project->createTask();
t->setName( "T1" );
project->addTask( t, project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
sm = project->createScheduleManager( "Test Plan" );
project->addScheduleManager( sm );
// standard worktime defines 8 hour day as default
Calendar *c = new Calendar();
c->setDefault( true );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = c->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
project->addCalendar( c );
ResourceGroup *g = new ResourceGroup();
project->addResourceGroup( g );
r = new Resource();
r->setAvailableFrom( QDateTime( yesterday, QTime(), Qt::LocalTime ) );
r->setCalendar( c );
r->setNormalRate( 100.0 );
project->addResource( g, r );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
t->estimate()->setType( Estimate::Type_Effort );
//qDebug()<<"Calculate forward, Task: ASAP -----------------------------------";
project->setConstraintStartTime( DateTime( today, QTime() ) );
project->setConstraintEndTime( DateTime( tomorrow, QTime() ) );
sm->createSchedules();
project->calculate( *sm );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
//### QVERIFY( t->schedulingError() == false );
sm->setBaselined( true );
}
void AccountsTester::cleanup()
{
delete project;
project = 0;
t = 0;
r = 0;
sm = 0;
topaccount = 0;
}
void AccountsTester::defaultAccount() {
Account *a = new Account( "Default Account" );
project->accounts().insert( a );
project->accounts().setDefaultAccount( a );
EffortCostMap ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->completion().setEntrymode( Completion::FollowPlan );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
t->completion().setEntrymode( Completion::EnterCompleted );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->completion().setEntrymode( Completion::EnterCompleted );
t->completion().setStarted( true );
t->completion().setStartTime( DateTime( tomorrow, QTime() ) );
t->completion().setPercentFinished( tomorrow, 50 );
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->completion().setEntrymode( Completion::EnterEffortPerTask );
t->completion().setActualEffort( tomorrow, Duration( 0, 4, 0 ) );
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 400.0 );
t->completion().setEntrymode( Completion::EnterEffortPerResource );
Completion::UsedEffort *ue = new Completion::UsedEffort();
Completion::UsedEffort::ActualEffort e( Duration( 0, 6, 0 ) ) ;
ue->setEffort( tomorrow, e );
t->completion().addUsedEffort( r, ue );
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 6.0 );
QCOMPARE( ec.totalCost(), 600.0 );
}
void AccountsTester::costPlaces() {
EffortCostMap ec;
Account *top = new Account( "Top account" );
project->accounts().insert( top );
Account *a = new Account( "Running account" );
project->accounts().insert( a, top );
a->addRunning( *t );
ec = a->plannedCost( sm->scheduleId() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
a = new Account( "Startup account" );
project->accounts().insert( a, top );
a->addStartup( *t );
t->setStartupCost( 200.0 );
ec = a->plannedCost( sm->scheduleId() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 200.0 );
a = new Account( "Shutdown cost" );
project->accounts().insert( a, top );
a->addShutdown( *t );
t->setShutdownCost( 300.0 );
ec = a->plannedCost( sm->scheduleId() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 300.0 );
ec = top->plannedCost( sm->scheduleId() );
// Debug::print( top, sm->scheduleId(), "All planned cost in child accounts--------" );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 1300.0 );
a = new Account( "All cost in one account" );
project->accounts().insert( a );
a->addRunning( *t );
a->addStartup( *t );
a->addShutdown( *t );
ec = a->plannedCost( sm->scheduleId() );
// Debug::print( a, sm->scheduleId(), "All planned cost in one account-----------" );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 1300.0 );
}
void AccountsTester::startupDefault() {
Account *a = new Account( "Default Account" );
project->accounts().insert( a );
project->accounts().setDefaultAccount( a );
EffortCostMap ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->setStartupCost( 25.0 );
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 825.0 );
qDebug()<< t->completion().entryModeToString();
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QVERIFY( ! t->completion().isStarted() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->completion().setEntrymode( Completion::EnterCompleted );
t->completion().setStarted( true );
t->completion().setStartTime( DateTime( tomorrow, QTime() ) );
t->completion().setPercentFinished( tomorrow, 50 );
QVERIFY( t->completion().isStarted() );
qDebug()<<t->completion().startTime()<<":"<<t->startTime().date()<<tomorrow;
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
t->completion().setEntrymode( Completion::EnterEffortPerTask );
t->completion().setActualEffort( tomorrow, Duration( 0, 4, 0 ) );
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
}
void AccountsTester::startupAccount() {
Account *a = new Account( "Account" );
project->accounts().insert( a );
a->addStartup( *t );
// planned wo startup cost
EffortCostMap ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// planned with startup cost, no running cost
t->setStartupCost( 25.0 );
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
// actual, task not started
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QVERIFY( ! t->completion().isStarted() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// start task (tomorrow), no actual effort
t->completion().setEntrymode( Completion::EnterCompleted );
t->completion().setStarted( true );
t->completion().setStartTime( DateTime( tomorrow, QTime() ) );
QVERIFY( t->completion().isStarted() );
ec = project->accounts().actualCost( *a, tomorrow, tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
// add actual effort, but no running cost
t->completion().setEntrymode( Completion::EnterEffortPerTask );
t->completion().setActualEffort( tomorrow, Duration( 0, 4, 0 ) );
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
// add running account
a->addRunning( *t );
// planned wo startup cost
t->completion().setStarted( false );
t->setStartupCost( 0.0 );
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// planned with startup cost
t->setStartupCost( 25.0 );
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 825.0 );
// actual, task not started
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QVERIFY( ! t->completion().isStarted() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// start task (tomorrow), 4h actual effort from before
t->completion().setEntrymode( Completion::EnterCompleted );
t->completion().setStarted( true );
t->completion().setStartTime( DateTime( tomorrow, QTime() ) );
QVERIFY( t->completion().isStarted() );
Debug::print( t, "Started tomorrow, 4h actual effort" );
Debug::print( t->completion(), t->name() );
ec = project->accounts().actualCost( *a, tomorrow, tomorrow );
Debug::print( ec );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
}
void AccountsTester::shutdownAccount() {
Account *a = new Account( "Account" );
project->accounts().insert( a );
a->addShutdown( *t );
t->completion().setStarted( true );
t->completion().setStartTime( t->startTime() );
// planned wo shutdown cost
EffortCostMap ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// planned with shutdown cost, no running account
t->setShutdownCost( 25.0 );
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
// actual, task not finished
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QVERIFY( ! t->completion().isFinished() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// finish task (tomorrow), no actual effort
t->completion().setEntrymode( Completion::EnterCompleted );
t->completion().setFinished( true );
t->completion().setFinishTime( DateTime( tomorrow, QTime() ) );
QVERIFY( t->completion().isFinished() );
ec = project->accounts().actualCost( *a, tomorrow, tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
// add actual effort, no running account
t->completion().setEntrymode( Completion::EnterEffortPerTask );
t->completion().setActualEffort( tomorrow, Duration( 0, 4, 0 ) );
ec = project->accounts().actualCost( *a, t->startTime().date(), tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 25.0 );
// add running account
a->addRunning( *t );
t->completion().setFinished( false );
t->setShutdownCost( 0.0 );
// planned wo finish cost
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
Debug::print( t, "planned wo finish cost, with running", true );
Debug::print( t->completion(), t->name() );
Debug::print( ec );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// planned with startup cost
t->setShutdownCost( 25.0 );
ec = project->accounts().plannedCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 825.0 );
// actual, task not finished
ec = project->accounts().actualCost( *a, t->startTime().date(), t->endTime().date() );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QVERIFY( ! t->completion().isFinished() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
// finish task (tomorrow), 4h actual effort from before
t->completion().setEntrymode( Completion::EnterCompleted );
t->completion().setFinished( true );
t->completion().setFinishTime( DateTime( tomorrow, QTime() ) );
QVERIFY( t->completion().isFinished() );
ec = project->accounts().actualCost( *a, tomorrow, tomorrow );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
ec = project->accounts().actualCost( *a, QDate(), QDate() );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
}
void AccountsTester::subaccounts()
{
Account *a1 = new Account( "Account" );
project->accounts().insert( a1 );
Account *a2 = new Account( "Sub-Account" );
project->accounts().insert( a2, a1 );
project->accounts().setDefaultAccount( a2 );
EffortCostMap ec = project->accounts().plannedCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().plannedCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().actualCost( *a2 );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
ec = project->accounts().actualCost( *a1 );
qDebug()<<t->startTime()<<t->endTime()<<ec.totalEffort().toDouble( Duration::Unit_h );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->completion().setEntrymode( Completion::FollowPlan );
ec = project->accounts().actualCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
ec = project->accounts().actualCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 800.0 );
t->completion().setEntrymode( Completion::EnterCompleted );
ec = project->accounts().actualCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
ec = project->accounts().actualCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
t->completion().setEntrymode( Completion::EnterEffortPerTask );
t->completion().setStarted( true );
t->completion().setStartTime( DateTime( tomorrow, QTime() ) );
t->completion().setPercentFinished( tomorrow, 50 );
t->completion().setActualEffort( tomorrow, Duration( 0, 4, 0 ) );
ec = project->accounts().actualCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 400.0 );
ec = project->accounts().actualCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 400.0 );
t->setStartupCost( 25.0 );
a1->addStartup( *t );
ec = project->accounts().actualCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 400.0 );
ec = project->accounts().actualCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
project->accounts().setDefaultAccount( a1 );
ec = project->accounts().plannedCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
ec = project->accounts().plannedCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 825.0 );
ec = project->accounts().actualCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 0.0 );
ec = project->accounts().actualCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 425.0 );
t->setShutdownCost( 1.0 );
a2->addShutdown( *t );
ec = project->accounts().plannedCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 1.0 );
ec = project->accounts().plannedCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 8.0 );
QCOMPARE( ec.totalCost(), 826.0 );
t->completion().setFinished( true );
t->completion().setFinishTime( DateTime( tomorrow, QTime() ) );
ec = project->accounts().actualCost( *a2 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 0.0 );
QCOMPARE( ec.totalCost(), 1.0 );
ec = project->accounts().actualCost( *a1 );
QCOMPARE( ec.totalEffort().toDouble( Duration::Unit_h ), 4.0 );
QCOMPARE( ec.totalCost(), 426.0 );
}
void AccountsTester::deleteAccount()
{
QVERIFY(project->accounts().allAccounts().isEmpty());
qInfo()<<"Add/delete one account";
Account *a1 = new Account( "Account" );
project->accounts().insert( a1 );
QCOMPARE(project->accounts().allAccounts().count(), 1);
QCOMPARE(project->accounts().allAccounts().first(), a1);
delete a1;
QVERIFY(project->accounts().allAccounts().isEmpty());
qInfo()<<"Add/delete one account with one sub-account";
a1 = new Account( "Account 1" );
project->accounts().insert( a1 );
Account *a2 = new Account( "Account 1.1" );
project->accounts().insert(a2, a1);
QCOMPARE(project->accounts().allAccounts().count(), 2);
qInfo()<<"Delete top account";
delete a1;
QVERIFY(project->accounts().allAccounts().isEmpty());
qInfo()<<"Add/delete one account with one sub-account";
a1 = new Account( "Account 1" );
project->accounts().insert( a1 );
a2 = new Account( "Account 1.1" );
project->accounts().insert(a2, a1);
project->accounts().setDefaultAccount(a2);
QCOMPARE(project->accounts().allAccounts().count(), 2);
qInfo()<<"Delete sub account";
delete a2;
QVERIFY(project->accounts().defaultAccount() == 0);
QCOMPARE(project->accounts().allAccounts().count(), 1);
qInfo()<<"Delete top account";
delete a1;
QVERIFY(project->accounts().allAccounts().isEmpty());
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::AccountsTester )
diff --git a/src/libs/kernel/tests/AppointmentIntervalTester.cpp b/src/libs/kernel/tests/AppointmentIntervalTester.cpp
index 7ca11d9c..1fb5437e 100644
--- a/src/libs/kernel/tests/AppointmentIntervalTester.cpp
+++ b/src/libs/kernel/tests/AppointmentIntervalTester.cpp
@@ -1,867 +1,868 @@
/* This file is part of the KDE project
Copyright (C) 2008 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "AppointmentIntervalTester.h"
#include <kptappointment.h>
#include <kptdatetime.h>
#include <kptduration.h>
#include <QTest>
#include <QMultiMap>
#include "DateTimeTester.h"
#include "debug.cpp"
namespace KPlato
{
void AppointmentIntervalTester::interval()
{
DateTime dt1 = DateTime( QDate( 2011, 01, 02 ), QTime( 7, 0, 0 ) );
DateTime dt2 = DateTime( QDate( 2011, 01, 02 ), QTime( 8, 0, 0 ) );
DateTime dt3 = DateTime( QDate( 2011, 01, 02 ), QTime( 9, 0, 0 ) );
DateTime dt4 = DateTime( QDate( 2011, 01, 02 ), QTime( 10, 0, 0 ) );
AppointmentInterval i1( dt1, dt2, 1 );
AppointmentInterval i2( dt1, dt2, 1 );
QVERIFY( ! ( i1 < i2 ) );
QVERIFY( ! ( i2 < i1 ) );
QVERIFY( i1.intersects( i2 ) );
QVERIFY( i2.intersects( i1 ) );
AppointmentInterval i3( dt2, dt3, 1 );
QVERIFY( i1 < i3 );
QVERIFY( ! ( i3 < i1 ) );
QVERIFY( ! i1.intersects( i3 ) );
QVERIFY( ! i3.intersects( i1 ) );
AppointmentInterval i4( dt2, dt4, 1 );
QVERIFY( i1 < i4 );
QVERIFY( i2 < i4 );
QVERIFY( i3 < i4 );
QVERIFY( ! i1.intersects( i4 ) );
QVERIFY( ! i4.intersects( i1 ) );
QVERIFY( i3.intersects( i4 ) );
QVERIFY( i4.intersects( i3 ) );
AppointmentInterval i5( dt3, dt4, 1 );
QVERIFY( ! i1.intersects( i4 ) );
QVERIFY( ! i4.intersects( i1 ) );
}
void AppointmentIntervalTester::addInterval()
{
AppointmentIntervalList lst;
DateTime dt1 = DateTime( QDate( 2011, 01, 02 ), QTime( 7, 0, 0 ) );
DateTime dt2 = dt1 + Duration( 0, 1, 0 );
double load = 1;
qDebug()<<"Add an interval"<<dt1<<dt2;
qDebug()<<endl<<lst;
lst.add( dt1, dt2, load );
qDebug()<<endl<<lst;
QCOMPARE( dt1, lst.map().values().first().startTime() );
QCOMPARE( dt2, lst.map().values().first().endTime() );
QCOMPARE( load, lst.map().values().first().load() );
qDebug()<<"add load";
qDebug()<<endl<<lst;
lst.add( dt1, dt2, load );
qDebug()<<endl<<lst;
QCOMPARE( dt1, lst.map().values().first().startTime() );
QCOMPARE( dt2, lst.map().values().first().endTime() );
QCOMPARE( load*2, lst.map().values().first().load() );
DateTime dt3 = dt2 + Duration( 0, 4, 0 );
DateTime dt4 = dt3 + Duration( 0, 1, 0 );
qDebug()<<"Add an interval after:"<<dt3<<dt4;
qDebug()<<endl<<lst;
lst.add( dt3, dt4, load );
qDebug()<<endl<<lst;
QCOMPARE( dt1, lst.map().values().first().startTime() );
QCOMPARE( dt2, lst.map().values().first().endTime() );
QCOMPARE( load*2, lst.map().values().first().load() );
QCOMPARE( dt3, lst.map().values().last().startTime() );
QCOMPARE( dt4, lst.map().values().last().endTime() );
QCOMPARE( load, lst.map().values().last().load() );
DateTime dt5 = dt2 + Duration( 0, 2, 0 );
DateTime dt6 = dt5 + Duration( 0, 1, 0 );
qDebug()<<"Add an interval in between:"<<dt5<<dt6;
qDebug()<<endl<<lst;
lst.add( dt5, dt6, load );
qDebug()<<endl<<lst;
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
DateTime dt7 = dt1 - Duration( 0, 1, 0 );
DateTime dt8 = dt7 + Duration( 0, 2, 0 );
qDebug()<<"Add an overlapping interval at start:"<<dt7<<dt8;
qDebug()<<endl<<lst;
lst.add( dt7, dt8, load );
qDebug()<<endl<<lst;
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt7, i.value().startTime() );
QCOMPARE( dt1, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt8, i.value().endTime() );
QCOMPARE( load*3, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
DateTime dt9 = dt7 + Duration( 0, 0, 30 );
DateTime dt10 = dt9 + Duration( 0, 0, 30 );
qDebug()<<"Add an overlapping interval at start > start, end == end"<<dt9<<dt10;
qDebug()<<endl<<lst;
lst.add( dt9, dt10, load );
qDebug()<<endl<<lst;
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt7, i.value().startTime() );
QCOMPARE( dt9, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt9, i.value().startTime() );
QCOMPARE( dt10, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt8, i.value().endTime() );
QCOMPARE( load*3, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
DateTime dt11 = dt3 + Duration( 0, 0, 10 );
DateTime dt12 = dt11 + Duration( 0, 0, 30 );
qDebug()<<"Add an overlapping interval at start > start, end < end:"<<dt11<<dt12;
qDebug()<<endl<<lst;
lst.add( dt11, dt12, load );
qDebug()<<endl<<lst;
{
QCOMPARE( lst.map().count(), 7 );
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt7, i.value().startTime() );
QCOMPARE( dt9, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt9, i.value().startTime() );
QCOMPARE( dt10, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt8, i.value().endTime() );
QCOMPARE( load*3, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt11, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt11, i.value().startTime() );
QCOMPARE( dt12, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt12, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
qDebug()<<"Add an interval overlapping 2 intervals at start == start.1, end == end.2"<<dt1<<dt4;
lst.clear();
qDebug()<<endl<<lst;
lst.add( dt1, dt2, load );
qDebug()<<endl<<lst;
lst.add( dt3, dt4, load );
qDebug()<<endl<<lst;
lst.add( dt1, dt4, load );
qDebug()<<endl<<lst;
{
QCOMPARE( lst.map().count(), 3 );
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
}
lst.clear();
dt5 = dt1 - Duration( 0, 1, 0 );
qDebug()<<"Add an interval overlapping 2 intervals at start < start.1, end == end.2"<<dt5<<dt4;
qDebug()<<endl<<lst;
lst.add( dt1, dt2, load );
qDebug()<<endl<<lst;
lst.add( dt3, dt4, load );
qDebug()<<endl<<lst;
lst.add( dt5, dt4, load );
qDebug()<<endl<<lst;
{
QCOMPARE( lst.map().count(), 4 );
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt1, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
}
// Add an interval overlapping 2 intervals at start < start.1, end > end.2
lst.clear();
dt5 = dt1 - Duration( 0, 1, 0 );
dt6 = dt4 + Duration( 0, 1, 0 );
lst.add( dt1, dt2, load );
lst.add( dt3, dt4, load );
lst.add( dt5, dt6, load );
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt1, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt4, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
// Add an interval overlapping 2 intervals at start < start.1, end < end.2
lst.clear();
dt5 = dt1 - Duration( 0, 1, 0 );
dt6 = dt4 - Duration( 0, 0, 30 );
lst.add( dt1, dt2, load );
lst.add( dt3, dt4, load );
lst.add( dt5, dt6, load );
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt1, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt6, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
// Add an interval overlapping 2 intervals at start > start.1, end < end.2
lst.clear();
dt5 = dt1 + Duration( 0, 0, 30 );
dt6 = dt4 - Duration( 0, 0, 30 );
lst.add( dt1, dt2, load );
lst.add( dt3, dt4, load );
lst.add( dt5, dt6, load );
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt5, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt6, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
// Add an interval overlapping 2 intervals at start > start.1, end == end.2
lst.clear();
dt5 = dt1 + Duration( 0, 0, 30 );
dt6 = dt4;
lst.add( dt1, dt2, load );
lst.add( dt3, dt4, load );
lst.add( dt5, dt6, load );
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt5, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
}
// Add an interval overlapping 2 intervals at start > start.1, end > end.2
lst.clear();
dt5 = dt1 + Duration( 0, 0, 30 );
dt6 = dt4 + Duration( 0, 0, 30 );
lst.add( dt1, dt2, load );
lst.add( dt3, dt4, load );
lst.add( dt5, dt6, load );
{
QMap<QDate, AppointmentInterval>::const_iterator i( lst.map().constBegin() );
QCOMPARE( dt1, i.value().startTime() );
QCOMPARE( dt5, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt5, i.value().startTime() );
QCOMPARE( dt2, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt2, i.value().startTime() );
QCOMPARE( dt3, i.value().endTime() );
QCOMPARE( load, i.value().load() );
++i;
QCOMPARE( dt3, i.value().startTime() );
QCOMPARE( dt4, i.value().endTime() );
QCOMPARE( load*2, i.value().load() );
++i;
QCOMPARE( dt4, i.value().startTime() );
QCOMPARE( dt6, i.value().endTime() );
QCOMPARE( load, i.value().load() );
}
}
void AppointmentIntervalTester::addTangentIntervals()
{
// Add an interval overlapping 2 intervals at start > start.1, end > end.2
AppointmentIntervalList lst;
DateTime dt1( QDate( 2010, 1, 1 ), QTime( 0, 0, 0 ) );
DateTime dt2( QDate( 2010, 1, 1 ), QTime( 12, 0, 0 ) );
double load = 1.;
qDebug()<<endl<<lst;
lst.add( dt2, dt1.addDays( 1 ), load );
qDebug()<<endl<<lst;
QCOMPARE( lst.map().count(), 1 );
lst.add( dt1, dt2, load );
QCOMPARE( lst.map().count(), 2 );
QCOMPARE( lst.map().values().at( 0 ).startTime(), dt1 );
QCOMPARE( lst.map().values().at( 1 ).endTime(), DateTime( dt1.addDays( 1 ) ) );
// add with a 12 hours hole
lst.add( dt2.addDays( 1 ), dt1.addDays( 2 ), load );
QCOMPARE( lst.map().count(), 3 );
// fill the hole
lst.add( dt1.addDays( 1 ), dt2.addDays( 1 ), load );
QCOMPARE( lst.map().count(), 4 );
}
void AppointmentIntervalTester::addAppointment()
{
Appointment app1, app2;
DateTime dt1 = DateTime( QDate( 2011, 01, 02 ), QTime( 7, 0, 0 ) );
DateTime dt2 = dt1 + Duration( 0, 1, 0 );
double load = 1;
app2.addInterval( dt1, dt2, load );
app1 += app2;
QCOMPARE( dt1, app1.intervals().map().values().first().startTime() );
QCOMPARE( dt2, app1.intervals().map().values().first().endTime() );
QCOMPARE( load, app1.intervals().map().values().first().load() );
app1 += app2;
qDebug()<<load<<app1.intervals().map().values().first().load();
QCOMPARE( dt1, app1.intervals().map().values().first().startTime() );
QCOMPARE( dt2, app1.intervals().map().values().first().endTime() );
QCOMPARE( load*2, app1.intervals().map().values().first().load() );
}
void AppointmentIntervalTester::subtractList()
{
QString s;
AppointmentIntervalList lst1;
AppointmentIntervalList lst2;
DateTime dt1 = DateTime( QDate( 2011, 01, 02 ), QTime( 7, 0, 0 ) );
DateTime dt2 = dt1 + Duration( 0, 3, 0 );
double load = 100;
lst1.add( dt1, dt2, load );
QCOMPARE( dt1, lst1.map().values().first().startTime() );
QCOMPARE( dt2, lst1.map().values().first().endTime() );
QCOMPARE( load, lst1.map().values().first().load() );
lst2 += lst1;
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().first().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( lst2.map().count(), 1 );
lst2 -= lst1;
QVERIFY( lst2.isEmpty() );
lst2.add( dt1, dt2, load * 2. );
lst2 -= lst1;
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().first().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( lst2.map().count(), 1 );
lst1.clear();
DateTime dt3 = dt2 + Duration( 0, 6, 0 );
DateTime dt4 = dt3 + Duration( 0, 1, 0 );
lst1.add( dt3, dt4, load );
qDebug()<<"Subtract non-overlapping intervals:";
qDebug()<<endl<<lst2<<endl<<"minus"<<endl<<lst1;
lst2 -= lst1;
qDebug()<<endl<<"result:"<<endl<<lst2;
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().first().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( lst2.map().count(), 1 );
DateTime dt5 = dt1 - Duration( 0, 6, 0 );
DateTime dt6 = dt5 + Duration( 0, 1, 0 );
lst1.add( dt5, dt6, load );
qDebug()<<"-------- lst2 -= lst1";
qDebug()<<endl<<lst2<<endl<<lst1;
lst2 -= lst1;
qDebug()<<endl<<lst2;
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().first().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( lst2.map().count(), 1 );
s = "Subtract tangent intervals";
qDebug()<<s;
lst1.clear();
lst1.add( dt1.addDays( -1 ), dt1, load ); // before
Debug::print( lst2, "List2: " + s );
Debug::print( lst1, "List1: " + s );
lst2 -= lst1;
Debug::print( lst2, "Result: " + s );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().first().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( lst2.map().count(), 1 );
lst1.clear();
lst1.add( dt2, dt2.addDays( 1 ), load ); // after
lst2 -= lst1;
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().first().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QVERIFY( lst2.map().count() == 1 );
// Subtract overlapping intervals
lst1.clear();
dt3 = dt1 + Duration( 0, 1, 0 );
// starts at start, end in the middle
lst1.add( dt1, dt3, load / 2. );
s = "Subtract half the load of the first hour of the interval";
qDebug()<<s;
Debug::print( lst2, "List2: " + s );
Debug::print( lst1, "List1: " + s );
lst2 -= lst1;
Debug::print( lst2, s );
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt3, lst2.map().values().first().endTime() );
QCOMPARE( load / 2., lst2.map().values().first().load() );
QCOMPARE( dt3, lst2.map().values().at( 1 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 1 ).load() );
s = "Subtract all load from first interval";
qDebug()<<s;
lst2 -= lst1; // remove first interval
QCOMPARE( lst2.map().count(), 1 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 0 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
s = "Subtract half the load from last hour of the interval";
qDebug()<<s;
lst1.clear();
dt4 = dt2 - Duration( 0, 1, 0 );
lst1.add( dt4, dt2, 50. );
Debug::print( lst1, "List1: " + s );
Debug::print( lst2, "List2: " + s );
lst2 -= lst1;
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt4, lst2.map().values().at( 0 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
QCOMPARE( dt4, lst2.map().values().at( 1 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( 50., lst2.map().values().at( 1 ).load() );
s = "Subtract all load from last interval";
qDebug()<<s;
Debug::print( lst1, "List1: " + s );
Debug::print( lst2, "List2: " + s );
AppointmentInterval i = lst2.map().values().at( 0 );
lst2 -= lst1;
Debug::print( lst2, "Result: " + s );
QCOMPARE( lst2.map().count(), 1 );
QCOMPARE( i.startTime(), lst2.map().values().at( 0 ).startTime() );
QCOMPARE( i.endTime(), lst2.map().values().at( 0 ).endTime() );
QCOMPARE( i.load(), lst2.map().values().at( 0 ).load() );
// Subtract overlapping intervals (start < start, end > end)
lst1.clear();
lst2.clear();
lst2.add( dt1, dt2, 100. );
dt3 = dt1 + Duration( 0, 1, 0 );
// starts before start, end in the middle
lst1.add( dt1.addSecs( -10 ), dt3, load / 2. );
s = "Subtract half the load of the first hour of the interval";
qDebug()<<s;
Debug::print( lst2, "List2: " + s );
Debug::print( lst1, "List1: " + s );
lst2 -= lst1;
Debug::print( lst2, s );
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt3, lst2.map().values().first().endTime() );
QCOMPARE( load / 2., lst2.map().values().first().load() );
QCOMPARE( dt3, lst2.map().values().at( 1 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 1 ).load() );
s = "Subtract all load from first interval";
qDebug()<<s;
lst2 -= lst1; // remove first interval
QCOMPARE( lst2.map().count(), 1 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 0 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
s = "Subtract half the load from last hour of the interval";
qDebug()<<s;
lst1.clear();
dt4 = dt2 - Duration( 0, 1, 0 );
lst1.add( dt4, dt2.addSecs( 10 ), 50. );
Debug::print( lst2, "List2: " + s );
Debug::print( lst1, "List1: " + s );
lst2 -= lst1;
Debug::print( lst2, "Result: " + s );
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt4, lst2.map().values().at( 0 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
QCOMPARE( dt4, lst2.map().values().at( 1 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( 50., lst2.map().values().at( 1 ).load() );
s = "Subtract all load from last interval";
qDebug()<<s;
Debug::print( lst1, "List1: " + s );
Debug::print( lst2, "List2: " + s );
i = lst2.map().values().at( 0 );
qDebug()<<"i:"<<i;
lst2 -= lst1;
Debug::print( lst2, "Result: " + s );
QCOMPARE( lst2.map().count(), 1 );
QCOMPARE( i.startTime(), lst2.map().values().at( 0 ).startTime() );
QCOMPARE( i.endTime(), lst2.map().values().at( 0 ).endTime() );
QCOMPARE( i.load(), lst2.map().values().at( 0 ).load() );
}
void AppointmentIntervalTester::subtractListMidnight()
{
QString s;
AppointmentIntervalList lst1;
AppointmentIntervalList lst2;
DateTime dt1 = DateTime( QDate( 2011, 01, 02 ), QTime( 22, 0, 0 ) );
DateTime dt2 = dt1 + Duration( 0, 3, 0 );
double load = 100;
lst1.add( dt1, dt2, load );
QCOMPARE( lst1.map().count(), 2 );
QCOMPARE( dt1, lst1.map().values().first().startTime() );
QCOMPARE( dt2, lst1.map().values().last().endTime() );
QCOMPARE( load, lst1.map().values().first().load() );
QCOMPARE( load, lst1.map().values().last().load() );
lst2 += lst1;
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().last().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( load, lst2.map().values().last().load() );
lst2 -= lst1;
QVERIFY( lst2.isEmpty() );
lst2.add( dt1, dt2, load * 2. );
lst2 -= lst1;
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().last().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( load, lst2.map().values().last().load() );
lst1.clear();
DateTime dt3 = dt2 + Duration( 0, 6, 0 );
DateTime dt4 = dt3 + Duration( 0, 1, 0 );
lst1.add( dt3, dt4, load );
qDebug()<<"Subtract non-overlapping intervals:";
qDebug()<<endl<<lst2<<endl<<"minus"<<endl<<lst1;
lst2 -= lst1;
qDebug()<<endl<<"result:"<<endl<<lst2;
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().last().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( load, lst2.map().values().last().load() );
DateTime dt5 = dt1 - Duration( 0, 6, 0 );
DateTime dt6 = dt5 + Duration( 0, 1, 0 );
lst1.add( dt5, dt6, load );
qDebug()<<"-------- lst2 -= lst1";
qDebug()<<endl<<lst2<<endl<<lst1;
lst2 -= lst1;
qDebug()<<endl<<lst2;
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().last().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( load, lst2.map().values().last().load() );
s = "Subtract tangent intervals";
qDebug()<<s;
lst1.clear();
lst1.add( dt1.addDays( -1 ), dt1, load ); // before
Debug::print( lst2, "List2: " + s );
Debug::print( lst1, "List1: " + s );
lst2 -= lst1;
Debug::print( lst2, "Result: " + s );
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().last().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( load, lst2.map().values().last().load() );
lst1.clear();
lst1.add( dt2, dt2.addDays( 1 ), load ); // after
lst2 -= lst1;
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt2, lst2.map().values().last().endTime() );
QCOMPARE( load, lst2.map().values().first().load() );
QCOMPARE( load, lst2.map().values().last().load() );
// Subtract overlapping intervals
lst1.clear();
dt3 = dt1 + Duration( 0, 1, 0 );
// starts at start, end in the middle (at 23:00)
lst1.add( dt1, dt3, load / 2. );
s = "Subtract half the load of the first hour of the interval";
qDebug()<<s;
Debug::print( lst2, "List2: " + s );
Debug::print( lst1, "List1: " + s );
lst2 -= lst1;
Debug::print( lst2, s );
QCOMPARE( lst2.map().count(), 3 );
QCOMPARE( dt1, lst2.map().values().first().startTime() );
QCOMPARE( dt3, lst2.map().values().first().endTime() );
QCOMPARE( load / 2., lst2.map().values().first().load() );
QCOMPARE( dt3, lst2.map().values().at( 1 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 2 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 1 ).load() );
QCOMPARE( load, lst2.map().values().at( 2 ).load() );
s = "Subtract all load from first interval";
qDebug()<<s;
lst2 -= lst1; // remove first interval
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
QCOMPARE( load, lst2.map().values().at( 1 ).load() );
s = "Subtract half the load from last 30 min of the last interval";
qDebug()<<s;
lst1.clear();
dt4 = dt2 - Duration( 0, 0, 30 );
lst1.add( dt4, dt2, 50. );
Debug::print( lst1, "List1: " + s );
Debug::print( lst2, "List2: " + s );
lst2 -= lst1;
QCOMPARE( lst2.map().count(), 3 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt4, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
QCOMPARE( load, lst2.map().values().at( 1 ).load() );
QCOMPARE( dt4, lst2.map().values().at( 2 ).startTime() );
QCOMPARE( dt2, lst2.map().values().at( 2 ).endTime() );
QCOMPARE( 50., lst2.map().values().at( 2 ).load() );
s = "Subtract all load from last interval";
qDebug()<<s;
Debug::print( lst1, "List1: " + s );
Debug::print( lst2, "List2: " + s );
lst2 -= lst1;
Debug::print( lst2, "Result: " + s );
QCOMPARE( lst2.map().count(), 2 );
QCOMPARE( dt3, lst2.map().values().at( 0 ).startTime() );
QCOMPARE( dt4, lst2.map().values().at( 1 ).endTime() );
QCOMPARE( load, lst2.map().values().at( 0 ).load() );
QCOMPARE( load, lst2.map().values().at( 1 ).load() );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::AppointmentIntervalTester )
diff --git a/src/libs/kernel/tests/CalendarTester.cpp b/src/libs/kernel/tests/CalendarTester.cpp
index b5aeabdb..20c08ecb 100644
--- a/src/libs/kernel/tests/CalendarTester.cpp
+++ b/src/libs/kernel/tests/CalendarTester.cpp
@@ -1,367 +1,368 @@
/* This file is part of the KDE project
Copyright (C) 2006-2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "CalendarTester.h"
#include "DateTimeTester.h"
#include <kptcalendar.h>
#include <kptdatetime.h>
#include <kptduration.h>
#include <kptmap.h>
#include "kptappointment.h"
#include <QTimeZone>
#include <QDateTime>
#include "debug.cpp"
namespace KPlato
{
QTimeZone createTimeZoneWithOffsetFromSystem(int hours, const QString & name, int *shiftDays)
{
QTimeZone systemTimeZone = QTimeZone::systemTimeZone();
int systemOffsetSeconds = systemTimeZone.standardTimeOffset(QDateTime(QDate(1980, 1, 1), QTime(), Qt::UTC));
int offsetSeconds = systemOffsetSeconds + 3600 * hours;
if (offsetSeconds >= (12*3600) ) {
qDebug() << "reducing offset by 24h";
offsetSeconds -= (24*3600);
*shiftDays = -1;
} else if (offsetSeconds <= -(12*3600) ) {
qDebug() << "increasing offset by 24h";
offsetSeconds += (24*3600);
*shiftDays = 1;
} else {
*shiftDays = 0;
}
qDebug() << "creating timezone for offset" << hours << offsetSeconds << "systemoffset" << systemOffsetSeconds
<< "shiftDays" << *shiftDays;
return QTimeZone(name.toLatin1(), offsetSeconds, name, name);
}
void CalendarTester::testSingleDay() {
Calendar t("Test");
QDate wdate(2006,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
t.addDay(day);
QVERIFY(t.findDay(wdate) == day);
QVERIFY(t.hasInterval(after, DateTime( after.addDays(1))) == false);
QVERIFY(t.hasInterval(before, DateTime(before.addDays(-1))) == false);
QVERIFY(t.hasInterval(after, before) == false);
QVERIFY(t.hasInterval(before, after));
QVERIFY((t.firstAvailableAfter(after, DateTime(after.addDays(10)))).isValid() == false);
QVERIFY((t.firstAvailableBefore(before, DateTime(before.addDays(-10)))).isValid() == false);
QCOMPARE(t.firstAvailableAfter(before,after), wdt1);
QCOMPARE(t.firstAvailableBefore(after, before), wdt2);
Duration e(0, 2, 0);
QCOMPARE((t.effort(before, after)), e);
}
void CalendarTester::testWeekdays() {
Calendar t("Test");
QDate wdate(2006,1,4); // wednesday
DateTime before = DateTime(wdate.addDays(-2), QTime());
DateTime after = DateTime(wdate.addDays(2), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
int length = t1.msecsTo( t2 );
CalendarDay *wd1 = t.weekday(Qt::Wednesday);
QVERIFY(wd1 != 0);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
QCOMPARE(t.firstAvailableAfter(before, after), DateTime(QDate(2006, 1, 4), QTime(8,0,0)));
QCOMPARE((t.firstAvailableBefore(after, before)), DateTime(QDate(2006, 1, 4), QTime(10,0,0)));
QCOMPARE(t.firstAvailableAfter(after, DateTime(QDate(QDate(2006,1,14)), QTime())), DateTime(QDate(2006, 1, 11), QTime(8,0,0)));
QCOMPARE(t.firstAvailableBefore(before, DateTime(QDate(2005,12,25), QTime())), DateTime(QDate(2005, 12, 28), QTime(10,0,0)));
}
void CalendarTester::testCalendarWithParent() {
Calendar p("Test 3 parent");
Calendar t("Test 3");
t.setParentCal(&p);
QDate wdate(2006,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
int length = t1.msecsTo( t2 );
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
p.addDay(day);
QVERIFY(p.findDay(wdate) == day);
//same tests as in testSingleDay()
QVERIFY(t.hasInterval(after, DateTime(after.addDays(1))) == false);
QVERIFY(t.hasInterval(before, DateTime(before.addDays(-1))) == false);
QVERIFY(t.hasInterval(after, before) == false);
QVERIFY(t.hasInterval(before, after));
QVERIFY((t.firstAvailableAfter(after, DateTime(after.addDays(10)))).isValid() == false);
QVERIFY((t.firstAvailableBefore(before, DateTime(before.addDays(-10)))).isValid() == false);
QVERIFY(t.firstAvailableAfter(before, after).isValid());
QVERIFY(t.firstAvailableBefore(after, before).isValid());
QCOMPARE(t.firstAvailableAfter(before,after), wdt1);
QCOMPARE(t.firstAvailableBefore(after, before), wdt2);
Duration e(0, 2, 0);
QCOMPARE((t.effort(before, after)), e);
}
void CalendarTester::testTimezone()
{
Calendar t("Test");
QDate wdate(2006,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
t.addDay(day);
Debug::print( &t, "Time zone testing" );
QVERIFY(t.findDay(wdate) == day);
// local zone: Europe/Berlin ( 1 hours from London )
int loShiftDays;
QTimeZone lo = createTimeZoneWithOffsetFromSystem(-1, "DummyLondon", &loShiftDays);
QVERIFY( lo.isValid() );
QDateTime dt1 = QDateTime( wdate, t1, lo ).addDays(loShiftDays).addSecs( -2 * 3600 );
QDateTime dt2 = QDateTime( wdate, t2, lo ).addDays(loShiftDays).addSecs( 0 * 3600 );
qDebug()<<QDateTime( wdt1 )<<QDateTime( wdt2 );
qDebug()<<dt1<<dt2<<"("<<dt1.toLocalTime()<<dt2.toLocalTime()<<")";
QCOMPARE(t.firstAvailableAfter( DateTime( dt1 ), after ), wdt1 );
QCOMPARE(t.firstAvailableBefore( DateTime( dt2 ), before ), wdt2 );
Duration e(0, 2, 0);
QCOMPARE( t.effort( DateTime( dt1 ), DateTime( dt2 ) ), e );
// local zone: Europe/Berlin ( 9 hours from America/Los_Angeles )
int laShiftDays;
QTimeZone la = createTimeZoneWithOffsetFromSystem(-9, "DummyLos_Angeles", &laShiftDays);
QVERIFY( la.isValid() );
QDateTime dt3 = QDateTime( wdate, t1, la ).addDays(laShiftDays).addSecs( -10 * 3600 );
QDateTime dt4 = QDateTime( wdate, t2, la ).addDays(laShiftDays).addSecs( -8 * 3600 );
qDebug()<<QDateTime( wdt1 )<<QDateTime( wdt2 );
qDebug()<<dt3<<dt4<<"("<<dt3.toLocalTime()<<dt4.toLocalTime()<<")";
QCOMPARE(t.firstAvailableAfter( DateTime( dt3 ), after ), wdt1 );
QCOMPARE(t.firstAvailableBefore( DateTime( dt4 ), before ), wdt2 );
QCOMPARE( t.effort( DateTime( dt3 ), DateTime( dt4 ) ), e );
QString s = "Test Cairo:";
qDebug()<<s;
// local zone: Europe/Berlin ( 1 hour from cairo )
int caShiftDays;
QTimeZone ca = createTimeZoneWithOffsetFromSystem(1, "DummyCairo", &caShiftDays);
QDateTime dt5 = QDateTime( wdate, t1, ca ).addDays(caShiftDays).addSecs( 0 * 3600 );
QDateTime dt6 = QDateTime( wdate, t2, ca ).addDays(caShiftDays).addSecs( 2 * 3600 );
qDebug()<<QDateTime( wdt1 )<<QDateTime( wdt2 );
qDebug()<<dt5<<dt6<<"("<<dt5.toLocalTime()<<dt6.toLocalTime()<<")";
QCOMPARE(t.firstAvailableAfter( DateTime( dt5 ), after ), wdt1 );
QCOMPARE(t.firstAvailableBefore( DateTime( dt6 ), before ), wdt2 );
QCOMPARE( t.effort( DateTime( dt5 ), DateTime( dt6 ) ), e );
}
void CalendarTester::workIntervals()
{
Calendar t("Test");
QDate wdate(2006,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
DateTime wdt1( wdate, t1 );
DateTime wdt2( wdate, t2 );
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval( TimeInterval( t1, length ) );
t.addDay(day);
QVERIFY(t.findDay(wdate) == day);
AppointmentIntervalList lst = t.workIntervals( before, after, 100. );
QCOMPARE( lst.map().count(), 1 );
QCOMPARE( wdate, lst.map().values().first().startTime().date() );
QCOMPARE( t1, lst.map().values().first().startTime().time() );
QCOMPARE( wdate, lst.map().values().first().endTime().date() );
QCOMPARE( t2, lst.map().values().first().endTime().time() );
QCOMPARE( 100., lst.map().values().first().load() );
QTime t3( 12, 0, 0 );
day->addInterval( TimeInterval( t3, length ) );
lst = t.workIntervals( before, after, 100. );
Debug::print( lst );
QCOMPARE( lst.map().count(), 2 );
QCOMPARE( wdate, lst.map().values().first().startTime().date() );
QCOMPARE( t1, lst.map().values().first().startTime().time() );
QCOMPARE( wdate, lst.map().values().first().endTime().date() );
QCOMPARE( t2, lst.map().values().first().endTime().time() );
QCOMPARE( 100., lst.map().values().first().load() );
QCOMPARE( wdate, lst.map().values().at( 1 ).startTime().date() );
QCOMPARE( t3, lst.map().values().at( 1 ).startTime().time() );
QCOMPARE( wdate, lst.map().values().at( 1 ).endTime().date() );
QCOMPARE( t3.addMSecs( length ), lst.map().values().at( 1 ).endTime().time() );
QCOMPARE( 100., lst.map().values().at( 1 ).load() );
// add interval before the existing
QTime t4( 5, 30, 0 );
day->addInterval( TimeInterval( t4, length ) );
lst = t.workIntervals( before, after, 100. );
Debug::print( lst );
QCOMPARE( lst.map().count(), 3 );
QCOMPARE( wdate, lst.map().values().first().startTime().date() );
QCOMPARE( t4, lst.map().values().first().startTime().time() );
QCOMPARE( wdate, lst.map().values().first().endTime().date() );
QCOMPARE( t4.addMSecs( length ), lst.map().values().first().endTime().time() );
QCOMPARE( 100., lst.map().values().first().load() );
QCOMPARE( wdate, lst.map().values().at( 1 ).startTime().date() );
QCOMPARE( t1, lst.map().values().at( 1 ).startTime().time() );
QCOMPARE( wdate, lst.map().values().at( 1 ).endTime().date() );
QCOMPARE( t2, lst.map().values().at( 1 ).endTime().time() );
QCOMPARE( 100., lst.map().values().at( 1 ).load() );
QCOMPARE( wdate, lst.map().values().at( 2 ).startTime().date() );
QCOMPARE( t3, lst.map().values().at( 2 ).startTime().time() );
QCOMPARE( wdate, lst.map().values().at( 2 ).endTime().date() );
QCOMPARE( t3.addMSecs( length ), lst.map().values().at( 2 ).endTime().time() );
QCOMPARE( 100., lst.map().values().at( 2 ).load() );
}
void CalendarTester::workIntervalsFullDays()
{
Calendar t("Test");
QDate wdate(2006,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(10), QTime());
CalendarDay *day = new CalendarDay( wdate, CalendarDay::Working );
day->addInterval( TimeInterval( QTime( 0, 0, 0), 24*60*60*1000 ) );
t.addDay(day);
QCOMPARE( day->numIntervals(), 1 );
QVERIFY( day->timeIntervals().constFirst()->endsMidnight() );
DateTime start = day->start();
DateTime end = day->end();
QCOMPARE( t.workIntervals( start, end, 100. ).map().count(), 1 );
QCOMPARE( t.workIntervals( before, after, 100. ).map().count(), 1 );
day = new CalendarDay( wdate.addDays( 1 ), CalendarDay::Working );
day->addInterval( TimeInterval( QTime( 0, 0, 0), 24*60*60*1000 ) );
t.addDay( day );
end = day->end();
QCOMPARE( t.workIntervals( start, end, 100. ).map().count(), 2 );
QCOMPARE( t.workIntervals( before, after, 100. ).map().count(), 2 );
day = new CalendarDay( wdate.addDays( 2 ), CalendarDay::Working );
day->addInterval( TimeInterval( QTime( 0, 0, 0), 24*60*60*1000 ) );
t.addDay( day );
end = day->end();
QCOMPARE( t.workIntervals( start, end, 100. ).map().count(), 3 );
QCOMPARE( t.workIntervals( before, after, 100. ).map().count(), 3 );
}
void CalendarTester::dstSpring()
{
QByteArray tz("TZ=Europe/Copenhagen");
putenv(tz.data());
Calendar t("DST");
QDate wdate(2016,3,27);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(0,0,0);
int length = 24*60*60*1000;
CalendarDay *wd1 = t.weekday(Qt::Sunday);
QVERIFY(wd1 != 0);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
AppointmentIntervalList lst = t.workIntervals( before, after, 100. );
qDebug()<<lst;
QCOMPARE( lst.map().count(), 1 );
QCOMPARE( lst.map().first().effort().toHours(), 23. ); // clazy:exclude=detaching-temporary
wd1->clearIntervals();
qDebug()<<"clear list";
wd1->addInterval(TimeInterval(QTime(0,0,0), Duration(2., Duration::Unit_h).milliseconds() ));
wd1->addInterval(TimeInterval(QTime(2,0,0), Duration(2., Duration::Unit_h).milliseconds() ));
lst = t.workIntervals( before, after, 100. );
qDebug()<<"DST?"<<DateTime(wdate, QTime(2,0,0))<<endl<<lst;
QCOMPARE( lst.map().count(), 2 );
AppointmentInterval ai = lst.map().values().value(0);
QCOMPARE( ai.startTime(), DateTime(wdate, QTime()));
QCOMPARE( ai.endTime(), DateTime(wdate, QTime(2,0,0)));
QCOMPARE( ai.effort().toHours(), 2.);
Debug::print(lst);
ai = lst.map().values().value(1);
QCOMPARE( ai.startTime(), DateTime(wdate, QTime(2,0,0)));
QCOMPARE( ai.endTime(), DateTime(wdate, QTime(4,0,0)));
QCOMPARE( ai.effort().toHours(), 1.); // Missing DST hour is skipped
unsetenv("TZ");
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::CalendarTester )
diff --git a/src/libs/kernel/tests/CommandsTester.cpp b/src/libs/kernel/tests/CommandsTester.cpp
index e3abdefd..2fde4d09 100644
--- a/src/libs/kernel/tests/CommandsTester.cpp
+++ b/src/libs/kernel/tests/CommandsTester.cpp
@@ -1,534 +1,535 @@
/* This file is part of the KDE project
Copyright (C) 2011 Pierre Stirnweiss <pstirnweiss@googlemail.com>
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 "CommandsTester.h"
#include <kptcommand.h>
#include <kptcalendar.h>
#include <kptproject.h>
#include <kptresource.h>
#include <QTest>
namespace KPlato {
void CommandsTester::initTestCase()
{
m_project = new Project();
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId(m_project); //->rootTask());
// standard worktime defines 8 hour day as default
QVERIFY( m_project->standardWorktime() );
QCOMPARE( m_project->standardWorktime()->day(), 8.0 );
/* m_calendar = new Calendar();
m_calendar->setDefault( true );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = m_calendar->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
m_project->addCalendar( m_calendar );
*/
}
void CommandsTester::cleanupTestCase()
{
delete m_project;
}
void CommandsTester::testNamedCommand()
{
//todo: test the schedule handling
}
void CommandsTester::testCalendarAddCmd()
{
Calendar *calendar1 = new Calendar();
Calendar *calendar2 = new Calendar();
CalendarAddCmd *cmd1 = new CalendarAddCmd(m_project, calendar1, 0, 0);
cmd1->execute();
QVERIFY(m_project->calendarAt(0) == calendar1);
CalendarAddCmd *cmd2 = new CalendarAddCmd(m_project, calendar2, 0, calendar1);
cmd2->execute();
QVERIFY(calendar1->childAt(0) == calendar2);
cmd2->unexecute();
QVERIFY(!calendar1->childCount());
cmd1->unexecute();
QVERIFY(!m_project->calendarCount());
delete cmd1;
delete cmd2; //These also delete the calendars
}
void CommandsTester::testCalendarRemoveCmd()
{
Calendar *calendar1 = new Calendar();
Calendar *calendar2 = new Calendar();
Calendar *calendar3 = new Calendar();
Calendar *calendar4 = new Calendar();
ResourceGroup *group1 = new ResourceGroup();
Resource *resource1 = new Resource();
m_project->addCalendar(calendar1);
m_project->addCalendar(calendar2, calendar1);
m_project->addCalendar(calendar3);
m_project->addCalendar(calendar4);
m_project->setDefaultCalendar(calendar3);
m_project->addResourceGroup(group1);
m_project->addResource(group1, resource1);
resource1->setCalendar(calendar4);
QVERIFY(m_project->calendarAt(0) == calendar1);
QVERIFY(calendar1->childAt(0) == calendar2);
QVERIFY(m_project->calendarAt(1) == calendar3);
QVERIFY(m_project->calendarAt(2) == calendar4);
QVERIFY(m_project->resourceGroupAt(0) == group1);
QVERIFY(group1->resourceAt(0) == resource1);
QVERIFY(m_project->defaultCalendar() == calendar3);
QVERIFY(resource1->calendar(true) == calendar4);
CalendarRemoveCmd *cmd1 = new CalendarRemoveCmd(m_project, calendar1); //calendar with one child calendar
CalendarRemoveCmd *cmd2 = new CalendarRemoveCmd(m_project, calendar2); //simple calendar
CalendarRemoveCmd *cmd3 = new CalendarRemoveCmd(m_project, calendar3); //default project calendar
CalendarRemoveCmd *cmd4 = new CalendarRemoveCmd(m_project, calendar4); //calendar used by a resource
cmd3->execute();
QVERIFY(!m_project->calendars().contains(calendar3));
QVERIFY(m_project->defaultCalendar() != calendar3);
cmd4->execute();
QVERIFY(!m_project->calendars().contains(calendar4));
QVERIFY(resource1->calendar(true) != calendar4);
cmd2->execute();
QVERIFY(!calendar1->calendars().contains(calendar2));
cmd2->unexecute();
QVERIFY(calendar1->calendars().contains(calendar2));
cmd1->execute();
QVERIFY(!m_project->calendars().contains(calendar1));
cmd1->unexecute();
QVERIFY(m_project->calendars().contains(calendar1));
QVERIFY(calendar1->calendars().contains(calendar2));
cmd3->unexecute();
QVERIFY(m_project->calendars().contains(calendar3));
QVERIFY(m_project->defaultCalendar() == calendar3);
cmd4->unexecute();
QVERIFY(m_project->calendars().contains(calendar4));
QVERIFY(resource1->calendar(true) == calendar4);
m_project->takeCalendar(calendar4);
m_project->takeCalendar(calendar3);
m_project->takeCalendar(calendar2);
m_project->takeCalendar(calendar1);
m_project->takeResource(group1, resource1);
m_project->takeResourceGroup(group1);
delete cmd1;
delete cmd2;
delete cmd3;
delete cmd4;
delete calendar4;
delete calendar3;
delete calendar2;
delete calendar1;
delete resource1;
delete group1;
}
void CommandsTester::testCalendarMoveCmd()
{
Calendar *calendar1 = new Calendar();
Calendar *calendar2 = new Calendar();
m_project->addCalendar(calendar1);
m_project->addCalendar(calendar2);
QVERIFY(m_project->calendarCount() == 2);
CalendarMoveCmd *cmd1 = new CalendarMoveCmd(m_project, calendar1, 0, calendar2);
cmd1->execute();
QVERIFY(calendar2->childAt(0) == calendar1);
cmd1->unexecute();
QVERIFY(!calendar2->childCount());
delete cmd1;
m_project->takeCalendar(calendar1);
m_project->takeCalendar(calendar2);
delete calendar1;
delete calendar2;
}
void CommandsTester::testCalendarModifyNameCmd()
{
Calendar *calendar1 = new Calendar(QString("test1"));
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendars().contains(calendar1));
QVERIFY(calendar1->name() == "test1");
CalendarModifyNameCmd *cmd1 = new CalendarModifyNameCmd(calendar1, QString("test2"));
cmd1->execute();
QVERIFY(calendar1->name() == "test2");
cmd1->unexecute();
QVERIFY(calendar1->name() == "test1");
m_project->takeCalendar(calendar1);
delete cmd1;
delete calendar1;
}
void CommandsTester::testCalendarModifyParentCmd()
{
Calendar *calendar1 = new Calendar();
calendar1->setTimeZone(QTimeZone("Europe/Berlin"));
Calendar *calendar2 = new Calendar();
calendar2->setTimeZone(QTimeZone("Africa/Cairo"));
m_project->addCalendar(calendar1);
m_project->addCalendar(calendar2);
QVERIFY(m_project->calendarCount() == 2);
QVERIFY(calendar1->timeZone().id() == "Europe/Berlin");
QVERIFY(calendar2->timeZone().id() == "Africa/Cairo");
CalendarModifyParentCmd *cmd1 = new CalendarModifyParentCmd(m_project, calendar1, calendar2);
cmd1->execute();
QVERIFY(calendar2->childAt(0) == calendar1);
QVERIFY(calendar1->timeZone().id() == "Africa/Cairo");
cmd1->unexecute();
QVERIFY(!calendar2->childCount());
QVERIFY(calendar1->timeZone().id() == "Europe/Berlin");
delete cmd1;
m_project->takeCalendar(calendar1);
m_project->takeCalendar(calendar2);
delete calendar1;
delete calendar2;
}
void CommandsTester::testCalendarModifyTimeZoneCmd()
{
Calendar *calendar1 = new Calendar();
calendar1->setTimeZone(QTimeZone("Europe/Berlin"));
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->timeZone().id() == "Europe/Berlin");
CalendarModifyTimeZoneCmd *cmd1 = new CalendarModifyTimeZoneCmd(calendar1, QTimeZone("Africa/Cairo"));
cmd1->execute();
QVERIFY(calendar1->timeZone().id() == "Africa/Cairo");
cmd1->unexecute();
QVERIFY(calendar1->timeZone().id() == "Europe/Berlin");
delete cmd1;
m_project->takeCalendar(calendar1);
delete calendar1;
}
void CommandsTester::testCalendarAddDayCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay();
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().isEmpty());
CalendarAddDayCmd *cmd1 = new CalendarAddDayCmd(calendar1, day1);
cmd1->execute();
QVERIFY(calendar1->days().contains(day1));
cmd1->unexecute();
QVERIFY(calendar1->days().isEmpty());
delete cmd1; //also deletes day1
m_project->takeCalendar(calendar1);
delete calendar1;
}
void CommandsTester::testCalendarRemoveDayCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
calendar1->addDay(day1);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
CalendarRemoveDayCmd *cmd1 = new CalendarRemoveDayCmd(calendar1, day1);
cmd1->execute();
QVERIFY(calendar1->days().isEmpty());
cmd1->unexecute();
QVERIFY(calendar1->days().contains(day1));
CalendarRemoveDayCmd *cmd2 = new CalendarRemoveDayCmd(calendar1, QDate(1974, 12, 19));
cmd2->execute();
QVERIFY(calendar1->days().isEmpty());
cmd2->unexecute();
QVERIFY(calendar1->days().contains(day1));
calendar1->takeDay(day1);
delete cmd1;
delete cmd2;
delete day1;
m_project->takeCalendar(calendar1);
delete calendar1;
}
void CommandsTester::testCalendarModifyDayCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
CalendarDay *day2 = new CalendarDay(QDate(2011,03,11));
calendar1->addDay(day1);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
CalendarModifyDayCmd *cmd1 = new CalendarModifyDayCmd(calendar1, day2);
cmd1->execute();
QVERIFY(calendar1->days().contains(day2));
cmd1->unexecute();
QVERIFY(calendar1->days().contains(day1));
QVERIFY(!calendar1->days().contains(day2));
calendar1->takeDay(day1);
delete cmd1; //also deletes day2
delete day1;
m_project->takeCalendar(calendar1);
delete calendar1;
}
void CommandsTester::testCalendarModifyStateCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
day1->setState(CalendarDay::Working);
calendar1->addDay(day1);
CalendarDay *day2 = new CalendarDay(QDate(2011,03,26));
TimeInterval *interval1 = new TimeInterval(QTime(8,0), 8);
day2->addInterval(interval1);
day2->setState(CalendarDay::Working);
calendar1->addDay(day2);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
QVERIFY(day1->state() == CalendarDay::Working);
QVERIFY(calendar1->days().contains(day2));
QVERIFY(day2->timeIntervals().contains(interval1));
CalendarModifyStateCmd *cmd1 = new CalendarModifyStateCmd(calendar1, day1, CalendarDay::NonWorking);
cmd1->execute();
QVERIFY(day1->state() == CalendarDay::NonWorking);
cmd1->unexecute();
QVERIFY(day1->state() == CalendarDay::Working);
CalendarModifyStateCmd *cmd2 = new CalendarModifyStateCmd(calendar1, day2, CalendarDay::NonWorking);
cmd2->execute();
QVERIFY(day2->state() == CalendarDay::NonWorking);
QVERIFY(!day2->timeIntervals().count());
cmd2->unexecute();
QVERIFY(day2->timeIntervals().contains(interval1));
QVERIFY(day2->state() == CalendarDay::Working);
calendar1->takeDay(day1);
calendar1->takeDay(day2);
day2->clearIntervals();
m_project->takeCalendar(calendar1);
delete cmd1;
delete cmd2;
delete interval1;
delete day1;
delete day2;
delete calendar1;
}
void CommandsTester::testCalendarModifyTimeIntervalCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
TimeInterval *interval1 = new TimeInterval(QTime(8,0), 3600000);
TimeInterval *interval2 = new TimeInterval(QTime(12,0),7200000);
day1->addInterval(interval1);
calendar1->addDay(day1);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
QVERIFY(day1->timeIntervals().contains(interval1));
QVERIFY(interval1->startTime() == QTime(8,0));
QVERIFY(interval1->hours() == 1);
CalendarModifyTimeIntervalCmd *cmd1 = new CalendarModifyTimeIntervalCmd(calendar1, *interval2, interval1);
cmd1->execute();
QVERIFY(interval1->startTime() == QTime(12,0));
QVERIFY(interval1->hours() == 2);
cmd1->unexecute();
QVERIFY(interval1->startTime() == QTime(8,0));
QVERIFY(interval1->hours() == 1);
day1->clearIntervals();
calendar1->takeDay(day1);
m_project->takeCalendar(calendar1);
delete cmd1;
delete interval1;
delete interval2;
delete day1;
delete calendar1;
}
void CommandsTester::testCalendarAddTimeIntervalCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
TimeInterval *interval1 = new TimeInterval(QTime(8,0), 3600000);
calendar1->addDay(day1);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
CalendarAddTimeIntervalCmd *cmd1 = new CalendarAddTimeIntervalCmd(calendar1, day1, interval1);
cmd1->execute();
QVERIFY(day1->timeIntervals().contains(interval1));
cmd1->unexecute();
QVERIFY(!day1->timeIntervals().contains(interval1));
calendar1->takeDay(day1);
m_project->takeCalendar(calendar1);
delete cmd1; //also delete interval
delete day1;
delete calendar1;
}
void CommandsTester::testCalendarRemoveTimeIntervalCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
TimeInterval *interval1 = new TimeInterval(QTime(8,0), 3600000);
calendar1->addDay(day1);
calendar1->addWorkInterval(day1, interval1);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
QVERIFY(day1->timeIntervals().contains(interval1));
CalendarRemoveTimeIntervalCmd *cmd1 = new CalendarRemoveTimeIntervalCmd(calendar1, day1, interval1);
cmd1->execute();
QVERIFY(!day1->timeIntervals().contains(interval1));
cmd1->unexecute();
QVERIFY(day1->timeIntervals().contains(interval1));
day1->clearIntervals();
calendar1->takeDay(day1);
m_project->takeCalendar(calendar1);
delete cmd1;
delete interval1;
delete day1;
delete calendar1;
}
void CommandsTester::testCalendarModifyWeekdayCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
TimeInterval *interval1 = new TimeInterval(QTime(8,0), 3600000);
day1->setState(CalendarDay::Working);
day1->addInterval(interval1);
CalendarDay *day2 = new CalendarDay(QDate(2011,3,26));
TimeInterval *interval2 = new TimeInterval(QTime(12,0), 7200000);
day2->setState(CalendarDay::NonWorking);
day2->addInterval(interval2);
m_project->addCalendar(calendar1);
calendar1->setWeekday(1, *day1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->weekday(1)->state() == day1->state());
QVERIFY(calendar1->weekday(1)->timeIntervals().first()->startTime() == day1->timeIntervals().first()->startTime());
QVERIFY(calendar1->weekday(1)->timeIntervals().first()->hours() == day1->timeIntervals().first()->hours());
CalendarModifyWeekdayCmd *cmd1 = new CalendarModifyWeekdayCmd(calendar1, 1, day2);
cmd1->execute();
QVERIFY(calendar1->weekday(1)->state() == day2->state());
QVERIFY(calendar1->weekday(1)->timeIntervals().first()->startTime() == day2->timeIntervals().first()->startTime());
QVERIFY(calendar1->weekday(1)->timeIntervals().first()->hours() == day2->timeIntervals().first()->hours());
cmd1->unexecute();
QVERIFY(calendar1->weekday(1)->state() == day1->state());
QVERIFY(calendar1->weekday(1)->timeIntervals().first()->startTime() == day1->timeIntervals().first()->startTime());
QVERIFY(calendar1->weekday(1)->timeIntervals().first()->hours() == day1->timeIntervals().first()->hours());
m_project->takeCalendar(calendar1);
day1->clearIntervals();
day2->clearIntervals();
delete cmd1; //also deletes day2
delete day1;
delete interval1;
delete interval2;
delete calendar1;
}
void CommandsTester::testCalendarModifyDateCmd()
{
Calendar *calendar1 = new Calendar();
CalendarDay *day1 = new CalendarDay(QDate(1974,12,19));
calendar1->addDay(day1);
m_project->addCalendar(calendar1);
QVERIFY(m_project->calendarCount() == 1);
QVERIFY(calendar1->days().contains(day1));
QVERIFY(day1->date() == QDate(1974,12,19));
CalendarModifyDateCmd *cmd1 = new CalendarModifyDateCmd(calendar1, day1, QDate(2011,3,26));
cmd1->execute();
QVERIFY(day1->date() == QDate(2011,3,26));
cmd1->unexecute();
QVERIFY(day1->date() == QDate(1974,12,19));
calendar1->takeDay(day1);
m_project->takeCalendar(calendar1);
delete cmd1;
delete day1;
delete calendar1;
}
void CommandsTester::testProjectModifyDefaultCalendarCmd()
{
Calendar *calendar1 = new Calendar();
Calendar *calendar2 = new Calendar();
m_project->addCalendar(calendar1);
m_project->addCalendar(calendar2);
m_project->setDefaultCalendar(calendar1);
QVERIFY(m_project->calendars().contains(calendar1));
QVERIFY(m_project->calendars().contains(calendar2));
QVERIFY(m_project->defaultCalendar() == calendar1);
ProjectModifyDefaultCalendarCmd *cmd1 = new ProjectModifyDefaultCalendarCmd(m_project, calendar2);
cmd1->execute();
QVERIFY(m_project->defaultCalendar() == calendar2);
cmd1->unexecute();
QVERIFY(m_project->defaultCalendar() == calendar1);
m_project->takeCalendar(calendar1);
m_project->takeCalendar(calendar2);
delete calendar1;
delete calendar2;
delete cmd1;
}
} // namespace KPlato
QTEST_GUILESS_MAIN( KPlato::CommandsTester)
diff --git a/src/libs/kernel/tests/DateTimeTester.cpp b/src/libs/kernel/tests/DateTimeTester.cpp
index fa25acfd..602b5ecb 100644
--- a/src/libs/kernel/tests/DateTimeTester.cpp
+++ b/src/libs/kernel/tests/DateTimeTester.cpp
@@ -1,187 +1,188 @@
/* This file is part of the KDE project
Copyright (C) 2006-2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "DateTimeTester.h"
#include <kptdatetime.h>
#include <kptduration.h>
#include "debug.cpp"
namespace KPlato
{
void DateTimeTester::subtractDay()
{
DateTime dt1(QDate(2006, 1, 1), QTime(8, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 2), QTime(8, 0, 0, 0));
Duration d(1, 0, 0, 0, 0);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt2-d).toString() == dt1.toString());
}
void DateTimeTester::subtractHour()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(1, 0, 0, 0));
Duration d(0, 1, 0, 0, 0);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt2-d).toString() == dt1.toString());
}
void DateTimeTester::subtractMinute()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(0, 1, 0, 0));
Duration d(0, 0, 1, 0, 0);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt2-d).toString() == dt1.toString());
}
void DateTimeTester::subtractSecond()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(0, 0, 1, 0));
Duration d(0, 0, 0, 1, 0);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt2-d).toString() == dt1.toString());
}
void DateTimeTester::subtractMillisecond()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(0, 0, 0, 1));
Duration d(0, 0, 0, 0, 1);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt2-d) == dt1);
dt1 = DateTime(QDate(2006, 1, 1), QTime(0, 0, 0, 1 ));
dt2 = DateTime(QDate(2006, 1, 1), QTime(0, 0, 0, 0));
d = Duration(0, 0, 0, 0, 1);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt1-d) == dt2);
dt1 = DateTime(QDate(2006, 1, 1), QTime(0, 0, 1, 1 ));
dt2 = DateTime(QDate(2006, 1, 1), QTime(0, 1, 0, 0));
d = Duration(0, 0, 0, 58, 999);
QVERIFY((dt2-dt1).toString() == d.toString());
QVERIFY((dt1-dt2).toString() == d.toString()); // result always positive
QVERIFY((dt2-d) == dt1);
}
void DateTimeTester::addDay()
{
DateTime dt1(QDate(2006, 1, 1), QTime(8, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 2), QTime(8, 0, 0, 0));
Duration d(1, 0, 0, 0, 0);
QVERIFY((dt1+d) == dt2);
}
void DateTimeTester::addHour()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(1, 0, 0, 0));
Duration d(0, 1, 0, 0, 0);
QVERIFY((dt1+d) == dt2);
}
void DateTimeTester::addMinute()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(0, 1, 0, 0));
Duration d(0, 0, 1, 0, 0);
QVERIFY((dt1+d) == dt2);
}
void DateTimeTester::addSecond()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(0, 0, 1, 0));
Duration d(0, 0, 0, 1, 0);
QVERIFY((dt1+d) == dt2);
}
void DateTimeTester::addMillisecond()
{
DateTime dt1(QDate(2006, 1, 1), QTime(0, 0, 0, 0 ));
DateTime dt2(QDate(2006, 1, 1), QTime(0, 0, 0, 1));
Duration d(0, 0, 0, 0, 1);
QVERIFY((dt1+d) == dt2);
}
void DateTimeTester::timeZones()
{
QByteArray tz("TZ=Europe/Copenhagen");
putenv(tz.data());
QTimeZone testZone("Europe/London");
DateTime dt1(QDate(2006, 1, 1), QTime(8, 0, 0, 0 ), testZone);
DateTime dt2 = dt1;
qDebug()<<dt1<<dt2;
QCOMPARE(dt1.timeZone(), dt2.timeZone());
dt2 += Duration(1, 0, 0, 0, 0);
qDebug()<<dt2;
QCOMPARE(dt1.timeZone(), dt2.timeZone());
dt2 -= Duration(1, 0, 0, 0, 0);
qDebug()<<dt1<<dt2;
QCOMPARE(dt1.timeZone(), dt2.timeZone());
QCOMPARE(dt2, dt1);
dt2 = dt1 + Duration(1, 0, 0, 0, 0);
qDebug()<<dt1<<dt2;
QCOMPARE(dt1.timeZone(), dt2.timeZone());
dt2 = dt2 - Duration(1, 0, 0, 0, 0);
qDebug()<<dt1<<dt2;
QCOMPARE(dt1.timeZone(), dt2.timeZone());
QCOMPARE(dt2, dt1);
DateTime dt3 = QDateTime(QDate(2006, 1, 1), QTime(8, 0, 0, 0 ), testZone);
qDebug()<<dt3;
QCOMPARE(dt3.timeZone(), testZone);
DateTime dt4(QDateTime(QDate(2006, 1, 1), QTime(8, 0, 0, 0 ), Qt::UTC));
dt4 += Duration(1, 0, 0, 0, 0);
qDebug()<<dt4;
QCOMPARE(dt4.timeSpec(), Qt::UTC);
unsetenv("TZ");
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::DateTimeTester )
diff --git a/src/libs/kernel/tests/DurationTester.cpp b/src/libs/kernel/tests/DurationTester.cpp
index 4c5fb045..beeb50ec 100644
--- a/src/libs/kernel/tests/DurationTester.cpp
+++ b/src/libs/kernel/tests/DurationTester.cpp
@@ -1,90 +1,91 @@
/* This file is part of the KDE project
Copyright (C) 2006-2007 Dag Andersen <danders@get2net.dk>
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 "DurationTester.h"
#include <kptduration.h>
#include <QTest>
namespace KPlato
{
void DurationTester::add() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY((d1+d1) == Duration(0, 4, 0));
}
void DurationTester::subtract() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY((d1-d1) == Duration(0, 0, 0));
QVERIFY((d2-d1) == Duration(0, 22, 0));
QVERIFY((d1-d2) == Duration::zeroDuration); // underflow, return 0
}
void DurationTester::divide() {
Duration d1(0, 2, 0);
QVERIFY((d1/2) == Duration(0, 1, 0));
}
void DurationTester::equal() {
Duration d1(0, 2, 0);
QVERIFY(d1==d1);
}
void DurationTester::lessThanOrEqual() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY(d1<=d1);
QVERIFY(d1<=d2);
}
void DurationTester::greaterThanOrEqual() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY(d1>=d1);
QVERIFY(d2>=d1);
}
void DurationTester::notEqual() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY(!(d1!=d1));
QVERIFY(d1!=d2);
QVERIFY(d2!=d1);
}
void DurationTester::greaterThan() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY(d2>d1);
QVERIFY(d1 > 1*60*60*1000);
}
void DurationTester::lessThan() {
Duration d1(0, 2, 0);
Duration d2(1, 0, 0);
QVERIFY(d1<d2);
QVERIFY(d1 < 3*60*60*1000);
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::DurationTester )
diff --git a/src/libs/kernel/tests/EstimateTester.cpp b/src/libs/kernel/tests/EstimateTester.cpp
index f414d219..79862784 100644
--- a/src/libs/kernel/tests/EstimateTester.cpp
+++ b/src/libs/kernel/tests/EstimateTester.cpp
@@ -1,274 +1,275 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
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 "EstimateTester.h"
#include "kptduration.h"
#include "kptnode.h"
#include <QTest>
namespace KPlato
{
void EstimateTester::expected() {
Estimate e1;
e1.clear();
QVERIFY( e1.expectedEstimate() == 0.0 );
QVERIFY( e1.expectedValue().milliseconds() == 0.0 );
e1.setExpectedEstimate( 1.0 );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1000*60*60 );
e1.setUnit( Duration::Unit_ms );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1 );
e1.setUnit( Duration::Unit_s );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1000 );
e1.setUnit( Duration::Unit_m );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1000*60 );
e1.setUnit( Duration::Unit_h );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1000*60*60 );
e1.setUnit( Duration::Unit_d );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1000*60*60*24 );
e1.setUnit( Duration::Unit_w );
QVERIFY( e1.expectedEstimate() == 1.0 );
QVERIFY( e1.expectedValue().milliseconds() == 1000*60*60*24*7 );
e1.setUnit( Duration::Unit_M );
QVERIFY( e1.expectedEstimate() == 1.0 );
QCOMPARE( e1.expectedValue().milliseconds(), qint64(1000*60*60) * (24*30) );
e1.setUnit( Duration::Unit_Y );
QVERIFY( e1.expectedEstimate() == 1.0 );
QCOMPARE( e1.expectedValue().milliseconds(), qint64(1000*60*60) * (24*365) );
}
void EstimateTester::optimistic() {
Estimate e1;
e1.clear();
QVERIFY( e1.optimisticEstimate() == 0.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 0.0 );
e1.setOptimisticEstimate( 1.0 );
QVERIFY( e1.optimisticEstimate() == 1.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 1000*60*60 );
e1.setUnit( Duration::Unit_ms );
QVERIFY( e1.optimisticEstimate() == 1.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 1 );
e1.setUnit( Duration::Unit_s );
QVERIFY( e1.optimisticEstimate() == 1.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 1000 );
e1.setUnit( Duration::Unit_m );
QVERIFY( e1.optimisticEstimate() == 1.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 1000*60 );
e1.setUnit( Duration::Unit_h );
QVERIFY( e1.optimisticEstimate() == 1.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 1000*60*60 );
e1.setUnit( Duration::Unit_d );
QVERIFY( e1.optimisticEstimate() == 1.0 );
QVERIFY( e1.optimisticValue().milliseconds() == 1000*60*60*24 );
}
void EstimateTester::pessimistic() {
Estimate e1;
e1.clear();
QVERIFY( e1.pessimisticEstimate() == 0.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 0.0 );
e1.setPessimisticEstimate( 1.0 );
QVERIFY( e1.pessimisticEstimate() == 1.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 1000*60*60 );
e1.setUnit( Duration::Unit_ms );
QVERIFY( e1.pessimisticEstimate() == 1.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 1 );
e1.setUnit( Duration::Unit_s );
QVERIFY( e1.pessimisticEstimate() == 1.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 1000 );
e1.setUnit( Duration::Unit_m );
QVERIFY( e1.pessimisticEstimate() == 1.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 1000*60 );
e1.setUnit( Duration::Unit_h );
QVERIFY( e1.pessimisticEstimate() == 1.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 1000*60*60 );
e1.setUnit( Duration::Unit_d );
QVERIFY( e1.pessimisticEstimate() == 1.0 );
QVERIFY( e1.pessimisticValue().milliseconds() == 1000*60*60*24 );
}
void EstimateTester::ratio() {
Estimate e1;
e1.clear();
e1.setExpectedEstimate( 1.0 );
e1.setOptimisticEstimate( 1.0 );
e1.setPessimisticEstimate( 1.0 );
QVERIFY( e1.pessimisticRatio() == 0 );
QVERIFY( e1.optimisticRatio() == 0 );
e1.setExpectedEstimate( 2.0 );
e1.setOptimisticEstimate( 1.0 );
e1.setPessimisticEstimate( 4.0 );
QVERIFY( e1.pessimisticRatio() == 100 );
QVERIFY( e1.optimisticRatio() == -50 );
e1.setUnit( Duration::Unit_h );
e1.setOptimisticEstimate( 0.5 );
QVERIFY( e1.pessimisticRatio() == 100 );
QVERIFY( e1.optimisticRatio() == -75 );
e1.clear();
e1.setUnit( Duration::Unit_d );
e1.setExpectedEstimate( 1.0 );
e1.setOptimisticRatio( -50 );
e1.setPessimisticRatio( 100 );
QVERIFY( e1.pessimisticEstimate() == 2.0 );
QVERIFY( e1.optimisticEstimate() == 0.5 );
QVERIFY( e1.pessimisticValue() == 1000*60*60 * 48 );
QVERIFY( e1.optimisticValue() == 1000*60*60 * 12 );
}
void EstimateTester::defaultScale() {
QList<qint64> s = Estimate::defaultScales();
QCOMPARE( s.count(), 8 );
Duration d = Estimate::scale( 1.0, Duration::Unit_Y, s );
QCOMPARE( d.milliseconds(), s[0] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_Y, s ) );
d = Estimate::scale( 1.0, Duration::Unit_M, s );
QCOMPARE( d.milliseconds(), s[1] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_M, s ) );
d = Estimate::scale( 1.0, Duration::Unit_w, s );
QCOMPARE( d.milliseconds(), s[2] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_w, s ) );
d = Estimate::scale( 1.0, Duration::Unit_d, s );
QCOMPARE( d.milliseconds(), s[3] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_d, s ) );
d = Estimate::scale( 1.0, Duration::Unit_h, s );
QCOMPARE( d.milliseconds(), s[4] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_h, s ) );
d = Estimate::scale( 1.0, Duration::Unit_m, s );
QCOMPARE( d.milliseconds(), s[5] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_m, s ) );
d = Estimate::scale( 1.0, Duration::Unit_s, s );
QCOMPARE( d.milliseconds(), s[6] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_s, s ) );
d = Estimate::scale( 1.0, Duration::Unit_ms, s );
QCOMPARE( d.milliseconds(), s[7] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_ms, s ) );
}
void EstimateTester::scale() {
StandardWorktime wt;
QList<qint64> s = wt.scales();
QCOMPARE( s.count(), 8 );
Duration d = Estimate::scale( 1.0, Duration::Unit_Y, s );
QCOMPARE( d.milliseconds(), s[0] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_Y, s ) );
d = Estimate::scale( 1.0, Duration::Unit_M, s );
QCOMPARE( d.milliseconds(), s[1] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_M, s ) );
d = Estimate::scale( 1.0, Duration::Unit_w, s );
QCOMPARE( d.milliseconds(), s[2] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_w, s ) );
d = Estimate::scale( 1.0, Duration::Unit_d, s );
QCOMPARE( d.milliseconds(), s[3] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_d, s ) );
d = Estimate::scale( 1.0, Duration::Unit_h, s );
QCOMPARE( d.milliseconds(), s[4] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_h, s ) );
d = Estimate::scale( 1.0, Duration::Unit_m, s );
QCOMPARE( d.milliseconds(), s[5] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_m, s ) );
d = Estimate::scale( 1.0, Duration::Unit_s, s );
QCOMPARE( d.milliseconds(), s[6] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_s, s ) );
d = Estimate::scale( 1.0, Duration::Unit_ms, s );
QCOMPARE( d.milliseconds(), s[7] );
QCOMPARE( 1.0, Estimate::scale( d, Duration::Unit_ms, s ) );
}
void EstimateTester::pert() {
Estimate e1;
e1.clear();
e1.setUnit( Duration::Unit_d );
e1.setExpectedEstimate( 4.0 );
e1.setOptimisticEstimate( 2.0 );
e1.setPessimisticEstimate( 8.0 );
QVERIFY( e1.deviation() == 1.0 );
QVERIFY( e1.deviation( Duration::Unit_h ) == 24.0 );
QVERIFY( e1.variance() == 1.0 );
QVERIFY( e1.variance( Duration::Unit_h ) == 24.0*24.0 );
qint64 day = 1000*60*60*24;
e1.setRisktype( Estimate::Risk_None );
QVERIFY( e1.pertExpected().milliseconds() == 4 * day );
e1.setRisktype( Estimate::Risk_Low );
QVERIFY( e1.pertExpected().milliseconds() == ((2 + 8 + (4*4))*day)/6 );
e1.setRisktype( Estimate::Risk_High );
QVERIFY( e1.pertExpected().milliseconds() == ((2 + 16 + (4*4))*day)/7 );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::EstimateTester )
diff --git a/src/libs/kernel/tests/PerformanceTester.cpp b/src/libs/kernel/tests/PerformanceTester.cpp
index 12404cc3..47795efc 100644
--- a/src/libs/kernel/tests/PerformanceTester.cpp
+++ b/src/libs/kernel/tests/PerformanceTester.cpp
@@ -1,1123 +1,1124 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
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 "PerformanceTester.h"
#include "kpttask.h"
#include "kptduration.h"
#include "kpteffortcostmap.h"
#include "kptcommand.h"
#include "debug.cpp"
namespace KPlato
{
void PerformanceTester::cleanup()
{
delete p1;
p1 = 0;
r1 = 0;
r2 = 0;
s1 = 0;
t1 = 0;
s2 = 0;
m1 = 0;
}
void PerformanceTester::init()
{
p1 = new Project();
p1->setId( p1->uniqueNodeId() );
p1->registerNodeId( p1 );
p1->setName( "PerformanceTester" );
p1->setConstraintStartTime( DateTime( QDateTime::fromString( "2010-11-19T08:00:00", Qt::ISODate ) ) );
p1->setConstraintEndTime( DateTime( QDateTime::fromString( "2010-11-29T16:00:00", Qt::ISODate ) ) );
Calendar *c = new Calendar();
c->setDefault( true );
QTime ta( 8, 0, 0 );
QTime tb ( 16, 0, 0 );
int length = ta.msecsTo( tb );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = c->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( ta, length );
}
p1->addCalendar( c );
s1 = p1->createTask();
s1->setName( "S1" );
p1->addTask( s1, p1 );
t1 = p1->createTask();
t1->setName( "T1" );
t1->estimate()->setUnit( Duration::Unit_d );
t1->estimate()->setExpectedEstimate( 5.0 );
t1->estimate()->setType( Estimate::Type_Effort );
p1->addSubTask( t1, s1 );
s2 = p1->createTask();
s2->setName( "S2" );
p1->addTask( s2, p1 );
m1 = p1->createTask();
m1->estimate()->setExpectedEstimate( 0 );
m1->setName( "M1" );
p1->addSubTask( m1, s2 );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
p1->addResourceGroup( g );
r1 = new Resource();
r1->setName( "R1" );
r1->setNormalRate( 1.0 );
p1->addResource( g, r1 );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t1->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( rr );
// material resource
ResourceGroup *m = new ResourceGroup();
m->setName( "M1" );
m->setType( ResourceGroup::Type_Material );
p1->addResourceGroup( m );
r2 = new Resource();
r2->setName( "Material" );
r2->setType( Resource::Type_Material );
r2->setCalendar( c ); // limit availability to working hours
r2->setNormalRate( 0.0 ); // NOTE
p1->addResource( m, r2 );
r3 = new Resource();
r3->setName( "Material 2" );
r3->setType( Resource::Type_Material );
r3->setNormalRate( 6.0 );
p1->addResource( m, r3 );
gr = new ResourceGroupRequest( m );
t1->addRequest( gr );
rr = new ResourceRequest( r2, 100 );
gr->addResourceRequest( rr );
ScheduleManager *sm = p1->createScheduleManager( "S1" );
p1->addScheduleManager( sm );
sm->createSchedules();
p1->calculate( *sm );
t1->completion().setStarted( true );
t1->completion().setStartTime( t1->startTime() );
Debug::print( p1, "Project data", true );
QCOMPARE( t1->startTime(), p1->mustStartOn() );
QCOMPARE( t1->endTime(), t1->startTime() + Duration( 4, 8, 0 ) );
}
void PerformanceTester::bcwsPrDayTask()
{
QDate d = t1->startTime().date().addDays( -1 );
EffortCostMap ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 0, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
qDebug()<<ecm;
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) ); // work+material resource
QCOMPARE( ecm.costOnDate( d ), 8.0 ); //material resource cost == 0
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 0, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
// add startup cost
t1->setStartupCost( 0.5 );
QCOMPARE( t1->startupCost(), 0.5 );
d = t1->startTime().date();
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.5 );
// add shutdown cost
t1->setShutdownCost( 0.5 );
QCOMPARE( t1->shutdownCost(), 0.5 );
d = t1->endTime().date();
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.5 );
// check sub-task
d = t1->startTime().date();
ecm = s1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.5 );
d = s1->endTime().date();
ecm = s1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.5 );
}
void PerformanceTester::bcwpPrDayTask()
{
QDate d = t1->startTime().date();
EffortCostMap ecm = t1->bcwpPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.0 );
ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 10 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 20 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 30 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 12.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 40 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 32.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 16.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 50 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 40.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 20.0 );
// modify last day
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 100 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 );
// re-check
d = t1->startTime().date();
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 8.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 12.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 32.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 16.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 );
// add startup cost
t1->completion().setStartTime( t1->startTime() );
t1->setStartupCost( 0.5 );
d = t1->startTime().date();
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.5 ); //10% progress
// add shutdown cost
t1->setShutdownCost( 0.5 );
t1->completion().setFinished( true );
t1->completion().setFinishTime( t1->endTime() );
d = t1->endTime().date();
ecm = t1->bcwpPrDay();
Debug::print( t1->completion(), t1->name(), "BCWP Performance with shutdown cost" );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.5 ); //100% progress
// check sub-task
d = s1->startTime().date();
ecm = s1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.5 ); //10% progress
// add shutdown cost
d = s1->endTime().date();
ecm = s1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.5 ); //100% progress
}
void PerformanceTester::acwpPrDayTask()
{
NamedCommand *cmd = new ModifyCompletionEntrymodeCmd( t1->completion(), Completion::EnterEffortPerResource );
cmd->execute(); delete cmd;
Completion::UsedEffort *ue = new Completion::UsedEffort();
t1->completion().addUsedEffort( r1, ue );
QDate d = t1->startTime().date();
EffortCostMap ecb = t1->bcwpPrDay();
EffortCostMap eca = t1->acwp();
QCOMPARE( ecb.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecb.costOnDate( d ), 8.0 );
QCOMPARE( ecb.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 0.0 );
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.0 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 10 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
// Debug::print( t1->completion(), t1->name(), QString( "ACWP on date: %1" ).arg( d.toString( Qt::ISODate ) ) );
// Debug::print( eca, "ACWP" );
QCOMPARE( ecb.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 4.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 20 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 6, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 8.0 );
QCOMPARE( eca.hoursOnDate( d ), 6.0 );
QCOMPARE( eca.costOnDate( d ), 6.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 30 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 12.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 50 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 40.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 20.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 80 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 12, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 64.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 32.0 );
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.0 );
// add startup cost
t1->completion().setStartTime( t1->startTime() );
t1->setStartupCost( 0.5 );
d = t1->startTime().date();
eca = t1->acwp();
Debug::print( t1->completion(), t1->name(), "ACWP Performance with startup cost" );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.5 );
// add shutdown cost
d = t1->endTime().date();
t1->setShutdownCost( 0.25 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 100 );
cmd->execute(); delete cmd;
t1->completion().setFinished( true );
t1->completion().setFinishTime( t1->endTime() );
eca = t1->acwp();
Debug::print( t1->completion(), t1->name(), "ACWP Performance with shutdown cost" );
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.25 );
// check sub-task
d = s1->startTime().date();
eca = s1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.5 );
// add shutdown cost
d = s1->endTime().date();
eca = s1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.25 );
}
void PerformanceTester::bcwsMilestone()
{
QDate d = m1->startTime().date().addDays( -1 );
EffortCostMap ecm = m1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration::zeroDuration );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
d = d.addDays( 1 );
ecm = m1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration::zeroDuration );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
// add startup cost
m1->setStartupCost( 0.5 );
QCOMPARE( m1->startupCost(), 0.5 );
d = m1->startTime().date();
ecm = m1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration::zeroDuration );
QCOMPARE( ecm.costOnDate( d ), 0.5 );
// add shutdown cost
m1->setShutdownCost( 0.25 );
QCOMPARE( m1->shutdownCost(), 0.25 );
d = m1->endTime().date();
ecm = m1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration::zeroDuration );
QCOMPARE( ecm.costOnDate( d ), 0.75 );
}
void PerformanceTester::bcwpMilestone()
{
QDate d = m1->startTime().date();
EffortCostMap ecm = m1->bcwpPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration::zeroDuration );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.0 );
ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd( m1->completion(), d, 100 );
cmd->execute(); delete cmd;
ecm = m1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.0 );
// add startup cost
d = m1->startTime().date();
m1->completion().setStarted( true );
m1->completion().setStartTime( m1->startTime() );
m1->setStartupCost( 0.5 );
ecm = m1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.5 );
// add shutdown cost
d = m1->endTime().date();
m1->setShutdownCost( 0.25 );
m1->completion().setFinished( true );
m1->completion().setFinishTime( m1->endTime() );
ecm = m1->bcwpPrDay();
Debug::print( m1->completion(), m1->name(), "BCWP Milestone with shutdown cost" );
Debug::print( ecm, "BCWP Milestone" );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.75 );
// check sub-milestone
d = s2->startTime().date();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.75 );
}
void PerformanceTester::acwpMilestone()
{
QDate d = m1->startTime().date();
EffortCostMap eca = m1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.0 );
// add startup cost
d = m1->startTime().date();
m1->completion().setStarted( true );
m1->completion().setStartTime( m1->startTime() );
m1->setStartupCost( 0.5 );
eca = m1->acwp();
Debug::print( m1->completion(), m1->name(), "ACWP Milestone with startup cost" );
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.5 );
// add shutdown cost
d = m1->endTime().date();
m1->setShutdownCost( 0.25 );
NamedCommand *cmd = new ModifyCompletionPercentFinishedCmd( m1->completion(), d, 100 );
cmd->execute(); delete cmd;
m1->completion().setFinished( true );
m1->completion().setFinishTime( m1->endTime() );
eca = m1->acwp();
Debug::print( m1->completion(), m1->name(), "ACWP Milestone with shutdown cost" );
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.75 );
// check sub-milestone
d = s2->endTime().date();
eca = s2->acwp();
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.75 );
}
void PerformanceTester::bcwsPrDayTaskMaterial()
{
r2->setNormalRate( 0.1 );
QDate d = t1->startTime().date().addDays( -1 );
EffortCostMap ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 0, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) ); //material+work resource
QCOMPARE( ecm.costOnDate( d ), 8.8 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.8 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.8 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.8 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.8 );
d = d.addDays( 1 );
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 0, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
// add startup cost
t1->setStartupCost( 0.5 );
QCOMPARE( t1->startupCost(), 0.5 );
d = t1->startTime().date();
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.5 + 0.8 );
// add shutdown cost
t1->setShutdownCost( 0.25 );
QCOMPARE( t1->shutdownCost(), 0.25 );
d = t1->endTime().date();
ecm = t1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.25 + 0.8 );
// check sub-task
d = s1->startTime().date();
ecm = s1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.5 + 0.8 );
d = s1->endTime().date();
ecm = s1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.25 + 0.8 );
}
void PerformanceTester::bcwpPrDayTaskMaterial()
{
r2->setNormalRate( 0.1 );
QDate d = t1->startTime().date();
EffortCostMap ecm = t1->bcwpPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.8 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.0 );
ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 10 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.4 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 20 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 8.0 + 0.8 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 30 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 12.0 + 1.2 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 40 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 32.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 16.0 + 1.6 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 50 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 40.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 20.0 + 2.0 );
// modify last day
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 100 );
cmd->execute();
delete cmd;
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 4.0 );
// add startup cost
t1->completion().setStartTime( t1->startTime() );
t1->setStartupCost( 0.5 );
d = t1->startTime().date();
ecm = t1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.5 + 0.4 ); // 10% progress
// add shutdown cost
t1->setShutdownCost( 0.25 );
t1->completion().setFinished( true );
t1->completion().setFinishTime( t1->endTime() );
d = t1->endTime().date();
ecm = t1->bcwpPrDay();
Debug::print( t1->completion(), t1->name(), "BCWP Material with shutdown cost" );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.25 + 4.0 ); // 100% progress
// check sub-task
d = s1->startTime().date();
ecm = s1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.5 + 0.4 ); // 10% progress
d = s1->endTime().date();
ecm = s1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.25 + 4.0 ); // 100% progress
}
void PerformanceTester::acwpPrDayTaskMaterial()
{
r2->setNormalRate( 0.1 );
NamedCommand *cmd = new ModifyCompletionEntrymodeCmd( t1->completion(), Completion::EnterEffortPerResource );
cmd->execute(); delete cmd;
Completion::UsedEffort *ue = new Completion::UsedEffort();
t1->completion().addUsedEffort( r1, ue );
QDate d = t1->startTime().date();
EffortCostMap ecb = t1->bcwpPrDay();
EffortCostMap eca = t1->acwp();
QCOMPARE( ecb.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecb.costOnDate( d ), 8.0 + 0.8 );
QCOMPARE( ecb.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 0.0 );
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.0 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 10 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
// Debug::print( t1->completion(), t1->name(), QString( "ACWP on date: %1" ).arg( d.toString( Qt::ISODate ) ) );
// Debug::print( eca, "ACWP" );
QCOMPARE( ecb.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 4.0 + 0.4 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 20 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 6, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 8.0 + 0.8 );
QCOMPARE( eca.hoursOnDate( d ), 6.0 );
QCOMPARE( eca.costOnDate( d ), 6.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 30 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 12.0 + 1.2 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 50 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 40.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 20.0 + 2.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 80 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 12, 0 ) ) );
cmd->execute(); delete cmd;
ecb = t1->bcwpPrDay();
eca = t1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 64.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 32.0 + 3.2 );
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.0 );
// add startup cost
t1->completion().setStartTime( t1->startTime() );
t1->setStartupCost( 0.5 );
d = t1->startTime().date();
eca = t1->acwp();
Debug::print( t1->completion(), t1->name(), "ACWP Performance with startup cost" );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 + 0.5 ); //NOTE: material not included
// add shutdown cost
d = t1->endTime().date();
t1->setShutdownCost( 0.25 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 100 );
cmd->execute(); delete cmd;
t1->completion().setFinished( true );
t1->completion().setFinishTime( t1->endTime() );
eca = t1->acwp();
Debug::print( t1->completion(), t1->name(), "ACWP Performance with shutdown cost" );
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.0 + 0.25 ); //NOTE: material not included
// check sub-task
d = s1->startTime().date();
eca = s1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 + 0.5 ); //NOTE: material not included
// add shutdown cost
d = s1->endTime().date();
eca = s1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.0 + 0.25 ); //NOTE: material not included
}
void PerformanceTester::bcwsPrDayProject()
{
QDate d = p1->startTime().date().addDays( -1 );
EffortCostMap ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 0, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
d = d.addDays( 1 );
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 0, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 0.0 );
// add startup cost to task
t1->setStartupCost( 0.5 );
QCOMPARE( t1->startupCost(), 0.5 );
d = p1->startTime().date();
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.5 );
// add shutdown cost to task
t1->setShutdownCost( 0.2 );
QCOMPARE( t1->shutdownCost(), 0.2 );
d = p1->endTime().date();
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.2 );
// add startup cost to milestone
m1->setStartupCost( 0.4 );
QCOMPARE( m1->startupCost(), 0.4 );
d = p1->startTime().date();
ecm = p1->bcwsPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.5 + 0.4 );
// add shutdown cost to milestone
m1->setShutdownCost( 0.3 );
QCOMPARE( m1->shutdownCost(), 0.3 );
d = p1->startTime().date();
ecm = p1->bcwsPrDay();
//Debug::print( p1, "BCWS Project", true );
//Debug::print( ecm, "BCWS Project" );
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.5 + 0.4 + 0.3 );
}
void PerformanceTester::bcwpPrDayProject()
{
QDate d = p1->startTime().date();
EffortCostMap ecm = p1->bcwpPrDay();
QCOMPARE( ecm.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecm.costOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.0 );
ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 10 );
cmd->execute(); delete cmd;
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 20 );
cmd->execute(); delete cmd;
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 30 );
cmd->execute(); delete cmd;
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 12.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 40 );
cmd->execute(); delete cmd;
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 32.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 16.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 50 );
cmd->execute(); delete cmd;
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 40.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 20.0 );
// modify last day
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 100 );
cmd->execute(); delete cmd;
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 );
// re-check
d = p1->startTime().date();
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 8.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 12.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 32.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 16.0 );
d = d.addDays( 1 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 );
// add startup cost to task
t1->completion().setStartTime( t1->startTime() );
t1->setStartupCost( 0.5 );
d = p1->startTime().date();
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.5 ); // 10% progress
// add shutdown cost to task
t1->setShutdownCost( 0.25 );
t1->completion().setFinished( true );
t1->completion().setFinishTime( t1->endTime() );
d = p1->endTime().date();
ecm = p1->bcwpPrDay();
QCOMPARE( ecm.bcwpEffortOnDate( d ), 80.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.25 ); // 100% progress
// check with ECCT_EffortWork
d = p1->startTime().date();
ecm = p1->bcwpPrDay( CURRENTSCHEDULE, ECCT_EffortWork );
QCOMPARE( ecm.totalEffort(), Duration( 40.0, Duration::Unit_h ) );
QCOMPARE( ecm.hoursOnDate( d ), 8.0 ); // hours from r1
QCOMPARE( ecm.costOnDate( d ), 8.0 + 0.5 ); // cost from r1 (1.0) + r2 (0.0) + startup cost (0.5)
QCOMPARE( ecm.bcwpEffortOnDate( d ), 4.0 ); // 10% progress
QCOMPARE( ecm.bcwpCostOnDate( d ), 4.0 + 0.5 ); // 10% progress
d = p1->endTime().date();
ecm = p1->bcwpPrDay( CURRENTSCHEDULE, ECCT_EffortWork );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 40.0 ); // hours from r1
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.25 ); // 100% progress
// add a new task with a material resource
Task *tt = p1->createTask();
tt->setName( "TT" );
p1->addTask( tt, p1 );
tt->estimate()->setUnit( Duration::Unit_d );
tt->estimate()->setExpectedEstimate( 5.0 );
tt->estimate()->setType( Estimate::Type_Duration );
tt->estimate()->setCalendar( p1->calendarAt( 0 ) );
r3->setNormalRate( 1.0 );
ResourceGroupRequest *gr = new ResourceGroupRequest( r3->parentGroup() );
tt->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r3, 100 );
gr->addResourceRequest( rr );
ScheduleManager *sm = p1->createScheduleManager( "" );
p1->addScheduleManager( sm );
sm->createSchedules();
p1->calculate( *sm );
QString s = " Material resource, no progress ";
Debug::print( tt, s, true );
d = tt->endTime().date();
ecm = tt->bcwpPrDay( sm->scheduleId(), ECCT_EffortWork );
Debug::print( ecm, "BCWP: " + tt->name() + s );
QCOMPARE( ecm.hoursOnDate( d ), 0.0 );
QCOMPARE( ecm.costOnDate( d ), 16.0 );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecm.bcwpCostOnDate( d ), 0.0 ); // 0% progress
d = p1->endTime().date();
ecm = p1->bcwpPrDay( sm->scheduleId(), ECCT_EffortWork );
Debug::print( p1, s, true );
Debug::print( ecm, "BCWP Project: " + p1->name() + s );
QCOMPARE( ecm.bcwpEffortOnDate( d ), 40.0 ); // hours from r1
QCOMPARE( ecm.bcwpCostOnDate( d ), 40.0 + 0.5 + 0.25 );
}
void PerformanceTester::acwpPrDayProject()
{
NamedCommand *cmd = new ModifyCompletionEntrymodeCmd( t1->completion(), Completion::EnterEffortPerResource );
cmd->execute(); delete cmd;
Completion::UsedEffort *ue = new Completion::UsedEffort();
t1->completion().addUsedEffort( r1, ue );
QDate d = p1->startTime().date();
EffortCostMap ecb = p1->bcwpPrDay();
EffortCostMap eca = p1->acwp();
QCOMPARE( ecb.effortOnDate( d ), Duration( 0, 16, 0 ) );
QCOMPARE( ecb.costOnDate( d ), 8.0 );
QCOMPARE( ecb.bcwpEffortOnDate( d ), 0.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 0.0 );
QCOMPARE( eca.hoursOnDate( d ), 0.0 );
QCOMPARE( eca.costOnDate( d ), 0.0 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 10 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = p1->bcwpPrDay();
eca = p1->acwp();
// Debug::print( eca, "ACWP Project" );
QCOMPARE( ecb.bcwpEffortOnDate( d ), 8.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 4.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 20 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 6, 0 ) ) );
cmd->execute(); delete cmd;
ecb = p1->bcwpPrDay();
eca = p1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 16.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 8.0 );
QCOMPARE( eca.hoursOnDate( d ), 6.0 );
QCOMPARE( eca.costOnDate( d ), 6.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 30 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = p1->bcwpPrDay();
eca = p1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 24.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 12.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 50 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 8, 0 ) ) );
cmd->execute(); delete cmd;
ecb = p1->bcwpPrDay();
eca = p1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 40.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 20.0 );
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 );
d = d.addDays( 1 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 80 );
cmd->execute(); delete cmd;
cmd = new AddCompletionActualEffortCmd( *ue, d, Completion::UsedEffort::ActualEffort( Duration( 0, 12, 0 ) ) );
cmd->execute(); delete cmd;
ecb = p1->bcwpPrDay();
eca = p1->acwp();
QCOMPARE( ecb.bcwpEffortOnDate( d ), 64.0 );
QCOMPARE( ecb.bcwpCostOnDate( d ), 32.0 );
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.0 );
// add startup cost
t1->completion().setStartTime( t1->startTime() );
t1->completion().setStarted( true );
t1->setStartupCost( 0.5 );
d = p1->startTime().date();
eca = p1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 8.0 );
QCOMPARE( eca.costOnDate( d ), 8.0 + 0.5 );
// add shutdown cost
d = t1->endTime().date();
t1->setShutdownCost( 0.25 );
cmd = new ModifyCompletionPercentFinishedCmd( t1->completion(), d, 100 );
cmd->execute(); delete cmd;
t1->completion().setFinished( true );
t1->completion().setFinishTime( t1->endTime() );
d = p1->endTime().date();
eca = p1->acwp();
QCOMPARE( eca.hoursOnDate( d ), 12.0 );
QCOMPARE( eca.costOnDate( d ), 12.25 );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::PerformanceTester )
diff --git a/src/libs/kernel/tests/ProjectTester.cpp b/src/libs/kernel/tests/ProjectTester.cpp
index 451d52ff..350afbe2 100644
--- a/src/libs/kernel/tests/ProjectTester.cpp
+++ b/src/libs/kernel/tests/ProjectTester.cpp
@@ -1,3252 +1,3253 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "ProjectTester.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include <QTest>
#include "debug.cpp"
namespace KPlato
{
void ProjectTester::initTestCase()
{
tz = "TZ=Europe/Copenhagen";
putenv(tz.data());
m_project = new Project();
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId( m_project );
m_project->setConstraintStartTime( QDateTime::fromString( "2012-02-01T00:00", Qt::ISODate ) );
m_project->setConstraintEndTime( QDateTime::fromString( "2013-02-01T00:00", Qt::ISODate ) );
// standard worktime defines 8 hour day as default
QVERIFY( m_project->standardWorktime() );
QCOMPARE( m_project->standardWorktime()->day(), 8.0 );
m_calendar = new Calendar();
m_calendar->setName( "C1" );
m_calendar->setDefault( true );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = m_calendar->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
m_project->addCalendar( m_calendar );
m_task = 0;
}
void ProjectTester::cleanupTestCase()
{
unsetenv("TZ");
delete m_project;
}
void ProjectTester::testAddTask()
{
m_task = m_project->createTask();
QVERIFY( m_project->addTask( m_task, m_project ) );
QVERIFY( m_task->parentNode() == m_project );
QCOMPARE( m_project->findNode( m_task->id() ), m_task );
m_project->takeTask( m_task );
delete m_task; m_task = 0;
}
void ProjectTester::testTakeTask()
{
m_task = m_project->createTask();
m_project->addTask( m_task, m_project );
QCOMPARE( m_project->findNode( m_task->id() ), m_task );
m_project->takeTask( m_task );
QVERIFY( m_project->findNode( m_task->id() ) == 0 );
delete ( m_task ); m_task = 0;
}
void ProjectTester::testTaskAddCmd()
{
m_task = m_project->createTask();
SubtaskAddCmd *cmd = new SubtaskAddCmd( m_project, m_task, m_project );
cmd->execute();
QVERIFY( m_task->parentNode() == m_project );
QCOMPARE( m_project->findNode( m_task->id() ), m_task );
cmd->unexecute();
QVERIFY( m_project->findNode( m_task->id() ) == 0 );
delete cmd;
m_task = 0;
}
void ProjectTester::testTaskDeleteCmd()
{
m_task = m_project->createTask();
QVERIFY( m_project->addTask( m_task, m_project ) );
QVERIFY( m_task->parentNode() == m_project );
NodeDeleteCmd *cmd = new NodeDeleteCmd( m_task );
cmd->execute();
QVERIFY( m_project->findNode( m_task->id() ) == 0 );
cmd->unexecute();
QCOMPARE( m_project->findNode( m_task->id() ), m_task );
cmd->execute();
delete cmd;
m_task = 0;
}
void ProjectTester::schedule()
{
QDate today = QDate::fromString( "2012-02-01", Qt::ISODate );
QDate tomorrow = today.addDays( 1 );
QDate yesterday = today.addDays( -1 );
QDate nextweek = today.addDays( 7 );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
Task *t = m_project->createTask();
t->setName( "T1" );
m_project->addTask( t, m_project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Duration );
QString s = "Calculate forward, Task: Duration -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
qDebug()<<t->name()<<t->id()<<m_project->findNode( t->id() );
qDebug()<<m_project->nodeDict();
Debug::print( m_project, s, true );
ScheduleManager *sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->startTime(), m_project->startTime() );
QCOMPARE( t->endTime(), DateTime(t->startTime().addDays( 1 )) );
s = "Calculate forward, Task: Duration w calendar -------------------------------";
qDebug()<<endl<<"Testing:"<<s;
t->estimate()->setCalendar( m_calendar );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->startTime(), m_calendar->firstAvailableAfter( m_project->startTime(), m_project->endTime() ) );
QCOMPARE( t->endTime(), DateTime( t->startTime().addMSecs( length ) ) );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
m_project->addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setAvailableFrom( QDateTime( yesterday, QTime(), Qt::LocalTime ) );
r->setCalendar( m_calendar );
m_project->addResource( g, r );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
t->estimate()->setType( Estimate::Type_Effort );
s = "Calculate forward, Task: ASAP -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, Task: ASAP, Resource 50% available -----------------";
qDebug()<<endl<<"Testing:"<<s;
r->setUnits( 50 );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 1, 8, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, Task: ASAP, Resource 50% available, Request 50% load ---------";
qDebug()<<endl<<"Testing:"<<s;
r->setUnits( 50 );
rr->setUnits( 50 );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 3, 8, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, Task: ASAP, Resource 200% available, Request 50% load ---------";
qDebug()<<endl<<"Testing:"<<s;
r->setUnits( 200 );
rr->setUnits( 50 );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, Task: ASAP, Resource 200% available, Request 100% load ---------";
qDebug()<<endl<<"Testing:"<<s;
r->setUnits( 200 );
rr->setUnits( 100 );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 4, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, 2 tasks: Resource 200% available, Request 50% load each ---------";
qDebug()<<endl<<"Testing:"<<s;
r->setUnits( 200 );
rr->setUnits( 50 );
Task *task2 = m_project->createTask( *t );
task2->setName( "T2" );
m_project->addTask( task2, t );
ResourceGroupRequest *gr2 = new ResourceGroupRequest( g );
task2->addRequest( gr2 );
ResourceRequest *rr2 = new ResourceRequest( r, 50 );
gr2->addResourceRequest( rr2 );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QCOMPARE( task2->startTime(), DateTime( today, t1 ) );
QCOMPARE( task2->endTime(), task2->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( task2->plannedEffort().toHours(), 8.0 );
QVERIFY( task2->schedulingError() == false );
m_project->takeTask( task2 );
delete task2;
s = "Calculate forward, Task: ASAP, Resource available tomorrow --------";
qDebug()<<endl<<"Testing:"<<s;
r->setAvailableFrom( QDateTime( tomorrow, QTime(), Qt::LocalTime ) );
qDebug()<<"Tomorrow:"<<QDateTime( tomorrow, QTime(), Qt::LocalTime )<<r->availableFrom();
r->setUnits( 100 );
rr->setUnits( 100 );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
// Debug::print( m_project, t, s);
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( r->availableFrom().date(), t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, Task: ALAP -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime(0,0,0) ) );
t->setConstraint( Node::ALAP );
r->setAvailableFrom( QDateTime( yesterday, QTime(), Qt::LocalTime ) );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->startTime() ) );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->endTime() );
QVERIFY( t->lateFinish() >= t->endTime() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
s = "Calculate forward, Task: MustStartOn -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
r->setAvailableFrom( QDateTime( yesterday, QTime(), Qt::LocalTime ) );
t->setConstraint( Node::MustStartOn );
t->setConstraintStartTime( DateTime( nextweek, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->startTime(), DateTime( t->constraintStartTime().date(), t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate backwards
s = "Calculate backward, Task: MustStartOn -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( nextweek.addDays( 1 ), QTime() ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), DateTime( t->constraintStartTime().date(), t1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate bacwords
s = "Calculate backwards, Task: MustFinishOn -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime(0,0,0) ) );
m_project->setConstraintEndTime( DateTime( nextweek.addDays( 1 ), QTime() ) );
t->setConstraint( Node::MustFinishOn );
t->setConstraintEndTime( DateTime( nextweek.addDays( -2 ), t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: MustFinishOn -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::MustFinishOn );
t->setConstraintEndTime( DateTime( tomorrow, t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: StartNotEarlier -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::StartNotEarlier );
t->setConstraintStartTime( DateTime( today, t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), DateTime( tomorrow, t1 ));
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate backward
s = "Calculate backwards, Task: StartNotEarlier -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( nextweek, QTime() ) );
t->setConstraint( Node::StartNotEarlier );
t->setConstraintStartTime( DateTime( today, t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, t, s );
Debug::print( m_project->resourceList().first(), s );
// Debug::printSchedulingLog( *sm, s );
QVERIFY( t->lateStart() >= t->constraintStartTime() );
QCOMPARE( t->earlyFinish(), t->endTime() );
QVERIFY( t->lateFinish() <= m_project->constraintEndTime() );
QVERIFY( t->endTime() <= t->lateFinish() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: FinishNotLater -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::FinishNotLater );
t->setConstraintEndTime( DateTime( tomorrow.addDays( 1 ), t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QVERIFY( t->startTime() >= t->earlyStart() );
QVERIFY( t->startTime() <= t->lateStart() );
QVERIFY( t->startTime() >= m_project->startTime() );
QVERIFY( t->endTime() >= t->earlyFinish() );
QVERIFY( t->endTime() <= t->lateFinish() );
QVERIFY( t->endTime() <= m_project->endTime() );
QVERIFY( t->earlyFinish() <= t->constraintEndTime() );
QVERIFY( t->lateFinish() <= m_project->constraintEndTime() );
QVERIFY( t->schedulingError() == false );
// Calculate backward
s = "Calculate backwards, Task: FinishNotLater -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( nextweek, QTime() ) );
t->setConstraint( Node::FinishNotLater );
t->setConstraintEndTime( DateTime( tomorrow, t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
//Debug::print( m_project, t, s );
QCOMPARE( t->earlyStart(), m_project->startTime() );
QCOMPARE( t->lateStart(), t->startTime() );
QCOMPARE( t->earlyFinish(), t->constraintEndTime() );
QVERIFY( t->lateFinish() <= m_project->constraintEndTime() );
QCOMPARE( t->startTime(), m_project->startTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forward, Task: FixedInterval -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::FixedInterval );
t->setConstraintStartTime( DateTime( tomorrow, t1 ) );
t->setConstraintEndTime( DateTime( tomorrow, t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
//Debug::print( m_project, t, s );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->constraintStartTime() );
QCOMPARE( t->earlyFinish(), t->constraintEndTime() );
QCOMPARE( t->lateFinish(), t->constraintEndTime() );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: FixedInterval -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::FixedInterval );
t->setConstraintStartTime( DateTime( tomorrow, QTime() ) ); // outside working hours
t->setConstraintEndTime( DateTime( tomorrow, t2 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->constraintStartTime() );
QCOMPARE( t->earlyFinish(), t->constraintEndTime() );
QCOMPARE( t->lateFinish(), t->constraintEndTime() );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: Milestone, ASAP-------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::ASAP );
t->estimate()->clear();
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
//Debug::print( m_project, t, s );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->earlyStart() );
QCOMPARE( t->earlyFinish(), t->earlyStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->earlyStart() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
// Calculate backward
s = "Calculate backwards, Task: Milestone, ASAP-------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( today, QTime() ) );
t->setConstraint( Node::ASAP );
t->estimate()->clear();
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->earlyStart() );
QCOMPARE( t->earlyFinish(), t->earlyStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->earlyStart() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: Milestone, ALAP-------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::ALAP );
t->estimate()->clear();
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->earlyStart() );
QCOMPARE( t->earlyFinish(), t->earlyStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->earlyStart() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
// Calculate backward
s = "Calculate backwards, Task: Milestone, ALAP-------------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( today, QTime() ) );
t->setConstraint( Node::ALAP );
t->estimate()->clear();
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->earlyStart() );
QCOMPARE( t->earlyFinish(), t->earlyStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->earlyStart() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: Milestone, MustStartOn ------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::MustStartOn );
t->setConstraintStartTime( DateTime( tomorrow, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->constraintStartTime() );
QCOMPARE( t->earlyFinish(), t->lateStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
// Calculate backward
s = "Calculate backwards, Task: Milestone, MustStartOn ------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( tomorrow, QTime() ) );
t->setConstraint( Node::MustStartOn );
t->setConstraintStartTime( DateTime( today, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), t->constraintStartTime() );
QCOMPARE( t->lateStart(), t->earlyStart() );
QCOMPARE( t->earlyFinish(), t->earlyStart() );
QCOMPARE( t->lateFinish(), m_project->constraintEndTime() );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
// Calculate forward
s = "Calculate forwards, Task: Milestone, MustFinishOn ------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::MustFinishOn );
t->setConstraintEndTime( DateTime( tomorrow, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->constraintEndTime() );
QCOMPARE( t->earlyFinish(), t->lateStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( m_project->endTime(), t->endTime() );
// Calculate backward
s = "Calculate backwards, Task: Milestone, MustFinishOn ------------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( tomorrow, QTime() ) );
t->setConstraint( Node::MustFinishOn );
t->setConstraintEndTime( DateTime( today, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), t->constraintEndTime() );
QCOMPARE( t->lateStart(), t->constraintEndTime() );
QCOMPARE( t->earlyFinish(), t->lateStart() );
QCOMPARE( t->lateFinish(), m_project->constraintEndTime() );
QCOMPARE( t->startTime(), t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( m_project->startTime(), t->startTime() );
// Calculate forward
s = "Calculate forwards, Task: Milestone, StartNotEarlier ---------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::StartNotEarlier );
t->setConstraintEndTime( DateTime( tomorrow, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), m_project->constraintStartTime() );
QCOMPARE( t->lateStart(), t->constraintStartTime() );
QCOMPARE( t->earlyFinish(), t->lateStart() );
QCOMPARE( t->lateFinish(), t->earlyFinish() );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( m_project->endTime(), t->endTime() );
// Calculate backward
s = "Calculate backwards, Task: Milestone, StartNotEarlier ---------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( tomorrow, QTime() ) );
t->setConstraint( Node::StartNotEarlier );
t->setConstraintStartTime( DateTime( today, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, s, true );
QVERIFY( t->earlyStart() >= t->constraintStartTime() );
QVERIFY( t->lateStart() >= t->earlyStart() );
QVERIFY( t->earlyFinish() <= t->lateFinish() );
QVERIFY( t->lateFinish() >= t->constraintStartTime() );
QVERIFY( t->startTime() >= t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( m_project->startTime(), t->startTime() );
// Calculate forward
s = "Calculate forwards, Task: Milestone, FinishNotLater ---------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::FinishNotLater );
t->setConstraintEndTime( DateTime( tomorrow, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QVERIFY( t->earlyStart() <= t->constraintEndTime() );
QVERIFY( t->lateStart() <= t->constraintEndTime() );
QVERIFY( t->earlyFinish() >= t->earlyStart() );
QVERIFY( t->lateFinish() >= t->earlyFinish() );
QVERIFY( t->startTime() <= t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( m_project->endTime(), t->endTime() );
// Calculate backward
s = "Calculate backwards, Task: Milestone, FinishNotLater ---------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintEndTime( DateTime( tomorrow, QTime() ) );
t->setConstraint( Node::FinishNotLater );
t->setConstraintEndTime( DateTime( today, t1 ) );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setSchedulingDirection( true );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
QCOMPARE( t->earlyStart(), t->constraintStartTime() );
QCOMPARE( t->lateStart(), t->earlyStart() );
QCOMPARE( t->earlyFinish(), t->lateStart() );
QCOMPARE( t->lateFinish(), m_project->constraintEndTime() );
QCOMPARE( t->startTime(), t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( m_project->startTime(), t->startTime() );
// Calculate forward
s = "Calculate forward, 2 Tasks, no overbooking ----------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
m_project->setConstraintEndTime( DateTime( today, QTime() ).addDays( 4 ) );
t->setConstraint( Node::ASAP );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 2.0 );
Task *tsk2 = m_project->createTask( *t );
tsk2->setName( "T2" );
m_project->addTask( tsk2, m_project );
gr = new ResourceGroupRequest( g );
tsk2->addRequest( gr );
rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setAllowOverbooking( false );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
Debug::print( m_project, t, s );
Debug::print( m_project, tsk2, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->constraintStartTime() ) );
QCOMPARE( t->lateStart(), tsk2->startTime() );
QCOMPARE( t->earlyFinish(), DateTime( tomorrow, t2 ) );
QCOMPARE( t->lateFinish(), t->lateFinish() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->earlyFinish() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( tsk2->earlyStart(), t->earlyStart() );
QCOMPARE( tsk2->lateStart(), t->earlyFinish() + Duration( 0, 16, 0 ) );
QCOMPARE( tsk2->earlyFinish(), DateTime( tomorrow, t2 ) );
QCOMPARE( tsk2->lateFinish(), t->lateFinish() );
QCOMPARE( tsk2->startTime(), DateTime( tomorrow.addDays( 1 ), t1 ) );
QCOMPARE( tsk2->endTime(), tsk2->lateFinish() );
QVERIFY( tsk2->schedulingError() == false );
QCOMPARE( m_project->endTime(), tsk2->endTime() );
// Calculate forward
s = "Calculate forward, 2 Tasks, relation ---------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( DateTime( today, QTime() ) );
t->setConstraint( Node::ASAP );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 2.0 );
Relation *rel = new Relation( t, tsk2 );
bool relationAdded = m_project->addRelation( rel );
QVERIFY( relationAdded );
sm = m_project->createScheduleManager( "Test Plan" );
sm->setAllowOverbooking( true );
sm->setSchedulingDirection( false );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
// Debug::print( m_project, t, s );
// Debug::print( m_project, tsk2, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->earlyStart(), t->requests().workTimeAfter( m_project->constraintStartTime() ) );
QCOMPARE( t->lateStart(), DateTime( today, t1 ) );
QCOMPARE( t->earlyFinish(), DateTime( tomorrow, t2 ) );
QCOMPARE( t->lateFinish(), t->lateFinish() );
QCOMPARE( t->startTime(), DateTime( today, t1 ) );
QCOMPARE( t->endTime(), t->earlyFinish() );
QVERIFY( t->schedulingError() == false );
QCOMPARE( tsk2->earlyStart(), tsk2->requests().workTimeAfter( t->earlyFinish() ) );
QCOMPARE( tsk2->lateStart(), DateTime( tomorrow.addDays( 1 ), t1 ) );
QCOMPARE( tsk2->earlyFinish(), DateTime( tomorrow.addDays( 2 ), t2 ) );
QCOMPARE( tsk2->lateFinish(), tsk2->earlyFinish() );
QCOMPARE( tsk2->startTime(), DateTime( tomorrow.addDays( 1 ), t1 ) );
QCOMPARE( tsk2->endTime(), tsk2->earlyFinish() );
QVERIFY( tsk2->schedulingError() == false );
QCOMPARE( m_project->endTime(), tsk2->endTime() );
}
void ProjectTester::scheduleFullday()
{
QString s = "Full day, 1 resource works 24 hours a day -------------";
qDebug()<<endl<<"Testing:"<<s;
m_project->setConstraintStartTime( QDateTime::fromString( "2011-09-01T00:00:00", Qt::ISODate) );
m_project->setConstraintEndTime( QDateTime::fromString( "2011-09-16T00:00:00", Qt::ISODate) );
qDebug()<<m_project->constraintStartTime()<<m_project->constraintEndTime();
Calendar *c = new Calendar("Test");
QTime t1(0,0,0);
int length = 24*60*60*1000;
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
m_project->addCalendar( c );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
m_project->addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setCalendar( c );
r->setAvailableFrom( m_project->constraintStartTime() );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
m_project->addResource( g, r );
Task *t = m_project->createTask();
t->setName( "T1" );
t->setId( m_project->uniqueNodeId() );
m_project->addTask( t, m_project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 3 * 14.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
t->addRequest( gr );
ScheduleManager *sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
// Debug::print( c, s );
// Debug::print( m_project, t, s );
// Debug::print( r, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), m_project->startTime() );
QCOMPARE( t->endTime(), DateTime( t->startTime().addDays( 14 ) ) );
s = "Full day, 8 hour shifts, 3 resources ---------------";
qDebug()<<endl<<"Testing:"<<s;
int hour = 60*60*1000;
Calendar *c1 = new Calendar("Test 1");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c1->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 6, 0, 0 ), 8*hour));
}
m_project->addCalendar( c1 );
Calendar *c2 = new Calendar("Test 2");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c2->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 14, 0, 0 ), 8*hour));
}
m_project->addCalendar( c2 );
Calendar *c3 = new Calendar("Test 3");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c3->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 0, 0, 0 ), 6*hour));
wd1->addInterval(TimeInterval(QTime( 22, 0, 0 ), 2*hour));
}
m_project->addCalendar( c3 );
r->setCalendar( c1 );
r = new Resource();
r->setName( "R2" );
r->setCalendar( c2 );
r->setAvailableFrom( m_project->constraintStartTime() );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
m_project->addResource( g, r );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
r = new Resource();
r->setName( "R3" );
r->setCalendar( c3 );
r->setAvailableFrom( m_project->constraintStartTime() );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
m_project->addResource( g, r );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
sm->createSchedules();
m_project->calculate( *sm );
// Debug::print( m_project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), m_project->startTime() );
QCOMPARE( t->endTime(), DateTime(t->startTime().addDays( 14 )) );
}
void ProjectTester::scheduleFulldayDstSpring()
{
QString s = "Daylight saving time - Spring, 1 resource works 24 hours a day -------------";
qDebug()<<endl<<"Testing:"<<s;
Project project;
project.setName( "DST" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
project.setConstraintStartTime( DateTime( QDate::fromString( "2011-03-25", Qt::ISODate) ) );
project.setConstraintEndTime( DateTime( QDate::fromString( "2011-03-29", Qt::ISODate) ) );
qDebug()<<project.constraintStartTime()<<project.constraintEndTime();
Calendar *c = new Calendar("Test");
QTime t1(0,0,0);
int length = 24*60*60*1000;
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setCalendar( c );
r->setAvailableFrom( project.constraintStartTime().addDays( -1 ) );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
project.addResource( g, r );
Task *t = project.createTask();
t->setName( "T1" );
t->setId( project.uniqueNodeId() );
project.addTask( t, &project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 3 * 4.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
t->addRequest( gr );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( c, s );
Debug::print( &project, t, s );
// Debug::print( r, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), project.constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 4, 0, 0 ) );
s = "Daylight saving time - Spring, Backward: 1 resource works 24 hours a day -------------";
qDebug()<<endl<<"Testing:"<<s;
// make room for the task
project.setConstraintStartTime( DateTime( QDate::fromString( "2011-03-24", Qt::ISODate) ) );
sm = project.createScheduleManager( "Test Backward" );
project.addScheduleManager( sm );
sm->setSchedulingDirection( true );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( c, s );
Debug::print( &project, t, s );
// Debug::print( r, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->endTime(), project.constraintEndTime() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 4, 0, 0 ) );
s = "Daylight saving time - Spring, 8 hour shifts, 3 resources ---------------";
qDebug()<<endl<<"Testing:"<<s;
int hour = 60*60*1000;
Calendar *c1 = new Calendar("Test 1");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c1->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 6, 0, 0 ), 8*hour));
}
project.addCalendar( c1 );
Calendar *c2 = new Calendar("Test 2");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c2->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 14, 0, 0 ), 8*hour));
}
project.addCalendar( c2 );
Calendar *c3 = new Calendar("Test 3");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c3->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 0, 0, 0 ), 6*hour));
wd1->addInterval(TimeInterval(QTime( 22, 0, 0 ), 2*hour));
}
project.addCalendar( c3 );
r->setCalendar( c1 );
r = new Resource();
r->setName( "R2" );
r->setCalendar( c2 );
r->setAvailableFrom( project.constraintStartTime().addDays( -1 ) );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
project.addResource( g, r );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
r = new Resource();
r->setName( "R3" );
r->setCalendar( c3 );
r->setAvailableFrom( project.constraintStartTime().addDays( -1 ) );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
project.addResource( g, r );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
project.setConstraintStartTime( DateTime( QDate::fromString( "2011-03-25", Qt::ISODate) ) );
sm = project.createScheduleManager( "Test Foreward" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( &project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime().toUTC(), project.constraintStartTime().toUTC() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 4, 0, 0 ) );
s = "Daylight saving time - Spring, Backward: 8 hour shifts, 3 resources ---------------";
qDebug()<<endl<<"Testing:"<<s;
project.setConstraintStartTime( DateTime( QDate::fromString( "2011-03-24", Qt::ISODate) ) );
sm = project.createScheduleManager( "Test Backward" );
project.addScheduleManager( sm );
sm->setSchedulingDirection( true );
sm->createSchedules();
project.calculate( *sm );
Debug::print( &project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->endTime(), project.constraintEndTime() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 4, 0, 0 ) );
}
void ProjectTester::scheduleFulldayDstFall()
{
QString s = "Daylight saving time - Fall, 1 resource works 24 hours a day -------------";
qDebug()<<endl<<"Testing:"<<s;
Project project;
project.setName( "DST" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
project.setConstraintStartTime( QDateTime::fromString( "2011-10-28T00:00:00", Qt::ISODate) );
project.setConstraintEndTime( QDateTime::fromString( "2011-11-01T00:00:00", Qt::ISODate) );
qDebug()<<project.constraintStartTime()<<project.constraintEndTime();
Calendar *c = new Calendar("Test");
QTime t1(0,0,0);
int length = 24*60*60*1000;
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setCalendar( c );
r->setAvailableFrom( project.constraintStartTime().addDays( -1 ) );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
project.addResource( g, r );
Task *t = project.createTask();
t->setName( "T1" );
t->setId( project.uniqueNodeId() );
project.addTask( t, &project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 3 * 4.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
t->addRequest( gr );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( c, s );
Debug::print( &project, t, s );
// Debug::print( r, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), project.constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 4, 0, 0 ) );
s = "Daylight saving time - Fall, Backward: 1 resource works 24 hours a day -------------";
qDebug()<<endl<<"Testing:"<<s;
sm = project.createScheduleManager( "Test Backward" );
project.addScheduleManager( sm );
sm->setSchedulingDirection( true );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( c, s );
Debug::print( &project, t, s );
// Debug::print( r, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->endTime(), project.constraintEndTime() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 4, 0, 0 ) );
s = "Daylight saving time - Fall, 8 hour shifts, 3 resources ---------------";
qDebug()<<endl<<"Testing:"<<s;
int hour = 60*60*1000;
Calendar *c1 = new Calendar("Test 1");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c1->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 6, 0, 0 ), 8*hour));
}
project.addCalendar( c1 );
Calendar *c2 = new Calendar("Test 2");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c2->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 14, 0, 0 ), 8*hour));
}
project.addCalendar( c2 );
Calendar *c3 = new Calendar("Test 3");
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c3->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(QTime( 0, 0, 0 ), 6*hour));
wd1->addInterval(TimeInterval(QTime( 22, 0, 0 ), 2*hour));
}
project.addCalendar( c3 );
r->setCalendar( c1 );
r = new Resource();
r->setName( "R2" );
r->setCalendar( c2 );
r->setAvailableFrom( project.constraintStartTime().addDays( -1 ) );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
project.addResource( g, r );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
r = new Resource();
r->setName( "R3" );
r->setCalendar( c3 );
r->setAvailableFrom( project.constraintStartTime().addDays( -1 ) );
r->setAvailableUntil( r->availableFrom().addDays( 21 ) );
project.addResource( g, r );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
sm = project.createScheduleManager( "Test Foreward" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( &project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), project.constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 4, 0, 0 ) );
s = "Daylight saving time - Fall, Backward: 8 hour shifts, 3 resources ---------------";
qDebug()<<endl<<"Testing:"<<s;
sm = project.createScheduleManager( "Test Foreward" );
project.addScheduleManager( sm );
sm->setSchedulingDirection( true );
sm->createSchedules();
project.calculate( *sm );
QCOMPARE( t->endTime(), project.constraintEndTime() );
QCOMPARE( t->startTime(), t->endTime() - Duration( 4, 0, 0 ) );
}
void ProjectTester::scheduleWithExternalAppointments()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate::fromString( "2012-02-01", Qt::ISODate ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 3 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar c("Test");
QTime t1(0,0,0);
int length = 24*60*60*1000;
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c.weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setCalendar( &c );
project.addResource( g, r );
r->addExternalAppointment( "Ext-1", "External project 1", targetstart, targetstart.addDays( 1 ), 100 );
r->addExternalAppointment( "Ext-1", "External project 1", targetend.addDays( -1 ), targetend, 100 );
Task *t = project.createTask();
t->setName( "T1" );
project.addTask( t, &project );
t->estimate()->setUnit( Duration::Unit_h );
t->estimate()->setExpectedEstimate( 8.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
t->addRequest( gr );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
QString s = "Schedule with external appointments ----------";
qDebug()<<endl<<"Testing:"<<s;
Debug::print( r, s );
Debug::print( &project, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), targetstart + Duration( 1, 0, 0 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
sm->setAllowOverbooking( true );
sm->createSchedules();
project.calculate( *sm );
// Debug::printSchedulingLog( *sm );
QCOMPARE( t->startTime(), targetstart );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
sm->setAllowOverbooking( false );
sm->setSchedulingDirection( true ); // backwards
sm->createSchedules();
project.calculate( *sm );
Debug::print( &project, s, true );
Debug::print( r, "", true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), targetend - Duration( 1, 8, 0 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
sm->setAllowOverbooking( true );
sm->createSchedules();
project.calculate( *sm );
// Debug::printSchedulingLog( *sm );
QCOMPARE( t->startTime(), targetend - Duration( 0, 8, 0 ) );
QCOMPARE( t->endTime(), targetend );
sm->setAllowOverbooking( false );
r->clearExternalAppointments();
sm->createSchedules();
project.calculate( *sm );
// Debug::printSchedulingLog( *sm );
QCOMPARE( t->endTime(), targetend );
QCOMPARE( t->startTime(), t->endTime() - Duration( 0, 8, 0 ) );
}
void ProjectTester::reschedule()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate::fromString( "2012-02-01", Qt::ISODate ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setCalendar( c );
project.addResource( g, r );
QString s = "Re-schedule; schedule tasks T1, T2, T3 ---------------";
qDebug()<<endl<<"Testing:"<<s;
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_h );
task1->estimate()->setExpectedEstimate( 8.0 );
task1->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
task1->addRequest( gr );
Task *task2 = project.createTask();
task2->setName( "T2" );
project.addTask( task2, &project );
task2->estimate()->setUnit( Duration::Unit_h );
task2->estimate()->setExpectedEstimate( 8.0 );
task2->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
task2->addRequest( gr );
Task *task3 = project.createTask();
task3->setName( "T3" );
project.addTask( task3, &project );
task3->estimate()->setUnit( Duration::Unit_h );
task3->estimate()->setExpectedEstimate( 8.0 );
task3->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( r, 100 ) );
task3->addRequest( gr );
Relation *rel = new Relation( task1, task2 );
project.addRelation( rel );
rel = new Relation( task1, task3 );
project.addRelation( rel );
ScheduleManager *sm = project.createScheduleManager( "Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( &project, task1, s, true );
// Debug::print( &project, task2, s, true );
// Debug::print( &project, task3, s, true );
// Debug::print( r, s );
// Debug::printSchedulingLog( *sm );
QVERIFY( task1->startTime() >= c->firstAvailableAfter( targetstart, targetend ) );
QVERIFY( task1->startTime() <= c->firstAvailableBefore( targetend, targetstart ) );
QCOMPARE( task1->endTime(), task1->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( task2->startTime() >= c->firstAvailableAfter( targetstart, targetend ) );
QVERIFY( task2->startTime() <= c->firstAvailableBefore( targetend, targetstart ) );
QCOMPARE( task2->endTime(), task2->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( task3->startTime() >= c->firstAvailableAfter( targetstart, targetend ) );
QVERIFY( task3->startTime() <= c->firstAvailableBefore( targetend, targetstart ) );
QCOMPARE( task3->endTime(), task3->startTime() + Duration( 0, 8, 0 ) );
DateTime restart = task1->endTime();
s = QString( "Re-schedule; re-schedule from %1 - tasks T1 (finished), T2, T3 ------" ).arg( restart.toString() );
qDebug()<<endl<<"Testing:"<<s;
task1->completion().setStarted( true );
task1->completion().setPercentFinished( task1->endTime().date(), 100 );
task1->completion().setFinished( true );
ScheduleManager *child = project.createScheduleManager( "Plan.1" );
project.addScheduleManager( child, sm );
child->setRecalculate( true );
child->setRecalculateFrom( restart );
child->createSchedules();
project.calculate( *child );
// Debug::print( &project, task1, s, true );
// Debug::print( &project, task2, s, true );
// Debug::print( &project, task3, s, true );
// Debug::printSchedulingLog( *child, s );
QCOMPARE( task1->startTime(), c->firstAvailableAfter( targetstart, targetend ) );
QCOMPARE( task1->endTime(), task1->startTime() + Duration( 0, 8, 0 ) );
// either task2 or task3 may be scheduled first
if ( task2->startTime() < task3->startTime() ) {
QCOMPARE( task2->startTime(), c->firstAvailableAfter( qMax(task1->endTime(), restart ), targetend ) );
QCOMPARE( task2->endTime(), task2->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( task3->startTime(), c->firstAvailableAfter( task2->endTime(), targetend ) );
QCOMPARE( task3->endTime(), task3->startTime() + Duration( 0, 8, 0 ) );
} else {
QCOMPARE( task3->startTime(), c->firstAvailableAfter( qMax(task1->endTime(), restart ), targetend ) );
QCOMPARE( task3->endTime(), task3->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( task2->startTime(), c->firstAvailableAfter( task3->endTime(), targetend ) );
QCOMPARE( task2->endTime(), task2->startTime() + Duration( 0, 8, 0 ) );
}
}
void ProjectTester::materialResource()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate::fromString( "2012-02-01", Qt::ISODate ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_h );
task1->estimate()->setExpectedEstimate( 8.0 );
task1->estimate()->setType( Estimate::Type_Effort );
QString s = "Calculate forward, Task: ASAP, Working + material resource --------";
qDebug()<<endl<<"Testing:"<<s;
qDebug()<<s;
ResourceGroup *g = new ResourceGroup();
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "Work" );
r->setAvailableFrom( targetstart );
r->setCalendar( c );
project.addResource( g, r );
ResourceGroup *mg = new ResourceGroup();
mg->setType( ResourceGroup::Type_Material );
project.addResourceGroup( mg );
Resource *mr = new Resource();
mr->setType( Resource::Type_Material );
mr->setName( "Material" );
mr->setAvailableFrom( targetstart );
mr->setCalendar( c );
project.addResource( mg, mr );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
ResourceGroupRequest *mgr = new ResourceGroupRequest( mg );
task1->addRequest( mgr );
ResourceRequest *mrr = new ResourceRequest( mr, 100 );
mgr->addResourceRequest( mrr );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r, s);
// Debug::print( mr, s);
// Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->earlyStart(), task1->requests().workTimeAfter( targetstart ) );
QVERIFY( task1->lateStart() >= task1->earlyStart() );
QVERIFY( task1->earlyFinish() <= task1->endTime() );
QVERIFY( task1->lateFinish() >= task1->endTime() );
QCOMPARE( task1->startTime(), DateTime( r->availableFrom().date(), t1 ) );
QCOMPARE( task1->endTime(), task1->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( task1->schedulingError() == false );
}
void ProjectTester::requiredResource()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate::fromString( "2012-02-01", Qt::ISODate ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_h );
task1->estimate()->setExpectedEstimate( 8.0 );
task1->estimate()->setType( Estimate::Type_Effort );
QString s = "Required resource: Working + required material resource --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceGroup *g = new ResourceGroup();
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "Work" );
r->setAvailableFrom( targetstart );
r->setCalendar( c );
project.addResource( g, r );
ResourceGroup *mg = new ResourceGroup();
mg->setType( ResourceGroup::Type_Material );
mg->setName( "MG" );
project.addResourceGroup( mg );
Resource *mr = new Resource();
mr->setType( Resource::Type_Material );
mr->setName( "Material" );
mr->setAvailableFrom( targetstart );
mr->setCalendar( c );
project.addResource( mg, mr );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
QList<Resource*> lst; lst << mr;
rr->setRequiredResources( lst );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r, s);
// Debug::print( mr, s);
// Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->earlyStart(), task1->requests().workTimeAfter( targetstart ) );
QVERIFY( task1->lateStart() >= task1->earlyStart() );
QVERIFY( task1->earlyFinish() <= task1->endTime() );
QVERIFY( task1->lateFinish() >= task1->endTime() );
QCOMPARE( task1->startTime(), DateTime( r->availableFrom().date(), t1 ) );
QCOMPARE( task1->endTime(), task1->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( task1->schedulingError() == false );
QList<Appointment*> apps = r->appointments( sm->scheduleId() );
QVERIFY( apps.count() == 1 );
QCOMPARE( task1->startTime(), apps.first()->startTime() );
QCOMPARE( task1->endTime(), apps.last()->endTime() );
apps = mr->appointments( sm->scheduleId() );
QVERIFY( apps.count() == 1 );
QCOMPARE( task1->startTime(), apps.first()->startTime() );
QCOMPARE( task1->endTime(), apps.last()->endTime() );
s = "Required resource limits availability --------";
qDebug()<<endl<<"Testing:"<<s;
DateTime tomorrow = targetstart.addDays( 1 );
mr->setAvailableFrom( tomorrow );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r, s);
// Debug::print( mr, s);
// Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->earlyStart(), task1->requests().workTimeAfter( targetstart ) );
QVERIFY( task1->lateStart() >= task1->earlyStart() );
QVERIFY( task1->earlyFinish() <= task1->endTime() );
QVERIFY( task1->lateFinish() >= task1->endTime() );
QCOMPARE( task1->startTime(), DateTime( mr->availableFrom().date(), t1 ) );
QCOMPARE( task1->endTime(), task1->startTime() + Duration( 0, 8, 0 ) );
QVERIFY( task1->schedulingError() == false );
apps = r->appointments( sm->scheduleId() );
QVERIFY( apps.count() == 1 );
QCOMPARE( task1->startTime(), apps.first()->startTime() );
QCOMPARE( task1->endTime(), apps.last()->endTime() );
apps = mr->appointments( sm->scheduleId() );
QVERIFY( apps.count() == 1 );
QCOMPARE( task1->startTime(), apps.first()->startTime() );
QCOMPARE( task1->endTime(), apps.last()->endTime() );
}
void ProjectTester::resourceWithLimitedAvailability()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate( 2010, 5, 1 ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
DateTime expectedEndTime( QDate( 2010, 5, 3 ), QTime( 16, 0, 0 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_d );
task1->estimate()->setExpectedEstimate( 4.0 );
task1->estimate()->setType( Estimate::Type_Effort );
QString s = "Two resources: One with available until < resulting task length --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceGroup *g = new ResourceGroup();
project.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
r1->setAvailableFrom( targetstart );
r1->setCalendar( c );
project.addResource( g, r1 );
Resource *r2 = new Resource();
r2->setName( "R2" );
r2->setAvailableFrom( targetstart );
r2->setAvailableUntil( targetstart.addDays( 1 ) );
r2->setCalendar( c );
project.addResource( g, r2 );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
ResourceRequest *rr1 = new ResourceRequest( r1, 100 );
gr->addResourceRequest( rr1 );
ResourceRequest *rr2 = new ResourceRequest( r2, 100 );
gr->addResourceRequest( rr2 );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r1, s);
// Debug::print( r2, s);
// Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), expectedEndTime );
}
void ProjectTester::unavailableResource()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate( 2010, 5, 1 ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_d );
task1->estimate()->setExpectedEstimate( 2.0 );
task1->estimate()->setType( Estimate::Type_Effort );
QString s = "One available resource --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceGroup *g = new ResourceGroup();
project.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
r1->setCalendar( c );
project.addResource( g, r1 );
Resource *r2 = new Resource();
r2->setName( "Unavailable" );
r2->setCalendar( c );
project.addResource( g, r2 );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
gr->addResourceRequest( new ResourceRequest( r1, 100 ) );
ScheduleManager *sm = project.createScheduleManager( "Plan R1" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
DateTime expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One available resource + one unavailable resource --------";
qDebug()<<endl<<"Testing:"<<s;
r2->setAvailableFrom( targetend );
r2->setAvailableUntil( targetend.addDays( 1 ) );
gr->addResourceRequest( new ResourceRequest( r2, 100 ) );
sm = project.createScheduleManager( "Team + Resource" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), expectedEndTime );
}
void ProjectTester::team()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate( 2010, 5, 1 ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_d );
task1->estimate()->setExpectedEstimate( 2.0 );
task1->estimate()->setType( Estimate::Type_Effort );
QString s = "One team with one resource --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceGroup *g = new ResourceGroup();
project.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
r1->setCalendar( c );
project.addResource( g, r1 );
Resource *r2 = new Resource();
r2->setName( "Team member" );
r2->setCalendar( c );
project.addResource( g, r2 );
Resource *team = new Resource();
team->setType( Resource::Type_Team );
team->setName( "Team" );
project.addResource( g, team );
team->addTeamMemberId( r2->id() );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
ResourceRequest *tr = new ResourceRequest( team, 100 );
gr->addResourceRequest( tr );
ScheduleManager *sm = project.createScheduleManager( "Team" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
DateTime expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with one resource + one resource --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceRequest *rr1 = new ResourceRequest( r1, 100 );
gr->addResourceRequest( rr1 );
sm = project.createScheduleManager( "Team + Resource" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 0, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with one resource + one resource, resource available too late --------";
qDebug()<<endl<<"Testing:"<<s;
r1->setAvailableFrom( targetend );
r1->setAvailableUntil( targetend.addDays( 7 ) );
sm = project.createScheduleManager( "Team + Resource not available" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with two resources --------";
qDebug()<<endl<<"Testing:"<<s;
r1->removeRequests();
team->addTeamMemberId( r1->id() );
r1->setAvailableFrom( targetstart );
r1->setAvailableUntil( targetend );
sm = project.createScheduleManager( "Team with 2 resources" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 0, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with two resources, one resource unavailable --------";
qDebug()<<endl<<"Testing:"<<s;
r1->setAvailableFrom( targetend );
r1->setAvailableUntil( targetend.addDays( 2 ) );
sm = project.createScheduleManager( "Team, one unavailable resource" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
gr->takeResourceRequest(tr);
task1->takeRequest(gr);
project.takeResource( g, team);
team->removeTeamMemberId( r2->id() );
}
void ProjectTester::inWBSOrder()
{
Project p;
p.setName( "WBS Order" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2012-02-01", Qt::ISODate );
st = DateTime( st.addDays( 1 ) );
st.setTime( QTime ( 0, 0, 0 ) );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Task *t = p.createTask();
t->setName( "T1" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T2" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T3" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T4" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
ScheduleManager *sm = p.createScheduleManager( "WBS Order, forward" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
QString s = "Schedule 4 tasks forward in wbs order -------";
// NOTE: It's not *mandatory* to schedule in wbs order but users expect it, so we'll try
// This test can be removed if for some important reason this isn't possible.
// Debug::print ( c, s );
// Debug::print( r1, s );
// Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 3, 8, 0 ) );
}
void ProjectTester::resourceConflictALAP()
{
Project p;
p.setName( "resourceConflictALAP" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2012-02-01", Qt::ISODate );
st = DateTime( st.addDays( 1 ) );
st.setTime( QTime ( 0, 0, 0 ) );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Task *t = p.createTask();
t->setName( "T1" );
t->setConstraint( Node::ALAP );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T2" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T3" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T4" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
ScheduleManager *sm = p.createScheduleManager( "T1 ALAP" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
QString s = "Schedule T1 ALAP -------";
// Debug::print ( c, s );
// Debug::print( r1, s );
// Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
s = "Schedule T1, T2 ALAP -------";
p.allTasks().at( 1 )->setConstraint( Node::ALAP );
sm = p.createScheduleManager( "T1, T2 ALAP" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
// Debug::print ( c, s );
// Debug::print( r1, s );
// Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
s = "Schedule T1, T2, T3 ALAP -------";
p.allTasks().at( 2 )->setConstraint( Node::ALAP );
sm = p.createScheduleManager( "T1, T2, T3 ALAP" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
// Debug::print ( c, s );
// Debug::print( r1, s );
// Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
s = "Schedule T1, T2, T3, T4 ALAP -------";
p.allTasks().at( 3 )->setConstraint( Node::ALAP );
sm = p.createScheduleManager( "T1, T2, T3, T4 ALAP" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
// Debug::print ( c, s );
// Debug::print( r1, s );
// Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
}
void ProjectTester::resourceConflictMustStartOn()
{
Project p;
p.setName( "resourceConflictMustStartOn" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2012-02-01T00:00:00", Qt::ISODate );
st = DateTime( st.addDays( 1 ) );
st.setTime( QTime ( 0, 0, 0 ) );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Task *t = p.createTask();
t->setName( "T1" );
t->setConstraint( Node::MustStartOn );
t->setConstraintStartTime( st + Duration( 1, 8, 0 ) );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T2" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T3" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
t = p.createTask();
t->setName( "T4" );
p.addSubTask( t, &p );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
t->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
ScheduleManager *sm = p.createScheduleManager( "T1 MustStartOn" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
QString s = "Schedule T1 MustStartOn -------";
// Debug::print ( c, s );
// Debug::print( r1, s );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
s = "Schedule T1, T2 MustStartOn -------";
p.allTasks().at( 1 )->setConstraint( Node::MustStartOn );
p.allTasks().at( 1 )->setConstraintStartTime( st + Duration( 2, 8, 0 ) );
sm = p.createScheduleManager( "T1, T2 MustStartOn" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
// Debug::print ( c, s );
// Debug::print( r1, s );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
s = "Schedule T1, T2, T3 MustStartOn -------";
p.allTasks().at( 2 )->setConstraint( Node::MustStartOn );
p.allTasks().at( 2 )->setConstraintStartTime( st + Duration( 3, 8, 0 ) );
sm = p.createScheduleManager( "T1, T2, T3 MustStartOn" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
// Debug::print ( c, s );
// Debug::print( r1, s );
// Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( p.allTasks().count(), 4 );
QCOMPARE( p.allTasks().at( 0 )->startTime(), st + Duration( 1, 8, 0 ) );
QCOMPARE( p.allTasks().at( 0 )->endTime(), st + Duration( 1, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->startTime(), st + Duration( 2, 8, 0 ) );
QCOMPARE( p.allTasks().at( 1 )->endTime(), st + Duration( 2, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->startTime(), st + Duration( 3, 8, 0 ) );
QCOMPARE( p.allTasks().at( 2 )->endTime(), st + Duration( 3, 8, 0 ) + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->startTime(), st + Duration( 0, 8, 0 ) );
QCOMPARE( p.allTasks().at( 3 )->endTime(), st + Duration( 0, 8, 0 ) + Duration( 0, 8, 0 ) );
s = "Schedule backwards, T1 MustStartOn, T2 ASAP -------";
p.takeTask( p.allTasks().at( 3 ), false );
p.takeTask( p.allTasks().at( 2 ), false );
Task *task1 = p.allTasks().at( 0 );
Task *task2 = p.allTasks().at( 1 );
DateTime et = p.constraintEndTime();
task1->setConstraint( Node::MustStartOn );
task1->setConstraintStartTime( et - Duration( 1, 16, 0 ) );
task2->setConstraint( Node::ASAP );
sm = p.createScheduleManager( "T1 MustStartOn, T2 ASAP" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->startTime(), task1->mustStartOn() );
QCOMPARE( task2->startTime(), et - Duration( 0, 16, 0 ) );
s = "Schedule backwards, T1 MustStartOn, T2 StartNotEarlier -------";
task2->setConstraint( Node::StartNotEarlier );
task2->setConstraintStartTime( task1->mustStartOn().addDays( -1 ) );
sm = p.createScheduleManager( "T1 MustStartOn, T2 StartNotEarlier" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->startTime(), task1->mustStartOn() );
QCOMPARE( task2->startTime(), et - Duration( 0, 16, 0 ) );
task2->setConstraintStartTime( task1->mustStartOn() );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->startTime(), task1->mustStartOn() );
QCOMPARE( task2->startTime(), et - Duration( 0, 16, 0 ) );
task2->setConstraintStartTime( task1->mustStartOn().addDays( 1 ) );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->startTime(), task1->mustStartOn() );
QCOMPARE( task2->startTime(), et - Duration( 0, 16, 0 ) );
}
void ProjectTester::resourceConflictMustFinishOn()
{
Project p;
p.setName( "P1" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2012-02-01", Qt::ISODate );
st = DateTime( st.addDays( 1 ) );
st.setTime( QTime ( 0, 0, 0 ) );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Task *task1 = p.createTask();
task1->setName( "T1" );
task1->setConstraint( Node::MustFinishOn );
task1->setConstraintEndTime( st + Duration( 1, 16, 0 ) );
p.addSubTask( task1, &p );
task1->estimate()->setUnit( Duration::Unit_d );
task1->estimate()->setExpectedEstimate( 1.0 );
task1->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
ResourceRequest *tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
Task *task2 = p.createTask();
task2->setName( "T2" );
p.addSubTask( task2, &p );
task2->estimate()->setUnit( Duration::Unit_d );
task2->estimate()->setExpectedEstimate( 1.0 );
task2->estimate()->setType( Estimate::Type_Effort );
gr = new ResourceGroupRequest( g );
task2->addRequest( gr );
tr = new ResourceRequest( r1, 100 );
gr->addResourceRequest( tr );
QString s = "Schedule T1 MustFinishOn, T2 ASAP -------";
qDebug()<<s;
ScheduleManager *sm = p.createScheduleManager( "T1 MustFinishOn" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->startTime(), p.constraintStartTime() + Duration( 0, 8, 0 ) );
s = "Schedule T1 MustFinishOn, T2 ALAP -------";
qDebug()<<s;
task2->setConstraint( Node::ALAP );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->startTime(), p.constraintStartTime() + Duration( 0, 8, 0 ) );
s = "Schedule T1 MustFinishOn, T2 StartNotEarlier -------";
qDebug()<<s;
task2->setConstraint( Node::StartNotEarlier );
task2->setConstraintStartTime( p.constraintStartTime() );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->startTime(), p.constraintStartTime() + Duration( 0, 8, 0 ) );
s = "Schedule T1 MustFinishOn, T2 StartNotEarlier -------";
qDebug()<<s;
task2->setConstraintStartTime( task1->mustFinishOn() - Duration( 0, 8, 0 ) );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->startTime(), task2->constraintStartTime() + Duration( 1, 0, 0 ) );
s = "Schedule T1 MustFinishOn, T2 StartNotEarlier -------";
qDebug()<<s;
task2->setConstraintStartTime( task1->mustFinishOn() );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->startTime(), task2->constraintStartTime() + Duration( 0, 16, 0 ) );
s = "Schedule backwards, T1 MustFinishOn, T2 ASAP -------";
qDebug()<<s;
task2->setConstraint( Node::ASAP );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->startTime(), task1->mustFinishOn() + Duration( 0, 16, 0 ) );
s = "Schedule backwards, T1 MustFinishOn, T2 ALAP -------";
qDebug()<<s;
DateTime et = p.constraintEndTime();
task2->setConstraint( Node::ALAP );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->endTime(), et - Duration( 0, 8, 0 ) );
s = "Schedule backwards, T1 MustFinishOn, T2 StartNotEarlier -------";
qDebug()<<s;
task2->setConstraint( Node::StartNotEarlier );
task2->setConstraintStartTime( task1->mustFinishOn() );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->endTime(), et - Duration( 0, 8, 0 ) );
task2->setConstraintStartTime( p.constraintStartTime() );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->endTime(), et - Duration( 0, 8, 0 ) );
s = "Schedule backwards, T1 MustFinishOn, T2 FinishNotLater -------";
qDebug()<<s;
task2->setConstraint( Node::FinishNotLater );
task2->setConstraintEndTime( task1->mustFinishOn() );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->endTime(), task1->startTime() - Duration( 0, 16, 0 ) );
task2->setConstraintEndTime( task1->mustFinishOn().addDays( 2 ) );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->endTime(), task1->mustFinishOn() );
QCOMPARE( task2->endTime(), task2->finishNotLater() );
}
void ProjectTester::fixedInterval()
{
Project p;
p.setName( "P1" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2010-10-20T08:00", Qt::ISODate );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Task *task1 = p.createTask();
task1->setName( "T1" );
task1->setConstraint( Node::FixedInterval );
task1->setConstraintStartTime( st );
task1->setConstraintEndTime( st.addDays( 1 ) );
p.addTask( task1, &p );
QString s = "Schedule T1 Fixed interval -------";
qDebug()<<s;
ScheduleManager *sm = p.createScheduleManager( "T1 Fixed interval" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), task1->constraintStartTime() );
QCOMPARE( task1->endTime(), task1->constraintEndTime() );
s = "Schedule backward: T1 Fixed interval -------";
qDebug()<<s;
sm->setSchedulingDirection( true );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( task1->startTime(), task1->constraintStartTime() );
QCOMPARE( task1->endTime(), task1->constraintEndTime() );
}
void ProjectTester::estimateDuration()
{
Project p;
p.setName( "P1" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2010-10-20 08:00", Qt::TextDate );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Task *task1 = p.createTask();
task1->setName( "T1" );
task1->setConstraint( Node::ASAP );
p.addTask( task1, &p );
task1->estimate()->setType( Estimate::Type_Duration );
task1->estimate()->setUnit( Duration::Unit_h );
task1->estimate()->setExpectedEstimate( 10 );
QString s = "Schedule T1 Estimate type Duration -------";
qDebug()<<s;
ScheduleManager *sm = p.createScheduleManager( "T1 Duration" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), p.constraintStartTime() );
QCOMPARE( task1->endTime(), task1->startTime() + Duration( 0, 10, 0 ) );
s = "Schedule backward: T1 Estimate type Duration -------";
qDebug()<<s;
sm->setSchedulingDirection( true );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->endTime(), p.constraintEndTime() );
QCOMPARE( task1->startTime(), task1->endTime() - Duration( 0, 10, 0 ) );
}
void ProjectTester::startStart()
{
Project p;
p.setName( "P1" );
p.setId( p.uniqueNodeId() );
p.registerNodeId( &p );
DateTime st = QDateTime::fromString( "2010-10-20T00:00:00", Qt::ISODate );
p.setConstraintStartTime( st );
p.setConstraintEndTime( st.addDays( 5 ) );
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
p.addCalendar( c );
p.setDefaultCalendar( c );
ResourceGroup *g = new ResourceGroup();
p.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
p.addResource( g, r1 );
Resource *r2 = new Resource();
r2->setName( "R2" );
p.addResource( g, r2 );
Task *task1 = p.createTask();
task1->setName( "T1" );
task1->setConstraint( Node::ASAP );
p.addTask( task1, &p );
task1->estimate()->setType( Estimate::Type_Duration );
task1->estimate()->setUnit( Duration::Unit_h );
task1->estimate()->setExpectedEstimate( 2 );
Task *task2 = p.createTask();
task2->setName( "T2" );
task2->setConstraint( Node::ASAP );
p.addTask( task2, &p );
task2->estimate()->setType( Estimate::Type_Duration );
task2->estimate()->setUnit( Duration::Unit_h );
task2->estimate()->setExpectedEstimate( 2 );
task1->addDependChildNode( task2, Relation::StartStart );
QString s = "Schedule T1 Lag = 0 -------";
qDebug()<<s;
ScheduleManager *sm = p.createScheduleManager( "Lag = 0" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), p.constraintStartTime() );
QCOMPARE( task1->lateStart(), task2->lateStart() );
QCOMPARE( task1->startTime(), task2->startTime() );
s = "Schedule backward T1 Lag = 0 -------";
qDebug()<<s;
sm = p.createScheduleManager( "Backward, Lag = 0" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
// Debug::printSchedulingLog(*sm, s);
Debug::print( &p, s, true );
qDebug()<<"predeccessors:"<<task2->dependParentNodes();
QCOMPARE( task2->endTime(), p.constraintEndTime() );
QCOMPARE( task1->lateStart(), task2->lateStart() );
QCOMPARE( task1->startTime(), task2->startTime() );
s = "Schedule T1 calendar -------";
qDebug()<<s;
task1->estimate()->setCalendar( c );
sm = p.createScheduleManager( "Lag = 0" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), p.constraintStartTime() + Duration( 0, 8, 0 ) );
QCOMPARE( task1->lateStart(), task2->lateStart() );
QCOMPARE( task1->startTime(), task2->startTime() );
s = "Schedule backward T1 calendar -------";
qDebug()<<s;
task1->estimate()->setCalendar( 0 );
task2->estimate()->setCalendar( c );
sm = p.createScheduleManager( "Backward, calendar, Lag = 0" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task2->endTime(), p.constraintEndTime() - Duration( 0, 8, 0 ) );
QCOMPARE( task1->lateStart(), task2->lateStart() );
QCOMPARE( task1->startTime(), task2->startTime() );
s = "Schedule Lag = 1 hour -------";
qDebug()<<s;
task1->estimate()->setCalendar( c );
task2->estimate()->setCalendar( 0 );
task1->dependChildNodes().at( 0 )->setLag( Duration( 0, 1, 0 ) );
sm = p.createScheduleManager( "Lag = 1 hour" );
p.addScheduleManager( sm );
sm->createSchedules();
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), p.constraintStartTime() + Duration( 0, 8, 0 ) );
QCOMPARE( task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at( 0 )->lag() );
QCOMPARE( task2->startTime(), task1->startTime() + task1->dependChildNodes().at( 0 )->lag() );
s = "Schedule backward Lag = 1 hour -------";
qDebug()<<s;
task1->estimate()->setCalendar( 0 );
task2->estimate()->setCalendar( c );
sm = p.createScheduleManager( "Backward, Lag = 1 hour" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task2->endTime(), p.constraintEndTime() - Duration( 0, 8, 0 ) );
QCOMPARE( task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at( 0 )->lag() );
QCOMPARE( task2->startTime(), task1->startTime() + task1->dependChildNodes().at( 0 )->lag() );
s = "Schedule resources Lag = 1 hour -------";
qDebug()<<s;
task1->estimate()->setCalendar( 0 );
task2->estimate()->setCalendar( 0 );
ResourceGroupRequest *gr1 = new ResourceGroupRequest( g );
task1->addRequest( gr1 );
ResourceRequest *rr1 = new ResourceRequest( r1, 100 );
gr1->addResourceRequest( rr1 );
task1->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr2 = new ResourceGroupRequest( g );
task2->addRequest( gr2 );
ResourceRequest *rr2 = new ResourceRequest( r2, 100 );
gr2->addResourceRequest( rr2 );
task2->estimate()->setType( Estimate::Type_Effort );
sm = p.createScheduleManager( "Resources, Lag = 1 hour" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( false );
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), p.constraintStartTime() + Duration( 0, 8, 0 ) );
QCOMPARE( task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at( 0 )->lag() );
QCOMPARE( task2->startTime(), task1->startTime() + task1->dependChildNodes().at( 0 )->lag() );
s = "Schedule backward resources Lag = 1 hour -------";
qDebug()<<s;
sm = p.createScheduleManager( "Resources backward, Lag = 1 hour" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task2->endTime(), p.constraintEndTime() - Duration( 0, 8, 0 ) );
QCOMPARE( task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at( 0 )->lag() );
QCOMPARE( task2->startTime(), task1->startTime() + task1->dependChildNodes().at( 0 )->lag() );
s = "Schedule resources w limited availability, Lag = 1 hour -------";
qDebug()<<s;
r1->setAvailableFrom( p.constraintStartTime() + Duration( 0, 9, 0 ) );
r1->setAvailableUntil( p.constraintEndTime() - Duration( 0, 12, 0 ) );
sm = p.createScheduleManager( "Resources, Lag = 1 hour" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( false );
p.calculate( *sm );
Debug::print( &p, s, true );
QCOMPARE( task1->startTime(), p.constraintStartTime() + Duration( 0, 9, 0 ) );
QCOMPARE( task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at( 0 )->lag() );
QCOMPARE( task2->startTime(), task1->startTime() + task1->dependChildNodes().at( 0 )->lag() );
s = "Schedule backward resources w limited availability Lag = 1 hour -------";
qDebug()<<s;
sm = p.createScheduleManager( "Resources backward, Lag = 1 hour" );
p.addScheduleManager( sm );
sm->createSchedules();
sm->setSchedulingDirection( true );
p.calculate( *sm );
Debug::print( &p, s, true );
QVERIFY( task2->lateStart() >= task1->lateStart() + task1->dependChildNodes().at( 0 )->lag() );
QVERIFY( task2->startTime() >= task1->startTime() + task1->dependChildNodes().at( 0 )->lag() );
}
void ProjectTester::scheduleTimeZone()
{
QByteArray tz( "TZ=Europe/Copenhagen" );
putenv( tz.data() );
qDebug()<<"Local timezone: "<<QTimeZone::systemTimeZone();
Calendar cal("LocalTime/Copenhagen");
QCOMPARE( cal.timeZone(), QTimeZone::systemTimeZone() );
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
project.setConstraintStartTime( QDateTime::fromString( "2016-07-04T00:00", Qt::ISODate ) );
project.setConstraintEndTime( QDateTime::fromString( "2016-07-10T00:00", Qt::ISODate ) );
// standard worktime defines 8 hour day as default
QVERIFY( project.standardWorktime() );
QCOMPARE( project.standardWorktime()->day(), 8.0 );
Calendar *calendar = new Calendar();
calendar->setName( "LocalTime, Copenhagen" );
calendar->setDefault( true );
QCOMPARE( calendar->timeZone(), QTimeZone::systemTimeZone() );
QTime time1( 9, 0, 0 );
QTime time2 ( 17, 0, 0 );
int length = time1.msecsTo( time2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = calendar->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( time1, length );
}
project.addCalendar( calendar );
Calendar *cal2 = new Calendar();
cal2->setName("Helsinki");
cal2->setTimeZone( QTimeZone( "Europe/Helsinki" ) );
QVERIFY( cal2->timeZone().isValid() );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = cal2->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( time1, length );
}
project.addCalendar( cal2 );
QDate today = project.constraintStartTime().date();
Task *t = project.createTask();
t->setName( "T1" );
project.addTask( t, &project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
Task *t2 = project.createTask();
t2->setName( "T2" );
project.addTask( t2, &project );
t2->estimate()->setUnit( Duration::Unit_d );
t2->estimate()->setExpectedEstimate( 1.0 );
t2->estimate()->setType( Estimate::Type_Effort );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
r->setCalendar( calendar );
project.addResource( g, r );
Resource *r2 = new Resource();
r2->setName( "R2" );
r2->setCalendar( cal2 );
project.addResource( g, r2 );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
ResourceGroupRequest *gr2 = new ResourceGroupRequest( g );
t2->addRequest( gr2 );
ResourceRequest *rr2 = new ResourceRequest( r2, 100 );
gr2->addResourceRequest( rr2 );
QString s = "Calculate forward, Task: ASAP -----------------------------------";
qDebug()<<endl<<"Testing:"<<s;
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
sm->createSchedules();
project.calculate( *sm );
Debug::print( &project, t, s );
// Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), DateTime( today, time1 ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( t->plannedEffort().toHours(), 8.0 );
QVERIFY( t->schedulingError() == false );
QCOMPARE( t2->startTime(), DateTime( today, time1.addSecs(-3600) ) );
QCOMPARE( t2->endTime(), t2->startTime() + Duration( 0, 8, 0 ) );
QCOMPARE( t2->plannedEffort().toHours(), 8.0 );
QVERIFY( t2->schedulingError() == false );
unsetenv("TZ");
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::ProjectTester )
diff --git a/src/libs/kernel/tests/ResourceTester.cpp b/src/libs/kernel/tests/ResourceTester.cpp
index 6c1c887d..4e0f3676 100644
--- a/src/libs/kernel/tests/ResourceTester.cpp
+++ b/src/libs/kernel/tests/ResourceTester.cpp
@@ -1,607 +1,608 @@
/* This file is part of the KDE project
Copyright (C) 2006-2010 Dag Andersen <danders@get2net.dk>
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 "ResourceTester.h"
#include "DateTimeTester.h"
#include <kptresource.h>
#include <kptcalendar.h>
#include <kptcommand.h>
#include <kptdatetime.h>
#include <kptduration.h>
#include <kptmap.h>
#include <QTest>
#include "kptglobal.h"
#include "kptxmlloaderobject.h"
#include <KoXmlReader.h>
#include "debug.cpp"
#include <QDomDocument>
#include <QDomElement>
namespace KPlato
{
void ResourceTester::testAvailable()
{
Resource r;
QVERIFY( ! r.availableFrom().isValid() );
QVERIFY( ! r.availableUntil().isValid() );
QDateTime qt = QDateTime::currentDateTime();
DateTime dt = DateTime( qt );
qDebug()<<"dt"<<dt;
r.setAvailableFrom( qt );
Debug::print( &r, "Test setAvailableFrom with QDateTime" );
DateTime x = r.availableFrom();
qDebug()<<"------"<<x;
QCOMPARE( x, dt );
qDebug()<<"----------------";
r.setAvailableUntil( qt.addDays( 1 ) );
Debug::print( &r, "Test setAvailableUntil with QDateTime" );
QCOMPARE( r.availableUntil(), DateTime( dt.addDays( 1 ) ) );
qDebug()<<"----------------";
}
void ResourceTester::testSingleDay() {
Calendar t("Test");
QDate wdate(2006,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
t.addDay(day);
QVERIFY(t.findDay(wdate) == day);
Resource r;
r.setAvailableFrom( before );
r.setAvailableUntil( after );
r.setCalendar( &t );
Debug::print( &r, "Test single resource, no group, no project", true );
QVERIFY(r.availableAfter(after, DateTime( after.addDays(1))).isValid() == false);
QVERIFY(r.availableBefore(before, DateTime(before.addDays(-1))).isValid() == false);
QVERIFY(r.availableAfter(before, after).isValid());
QVERIFY((r.availableAfter(after, DateTime(after.addDays(10)))).isValid() == false);
QVERIFY((r.availableBefore(before, DateTime(before.addDays(-10)))).isValid() == false);
QCOMPARE(r.availableAfter(before,after), wdt1);
QCOMPARE(r.availableBefore(after, before), wdt2);
Duration e(0, 2, 0);
QCOMPARE( r.effort( before, Duration( 2, 0, 0 ) ).toString(), e.toString());
}
void ResourceTester::team()
{
Resource team;
team.setId( "team" );
Resource tm1, tm2;
tm1.setId( "tm1" );
tm2.setId( "tm2" );
QVERIFY( team.teamMemberIds().isEmpty() );
team.addTeamMemberId( tm1.id() );
QVERIFY( team.teamMemberIds().count() == 1 );
team.removeTeamMemberId( tm1.id() );
QVERIFY( team.teamMemberIds().isEmpty() );
team.addTeamMemberId( tm1.id() );
team.addTeamMemberId( tm2.id() );
QVERIFY( team.teamMemberIds().count() == 2 );
team.addTeamMemberId( tm2.id() );
QVERIFY( team.teamMemberIds().count() == 2 );
team.removeTeamMemberId( tm1.id() );
team.removeTeamMemberId( tm2.id() );
QVERIFY( team.teamMemberIds().isEmpty() );
AddResourceTeamCmd ac( &team, tm1.id() );
ac.execute();
QVERIFY( team.teamMemberIds().count() == 1 );
ac.unexecute();
QVERIFY( team.teamMemberIds().isEmpty() );
ac.execute();
RemoveResourceTeamCmd rc( &team, tm1.id() );
rc.execute();
QVERIFY( team.teamMemberIds().isEmpty() );
rc.unexecute();
QVERIFY( team.teamMemberIds().count() == 1 );
{
Project p1;
AddResourceGroupCmd *c1 = new AddResourceGroupCmd( &p1, new ResourceGroup() );
c1->redo();
ResourceGroup *g = p1.resourceGroups().at( 0 );
QVERIFY( g );
delete c1;
AddResourceCmd *c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r1 = g->resourceAt( 0 );
QVERIFY( r1 );
delete c2;
c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r2 = g->resourceAt( 1 );
QVERIFY( r2 );
delete c2;
c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r3 = g->resourceAt( 2 );
QVERIFY( r3 );
delete c2;
AddResourceTeamCmd *c3 = new AddResourceTeamCmd( r1, r2->id() );
c3->redo();
delete c3;
QCOMPARE( r1->teamMemberIds().count(), 1 );
QCOMPARE( r1->teamMembers().count(), 1 );
QCOMPARE( r1->teamMembers().at( 0 ), r2 );
c3 = new AddResourceTeamCmd( r1, r3->id() );
c3->redo();
delete c3;
QCOMPARE( r1->teamMemberIds().count(), 2 );
QCOMPARE( r1->teamMembers().count(), 2 );
QCOMPARE( r1->teamMembers().at( 1 ), r3 );
RemoveResourceTeamCmd *c4 = new RemoveResourceTeamCmd( r1, r2->id() );
c4->redo();
delete c4;
QCOMPARE( r1->teamMemberIds().count(), 1 );
QCOMPARE( r1->teamMembers().count(), 1 );
QCOMPARE( r1->teamMembers().at( 0 ), r3 );
}
{
Project p1;
p1.setId( "p1" );
AddResourceGroupCmd *c1 = new AddResourceGroupCmd( &p1, new ResourceGroup() );
c1->redo();
ResourceGroup *g = p1.resourceGroups().at( 0 );
QVERIFY( g );
delete c1;
Resource *r1 = new Resource();
r1->setType( Resource::Type_Team );
AddResourceCmd *c2 = new AddResourceCmd( g, r1 );
c2->redo();
r1 = g->resourceAt( 0 );
QVERIFY( r1 );
delete c2;
c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r2 = g->resourceAt( 1 );
QVERIFY( r2 );
delete c2;
c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r3 = g->resourceAt( 2 );
QVERIFY( r3 );
delete c2;
AddResourceTeamCmd *c3 = new AddResourceTeamCmd( r1, r2->id() );
c3->redo();
delete c3;
QCOMPARE( r1->teamMemberIds().count(), 1 );
QCOMPARE( r1->teamMembers().count(), 1 );
QCOMPARE( r1->teamMembers().at( 0 ), r2 );
c3 = new AddResourceTeamCmd( r1, r3->id() );
c3->redo();
delete c3;
QCOMPARE( r1->teamMemberIds().count(), 2 );
QCOMPARE( r1->teamMembers().count(), 2 );
QCOMPARE( r1->teamMembers().at( 1 ), r3 );
// copy
Project p2;
c1 = new AddResourceGroupCmd( &p2, new ResourceGroup( g ) );
c1->redo();
ResourceGroup *g2 = p2.resourceGroups().at( 0 );
QVERIFY( g2 );
delete c1;
c2 = new AddResourceCmd( g2, new Resource( r1 ) );
c2->redo();
Resource *r11 = g2->resourceAt( 0 );
QVERIFY( r11 );
delete c2;
c2 = new AddResourceCmd( g2, new Resource( r2 ) );
c2->redo();
Resource *r12 = g->resourceAt( 1 );
QVERIFY( r12 );
delete c2;
c2 = new AddResourceCmd( g2, new Resource( r3 ) );
c2->redo();
Resource *r13 = g->resourceAt( 2 );
QVERIFY( r13 );
delete c2;
QCOMPARE( r1->teamMemberIds().count(), 2 );
QCOMPARE( r1->teamMembers().count(), 2 );
QCOMPARE( r1->teamMembers().at( 0 ), r12 );
QCOMPARE( r1->teamMembers().at( 1 ), r13 );
// xml
QDomDocument qdoc;
QDomElement e = qdoc.createElement( "plan" );
qdoc.appendChild( e );
p1.save( e );
KoXmlDocument xdoc;
xdoc.setContent( qdoc.toString() );
XMLLoaderObject sts;
Project p3;
sts.setProject( &p3 );
sts.setVersion( PLAN_FILE_SYNTAX_VERSION );
KoXmlElement xe = xdoc.documentElement().firstChildElement();
p3.load( xe, sts );
QCOMPARE( p3.numResourceGroups(), 1 );
ResourceGroup *g3 = p3.resourceGroupAt( 0 );
QCOMPARE( g3->numResources(), 3 );
Resource *r21 = g3->resourceAt( 0 );
QCOMPARE( r21->type(), Resource::Type_Team );
QCOMPARE( r21->teamMemberIds().count(), 2 );
QCOMPARE( r21->teamMembers().count(), 2 );
QCOMPARE( r21->teamMembers().at( 0 ), g3->resourceAt( 1 ) );
QCOMPARE( r21->teamMembers().at( 1 ), g3->resourceAt( 2 ) );
}
{
// team members in different group
Project p1;
p1.setId( "p1" );
AddResourceGroupCmd *c1 = new AddResourceGroupCmd( &p1, new ResourceGroup() );
c1->redo();
ResourceGroup *g = p1.resourceGroups().at( 0 );
QVERIFY( g );
delete c1;
ResourceGroup *mg = new ResourceGroup();
c1 = new AddResourceGroupCmd( &p1, mg );
c1->redo();
QCOMPARE( mg, p1.resourceGroups().at( 1 ) );
delete c1;
Resource *r1 = new Resource();
r1->setType( Resource::Type_Team );
AddResourceCmd *c2 = new AddResourceCmd( g, r1 );
c2->redo();
QCOMPARE( r1, g->resourceAt( 0 ) );
delete c2;
Resource *r2 = new Resource();
c2 = new AddResourceCmd( mg, r2 );
c2->redo();
QCOMPARE( r2, mg->resourceAt( 0 ) );
delete c2;
Resource *r3 = new Resource();
c2 = new AddResourceCmd( mg, r3 );
c2->redo();
QCOMPARE( r3, mg->resourceAt( 1 ) );
delete c2;
AddResourceTeamCmd *c3 = new AddResourceTeamCmd( r1, r2->id() );
c3->redo();
delete c3;
QCOMPARE( r1->teamMemberIds().count(), 1 );
QCOMPARE( r1->teamMembers().count(), 1 );
QCOMPARE( r1->teamMembers().at( 0 ), r2 );
c3 = new AddResourceTeamCmd( r1, r3->id() );
c3->redo();
delete c3;
QCOMPARE( r1->teamMemberIds().count(), 2 );
QCOMPARE( r1->teamMembers().count(), 2 );
QCOMPARE( r1->teamMembers().at( 0 ), r2 );
QCOMPARE( r1->teamMembers().at( 1 ), r3 );
// copy
Project p2;
c1 = new AddResourceGroupCmd( &p2, new ResourceGroup( g ) );
c1->redo();
ResourceGroup *g2 = p2.resourceGroups().at( 0 );
QVERIFY( g2 );
delete c1;
c1 = new AddResourceGroupCmd( &p2, new ResourceGroup( mg ) );
c1->redo();
ResourceGroup *mg2 = p2.resourceGroups().at( 1 );
QVERIFY( mg2 );
delete c1;
c2 = new AddResourceCmd( g2, new Resource( r1 ) );
c2->redo();
Resource *r11 = g2->resourceAt( 0 );
QVERIFY( r11 );
delete c2;
c2 = new AddResourceCmd( mg2, new Resource( r2 ) );
c2->redo();
Resource *r12 = mg2->resourceAt( 0 );
QVERIFY( r12 );
delete c2;
c2 = new AddResourceCmd( mg2, new Resource( r3 ) );
c2->redo();
Resource *r13 = mg2->resourceAt( 1 );
QVERIFY( r13 );
delete c2;
QCOMPARE( r11->teamMemberIds().count(), 2 );
QCOMPARE( r11->teamMembers().count(), 2 );
QCOMPARE( r11->teamMembers().at( 0 ), r12 );
QCOMPARE( r11->teamMembers().at( 1 ), r13 );
// xml
QDomDocument qdoc;
QDomElement e = qdoc.createElement( "plan" );
qdoc.appendChild( e );
p1.save( e );
KoXmlDocument xdoc;
xdoc.setContent( qdoc.toString() );
XMLLoaderObject sts;
Project p3;
sts.setProject( &p3 );
sts.setVersion( PLAN_FILE_SYNTAX_VERSION );
KoXmlElement xe = xdoc.documentElement().firstChildElement();
p3.load( xe, sts );
QCOMPARE( p3.numResourceGroups(), 2 );
ResourceGroup *g3 = p3.resourceGroupAt( 0 );
QCOMPARE( g3->numResources(), 1 );
ResourceGroup *mg3 = p3.resourceGroupAt( 1 );
QCOMPARE( mg3->numResources(), 2 );
Resource *r21 = g3->resourceAt( 0 );
QCOMPARE( r21->type(), Resource::Type_Team );
QCOMPARE( r21->teamMemberIds().count(), 2 );
QCOMPARE( r21->teamMembers().count(), 2 );
QCOMPARE( r21->teamMembers().at( 0 ), mg3->resourceAt( 0 ) );
QCOMPARE( r21->teamMembers().at( 1 ), mg3->resourceAt( 1 ) );
}
}
void ResourceTester::required()
{
Project p;
AddResourceGroupCmd *c1 = new AddResourceGroupCmd( &p, new ResourceGroup() );
c1->redo();
ResourceGroup *g = p.resourceGroups().at( 0 );
QVERIFY( g );
delete c1;
AddResourceCmd *c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r1 = g->resourceAt( 0 );
QVERIFY( r1 );
delete c2;
c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r2 = g->resourceAt( 1 );
QVERIFY( r2 );
delete c2;
c2 = new AddResourceCmd( g, new Resource() );
c2->redo();
Resource *r3 = g->resourceAt( 2 );
QVERIFY( r3 );
delete c2;
QVERIFY( r1->requiredIds().isEmpty() );
QVERIFY( r1->requiredResources().isEmpty() );
r1->addRequiredId( "" ); // not allowed to add empty id
QVERIFY( r1->requiredIds().isEmpty() );
r1->addRequiredId( r2->id() );
QCOMPARE( r1->requiredIds().count(), 1 );
QCOMPARE( r1->requiredResources().count(), 1 );
QCOMPARE( r1->requiredResources().at( 0 ), r2 );
r1->addRequiredId( r3->id() );
QCOMPARE( r1->requiredIds().count(), 2 );
QCOMPARE( r1->requiredResources().count(), 2 );
QCOMPARE( r1->requiredResources().at( 0 ), r2 );
QCOMPARE( r1->requiredResources().at( 1 ), r3 );
r1->addRequiredId( r2->id() ); // not allowed to add existing id
QCOMPARE( r1->requiredIds().count(), 2 );
QCOMPARE( r1->requiredResources().count(), 2 );
QCOMPARE( r1->requiredResources().at( 0 ), r2 );
QCOMPARE( r1->requiredResources().at( 1 ), r3 );
QStringList lst;
r1->setRequiredIds( lst );
QCOMPARE( r1->requiredIds().count(), 0 );
QCOMPARE( r1->requiredIds().count(), 0 );
lst << r2->id() << r3->id();
r1->setRequiredIds( lst );
QCOMPARE( r1->requiredIds().count(), 2 );
QCOMPARE( r1->requiredResources().count(), 2 );
QCOMPARE( r1->requiredResources().at( 0 ), r2 );
QCOMPARE( r1->requiredResources().at( 1 ), r3 );
// copy to different project
Project p2;
c1 = new AddResourceGroupCmd( &p2, new ResourceGroup( g ) );
c1->redo();
delete c1;
ResourceGroup *g1 = p2.resourceGroupAt( 0 );
c2 = new AddResourceCmd( g1, new Resource( r1 ) );
c2->redo();
Resource *r4 = g1->resourceAt( 0 );
QVERIFY( r4 );
delete c2;
c2 = new AddResourceCmd( g1, new Resource( r2 ) );
c2->redo();
Resource *r5 = g1->resourceAt( 1 );
QVERIFY( r5 );
delete c2;
c2 = new AddResourceCmd( g1, new Resource( r3 ) );
c2->redo();
Resource *r6 = g1->resourceAt( 2 );
QVERIFY( r6 );
delete c2;
QCOMPARE( r4->requiredIds().count(), 2 );
QCOMPARE( r4->requiredResources().count(), 2 );
QCOMPARE( r4->requiredResources().at( 0 ), r5 );
QCOMPARE( r4->requiredResources().at( 1 ), r6 );
// using xml
{
QDomDocument qdoc;
QDomElement e = qdoc.createElement( "plan" );
qdoc.appendChild( e );
p2.setId( "p2" );
p2.save( e );
KoXmlDocument xdoc;
xdoc.setContent( qdoc.toString() );
XMLLoaderObject sts;
sts.setProject( &p );
sts.setVersion( PLAN_FILE_SYNTAX_VERSION );
Project p3;
KoXmlElement xe = xdoc.documentElement().firstChildElement();
p3.load( xe, sts );
ResourceGroup *g2 = p3.resourceGroupAt( 0 );
QVERIFY( g2 );
QCOMPARE( g2->numResources(), 3 );
Resource *r7 = g2->resourceAt( 0 );
QVERIFY( r7 );
Resource *r8 = g2->resourceAt( 1 );
QVERIFY( r8 );
Resource *r9 = g2->resourceAt( 2 );
QVERIFY( r9 );
QCOMPARE( r7->requiredIds().count(), 2 );
QCOMPARE( r7->requiredResources().count(), 2 );
QCOMPARE( r7->requiredResources().at( 0 ), r8 );
QCOMPARE( r7->requiredResources().at( 1 ), r9 );
}
{
// required in different group
Project p4;
p4.setId( "p4" );
c1 = new AddResourceGroupCmd( &p4, new ResourceGroup() );
c1->redo();
delete c1;
ResourceGroup *m = new ResourceGroup();
m->setType( ResourceGroup::Type_Material );
c1 = new AddResourceGroupCmd( &p4, m );
c1->redo();
delete c1;
ResourceGroup *g3 = p4.resourceGroupAt( 0 );
c2 = new AddResourceCmd( g3, new Resource() );
c2->redo();
Resource *r10 = g3->resourceAt( 0 );
QVERIFY( r4 );
delete c2;
Resource *r11 = new Resource();
r11->setType( Resource::Type_Material );
c2 = new AddResourceCmd( m, r11 );
c2->redo();
QVERIFY( m->resourceAt( 0 ) == r11 );
delete c2;
Resource *r12 = new Resource();
r12->setType( Resource::Type_Material );
c2 = new AddResourceCmd( m, r12 );
c2->redo();
QVERIFY( m->resourceAt( 1 ) == r12 );
delete c2;
r10->addRequiredId( r11->id() );
r10->addRequiredId( r12->id() );
QCOMPARE( r10->requiredIds().count(), 2 );
QCOMPARE( r10->requiredResources().count(), 2 );
QCOMPARE( r10->requiredResources().at( 0 ), r11 );
QCOMPARE( r10->requiredResources().at( 1 ), r12 );
// using xml
QDomDocument qdoc;
QDomElement e = qdoc.createElement( "plan" );
qdoc.appendChild( e );
p4.save( e );
KoXmlDocument xdoc;
xdoc.setContent( qdoc.toString() );
XMLLoaderObject sts;
sts.setProject( &p4 );
sts.setVersion( PLAN_FILE_SYNTAX_VERSION );
Project p5;
KoXmlElement xe = xdoc.documentElement().firstChildElement();
p5.load( xe, sts );
ResourceGroup *g4 = p5.resourceGroupAt( 0 );
QVERIFY( g4 );
QCOMPARE( g4->numResources(), 1 );
ResourceGroup *g5 = p5.resourceGroupAt( 1 );
QVERIFY( g5 );
QCOMPARE( g5->numResources(), 2 );
Resource *r13 = g4->resourceAt( 0 );
QVERIFY( r13 );
QCOMPARE( r13->id(), r10->id() );
Resource *r14 = g5->resourceAt( 0 );
QVERIFY( r14 );
QCOMPARE( r14->id(), r11->id() );
Resource *r15 = g5->resourceAt( 1 );
QVERIFY( r15 );
QCOMPARE( r15->id(), r12->id() );
QCOMPARE( r13->requiredIds().count(), 2 );
QCOMPARE( r13->requiredResources().count(), 2 );
QCOMPARE( r13->requiredResources().at( 0 ), r14 );
QCOMPARE( r13->requiredResources().at( 1 ), r15 );
}
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::ResourceTester )
diff --git a/src/libs/kernel/tests/ScheduleTester.cpp b/src/libs/kernel/tests/ScheduleTester.cpp
index a5ca2b05..10c06580 100644
--- a/src/libs/kernel/tests/ScheduleTester.cpp
+++ b/src/libs/kernel/tests/ScheduleTester.cpp
@@ -1,146 +1,147 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
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 "ScheduleTester.h"
#include "kptdatetime.h"
#include "kptschedule.h"
#include <QTest>
namespace QTest
{
template<>
char *toString(const KPlato::DateTime &dt)
{
return toString( dt.toString() );
}
template<>
char *toString(const KPlato::DateTimeInterval &dt)
{
if ( dt.first.isValid() && dt.second.isValid() )
return toString( dt.first.toString() + " - " + dt.second.toString() );
return toString( "invalid interval" );
}
}
namespace KPlato
{
void ScheduleTester::initTestCase()
{
date = QDate::currentDate();
t1 = QTime( 9, 0, 0 );
t2 = QTime( 12, 0, 0 );
t3 = QTime( 17, 0, 0 );
DateTime dt1(date, t1 );
DateTime dt2( date, t3 );
resourceSchedule.addAppointment( &nodeSchedule, dt1, dt2, 100. );
dt1 = DateTime( date.addDays(1), t1 );
dt2 = DateTime( date.addDays(1), t2 );
resourceSchedule.addAppointment( &nodeSchedule, dt1, dt2, 100. );
dt1 = DateTime( date.addDays(1), t2 );
dt2 = DateTime( date.addDays(1), t3 );
resourceSchedule.addAppointment( &nodeSchedule, dt1, dt2, 100. );
dt1 = DateTime( date.addDays(2), t1 );
dt2 = DateTime( date.addDays(2), t3 );
resourceSchedule.addAppointment( &nodeSchedule, dt1, dt2, 100. );
}
void ScheduleTester::available()
{
// before any interval
DateTimeInterval i1( DateTime( date.addDays( -1 ), t1 ), DateTime( date.addDays( -1 ), t2 ) );
QCOMPARE( i1, resourceSchedule.available( i1 ) );
// upto first interval
i1 = DateTimeInterval( DateTime( date, QTime( 0, 0, 0 ) ), DateTime( date, t1 ) );
QCOMPARE( i1, resourceSchedule.available( i1 ) );
// partly into first interval
i1 = DateTimeInterval( DateTime( date, QTime( 0, 0, 0 ) ), DateTime( date, t2 ) );
DateTimeInterval res( DateTime( date, QTime( 0, 0, 0 ) ), DateTime( date, t1 ) );
QCOMPARE( res, resourceSchedule.available( i1 ) );
// between two intervals, start at end of first
i1 = DateTimeInterval( DateTime( date, t3 ), DateTime( date, t3.addSecs( 100 ) ) );
QCOMPARE( i1, resourceSchedule.available( i1 ) );
// between two intervals, free of both
i1 = DateTimeInterval( DateTime( date, t3.addSecs(10 ) ), DateTime( date, t3.addSecs( 100 ) ) );
QCOMPARE( i1, resourceSchedule.available( i1 ) );
// between two intervals, fill whole hole
i1 = DateTimeInterval( DateTime( date, t3 ), DateTime( date.addDays(1), t1 ) );
QCOMPARE( i1, resourceSchedule.available( i1 ) );
// between two intervals, end at start of second
i1 = DateTimeInterval( DateTime( date, t3.addSecs(1) ), DateTime( date.addDays(1), t1 ) );
QCOMPARE( i1, resourceSchedule.available( i1 ) );
// start into first interval, end into second -> end of first to start of second
i1 = DateTimeInterval( DateTime( date, t1.addSecs(1) ), DateTime( date.addDays(1), t1.addSecs( 1 ) ) );
res = DateTimeInterval( DateTime( date, t3 ), DateTime( date.addDays(1), t1 ) );
QCOMPARE( res, resourceSchedule.available( i1 ) );
}
void ScheduleTester::busy()
{
// whole first interval
DateTimeInterval i1( DateTime( date, t1 ), DateTime( date, t3 ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
// start into interval, end at end
i1 = DateTimeInterval( DateTime( date, t1.addSecs(100) ), DateTime( date, t3 ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
// start/end into interval
i1 = DateTimeInterval( DateTime( date, t1.addSecs(100) ), DateTime( date, t3.addSecs( -100 ) ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
// span 2 adjacent intervals, whole intervals
i1 = DateTimeInterval( DateTime( date.addDays(1), t1 ), DateTime( date.addDays(1), t3 ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
// span 2 adjacent intervals, start into first
i1 = DateTimeInterval( DateTime( date.addDays(1), t1.addSecs( 100 ) ), DateTime( date.addDays(1), t3 ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
// span 2 adjacent intervals, start into first, end into second
i1 = DateTimeInterval( DateTime( date.addDays(1), t1.addSecs( 100 ) ), DateTime( date.addDays(1), t3.addSecs( -100 ) ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
// span 2 adjacent intervals, start at first, end into second
i1 = DateTimeInterval( DateTime( date.addDays(1), t1 ), DateTime( date.addDays(1), t3.addSecs( -100 ) ) );
i1 = resourceSchedule.available( i1 );
QVERIFY( ! i1.first.isValid() && ! i1.second.isValid() );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::ScheduleTester )
diff --git a/src/libs/kernel/tests/WorkInfoCacheTester.cpp b/src/libs/kernel/tests/WorkInfoCacheTester.cpp
index 85bc4538..c5953944 100644
--- a/src/libs/kernel/tests/WorkInfoCacheTester.cpp
+++ b/src/libs/kernel/tests/WorkInfoCacheTester.cpp
@@ -1,391 +1,392 @@
/* This file is part of the KDE project
Copyright (C) 20012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "WorkInfoCacheTester.h"
#include "DateTimeTester.h"
#include "kptresource.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptglobal.h"
#include <QTest>
#include <QDir>
#include <cstdlib>
#include "debug.cpp"
namespace KPlato
{
QTimeZone createTimeZoneWithOffsetFromSystem(int hours, const QString & name, int *shiftDays)
{
QTimeZone systemTimeZone = QTimeZone::systemTimeZone();
int systemOffsetSeconds = systemTimeZone.standardTimeOffset(QDateTime(QDate(1980, 1, 1), QTime(), Qt::UTC));
int offsetSeconds = systemOffsetSeconds + 3600 * hours;
if (offsetSeconds >= (12*3600) ) {
qDebug() << "reducing offset by 24h";
offsetSeconds -= (24*3600);
*shiftDays = -1;
} else if (offsetSeconds <= -(12*3600) ) {
qDebug() << "increasing offset by 24h";
offsetSeconds += (24*3600);
*shiftDays = 1;
} else {
*shiftDays = 0;
}
qDebug() << "creating timezone for offset" << hours << offsetSeconds << "systemoffset" << systemOffsetSeconds
<< "shiftDays" << *shiftDays;
return QTimeZone(name.toLatin1(), offsetSeconds, name, name);
}
void WorkInfoCacheTester::basics()
{
Calendar cal("Test");
QDate wdate(2012,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Resource r;
r.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r.workInfoCache();
QVERIFY( ! wic.isValid() );
r.calendarIntervals( before, after );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 1 );
wdt1 = wdt1.addDays( 1 );
wdt2 = wdt2.addDays( 1 );
day = new CalendarDay(wdt1.date(), CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
r.calendarIntervals( before, after );
QCOMPARE( wic.intervals.map().count(), 1 );
after = after.addDays( 1 );
r.calendarIntervals( wdt1, after );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 2 );
}
void WorkInfoCacheTester::addAfter()
{
Calendar cal("Test");
QDate wdate(2012,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
QTime t3(12,0,0);
QTime t4(14,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
length = t3.msecsTo( t4 );
day->addInterval(TimeInterval(t3, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Resource r;
r.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r.workInfoCache();
QVERIFY( ! wic.isValid() );
r.calendarIntervals( before, after );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 2 );
wdt1 = wdt1.addDays( 1 );
wdt2 = wdt2.addDays( 1 );
day = new CalendarDay(wdt1.date(), CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
// wdate: 8-10, 12-14
// wdate+1: 8-10
r.calendarIntervals( DateTime( wdate, t1 ), DateTime( wdate, t2 ) );
QCOMPARE( wic.intervals.map().count(), 1 );
r.calendarIntervals( DateTime( wdate, t3 ), DateTime( wdate, t4 ) );
QCOMPARE( wic.intervals.map().count(), 2 );
r.calendarIntervals( DateTime( wdate.addDays( 1 ), t1 ), DateTime( wdate.addDays( 1 ), t2 ) );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 3 );
}
void WorkInfoCacheTester::addBefore()
{
Calendar cal("Test");
QDate wdate(2012,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
QTime t3(12,0,0);
QTime t4(14,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
length = t3.msecsTo( t4 );
day->addInterval(TimeInterval(t3, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Resource r;
r.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r.workInfoCache();
QVERIFY( ! wic.isValid() );
r.calendarIntervals( before, after );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 2 );
wdt1 = wdt1.addDays( 1 );
wdt2 = wdt2.addDays( 1 );
day = new CalendarDay(wdt1.date(), CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
// wdate: 8-10, 12-14
// wdate+1: 8-10
r.calendarIntervals( DateTime( wdate.addDays( 1 ), t1 ), DateTime( wdate.addDays( 1 ), t2 ) );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 1 );
r.calendarIntervals( DateTime( wdate, t3 ), DateTime( wdate, t4 ) );
QCOMPARE( wic.intervals.map().count(), 2 );
r.calendarIntervals( DateTime( wdate, t1 ), DateTime( wdate, t2 ) );
QCOMPARE( wic.intervals.map().count(), 3 );
}
void WorkInfoCacheTester::addMiddle()
{
Calendar cal("Test");
QDate wdate(2012,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
QTime t1(8,0,0);
QTime t2(10,0,0);
QTime t3(12,0,0);
QTime t4(14,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
length = t3.msecsTo( t4 );
day->addInterval(TimeInterval(t3, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Resource r;
r.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r.workInfoCache();
QVERIFY( ! wic.isValid() );
r.calendarIntervals( before, after );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 2 );
wdt1 = wdt1.addDays( 1 );
wdt2 = wdt2.addDays( 1 );
day = new CalendarDay(wdt1.date(), CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
// wdate: 8-10, 12-14
// wdate+1: 8-10
r.calendarIntervals( DateTime( wdate.addDays( 1 ), t1 ), DateTime( wdate.addDays( 1 ), t2 ) );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 1 );
// the middle interval will be filled in automatically
r.calendarIntervals( DateTime( wdate, t1 ), DateTime( wdate, t2 ) );
QCOMPARE( wic.intervals.map().count(), 3 );
}
void WorkInfoCacheTester::fullDay()
{
Calendar cal("Test");
QDate wdate(2012,1,2);
QTime t1(0,0,0);
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate.addDays( 1 ), t1);
long length = ( wdt2 - wdt1 ).milliseconds();
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Resource r;
r.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r.workInfoCache();
QVERIFY( ! wic.isValid() );
r.calendarIntervals( wdt1, wdt2 );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 1 );
day = new CalendarDay(wdt2.date(), CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
r.calendarIntervals( wdt1, DateTime( wdt2.addDays( 2 ) ) );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 2 );
}
void WorkInfoCacheTester::timeZone()
{
QByteArray tz( "TZ=Europe/Berlin" );
putenv( tz.data() );
qDebug()<<"Local timezone: "<<QTimeZone::systemTimeZone();
Calendar cal("Test");
// local zone: Europe/Berlin ( 9 hours from America/Los_Angeles )
int laShiftDays;
QTimeZone la = createTimeZoneWithOffsetFromSystem(-9, "DummyLos_Angeles", &laShiftDays);
QVERIFY( la.isValid() );
cal.setTimeZone( la );
QDate wdate(2012,1,2);
DateTime before = DateTime(wdate.addDays(-1), QTime());
DateTime after = DateTime(wdate.addDays(2), QTime());
// qDebug() << "before, after" << before << after;
QTime t1(14,0,0); // 23 LA
QTime t2(16,0,0); // 01 LA next day
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
qDebug() << "wdt1, wdt2" << wdt1 << wdt2 << wdt1.toTimeZone(la) << wdt2.toTimeZone(la);
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Debug::print( &cal, "DummyLos_Angeles" );
Resource r;
r.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r.workInfoCache();
QVERIFY( ! wic.isValid() );
r.calendarIntervals( before, after );
qDebug()<<wic.intervals.map();
QCOMPARE( wic.intervals.map().count(), 2 );
wdate = wdate.addDays( -laShiftDays );
qDebug() << wdate;
qDebug() << wic.intervals.map().value( wdate );
qDebug() << wic.intervals.map().value( wdate ).startTime();
qDebug() << DateTime( wdate, QTime( 23, 0, 0 ) );
QCOMPARE( wic.intervals.map().value( wdate ).startTime(), DateTime( wdate, QTime( 23, 0, 0 ) ) );
QCOMPARE( wic.intervals.map().value( wdate ).endTime(), DateTime( wdate.addDays( 1 ), QTime( 0, 0, 0 ) ) );
wdate = wdate.addDays( 1 );
QCOMPARE( wic.intervals.map().value( wdate ).startTime(), DateTime( wdate, QTime( 0, 0, 0 ) ) );
QCOMPARE( wic.intervals.map().value( wdate ).endTime(), DateTime( wdate, QTime( 1, 0, 0 ) ) );
unsetenv("TZ");
}
void WorkInfoCacheTester::doubleTimeZones()
{
QByteArray tz( "TZ=Europe/Copenhagen" );
putenv( tz.data() );
qDebug()<<"Local timezone: "<<QTimeZone::systemTimeZone();
Calendar cal("LocalTime/Copenhagen");
QCOMPARE( cal.timeZone(), QTimeZone::systemTimeZone() );
Calendar cal2("Helsinki");
cal2.setTimeZone( QTimeZone( "Europe/Helsinki" ) );
QVERIFY( cal2.timeZone().isValid() );
QDate wdate(2012,1,2);
DateTime before = DateTime(wdate, QTime());
DateTime after = DateTime(wdate.addDays(1), QTime());
// qDebug() << "before, after" << before << after;
QTime t1(14,0,0); // 23 LA
QTime t2(16,0,0); // 01 LA next day
DateTime wdt1(wdate, t1);
DateTime wdt2(wdate, t2);
int length = t1.msecsTo( t2 );
CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal.addDay(day);
QVERIFY(cal.findDay(wdate) == day);
Debug::print( &cal, "" );
Resource r1;
r1.setCalendar( &cal );
const Resource::WorkInfoCache &wic = r1.workInfoCache();
QVERIFY( ! wic.isValid() );
r1.calendarIntervals( before, after );
Debug::print(wic.intervals);
QCOMPARE( wic.intervals.map().count(), 1 );
QCOMPARE( wic.intervals.map().value( wdate ).startTime(), DateTime( wdate, QTime( 14, 0, 0 ) ) );
QCOMPARE( wic.intervals.map().value( wdate ).endTime(), DateTime( wdate, QTime( 16, 0, 0 ) ) );
day = new CalendarDay(wdate, CalendarDay::Working);
day->addInterval(TimeInterval(t1, length));
cal2.addDay(day);
QVERIFY(cal2.findDay(wdate) == day);
Debug::print( &cal2, "" );
Resource r2;
r2.setCalendar( &cal2 );
const Resource::WorkInfoCache &wic2 = r2.workInfoCache();
QVERIFY( ! wic2.isValid() );
r2.calendarIntervals( before, after );
Debug::print(wic2.intervals);
QCOMPARE( wic2.intervals.map().count(), 1 );
QCOMPARE( wic2.intervals.map().value( wdate ).startTime(), DateTime( wdate, QTime( 13, 0, 0 ) ) );
QCOMPARE( wic2.intervals.map().value( wdate ).endTime(), DateTime( wdate, QTime( 15, 0, 0 ) ) );
unsetenv("TZ");
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::WorkInfoCacheTester )
diff --git a/src/libs/kernel/tests/debug.cpp b/src/libs/kernel/tests/debug.cpp
index 181b9558..840ab7e6 100644
--- a/src/libs/kernel/tests/debug.cpp
+++ b/src/libs/kernel/tests/debug.cpp
@@ -1,382 +1,383 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2010 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptappointment.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptproject.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include <QTest>
#include <QStringList>
#include <QString>
namespace QTest
{
template<>
char *toString(const KPlato::DateTime &dt)
{
QString s;
switch ( dt.timeSpec() ) {
case Qt::LocalTime: s = " LocalTime"; break;
case Qt::UTC: s = " UTC"; break;
case Qt::OffsetFromUTC: s = " OffsetFromUTC"; break;
case Qt::TimeZone: s = " TimeZone (" + dt.timeZone().id() + ')'; break;
}
return toString(QString("%1T%2 %3").arg(dt.date().toString(Qt::ISODate), dt.time().toString("hh:mm:ss.zzz"), s));
}
template<>
char *toString(const KPlato::Duration &d)
{
return toString( d.toString() );
}
}
namespace KPlato
{
class Debug
{
public:
Debug() {}
static
void print( Calendar *c, const QString &str, bool full = true ) {
Q_UNUSED(full);
QTimeZone tz = c->timeZone();
QString s = tz.isValid() ? QString::fromLatin1(tz.id()) : QStringLiteral("LocalTime");
qDebug()<<"Debug info: Calendar"<<c->name()<<s<<str;
for ( int wd = 1; wd <= 7; ++wd ) {
CalendarDay *d = c->weekday( wd );
qDebug()<<" "<<wd<<':'<<d->stateToString( d->state() );
foreach ( TimeInterval *t, d->timeIntervals() ) {
qDebug()<<" interval:"<<t->first<<t->second<<'('<<Duration( qint64(t->second) ).toString()<<')';
}
}
foreach ( const CalendarDay *d, c->days() ) {
qDebug()<<" "<<d->date()<<':';
foreach ( TimeInterval *t, d->timeIntervals() ) {
qDebug()<<" interval:"<<t->first<<t->second;
}
}
}
static
QString printAvailable( Resource *r, const QString &lead = QString() ) {
QStringList sl;
sl<<lead<<"Available:"
<<( r->availableFrom().isValid()
? r->availableFrom().toString()
: ( r->project() ? ('('+r->project()->constraintStartTime().toString()+')' ) : QString() ) )
<<( r->availableUntil().isValid()
? r->availableUntil().toString()
: ( r->project() ? ('('+r->project()->constraintEndTime().toString()+')' ) : QString() ) )
<<QString::number( r->units() )<<"%"
<<"cost: normal"<<QString::number(r->normalRate() )<<" overtime"<<QString::number(r->overtimeRate() );
return sl.join( " " );
}
static
void print( Resource *r, const QString &str, bool full = true ) {
qDebug()<<"Debug info: Resource"<<r->name()<<str;
qDebug()<<"Parent group:"<<( r->parentGroup()
? ( r->parentGroup()->name() + " Type: "+ r->parentGroup()->typeToString() )
: QString( "None" ) );
qDebug()<<"Available:"
<<( r->availableFrom().isValid()
? r->availableFrom().toString()
: ( r->project() ? ('('+r->project()->constraintStartTime().toString()+')' ) : QString() ) )
<<( r->availableUntil().isValid()
? r->availableUntil().toString()
: ( r->project() ? ('('+r->project()->constraintEndTime().toString()+')' ) : QString() ) )
<<r->units()<<'%';
qDebug()<<"Type:"<<r->typeToString();
if ( r->type() == Resource::Type_Team ) {
qDebug()<<"Team members:"<<r->teamMembers().count();
foreach ( Resource *tm, r->teamMembers() ) {
qDebug()<<" "<<tm->name()<<"Available:"
<<( r->availableFrom().isValid()
? r->availableFrom().toString()
: ( r->project() ? ('('+r->project()->constraintStartTime().toString()+')' ) : QString() ) )
<<( r->availableUntil().isValid()
? r->availableUntil().toString()
: ( r->project() ? ('('+r->project()->constraintEndTime().toString()+')' ) : QString() ) )
<<r->units()<<'%';
}
} else {
Calendar *cal = r->calendar( true );
QString s;
if ( cal ) {
s = cal->name();
} else {
cal = r->calendar( false );
if ( cal ) {
s = cal->name() + " (Default)";
} else {
s = "No calendar";
}
}
qDebug()<<"Calendar:"<<s;
if ( cal ) {
print( cal, "Resource calendar" );
}
}
if ( ! full ) return;
qDebug()<<"External appointments:"<<r->numExternalAppointments();
foreach ( Appointment *a, r->externalAppointmentList() ) {
qDebug()<<" appointment:"<<a->startTime().toString()<<a->endTime().toString();
foreach( const AppointmentInterval &i, a->intervals().map() ) {
qDebug()<<" "<<i.startTime().toString()<<i.endTime().toString()<<i.load();
}
}
}
static
void print( Project *p, const QString &str, bool all = false ) {
qDebug()<<"Debug info: Project"<<p->name()<<str;
qDebug()<<"project target start:"<<QTest::toString( QDateTime(p->constraintStartTime()) );
qDebug()<<" project target end:"<<QTest::toString( QDateTime(p->constraintEndTime()) );
if ( p->isScheduled() ) {
qDebug()<<" project start time:"<<QTest::toString( QDateTime(p->startTime()) );
qDebug()<<" project end time:"<<QTest::toString( QDateTime(p->endTime()) );
} else {
qDebug()<<" Not scheduled";
}
if ( ! all ) {
return;
}
for ( int i = 0; i < p->numChildren(); ++i ) {
qDebug();
print( static_cast<Task*>( p->childNode( i ) ), true );
}
}
static
void print( Project *p, Task *t, const QString &str, bool full = true ) {
Q_UNUSED(full);
print( p, str );
print( t );
}
static
void print( Task *t, const QString &str, bool full = true ) {
qDebug()<<"Debug info: Task"<<t->name()<<str;
print( t, full );
}
static
void print( Task *t, bool full = true ) {
QString pad;
if ( t->level() > 0 ) {
pad = QString("%1").arg( "", t->level()*2, ' ' );
}
qDebug()<<pad<<"Task"<<t->name()<<t->typeToString()<<t->constraintToString();
if (t->isScheduled()) {
qDebug()<<pad<<" earlyStart:"<<QTest::toString( QDateTime(t->earlyStart()) );
qDebug()<<pad<<" lateStart:"<<QTest::toString( QDateTime(t->lateStart()) );
qDebug()<<pad<<" earlyFinish:"<<QTest::toString( QDateTime(t->earlyFinish()) );
qDebug()<<pad<<" lateFinish:"<<QTest::toString( QDateTime(t->lateFinish()) );
qDebug()<<pad<<" startTime:"<<QTest::toString( QDateTime(t->startTime()) );
qDebug()<<pad<<" endTime:"<<QTest::toString( QDateTime(t->endTime()) );
} else {
qDebug()<<pad<<" Not scheduled";
}
qDebug()<<pad;
switch ( t->constraint() ) {
case Node::MustStartOn:
case Node::StartNotEarlier:
qDebug()<<pad<<"startConstraint:"<<QTest::toString( QDateTime(t->constraintStartTime()) );
break;
case Node::FixedInterval:
qDebug()<<pad<<"startConstraint:"<<QTest::toString( QDateTime(t->constraintStartTime()) );
case Node::MustFinishOn:
case Node::FinishNotLater:
qDebug()<<pad<<" endConstraint:"<<QTest::toString( QDateTime(t->constraintEndTime()) );
break;
default: break;
}
qDebug()<<pad<<"Estimate :"<<t->estimate()->expectedEstimate()<<Duration::unitToString(t->estimate()->unit())
<<t->estimate()->typeToString()
<<(t->estimate()->type() == Estimate::Type_Duration
? (t->estimate()->calendar()?t->estimate()->calendar()->name():"Fixed")
: QString( "%1 h" ).arg( t->estimate()->expectedValue().toDouble( Duration::Unit_h ) ) );
foreach ( ResourceGroupRequest *gr, t->requests().requests() ) {
qDebug()<<pad<<"Group request:"<<gr->group()->name()<<gr->units();
foreach ( ResourceRequest *rr, gr->resourceRequests() ) {
qDebug()<<pad<<printAvailable( rr->resource(), " " + rr->resource()->name() );
}
}
if (t->isStartNode()) {
qDebug()<<pad<<"Start node";
}
QStringList rel;
foreach (Relation *r, t->dependChildNodes()) {
QString type;
switch(r->type()) {
case Relation::StartStart: type = "SS"; break;
case Relation::FinishFinish: type = "FF"; break;
default: type = "FS"; break;
}
rel << QString("(%1) -> %2, %3 %4").arg(r->parent()->name()).arg(r->child()->name()).arg(type).arg(r->lag() == 0?QString():r->lag().toString(Duration::Format_HourFraction));
}
if (!rel.isEmpty()) {
qDebug()<<pad<<"Successors:"<<rel.join(" : ");
}
if (t->isEndNode()) {
qDebug()<<pad<<"End node";
}
rel.clear();
foreach (Relation *r, t->dependParentNodes()) {
QString type;
switch(r->type()) {
case Relation::StartStart: type = "SS"; break;
case Relation::FinishFinish: type = "FF"; break;
default: type = "FS"; break;
}
rel << QString("%1 -> (%2), %3 %4").arg(r->parent()->name()).arg(r->child()->name()).arg(type).arg(r->lag() == 0?QString():r->lag().toString(Duration::Format_HourFraction));
}
if (!rel.isEmpty()) {
qDebug()<<pad<<"Predeccessors:"<<rel.join(" : ");
}
qDebug()<<pad;
Schedule *s = t->currentSchedule();
if ( s ) {
qDebug()<<pad<<"Appointments:"<<s->appointments().count();
foreach ( Appointment *a, s->appointments() ) {
qDebug()<<pad<<" Resource:"<<a->resource()->resource()->name()<<"booked:"<<QTest::toString( QDateTime(a->startTime()) )<<QTest::toString( QDateTime(a->endTime()) )<<"effort:"<<a->effort( a->startTime(), a->endTime() ).toDouble( Duration::Unit_h )<<'h';
if ( ! full ) { continue; }
foreach( const AppointmentInterval &i, a->intervals().map() ) {
qDebug()<<pad<<" "<<QTest::toString( QDateTime(i.startTime()) )<<QTest::toString( QDateTime(i.endTime()) )<<i.load()<<"effort:"<<i.effort( i.startTime(), i.endTime() ).toDouble( Duration::Unit_h )<<'h';
}
}
}
if ( t->runningAccount() ) {
qDebug()<<pad<<"Running account :"<<t->runningAccount()->name();
}
if ( t->startupAccount() ) {
qDebug()<<pad<<"Startup account :"<<t->startupAccount()->name()<<" cost:"<<t->startupCost();
}
if ( t->shutdownAccount() ) {
qDebug()<<pad<<"Shutdown account:"<<t->shutdownAccount()->name()<<" cost:"<<t->shutdownCost();
}
if ( full ) {
for ( int i = 0; i < t->numChildren(); ++i ) {
qDebug()<<pad;
print( static_cast<Task*>( t->childNode( i ) ), full );
}
}
}
static
void print( const Completion &c, const QString &name, const QString &s = QString() ) {
qDebug()<<"Completion:"<<name<<s;
qDebug()<<" Entry mode:"<<c.entryModeToString();
qDebug()<<" Started:"<<c.isStarted()<<c.startTime();
qDebug()<<" Finished:"<<c.isFinished()<<c.finishTime();
qDebug()<<" Completion:"<<c.percentFinished()<<'%';
if ( ! c.usedEffortMap().isEmpty() ) {
qDebug()<<" Used effort:";
foreach ( const Resource *r, c.usedEffortMap().keys() ) { // clazy:exclude=container-anti-pattern
Completion::UsedEffort *ue = c.usedEffort( r );
foreach ( const QDate &d, ue->actualEffortMap().keys() ) { // clazy:exclude=container-anti-pattern
qDebug()<<" "<<r->name()<<':';
qDebug()<<" "<<d.toString( Qt::ISODate )<<':'<<c.actualEffort( d ).toString()<<c.actualCost( d );
}
}
}
}
static
void print( const EffortCostMap &m, const QString &s = QString() ) {
qDebug()<<"EffortCostMap"<<s;
if ( m.days().isEmpty() ) {
qDebug()<<" Empty";
return;
}
qDebug()<<m.startDate().toString(Qt::ISODate)<<m.endDate().toString(Qt::ISODate)
<<" total="
<<m.totalEffort().toString()
<<m.totalCost()
<<'('
<<m.bcwpTotalEffort()
<<m.bcwpTotalCost()
<<')';
if ( ! m.days().isEmpty() ) {
foreach ( const QDate &d, m.days().keys() ) { // clazy:exclude=container-anti-pattern
qDebug()<<" "<<d.toString(Qt::ISODate)<<':'<<m.hoursOnDate( d )<<'h'<<m.costOnDate( d )
<<'('<<m.bcwpEffortOnDate( d )<<'h'<<m.bcwpCostOnDate( d )<<')';
}
}
}
static
void printSchedulingLog( const ScheduleManager &sm, const QString &s )
{
qDebug()<<"Scheduling log"<<s;
qDebug()<<"Scheduling:"<<sm.name()<<(sm.recalculate()?QString("recalculate from %1").arg(sm.recalculateFrom().toString()):"");
foreach ( const QString &s, sm.expected()->logMessages() ) {
qDebug()<<s;
}
}
static
void print( Account *a, long id = -1, const QString &s = QString() )
{
qDebug()<<"Debug info Account:"<<a->name()<<s;
qDebug()<<"Account:"<<a->name()<<(a->isDefaultAccount() ? "Default" : "");
EffortCostMap ec = a->plannedCost( id );
qDebug()<<"Planned cost:"<<ec.totalCost()<<"effort:"<<ec.totalEffort().toString();
if ( ! a->isElement() ) {
foreach ( Account *c, a->accountList() ) {
print( c );
}
return;
}
qDebug()<<"Cost places:";
foreach ( Account::CostPlace *cp, a->costPlaces() ) {
qDebug()<<" Node:"<<(cp->node() ? cp->node()->name() : "");
qDebug()<<" running:"<<cp->running();
qDebug()<<" startup:"<<cp->startup();
qDebug()<<" shutdown:"<<cp->shutdown();
}
}
static
void print( const AppointmentInterval &i, const QString &indent = QString() )
{
QString s = indent + "Interval:";
if ( ! i.isValid() ) {
qDebug()<<s<<"Not valid";
} else {
qDebug()<<s<<i.startTime()<<i.endTime()<<i.load()<<'%';
}
}
static
void print( const AppointmentIntervalList &lst, const QString &s = QString() )
{
qDebug()<<"Interval list:"<<lst.map().count()<<s;
foreach ( const AppointmentInterval &i, lst.map() ) {
print( i, " " );
}
}
};
}
diff --git a/src/libs/kundo2/kundo2commandextradata.cpp b/src/libs/kundo2/kundo2commandextradata.cpp
index 5a4fbcd5..ef449b19 100644
--- a/src/libs/kundo2/kundo2commandextradata.cpp
+++ b/src/libs/kundo2/kundo2commandextradata.cpp
@@ -1,24 +1,25 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "kundo2commandextradata.h"
KUndo2CommandExtraData::~KUndo2CommandExtraData()
{
}
diff --git a/src/libs/kundo2/kundo2group.cpp b/src/libs/kundo2/kundo2group.cpp
index 0d9fbdbd..5ba9bc9f 100644
--- a/src/libs/kundo2/kundo2group.cpp
+++ b/src/libs/kundo2/kundo2group.cpp
@@ -1,469 +1,470 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
+// clazy:excludeall=qstring-arg
#include "kundo2group.h"
#include "kundo2stack.h"
#include "kundo2stack_p.h"
#include <klocalizedstring.h>
#ifndef QT_NO_UNDOGROUP
/*!
\class KUndo2Group
\brief The KUndo2Group class is a group of KUndo2QStack objects.
\since 4.2
For an overview of the Qt's undo framework, see the
\link qundo.html overview\endlink.
An application often has multiple undo stacks, one for each opened document. At the
same time, an application usually has one undo action and one redo action, which
triggers undo or redo in the active document.
KUndo2Group is a group of KUndo2QStack objects, one of which may be active. It has
an undo() and redo() slot, which calls KUndo2QStack::undo() and KUndo2QStack::redo()
for the active stack. It also has the functions createUndoAction() and createRedoAction().
The actions returned by these functions behave in the same way as those returned by
KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction() of the active
stack.
Stacks are added to a group with addStack() and removed with removeStack(). A stack
is implicitly added to a group when it is created with the group as its parent
QObject.
It is the programmer's responsibility to specify which stack is active by
calling KUndo2QStack::setActive(), usually when the associated document window receives focus.
The active stack may also be set with setActiveStack(), and is returned by activeStack().
When a stack is added to a group using addStack(), the group does not take ownership
of the stack. This means the stack has to be deleted separately from the group. When
a stack is deleted, it is automatically removed from a group. A stack may belong to
only one group. Adding it to another group will cause it to be removed from the previous
group.
A KUndo2Group is also useful in conjunction with KUndo2View. If a KUndo2View is
set to watch a group using KUndo2View::setGroup(), it will update itself to display
the active stack.
*/
/*!
Creates an empty KUndo2Group object with parent \a parent.
\sa addStack()
*/
KUndo2Group::KUndo2Group(QObject *parent)
: QObject(parent), m_active(0)
{
}
/*!
Destroys the KUndo2Group.
*/
KUndo2Group::~KUndo2Group()
{
// Ensure all KUndo2Stacks no longer refer to this group.
QList<KUndo2QStack *>::iterator it = m_stack_list.begin();
QList<KUndo2QStack *>::iterator end = m_stack_list.end();
while (it != end) {
(*it)->m_group = 0;
++it;
}
}
/*!
Adds \a stack to this group. The group does not take ownership of the stack. Another
way of adding a stack to a group is by specifying the group as the stack's parent
QObject in KUndo2QStack::KUndo2QStack(). In this case, the stack is deleted when the
group is deleted, in the usual manner of QObjects.
\sa removeStack() stacks() KUndo2QStack::KUndo2QStack()
*/
void KUndo2Group::addStack(KUndo2QStack *stack)
{
if (m_stack_list.contains(stack))
return;
m_stack_list.append(stack);
if (KUndo2Group *other = stack->m_group)
other->removeStack(stack);
stack->m_group = this;
}
/*!
Removes \a stack from this group. If the stack was the active stack in the group,
the active stack becomes 0.
\sa addStack() stacks() KUndo2QStack::~KUndo2QStack()
*/
void KUndo2Group::removeStack(KUndo2QStack *stack)
{
if (m_stack_list.removeAll(stack) == 0)
return;
if (stack == m_active)
setActiveStack(0);
stack->m_group = 0;
}
/*!
Returns a list of stacks in this group.
\sa addStack() removeStack()
*/
QList<KUndo2QStack*> KUndo2Group::stacks() const
{
return m_stack_list;
}
/*!
Sets the active stack of this group to \a stack.
If the stack is not a member of this group, this function does nothing.
Synonymous with calling KUndo2QStack::setActive() on \a stack.
The actions returned by createUndoAction() and createRedoAction() will now behave
in the same way as those returned by \a stack's KUndo2QStack::createUndoAction()
and KUndo2QStack::createRedoAction().
\sa KUndo2QStack::setActive() activeStack()
*/
void KUndo2Group::setActiveStack(KUndo2QStack *stack)
{
if (m_active == stack)
return;
if (m_active != 0) {
disconnect(m_active, &KUndo2QStack::canUndoChanged,
this, &KUndo2Group::canUndoChanged);
disconnect(m_active, &KUndo2QStack::undoTextChanged,
this, &KUndo2Group::undoTextChanged);
disconnect(m_active, &KUndo2QStack::canRedoChanged,
this, &KUndo2Group::canRedoChanged);
disconnect(m_active, &KUndo2QStack::redoTextChanged,
this, &KUndo2Group::redoTextChanged);
disconnect(m_active, &KUndo2QStack::indexChanged,
this, &KUndo2Group::indexChanged);
disconnect(m_active, &KUndo2QStack::cleanChanged,
this, &KUndo2Group::cleanChanged);
}
m_active = stack;
if (m_active == 0) {
emit canUndoChanged(false);
emit undoTextChanged(QString());
emit canRedoChanged(false);
emit redoTextChanged(QString());
emit cleanChanged(true);
emit indexChanged(0);
} else {
connect(m_active, &KUndo2QStack::canUndoChanged,
this, &KUndo2Group::canUndoChanged);
connect(m_active, &KUndo2QStack::undoTextChanged,
this, &KUndo2Group::undoTextChanged);
connect(m_active, &KUndo2QStack::canRedoChanged,
this, &KUndo2Group::canRedoChanged);
connect(m_active, &KUndo2QStack::redoTextChanged,
this, &KUndo2Group::redoTextChanged);
connect(m_active, &KUndo2QStack::indexChanged,
this, &KUndo2Group::indexChanged);
connect(m_active, &KUndo2QStack::cleanChanged,
this, &KUndo2Group::cleanChanged);
emit canUndoChanged(m_active->canUndo());
emit undoTextChanged(m_active->undoText());
emit canRedoChanged(m_active->canRedo());
emit redoTextChanged(m_active->redoText());
emit cleanChanged(m_active->isClean());
emit indexChanged(m_active->index());
}
emit activeStackChanged(m_active);
}
/*!
Returns the active stack of this group.
If none of the stacks are active, or if the group is empty, this function
returns 0.
\sa setActiveStack() KUndo2QStack::setActive()
*/
KUndo2QStack *KUndo2Group::activeStack() const
{
return m_active;
}
/*!
Calls KUndo2QStack::undo() on the active stack.
If none of the stacks are active, or if the group is empty, this function
does nothing.
\sa redo() canUndo() setActiveStack()
*/
void KUndo2Group::undo()
{
if (m_active != 0)
m_active->undo();
}
/*!
Calls KUndo2QStack::redo() on the active stack.
If none of the stacks are active, or if the group is empty, this function
does nothing.
\sa undo() canRedo() setActiveStack()
*/
void KUndo2Group::redo()
{
if (m_active != 0)
m_active->redo();
}
/*!
Returns the value of the active stack's KUndo2QStack::canUndo().
If none of the stacks are active, or if the group is empty, this function
returns false.
\sa canRedo() setActiveStack()
*/
bool KUndo2Group::canUndo() const
{
return m_active != 0 && m_active->canUndo();
}
/*!
Returns the value of the active stack's KUndo2QStack::canRedo().
If none of the stacks are active, or if the group is empty, this function
returns false.
\sa canUndo() setActiveStack()
*/
bool KUndo2Group::canRedo() const
{
return m_active != 0 && m_active->canRedo();
}
/*!
Returns the value of the active stack's KUndo2QStack::undoActionText().
If none of the stacks are active, or if the group is empty, this function
returns an empty string.
\sa undoItemText() redoActionText() setActiveStack()
*/
QString KUndo2Group::undoText() const
{
return m_active == 0 ? QString() : m_active->undoText();
}
/*!
Returns the value of the active stack's KUndo2QStack::redoActionText().
If none of the stacks are active, or if the group is empty, this function
returns an empty string.
\sa redoItemText() undoActionText() setActiveStack()
*/
QString KUndo2Group::redoText() const
{
return m_active == 0 ? QString() : m_active->redoText();
}
/*!
Returns the value of the active stack's KUndo2QStack::isClean().
If none of the stacks are active, or if the group is empty, this function
returns true.
\sa setActiveStack()
*/
bool KUndo2Group::isClean() const
{
return m_active == 0 || m_active->isClean();
}
#ifndef QT_NO_ACTION
/*!
Creates an undo QAction object with parent \a parent.
Triggering this action will cause a call to KUndo2QStack::undo() on the active stack.
The text of this action will always be the text of the command which will be undone
in the next call to undo(), prefixed by \a prefix. If there is no command available
for undo, if the group is empty or if none of the stacks are active, this action will
be disabled.
If \a prefix is empty, the default prefix "Undo" is used.
\sa createRedoAction() canUndo() KUndo2Command::text()
*/
QAction *KUndo2Group::createUndoAction(QObject *parent) const
{
KUndo2Action *result = new KUndo2Action(i18n("Undo %1"), i18nc("Default text for undo action", "Undo"), parent);
result->setEnabled(canUndo());
result->setPrefixedText(undoText());
connect(this, &KUndo2Group::canUndoChanged,
result, &QAction::setEnabled);
connect(this, &KUndo2Group::undoTextChanged,
result, &KUndo2Action::setPrefixedText);
connect(result, &QAction::triggered, this, &KUndo2Group::undo);
return result;
}
/*!
Creates an redo QAction object with parent \a parent.
Triggering this action will cause a call to KUndo2QStack::redo() on the active stack.
The text of this action will always be the text of the command which will be redone
in the next call to redo(), prefixed by \a prefix. If there is no command available
for redo, if the group is empty or if none of the stacks are active, this action will
be disabled.
If \a prefix is empty, the default prefix "Undo" is used.
\sa createUndoAction() canRedo() KUndo2Command::text()
*/
QAction *KUndo2Group::createRedoAction(QObject *parent) const
{
KUndo2Action *result = new KUndo2Action(i18n("Redo %1"), i18nc("Default text for redo action", "Redo"), parent);
result->setEnabled(canRedo());
result->setPrefixedText(redoText());
connect(this, &KUndo2Group::canRedoChanged,
result, &QAction::setEnabled);
connect(this, &KUndo2Group::redoTextChanged,
result, &KUndo2Action::setPrefixedText);
connect(result, &QAction::triggered, this, &KUndo2Group::redo);
return result;
}
#endif // QT_NO_ACTION
/*! \fn void KUndo2Group::activeStackChanged(KUndo2QStack *stack)
This signal is emitted whenever the active stack of the group changes. This can happen
when setActiveStack() or KUndo2QStack::setActive() is called, or when the active stack
is removed form the group. \a stack is the new active stack. If no stack is active,
\a stack is 0.
\sa setActiveStack() KUndo2QStack::setActive()
*/
/*! \fn void KUndo2Group::indexChanged(int idx)
This signal is emitted whenever the active stack emits KUndo2QStack::indexChanged()
or the active stack changes.
\a idx is the new current index, or 0 if the active stack is 0.
\sa KUndo2QStack::indexChanged() setActiveStack()
*/
/*! \fn void KUndo2Group::cleanChanged(bool clean)
This signal is emitted whenever the active stack emits KUndo2QStack::cleanChanged()
or the active stack changes.
\a clean is the new state, or true if the active stack is 0.
\sa KUndo2QStack::cleanChanged() setActiveStack()
*/
/*! \fn void KUndo2Group::canUndoChanged(bool canUndo)
This signal is emitted whenever the active stack emits KUndo2QStack::canUndoChanged()
or the active stack changes.
\a canUndo is the new state, or false if the active stack is 0.
\sa KUndo2QStack::canUndoChanged() setActiveStack()
*/
/*! \fn void KUndo2Group::canRedoChanged(bool canRedo)
This signal is emitted whenever the active stack emits KUndo2QStack::canRedoChanged()
or the active stack changes.
\a canRedo is the new state, or false if the active stack is 0.
\sa KUndo2QStack::canRedoChanged() setActiveStack()
*/
/*! \fn void KUndo2Group::undoTextChanged(const QString &undoText)
This signal is emitted whenever the active stack emits KUndo2QStack::undoTextChanged()
or the active stack changes.
\a undoText is the new state, or an empty string if the active stack is 0.
\sa KUndo2QStack::undoTextChanged() setActiveStack()
*/
/*! \fn void KUndo2Group::redoTextChanged(const QString &redoText)
This signal is emitted whenever the active stack emits KUndo2QStack::redoTextChanged()
or the active stack changes.
\a redoText is the new state, or an empty string if the active stack is 0.
\sa KUndo2QStack::redoTextChanged() setActiveStack()
*/
#endif // QT_NO_UNDOGROUP
diff --git a/src/libs/kundo2/kundo2magicstring.cpp b/src/libs/kundo2/kundo2magicstring.cpp
index 3bf9bfc1..628b8be1 100644
--- a/src/libs/kundo2/kundo2magicstring.cpp
+++ b/src/libs/kundo2/kundo2magicstring.cpp
@@ -1,58 +1,59 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2014 Alexander Potashev <aspotashev@gmail.com>
*
* 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 "kundo2magicstring.h"
KUndo2MagicString::KUndo2MagicString()
{
}
KUndo2MagicString::KUndo2MagicString(const QString &text)
: m_text(text)
{
}
QString KUndo2MagicString::toString() const
{
int cdpos = m_text.indexOf(QLatin1Char('\n'));
return cdpos > 0 ? m_text.left(cdpos) : m_text;
}
QString KUndo2MagicString::toSecondaryString() const
{
int cdpos = m_text.indexOf(QLatin1Char('\n'));
return cdpos > 0 ? m_text.mid(cdpos + 1) : m_text;
}
bool KUndo2MagicString::isEmpty() const
{
return m_text.isEmpty();
}
bool KUndo2MagicString::operator==(const KUndo2MagicString &rhs) const
{
return m_text == rhs.m_text;
}
bool KUndo2MagicString::operator!=(const KUndo2MagicString &rhs) const
{
return !(*this == rhs);
}
diff --git a/src/libs/kundo2/kundo2model.cpp b/src/libs/kundo2/kundo2model.cpp
index 2c304e4f..2e8a7fde 100644
--- a/src/libs/kundo2/kundo2model.cpp
+++ b/src/libs/kundo2/kundo2model.cpp
@@ -1,217 +1,218 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
*
* 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.
*/
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
+// clazy:excludeall=qstring-arg
#include "kundo2model.h"
#include <klocalizedstring.h>
KUndo2Model::KUndo2Model(QObject *parent)
: QAbstractItemModel(parent)
{
m_stack = 0;
m_sel_model = new QItemSelectionModel(this, this);
connect(m_sel_model, &QItemSelectionModel::currentChanged, this, &KUndo2Model::setStackCurrentIndex);
m_emty_label = i18n("<empty>");
}
QItemSelectionModel *KUndo2Model::selectionModel() const
{
return m_sel_model;
}
KUndo2QStack *KUndo2Model::stack() const
{
return m_stack;
}
void KUndo2Model::setStack(KUndo2QStack *stack)
{
if (m_stack == stack)
return;
if (m_stack != 0) {
disconnect(m_stack, &KUndo2QStack::cleanChanged, this, &KUndo2Model::stackChanged);
disconnect(m_stack, &KUndo2QStack::indexChanged, this, &KUndo2Model::stackChanged);
disconnect(m_stack, &QObject::destroyed, this, &KUndo2Model::stackDestroyed);
}
m_stack = stack;
if (m_stack != 0) {
connect(m_stack, &KUndo2QStack::cleanChanged, this, &KUndo2Model::stackChanged);
connect(m_stack, &KUndo2QStack::indexChanged, this, &KUndo2Model::stackChanged);
connect(m_stack, &QObject::destroyed, this, &KUndo2Model::stackDestroyed);
}
stackChanged();
}
void KUndo2Model::stackDestroyed(QObject *obj)
{
if (obj != m_stack)
return;
m_stack = 0;
stackChanged();
}
void KUndo2Model::stackChanged()
{
beginResetModel();
endResetModel();
m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect);
}
void KUndo2Model::setStackCurrentIndex(const QModelIndex &index)
{
if (m_stack == 0)
return;
if (index == selectedIndex())
return;
if (index.column() != 0)
return;
m_stack->setIndex(index.row());
}
QModelIndex KUndo2Model::selectedIndex() const
{
return m_stack == 0 ? QModelIndex() : createIndex(m_stack->index(), 0);
}
QModelIndex KUndo2Model::index(int row, int column, const QModelIndex &parent) const
{
if (m_stack == 0)
return QModelIndex();
if (parent.isValid())
return QModelIndex();
if (column != 0)
return QModelIndex();
if (row < 0 || row > m_stack->count())
return QModelIndex();
return createIndex(row, column);
}
QModelIndex KUndo2Model::parent(const QModelIndex&) const
{
return QModelIndex();
}
int KUndo2Model::rowCount(const QModelIndex &parent) const
{
if (m_stack == 0)
return 0;
if (parent.isValid())
return 0;
return m_stack->count() + 1;
}
int KUndo2Model::columnCount(const QModelIndex&) const
{
return 1;
}
QVariant KUndo2Model::data(const QModelIndex &index, int role) const
{
if (m_stack == 0)
return QVariant();
if (index.column() != 0)
return QVariant();
if (index.row() < 0 || index.row() > m_stack->count())
return QVariant();
if (role == Qt::DisplayRole) {
if (index.row() == 0)
return m_emty_label;
return m_stack->text(index.row() - 1);
} else if (role == Qt::DecorationRole) {
if (index.row() == m_stack->cleanIndex() && !m_clean_icon.isNull())
return m_clean_icon;
}
return QVariant();
}
QString KUndo2Model::emptyLabel() const
{
return m_emty_label;
}
void KUndo2Model::setEmptyLabel(const QString &label)
{
m_emty_label = label;
stackChanged();
}
void KUndo2Model::setCleanIcon(const QIcon &icon)
{
m_clean_icon = icon;
stackChanged();
}
QIcon KUndo2Model::cleanIcon() const
{
return m_clean_icon;
}
diff --git a/src/libs/kundo2/kundo2stack.cpp b/src/libs/kundo2/kundo2stack.cpp
index 4c322800..c751c13f 100644
--- a/src/libs/kundo2/kundo2stack.cpp
+++ b/src/libs/kundo2/kundo2stack.cpp
@@ -1,1446 +1,1447 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
+// clazy:excludeall=qstring-arg
#include <QDebug>
#include <klocalizedstring.h>
#include <kstandardaction.h>
#include <kactioncollection.h>
#include "kundo2stack.h"
#include "kundo2stack_p.h"
#include "kundo2group.h"
#include <KoIcon.h>
#include<QtGlobal>
#ifndef QT_NO_UNDOCOMMAND
/*!
\class KUndo2Command
\brief The KUndo2Command class is the base class of all commands stored on a KUndo2QStack.
\since 4.2
For an overview of Qt's Undo Framework, see the
\l{Overview of Qt's Undo Framework}{overview document}.
A KUndo2Command represents a single editing action on a document; for example,
inserting or deleting a block of text in a text editor. KUndo2Command can apply
a change to the document with redo() and undo the change with undo(). The
implementations for these functions must be provided in a derived class.
\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 0
A KUndo2Command has an associated text(). This is a short string
describing what the command does. It is used to update the text
properties of the stack's undo and redo actions; see
KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction().
KUndo2Command objects are owned by the stack they were pushed on.
KUndo2QStack deletes a command if it has been undone and a new command is pushed. For example:
\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 1
In effect, when a command is pushed, it becomes the top-most command
on the stack.
To support command compression, KUndo2Command has an id() and the virtual function
mergeWith(). These functions are used by KUndo2QStack::push().
To support command macros, a KUndo2Command object can have any number of child
commands. Undoing or redoing the parent command will cause the child
commands to be undone or redone. A command can be assigned
to a parent explicitly in the constructor. In this case, the command
will be owned by the parent.
The parent in this case is usually an empty command, in that it doesn't
provide its own implementation of undo() and redo(). Instead, it uses
the base implementations of these functions, which simply call undo() or
redo() on all its children. The parent should, however, have a meaningful
text().
\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 2
Another way to create macros is to use the convenience functions
KUndo2QStack::beginMacro() and KUndo2QStack::endMacro().
\sa KUndo2QStack
*/
/*!
Constructs a KUndo2Command object with the given \a parent and \a text.
If \a parent is not 0, this command is appended to parent's child list.
The parent command then owns this command and will delete it in its
destructor.
\sa ~KUndo2Command()
*/
KUndo2Command::KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent):
m_hasParent(parent != 0),
m_timedID(0),
m_endOfCommand(QTime::currentTime())
{
d = new KUndo2CommandPrivate;
if (parent != 0) {
parent->d->child_list.append(this);
}
setText(text);
setTime();
}
/*!
Constructs a KUndo2Command object with parent \a parent.
If \a parent is not 0, this command is appended to parent's child list.
The parent command then owns this command and will delete it in its
destructor.
\sa ~KUndo2Command()
*/
KUndo2Command::KUndo2Command(KUndo2Command *parent):
m_hasParent(parent != 0),m_timedID(0)
{
d = new KUndo2CommandPrivate;
if (parent != 0)
parent->d->child_list.append(this);
setTime();
}
/*!
Destroys the KUndo2Command object and all child commands.
\sa KUndo2Command()
*/
KUndo2Command::~KUndo2Command()
{
qDeleteAll(d->child_list);
delete d;
}
/*!
Returns the ID of this command.
A command ID is used in command compression. It must be an integer unique to
this command's class, or -1 if the command doesn't support compression.
If the command supports compression this function must be overridden in the
derived class to return the correct ID. The base implementation returns -1.
KUndo2QStack::push() will only try to merge two commands if they have the
same ID, and the ID is not -1.
\sa mergeWith(), KUndo2QStack::push()
*/
int KUndo2Command::id() const
{
return -1;
}
/*!
Attempts to merge this command with \a command. Returns true on
success; otherwise returns false.
If this function returns true, calling this command's redo() must have the same
effect as redoing both this command and \a command.
Similarly, calling this command's undo() must have the same effect as undoing
\a command and this command.
KUndo2QStack will only try to merge two commands if they have the same id, and
the id is not -1.
The default implementation returns false.
\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 3
\sa id() KUndo2QStack::push()
*/
bool KUndo2Command::mergeWith(const KUndo2Command *command)
{
Q_UNUSED(command);
return false;
}
/*!
Applies a change to the document. This function must be implemented in
the derived class. Calling KUndo2QStack::push(),
KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to
undefined behavior.
The default implementation calls redo() on all child commands.
\sa undo()
*/
void KUndo2Command::redo()
{
for (int i = 0; i < d->child_list.size(); ++i)
d->child_list.at(i)->redo();
}
/*!
Reverts a change to the document. After undo() is called, the state of
the document should be the same as before redo() was called. This function must
be implemented in the derived class. Calling KUndo2QStack::push(),
KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to
undefined behavior.
The default implementation calls undo() on all child commands in reverse order.
\sa redo()
*/
void KUndo2Command::undo()
{
for (int i = d->child_list.size() - 1; i >= 0; --i)
d->child_list.at(i)->undo();
}
/*!
Returns a short text string describing what this command does; for example,
"insert text".
The text is used when the text properties of the stack's undo and redo
actions are updated.
\sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction()
*/
QString KUndo2Command::actionText() const
{
if(d->actionText!=0)
return d->actionText;
else
return QString();
}
/*!
Returns a short text string describing what this command does; for example,
"insert text".
The text is used when the text properties of the stack's undo and redo
actions are updated.
\sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction()
*/
KUndo2MagicString KUndo2Command::text() const
{
return d->text;
}
/*!
Sets the command's text to be the \a text specified.
The specified text should be a short user-readable string describing what this
command does.
\sa text() KUndo2QStack::createUndoAction() KUndo2QStack::createRedoAction()
*/
void KUndo2Command::setText(const KUndo2MagicString &undoText)
{
d->text = undoText;
d->actionText = undoText.toSecondaryString();
}
/*!
\since 4.4
Returns the number of child commands in this command.
\sa child()
*/
int KUndo2Command::childCount() const
{
return d->child_list.count();
}
/*!
\since 4.4
Returns the child command at \a index.
\sa childCount(), KUndo2QStack::command()
*/
const KUndo2Command *KUndo2Command::child(int index) const
{
if (index < 0 || index >= d->child_list.count())
return 0;
return d->child_list.at(index);
}
bool KUndo2Command::hasParent()
{
return m_hasParent;
}
int KUndo2Command::timedId()
{
return m_timedID;
}
void KUndo2Command::setTimedID(int value)
{
m_timedID = value;
}
bool KUndo2Command::timedMergeWith(KUndo2Command *other)
{
if(other->timedId() == this->timedId() && other->timedId()!=-1 )
m_mergeCommandsVector.append(other);
else
return false;
return true;
}
void KUndo2Command::setTime()
{
m_timeOfCreation = QTime::currentTime();
}
QTime KUndo2Command::time()
{
return m_timeOfCreation;
}
void KUndo2Command::setEndTime()
{
m_endOfCommand = QTime::currentTime();
}
QTime KUndo2Command::endTime()
{
return m_endOfCommand;
}
void KUndo2Command::undoMergedCommands()
{
undo();
if (!mergeCommandsVector().isEmpty()) {
QVectorIterator<KUndo2Command*> it(mergeCommandsVector());
it.toFront();
while (it.hasNext()) {
KUndo2Command* cmd = it.next();
cmd->undoMergedCommands();
}
}
}
void KUndo2Command::redoMergedCommands()
{
if (!mergeCommandsVector().isEmpty()) {
QVectorIterator<KUndo2Command*> it(mergeCommandsVector());
it.toBack();
while (it.hasPrevious()) {
KUndo2Command* cmd = it.previous();
cmd->redoMergedCommands();
}
}
redo();
}
QVector<KUndo2Command*> KUndo2Command::mergeCommandsVector()
{
return m_mergeCommandsVector;
}
bool KUndo2Command::isMerged()
{
return !m_mergeCommandsVector.isEmpty();
}
KUndo2CommandExtraData* KUndo2Command::extraData() const
{
return d->extraData.data();
}
void KUndo2Command::setExtraData(KUndo2CommandExtraData *data)
{
d->extraData.reset(data);
}
#endif // QT_NO_UNDOCOMMAND
#ifndef QT_NO_UNDOSTACK
/*!
\class KUndo2QStack
\brief The KUndo2QStack class is a stack of KUndo2Command objects.
\since 4.2
For an overview of Qt's Undo Framework, see the
\l{Overview of Qt's Undo Framework}{overview document}.
An undo stack maintains a stack of commands that have been applied to a
document.
New commands are pushed on the stack using push(). Commands can be
undone and redone using undo() and redo(), or by triggering the
actions returned by createUndoAction() and createRedoAction().
KUndo2QStack keeps track of the \a current command. This is the command
which will be executed by the next call to redo(). The index of this
command is returned by index(). The state of the edited object can be
rolled forward or back using setIndex(). If the top-most command on the
stack has already been redone, index() is equal to count().
KUndo2QStack provides support for undo and redo actions, command
compression, command macros, and supports the concept of a
\e{clean state}.
\section1 Undo and Redo Actions
KUndo2QStack provides convenient undo and redo QAction objects, which
can be inserted into a menu or a toolbar. When commands are undone or
redone, KUndo2QStack updates the text properties of these actions
to reflect what change they will trigger. The actions are also disabled
when no command is available for undo or redo. These actions
are returned by KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction().
\section1 Command Compression and Macros
Command compression is useful when several commands can be compressed
into a single command that can be undone and redone in a single operation.
For example, when a user types a character in a text editor, a new command
is created. This command inserts the character into the document at the
cursor position. However, it is more convenient for the user to be able
to undo or redo typing of whole words, sentences, or paragraphs.
Command compression allows these single-character commands to be merged
into a single command which inserts or deletes sections of text.
For more information, see KUndo2Command::mergeWith() and push().
A command macro is a sequence of commands, all of which are undone and
redone in one go. Command macros are created by giving a command a list
of child commands.
Undoing or redoing the parent command will cause the child commands to
be undone or redone. Command macros may be created explicitly
by specifying a parent in the KUndo2Command constructor, or by using the
convenience functions beginMacro() and endMacro().
Although command compression and macros appear to have the same effect to the
user, they often have different uses in an application. Commands that
perform small changes to a document may be usefully compressed if there is
no need to individually record them, and if only larger changes are relevant
to the user.
However, for commands that need to be recorded individually, or those that
cannot be compressed, it is useful to use macros to provide a more convenient
user experience while maintaining a record of each command.
\section1 Clean State
KUndo2QStack supports the concept of a clean state. When the
document is saved to disk, the stack can be marked as clean using
setClean(). Whenever the stack returns to this state through undoing and
redoing commands, it emits the signal cleanChanged(). This signal
is also emitted when the stack leaves the clean state. This signal is
usually used to enable and disable the save actions in the application,
and to update the document's title to reflect that it contains unsaved
changes.
\sa KUndo2Command, KUndo2View
*/
#ifndef QT_NO_ACTION
KUndo2Action::KUndo2Action(const QString &textTemplate, const QString &defaultText, QObject *parent)
: QAction(parent)
{
m_textTemplate = textTemplate;
m_defaultText = defaultText;
}
void KUndo2Action::setPrefixedText(const QString &text)
{
if (text.isEmpty())
setText(m_defaultText);
else
setText(m_textTemplate.arg(text));
}
#endif // QT_NO_ACTION
/*! \internal
Sets the current index to \a idx, emitting appropriate signals. If \a clean is true,
makes \a idx the clean index as well.
*/
void KUndo2QStack::setIndex(int idx, bool clean)
{
bool was_clean = m_index == m_clean_index;
if (m_lastMergedIndex <= idx) {
m_lastMergedSetCount = idx - m_lastMergedIndex;
} else {
m_lastMergedSetCount = 1;
m_lastMergedIndex = idx-1;
}
if(idx == 0){
m_lastMergedSetCount = 0;
m_lastMergedIndex = 0;
}
if (idx != m_index) {
m_index = idx;
emit indexChanged(m_index);
emit canUndoChanged(canUndo());
emit undoTextChanged(undoText());
emit canRedoChanged(canRedo());
emit redoTextChanged(redoText());
}
if (clean)
m_clean_index = m_index;
bool is_clean = m_index == m_clean_index;
if (is_clean != was_clean)
emit cleanChanged(is_clean);
}
void KUndo2QStack::purgeRedoState()
{
bool macro = !m_macro_stack.isEmpty();
if (macro) return;
bool redoStateChanged = false;
bool cleanStateChanged = false;
while (m_index < m_command_list.size()) {
delete m_command_list.takeLast();
redoStateChanged = true;
}
if (m_clean_index > m_index) {
m_clean_index = -1; // we've deleted the clean state
cleanStateChanged = true;
}
if (redoStateChanged) {
emit canRedoChanged(canRedo());
emit redoTextChanged(redoText());
}
if (cleanStateChanged) {
emit cleanChanged(isClean());
}
}
/*! \internal
If the number of commands on the stack exceedes the undo limit, deletes commands from
the bottom of the stack.
Returns true if commands were deleted.
*/
bool KUndo2QStack::checkUndoLimit()
{
if (m_undo_limit <= 0 || !m_macro_stack.isEmpty() || m_undo_limit >= m_command_list.count())
return false;
int del_count = m_command_list.count() - m_undo_limit;
for (int i = 0; i < del_count; ++i)
delete m_command_list.takeFirst();
m_index -= del_count;
if (m_clean_index != -1) {
if (m_clean_index < del_count)
m_clean_index = -1; // we've deleted the clean command
else
m_clean_index -= del_count;
}
return true;
}
/*!
Constructs an empty undo stack with the parent \a parent. The
stack will initially be in the clean state. If \a parent is a
KUndo2Group object, the stack is automatically added to the group.
\sa push()
*/
KUndo2QStack::KUndo2QStack(QObject *parent)
: QObject(parent), m_index(0), m_clean_index(0), m_group(0), m_undo_limit(0), m_useCumulativeUndoRedo(false), m_lastMergedSetCount(0), m_lastMergedIndex(0)
{
setTimeT1(5);
setTimeT2(1);
setStrokesN(2);
#ifndef QT_NO_UNDOGROUP
if (KUndo2Group *group = qobject_cast<KUndo2Group*>(parent))
group->addStack(this);
#endif
}
/*!
Destroys the undo stack, deleting any commands that are on it. If the
stack is in a KUndo2Group, the stack is automatically removed from the group.
\sa KUndo2QStack()
*/
KUndo2QStack::~KUndo2QStack()
{
#ifndef QT_NO_UNDOGROUP
if (m_group != 0)
m_group->removeStack(this);
#endif
clear();
}
/*!
Clears the command stack by deleting all commands on it, and returns the stack
to the clean state.{
}
Commands are not undone or redone; the state of the edited object remains
unchanged.
This function is usually used when the contents of the document are
abandoned.
\sa KUndo2QStack()
*/
void KUndo2QStack::clear()
{
if (m_command_list.isEmpty())
return;
bool was_clean = isClean();
m_macro_stack.clear();
qDeleteAll(m_command_list);
m_command_list.clear();
m_index = 0;
m_clean_index = 0;
emit indexChanged(0);
emit canUndoChanged(false);
emit undoTextChanged(QString());
emit canRedoChanged(false);
emit redoTextChanged(QString());
if (!was_clean)
emit cleanChanged(true);
}
/*!
Pushes \a cmd on the stack or merges it with the most recently executed command.
In either case, executes \a cmd by calling its redo() function.
If \a cmd's id is not -1, and if the id is the same as that of the
most recently executed command, KUndo2QStack will attempt to merge the two
commands by calling KUndo2Command::mergeWith() on the most recently executed
command. If KUndo2Command::mergeWith() returns true, \a cmd is deleted and false
is returned.
In all other cases \a cmd is simply pushed on the stack and true is returned.
If commands were undone before \a cmd was pushed, the current command and
all commands above it are deleted. Hence \a cmd always ends up being the
top-most on the stack.
Once a command is pushed, the stack takes ownership of it. There
are no getters to return the command, since modifying it after it has
been executed will almost always lead to corruption of the document's
state.
\sa KUndo2Command::id() KUndo2Command::mergeWith()
*/
bool KUndo2QStack::push(KUndo2Command *cmd)
{
cmd->redoMergedCommands();
cmd->setEndTime();
bool macro = !m_macro_stack.isEmpty();
KUndo2Command *cur = 0;
if (macro) {
KUndo2Command *macro_cmd = m_macro_stack.last();
if (!macro_cmd->d->child_list.isEmpty())
cur = macro_cmd->d->child_list.last();
} else {
if (m_index > 0)
cur = m_command_list.at(m_index - 1);
while (m_index < m_command_list.size())
delete m_command_list.takeLast();
if (m_clean_index > m_index)
m_clean_index = -1; // we've deleted the clean state
}
bool try_merge = cur != 0
&& cur->id() != -1
&& cur->id() == cmd->id()
&& (macro || m_index != m_clean_index);
/*!
*Here we are going to try to merge several commands together using the QVector field in the commands using
*3 parameters. N : Number of commands that should remain individual at the top of the stack. T1 : Time lapsed between current command and previously merged command -- signal to
*merge throughout the stack. T2 : Time lapsed between two commands signalling both commands belong to the same set
*Whenever a KUndo2Command is initialized -- it consists of a start-time and when it is pushed --an end time.
*Every time a command is pushed -- it checks whether the command pushed was pushed after T1 seconds of the last merged command
*Then the merging begins with each group depending on the time in between each command (T2).
*
*@TODO : Currently it is not able to merge two merged commands together.
*/
if (!macro && m_command_list.size() > 1 && cmd->timedId() != -1 && m_useCumulativeUndoRedo) {
KUndo2Command* lastcmd = m_command_list.last();
if (qAbs(cmd->time().msecsTo(lastcmd->endTime())) < m_timeT2 * 1000) {
m_lastMergedSetCount++;
} else {
m_lastMergedSetCount = 0;
m_lastMergedIndex = m_index-1;
}
if (lastcmd->timedId() == -1){
m_lastMergedSetCount = 0;
m_lastMergedIndex = m_index;
}
if (m_lastMergedSetCount > m_strokesN) {
KUndo2Command* toMerge = m_command_list.at(m_lastMergedIndex);
if (toMerge && m_command_list.size() >= m_lastMergedIndex + 1 && m_command_list.at(m_lastMergedIndex + 1)) {
if(toMerge->timedMergeWith(m_command_list.at(m_lastMergedIndex + 1))){
m_command_list.removeAt(m_lastMergedIndex + 1);
}
m_lastMergedSetCount--;
m_lastMergedIndex = m_command_list.indexOf(toMerge);
}
}
m_index = m_command_list.size();
if(m_lastMergedIndex<m_index){
if (cmd->time().msecsTo(m_command_list.at(m_lastMergedIndex)->endTime()) < -m_timeT1 * 1000) { //T1 time elapsed
QListIterator<KUndo2Command*> it(m_command_list);
it.toBack();
m_lastMergedSetCount = 1;
while (it.hasPrevious()) {
KUndo2Command* curr = it.previous();
KUndo2Command* lastCmdInCurrent = curr;
if (!lastcmd->mergeCommandsVector().isEmpty()) {
if (qAbs(lastcmd->mergeCommandsVector().constLast()->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent && lastcmd != curr) {
if(lastcmd->timedMergeWith(curr)){
if (m_command_list.contains(curr)) {
m_command_list.removeOne(curr);
}
}
} else {
lastcmd = curr; //end of a merge set
}
} else {
if (qAbs(lastcmd->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent &&lastcmd!=curr) {
if(lastcmd->timedMergeWith(curr)){
if (m_command_list.contains(curr)){
m_command_list.removeOne(curr);
}
}
} else {
lastcmd = curr; //end of a merge set
}
}
}
m_lastMergedIndex = m_command_list.size()-1;
}
}
m_index = m_command_list.size();
}
if (try_merge && cur->mergeWith(cmd)) {
delete cmd;
cmd = 0;
if (!macro) {
emit indexChanged(m_index);
emit canUndoChanged(canUndo());
emit undoTextChanged(undoText());
emit canRedoChanged(canRedo());
emit redoTextChanged(redoText());
}
} else {
if (macro) {
m_macro_stack.last()->d->child_list.append(cmd);
} else {
m_command_list.append(cmd);
if(checkUndoLimit())
{
m_lastMergedIndex = m_index - m_strokesN;
}
setIndex(m_index + 1, false);
}
}
return cmd;
}
/*!
Marks the stack as clean and emits cleanChanged() if the stack was
not already clean.
Whenever the stack returns to this state through the use of undo/redo
commands, it emits the signal cleanChanged(). This signal is also
emitted when the stack leaves the clean state.
\sa isClean(), cleanIndex()
*/
void KUndo2QStack::setClean()
{
if (!m_macro_stack.isEmpty()) {
qWarning("KUndo2QStack::setClean(): cannot set clean in the middle of a macro");
return;
}
setIndex(m_index, true);
}
/*!
If the stack is in the clean state, returns true; otherwise returns false.
\sa setClean() cleanIndex()
*/
bool KUndo2QStack::isClean() const
{
if (!m_macro_stack.isEmpty())
return false;
return m_clean_index == m_index;
}
/*!
Returns the clean index. This is the index at which setClean() was called.
A stack may not have a clean index. This happens if a document is saved,
some commands are undone, then a new command is pushed. Since
push() deletes all the undone commands before pushing the new command, the stack
can't return to the clean state again. In this case, this function returns -1.
\sa isClean() setClean()
*/
int KUndo2QStack::cleanIndex() const
{
return m_clean_index;
}
/*!
Undoes the command below the current command by calling KUndo2Command::undo().
Decrements the current command index.
If the stack is empty, or if the bottom command on the stack has already been
undone, this function does nothing.
\sa redo() index()
*/
void KUndo2QStack::undo()
{
if (m_index == 0)
return;
if (!m_macro_stack.isEmpty()) {
qWarning("KUndo2QStack::undo(): cannot undo in the middle of a macro");
return;
}
int idx = m_index - 1;
m_command_list.at(idx)->undoMergedCommands();
setIndex(idx, false);
}
/*!
Redoes the current command by calling KUndo2Command::redo(). Increments the current
command index.
If the stack is empty, or if the top command on the stack has already been
redone, this function does nothing.
\sa undo() index()
*/
void KUndo2QStack::redo()
{
if (m_index == m_command_list.size())
return;
if (!m_macro_stack.isEmpty()) {
qWarning("KUndo2QStack::redo(): cannot redo in the middle of a macro");
return;
}
m_command_list.at(m_index)->redoMergedCommands();
setIndex(m_index + 1, false);
}
/*!
Returns the number of commands on the stack. Macro commands are counted as
one command.
\sa index() setIndex() command()
*/
int KUndo2QStack::count() const
{
return m_command_list.size();
}
/*!
Returns the index of the current command. This is the command that will be
executed on the next call to redo(). It is not always the top-most command
on the stack, since a number of commands may have been undone.
\sa undo() redo() count()
*/
int KUndo2QStack::index() const
{
return m_index;
}
/*!
Repeatedly calls undo() or redo() until the current command index reaches
\a idx. This function can be used to roll the state of the document forwards
of backwards. indexChanged() is emitted only once.
\sa index() count() undo() redo()
*/
void KUndo2QStack::setIndex(int idx)
{
if (!m_macro_stack.isEmpty()) {
qWarning("KUndo2QStack::setIndex(): cannot set index in the middle of a macro");
return;
}
if (idx < 0)
idx = 0;
else if (idx > m_command_list.size())
idx = m_command_list.size();
int i = m_index;
while (i < idx) {
m_command_list.at(i++)->redoMergedCommands();
notifySetIndexChangedOneCommand();
}
while (i > idx) {
m_command_list.at(--i)->undoMergedCommands();
notifySetIndexChangedOneCommand();
}
setIndex(idx, false);
}
/**
* Called by setIndex after every command execution. It is needed by
* Krita to insert barriers between different kind of commands
*/
void KUndo2QStack::notifySetIndexChangedOneCommand()
{
}
/*!
Returns true if there is a command available for undo; otherwise returns false.
This function returns false if the stack is empty, or if the bottom command
on the stack has already been undone.
Synonymous with index() == 0.
\sa index() canRedo()
*/
bool KUndo2QStack::canUndo() const
{
if (!m_macro_stack.isEmpty())
return false;
return m_index > 0;
}
/*!
Returns true if there is a command available for redo; otherwise returns false.
This function returns false if the stack is empty or if the top command
on the stack has already been redone.
Synonymous with index() == count().
\sa index() canUndo()
*/
bool KUndo2QStack::canRedo() const
{
if (!m_macro_stack.isEmpty())
return false;
return m_index < m_command_list.size();
}
/*!
Returns the text of the command which will be undone in the next call to undo().
\sa KUndo2Command::text() redoActionText() undoItemText()
*/
QString KUndo2QStack::undoText() const
{
if (!m_macro_stack.isEmpty())
return QString();
if (m_index > 0 && m_command_list.at(m_index-1)!=0)
return m_command_list.at(m_index - 1)->actionText();
return QString();
}
/*!
Returns the text of the command which will be redone in the next call to redo().
\sa KUndo2Command::text() undoActionText() redoItemText()
*/
QString KUndo2QStack::redoText() const
{
if (!m_macro_stack.isEmpty())
return QString();
if (m_index < m_command_list.size())
return m_command_list.at(m_index)->actionText();
return QString();
}
#ifndef QT_NO_ACTION
/*!
Creates an undo QAction object with the given \a parent.
Triggering this action will cause a call to undo(). The text of this action
is the text of the command which will be undone in the next call to undo(),
prefixed by the specified \a prefix. If there is no command available for undo,
this action will be disabled.
If \a prefix is empty, the default prefix "Undo" is used.
\sa createRedoAction(), canUndo(), KUndo2Command::text()
*/
QAction *KUndo2QStack::createUndoAction(QObject *parent) const
{
KUndo2Action *result = new KUndo2Action(i18n("Undo %1"), i18nc("Default text for undo action", "Undo"), parent);
result->setEnabled(canUndo());
result->setPrefixedText(undoText());
connect(this, &KUndo2QStack::canUndoChanged,
result, &QAction::setEnabled);
connect(this, &KUndo2QStack::undoTextChanged,
result, &KUndo2Action::setPrefixedText);
connect(result, &QAction::triggered, this, &KUndo2QStack::undo);
return result;
}
/*!
Creates an redo QAction object with the given \a parent.
Triggering this action will cause a call to redo(). The text of this action
is the text of the command which will be redone in the next call to redo(),
prefixed by the specified \a prefix. If there is no command available for redo,
this action will be disabled.
If \a prefix is empty, the default prefix "Redo" is used.
\sa createUndoAction(), canRedo(), KUndo2Command::text()
*/
QAction *KUndo2QStack::createRedoAction(QObject *parent) const
{
KUndo2Action *result = new KUndo2Action(i18n("Redo %1"), i18nc("Default text for redo action", "Redo"), parent);
result->setEnabled(canRedo());
result->setPrefixedText(redoText());
connect(this, &KUndo2QStack::canRedoChanged,
result, &QAction::setEnabled);
connect(this, &KUndo2QStack::redoTextChanged,
result, &KUndo2Action::setPrefixedText);
connect(result, &QAction::triggered, this, &KUndo2QStack::redo);
return result;
}
#endif // QT_NO_ACTION
/*!
Begins composition of a macro command with the given \a text description.
An empty command described by the specified \a text is pushed on the stack.
Any subsequent commands pushed on the stack will be appended to the empty
command's children until endMacro() is called.
Calls to beginMacro() and endMacro() may be nested, but every call to
beginMacro() must have a matching call to endMacro().
While a macro is composed, the stack is disabled. This means that:
\list
\i indexChanged() and cleanChanged() are not emitted,
\i canUndo() and canRedo() return false,
\i calling undo() or redo() has no effect,
\i the undo/redo actions are disabled.
\endlist
The stack becomes enabled and appropriate signals are emitted when endMacro()
is called for the outermost macro.
\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 4
This code is equivalent to:
\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 5
\sa endMacro()
*/
void KUndo2QStack::beginMacro(const KUndo2MagicString &text)
{
KUndo2Command *cmd = new KUndo2Command();
cmd->setText(text);
if (m_macro_stack.isEmpty()) {
while (m_index < m_command_list.size())
delete m_command_list.takeLast();
if (m_clean_index > m_index)
m_clean_index = -1; // we've deleted the clean state
m_command_list.append(cmd);
} else {
m_macro_stack.last()->d->child_list.append(cmd);
}
m_macro_stack.append(cmd);
if (m_macro_stack.count() == 1) {
emit canUndoChanged(false);
emit undoTextChanged(QString());
emit canRedoChanged(false);
emit redoTextChanged(QString());
}
}
/*!
Ends composition of a macro command.
If this is the outermost macro in a set nested macros, this function emits
indexChanged() once for the entire macro command.
\sa beginMacro()
*/
void KUndo2QStack::endMacro()
{
if (m_macro_stack.isEmpty()) {
qWarning("KUndo2QStack::endMacro(): no matching beginMacro()");
return;
}
m_macro_stack.removeLast();
if (m_macro_stack.isEmpty()) {
checkUndoLimit();
setIndex(m_index + 1, false);
}
}
/*!
\since 4.4
Returns a const pointer to the command at \a index.
This function returns a const pointer, because modifying a command,
once it has been pushed onto the stack and executed, almost always
causes corruption of the state of the document, if the command is
later undone or redone.
\sa KUndo2Command::child()
*/
const KUndo2Command *KUndo2QStack::command(int index) const
{
if (index < 0 || index >= m_command_list.count())
return 0;
return m_command_list.at(index);
}
/*!
Returns the text of the command at index \a idx.
\sa beginMacro()
*/
QString KUndo2QStack::text(int idx) const
{
if (idx < 0 || idx >= m_command_list.size())
return QString();
return m_command_list.at(idx)->text().toString();
}
/*!
\property KUndo2QStack::undoLimit
\brief the maximum number of commands on this stack.
\since 4.3
When the number of commands on a stack exceedes the stack's undoLimit, commands are
deleted from the bottom of the stack. Macro commands (commands with child commands)
are treated as one command. The default value is 0, which means that there is no
limit.
This property may only be set when the undo stack is empty, since setting it on a
non-empty stack might delete the command at the current index. Calling setUndoLimit()
on a non-empty stack prints a warning and does nothing.
*/
void KUndo2QStack::setUndoLimit(int limit)
{
if (!m_command_list.isEmpty()) {
qWarning("KUndo2QStack::setUndoLimit(): an undo limit can only be set when the stack is empty");
return;
}
if (limit == m_undo_limit)
return;
m_undo_limit = limit;
checkUndoLimit();
emit undoLimitChanged(m_undo_limit);
}
int KUndo2QStack::undoLimit() const
{
return m_undo_limit;
}
/*!
\property KUndo2QStack::active
\brief the active status of this stack.
An application often has multiple undo stacks, one for each opened document. The active
stack is the one associated with the currently active document. If the stack belongs
to a KUndo2Group, calls to KUndo2Group::undo() or KUndo2Group::redo() will be forwarded
to this stack when it is active. If the KUndo2Group is watched by a KUndo2View, the view
will display the contents of this stack when it is active. If the stack does not belong to
a KUndo2Group, making it active has no effect.
It is the programmer's responsibility to specify which stack is active by
calling setActive(), usually when the associated document window receives focus.
\sa KUndo2Group
*/
void KUndo2QStack::setActive(bool active)
{
#ifdef QT_NO_UNDOGROUP
Q_UNUSED(active);
#else
bool prev = isActive();
if (m_group != 0) {
if (active)
m_group->setActiveStack(this);
else if (m_group->activeStack() == this)
m_group->setActiveStack(0);
}
if (isActive() != prev) {
emit activeChanged(!prev);
}
#endif
}
bool KUndo2QStack::isActive() const
{
#ifdef QT_NO_UNDOGROUP
return true;
#else
return m_group == 0 || m_group->activeStack() == this;
#endif
}
void KUndo2QStack::setUseCumulativeUndoRedo(bool value)
{
m_useCumulativeUndoRedo = value;
}
bool KUndo2QStack::useCumulativeUndoRedo()
{
return m_useCumulativeUndoRedo;
}
void KUndo2QStack::setTimeT1(double value)
{
m_timeT1 = value;
}
double KUndo2QStack::timeT1()
{
return m_timeT1;
}
void KUndo2QStack::setTimeT2(double value)
{
m_timeT2 = value;
}
double KUndo2QStack::timeT2()
{
return m_timeT2;
}
int KUndo2QStack::strokesN()
{
return m_strokesN;
}
void KUndo2QStack::setStrokesN(int value)
{
m_strokesN = value;
}
QAction* KUndo2Stack::createRedoAction(KActionCollection* actionCollection, const QString& actionName)
{
QAction* action = KUndo2QStack::createRedoAction(actionCollection);
if (actionName.isEmpty()) {
action->setObjectName(KStandardAction::name(KStandardAction::Redo));
} else {
action->setObjectName(actionName);
}
action->setIcon(koIcon("edit-redo"));
action->setIconText(i18n("Redo"));
action->setShortcuts(KStandardShortcut::redo());
actionCollection->addAction(action->objectName(), action);
return action;
}
QAction* KUndo2Stack::createUndoAction(KActionCollection* actionCollection, const QString& actionName)
{
QAction* action = KUndo2QStack::createUndoAction(actionCollection);
if (actionName.isEmpty()) {
action->setObjectName(KStandardAction::name(KStandardAction::Undo));
} else {
action->setObjectName(actionName);
}
action->setIcon(koIcon("edit-undo"));
action->setIconText(i18n("Undo"));
action->setShortcuts(KStandardShortcut::undo());
actionCollection->addAction(action->objectName(), action);
return action;
}
/*!
\fn void KUndo2QStack::indexChanged(int idx)
This signal is emitted whenever a command modifies the state of the document.
This happens when a command is undone or redone. When a macro
command is undone or redone, or setIndex() is called, this signal
is emitted only once.
\a idx specifies the index of the current command, ie. the command which will be
executed on the next call to redo().
\sa index() setIndex()
*/
/*!
\fn void KUndo2QStack::cleanChanged(bool clean)
This signal is emitted whenever the stack enters or leaves the clean state.
If \a clean is true, the stack is in a clean state; otherwise this signal
indicates that it has left the clean state.
\sa isClean() setClean()
*/
/*!
\fn void KUndo2QStack::undoTextChanged(const QString &undoText)
This signal is emitted whenever the value of undoText() changes. It is
used to update the text property of the undo action returned by createUndoAction().
\a undoText specifies the new text.
*/
/*!
\fn void KUndo2QStack::canUndoChanged(bool canUndo)
This signal is emitted whenever the value of canUndo() changes. It is
used to enable or disable the undo action returned by createUndoAction().
\a canUndo specifies the new value.
*/
/*!
\fn void KUndo2QStack::redoTextChanged(const QString &redoText)
This signal is emitted whenever the value of redoText() changes. It is
used to update the text property of the redo action returned by createRedoAction().
\a redoText specifies the new text.
*/
/*!
\fn void KUndo2QStack::canRedoChanged(bool canRedo)
This signal is emitted whenever the value of canRedo() changes. It is
used to enable or disable the redo action returned by createRedoAction().
\a canRedo specifies the new value.
*/
KUndo2Stack::KUndo2Stack(QObject *parent):
KUndo2QStack(parent)
{
}
#endif // QT_NO_UNDOSTACK
diff --git a/src/libs/kundo2/kundo2view.cpp b/src/libs/kundo2/kundo2view.cpp
index cf49cdda..37309c48 100644
--- a/src/libs/kundo2/kundo2view.cpp
+++ b/src/libs/kundo2/kundo2view.cpp
@@ -1,285 +1,286 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
*
* 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.
*/
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
+// clazy:excludeall=qstring-arg
#include "kundo2stack.h"
#include "kundo2view.h"
#include "kundo2model.h"
#include "kundo2group.h"
#ifndef QT_NO_UNDOVIEW
#include <QAbstractItemModel>
#include <QPointer>
#include <QIcon>
/*!
\class KUndo2View
\brief The KUndo2View class displays the contents of a KUndo2QStack.
\since 4.2
\ingroup advanced
KUndo2View is a QListView which displays the list of commands pushed on an undo stack.
The most recently executed command is always selected. Selecting a different command
results in a call to KUndo2QStack::setIndex(), rolling the state of the document
backwards or forward to the new command.
The stack can be set explicitly with setStack(). Alternatively, a QUndoGroup object can
be set with setGroup(). The view will then update itself automatically whenever the
active stack of the group changes.
*/
class KUndo2ViewPrivate
{
public:
KUndo2ViewPrivate() :
#ifndef QT_NO_UNDOGROUP
group(0),
#endif
model(0) {}
#ifndef QT_NO_UNDOGROUP
QPointer<KUndo2Group> group;
#endif
KUndo2Model *model;
KUndo2View* q;
void init(KUndo2View* view);
};
void KUndo2ViewPrivate::init(KUndo2View* view)
{
q = view;
model = new KUndo2Model(q);
q->setModel(model);
q->setSelectionModel(model->selectionModel());
}
/*!
Constructs a new view with parent \a parent.
*/
KUndo2View::KUndo2View(QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate)
{
d->init(this);
}
/*!
Constructs a new view with parent \a parent and sets the observed stack to \a stack.
*/
KUndo2View::KUndo2View(KUndo2QStack *stack, QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate)
{
d->init(this);
setStack(stack);
}
#ifndef QT_NO_UNDOGROUP
/*!
Constructs a new view with parent \a parent and sets the observed group to \a group.
The view will update itself autmiatically whenever the active stack of the group changes.
*/
KUndo2View::KUndo2View(KUndo2Group *group, QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate)
{
d->init(this);
setGroup(group);
}
#endif // QT_NO_UNDOGROUP
/*!
Destroys this view.
*/
KUndo2View::~KUndo2View()
{
delete d;
}
/*!
Returns the stack currently displayed by this view. If the view is looking at a
QUndoGroup, this the group's active stack.
\sa setStack() setGroup()
*/
KUndo2QStack *KUndo2View::stack() const
{
return d->model->stack();
}
/*!
Sets the stack displayed by this view to \a stack. If \a stack is 0, the view
will be empty.
If the view was previously looking at a QUndoGroup, the group is set to 0.
\sa stack() setGroup()
*/
void KUndo2View::setStack(KUndo2QStack *stack)
{
#ifndef QT_NO_UNDOGROUP
setGroup(0);
#endif
d->model->setStack(stack);
}
#ifndef QT_NO_UNDOGROUP
/*!
Sets the group displayed by this view to \a group. If \a group is 0, the view will
be empty.
The view will update itself autmiatically whenever the active stack of the group changes.
\sa group() setStack()
*/
void KUndo2View::setGroup(KUndo2Group *group)
{
if (d->group == group)
return;
if (d->group != 0) {
disconnect(d->group.data(), &KUndo2Group::activeStackChanged,
d->model, &KUndo2Model::setStack);
}
d->group = group;
if (d->group != 0) {
connect(d->group.data(), &KUndo2Group::activeStackChanged,
d->model, &KUndo2Model::setStack);
d->model->setStack((KUndo2QStack *)d->group->activeStack());
} else {
d->model->setStack(0);
}
}
/*!
Returns the group displayed by this view.
If the view is not looking at group, this function returns 0.
\sa setGroup() setStack()
*/
KUndo2Group *KUndo2View::group() const
{
return d->group;
}
#endif // QT_NO_UNDOGROUP
/*!
\property KUndo2View::emptyLabel
\brief the label used for the empty state.
The empty label is the topmost element in the list of commands, which represents
the state of the document before any commands were pushed on the stack. The default
is the string "<empty>".
*/
void KUndo2View::setEmptyLabel(const QString &label)
{
bool changed = d->model->emptyLabel() != label;
d->model->setEmptyLabel(label);
if (changed) {
emit emptyLabelChanged();
}
}
QString KUndo2View::emptyLabel() const
{
return d->model->emptyLabel();
}
/*!
\property KUndo2View::cleanIcon
\brief the icon used to represent the clean state.
A stack may have a clean state set with KUndo2QStack::setClean(). This is usually
the state of the document at the point it was saved. KUndo2View can display an
icon in the list of commands to show the clean state. If this property is
a null icon, no icon is shown. The default value is the null icon.
*/
void KUndo2View::setCleanIcon(const QIcon &icon)
{
d->model->setCleanIcon(icon);
emit cleanIconChanged();
}
QIcon KUndo2View::cleanIcon() const
{
return d->model->cleanIcon();
}
#endif // QT_NO_UNDOVIEW
diff --git a/src/libs/main/Calligra2Migration.cpp b/src/libs/main/Calligra2Migration.cpp
index c33d9964..97e59924 100644
--- a/src/libs/main/Calligra2Migration.cpp
+++ b/src/libs/main/Calligra2Migration.cpp
@@ -1,146 +1,147 @@
/* This file is part of the KDE project
* Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
*
* 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 "Calligra2Migration.h"
#include "KoResourcePaths.h"
#include <kdelibs4configmigrator.h>
#include <kdelibs4migration.h>
#include <QStandardPaths>
#include <QDir>
#include <QPluginLoader>
#include <QLoggingCategory>
#include <QDebug>
Q_DECLARE_LOGGING_CATEGORY(CALLIGRA2MIGRATION)
Q_LOGGING_CATEGORY(CALLIGRA2MIGRATION, "calligra.lib.migration")
Calligra2Migration::Calligra2Migration(const QString &appName, const QString &oldAppName)
: m_newAppName(appName)
, m_oldAppName(oldAppName)
{
qCDebug(CALLIGRA2MIGRATION)<<appName<<oldAppName;
}
void Calligra2Migration::setConfigFiles(const QStringList &configFiles)
{
m_configFiles = configFiles;
}
void Calligra2Migration::setUiFiles(const QStringList &uiFiles)
{
m_uiFiles = uiFiles;
}
void Calligra2Migration::migrate()
{
const QString newdatapath = KoResourcePaths::saveLocation("data", m_newAppName, false);
QDir newdatadir(newdatapath);
if (newdatadir.exists()) {
// assume we have migrated
qCDebug(CALLIGRA2MIGRATION)<<"migration has been done";
return;
}
// do common calligra in case not yet migrated
Kdelibs4ConfigMigrator m("calligra");
m.setConfigFiles(QStringList() << QStringLiteral("calligrarc"));
m.setUiFiles(QStringList() << QStringLiteral("calligra_shell.rc") << QStringLiteral("osx.stylesheet"));
m.migrate();
bool didSomething = false;
Kdelibs4ConfigMigrator cm(m_oldAppName.isEmpty() ? m_newAppName : m_oldAppName);
cm.setConfigFiles(m_configFiles);
cm.setUiFiles(m_uiFiles);
if (cm.migrate() && !m_oldAppName.isEmpty()) {
// rename config files to new names
qCDebug(CALLIGRA2MIGRATION)<<"rename config files to new names"<<m_configFiles;
for (const QString &oldname : qAsConst(m_configFiles)) {
QString newname = oldname;
newname.replace(m_oldAppName, m_newAppName);
if (oldname == newname) {
continue;
}
QString oldfile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, oldname);
if (!oldfile.isEmpty()) {
qCDebug(CALLIGRA2MIGRATION)<<"config rename:"<<oldfile;
QFile f(oldfile);
QFileInfo fi(f);
f.rename(fi.absolutePath() + '/' + newname);
didSomething = true;
qCDebug(CALLIGRA2MIGRATION)<<"config renamed:"<<f.fileName();
}
}
// subdirectory must be renamed
// eg: .local/share/kxmlgui5/ + m_oldAppName
// rename to: .local/share/kxmlgui5/ + m_newAppName
const QString loc = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kxmlgui5/");
const QString oldui = loc + m_oldAppName;
const QString newui = loc + m_newAppName;
qCDebug(CALLIGRA2MIGRATION)<<"rename ui dir:"<<oldui;
QDir newdir(newui);
if (!newdir.exists()) {
newdir.rename(oldui, newui);
didSomething = true;
qCDebug(CALLIGRA2MIGRATION)<<"renamed ui dir:"<<newui;
}
qCDebug(CALLIGRA2MIGRATION)<<"rename ui files to new names"<<m_uiFiles;
for (const QString &oldname : qAsConst(m_uiFiles)) {
QString newname = oldname;
newname.replace(m_oldAppName, m_newAppName);
if (oldname == newname) {
continue;
}
const QString oldfile = newui + QLatin1Char('/') + oldname;
QFile f(oldfile);
if (f.exists()) {
const QString newfile = newui + QLatin1Char('/') + newname;
f.rename(newfile);
didSomething = true;
QFileInfo fi(f);
qCDebug(CALLIGRA2MIGRATION)<<"ui renamed:"<<oldfile<<"->"<<fi.filePath();
}
}
}
// migrate data dir, must be done after ui files
Kdelibs4Migration md;
if (md.kdeHomeFound()) {
const QString oldpath = md.saveLocation("data", m_oldAppName.isEmpty() ? m_newAppName : m_oldAppName);
if (!oldpath.isEmpty()) {
qCDebug(CALLIGRA2MIGRATION)<<"old data:"<<oldpath;
newdatadir.rename(oldpath, newdatapath);
qCDebug(CALLIGRA2MIGRATION)<<"renamed data:"<<newdatapath;
}
} else {
qCWarning(CALLIGRA2MIGRATION)<<"kde home not found";
}
// Copied from Kdelibs4ConfigMigrator:
// Trigger KSharedConfig::openConfig()->reparseConfiguration() via the framework integration plugin
if (didSomething) {
qCDebug(CALLIGRA2MIGRATION)<<"reparse configuration";
QPluginLoader lib(QStringLiteral("kf5/FrameworkIntegrationPlugin"));
QObject *rootObj = lib.instance();
if (rootObj) {
QMetaObject::invokeMethod(rootObj, "reparseConfiguration");
}
}
}
diff --git a/src/libs/main/KoApplication.cpp b/src/libs/main/KoApplication.cpp
index c642fad9..eb02a27e 100644
--- a/src/libs/main/KoApplication.cpp
+++ b/src/libs/main/KoApplication.cpp
@@ -1,633 +1,634 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2009 Thomas Zander <zander@kde.org>
Copyright (C) 2012 Boudewijn Rempt <boud@valdyas.org>
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 "KoApplication.h"
#include "KoGlobal.h"
#ifndef QT_NO_DBUS
#include "KoApplicationAdaptor.h"
#include <QDBusConnection>
#include <QDBusReply>
#include <QDBusConnectionInterface>
#endif
#include "KoPrintJob.h"
#include "KoDocumentEntry.h"
#include "KoDocument.h"
#include "KoMainWindow.h"
#include "KoAutoSaveRecoveryDialog.h"
#include <KoDpi.h>
#include "KoPart.h"
#include <KoPluginLoader.h>
#include <config.h>
#include <KoResourcePaths.h>
#include <KoComponentData.h>
#include <klocalizedstring.h>
#include <kdesktopfile.h>
#include <kmessagebox.h>
#include <kiconloader.h>
#include <MainDebug.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <krecentdirs.h>
#include <KAboutData>
#include <KSharedConfig>
#include <KDBusService>
#include <QFile>
#include <QWidget>
#include <QSysInfo>
#include <QStringList>
#include <QProcessEnvironment>
#include <QDir>
#include <QPluginLoader>
#include <QCommandLineParser>
#include <QMimeDatabase>
#include <stdlib.h>
#ifdef Q_OS_WIN
#include <windows.h>
#include <tchar.h>
#endif
#include <QDesktopWidget>
KoApplication* KoApplication::KoApp = 0;
namespace {
const QTime appStartTime(QTime::currentTime());
}
class KoApplicationPrivate
{
public:
KoApplicationPrivate()
: splashScreen(0)
{}
QByteArray nativeMimeType;
QWidget *splashScreen;
QList<KoPart *> partList;
};
class KoApplication::ResetStarting
{
public:
ResetStarting(QWidget *splash = 0)
: m_splash(splash)
{
}
~ResetStarting() {
if (m_splash) {
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false);
if (hideSplash) {
m_splash->hide();
}
else {
m_splash->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
QRect r(QPoint(), m_splash->size());
m_splash->move(QApplication::desktop()->screenGeometry().center() - r.center());
m_splash->setWindowTitle(qAppName());
foreach(QObject *o, m_splash->children()) {
QWidget *w = qobject_cast<QWidget*>(o);
if (w && w->isHidden()) {
w->setVisible(true);
}
}
m_splash->show();
}
}
}
QWidget *m_splash;
};
KoApplication::KoApplication(const QByteArray &nativeMimeType,
const QString &windowIconName,
AboutDataGenerator aboutDataGenerator,
int &argc, char **argv)
: QApplication(argc, argv)
, d(new KoApplicationPrivate())
{
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
QScopedPointer<KAboutData> aboutData(aboutDataGenerator());
KAboutData::setApplicationData(*aboutData);
setWindowIcon(QIcon::fromTheme(windowIconName, windowIcon()));
KoApplication::KoApp = this;
d->nativeMimeType = nativeMimeType;
// Initialize all Calligra directories etc.
KoGlobal::initialize();
#ifndef QT_NO_DBUS
KDBusService service(KDBusService::Multiple);
new KoApplicationAdaptor(this);
QDBusConnection::sessionBus().registerObject("/application", this);
#endif
#ifdef Q_OS_MACX
if ( QSysInfo::MacintoshVersion > QSysInfo::MV_10_8 )
{
// fix Mac OS X 10.9 (mavericks) font issue
// https://bugreports.qt-project.org/browse/QTBUG-32789
QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
}
setAttribute(Qt::AA_DontShowIconsInMenus, true);
#endif
}
#if defined(Q_OS_WIN) && defined(ENV32BIT)
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL isWow64()
{
BOOL bIsWow64 = FALSE;
//IsWow64Process is not available on all supported versions of Windows.
//Use GetModuleHandle to get a handle to the DLL that contains the function
//and GetProcAddress to get a pointer to the function if available.
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
if(0 != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
//handle error
}
}
return bIsWow64;
}
#endif
bool KoApplication::start()
{
KAboutData aboutData = KAboutData::applicationData();
// process commandline parameters
QCommandLineParser parser;
aboutData.setupCommandLine(&parser);
parser.addHelpOption();
parser.addVersionOption();
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("benchmark-loading"), i18n("just load the file and then exit")));
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("benchmark-loading-show-window"), i18n("load the file, show the window and progressbar and then exit")));
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("profile-filename"), i18n("Filename to write profiling information into."), QStringLiteral("filename")));
parser.addPositionalArgument(QStringLiteral("[file(s)]"), i18n("File(s) or URL(s) to open"));
parser.process(*this);
aboutData.processCommandLine(&parser);
#if defined(Q_OS_WIN) || defined (Q_OS_MACX)
#ifdef ENV32BIT
if (isWow64()) {
KMessageBox::information(0,
i18n("You are running a 32 bits build on a 64 bits Windows.\n"
"This is not recommended.\n"
"Please download and install the x64 build instead."),
qApp->applicationName(),
"calligra_32_on_64_warning");
}
#endif
QDir appdir(applicationDirPath());
appdir.cdUp();
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
if (!env.contains("XDG_DATA_DIRS")) {
qputenv("XDG_DATA_DIRS", QFile::encodeName(appdir.absolutePath() + "/share"));
}
qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";"
+ appdir.absolutePath() + "/lib" + ";"
+ appdir.absolutePath() + "/lib/kde4" + ";"
+ appdir.absolutePath() + "/Frameworks" + ";"
+ appdir.absolutePath()));
#endif
if (d->splashScreen) {
d->splashScreen->show();
d->splashScreen->repaint();
processEvents();
}
ResetStarting resetStarting(d->splashScreen); // remove the splash when done
Q_UNUSED(resetStarting);
// Find the part component file corresponding to the application instance name
KoDocumentEntry entry;
QList<QPluginLoader*> pluginLoaders = KoPluginLoader::pluginLoaders("calligraplan/parts", d->nativeMimeType);
Q_FOREACH (QPluginLoader *loader, pluginLoaders) {
if (loader->fileName().contains(applicationName()+QString("part"))) {
entry = KoDocumentEntry(loader);
pluginLoaders.removeOne(loader);
break;
}
}
qDeleteAll(pluginLoaders);
if (entry.isEmpty()) {
QMessageBox::critical(0, i18n("%1: Critical Error", applicationName()), i18n("Essential application components could not be found.\n"
"This might be an installation issue.\n"
"Try restarting or reinstalling."));
return false;
}
// No argument -> create an empty document
const QStringList fileUrls = parser.positionalArguments();
if (fileUrls.isEmpty()) {
// if there's no document, add the current working directory
// to the recent dirs so the open dialog and open pane show
// the directory from where the app was started, instead of
// the last directory from where we opened a file
KRecentDirs::add(":OpenDialog", QDir::currentPath());
QString errorMsg;
KoPart *part = entry.createKoPart(&errorMsg);
d->partList << part;
if (!part) {
if (!errorMsg.isEmpty())
KMessageBox::error(0, errorMsg);
return false;
}
// XXX: the document should be separate plugin
KoDocument *doc = part->document();
KoMainWindow *mainWindow = part->createMainWindow();
mainWindow->show();
QObject::connect(doc, &KoDocument::sigProgress, mainWindow, &KoMainWindow::slotProgress);
// for initDoc to fill in the recent docs list
// and for KoDocument::slotStarted
part->addMainWindow(mainWindow);
// Check for autosave files from a previous run. There can be several, and
// we want to offer a restore for every one. Including a nice thumbnail!
QStringList autoSaveFiles;
// get all possible autosave files in the home dir, this is for unsaved document autosave files
// Using the extension allows to avoid relying on the mime magic when opening
QMimeType mimeType = QMimeDatabase().mimeTypeForName(doc->nativeFormatMimeType());
if (!mimeType.isValid()) {
qFatal("It seems your installation is broken/incomplete because we failed to load the native mimetype \"%s\".", doc->nativeFormatMimeType().constData());
}
const QString extension = mimeType.preferredSuffix();
QStringList filters;
filters << QString(".%1-%2-%3-autosave%4").arg(part->componentData().componentName()).arg("*").arg("*").arg(extension);
#ifdef Q_OS_WIN
QDir autosaveDir = QDir::tempPath();
#else
QDir autosaveDir = QDir::home();
#endif
// all autosave files for our application
autoSaveFiles = autosaveDir.entryList(filters, QDir::Files | QDir::Hidden);
QStringList pids;
QString ourPid;
ourPid.setNum(applicationPid());
#ifndef QT_NO_DBUS
// all running instances of our application -- bit hackish, but we cannot get at the dbus name here, for some reason
QDBusReply<QStringList> reply = QDBusConnection::sessionBus().interface()->registeredServiceNames();
foreach (const QString &name, reply.value()) {
if (name.contains(part->componentData().componentName())) {
// we got another instance of ourselves running, let's get the pid
QString pid = name.split('-').last();
if (pid != ourPid) {
pids << pid;
}
}
}
#endif
// remove the autosave files that are saved for other, open instances of ourselves
foreach(const QString &autoSaveFileName, autoSaveFiles) {
if (!QFile::exists(autosaveDir.absolutePath() + QDir::separator() + autoSaveFileName)) {
autoSaveFiles.removeAll(autoSaveFileName);
continue;
}
QStringList split = autoSaveFileName.split('-');
if (split.size() == 4) {
if (pids.contains(split[1])) {
// We've got an active, owned autosave file. Remove.
autoSaveFiles.removeAll(autoSaveFileName);
}
}
}
// Allow the user to make their selection
if (autoSaveFiles.size() > 0) {
KoAutoSaveRecoveryDialog dlg(autoSaveFiles);
if (dlg.exec() == QDialog::Accepted) {
QStringList filesToRecover = dlg.recoverableFiles();
foreach (const QString &autoSaveFileName, autoSaveFiles) {
if (!filesToRecover.contains(autoSaveFileName)) {
// remove the files the user didn't want to recover
QFile::remove(autosaveDir.absolutePath() + QDir::separator() + autoSaveFileName);
}
}
autoSaveFiles = filesToRecover;
}
else {
// don't recover any of the files, but don't delete them either
autoSaveFiles.clear();
}
}
if (autoSaveFiles.size() > 0) {
short int numberOfOpenDocuments = 0; // number of documents open
// bah, we need to re-use the document that was already created
QUrl url = QUrl::fromLocalFile(autosaveDir.absolutePath() + QDir::separator() + autoSaveFiles.takeFirst());
if (mainWindow->openDocument(part, url)) {
doc->resetURL();
doc->setModified(true);
// TODO: what if the app crashes immediately, before another autosave was made? better keep & rename
QFile::remove(url.toLocalFile());
numberOfOpenDocuments++;
}
// And then for the other autosave files, we copy & paste the code
// and loop through them.
foreach(const QString &autoSaveFile, autoSaveFiles) {
// For now create an empty document
QString errorMsg;
KoPart *part = entry.createKoPart(&errorMsg);
d->partList << part;
if (part) {
url = QUrl::fromLocalFile(autosaveDir.absolutePath() + QDir::separator() + autoSaveFile);
KoMainWindow *mainWindow = part->createMainWindow();
mainWindow->show();
if (mainWindow->openDocument(part, url)) {
doc->resetURL();
doc->setModified(true);
// TODO: what if the app crashes immediately, before another autosave was made? better keep & rename
QFile::remove(url.toLocalFile());
numberOfOpenDocuments++;
}
}
}
return (numberOfOpenDocuments > 0);
}
else {
part->showStartUpWidget(mainWindow);
}
}
else {
const bool benchmarkLoading = parser.isSet("benchmark-loading")
|| parser.isSet("benchmark-loading-show-window");
// only show the mainWindow when no command-line mode option is passed
const bool showmainWindow = parser.isSet("benchmark-loading-show-window")
|| !parser.isSet("benchmark-loading");
const QString profileFileName = parser.value("profile-filename");
QTextStream profileoutput;
QFile profileFile(profileFileName);
if (!profileFileName.isEmpty() && profileFile.open(QFile::WriteOnly | QFile::Truncate)) {
profileoutput.setDevice(&profileFile);
}
// Loop through arguments
short int numberOfOpenDocuments = 0; // number of documents open
// TODO: remove once Qt has proper handling itself
const QRegExp withProtocolChecker( QStringLiteral("^[a-zA-Z]+:") );
for (int argNumber = 0; argNumber < fileUrls.size(); ++argNumber) {
const QString fileUrl = fileUrls.at(argNumber);
// convert to an url
const bool startsWithProtocol = (withProtocolChecker.indexIn(fileUrl) == 0);
const QUrl url = startsWithProtocol ?
QUrl::fromUserInput(fileUrl) :
QUrl::fromLocalFile(QDir::current().absoluteFilePath(fileUrl));
// For now create an empty document
QString errorMsg;
KoPart *part = entry.createKoPart(&errorMsg);
d->partList << part;
if (part) {
KoDocument *doc = part->document();
// show a mainWindow asap
KoMainWindow *mainWindow = part->createMainWindow();
if (showmainWindow) {
mainWindow->show();
}
if (benchmarkLoading) {
doc->setReadWrite(false);
connect(mainWindow, &KoMainWindow::loadCompleted, this, &KoApplication::benchmarkLoadingFinished);
connect(mainWindow, &KoMainWindow::loadCompleted, this, &KoApplication::benchmarkLoadingFinished);
}
if (profileoutput.device()) {
doc->setProfileStream(&profileoutput);
profileoutput << "KoApplication::start\t"
<< appStartTime.msecsTo(QTime::currentTime())
<<"\t0" << endl;
doc->setAutoErrorHandlingEnabled(false);
}
doc->setProfileReferenceTime(appStartTime);
#if 0
// are we just trying to open a Calligra-style template?
if (doTemplate) {
QString templatePath;
if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
templatePath = url.toLocalFile();
debugMain << "using full path...";
} else {
QString desktopName(fileUrls.at(argNumber));
const QString templatesResourcePath = part->templatesResourcePath();
QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName);
if (paths.isEmpty()) {
paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName);
}
if (paths.isEmpty()) {
KMessageBox::error(0, i18n("No template found for: %1", desktopName));
delete mainWindow;
} else if (paths.count() > 1) {
KMessageBox::error(0, i18n("Too many templates found for: %1", desktopName));
delete mainWindow;
} else {
templatePath = paths.at(0);
}
}
if (!templatePath.isEmpty()) {
QUrl templateBase;
templateBase.setPath(templatePath);
KDesktopFile templateInfo(templatePath);
QString templateName = templateInfo.readUrl();
QUrl templateURL;
templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName);
if (mainWindow->openDocument(part, templateURL)) {
doc->resetURL();
doc->setEmpty();
doc->setTitleModified();
debugMain << "Template loaded...";
numberOfOpenDocuments++;
} else {
KMessageBox::error(0, i18n("Template %1 failed to load.", templateURL.toDisplayString()));
delete mainWindow;
}
}
// now try to load
}
else if (doNew) {
if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) {
KMessageBox::error(0, i18n("No template found at: %1", url.toDisplayString()));
delete mainWindow;
} else {
if (mainWindow->openDocument(part, url)) {
doc->resetURL();
doc->setEmpty();
doc->setTitleModified();
debugMain << "Template loaded...";
numberOfOpenDocuments++;
} else {
KMessageBox::error(0, i18n("Template %1 failed to load.", url.toDisplayString()));
delete mainWindow;
}
}
}
else
#endif
if (mainWindow->openDocument(part, url)) {
if (benchmarkLoading) {
if (profileoutput.device()) {
profileoutput << "KoApplication::start\t"
<< appStartTime.msecsTo(QTime::currentTime())
<<"\t100" << endl;
}
return true; // only load one document!
} else {
// Normal case, success
numberOfOpenDocuments++;
}
} else {
// .... if failed
// delete doc; done by openDocument
// delete mainWindow; done by ~KoDocument
}
if (profileoutput.device()) {
profileoutput << "KoApplication::start\t"
<< appStartTime.msecsTo(QTime::currentTime())
<<"\t100" << endl;
}
}
}
if (benchmarkLoading) {
return false; // no valid urls found.
}
if (numberOfOpenDocuments == 0) { // no doc, e.g. all URLs were malformed
return false;
}
}
// not calling this before since the program will quit there.
return true;
}
KoApplication::~KoApplication()
{
delete d;
}
void KoApplication::benchmarkLoadingFinished()
{
KoPart *part = d->partList.value(0);
if (!part) {
return;
}
KoMainWindow *mainWindow = part->mainWindows().value(0);
if (!mainWindow) {
return;
}
// close the document
mainWindow->slotFileQuit();
}
void KoApplication::setSplashScreen(QWidget *splashScreen)
{
d->splashScreen = splashScreen;
}
QList<KoPart*> KoApplication::partList() const
{
return d->partList;
}
QStringList KoApplication::mimeFilter(KoFilterManager::Direction direction) const
{
KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(d->nativeMimeType);
QJsonObject json = entry.metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList mimeTypes = json.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
#else
QStringList mimeTypes = json.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
return KoFilterManager::mimeFilter(d->nativeMimeType, direction, mimeTypes);
}
bool KoApplication::notify(QObject *receiver, QEvent *event)
{
try {
return QApplication::notify(receiver, event);
} catch (std::exception &e) {
qWarning("Error %s sending event %i to object %s",
e.what(), event->type(), qPrintable(receiver->objectName()));
} catch (...) {
qWarning("Error <unknown> sending event %i to object %s",
event->type(), qPrintable(receiver->objectName()));
}
return false;
}
KoApplication *KoApplication::koApplication()
{
return KoApp;
}
diff --git a/src/libs/main/KoApplicationAdaptor.cpp b/src/libs/main/KoApplicationAdaptor.cpp
index b3a00bfb..f9a7c610 100644
--- a/src/libs/main/KoApplicationAdaptor.cpp
+++ b/src/libs/main/KoApplicationAdaptor.cpp
@@ -1,97 +1,98 @@
/* This file is part of the KDE project
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2006 Fredrik Edemar <f_edemar@linux.se>
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.
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 "KoApplicationAdaptor.h"
#include <MainDebug.h>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include "KoApplication.h"
#include "KoPart.h"
#include "KoDocument.h"
#include "KoMainWindow.h"
#include "KoDocumentEntry.h"
#include "KoView.h"
KoApplicationAdaptor::KoApplicationAdaptor(KoApplication *parent)
: QDBusAbstractAdaptor(parent)
, m_application(parent)
{
// constructor
setAutoRelaySignals(true);
}
KoApplicationAdaptor::~KoApplicationAdaptor()
{
// destructor
}
//QString KoApplicationAdaptor::createDocument(const QString &nativeFormat)
//{
// KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(nativeFormat);
// if (entry.isEmpty()) {
// KMessageBox::questionYesNo(0, i18n("Unknown Calligra MimeType %1. Check your installation.", nativeFormat));
// return QString();
// }
// KoPart *part = entry.createKoPart(0);
// if (part) {
// m_application->addPart(part);
// return '/' + part->document()->objectName();
// }
// else {
// return QString();
// }
//}
QStringList KoApplicationAdaptor::getDocuments()
{
QStringList lst;
QList<KoPart*> parts = m_application->partList();
foreach(KoPart *part, parts) {
lst.append('/' + part->document()->objectName());
}
return lst;
}
QStringList KoApplicationAdaptor::getViews()
{
QStringList lst;
QList<KoPart*> parts = m_application->partList();
foreach(KoPart *part, parts) {
foreach(KoView* view, part->views()) {
lst.append('/' + view->objectName());
}
}
return lst;
}
QStringList KoApplicationAdaptor::getWindows()
{
QStringList lst;
QList<KMainWindow*> mainWindows = KMainWindow::memberList();
if (!mainWindows.isEmpty()) {
foreach(KMainWindow* mainWindow, mainWindows) {
lst.append(static_cast<KoMainWindow*>(mainWindow)->objectName());
}
}
return lst;
}
diff --git a/src/libs/main/KoAutoSaveRecoveryDialog.cpp b/src/libs/main/KoAutoSaveRecoveryDialog.cpp
index eb049cf4..8622bee4 100644
--- a/src/libs/main/KoAutoSaveRecoveryDialog.cpp
+++ b/src/libs/main/KoAutoSaveRecoveryDialog.cpp
@@ -1,249 +1,250 @@
/* This file is part of the KDE project
Copyright (C) 2012 Boudewijn Rempt <boud@valdyas.org>
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 "KoAutoSaveRecoveryDialog.h"
#include <KoStore.h>
#include <kwidgetitemdelegate.h>
#include <klocalizedstring.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QListView>
#include <QAbstractTableModel>
#include <QLabel>
#include <QDir>
#include <QFileInfo>
#include <QDateTime>
#include <QImage>
#include <QPixmap>
#include <QHeaderView>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QCheckBox>
struct FileItem {
FileItem() : checked(true) {}
QImage thumbnail;
QString name;
QString date;
bool checked;
};
class FileItemDelegate : public KWidgetItemDelegate
{
public:
FileItemDelegate(QAbstractItemView *itemView, KoAutoSaveRecoveryDialog *dlg)
: KWidgetItemDelegate(itemView, dlg)
, m_parent(dlg)
{
}
QList<QWidget*> createItemWidgets(const QModelIndex &index) const
{
QWidget *page = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(page);
QCheckBox *checkBox = new QCheckBox;
checkBox->setProperty("fileitem", index.data());
connect(checkBox, &QAbstractButton::toggled, m_parent, &KoAutoSaveRecoveryDialog::toggleFileItem);
QLabel *thumbnail = new QLabel;
QLabel *filename = new QLabel;
QLabel *dateModified = new QLabel;
layout->addWidget(checkBox);
layout->addWidget(thumbnail);
layout->addWidget(filename);
layout->addWidget(dateModified);
page->setFixedSize(600, 200);
return QList<QWidget*>() << page;
}
void updateItemWidgets(const QList<QWidget*> widgets,
const QStyleOptionViewItem &option,
const QPersistentModelIndex &index) const
{
FileItem *fileItem = (FileItem*)index.data().value<void*>();
QWidget* page= widgets[0];
QHBoxLayout* layout = qobject_cast<QHBoxLayout*>(page->layout());
QCheckBox *checkBox = qobject_cast<QCheckBox*>(layout->itemAt(0)->widget());
QLabel *thumbnail = qobject_cast<QLabel*>(layout->itemAt(1)->widget());
QLabel *filename = qobject_cast<QLabel*>(layout->itemAt(2)->widget());
QLabel *modified = qobject_cast<QLabel*>(layout->itemAt(3)->widget());
checkBox->setChecked(fileItem->checked);
thumbnail->setPixmap(QPixmap::fromImage(fileItem->thumbnail));
filename->setText(fileItem->name);
modified->setText(fileItem->date);
// move the page _up_ otherwise it will draw relative to the actual position
page->setGeometry(option.rect.translated(0, -option.rect.y()));
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const
{
//paint background for selected or hovered item
QStyleOptionViewItem opt = option;
itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, 0);
}
QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const
{
return QSize(600, 200);
}
KoAutoSaveRecoveryDialog *m_parent;
};
class KoAutoSaveRecoveryDialog::FileItemModel : public QAbstractListModel
{
public:
FileItemModel(QList<FileItem*> fileItems, QObject *parent)
: QAbstractListModel(parent)
, m_fileItems(fileItems){}
virtual ~FileItemModel()
{
qDeleteAll(m_fileItems);
m_fileItems.clear();
}
int rowCount(const QModelIndex &/*parent*/) const { return m_fileItems.size(); }
Qt::ItemFlags flags(const QModelIndex& /*index*/) const
{
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
return flags;
}
QVariant data(const QModelIndex& index, int role) const
{
if (index.isValid() && index.row() < m_fileItems.size()) {
FileItem *item = m_fileItems.at(index.row());
switch (role) {
case Qt::DisplayRole:
{
return QVariant::fromValue<void*>((void*)item);
}
case Qt::SizeHintRole:
return QSize(600, 200);
}
}
return QVariant();
}
bool setData(const QModelIndex& index, const QVariant& /*value*/, int role)
{
if (index.isValid() && index.row() < m_fileItems.size()) {
if (role == Qt::CheckStateRole) {
m_fileItems.at(index.row())->checked = !m_fileItems.at(index.row())->checked;
return true;
}
}
return false;
}
QList<FileItem *> m_fileItems;
};
KoAutoSaveRecoveryDialog::KoAutoSaveRecoveryDialog(const QStringList &filenames, QWidget *parent) :
KoDialog(parent)
{
setCaption(i18nc("@title:window", "Recover Files"));
setMinimumSize(650, 500);
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
if (filenames.size() == 1) {
layout->addWidget(new QLabel(i18n("The following autosave file can be recovered:")));
}
else {
layout->addWidget(new QLabel(i18n("The following autosave files can be recovered:")));
}
m_listView = new QListView();
m_listView->setAcceptDrops(false);
KWidgetItemDelegate *delegate = new FileItemDelegate(m_listView, this);
m_listView->setItemDelegate(delegate);
QList<FileItem*> fileItems;
foreach(const QString &filename, filenames) {
FileItem *file = new FileItem();
file->name = filename;
QString path = QDir::homePath() + "/" + filename;
// get thumbnail -- all calligra apps save a thumbnail
KoStore* store = KoStore::createStore(path, KoStore::Read);
if (store && ( store->open(QString("Thumbnails/thumbnail.png"))
|| store->open(QString("preview.png")))) {
// Hooray! No long delay for the user...
QByteArray bytes = store->read(store->size());
store->close();
delete store;
QImage img;
img.loadFromData(bytes);
file->thumbnail = img.scaled(QSize(200,200), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
// get the date
QDateTime date = QFileInfo(path).lastModified();
file->date = "(" + date.toString(Qt::LocalDate) + ")";
fileItems.append(file);
}
m_model = new FileItemModel(fileItems, m_listView);
m_listView->setModel(m_model);
layout->addWidget(m_listView);
setMainWidget(page);
}
QStringList KoAutoSaveRecoveryDialog::recoverableFiles()
{
QStringList files;
foreach(FileItem* fileItem, m_model->m_fileItems) {
if (fileItem->checked) {
files << fileItem->name;
}
}
return files;
}
void KoAutoSaveRecoveryDialog::toggleFileItem(bool toggle)
{
// I've made better man from a piece of putty and matchstick!
QVariant v = sender()->property("fileitem") ;
if (v.isValid()) {
FileItem *fileItem = (FileItem*)v.value<void*>();
fileItem->checked = toggle;
}
}
diff --git a/src/libs/main/KoComponentData.cpp b/src/libs/main/KoComponentData.cpp
index 2014b30c..44d06ebc 100644
--- a/src/libs/main/KoComponentData.cpp
+++ b/src/libs/main/KoComponentData.cpp
@@ -1,73 +1,74 @@
/* This file is part of the KDE project
Copyright (C) 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
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 "KoComponentData.h"
#include "KoComponentData_p.h"
KoComponentData::KoComponentData(const KAboutData &aboutData)
: d(new KoComponentDataPrivate(aboutData))
{
}
KoComponentData::KoComponentData(const KoComponentData &other)
: d(other.d)
{
}
KoComponentData::~KoComponentData()
{
}
KoComponentData& KoComponentData::operator=(const KoComponentData &other)
{
d = other.d;
return *this;
}
bool KoComponentData::operator==(const KoComponentData &other) const
{
const bool equals = (d == other.d);
return equals;
}
const KAboutData& KoComponentData::aboutData() const
{
return d->aboutData;
}
QString KoComponentData::componentName() const
{
return d->aboutData.componentName();
}
QString KoComponentData::componentDisplayName() const
{
return d->aboutData.displayName();
}
const KSharedConfig::Ptr& KoComponentData::config() const
{
if (!d->sharedConfig) {
d->sharedConfig = KSharedConfig::openConfig(d->aboutData.componentName() + QLatin1String("rc"));
}
return d->sharedConfig;
}
diff --git a/src/libs/main/KoDockFactoryBase.cpp b/src/libs/main/KoDockFactoryBase.cpp
index a8bda913..12797749 100644
--- a/src/libs/main/KoDockFactoryBase.cpp
+++ b/src/libs/main/KoDockFactoryBase.cpp
@@ -1,28 +1,29 @@
/* This file is part of the KDE project
Copyright (C) 2010 Thomas Zander <zander@kde.org>
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 "KoDockFactoryBase.h"
KoDockFactoryBase::KoDockFactoryBase()
{
}
KoDockFactoryBase::~KoDockFactoryBase()
{
}
diff --git a/src/libs/main/KoDockRegistry.cpp b/src/libs/main/KoDockRegistry.cpp
index 5288f8fd..cc76ea70 100644
--- a/src/libs/main/KoDockRegistry.cpp
+++ b/src/libs/main/KoDockRegistry.cpp
@@ -1,82 +1,83 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
*
* 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 "KoDockRegistry.h"
#include <QGlobalStatic>
#include <QFontDatabase>
#include <QDebug>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include "KoPluginLoader.h"
Q_GLOBAL_STATIC(KoDockRegistry, s_instance)
KoDockRegistry::KoDockRegistry()
: d(0)
{
}
void KoDockRegistry::init()
{
KoPluginLoader::PluginsConfig config;
config.whiteList = "DockerPlugins";
config.blacklist = "DockerPluginsDisabled";
config.group = "calligra";
KoPluginLoader::load(QStringLiteral("calligra/dockers"), config);
}
KoDockRegistry::~KoDockRegistry()
{
qDeleteAll(doubleEntries());
qDeleteAll(values());
}
KoDockRegistry* KoDockRegistry::instance()
{
if (!s_instance.exists()) {
s_instance->init();
}
return s_instance;
}
QFont KoDockRegistry::dockFont()
{
KConfigGroup group( KSharedConfig::openConfig(), "GUI");
QFont dockWidgetFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
QFont smallFont = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
int pointSize = group.readEntry("palettefontsize", dockWidgetFont.pointSize());
// Not set by the user
if (pointSize == dockWidgetFont.pointSize()) {
// and there is no setting for the smallest readable font, calculate something small
if (smallFont.pointSize() >= pointSize) {
smallFont.setPointSizeF(pointSize * 0.9);
}
}
else {
// paletteFontSize was set, use that
smallFont.setPointSize(pointSize);
}
return smallFont;
}
diff --git a/src/libs/main/KoDocument.cpp b/src/libs/main/KoDocument.cpp
index 04ef9782..a0734f49 100644
--- a/src/libs/main/KoDocument.cpp
+++ b/src/libs/main/KoDocument.cpp
@@ -1,2689 +1,2690 @@
/* This file is part of the KDE project
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* Copyright (C) 2000-2005 David Faure <faure@kde.org>
* Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2010-2012 Boudewijn Rempt <boud@kogmbh.com>
* Copyright (C) 2011 Inge Wallin <ingwa@kogmbh.com>
*
* 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 "KoDocument.h"
#include "KoMainWindow.h" // XXX: remove
#include <kmessagebox.h> // XXX: remove
#include <KNotification> // XXX: remove
#include "KoComponentData.h"
#include "KoPart.h"
#include "KoEmbeddedDocumentSaver.h"
#include "KoFilterManager.h"
#include "KoFileDialog.h"
#include "KoDocumentInfo.h"
#include "KoView.h"
#include "KoOdfReadStore.h"
#include "KoOdfWriteStore.h"
#include "KoXmlNS.h"
#include <KoProgressProxy.h>
#include <KoProgressUpdater.h>
#include <KoUpdater.h>
//#include <KoDocumentRdfBase.h>
#include <KoDpi.h>
#include <KoUnit.h>
#include <KoXmlWriter.h>
#include <KoDocumentInfoDlg.h>
#include <KoPageLayout.h>
//#include <KoGridData.h>
//#include <KoGuidesData.h>
#include <kfileitem.h>
#include <KoNetAccess.h>
#include <klocalizedstring.h>
#include <MainDebug.h>
#include <kconfiggroup.h>
#include <kio/job.h>
#include <kdirnotify.h>
#include <KBackup>
#include <QMimeDatabase>
#include <QTemporaryFile>
#include <QApplication>
#include <QtGlobal>
#include <QBuffer>
#include <QDir>
#include <QFileInfo>
#include <QPainter>
#include <QTimer>
#ifndef QT_NO_DBUS
#include <KJobWidgets>
#include <QDBusConnection>
#endif
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include <kactioncollection.h>
#include "KoUndoStackAction.h"
#include <unistd.h>
using namespace std;
/**********************************************************
*
* KoDocument
*
**********************************************************/
namespace {
class DocumentProgressProxy : public KoProgressProxy {
public:
KoMainWindow *m_mainWindow;
DocumentProgressProxy(KoMainWindow *mainWindow)
: m_mainWindow(mainWindow)
{
}
~DocumentProgressProxy() {
// signal that the job is done
setValue(-1);
}
int maximum() const {
return 100;
}
void setValue(int value) {
if (m_mainWindow) {
m_mainWindow->slotProgress(value);
}
}
void setRange(int /*minimum*/, int /*maximum*/) {
}
void setFormat(const QString &/*format*/) {
}
};
}
//static
QString KoDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class Q_DECL_HIDDEN KoDocument::Private
{
public:
Private(KoDocument *document, KoPart *part) :
document(document),
parentPart(part),
docInfo(0),
// docRdf(0),
progressUpdater(0),
progressProxy(0),
profileStream(0),
filterManager(0),
specialOutputFlag(0), // default is native format
isImporting(false),
isExporting(false),
password(QString()),
modifiedAfterAutosave(false),
autosaving(false),
shouldCheckAutoSaveFile(true),
autoErrorHandlingEnabled(true),
backupFile(true),
backupPath(QString()),
doNotSaveExtDoc(false),
storeInternal(false),
isLoading(false),
undoStack(0),
modified(false),
readwrite(true),
alwaysAllowSaving(false),
disregardAutosaveFailure(false)
{
m_job = 0;
m_statJob = 0;
m_uploadJob = 0;
m_saveOk = false;
m_waitForSave = false;
m_duringSaveAs = false;
m_bTemp = false;
m_bAutoDetectedMime = false;
confirmNonNativeSave[0] = true;
confirmNonNativeSave[1] = true;
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
KoDocument *document;
KoPart *const parentPart;
KoDocumentInfo *docInfo;
// KoDocumentRdfBase *docRdf;
KoProgressUpdater *progressUpdater;
KoProgressProxy *progressProxy;
QTextStream *profileStream;
QTime profileReferenceTime;
KoUnit unit;
KoFilterManager *filterManager; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
bool confirmNonNativeSave [2]; // used to pop up a dialog when saving for the
// first time if the file is in a foreign format
// (Save/Save As, Export)
int specialOutputFlag; // See KoFileDialog in koMainWindow.cc
bool isImporting;
bool isExporting; // File --> Import/Export vs File --> Open/Save
QString password; // The password used to encrypt an encrypted document
QTimer autoSaveTimer;
QString lastErrorMessage; // see openFile()
int autoSaveDelay; // in seconds, 0 to disable.
bool modifiedAfterAutosave;
bool autosaving;
bool shouldCheckAutoSaveFile; // usually true
bool autoErrorHandlingEnabled; // usually true
bool backupFile;
QString backupPath;
bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents
bool storeInternal; // Store this doc internally even if url is external
bool isLoading; // True while loading (openUrl is async)
QList<KoVersionInfo> versionInfo;
KUndo2Stack *undoStack;
// KoGridData gridData;
// KoGuidesData guidesData;
bool isEmpty;
KoPageLayout pageLayout;
KIO::FileCopyJob * m_job;
KIO::StatJob * m_statJob;
KIO::FileCopyJob * m_uploadJob;
QUrl m_originalURL; // for saveAs
QString m_originalFilePath; // for saveAs
bool m_saveOk : 1;
bool m_waitForSave : 1;
bool m_duringSaveAs : 1;
bool m_bTemp: 1; // If @p true, @p m_file is a temporary file that needs to be deleted later.
bool m_bAutoDetectedMime : 1; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // Remote (or local) url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QEventLoop m_eventLoop;
bool modified;
bool readwrite;
bool alwaysAllowSaving;
bool disregardAutosaveFailure;
bool openFile()
{
DocumentProgressProxy *progressProxy = 0;
if (!document->progressProxy()) {
KoMainWindow *mainWindow = 0;
if (parentPart->mainWindows().count() > 0) {
mainWindow = parentPart->mainWindows()[0];
}
progressProxy = new DocumentProgressProxy(mainWindow);
document->setProgressProxy(progressProxy);
}
document->setUrl(m_url);
bool ok = document->openFile();
if (progressProxy) {
document->setProgressProxy(0);
delete progressProxy;
}
return ok;
}
bool openLocalFile()
{
m_bTemp = false;
// set the mimetype only if it was not already set (for example, by the host application)
if (mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QMimeType mime = QMimeDatabase().mimeTypeForUrl(m_url);
if (mime.isValid()) {
mimeType = mime.name().toLatin1();
m_bAutoDetectedMime = true;
}
}
const bool ret = openFile();
if (ret) {
emit document->completed();
} else {
emit document->canceled(QString());
}
return ret;
}
void openRemoteFile()
{
m_bTemp = true;
// Use same extension as remote file. This is important for mimetype-determination (e.g. koffice)
QString fileName = m_url.fileName();
QFileInfo fileInfo(fileName);
QString ext = fileInfo.completeSuffix();
QString extension;
if (!ext.isEmpty() && m_url.query().isNull()) // not if the URL has a query, e.g. cgi.pl?something
extension = '.'+ext; // keep the '.'
QTemporaryFile tempFile(QDir::tempPath() + "/" + qAppName() + QLatin1String("_XXXXXX") + extension);
tempFile.setAutoRemove(false);
tempFile.open();
m_file = tempFile.fileName();
const QUrl destURL = QUrl::fromLocalFile( m_file );
KIO::JobFlags flags = KIO::DefaultFlags;
flags |= KIO::Overwrite;
m_job = KIO::file_copy(m_url, destURL, 0600, flags);
#ifndef QT_NO_DBUS
KJobWidgets::setWindow(m_job, 0);
if (m_job->uiDelegate()) {
KJobWidgets::setWindow(m_job, parentPart->currentMainwindow());
}
#endif
QObject::connect(m_job, SIGNAL(result(KJob*)), document, SLOT(_k_slotJobFinished(KJob*))); // clazy:exclude=old-style-connect
QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*,QString)), document, SLOT(_k_slotGotMimeType(KIO::Job*,QString))); // clazy:exclude=old-style-connect
}
// Set m_file correctly for m_url
void prepareSaving()
{
// Local file
if ( m_url.isLocalFile() )
{
if ( m_bTemp ) // get rid of a possible temp file first
{ // (happens if previous url was remote)
QFile::remove( m_file );
m_bTemp = false;
}
m_file = m_url.toLocalFile();
}
else
{ // Remote file
// We haven't saved yet, or we did but locally - provide a temp file
if ( m_file.isEmpty() || !m_bTemp )
{
QTemporaryFile tempFile;
tempFile.setAutoRemove(false);
tempFile.open();
m_file = tempFile.fileName();
m_bTemp = true;
}
// otherwise, we already had a temp file
}
}
void _k_slotJobFinished( KJob * job )
{
Q_ASSERT( job == m_job );
m_job = 0;
if (job->error())
emit document->canceled( job->errorString() );
else {
if ( openFile() ) {
emit document->completed();
}
else {
emit document->canceled(QString());
}
}
}
void _k_slotStatJobFinished(KJob * job)
{
Q_ASSERT(job == m_statJob);
m_statJob = 0;
// this could maybe confuse some apps? So for now we'll just fallback to KIO::get
// and error again. Well, maybe this even helps with wrong stat results.
if (!job->error()) {
const QUrl localUrl = static_cast<KIO::StatJob*>(job)->mostLocalUrl();
if (localUrl.isLocalFile()) {
m_file = localUrl.toLocalFile();
openLocalFile();
return;
}
}
openRemoteFile();
}
void _k_slotGotMimeType(KIO::Job *job, const QString &mime)
{
// kDebug(1000) << mime;
Q_ASSERT(job == m_job); Q_UNUSED(job);
// set the mimetype only if it was not already set (for example, by the host application)
if (mimeType.isEmpty()) {
mimeType = mime.toLatin1();
m_bAutoDetectedMime = true;
}
}
void _k_slotUploadFinished( KJob * )
{
if (m_uploadJob->error())
{
QFile::remove(m_uploadJob->srcUrl().toLocalFile());
m_uploadJob = 0;
if (m_duringSaveAs) {
document->setUrl(m_originalURL);
m_file = m_originalFilePath;
}
}
else
{
::org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(m_url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()));
m_uploadJob = 0;
document->setModified( false );
emit document->completed();
m_saveOk = true;
}
m_duringSaveAs = false;
m_originalURL = QUrl();
m_originalFilePath.clear();
if (m_waitForSave) {
m_eventLoop.quit();
}
}
};
KoDocument::KoDocument(KoPart *parent, KUndo2Stack *undoStack)
: d(new Private(this, parent))
{
Q_ASSERT(parent);
d->isEmpty = true;
d->filterManager = new KoFilterManager(this, d->progressUpdater);
connect(&d->autoSaveTimer, &QTimer::timeout, this, &KoDocument::slotAutoSave);
setAutoSave(defaultAutoSave());
setObjectName(newObjectName());
d->docInfo = new KoDocumentInfo(this);
d->pageLayout.width = 0;
d->pageLayout.height = 0;
d->pageLayout.topMargin = 0;
d->pageLayout.bottomMargin = 0;
d->pageLayout.leftMargin = 0;
d->pageLayout.rightMargin = 0;
d->undoStack = undoStack;
d->undoStack->setParent(this);
KConfigGroup cfgGrp(d->parentPart->componentData().config(), "Undo");
d->undoStack->setUndoLimit(cfgGrp.readEntry("UndoLimit", 1000));
connect(d->undoStack, &KUndo2QStack::indexChanged, this, &KoDocument::slotUndoStackIndexChanged);
}
KoDocument::~KoDocument()
{
d->autoSaveTimer.disconnect(this);
d->autoSaveTimer.stop();
d->parentPart->deleteLater();
delete d->filterManager;
delete d;
}
KoPart *KoDocument::documentPart() const
{
return d->parentPart;
}
bool KoDocument::exportDocument(const QUrl &_url)
{
bool ret;
d->isExporting = true;
//
// Preserve a lot of state here because we need to restore it in order to
// be able to fake a File --> Export. Can't do this in saveFile() because,
// for a start, KParts has already set url and m_file and because we need
// to restore the modified flag etc. and don't want to put a load on anyone
// reimplementing saveFile() (Note: importDocument() and exportDocument()
// will remain non-virtual).
//
QUrl oldURL = url();
QString oldFile = localFilePath();
bool wasModified = isModified();
QByteArray oldMimeType = mimeType();
// save...
ret = saveAs(_url);
//
// This is sooooo hacky :(
// Hopefully we will restore enough state.
//
debugMain << "Restoring KoDocument state to before export";
// always restore url & m_file because KParts has changed them
// (regardless of failure or success)
setUrl(oldURL);
setLocalFilePath(oldFile);
// on successful export we need to restore modified etc. too
// on failed export, mimetype/modified hasn't changed anyway
if (ret) {
setModified(wasModified);
d->mimeType = oldMimeType;
}
d->isExporting = false;
return ret;
}
bool KoDocument::saveFile()
{
debugMain << "doc=" << url().url();
// Save it to be able to restore it after a failed save
const bool wasModified = isModified();
// The output format is set by koMainWindow, and by openFile
QByteArray outputMimeType = d->outputMimeType;
if (outputMimeType.isEmpty()) {
outputMimeType = d->outputMimeType = nativeFormatMimeType();
debugMain << "Empty output mime type, saving to" << outputMimeType;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
if (backupFile()) {
if (url().isLocalFile())
KBackup::backupFile(url().toLocalFile(), d->backupPath);
else {
KIO::UDSEntry entry;
if (KIO::NetAccess::stat(url(),
entry,
d->parentPart->currentMainwindow())) { // this file exists => backup
emit statusBarMessage(i18n("Making backup..."));
QUrl backup;
if (d->backupPath.isEmpty())
backup = url();
else
backup = QUrl::fromLocalFile(d->backupPath + '/' + url().fileName());
backup.setPath(backup.path() + QString::fromLatin1("~"));
KFileItem item(entry, url());
Q_ASSERT(item.name() == url().fileName());
KIO::FileCopyJob *job = KIO::file_copy(url(), backup, item.permissions(), KIO::Overwrite | KIO::HideProgressInfo);
job->exec();
}
}
}
emit statusBarMessage(i18n("Saving..."));
qApp->processEvents();
bool ret = false;
bool suppressErrorDialog = false;
if (!isNativeFormat(outputMimeType)) {
debugMain << "Saving to format" << outputMimeType << "in" << localFilePath();
// Not native format : save using export filter
KoFilter::ConversionStatus status = d->filterManager->exportDocument(localFilePath(), outputMimeType);
ret = status == KoFilter::OK;
suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph);
} else {
// Native format => normal save
Q_ASSERT(!localFilePath().isEmpty());
ret = saveNativeFormat(localFilePath());
}
if (ret) {
d->undoStack->setClean();
removeAutoSaveFiles();
// Restart the autosave timer
// (we don't want to autosave again 2 seconds after a real save)
setAutoSave(d->autoSaveDelay);
}
QApplication::restoreOverrideCursor();
if (!ret) {
if (!suppressErrorDialog) {
if (errorMessage().isEmpty()) {
KMessageBox::error(0, i18n("Could not save\n%1", localFilePath()));
} else if (errorMessage() != "USER_CANCELED") {
KMessageBox::error(0, i18n("Could not save %1\nReason: %2", localFilePath(), errorMessage()));
}
}
// couldn't save file so this new URL is invalid
// FIXME: we should restore the current document's true URL instead of
// setting it to nothing otherwise anything that depends on the URL
// being correct will not work (i.e. the document will be called
// "Untitled" which may not be true)
//
// Update: now the URL is restored in KoMainWindow but really, this
// should still be fixed in KoDocument/KParts (ditto for file).
// We still resetURL() here since we may or may not have been called
// by KoMainWindow - Clarence
resetURL();
// As we did not save, restore the "was modified" status
setModified(wasModified);
}
if (ret) {
d->mimeType = outputMimeType;
setConfirmNonNativeSave(isExporting(), false);
}
emit clearStatusBarMessage();
if (ret) {
KNotification *notify = new KNotification("DocumentSaved");
notify->setText(i18n("Document <i>%1</i> saved", url().url()));
notify->addContext("url", url().url());
QTimer::singleShot(0, notify, &KNotification::sendEvent);
}
return ret;
}
QByteArray KoDocument::mimeType() const
{
return d->mimeType;
}
void KoDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
void KoDocument::setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag)
{
d->outputMimeType = mimeType;
d->specialOutputFlag = specialOutputFlag;
}
QByteArray KoDocument::outputMimeType() const
{
return d->outputMimeType;
}
int KoDocument::specialOutputFlag() const
{
return d->specialOutputFlag;
}
bool KoDocument::confirmNonNativeSave(const bool exporting) const
{
// "exporting ? 1 : 0" is different from "exporting" because a bool is
// usually implemented like an "int", not "unsigned : 1"
return d->confirmNonNativeSave [ exporting ? 1 : 0 ];
}
void KoDocument::setConfirmNonNativeSave(const bool exporting, const bool on)
{
d->confirmNonNativeSave [ exporting ? 1 : 0] = on;
}
bool KoDocument::saveInBatchMode() const
{
return d->filterManager->getBatchMode();
}
void KoDocument::setSaveInBatchMode(const bool batchMode)
{
d->filterManager->setBatchMode(batchMode);
}
bool KoDocument::isImporting() const
{
return d->isImporting;
}
bool KoDocument::isExporting() const
{
return d->isExporting;
}
void KoDocument::setCheckAutoSaveFile(bool b)
{
d->shouldCheckAutoSaveFile = b;
}
void KoDocument::setAutoErrorHandlingEnabled(bool b)
{
d->autoErrorHandlingEnabled = b;
}
bool KoDocument::isAutoErrorHandlingEnabled() const
{
return d->autoErrorHandlingEnabled;
}
void KoDocument::slotAutoSave()
{
if (d->modified && d->modifiedAfterAutosave && !d->isLoading) {
// Give a warning when trying to autosave an encrypted file when no password is known (should not happen)
if (d->specialOutputFlag == SaveEncrypted && d->password.isNull()) {
// That advice should also fix this error from occurring again
emit statusBarMessage(i18n("The password of this encrypted document is not known. Autosave aborted! Please save your work manually."));
} else {
connect(this, &KoDocument::sigProgress, d->parentPart->currentMainwindow(), &KoMainWindow::slotProgress);
emit statusBarMessage(i18n("Autosaving..."));
d->autosaving = true;
bool ret = saveNativeFormat(autoSaveFile(localFilePath()));
setModified(true);
if (ret) {
d->modifiedAfterAutosave = false;
d->autoSaveTimer.stop(); // until the next change
}
d->autosaving = false;
emit clearStatusBarMessage();
disconnect(this, &KoDocument::sigProgress, d->parentPart->currentMainwindow(), &KoMainWindow::slotProgress);
if (!ret && !d->disregardAutosaveFailure) {
emit statusBarMessage(i18n("Error during autosave! Partition full?"));
}
}
}
}
void KoDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setAutoSave(d->autoSaveDelay);
// XXX: this doesn't belong in KoDocument
foreach(KoView *view, d->parentPart->views()) {
view->updateReadWrite(readwrite);
}
foreach(KoMainWindow *mainWindow, d->parentPart->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KoDocument::setAutoSave(int delay)
{
d->autoSaveDelay = delay;
if (isReadWrite() && d->autoSaveDelay > 0)
d->autoSaveTimer.start(d->autoSaveDelay * 1000);
else
d->autoSaveTimer.stop();
}
KoDocumentInfo *KoDocument::documentInfo() const
{
return d->docInfo;
}
/*
KoDocumentRdfBase *KoDocument::documentRdf() const
{
return d->docRdf;
}
void KoDocument::setDocumentRdf(KoDocumentRdfBase *rdfDocument)
{
delete d->docRdf;
d->docRdf = rdfDocument;
}
*/
bool KoDocument::isModified() const
{
return d->modified;
}
bool KoDocument::saveNativeFormat(const QString & file)
{
d->lastErrorMessage.clear();
KoStore::Backend backend = KoStore::Auto;
if (d->specialOutputFlag == SaveAsDirectoryStore) {
backend = KoStore::Directory;
debugMain << "Saving as uncompressed XML, using directory store.";
}
#ifdef QCA2
else if (d->specialOutputFlag == SaveEncrypted) {
backend = KoStore::Encrypted;
debugMain << "Saving using encrypted backend.";
}
#endif
else if (d->specialOutputFlag == SaveAsFlatXML) {
debugMain << "Saving as a flat XML file.";
QFile f(file);
if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
bool success = saveToStream(&f);
f.close();
return success;
} else
return false;
}
debugMain << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType();
// OLD: bool oasis = d->specialOutputFlag == SaveAsOASIS;
// OLD: QCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType();
QByteArray mimeType = d->outputMimeType;
debugMain << "KoDocument::savingTo mimeType=" << mimeType;
QByteArray nativeOasisMime = nativeOasisMimeType();
bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" || mimeType.startsWith("application/vnd.oasis.opendocument"));
// TODO: use std::auto_ptr or create store on stack [needs API fixing],
// to remove all the 'delete store' in all the branches
KoStore *store = KoStore::createStore(file, KoStore::Write, mimeType, backend);
if (d->specialOutputFlag == SaveEncrypted && !d->password.isNull())
store->setPassword(d->password);
if (store->bad()) {
d->lastErrorMessage = i18n("Could not create the file for saving"); // more details needed?
delete store;
return false;
}
if (oasis) {
return saveNativeFormatODF(store, mimeType);
} else {
return saveNativeFormatCalligra(store);
}
}
bool KoDocument::saveNativeFormatODF(KoStore *store, const QByteArray &mimeType)
{
debugMain << "Saving to OASIS format";
// Tell KoStore not to touch the file names
KoOdfWriteStore odfStore(store);
KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
KoEmbeddedDocumentSaver embeddedSaver;
SavingContext documentContext(odfStore, embeddedSaver);
if (!saveOdf(documentContext)) {
debugMain << "saveOdf failed";
odfStore.closeManifestWriter(false);
delete store;
return false;
}
// Save embedded objects
if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
debugMain << "save embedded documents failed";
odfStore.closeManifestWriter(false);
delete store;
return false;
}
if (store->open("meta.xml")) {
if (!d->docInfo->saveOasis(store) || !store->close()) {
odfStore.closeManifestWriter(false);
delete store;
return false;
}
manifestWriter->addManifestEntry("meta.xml", "text/xml");
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("meta.xml"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
/*
if (d->docRdf && !d->docRdf->saveOasis(store, manifestWriter)) {
d->lastErrorMessage = i18n("Not able to write RDF metadata. Partition full?");
odfStore.closeManifestWriter(false);
delete store;
return false;
}
*/
if (store->open("Thumbnails/thumbnail.png")) {
if (!saveOasisPreview(store, manifestWriter) || !store->close()) {
d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
// No manifest entry!
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
if (!d->versionInfo.isEmpty()) {
if (store->open("VersionList.xml")) {
KoStoreDevice dev(store);
KoXmlWriter *xmlWriter = KoOdfWriteStore::createOasisXmlWriter(&dev,
"VL:version-list");
for (int i = 0; i < d->versionInfo.size(); ++i) {
KoVersionInfo *version = &d->versionInfo[i];
xmlWriter->startElement("VL:version-entry");
xmlWriter->addAttribute("VL:title", version->title);
xmlWriter->addAttribute("VL:comment", version->comment);
xmlWriter->addAttribute("VL:creator", version->saved_by);
xmlWriter->addAttribute("dc:date-time", version->date.toString(Qt::ISODate));
xmlWriter->endElement();
}
xmlWriter->endElement(); // root element
xmlWriter->endDocument();
delete xmlWriter;
store->close();
manifestWriter->addManifestEntry("VersionList.xml", "text/xml");
for (int i = 0; i < d->versionInfo.size(); ++i) {
KoVersionInfo *version = &d->versionInfo[i];
store->addDataToFile(version->data, "Versions/" + version->title);
}
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("VersionList.xml"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
}
// Write out manifest file
if (!odfStore.closeManifestWriter()) {
d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml"));
delete store;
return false;
}
// Remember the given password, if necessary
if (store->isEncrypted() && !d->isExporting)
d->password = store->password();
delete store;
return true;
}
bool KoDocument::saveNativeFormatCalligra(KoStore *store)
{
debugMain << "Saving root";
if (store->open("root")) {
KoStoreDevice dev(store);
if (!saveToStream(&dev) || !store->close()) {
debugMain << "saveToStream failed";
delete store;
return false;
}
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"));
delete store;
return false;
}
if (store->open("documentinfo.xml")) {
QDomDocument doc = KoDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = d->docInfo->save(doc);
KoStoreDevice dev(store);
QByteArray s = doc.toByteArray(); // this is already Utf8!
(void)dev.write(s.data(), s.size());
(void)store->close();
}
if (store->open("preview.png")) {
// ### TODO: missing error checking (The partition could be full!)
savePreview(store);
(void)store->close();
}
if (!completeSaving(store)) {
delete store;
return false;
}
debugMain << "Saving done of url:" << url().url();
if (!store->finalize()) {
delete store;
return false;
}
// Success
delete store;
return true;
}
bool KoDocument::saveToStream(QIODevice *dev)
{
QDomDocument doc = saveXML();
// 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())
warnMain << "wrote " << nwritten << "- expected" << s.size();
return nwritten == (int)s.size();
}
QString KoDocument::checkImageMimeTypes(const QString &mimeType, const QUrl &url) const
{
if (!url.isLocalFile()) return mimeType;
if (url.toLocalFile().endsWith(".kpp")) return "image/png";
QStringList imageMimeTypes;
imageMimeTypes << "image/jpeg"
<< "image/x-psd" << "image/photoshop" << "image/x-photoshop" << "image/x-vnd.adobe.photoshop" << "image/vnd.adobe.photoshop"
<< "image/x-portable-pixmap" << "image/x-portable-graymap" << "image/x-portable-bitmap"
<< "application/pdf"
<< "image/x-exr"
<< "image/x-xcf"
<< "image/x-eps"
<< "image/png"
<< "image/bmp" << "image/x-xpixmap" << "image/gif" << "image/x-xbitmap"
<< "image/tiff"
<< "image/jp2";
if (!imageMimeTypes.contains(mimeType)) return mimeType;
QFile f(url.toLocalFile());
f.open(QIODevice::ReadOnly);
QByteArray ba = f.read(qMin(f.size(), (qint64)512)); // should be enough for images
QMimeType mime = QMimeDatabase().mimeTypeForData(ba);
f.close();
return mime.name();
}
// Called for embedded documents
bool KoDocument::saveToStore(KoStore *_store, const QString & _path)
{
debugMain << "Saving document to store" << _path;
_store->pushDirectory();
// Use the path as the internal url
if (_path.startsWith(STORE_PROTOCOL))
setUrl(QUrl(_path));
else // ugly hack to pass a relative URI
setUrl(QUrl(INTERNAL_PREFIX + _path));
// In the current directory we're the king :-)
if (_store->open("root")) {
KoStoreDevice dev(_store);
if (!saveToStream(&dev)) {
_store->close();
return false;
}
if (!_store->close())
return false;
}
if (!completeSaving(_store))
return false;
// Now that we're done leave the directory again
_store->popDirectory();
debugMain << "Saved document to store";
return true;
}
bool KoDocument::saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter)
{
const QPixmap pix = generatePreview(QSize(128, 128));
if (pix.isNull())
return true; //no thumbnail to save, but the process succeeded
QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
if (preview.isNull())
return false; //thumbnail to save, but the process failed
// ### TODO: freedesktop.org Thumbnail specification (date...)
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly))
return false;
if (! preview.save(&io, "PNG", 0))
return false;
io.close();
manifestWriter->addManifestEntry("Thumbnails/thumbnail.png", "image/png");
return true;
}
bool KoDocument::savePreview(KoStore *store)
{
QPixmap pix = generatePreview(QSize(256, 256));
const QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly))
return false;
if (! preview.save(&io, "PNG")) // ### TODO What is -9 in quality terms?
return false;
io.close();
return true;
}
QPixmap KoDocument::generatePreview(const QSize& size)
{
qreal docWidth, docHeight;
int pixmapSize = qMax(size.width(), size.height());
if (d->pageLayout.width > 1.0) {
docWidth = d->pageLayout.width / 72 * KoDpi::dpiX();
docHeight = d->pageLayout.height / 72 * KoDpi::dpiY();
} else {
// If we don't have a page layout, just draw the top left hand corner
docWidth = 500.0;
docHeight = 500.0;
}
qreal ratio = docWidth / docHeight;
int previewWidth, previewHeight;
if (ratio > 1.0) {
previewWidth = (int) pixmapSize;
previewHeight = (int)(pixmapSize / ratio);
} else {
previewWidth = (int)(pixmapSize * ratio);
previewHeight = (int) pixmapSize;
}
QPixmap pix((int)docWidth, (int)docHeight);
pix.fill(QColor(245, 245, 245));
QRect rc(0, 0, pix.width(), pix.height());
QPainter p;
p.begin(&pix);
paintContent(p, rc);
p.end();
return pix.scaled(QSize(previewWidth, previewHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QString KoDocument::autoSaveFile(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
QMimeType mime = QMimeDatabase().mimeTypeForName(nativeFormatMimeType());
if (! mime.isValid()) {
qFatal("It seems your installation is broken/incomplete because we failed to load the native mimetype \"%s\".", nativeFormatMimeType().constData());
}
const QString extension = mime.preferredSuffix();
if (path.isEmpty()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::tempPath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::homePath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
QUrl url = QUrl::fromLocalFile(path);
Q_ASSERT(url.isLocalFile());
QString dir = QFileInfo(url.toLocalFile()).absolutePath();
QString filename = url.fileName();
retval = QString("%1.%2-autosave%3").arg(dir).arg(filename).arg(extension);
}
return retval;
}
void KoDocument::setDisregardAutosaveFailure(bool disregardFailure)
{
d->disregardAutosaveFailure = disregardFailure;
}
bool KoDocument::importDocument(const QUrl &_url)
{
bool ret;
debugMain << "url=" << _url.url();
d->isImporting = true;
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KoParts::openUrl()) to simulate a
// File --> Import
if (ret) {
debugMain << "success, resetting url";
resetURL();
setTitleModified();
}
d->isImporting = false;
return ret;
}
bool KoDocument::openUrl(const QUrl &_url)
{
debugMain << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
abortLoad();
QUrl url(_url);
bool autosaveOpened = false;
d->isLoading = true;
if (url.isLocalFile() && d->shouldCheckAutoSaveFile) {
QString file = url.toLocalFile();
QString asf = autoSaveFile(file);
if (QFile::exists(asf)) {
//debugMain <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = KMessageBox::warningYesNoCancel(0,
i18n("An autosaved file exists for this document.\nDo you want to open it instead?"));
switch (res) {
case KMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case KMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
d->isLoading = false;
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened) {
resetURL(); // Force save to act like 'Save As'
setReadWrite(true); // enable save button
setModified(true);
}
else {
d->parentPart->addRecentURLToAllMainWindows(_url);
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable, unless we add a KIO::stat here (async).
KFileItem file(url, mimeType(), KFileItem::Unknown);
setReadWrite(file.isWritable());
}
}
return ret;
}
// It seems that people have started to save .docx files as .doc and
// similar for xls and ppt. So let's make a small replacement table
// here and see if we can open the files anyway.
static const struct MimetypeReplacement {
const char *typeFromName; // If the mime type from the name is this...
const char *typeFromContents; // ...and findByFileContents() reports this type...
const char *useThisType; // ...then use this type for real.
} replacementMimetypes[] = {
// doc / docx
{
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
},
{
"application/msword",
"application/zip",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
},
{
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/msword",
"application/msword"
},
{
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/x-ole-storage",
"application/msword"
},
// xls / xlsx
{
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
},
{
"application/vnd.ms-excel",
"application/zip",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
},
{
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel",
"application/vnd.ms-excel"
},
{
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/x-ole-storage",
"application/vnd.ms-excel"
},
// ppt / pptx
{
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
},
{
"application/vnd.ms-powerpoint",
"application/zip",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
},
{
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint"
},
{
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/x-ole-storage",
"application/vnd.ms-powerpoint"
}
};
bool KoDocument::openFile()
{
//debugMain <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QApplication::restoreOverrideCursor();
if (d->autoErrorHandlingEnabled)
// Maybe offer to create a new document with that name ?
KMessageBox::error(0, i18n("The file %1 does not exist.", localFilePath()));
d->isLoading = false;
return false;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
d->specialOutputFlag = 0;
QByteArray _native_format = nativeFormatMimeType();
QUrl u = QUrl::fromLocalFile(localFilePath());
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = QMimeDatabase().mimeTypeForUrl(u).name();
}
// for images, always check content.
typeName = checkImageMimeTypes(typeName, u);
// Sometimes it seems that arguments().mimeType() contains a much
// too generic mime type. In that case, let's try some educated
// guesses based on what we know about file extension.
//
// FIXME: Should we just ignore this and always call
// KMimeType::findByUrl()? David Faure says that it's
// impossible for findByUrl() to fail to initiate the
// mimetype for "*.doc" to application/msword. This hints
// that we should do that. But why does it happen like
// this at all?
if (typeName == "application/zip") {
QString filename = u.fileName();
// None of doc, xls or ppt are really zip files. But docx,
// xlsx and pptx are. This miscategorization seems to only
// crop up when there is a, say, docx file saved as doc. The
// conversion to the docx mimetype will happen below.
if (filename.endsWith(".doc"))
typeName = "application/msword";
else if (filename.endsWith(".xls"))
typeName = "application/vnd.ms-excel";
else if (filename.endsWith(".ppt"))
typeName = "application/vnd.ms-powerpoint";
// Potentially more guesses here...
} else if (typeName == "application/x-ole-storage") {
QString filename = u.fileName();
// None of docx, xlsx or pptx are really OLE files. But doc,
// xls and ppt are. This miscategorization seems to only crop
// up when there is a, say, doc file saved as docx. The
// conversion to the doc mimetype will happen below.
if (filename.endsWith(".docx"))
typeName = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
else if (filename.endsWith(".xlsx"))
typeName = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
else if (filename.endsWith(".pptx"))
typeName = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
// Potentially more guesses here...
}
//debugMain << "mimetypes 3:" << typeName;
// In some cases docx files are saved as doc and similar. We have
// a small hardcoded table for those cases. Check if this is
// applicable here.
for (uint i = 0; i < sizeof(replacementMimetypes) / sizeof(struct MimetypeReplacement); ++i) {
const MimetypeReplacement *replacement = &replacementMimetypes[i];
if (typeName == replacement->typeFromName) {
//debugMain << "found potential replacement target:" << typeName;
// QT5TODO: this needs a new look with the different behaviour of QMimeDatabase
QString typeFromContents = QMimeDatabase().mimeTypeForUrl(u).name();
//debugMain << "found potential replacement:" << typeFromContents;
if (typeFromContents == replacement->typeFromContents) {
typeName = replacement->useThisType;
//debugMain << "So really use this:" << typeName;
break;
}
}
}
//debugMain << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = u.path();
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName(typeName);
const QStringList patterns = mime.isValid() ? mime.globPatterns() : QStringList();
// Find the extension that makes it a backup file, and remove it
for (QStringList::ConstIterator it = patterns.begin(); it != patterns.end(); ++it) {
QString ext = *it;
if (!ext.isEmpty() && ext[0] == '*') {
ext.remove(0, 1);
if (path.endsWith(ext)) {
path.chop(ext.length());
break;
}
}
}
typeName = db.mimeTypeForFile(path, QMimeDatabase::MatchExtension).name();
}
// Special case for flat XML files (e.g. using directory store)
if (u.fileName() == "maindoc.xml" || u.fileName() == "content.xml" || typeName == "inode/directory") {
typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype
d->specialOutputFlag = SaveAsDirectoryStore;
debugMain << "loading" << u.fileName() << ", using directory store for" << localFilePath() << "; typeName=" << typeName;
}
debugMain << localFilePath() << "type:" << typeName;
QString importedFile = localFilePath();
// create the main progress monitoring object for loading, this can
// contain subtasks for filtering and loading
KoProgressProxy *progressProxy = 0;
if (d->progressProxy) {
progressProxy = d->progressProxy;
}
d->progressUpdater = new KoProgressUpdater(progressProxy,
KoProgressUpdater::Unthreaded,
d->profileStream);
d->progressUpdater->setReferenceTime(d->profileReferenceTime);
d->progressUpdater->start(100, i18n("Opening Document"));
setupOpenFileSubProgress();
if (!isNativeFormat(typeName.toLatin1())) {
KoFilter::ConversionStatus status;
importedFile = d->filterManager->importDocument(localFilePath(), typeName, status);
if (status != KoFilter::OK) {
QApplication::restoreOverrideCursor();
QString msg;
switch (status) {
case KoFilter::OK: break;
case KoFilter::FilterCreationError:
msg = i18n("Could not create the filter plugin"); break;
case KoFilter::CreationError:
msg = i18n("Could not create the output document"); break;
case KoFilter::FileNotFound:
msg = i18n("File not found"); break;
case KoFilter::StorageCreationError:
msg = i18n("Cannot create storage"); break;
case KoFilter::BadMimeType:
msg = i18n("Bad MIME type"); break;
case KoFilter::EmbeddedDocError:
msg = i18n("Error in embedded document"); break;
case KoFilter::WrongFormat:
msg = i18n("Format not recognized"); break;
case KoFilter::NotImplemented:
msg = i18n("Not implemented"); break;
case KoFilter::ParsingError:
msg = i18n("Parsing error"); break;
case KoFilter::PasswordProtected:
msg = i18n("Document is password protected"); break;
case KoFilter::InvalidFormat:
msg = i18n("Invalid file format"); break;
case KoFilter::InternalError:
case KoFilter::UnexpectedEOF:
case KoFilter::UnexpectedOpcode:
case KoFilter::StupidError: // ?? what is this ??
case KoFilter::UsageError:
msg = i18n("Internal error"); break;
case KoFilter::OutOfMemory:
msg = i18n("Out of memory"); break;
case KoFilter::FilterEntryNull:
msg = i18n("Empty Filter Plugin"); break;
case KoFilter::NoDocumentCreated:
msg = i18n("Trying to load into the wrong kind of document"); break;
case KoFilter::DownloadFailed:
msg = i18n("Failed to download remote file"); break;
case KoFilter::UserCancelled:
case KoFilter::BadConversionGraph:
// intentionally we do not prompt the error message here
break;
default: msg = i18n("Unknown error"); break;
}
if (d->autoErrorHandlingEnabled && !msg.isEmpty()) {
QString errorMsg(i18n("Could not open %2.\nReason: %1.\n%3", msg, prettyPathOrUrl(), errorMessage()));
KMessageBox::error(0, errorMsg);
}
d->isLoading = false;
delete d->progressUpdater;
d->progressUpdater = 0;
return false;
}
d->isEmpty = false;
debugMain << "importedFile" << importedFile << "status:" << static_cast<int>(status);
}
QApplication::restoreOverrideCursor();
bool ok = true;
if (!importedFile.isEmpty()) { // Something to load (tmp or native file) ?
// The filter, if any, has been applied. It's all native format now.
if (!loadNativeFormat(importedFile)) {
ok = false;
if (d->autoErrorHandlingEnabled) {
showLoadingErrorDialog();
}
}
}
if (importedFile != localFilePath()) {
// We opened a temporary file (result of an import filter)
// Set document URL to empty - we don't want to save in /tmp !
// But only if in readwrite mode (no saving problem otherwise)
// --
// But this isn't true at all. If this is the result of an
// import, then importedFile=temporary_file.kwd and
// file/m_url=foreignformat.ext so m_url is correct!
// So don't resetURL() or else the caption won't be set when
// foreign files are opened (an annoying bug).
// - Clarence
//
#if 0
if (isReadWrite())
resetURL();
#endif
// remove temp file - uncomment this to debug import filters
if (!importedFile.isEmpty()) {
#ifndef NDEBUG
if (!getenv("CALLIGRA_DEBUG_FILTERS"))
#endif
QFile::remove(importedFile);
}
}
if (ok) {
setMimeTypeAfterLoading(typeName);
KNotification *notify = new KNotification("DocumentLoaded");
notify->setText(i18n("Document <i>%1</i> loaded", url().url()));
notify->addContext("url", url().url());
QTimer::singleShot(0, notify, &KNotification::sendEvent);
}
if (progressUpdater()) {
QPointer<KoUpdater> updater
= progressUpdater()->startSubtask(1, "clear undo stack");
updater->setProgress(0);
undoStack()->clear();
updater->setProgress(100);
}
delete d->progressUpdater;
d->progressUpdater = 0;
d->isLoading = false;
return ok;
}
KoProgressUpdater *KoDocument::progressUpdater() const
{
return d->progressUpdater;
}
void KoDocument::setProgressProxy(KoProgressProxy *progressProxy)
{
d->progressProxy = progressProxy;
}
KoProgressProxy* KoDocument::progressProxy() const
{
if (!d->progressProxy) {
KoMainWindow *mainWindow = 0;
if (d->parentPart->mainwindowCount() > 0) {
mainWindow = d->parentPart->mainWindows()[0];
}
d->progressProxy = new DocumentProgressProxy(mainWindow);
}
return d->progressProxy;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KoDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
const bool needConfirm = !isNativeFormat(d->mimeType);
setConfirmNonNativeSave(false, needConfirm);
setConfirmNonNativeSave(true, needConfirm);
}
// The caller must call store->close() if loadAndParse returns true.
bool KoDocument::oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc)
{
//debugMain <<"Trying to open" << filename;
if (!store->open(filename)) {
warnMain << "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);
store->close();
if (!ok) {
errorMain << "Parsing error in " << filename << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
d->lastErrorMessage = i18n("Parsing error in %1 at line %2, column %3\nError message: %4"
, filename , errorLine, errorColumn ,
QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0));
return false;
}
debugMain << "File" << filename << " loaded and parsed";
return true;
}
bool KoDocument::loadNativeFormat(const QString & file_)
{
QString file = file_;
QFileInfo fileInfo(file);
if (!fileInfo.exists()) { // check duplicated from openUrl, but this is useful for templates
d->lastErrorMessage = i18n("The file %1 does not exist.", file);
return false;
}
if (!fileInfo.isFile()) {
file += "/content.xml";
QFileInfo fileInfo2(file);
if (!fileInfo2.exists() || !fileInfo2.isFile()) {
d->lastErrorMessage = i18n("%1 is not a file." , file_);
return false;
}
}
QApplication::setOverrideCursor(Qt::WaitCursor);
debugMain << file;
QFile in;
bool isRawXML = false;
if (d->specialOutputFlag != SaveAsDirectoryStore) { // Don't try to open a directory ;)
in.setFileName(file);
if (!in.open(QIODevice::ReadOnly)) {
QApplication::restoreOverrideCursor();
d->lastErrorMessage = i18n("Could not open the file for reading (check read permissions).");
return false;
}
char buf[6];
buf[5] = 0;
int pos = 0;
do {
if (in.read(buf + pos , 1) < 1) {
QApplication::restoreOverrideCursor();
in.close();
d->lastErrorMessage = i18n("Could not read the beginning of the file.");
return false;
}
if (QChar(buf[pos]).isSpace())
continue;
pos++;
} while (pos < 5);
isRawXML = (qstrnicmp(buf, "<?xml", 5) == 0);
if (! isRawXML)
// also check for broken MathML files, which seem to be rather common
isRawXML = (qstrnicmp(buf, "<math", 5) == 0); // file begins with <math ?
//debugMain <<"PATTERN=" << buf;
}
// Is it plain XML?
if (isRawXML) {
in.seek(0);
QString errorMsg;
int errorLine;
int errorColumn;
KoXmlDocument doc = KoXmlDocument(true);
bool res;
if (doc.setContent(&in, &errorMsg, &errorLine, &errorColumn)) {
res = loadXML(doc, 0);
if (res)
res = completeLoading(0);
} else {
errorMain << "Parsing Error! Aborting! (in KoDocument::loadNativeFormat (QFile))" << endl
<< " Line: " << errorLine << " Column: " << errorColumn << endl
<< " Message: " << errorMsg << endl;
d->lastErrorMessage = i18n("parsing error in the main document at line %1, column %2\nError message: %3", errorLine, errorColumn, i18n(errorMsg.toUtf8()));
res = false;
}
QApplication::restoreOverrideCursor();
in.close();
d->isEmpty = false;
return res;
} else { // It's a calligra store (tar.gz, zip, directory, etc.)
in.close();
return loadNativeFormatFromStore(file);
}
}
bool KoDocument::loadNativeFormatFromStore(const QString& file)
{
KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto;
KoStore *store = KoStore::createStore(file, KoStore::Read, "", backend);
if (store->bad()) {
d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file);
delete store;
QApplication::restoreOverrideCursor();
return false;
}
// Remember that the file was encrypted
if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting)
d->specialOutputFlag = SaveEncrypted;
const bool success = loadNativeFormatFromStoreInternal(store);
// Retrieve the password after loading the file, only then is it guaranteed to exist
if (success && store->isEncrypted() && !d->isImporting)
d->password = store->password();
delete store;
return success;
}
bool KoDocument::loadNativeFormatFromStore(QByteArray &data)
{
bool succes;
KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto;
QBuffer buffer(&data);
KoStore *store = KoStore::createStore(&buffer, KoStore::Read, "", backend);
if (store->bad()) {
delete store;
return false;
}
// Remember that the file was encrypted
if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting)
d->specialOutputFlag = SaveEncrypted;
succes = loadNativeFormatFromStoreInternal(store);
// Retrieve the password after loading the file, only then is it guaranteed to exist
if (succes && store->isEncrypted() && !d->isImporting)
d->password = store->password();
delete store;
return succes;
}
bool KoDocument::loadNativeFormatFromStoreInternal(KoStore *store)
{
bool oasis = true;
/* if (oasis && store->hasFile("manifest.rdf") && d->docRdf) {
d->docRdf->loadOasis(store);
}
*/
// OASIS/OOo file format?
if (store->hasFile("content.xml")) {
// We could check the 'mimetype' file, but let's skip that and be tolerant.
if (!loadOasisFromStore(store)) {
QApplication::restoreOverrideCursor();
return false;
}
} else if (store->hasFile("root") || store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml)
oasis = false;
KoXmlDocument doc = KoXmlDocument(true);
bool ok = oldLoadAndParse(store, "root", doc);
if (ok)
ok = loadXML(doc, store);
if (!ok) {
QApplication::restoreOverrideCursor();
return false;
}
} else {
errorMain << "ERROR: No maindoc.xml" << endl;
d->lastErrorMessage = i18n("Invalid document: no file 'maindoc.xml'.");
QApplication::restoreOverrideCursor();
return false;
}
if (oasis && store->hasFile("meta.xml")) {
KoXmlDocument metaDoc;
KoOdfReadStore oasisStore(store);
if (oasisStore.loadAndParse("meta.xml", metaDoc, d->lastErrorMessage)) {
d->docInfo->loadOasis(metaDoc);
}
} else if (!oasis && store->hasFile("documentinfo.xml")) {
KoXmlDocument doc = KoXmlDocument(true);
if (oldLoadAndParse(store, "documentinfo.xml", doc)) {
d->docInfo->load(doc);
}
} else {
//kDebug( 30003 ) <<"cannot open document info";
delete d->docInfo;
d->docInfo = new KoDocumentInfo(this);
}
if (oasis && store->hasFile("VersionList.xml")) {
KNotification *notify = new KNotification("DocumentHasVersions");
notify->setText(i18n("Document <i>%1</i> contains several versions. Go to File->Versions to open an old version.", store->urlOfStore().url()));
notify->addContext("url", store->urlOfStore().url());
QTimer::singleShot(0, notify, &KNotification::sendEvent);
KoXmlDocument versionInfo;
KoOdfReadStore oasisStore(store);
if (oasisStore.loadAndParse("VersionList.xml", versionInfo, d->lastErrorMessage)) {
KoXmlNode list = KoXml::namedItemNS(versionInfo, KoXmlNS::VL, "version-list");
KoXmlElement e;
forEachElement(e, list) {
if (e.localName() == "version-entry" && e.namespaceURI() == KoXmlNS::VL) {
KoVersionInfo version;
version.comment = e.attribute("comment");
version.title = e.attribute("title");
version.saved_by = e.attribute("creator");
version.date = QDateTime::fromString(e.attribute("date-time"), Qt::ISODate);
store->extractFile("Versions/" + version.title, version.data);
d->versionInfo.append(version);
}
}
}
}
bool res = completeLoading(store);
QApplication::restoreOverrideCursor();
d->isEmpty = false;
return res;
}
// For embedded documents
bool KoDocument::loadFromStore(KoStore *_store, const QString& url)
{
if (_store->open(url)) {
KoXmlDocument doc = KoXmlDocument(true);
doc.setContent(_store->device());
if (!loadXML(doc, _store)) {
_store->close();
return false;
}
_store->close();
} else {
qWarning() << "couldn't open " << url;
}
_store->pushDirectory();
// Store as document URL
if (url.startsWith(STORE_PROTOCOL)) {
setUrl(QUrl::fromUserInput(url));
} else {
setUrl(QUrl(INTERNAL_PREFIX + url));
_store->enterDirectory(url);
}
bool result = completeLoading(_store);
// Restore the "old" path
_store->popDirectory();
return result;
}
bool KoDocument::loadOasisFromStore(KoStore *store)
{
KoOdfReadStore odfStore(store);
if (! odfStore.loadAndParse(d->lastErrorMessage)) {
return false;
}
return loadOdf(odfStore);
}
bool KoDocument::addVersion(const QString& comment)
{
debugMain << "Saving the new version....";
KoStore::Backend backend = KoStore::Auto;
if (d->specialOutputFlag != 0)
return false;
QByteArray mimeType = d->outputMimeType;
QByteArray nativeOasisMime = nativeOasisMimeType();
bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template");
if (!oasis)
return false;
// TODO: use std::auto_ptr or create store on stack [needs API fixing],
// to remove all the 'delete store' in all the branches
QByteArray data;
QBuffer buffer(&data);
KoStore *store = KoStore::createStore(&buffer/*file*/, KoStore::Write, mimeType, backend);
if (store->bad()) {
delete store;
return false;
}
debugMain << "Saving to OASIS format";
KoOdfWriteStore odfStore(store);
KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
Q_UNUSED(manifestWriter); // XXX why?
KoEmbeddedDocumentSaver embeddedSaver;
SavingContext documentContext(odfStore, embeddedSaver);
if (!saveOdf(documentContext)) {
debugMain << "saveOdf failed";
delete store;
return false;
}
// Save embedded objects
if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
debugMain << "save embedded documents failed";
delete store;
return false;
}
// Write out manifest file
if (!odfStore.closeManifestWriter()) {
d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml"));
delete store;
return false;
}
if (!store->finalize()) {
delete store;
return false;
}
delete store;
KoVersionInfo version;
version.comment = comment;
version.title = "Version" + QString::number(d->versionInfo.count() + 1);
version.saved_by = documentInfo()->authorInfo("creator");
version.date = QDateTime::currentDateTime();
version.data = data;
d->versionInfo.append(version);
save(); //finally save the document + the new version
return true;
}
bool KoDocument::isStoredExtern() const
{
return !storeInternal() && hasExternURL();
}
void KoDocument::setModified()
{
d->modified = true;
}
void KoDocument::setModified(bool mod)
{
if (isAutosaving()) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
qCritical(/*1000*/) << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//debugMain<<" url:" << url.path();
//debugMain<<" mod="<<mod<<" MParts mod="<<KoParts::ReadWritePart::isModified()<<" isModified="<<isModified();
if (mod && !d->modifiedAfterAutosave) {
// First change since last autosave -> start the autosave timer
setAutoSave(d->autoSaveDelay);
}
d->modifiedAfterAutosave = mod;
if (mod == isModified())
return;
d->modified = mod;
if (mod) {
d->isEmpty = false;
documentInfo()->updateParameters();
}
// This influences the title
setTitleModified();
emit modified(mod);
}
bool KoDocument::alwaysAllowSaving() const
{
return d->alwaysAllowSaving;
}
void KoDocument::setAlwaysAllowSaving(bool allow)
{
d->alwaysAllowSaving = allow;
}
int KoDocument::queryCloseDia()
{
//debugMain;
QString name;
if (documentInfo()) {
name = documentInfo()->aboutInfo("title");
}
if (name.isEmpty())
name = url().fileName();
if (name.isEmpty())
name = i18n("Untitled");
int res = KMessageBox::warningYesNoCancel(0,
i18n("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name));
switch (res) {
case KMessageBox::Yes :
save(); // NOTE: External files always in native format. ###TODO: Handle non-native format
setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
case KMessageBox::No :
removeAutoSaveFiles();
setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
default : // case KMessageBox::Cancel :
return res; // cancels the rest of the files
}
return res;
}
QString KoDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_WS_WIN
if (url().isLocalFile()) {
_url = QDir::convertSeparators(_url);
}
#endif
return _url;
}
// Note: We do not: Get caption from document info (title(), in about page)
QString KoDocument::caption() const
{
QString c = url().fileName();
if (!c.isEmpty() && c.endsWith(".plan")) {
c.remove(c.lastIndexOf(".plan"), 5);
}
return c;
}
void KoDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
bool KoDocument::completeLoading(KoStore*)
{
return true;
}
bool KoDocument::completeSaving(KoStore*)
{
return true;
}
QDomDocument KoDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument(d->parentPart->componentData().componentName(), tagName, version);
}
//static
QDomDocument KoDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
QDomDocument KoDocument::saveXML()
{
errorMain << "not implemented" << endl;
d->lastErrorMessage = i18n("Internal error: saveXML not implemented");
return QDomDocument();
}
bool KoDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
int KoDocument::supportedSpecialFormats() const
{
// Apps which support special output flags can add reimplement and add to this.
// E.g. this is how did "saving in the 1.1 format".
// SaveAsDirectoryStore is a given since it's implemented by KoDocument itself.
// SaveEncrypted is implemented in KoDocument as well, if QCA2 was found.
#ifdef QCA2
return SaveAsDirectoryStore | SaveEncrypted;
#else
return SaveAsDirectoryStore;
#endif
}
void KoDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KoDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KoDocument::showLoadingErrorDialog()
{
if (errorMessage().isEmpty()) {
KMessageBox::error(0, i18n("Could not open\n%1", localFilePath()));
}
else if (errorMessage() != "USER_CANCELED") {
KMessageBox::error(0, i18n("Could not open %1\nReason: %2", localFilePath(), errorMessage()));
}
}
bool KoDocument::isAutosaving() const
{
return d->autosaving;
}
bool KoDocument::isLoading() const
{
return d->isLoading;
}
void KoDocument::removeAutoSaveFiles()
{
// Eliminate any auto-save file
QString asf = autoSaveFile(localFilePath()); // the one in the current dir
if (QFile::exists(asf))
QFile::remove(asf);
asf = autoSaveFile(QString()); // and the one in $HOME
if (QFile::exists(asf))
QFile::remove(asf);
}
void KoDocument::setBackupFile(bool _b)
{
if (d->backupFile != _b) {
d->backupFile = _b;
emit backupFileChanged(_b);
}
}
bool KoDocument::backupFile()const
{
return d->backupFile;
}
void KoDocument::setBackupPath(const QString & _path)
{
d->backupPath = _path;
}
QString KoDocument::backupPath()const
{
return d->backupPath;
}
bool KoDocument::storeInternal() const
{
return d->storeInternal;
}
void KoDocument::setStoreInternal(bool i)
{
d->storeInternal = i;
//debugMain<<"="<<d->storeInternal<<" doc:"<<url().url();
}
bool KoDocument::hasExternURL() const
{
return !url().scheme().isEmpty()
&& url().scheme() != STORE_PROTOCOL
&& url().scheme() != INTERNAL_PROTOCOL;
}
static const struct {
const char *localName;
const char *documentType;
} TN2DTArray[] = {
{ "text", I18N_NOOP("a word processing") },
{ "spreadsheet", I18N_NOOP("a spreadsheet") },
{ "presentation", I18N_NOOP("a presentation") },
{ "chart", I18N_NOOP("a chart") },
{ "drawing", I18N_NOOP("a drawing") }
};
static const unsigned int numTN2DT = sizeof(TN2DTArray) / sizeof(*TN2DTArray);
QString KoDocument::tagNameToDocumentType(const QString& localName)
{
for (unsigned int i = 0 ; i < numTN2DT ; ++i)
if (localName == TN2DTArray[i].localName)
return i18n(TN2DTArray[i].documentType);
return localName;
}
KoPageLayout KoDocument::pageLayout(int /*pageNumber*/) const
{
return d->pageLayout;
}
void KoDocument::setPageLayout(const KoPageLayout &pageLayout)
{
d->pageLayout = pageLayout;
}
KoUnit KoDocument::unit() const
{
return d->unit;
}
void KoDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
void KoDocument::saveUnitOdf(KoXmlWriter *settingsWriter) const
{
settingsWriter->addConfigItem("unit", unit().symbol());
}
void KoDocument::initEmpty()
{
setEmpty();
setModified(false);
}
QList<KoVersionInfo> & KoDocument::versionList()
{
return d->versionInfo;
}
KUndo2Stack *KoDocument::undoStack()
{
return d->undoStack;
}
void KoDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KoDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KoDocument::endMacro()
{
d->undoStack->endMacro();
}
void KoDocument::slotUndoStackIndexChanged(int idx)
{
// even if the document was already modified, call setModified to re-start autosave timer
setModified(idx != d->undoStack->cleanIndex());
}
void KoDocument::setProfileStream(QTextStream *profilestream)
{
d->profileStream = profilestream;
}
void KoDocument::setProfileReferenceTime(const QTime& referenceTime)
{
d->profileReferenceTime = referenceTime;
}
void KoDocument::clearUndoHistory()
{
d->undoStack->clear();
}
/*
KoGridData &KoDocument::gridData()
{
return d->gridData;
}
KoGuidesData &KoDocument::guidesData()
{
return d->guidesData;
}
*/
bool KoDocument::isEmpty() const
{
return d->isEmpty;
}
void KoDocument::setEmpty()
{
d->isEmpty = true;
}
// static
int KoDocument::defaultAutoSave()
{
return 300;
}
void KoDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
int KoDocument::pageCount() const {
return 1;
}
void KoDocument::setupOpenFileSubProgress() {}
KoDocumentInfoDlg *KoDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
KoDocumentInfoDlg *dlg = new KoDocumentInfoDlg(parent, docInfo);
KoMainWindow *mainwin = dynamic_cast<KoMainWindow*>(parent);
if (mainwin) {
connect(dlg, &KoDocumentInfoDlg::saveRequested, mainwin, &KoMainWindow::slotFileSave);
}
return dlg;
}
bool KoDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KoDocument::url() const
{
return d->m_url;
}
bool KoDocument::closeUrl(bool promptToSave)
{
abortLoad(); //just in case
if (promptToSave) {
if ( d->document->isReadWrite() && d->document->isModified()) {
if (!queryClose())
return false;
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
if ( d->m_bTemp )
{
QFile::remove( d->m_file );
d->m_bTemp = false;
}
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
bool KoDocument::saveAs( const QUrl &kurl )
{
if (!kurl.isValid())
{
qCritical(/*1000*/) << "saveAs: Malformed URL " << kurl.url() << endl;
return false;
}
d->m_duringSaveAs = true;
d->m_originalURL = d->m_url;
d->m_originalFilePath = d->m_file;
d->m_url = kurl; // Store where to upload in saveToURL
d->prepareSaving();
bool result = save(); // Save local file and upload local file
if (!result) {
d->m_url = d->m_originalURL;
d->m_file = d->m_originalFilePath;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
}
return result;
}
bool KoDocument::save()
{
d->m_saveOk = false;
if ( d->m_file.isEmpty() ) // document was created empty
d->prepareSaving();
DocumentProgressProxy *progressProxy = 0;
if (!d->document->progressProxy()) {
KoMainWindow *mainWindow = 0;
if (d->parentPart->mainwindowCount() > 0) {
mainWindow = d->parentPart->mainWindows()[0];
}
progressProxy = new DocumentProgressProxy(mainWindow);
d->document->setProgressProxy(progressProxy);
}
d->document->setUrl(url());
// THIS IS WRONG! KoDocument::saveFile should move here, and whoever subclassed KoDocument to
// reimplement saveFile should now subclass KoPart.
bool ok = d->document->saveFile();
if (progressProxy) {
d->document->setProgressProxy(0);
delete progressProxy;
}
if (ok) {
return saveToUrl();
}
else {
emit canceled(QString());
}
return false;
}
bool KoDocument::waitSaveComplete()
{
if (!d->m_uploadJob)
return d->m_saveOk;
d->m_waitForSave = true;
d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
d->m_waitForSave = false;
return d->m_saveOk;
}
void KoDocument::abortLoad()
{
if ( d->m_statJob ) {
//kDebug(1000) << "Aborting job" << d->m_statJob;
d->m_statJob->kill();
d->m_statJob = 0;
}
if ( d->m_job ) {
//kDebug(1000) << "Aborting job" << d->m_job;
d->m_job->kill();
d->m_job = 0;
}
}
void KoDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KoDocument::localFilePath() const
{
return d->m_file;
}
void KoDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KoDocument::queryClose()
{
if ( !d->document->isReadWrite() || !d->document->isModified() )
return true;
QString docName = url().fileName();
if (docName.isEmpty()) docName = i18n( "Untitled" );
int res = KMessageBox::warningYesNoCancel( 0,
i18n( "The document \"%1\" has been modified.\n"
"Do you want to save your changes or discard them?" , docName ),
i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() );
bool abortClose=false;
bool handled=false;
switch(res) {
case KMessageBox::Yes :
if (!handled)
{
if (d->m_url.isEmpty())
{
KoMainWindow *mainWindow = 0;
if (d->parentPart->mainWindows().count() > 0) {
mainWindow = d->parentPart->mainWindows()[0];
}
KoFileDialog dialog(mainWindow, KoFileDialog::SaveFile, "SaveDocument");
QUrl url = QUrl::fromLocalFile(dialog.filename());
if (url.isEmpty())
return false;
saveAs( url );
}
else
{
save();
}
} else if (abortClose) return false;
return waitSaveComplete();
case KMessageBox::No :
return true;
default : // case KMessageBox::Cancel :
return false;
}
}
bool KoDocument::saveToUrl()
{
if ( d->m_url.isLocalFile() ) {
d->document->setModified( false );
emit completed();
// if m_url is a local file there won't be a temp file -> nothing to remove
Q_ASSERT( !d->m_bTemp );
d->m_saveOk = true;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
return true; // Nothing to do
}
#ifndef Q_OS_WIN
else {
if (d->m_uploadJob) {
QFile::remove(d->m_uploadJob->srcUrl().toLocalFile());
d->m_uploadJob->kill();
d->m_uploadJob = 0;
}
QTemporaryFile *tempFile = new QTemporaryFile();
tempFile->open();
QString uploadFile = tempFile->fileName();
delete tempFile;
QUrl uploadUrl;
uploadUrl.setPath( uploadFile );
// Create hardlink
if (::link(QFile::encodeName(d->m_file), QFile::encodeName(uploadFile)) != 0) {
// Uh oh, some error happened.
return false;
}
d->m_uploadJob = KIO::file_move( uploadUrl, d->m_url, -1, KIO::Overwrite );
#ifndef QT_NO_DBUS
KJobWidgets::setWindow(d->m_uploadJob, 0);
#endif
connect( d->m_uploadJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotUploadFinished(KJob*)) ); // clazy:exclude=old-style-connect
return true;
}
#else
return false;
#endif
}
bool KoDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
return d->openLocalFile();
}
else {
d->openRemoteFile();
return true;
}
}
// have to include this because of Q_PRIVATE_SLOT
#include <moc_KoDocument.cpp>
diff --git a/src/libs/main/KoDocumentEntry.cpp b/src/libs/main/KoDocumentEntry.cpp
index e22773d3..bc0e705c 100644
--- a/src/libs/main/KoDocumentEntry.cpp
+++ b/src/libs/main/KoDocumentEntry.cpp
@@ -1,168 +1,169 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright 2007 David Faure <faure@kde.org>
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 "KoDocumentEntry.h"
#include "KoPart.h"
#include "KoDocument.h"
#include "KoFilter.h"
#include <MainDebug.h>
#include <KoPluginLoader.h>
#include <config.h> // CALLIGRA_OLD_PLUGIN_METADATA
#include <kservicetype.h>
#include <kpluginfactory.h>
#include <QCoreApplication>
#include <limits.h> // UINT_MAX
KoDocumentEntry::KoDocumentEntry()
: m_loader(0)
{
}
KoDocumentEntry::KoDocumentEntry(QPluginLoader *loader)
: m_loader(loader)
{
}
KoDocumentEntry::~KoDocumentEntry()
{
}
QJsonObject KoDocumentEntry::metaData() const
{
return m_loader ? m_loader->metaData().value("MetaData").toObject() : QJsonObject();
}
QString KoDocumentEntry::fileName() const
{
return m_loader ? m_loader->fileName() : QString();
}
/**
* @return TRUE if the service pointer is null
*/
bool KoDocumentEntry::isEmpty() const {
return (m_loader == 0);
}
/**
* @return name of the associated service
*/
QString KoDocumentEntry::name() const {
QJsonObject json = metaData();
json = json.value("KPlugin").toObject();
return json.value("Name").toString();
}
/**
* Mimetypes (and other service types) which this document can handle.
*/
QStringList KoDocumentEntry::mimeTypes() const {
QJsonObject json = metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
return json.value("MimeType").toString().split(';', QString::SkipEmptyParts);
#else
QJsonObject pluginData = json.value("KPlugin").toObject();
return pluginData.value("MimeTypes").toVariant().toStringList();
#endif
}
/**
* @return TRUE if the document can handle the requested mimetype.
*/
bool KoDocumentEntry::supportsMimeType(const QString & _mimetype) const {
return mimeTypes().contains(_mimetype);
}
KoPart *KoDocumentEntry::createKoPart(QString* errorMsg) const
{
if (!m_loader) {
return 0;
}
QObject *obj = m_loader->instance();
KPluginFactory *factory = qobject_cast<KPluginFactory *>(obj);
KoPart *part = factory->create<KoPart>(0, QVariantList());
if (!part) {
if (errorMsg)
*errorMsg = m_loader->errorString();
return 0;
}
return part;
}
KoDocumentEntry KoDocumentEntry::queryByMimeType(const QString & mimetype)
{
QList<KoDocumentEntry> vec = query(mimetype);
if (vec.isEmpty()) {
warnMain << "Got no results with " << mimetype;
// Fallback to the old way (which was probably wrong, but better be safe)
vec = query(mimetype);
if (vec.isEmpty()) {
// Still no match. Either the mimetype itself is unknown, or we have no service for it.
// Help the user debugging stuff by providing some more diagnostics
if (!KServiceType::serviceType(mimetype)) {
errorMain << "Unknown Calligra Plan MimeType " << mimetype << "." << endl;
} else {
errorMain << "Found no Calligra part able to handle " << mimetype << "!" << endl;
errorMain << "Check your installation (does the desktop file have X-KDE-NativeMimeType and Calligraplan/Part, did you install Calligra in a different prefix than KDE, without adding the prefix to /etc/kderc ?)" << endl;
}
return KoDocumentEntry();
}
}
#if 0
// Filthy hack alert -- this'll be properly fixed in the mvc branch.
if (qApp->applicationName() == "flow" && vec.size() == 2) {
return KoDocumentEntry(vec[1]);
}
#endif
return KoDocumentEntry(vec[0]);
}
QList<KoDocumentEntry> KoDocumentEntry::query(const QString & mimetype)
{
QList<KoDocumentEntry> lst;
// Query the trader
const QList<QPluginLoader *> offers = KoPluginLoader::pluginLoaders(QStringLiteral("calligraplan/parts"), mimetype);
foreach(QPluginLoader *pluginLoader, offers) {
lst.append(KoDocumentEntry(pluginLoader));
}
if (lst.count() > 1 && !mimetype.isEmpty()) {
warnMain << "KoDocumentEntry::query " << mimetype << " got " << lst.count() << " offers!";
foreach(const KoDocumentEntry &entry, lst) {
warnMain << entry.name();
}
}
return lst;
}
diff --git a/src/libs/main/KoFilter.cpp b/src/libs/main/KoFilter.cpp
index 2048356d..357ecf22 100644
--- a/src/libs/main/KoFilter.cpp
+++ b/src/libs/main/KoFilter.cpp
@@ -1,64 +1,65 @@
/* This file is part of the KDE libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
2002 Werner Trobin <trobin@kde.org>
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 "KoFilter.h"
#include <QFile>
#include <QUrl>
#include <MainDebug.h>
#include <QStack>
#include "KoFilterManager.h"
#include "KoUpdater.h"
class Q_DECL_HIDDEN KoFilter::Private
{
public:
QPointer<KoUpdater> updater;
Private() :updater(0) {}
};
KoFilter::KoFilter(QObject *parent)
: QObject(parent), m_chain(0), d(new Private)
{
}
KoFilter::~KoFilter()
{
if (d->updater) d->updater->setProgress(100);
delete d;
}
void KoFilter::setUpdater(const QPointer<KoUpdater>& updater)
{
if (d->updater && !updater) {
connect(this, &KoFilter::sigProgress, this, &KoFilter::slotProgress);
} else if (!d->updater && updater) {
connect(this, &KoFilter::sigProgress, this, &KoFilter::slotProgress);
}
d->updater = updater;
}
void KoFilter::slotProgress(int value)
{
if (d->updater) {
d->updater->setValue(value);
}
}
diff --git a/src/libs/main/KoFilterChain.cpp b/src/libs/main/KoFilterChain.cpp
index 4b49cfbf..9ff20635 100644
--- a/src/libs/main/KoFilterChain.cpp
+++ b/src/libs/main/KoFilterChain.cpp
@@ -1,543 +1,544 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
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 "KoFilterChain.h"
#include "KoFilterManager.h" // KoFilterManager::filterAvailable, private API
#include "KoDocumentEntry.h"
#include "KoFilterEntry.h"
#include "KoDocument.h"
#include "KoPart.h"
#include "PriorityQueue_p.h"
#include "KoFilterGraph.h"
#include "KoFilterEdge.h"
#include "KoFilterChainLink.h"
#include "KoFilterVertex.h"
#include <QMetaMethod>
#include <QTemporaryFile>
#include <QMimeDatabase>
#include <MainDebug.h>
#include <limits.h> // UINT_MAX
// Those "defines" are needed in the setupConnections method below.
// Please always keep the strings and the length in sync!
using namespace CalligraFilter;
KoFilterChain::KoFilterChain(const KoFilterManager* manager) :
m_manager(manager), m_state(Beginning), m_inputStorage(0),
m_inputStorageDevice(0), m_outputStorage(0), m_outputStorageDevice(0),
m_inputDocument(0), m_outputDocument(0), m_inputTempFile(0),
m_outputTempFile(0), m_inputQueried(Nil), m_outputQueried(Nil), d(0)
{
}
KoFilterChain::~KoFilterChain()
{
m_chainLinks.deleteAll();
if (filterManagerParentChain() && filterManagerParentChain()->m_outputStorage)
filterManagerParentChain()->m_outputStorage->leaveDirectory();
manageIO(); // Called for the 2nd time in a row -> clean up
}
KoFilter::ConversionStatus KoFilterChain::invokeChain()
{
KoFilter::ConversionStatus status = KoFilter::OK;
m_state = Beginning;
int count = m_chainLinks.count();
// This is needed due to nasty Microsoft design
const ChainLink* parentChainLink = 0;
if (filterManagerParentChain())
parentChainLink = filterManagerParentChain()->m_chainLinks.current();
// No iterator here, as we need m_chainLinks.current() in outputDocument()
m_chainLinks.first();
for (; count > 1 && m_chainLinks.current() && status == KoFilter::OK;
m_chainLinks.next(), --count) {
status = m_chainLinks.current()->invokeFilter(parentChainLink);
m_state = Middle;
manageIO();
}
if (!m_chainLinks.current()) {
warnFilter << "Huh?? Found a null pointer in the chain";
return KoFilter::StupidError;
}
if (status == KoFilter::OK) {
if (m_state & Beginning)
m_state |= End;
else
m_state = End;
status = m_chainLinks.current()->invokeFilter(parentChainLink);
manageIO();
}
m_state = Done;
if (status == KoFilter::OK)
finalizeIO();
return status;
}
QString KoFilterChain::chainOutput() const
{
if (m_state == Done)
return m_inputFile; // as we already called manageIO()
return QString();
}
QString KoFilterChain::inputFile()
{
if (m_inputQueried == File)
return m_inputFile;
else if (m_inputQueried != Nil) {
warnFilter << "You already asked for some different source.";
return QString();
}
m_inputQueried = File;
if (m_state & Beginning) {
if (static_cast<KoFilterManager::Direction>(filterManagerDirection()) ==
KoFilterManager::Import)
m_inputFile = filterManagerImportFile();
else
inputFileHelper(filterManagerKoDocument(), filterManagerImportFile());
} else
if (m_inputFile.isEmpty())
inputFileHelper(m_inputDocument, QString());
return m_inputFile;
}
QString KoFilterChain::outputFile()
{
// sanity check: No embedded filter should ask for a plain file
// ###### CHECK: This will break as soon as we support exporting embedding filters
if (filterManagerParentChain())
warnFilter << "An embedded filter has to use storageFile()!";
if (m_outputQueried == File)
return m_outputFile;
else if (m_outputQueried != Nil) {
warnFilter << "You already asked for some different destination.";
return QString();
}
m_outputQueried = File;
if (m_state & End) {
if (static_cast<KoFilterManager::Direction>(filterManagerDirection()) ==
KoFilterManager::Import)
outputFileHelper(false); // This (last) one gets deleted by the caller
else
m_outputFile = filterManagerExportFile();
} else
outputFileHelper(true);
return m_outputFile;
}
KoStoreDevice* KoFilterChain::storageFile(const QString& name, KoStore::Mode mode)
{
// Plain normal use case
if (m_inputQueried == Storage && mode == KoStore::Read &&
m_inputStorage && m_inputStorage->mode() == KoStore::Read)
return storageNewStreamHelper(&m_inputStorage, &m_inputStorageDevice, name);
else if (m_outputQueried == Storage && mode == KoStore::Write &&
m_outputStorage && m_outputStorage->mode() == KoStore::Write)
return storageNewStreamHelper(&m_outputStorage, &m_outputStorageDevice, name);
else if (m_inputQueried == Nil && mode == KoStore::Read)
return storageHelper(inputFile(), name, KoStore::Read,
&m_inputStorage, &m_inputStorageDevice);
else if (m_outputQueried == Nil && mode == KoStore::Write)
return storageHelper(outputFile(), name, KoStore::Write,
&m_outputStorage, &m_outputStorageDevice);
else {
warnFilter << "Oooops, how did we get here? You already asked for a"
<< " different source/destination?" << endl;
return 0;
}
}
KoDocument* KoFilterChain::inputDocument()
{
if (m_inputQueried == Document)
return m_inputDocument;
else if (m_inputQueried != Nil) {
warnFilter << "You already asked for some different source.";
return 0;
}
if ((m_state & Beginning) &&
static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Export &&
filterManagerKoDocument())
m_inputDocument = filterManagerKoDocument();
else if (!m_inputDocument)
m_inputDocument = createDocument(inputFile());
m_inputQueried = Document;
return m_inputDocument;
}
KoDocument* KoFilterChain::outputDocument()
{
// sanity check: No embedded filter should ask for a document
// ###### CHECK: This will break as soon as we support exporting embedding filters
if (filterManagerParentChain()) {
warnFilter << "An embedded filter has to use storageFile()!";
return 0;
}
if (m_outputQueried == Document)
return m_outputDocument;
else if (m_outputQueried != Nil) {
warnFilter << "You already asked for some different destination.";
return 0;
}
if ((m_state & End) &&
static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Import &&
filterManagerKoDocument())
m_outputDocument = filterManagerKoDocument();
else
m_outputDocument = createDocument(m_chainLinks.current()->to());
m_outputQueried = Document;
return m_outputDocument;
}
void KoFilterChain::dump()
{
debugFilter << "########## KoFilterChain with" << m_chainLinks.count() << " members:";
ChainLink* link = m_chainLinks.first();
while (link) {
link->dump();
link = m_chainLinks.next();
}
debugFilter << "########## KoFilterChain (done) ##########";
}
void KoFilterChain::appendChainLink(KoFilterEntry::Ptr filterEntry, const QByteArray& from, const QByteArray& to)
{
m_chainLinks.append(new ChainLink(this, filterEntry, from, to));
}
void KoFilterChain::prependChainLink(KoFilterEntry::Ptr filterEntry, const QByteArray& from, const QByteArray& to)
{
m_chainLinks.prepend(new ChainLink(this, filterEntry, from, to));
}
QString KoFilterChain::filterManagerImportFile() const
{
return m_manager->importFile();
}
QString KoFilterChain::filterManagerExportFile() const
{
return m_manager->exportFile();
}
KoDocument* KoFilterChain::filterManagerKoDocument() const
{
return m_manager->document();
}
int KoFilterChain::filterManagerDirection() const
{
return m_manager->direction();
}
KoFilterChain* KoFilterChain::filterManagerParentChain() const
{
return m_manager->parentChain();
}
void KoFilterChain::manageIO()
{
m_inputQueried = Nil;
m_outputQueried = Nil;
delete m_inputStorageDevice;
m_inputStorageDevice = 0;
if (m_inputStorage) {
m_inputStorage->close();
delete m_inputStorage;
m_inputStorage = 0;
}
delete m_inputTempFile; // autodelete
m_inputTempFile = 0;
m_inputFile.clear();
if (!m_outputFile.isEmpty()) {
if (m_outputTempFile == 0) {
m_inputTempFile = new QTemporaryFile;
m_inputTempFile->setAutoRemove(true);
m_inputTempFile->setFileName(m_outputFile);
}
else {
m_inputTempFile = m_outputTempFile;
m_outputTempFile = 0;
}
m_inputFile = m_outputFile;
m_outputFile.clear();
m_inputTempFile = m_outputTempFile;
m_outputTempFile = 0;
delete m_outputStorageDevice;
m_outputStorageDevice = 0;
if (m_outputStorage) {
m_outputStorage->close();
// Don't delete the storage if we're just pointing to the
// storage of the parent filter chain
if (!filterManagerParentChain() || m_outputStorage->mode() != KoStore::Write)
delete m_outputStorage;
m_outputStorage = 0;
}
}
if (m_inputDocument != filterManagerKoDocument())
delete m_inputDocument;
m_inputDocument = m_outputDocument;
m_outputDocument = 0;
}
void KoFilterChain::finalizeIO()
{
// In case we export (to a file, of course) and the last
// filter chose to output a KoDocument we have to save it.
// Should be very rare, but well...
// Note: m_*input*Document as we already called manageIO()
if (m_inputDocument &&
static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Export) {
debugFilter << "Saving the output document to the export file " << m_chainLinks.current()->to();
m_inputDocument->setOutputMimeType(m_chainLinks.current()->to());
m_inputDocument->saveNativeFormat(filterManagerExportFile());
m_inputFile = filterManagerExportFile();
}
}
bool KoFilterChain::createTempFile(QTemporaryFile** tempFile, bool autoDelete)
{
if (*tempFile) {
errorFilter << "Ooops, why is there already a temp file???" << endl;
return false;
}
*tempFile = new QTemporaryFile();
(*tempFile)->setAutoRemove(autoDelete);
return (*tempFile)->open();
}
/* Note about Windows & usage of QTemporaryFile
The QTemporaryFile objects m_inputTempFile and m_outputTempFile are just used
to reserve a temporary file with a unique name which then can be used to store
an intermediate format. The filters themselves do not get access to these objects,
but can query KoFilterChain only for the filename and then have to open the files
themselves with their own file handlers (TODO: change this).
On Windows this seems to be a problem and results in content not sync'ed to disk etc.
So unless someone finds out which flags might be needed on opening the files on
Windows to prevent this behaviour (unless these details are hidden away by the
Qt abstraction and cannot be influenced), a workaround is to destruct the
QTemporaryFile objects right after creation again and just take the name,
to avoid having two file handlers on the same file.
A better fix might be to use the QTemporaryFile objects also by the filters,
instead of having them open the same file on their own again, but that needs more work
and is left for... you :)
*/
void KoFilterChain::inputFileHelper(KoDocument* document, const QString& alternativeFile)
{
if (document) {
if (!createTempFile(&m_inputTempFile)) {
delete m_inputTempFile;
m_inputTempFile = 0;
m_inputFile.clear();
return;
}
m_inputFile = m_inputTempFile->fileName();
// See "Note about Windows & usage of QTemporaryFile" above
#ifdef Q_OS_WIN
m_inputTempFile->close();
m_inputTempFile->setAutoRemove(true);
delete m_inputTempFile;
m_inputTempFile = 0;
#endif
document->setOutputMimeType(m_chainLinks.current()->from());
if (!document->saveNativeFormat(m_inputFile)) {
delete m_inputTempFile;
m_inputTempFile = 0;
m_inputFile.clear();
return;
}
} else
m_inputFile = alternativeFile;
}
void KoFilterChain::outputFileHelper(bool autoDelete)
{
if (!createTempFile(&m_outputTempFile, autoDelete)) {
delete m_outputTempFile;
m_outputTempFile = 0;
m_outputFile.clear();
} else {
m_outputFile = m_outputTempFile->fileName();
// See "Note about Windows & usage of QTemporaryFile" above
#ifdef Q_OS_WIN
m_outputTempFile->close();
m_outputTempFile->setAutoRemove(true);
delete m_outputTempFile;
m_outputTempFile = 0;
#endif
}
}
KoStoreDevice* KoFilterChain::storageNewStreamHelper(KoStore** storage, KoStoreDevice** device,
const QString& name)
{
delete *device;
*device = 0;
if ((*storage)->isOpen())
(*storage)->close();
if ((*storage)->bad())
return storageCleanupHelper(storage);
if (!(*storage)->open(name))
return 0;
*device = new KoStoreDevice(*storage);
return *device;
}
KoStoreDevice* KoFilterChain::storageHelper(const QString& file, const QString& streamName,
KoStore::Mode mode, KoStore** storage,
KoStoreDevice** device)
{
if (file.isEmpty())
return 0;
if (*storage) {
debugFilter << "Uh-oh, we forgot to clean up...";
return 0;
}
storageInit(file, mode, storage);
if ((*storage)->bad())
return storageCleanupHelper(storage);
// Seems that we got a valid storage, at least. Even if we can't open
// the stream the "user" asked us to open, we nonetheless change the
// IOState from File to Storage, as it might be possible to open other streams
if (mode == KoStore::Read)
m_inputQueried = Storage;
else // KoStore::Write
m_outputQueried = Storage;
return storageCreateFirstStream(streamName, storage, device);
}
void KoFilterChain::storageInit(const QString& file, KoStore::Mode mode, KoStore** storage)
{
QByteArray appIdentification("");
if (mode == KoStore::Write) {
// To create valid storages we also have to add the mimetype
// magic "applicationIndentifier" to the storage.
// As only filters with a Calligra destination should query
// for a storage to write to, we don't check the content of
// the mimetype here. It doesn't do a lot of harm if someone
// "abuses" this method.
appIdentification = m_chainLinks.current()->to();
}
*storage = KoStore::createStore(file, mode, appIdentification);
}
KoStoreDevice* KoFilterChain::storageCreateFirstStream(const QString& streamName, KoStore** storage,
KoStoreDevice** device)
{
if (!(*storage)->open(streamName))
return 0;
if (*device) {
debugFilter << "Uh-oh, we forgot to clean up the storage device!";
(*storage)->close();
return storageCleanupHelper(storage);
}
*device = new KoStoreDevice(*storage);
return *device;
}
KoStoreDevice* KoFilterChain::storageCleanupHelper(KoStore** storage)
{
// Take care not to delete the storage of the parent chain
if (*storage != m_outputStorage || !filterManagerParentChain() ||
(*storage)->mode() != KoStore::Write)
delete *storage;
*storage = 0;
return 0;
}
KoDocument* KoFilterChain::createDocument(const QString& file)
{
QUrl url;
url.setPath(file);
QMimeType t = QMimeDatabase().mimeTypeForUrl(url);
if (t.isDefault()) {
errorFilter << "No mimetype found for " << file << endl;
return 0;
}
KoDocument *doc = createDocument(t.name().toLatin1());
if (!doc || !doc->loadNativeFormat(file)) {
errorFilter << "Couldn't load from the file" << endl;
delete doc;
return 0;
}
return doc;
}
KoDocument* KoFilterChain::createDocument(const QByteArray& mimeType)
{
KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(mimeType);
if (entry.isEmpty()) {
errorFilter << "Couldn't find a part that can handle mimetype " << mimeType << endl;
}
QString errorMsg;
KoPart *part = entry.createKoPart(&errorMsg);
if (!part) {
errorFilter << "Couldn't create the document: " << errorMsg << endl;
return 0;
}
return part->document();
}
int KoFilterChain::weight() const
{
return m_chainLinks.count();
}
diff --git a/src/libs/main/KoFilterChainLink.cpp b/src/libs/main/KoFilterChainLink.cpp
index 7d2391fb..9c7efa04 100644
--- a/src/libs/main/KoFilterChainLink.cpp
+++ b/src/libs/main/KoFilterChainLink.cpp
@@ -1,152 +1,153 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
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 "KoFilterChainLink.h"
#include <QMetaMethod>
#include <QPluginLoader>
#include <MainDebug.h>
#include "KoFilterEntry.h"
#include "KoFilterManager.h"
#include "KoProgressUpdater.h"
#include "KoUpdater.h"
namespace
{
const char SIGNAL_PREFIX[] = "commSignal";
const int SIGNAL_PREFIX_LEN = 10;
const char SLOT_PREFIX[] = "commSlot";
const int SLOT_PREFIX_LEN = 8;
KoUpdater *createUpdater(KoFilterChain *chain)
{
QPointer<KoUpdater> updater = 0;
Q_ASSERT(chain);
Q_ASSERT(chain->manager());
KoProgressUpdater *pu = chain->manager()->progressUpdater();
if (pu) {
updater = pu->startSubtask(1, "filter");
updater->setProgress(0);
}
return updater;
}
}
namespace CalligraFilter {
ChainLink::ChainLink(KoFilterChain *chain, KoFilterEntry::Ptr filterEntry,
const QByteArray& from, const QByteArray& to)
: m_chain(chain)
, m_filterEntry(filterEntry)
, m_from(from)
, m_to(to)
, m_filter(0)
, m_updater(createUpdater(chain))
{
}
ChainLink::~ChainLink() {
}
KoFilter::ConversionStatus ChainLink::invokeFilter(const ChainLink *const parentChainLink)
{
if (!m_filterEntry) {
errorFilter << "This filter entry is null. Strange stuff going on." << endl;
return KoFilter::FilterEntryNull;
}
m_filter = m_filterEntry->createFilter(m_chain);
if (!m_filter) {
errorFilter << "Couldn't create the filter." << endl;
return KoFilter::FilterCreationError;
}
if (m_updater) {
// if there is an updater, use that for progress reporting
m_filter->setUpdater(m_updater);
}
if (parentChainLink) {
setupCommunication(parentChainLink->m_filter);
}
KoFilter::ConversionStatus status = m_filter->convert(m_from, m_to);
delete m_filter;
m_filter = 0;
if (m_updater) {
m_updater->setProgress(100);
}
return status;
}
void ChainLink::dump() const
{
debugFilter << " Link:" << m_filterEntry->fileName();
}
void ChainLink::setupCommunication(const KoFilter *const parentFilter) const
{
if (!parentFilter)
return;
const QMetaObject *const parent = parentFilter->metaObject();
const QMetaObject *const child = m_filter->metaObject();
if (!parent || !child)
return;
setupConnections(parentFilter, m_filter);
setupConnections(m_filter, parentFilter);
}
void ChainLink::setupConnections(const KoFilter *sender, const KoFilter *receiver) const
{
const QMetaObject * const parent = sender->metaObject();
const QMetaObject * const child = receiver->metaObject();
if (!parent || !child)
return;
int senderMethodCount = parent->methodCount();
for (int i = 0; i < senderMethodCount; ++i) {
QMetaMethod metaMethodSignal = parent->method(i);
if (metaMethodSignal.methodType() != QMetaMethod::Signal)
continue;
// ### untested (QMetaMethod::signature())
if (strncmp(metaMethodSignal.methodSignature(), SIGNAL_PREFIX, SIGNAL_PREFIX_LEN) == 0) {
int receiverMethodCount = child->methodCount();
for (int j = 0; j < receiverMethodCount; ++j) {
QMetaMethod metaMethodSlot = child->method(j);
if (metaMethodSlot.methodType() != QMetaMethod::Slot)
continue;
if (strncmp(metaMethodSlot.methodSignature().constData(), SLOT_PREFIX, SLOT_PREFIX_LEN) == 0) {
if (strcmp(metaMethodSignal.methodSignature().constData() + SIGNAL_PREFIX_LEN, metaMethodSlot.methodSignature().constData() + SLOT_PREFIX_LEN) == 0) {
QByteArray signalString;
signalString.setNum(QSIGNAL_CODE);
signalString += metaMethodSignal.methodSignature();
QByteArray slotString;
slotString.setNum(QSLOT_CODE);
slotString += metaMethodSlot.methodSignature();
QObject::connect(sender, signalString, receiver, slotString);
}
}
}
}
}
}
}
diff --git a/src/libs/main/KoFilterChainLinkList.cpp b/src/libs/main/KoFilterChainLinkList.cpp
index 9b891cce..fc736282 100644
--- a/src/libs/main/KoFilterChainLinkList.cpp
+++ b/src/libs/main/KoFilterChainLinkList.cpp
@@ -1,74 +1,75 @@
/* This file is part of the Calligra libraries
Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
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 "KoFilterChainLinkList.h"
#include "KoFilterChainLink.h"
namespace CalligraFilter {
ChainLinkList::ChainLinkList() {}
ChainLinkList::~ChainLinkList()
{
deleteAll();
}
void ChainLinkList::deleteAll()
{
while(!m_chainLinks.isEmpty()) {
delete m_chainLinks.takeFirst();
}
}
int ChainLinkList::count() const
{
return m_chainLinks.count();
}
ChainLink* ChainLinkList::current() const
{
// use value() because m_current might be out of range for m_chainLinks
return m_chainLinks.value(m_current);
}
ChainLink* ChainLinkList::first()
{
m_current = 0;
return current();
}
ChainLink* ChainLinkList::next()
{
++m_current;
return current();
}
void ChainLinkList::prepend(ChainLink* link)
{
Q_ASSERT(link);
m_chainLinks.prepend(link);
m_current = 0;
}
void ChainLinkList::append(ChainLink* link)
{
Q_ASSERT(link);
m_chainLinks.append(link);
m_current = m_chainLinks.count() -1;
}
}
diff --git a/src/libs/main/KoFilterEdge.cpp b/src/libs/main/KoFilterEdge.cpp
index 2d63fe95..2c19725f 100644
--- a/src/libs/main/KoFilterEdge.cpp
+++ b/src/libs/main/KoFilterEdge.cpp
@@ -1,51 +1,52 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
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 "KoFilterEdge.h"
#include "KoFilterVertex.h"
#include "PriorityQueue_p.h"
namespace CalligraFilter {
Edge::Edge(Vertex* vertex, KoFilterEntry::Ptr filterEntry) :
m_vertex(vertex), m_filterEntry(filterEntry), d(0)
{
}
void Edge::relax(const Vertex* predecessor, PriorityQueue<Vertex>& queue)
{
if (!m_vertex || !predecessor || !m_filterEntry)
return;
if (m_vertex->setKey(predecessor->key() + m_filterEntry->weight)) {
queue.keyDecreased(m_vertex); // maintain the heap property
m_vertex->setPredecessor(predecessor);
}
}
void Edge::dump(const QByteArray& indent) const
{
if (m_vertex)
debugFilter << indent << "Edge -> '" << m_vertex->mimeType()
<< "' (" << m_filterEntry->weight << ")" << endl;
else
debugFilter << indent << "Edge -> '(null)' ("
<< m_filterEntry->weight << ")" << endl;
}
}
diff --git a/src/libs/main/KoFilterEntry.cpp b/src/libs/main/KoFilterEntry.cpp
index d1ebc6f6..38c0c3e5 100644
--- a/src/libs/main/KoFilterEntry.cpp
+++ b/src/libs/main/KoFilterEntry.cpp
@@ -1,102 +1,103 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright 2007 David Faure <faure@kde.org>
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 "KoFilterEntry.h"
#include "KoDocument.h"
#include "KoFilter.h"
#include <MainDebug.h>
#include <KoPluginLoader.h>
#include <config.h> // CALLIGRA_OLD_PLUGIN_METADATA
#include <kpluginfactory.h>
#include <QFile>
#include <limits.h> // UINT_MAX
KoFilterEntry::KoFilterEntry(QPluginLoader *loader)
: m_loader(loader)
{
QJsonObject metadata = loader->metaData().value("MetaData").toObject();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
import = metadata.value("X-KDE-Import").toString().split(',');
export_ = metadata.value("X-KDE-Export").toString().split(',');
int w = metadata.value("X-KDE-Weight").toString().toInt();
#else
import = metadata.value("X-KDE-Import").toVariant().toStringList();
export_ = metadata.value("X-KDE-Export").toVariant().toStringList();
int w = metadata.value("X-KDE-Weight").toInt();
#endif
weight = w < 0 ? UINT_MAX : static_cast<unsigned int>(w);
available = metadata.value("X-KDE-Available").toString();
}
KoFilterEntry::~KoFilterEntry()
{
delete m_loader;
}
QString KoFilterEntry::fileName() const
{
return m_loader->fileName();
}
QList<KoFilterEntry::Ptr> KoFilterEntry::query()
{
QList<KoFilterEntry::Ptr> lst;
QList<QPluginLoader *> offers = KoPluginLoader::pluginLoaders(QStringLiteral("calligraplan/formatfilters"));
QList<QPluginLoader *>::ConstIterator it = offers.constBegin();
unsigned int max = offers.count();
//debugFilter <<"Query returned" << max <<" offers";
for (unsigned int i = 0; i < max; i++) {
//debugFilter <<" desktopEntryPath=" << (*it)->entryPath()
// << " library=" << (*it)->library() << endl;
// Append converted offer
lst.append(KoFilterEntry::Ptr(new KoFilterEntry(*it)));
// Next service
it++;
}
return lst;
}
KoFilter* KoFilterEntry::createFilter(KoFilterChain* chain, QObject* parent)
{
KLibFactory *factory = qobject_cast<KLibFactory *>(m_loader->instance());
if (!factory) {
warnMain << m_loader->errorString();
return 0;
}
QObject* obj = factory->create<KoFilter>(parent);
if (!obj || !obj->inherits("KoFilter")) {
delete obj;
return 0;
}
KoFilter* filter = static_cast<KoFilter*>(obj);
filter->m_chain = chain;
return filter;
}
diff --git a/src/libs/main/KoFilterGraph.cpp b/src/libs/main/KoFilterGraph.cpp
index 9882b654..cd763111 100644
--- a/src/libs/main/KoFilterGraph.cpp
+++ b/src/libs/main/KoFilterGraph.cpp
@@ -1,260 +1,261 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
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 "KoFilterGraph.h"
#include "KoFilterManager.h" // KoFilterManager::filterAvailable, private API
#include "KoDocumentEntry.h"
#include "KoFilterEntry.h"
#include "KoDocument.h"
#include <config.h> // CALLIGRA_OLD_PLUGIN_METADATA
#include "PriorityQueue_p.h"
#include "KoFilterEdge.h"
#include "KoFilterChainLink.h"
#include "KoFilterVertex.h"
#include <QPluginLoader>
#include <QMetaMethod>
#include <MainDebug.h>
#include <limits.h> // UINT_MAX
namespace CalligraFilter {
Graph::Graph(const QByteArray& from)
: m_from(from)
, m_graphValid(false)
, d(0)
{
buildGraph();
shortestPaths(); // Will return after a single lookup if "from" is invalid (->no check here)
}
Graph::~Graph()
{
foreach(Vertex* vertex, m_vertices) {
delete vertex;
}
m_vertices.clear();
}
void Graph::setSourceMimeType(const QByteArray& from)
{
if (from == m_from)
return;
m_from = from;
m_graphValid = false;
// Initialize with "infinity" ...
foreach(Vertex* vertex, m_vertices) {
vertex->reset();
}
// ...and re-run the shortest path search for the new source mime
shortestPaths();
}
KoFilterChain::Ptr Graph::chain(const KoFilterManager* manager, QByteArray& to) const
{
if (!isValid() || !manager)
return KoFilterChain::Ptr();
if (to.isEmpty()) { // if the destination is empty we search the closest Calligra part
to = findCalligraPart();
if (to.isEmpty()) // still empty? strange stuff...
return KoFilterChain::Ptr();
}
const Vertex* vertex = m_vertices.value(to);
if (!vertex || vertex->key() == UINT_MAX)
return KoFilterChain::Ptr();
KoFilterChain::Ptr ret(new KoFilterChain(manager));
// Fill the filter chain with all filters on the path
const Vertex* tmp = vertex->predecessor();
while (tmp) {
const Edge* const edge = tmp->findEdge(vertex);
Q_ASSERT(edge);
ret->prependChainLink(edge->filterEntry(), tmp->mimeType(), vertex->mimeType());
vertex = tmp;
tmp = tmp->predecessor();
}
return ret;
}
void Graph::dump() const
{
#ifndef NDEBUG
debugFilter << "+++++++++ Graph::dump +++++++++";
debugFilter << "From:" << m_from;
foreach(Vertex *vertex, m_vertices) {
vertex->dump(" ");
}
debugFilter << "+++++++++ Graph::dump (done) +++++++++";
#endif
}
// Query the trader and create the vertices and edges representing
// available mime types and filters.
void Graph::buildGraph()
{
// Make sure that all available parts are added to the graph
const QList<KoDocumentEntry> parts(KoDocumentEntry::query());
foreach(const KoDocumentEntry& part, parts) {
QJsonObject metaData = part.metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
#else
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
foreach(const QString& nativeMimeType, nativeMimeTypes) {
const QByteArray key = nativeMimeType.toLatin1();
if (!m_vertices.contains(key))
m_vertices[key] = new Vertex(key);
}
}
// no constraint here - we want *all* :)
const QList<KoFilterEntry::Ptr> filters(KoFilterEntry::query());
foreach(KoFilterEntry::Ptr filter, filters) {
// First add the "starting points" to the dict
foreach (const QString& import, filter->import) {
const QByteArray key = import.toLatin1(); // latin1 is okay here (werner)
// already there?
if (!m_vertices.contains(key))
m_vertices.insert(key, new Vertex(key));
}
// Are we allowed to use this filter at all?
if (KoFilterManager::filterAvailable(filter)) {
foreach(const QString& exportIt, filter->export_) {
// First make sure the export vertex is in place
const QByteArray key = exportIt.toLatin1(); // latin1 is okay here
Vertex* exp = m_vertices.value(key);
if (!exp) {
exp = new Vertex(key);
m_vertices.insert(key, exp);
}
// Then create the appropriate edges
foreach(const QString& import, filter->import) {
m_vertices[import.toLatin1()]->addEdge(new Edge(exp, filter));
}
}
} else
debugFilter << "Filter:" << filter->fileName() << " doesn't apply.";
}
}
// As all edges (=filters) are required to have a positive weight
// we can use Dijkstra's shortest path algorithm from Cormen's
// "Introduction to Algorithms" (p. 527)
// Note: I did some adaptions as our data structures are slightly
// different from the ones used in the book. Further we simply stop
// the algorithm is we don't find any node with a weight != Infinity
// (==UINT_MAX), as this means that the remaining nodes in the queue
// aren't connected anyway.
void Graph::shortestPaths()
{
// Is the requested start mime type valid?
Vertex* from = m_vertices.value(m_from);
if (!from)
return;
// Inititalize start vertex
from->setKey(0);
// Fill the priority queue with all the vertices
PriorityQueue<Vertex> queue(m_vertices);
while (!queue.isEmpty()) {
Vertex *min = queue.extractMinimum();
// Did we already relax all connected vertices?
if (min->key() == UINT_MAX)
break;
min->relaxVertices(queue);
}
m_graphValid = true;
}
QByteArray Graph::findCalligraPart() const
{
// Here we simply try to find the closest Calligra mimetype
const QList<KoDocumentEntry> parts(KoDocumentEntry::query());
QList<KoDocumentEntry>::ConstIterator partIt(parts.constBegin());
QList<KoDocumentEntry>::ConstIterator partEnd(parts.constEnd());
const Vertex *v = 0;
// Be sure that v gets initialized correctly
while (!v && partIt != partEnd) {
QJsonObject metaData = (*partIt).metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
#else
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
QStringList::ConstIterator it = nativeMimeTypes.constBegin();
QStringList::ConstIterator end = nativeMimeTypes.constEnd();
for (; !v && it != end; ++it)
if (!(*it).isEmpty())
v = m_vertices.value((*it).toLatin1());
++partIt;
}
if (!v)
return "";
// Now we try to find the "cheapest" Calligra vertex
while (partIt != partEnd) {
QJsonObject metaData = (*partIt).metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
#else
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
QStringList::ConstIterator it = nativeMimeTypes.constBegin();
QStringList::ConstIterator end = nativeMimeTypes.constEnd();
for (; !v && it != end; ++it) {
QString key = *it;
if (!key.isEmpty()) {
Vertex* tmp = m_vertices.value(key.toLatin1());
if (!v || (tmp && tmp->key() < v->key()))
v = tmp;
}
}
++partIt;
}
// It seems it already is a Calligra part
if (v->key() == 0)
return "";
return v->mimeType();
}
}
diff --git a/src/libs/main/KoFilterManager.cpp b/src/libs/main/KoFilterManager.cpp
index a326c48f..f1fc7d34 100644
--- a/src/libs/main/KoFilterManager.cpp
+++ b/src/libs/main/KoFilterManager.cpp
@@ -1,564 +1,565 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
2000, 2001 Werner Trobin <trobin@kde.org>
Copyright (C) 2004 Nicolas Goutte <goutte@kde.org>
Copyright (C) 2009 Thomas Zander <zander@kde.org>
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 "KoFilterManager.h"
#include "KoFilterManager_p.h"
#include "KoDocument.h"
#include "KoDocumentEntry.h"
#include "KoProgressUpdater.h"
#include <config.h> // CALLIGRA_OLD_PLUGIN_METADATA
#include <QFile>
#include <QLabel>
#include <QVBoxLayout>
#include <QList>
#include <QApplication>
#include <QByteArray>
#include <QMimeDatabase>
#include <QPluginLoader>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <MainDebug.h>
#include <queue>
#include <unistd.h>
// static cache for filter availability
QMap<QString, bool> KoFilterManager::m_filterAvailable;
KoFilterManager::KoFilterManager(KoDocument* document,
KoProgressUpdater* progressUpdater) :
m_document(document), m_parentChain(0), m_graph(""),
d(new Private())
{
Q_UNUSED(progressUpdater)
d->batch = false;
}
KoFilterManager::KoFilterManager(const QString& url, const QByteArray& mimetypeHint,
KoFilterChain* const parentChain) :
m_document(0), m_parentChain(parentChain), m_importUrl(url), m_importUrlMimetypeHint(mimetypeHint),
m_graph(""), d(new Private)
{
d->batch = false;
}
KoFilterManager::KoFilterManager(const QByteArray& mimeType) :
m_document(0), m_parentChain(0), m_graph(""), d(new Private)
{
d->batch = false;
d->importMimeType = mimeType;
}
KoFilterManager::~KoFilterManager()
{
delete d;
}
QString KoFilterManager::importDocument(const QString& url,
const QString& documentMimeType,
KoFilter::ConversionStatus& status)
{
// Find the mime type for the file to be imported.
QString typeName(documentMimeType);
QUrl u(url);
if (documentMimeType.isEmpty()) {
QMimeType t = QMimeDatabase().mimeTypeForUrl(u);
if (t.isValid()) {
typeName = t.name();
}
}
m_graph.setSourceMimeType(typeName.toLatin1()); // .latin1() is okay here (Werner)
if (!m_graph.isValid()) {
bool userCancelled = false;
warnFilter << "Can't open " << typeName << ", trying filter chooser";
if (m_document) {
if (!m_document->isAutoErrorHandlingEnabled()) {
status = KoFilter::BadConversionGraph;
return QString();
}
QByteArray nativeFormat = m_document->nativeFormatMimeType();
QApplication::setOverrideCursor(Qt::ArrowCursor);
KoFilterChooser chooser(0,
KoFilterManager::mimeFilter(nativeFormat, KoFilterManager::Import,
m_document->extraNativeMimeTypes()), nativeFormat, u);
if (chooser.exec()) {
QByteArray f = chooser.filterSelected().toLatin1();
if (f == nativeFormat) {
status = KoFilter::OK;
QApplication::restoreOverrideCursor();
return url;
}
m_graph.setSourceMimeType(f);
} else
userCancelled = true;
QApplication::restoreOverrideCursor();
}
if (!m_graph.isValid()) {
errorFilter << "Couldn't create a valid graph for this source mimetype: "
<< typeName;
importErrorHelper(typeName, userCancelled);
status = KoFilter::BadConversionGraph;
return QString();
}
}
KoFilterChain::Ptr chain(0);
// Are we owned by a KoDocument?
if (m_document) {
QByteArray mimeType = m_document->nativeFormatMimeType();
QStringList extraMimes = m_document->extraNativeMimeTypes();
int i = 0;
int n = extraMimes.count();
chain = m_graph.chain(this, mimeType);
while (i < n) {
QByteArray extraMime = extraMimes[i].toUtf8();
// TODO check if its the same target mime then continue
KoFilterChain::Ptr newChain(0);
newChain = m_graph.chain(this, extraMime);
if (!chain || (newChain && newChain->weight() < chain->weight()))
chain = newChain;
++i;
}
} else if (!d->importMimeType.isEmpty()) {
chain = m_graph.chain(this, d->importMimeType);
} else {
errorFilter << "You aren't supposed to use import() from a filter!" << endl;
status = KoFilter::UsageError;
return QString();
}
if (!chain) {
errorFilter << "Couldn't create a valid filter chain!" << endl;
importErrorHelper(typeName);
status = KoFilter::BadConversionGraph;
return QString();
}
// Okay, let's invoke the filters one after the other
m_direction = Import; // vital information!
m_importUrl = url; // We want to load that file
m_exportUrl.clear(); // This is null for sure, as embedded stuff isn't
// allowed to use that method
status = chain->invokeChain();
m_importUrl.clear(); // Reset the import URL
if (status == KoFilter::OK)
return chain->chainOutput();
return QString();
}
KoFilter::ConversionStatus KoFilterManager::exportDocument(const QString& url, QByteArray& mimeType)
{
bool userCancelled = false;
// The import url should already be set correctly (null if we have a KoDocument
// file manager and to the correct URL if we have an embedded manager)
m_direction = Export; // vital information!
m_exportUrl = url;
KoFilterChain::Ptr chain;
if (m_document) {
// We have to pick the right native mimetype as source.
QStringList nativeMimeTypes;
nativeMimeTypes.append(m_document->nativeFormatMimeType());
nativeMimeTypes += m_document->extraNativeMimeTypes();
QStringList::ConstIterator it = nativeMimeTypes.constBegin();
const QStringList::ConstIterator end = nativeMimeTypes.constEnd();
for (; !chain && it != end; ++it) {
m_graph.setSourceMimeType((*it).toLatin1());
if (m_graph.isValid())
chain = m_graph.chain(this, mimeType);
}
} else if (!m_importUrlMimetypeHint.isEmpty()) {
debugFilter << "Using the mimetype hint:" << m_importUrlMimetypeHint;
m_graph.setSourceMimeType(m_importUrlMimetypeHint);
} else {
QUrl u;
u.setPath(m_importUrl);
QMimeType t = QMimeDatabase().mimeTypeForUrl(u);
if (!t.isValid() || t.isDefault()) {
errorFilter << "No mimetype found for" << m_importUrl;
return KoFilter::BadMimeType;
}
m_graph.setSourceMimeType(t.name().toLatin1());
if (!m_graph.isValid()) {
warnFilter << "Can't open" << t.name() << ", trying filter chooser";
QApplication::setOverrideCursor(Qt::ArrowCursor);
KoFilterChooser chooser(0, KoFilterManager::mimeFilter(), QString(), u);
if (chooser.exec())
m_graph.setSourceMimeType(chooser.filterSelected().toLatin1());
else
userCancelled = true;
QApplication::restoreOverrideCursor();
}
}
if (!m_graph.isValid()) {
errorFilter << "Couldn't create a valid graph for this source mimetype.";
if (!d->batch && !userCancelled) KMessageBox::error(0, i18n("Could not export file."), i18n("Missing Export Filter"));
return KoFilter::BadConversionGraph;
}
if (!chain) // already set when coming from the m_document case
chain = m_graph.chain(this, mimeType);
if (!chain) {
errorFilter << "Couldn't create a valid filter chain to " << mimeType << " !" << endl;
if (!d->batch) KMessageBox::error(0, i18n("Could not export file."), i18n("Missing Export Filter"));
return KoFilter::BadConversionGraph;
}
return chain->invokeChain();
}
namespace // in order not to mess with the global namespace ;)
{
// This class is needed only for the static mimeFilter method
class Vertex
{
public:
Vertex(const QByteArray& mimeType) : m_color(White), m_mimeType(mimeType) {}
enum Color { White, Gray, Black };
Color color() const {
return m_color;
}
void setColor(Color color) {
m_color = color;
}
QByteArray mimeType() const {
return m_mimeType;
}
void addEdge(Vertex* vertex) {
if (vertex) m_edges.append(vertex);
}
QList<Vertex*> edges() const {
return m_edges;
}
private:
Color m_color;
QByteArray m_mimeType;
QList<Vertex*> m_edges;
};
// Some helper methods for the static stuff
// This method builds up the graph in the passed ascii dict
void buildGraph(QHash<QByteArray, Vertex*>& vertices, KoFilterManager::Direction direction)
{
QStringList stopList; // Lists of mimetypes that are considered end of chains
stopList << "text/plain";
stopList << "text/csv";
stopList << "text/x-tex";
stopList << "text/html";
// partly copied from build graph, but I don't see any other
// way without crude hacks, as we have to obey the direction here
QList<KoDocumentEntry> parts(KoDocumentEntry::query(QString()));
QList<KoDocumentEntry>::ConstIterator partIt(parts.constBegin());
QList<KoDocumentEntry>::ConstIterator partEnd(parts.constEnd());
while (partIt != partEnd) {
QJsonObject metaData = (*partIt).metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
#else
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
QStringList::ConstIterator it = nativeMimeTypes.constBegin();
const QStringList::ConstIterator end = nativeMimeTypes.constEnd();
for (; it != end; ++it)
if (!(*it).isEmpty()) {
const QByteArray key = (*it).toLatin1();
if (!vertices.contains(key))
vertices.insert(key, new Vertex(key));
}
++partIt;
}
QList<KoFilterEntry::Ptr> filters = KoFilterEntry::query(); // no constraint here - we want *all* :)
QList<KoFilterEntry::Ptr>::ConstIterator it = filters.constBegin();
QList<KoFilterEntry::Ptr>::ConstIterator end = filters.constEnd();
foreach(KoFilterEntry::Ptr filterEntry, filters)
for (; it != end; ++it) {
QStringList impList; // Import list
QStringList expList; // Export list
// Now we have to exclude the "stop" mimetypes (in the right direction!)
if (direction == KoFilterManager::Import) {
// Import: "stop" mime type should not appear in export
foreach(const QString & testIt, (*it)->export_) {
if (!stopList.contains(testIt))
expList.append(testIt);
}
impList = (*it)->import;
} else {
// Export: "stop" mime type should not appear in import
foreach(const QString & testIt, (*it)->import) {
if (!stopList.contains(testIt))
impList.append(testIt);
}
expList = (*it)->export_;
}
if (impList.empty() || expList.empty()) {
// This filter cannot be used under these conditions
debugFilter << "Filter:" << (*it)->fileName() << " ruled out";
continue;
}
// First add the "starting points" to the dict
QStringList::ConstIterator importIt = impList.constBegin();
const QStringList::ConstIterator importEnd = impList.constEnd();
for (; importIt != importEnd; ++importIt) {
const QByteArray key = (*importIt).toLatin1(); // latin1 is okay here (werner)
// already there?
if (!vertices[ key ])
vertices.insert(key, new Vertex(key));
}
// Are we allowed to use this filter at all?
if (KoFilterManager::filterAvailable(*it)) {
QStringList::ConstIterator exportIt = expList.constBegin();
const QStringList::ConstIterator exportEnd = expList.constEnd();
for (; exportIt != exportEnd; ++exportIt) {
// First make sure the export vertex is in place
const QByteArray key = (*exportIt).toLatin1(); // latin1 is okay here
Vertex* exp = vertices[ key ];
if (!exp) {
exp = new Vertex(key);
vertices.insert(key, exp);
}
// Then create the appropriate edges depending on the
// direction (import/export)
// This is the chunk of code which actually differs from the
// graph stuff (apart from the different vertex class)
importIt = impList.constBegin(); // ### TODO: why only the first one?
if (direction == KoFilterManager::Import) {
for (; importIt != importEnd; ++importIt)
exp->addEdge(vertices[(*importIt).toLatin1()]);
} else {
for (; importIt != importEnd; ++importIt)
vertices[(*importIt).toLatin1()]->addEdge(exp);
}
}
} else {
debugFilter << "Filter:" << (*it)->fileName() << " does not apply.";
}
}
}
// This method runs a BFS on the graph to determine the connected
// nodes. Make sure that the graph is "cleared" (the colors of the
// nodes are all white)
QStringList connected(const QHash<QByteArray, Vertex*>& vertices, const QByteArray& mimetype)
{
if (mimetype.isEmpty())
return QStringList();
Vertex *v = vertices[ mimetype ];
if (!v)
return QStringList();
v->setColor(Vertex::Gray);
std::queue<Vertex*> queue;
queue.push(v);
QStringList connected;
while (!queue.empty()) {
v = queue.front();
queue.pop();
QList<Vertex*> edges = v->edges();
foreach(Vertex* current, edges) {
if (current->color() == Vertex::White) {
current->setColor(Vertex::Gray);
queue.push(current);
}
}
v->setColor(Vertex::Black);
connected.append(v->mimeType());
}
return connected;
}
} // anon namespace
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KoFilterManager::mimeFilter(const QByteArray &mimetype, Direction direction, const QStringList &extraNativeMimeTypes)
{
//debugFilter <<"mimetype=" << mimetype <<" extraNativeMimeTypes=" << extraNativeMimeTypes;
QHash<QByteArray, Vertex*> vertices;
buildGraph(vertices, direction);
// TODO maybe use the fake vertex trick from the method below, to make the search faster?
QStringList nativeMimeTypes;
nativeMimeTypes.append(QString::fromLatin1(mimetype));
nativeMimeTypes += extraNativeMimeTypes;
// Add the native mimetypes first so that they are on top.
QStringList lst = nativeMimeTypes;
// Now look for filters which output each of those natives mimetypes
foreach(const QString &natit, nativeMimeTypes) {
const QStringList outMimes = connected(vertices, natit.toLatin1());
//debugFilter <<"output formats connected to mime" << natit <<" :" << outMimes;
foreach(const QString &mit, outMimes) {
if (!lst.contains(mit)) // append only if not there already. Qt4: QSet<QString>?
lst.append(mit);
}
}
foreach(Vertex* vertex, vertices) {
delete vertex;
}
vertices.clear();
return lst;
}
QStringList KoFilterManager::mimeFilter()
{
QHash<QByteArray, Vertex*> vertices;
buildGraph(vertices, KoFilterManager::Import);
QList<KoDocumentEntry> parts(KoDocumentEntry::query( QString()));
QList<KoDocumentEntry>::ConstIterator partIt(parts.constBegin());
QList<KoDocumentEntry>::ConstIterator partEnd(parts.constEnd());
if (partIt == partEnd)
return QStringList();
// To find *all* reachable mimetypes, we have to resort to
// a small hat trick, in order to avoid multiple searches:
// We introduce a fake vertex, which is connected to every
// single Calligra mimetype. Due to that one BFS is enough :)
// Now we just need an... ehrm.. unique name for our fake mimetype
Vertex *v = new Vertex("supercalifragilistic/x-pialadocious");
vertices.insert("supercalifragilistic/x-pialadocious", v);
while (partIt != partEnd) {
QJsonObject metaData = (*partIt).metaData();
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
#else
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
QStringList::ConstIterator it = nativeMimeTypes.constBegin();
const QStringList::ConstIterator end = nativeMimeTypes.constEnd();
for (; it != end; ++it)
if (!(*it).isEmpty())
v->addEdge(vertices[(*it).toLatin1()]);
++partIt;
}
QStringList result = connected(vertices, "supercalifragilistic/x-pialadocious");
// Finally we have to get rid of our fake mimetype again
result.removeAll("supercalifragilistic/x-pialadocious");
return result;
}
// Here we check whether the filter is available. This stuff is quite slow,
// but I don't see any other convenient (for the user) way out :}
bool KoFilterManager::filterAvailable(KoFilterEntry::Ptr entry)
{
if (!entry)
return false;
if (entry->available != "check")
return true;
// QT5TODO
#if 1
return true;
#else
//kDebug( 30500 ) <<"Checking whether" << entry->service()->name() <<" applies.";
// generate some "unique" key
QString key = entry->service()->name() + " - " + entry->service()->library();
if (!m_filterAvailable.contains(key)) {
//kDebug( 30500 ) <<"Not cached, checking...";
KLibrary library(QFile::encodeName(entry->service()->library()));
if (library.fileName().isEmpty()) {
warnFilter << "Huh?? Couldn't load the lib: "
<< entry->service()->library();
m_filterAvailable[ key ] = false;
return false;
}
// This code is "borrowed" from klibloader ;)
QByteArray symname = "check_" + QString(library.objectName()).toLatin1();
KLibrary::void_function_ptr sym = library.resolveFunction(symname);
if (!sym) {
// warnFilter << "The library " << library.objectName()
// << " does not offer a check_" << library.objectName()
// << " function." << endl;
m_filterAvailable[key] = false;
} else {
typedef int (*t_func)();
t_func check = (t_func)sym;
m_filterAvailable[ key ] = check() == 1;
}
}
return m_filterAvailable[key];
#endif
}
void KoFilterManager::importErrorHelper(const QString& mimeType, const bool suppressDialog)
{
QString tmp = i18n("Could not import file of type\n%1", mimeType);
// ###### FIXME: use KLibLoader::lastErrorMessage() here
if (!suppressDialog) KMessageBox::error(0, tmp, i18n("Missing Import Filter"));
}
void KoFilterManager::setBatchMode(const bool batch)
{
d->batch = batch;
}
bool KoFilterManager::getBatchMode(void) const
{
return d->batch;
}
KoProgressUpdater* KoFilterManager::progressUpdater() const
{
return 0;
// if (d->progressUpdater.isNull()) {
// // somebody, probably its parent, deleted our progress updater for us
// return 0;
// }
// return d->progressUpdater.data();
}
diff --git a/src/libs/main/KoFilterManager_p.cpp b/src/libs/main/KoFilterManager_p.cpp
index 4e326727..22fbe4ed 100644
--- a/src/libs/main/KoFilterManager_p.cpp
+++ b/src/libs/main/KoFilterManager_p.cpp
@@ -1,91 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
2000, 2001 Werner Trobin <trobin@kde.org>
Copyright (C) 2004 Nicolas Goutte <goutte@kde.org>
Copyright (C) 2009 Thomas Zander <zander@kde.org>
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 "KoFilterManager_p.h"
#include <QVBoxLayout>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMimeDatabase>
#include <klocalizedstring.h>
#include <ksqueezedtextlabel.h>
#include <unistd.h>
KoFilterChooser::KoFilterChooser(QWidget *parent, const QStringList &mimeTypes, const QString &/*nativeFormat*/, const QUrl &url)
: KoDialog(parent),
m_mimeTypes(mimeTypes)
{
setObjectName("kofilterchooser");
setInitialSize(QSize(300, 350));
setButtons(KoDialog::Ok|KoDialog::Cancel);
setDefaultButton(KoDialog::Ok);
setCaption(i18n("Choose Filter"));
setModal(true);
QWidget *page = new QWidget(this);
setMainWidget(page);
QVBoxLayout *layout = new QVBoxLayout(page);
if (url.isValid()) {
KSqueezedTextLabel *l = new KSqueezedTextLabel(url.path(), page);
layout->addWidget(l);
}
m_filterList = new QListWidget(page);
layout->addWidget(m_filterList);
page->setLayout(layout);
Q_ASSERT(!m_mimeTypes.isEmpty());
QMimeDatabase db;
for (QStringList::ConstIterator it = m_mimeTypes.constBegin();
it != m_mimeTypes.constEnd();
++it) {
QMimeType mime = db.mimeTypeForName(*it);
const QString name = mime.isValid() ? mime.comment() : *it;
if (! name.isEmpty()) {
QListWidgetItem *item = new QListWidgetItem(name, m_filterList);
item->setData(32, *it);
}
}
m_filterList->sortItems();
if (m_filterList->currentRow() == -1)
m_filterList->setCurrentRow(0);
m_filterList->setFocus();
connect(m_filterList, &QListWidget::itemDoubleClicked, this, &QDialog::accept);
resize(QSize(520, 400));//.expandedTo(minimumSizeHint()));
}
KoFilterChooser::~KoFilterChooser()
{
}
QString KoFilterChooser::filterSelected()
{
QListWidgetItem *item = m_filterList->currentItem();
return item->data(32).toString();
}
diff --git a/src/libs/main/KoFilterVertex.cpp b/src/libs/main/KoFilterVertex.cpp
index 3113cc1b..e19a7f83 100644
--- a/src/libs/main/KoFilterVertex.cpp
+++ b/src/libs/main/KoFilterVertex.cpp
@@ -1,95 +1,96 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
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 "KoFilterVertex.h"
#include <limits.h> // UINT_MAX
#include "KoFilterEdge.h"
namespace CalligraFilter {
Vertex::Vertex(const QByteArray& mimeType)
: m_predecessor(0)
, m_mimeType(mimeType)
, m_weight(UINT_MAX)
, m_index(-1)
, d(0)
{
}
Vertex::~Vertex()
{
qDeleteAll(m_edges);
}
bool Vertex::setKey(unsigned int key)
{
if (m_weight > key) {
m_weight = key;
return true;
}
return false;
}
void Vertex::reset()
{
m_weight = UINT_MAX;
m_predecessor = 0;
}
void Vertex::addEdge(Edge* edge)
{
if (!edge || edge->weight() == 0)
return;
m_edges.append(edge);
}
const Edge* Vertex::findEdge(const Vertex* vertex) const
{
if (!vertex)
return 0;
const Edge* edge = 0;
foreach(Edge* e, m_edges) {
if (e->vertex() == vertex &&
(!edge || e->weight() < edge->weight())) {
edge = e;
}
}
return edge;
}
void Vertex::relaxVertices(PriorityQueue<Vertex>& queue)
{
foreach(Edge* e, m_edges) {
e->relax(this, queue);
}
}
void Vertex::dump(const QByteArray& indent) const
{
#ifdef NDEBUG
Q_UNUSED(indent)
#else
debugFilter << indent << "Vertex:" << m_mimeType << " (" << m_weight << "):";
const QByteArray i(indent + " ");
foreach(Edge* edge, m_edges) {
edge->dump(i);
}
#endif
}
}
diff --git a/src/libs/main/KoMainWindow.cpp b/src/libs/main/KoMainWindow.cpp
index b8d5946a..48508a69 100644
--- a/src/libs/main/KoMainWindow.cpp
+++ b/src/libs/main/KoMainWindow.cpp
@@ -1,2150 +1,2151 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2006 David Faure <faure@kde.org>
Copyright (C) 2007, 2009 Thomas zander <zander@kde.org>
Copyright (C) 2010 Benjamin Port <port.benjamin@gmail.com>
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 <KoPageLayoutDialog.h>
#include <KoPageLayout.h>
#include "KoApplication.h"
#include <KoIcon.h>
#include "KoResourcePaths.h"
#include "KoComponentData.h"
#include <config.h>
#include <KoDockRegistry.h>
#include <krecentdirs.h>
#include <khelpmenu.h>
#include <krecentfilesaction.h>
#include <kaboutdata.h>
#include <ktoggleaction.h>
#include <kmessagebox.h>
#include <KoNetAccess.h>
#include <kedittoolbar.h>
#include <QTemporaryFile>
#include <krecentdocument.h>
#include <klocalizedstring.h>
#include <ktoolinvocation.h>
#include <kxmlguifactory.h>
#include <kfileitem.h>
#include <ktoolbar.h>
#include <kactionmenu.h>
#include <kactioncollection.h>
#include <KWindowConfig>
#ifdef HAVE_KACTIVITIES
#include <KActivities/ResourceInstance>
#endif
// // qt includes
#include <QDockWidget>
#include <QApplication>
#include <QLayout>
#include <QLabel>
#include <QProgressBar>
#include <QTabBar>
#include <QPrinter>
#include <QPrintDialog>
#include <QDesktopWidget>
#include <QPrintPreviewDialog>
#include <QCloseEvent>
#include <QPointer>
#include <QByteArray>
#include <QMutex>
#include <QMutexLocker>
#include <QFontDatabase>
#include <QMimeDatabase>
#include <QStatusBar>
#include <QMenuBar>
#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<KoView*> rootViews;
// PartManager
QPointer<KoPart> rootPart;
QPointer<KoPart> partToOpen;
QPointer<KoPart> activePart;
QPointer<KoPart> m_activePart;
QPointer<KoPart> m_registeredPart;
KoView *activeView;
QWidget *m_activeWidget;
QPointer<QProgressBar> progress;
QMutex progressMutex;
QList<QAction *> 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<QString, QDockWidget *> dockWidgetsMap;
KActionMenu *dockWidgetMenu;
QMap<QDockWidget *, bool> dockWidgetVisibilityMap;
QList<QDockWidget *> 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<KoPrintJob* (KoMainWindow::*)(void)>(&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;
}
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<KoView*> 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<KoMainWindow *>(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)
{
// 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();
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<KoDocument*>(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<KoDocument*>(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<KoDocument*>(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("<qt>Saving as a %1 may result in some loss of formatting."
"<p>Do you still want to save in this format?</qt>",
QString("<b>%1</b>").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("<qt>Exporting as a %1 may result in some loss of formatting."
"<p>Do you still want to export to this format?</qt>",
QString("<b>%1</b>").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<QString, QDockWidget*>::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("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", 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()<<Q_FUNC_INFO<<initDocFlags<<doc<<newpart<<newdoc;
if (!newdoc)
return;
disconnect(newdoc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress);
if ((!doc && initDocFlags == InitDocFileNew) || (doc && !doc->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)
{
// Create a copy, because the original QUrl in the map of recent files in
// KRecentFilesAction may get deleted.
(void) openDocument(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<KoPageLayoutDialog> 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()<<Q_FUNC_INFO<<pDoc->url()<<pDoc->url().isValid()<<startUrl.toLocalFile();
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KoPrintJob *printJob = rootView()->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<KToggleAction *>(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<QStatusBar *>();
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()<<Q_FUNC_INFO<<factory->id()<<d->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<Qt::DockWidgetArea>(group.readEntry("DockArea", static_cast<int>(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<QDockWidget*> KoMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
/*QList<KoCanvasObserverBase*> KoMainWindow::canvasObservers() const
{
QList<KoCanvasObserverBase*> observers;
foreach(QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(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<QDockWidget*>(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<KoPart*>(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<KoView *>(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<KToolBar *>(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<const QWidget *>( 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/KoPart.cpp b/src/libs/main/KoPart.cpp
index 20f5639a..91aaa011 100644
--- a/src/libs/main/KoPart.cpp
+++ b/src/libs/main/KoPart.cpp
@@ -1,282 +1,283 @@
/* This file is part of the KDE project
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* Copyright (C) 2000-2005 David Faure <faure@kde.org>
* Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2010-2012 Boudewijn Rempt <boud@kogmbh.com>
* Copyright (C) 2011 Inge Wallin <ingwa@kogmbh.com>
*
* 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 <KoComponentData.h>
//#include <KoCanvasController.h>
//#include <KoCanvasControllerWidget.h>
#include <KoResourcePaths.h>
#include <MainDebug.h>
#include <kxmlguifactory.h>
#include <kdesktopfile.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <QFileInfo>
#include <QGraphicsScene>
#include <QGraphicsProxyWidget>
#include <QMimeDatabase>
#ifndef QT_NO_DBUS
#include <QDBusConnection>
#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<KoView*> views;
QList<KoMainWindow*> mainWindows;
KoDocument *document;
QList<KoDocument*> 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();
}
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<KoApplication*>(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<KoApplication*>(qApp);
if (0 != app) {
emit app->documentClosed('/'+objectName());
}
}
}
QList<KoView*> 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<KoCanvasControllerWidget*>();
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<KoMainWindow*>& 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<KoMainWindow*>(widget);
while (!mainWindow && widget) {
widget = widget->parentWidget();
mainWindow = qobject_cast<KoMainWindow*>(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/main/KoPartAdaptor.cpp b/src/libs/main/KoPartAdaptor.cpp
index 2898513a..3dd61955 100644
--- a/src/libs/main/KoPartAdaptor.cpp
+++ b/src/libs/main/KoPartAdaptor.cpp
@@ -1,265 +1,266 @@
/* This file is part of the KDE project
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2006 Fredrik Edemar <f_edemar@linux.se>
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.
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 "KoPartAdaptor.h"
#include <QList>
#include "KoDocument.h"
#include "KoPart.h"
#include "KoDocumentInfo.h"
#include "KoView.h"
#include <MainDebug.h>
KoPartAdaptor::KoPartAdaptor(KoPart *doc)
: QDBusAbstractAdaptor(doc)
{
setAutoRelaySignals(true);
m_pDoc = doc;
}
KoPartAdaptor::~KoPartAdaptor()
{
}
void KoPartAdaptor::openUrl(const QString & url)
{
m_pDoc->document()->openUrl(QUrl(url));
}
bool KoPartAdaptor::isLoading()
{
return m_pDoc->document()->isLoading();
}
QString KoPartAdaptor::url()
{
return m_pDoc->document()->url().url();
}
bool KoPartAdaptor::isModified()
{
return m_pDoc->document()->isModified();
}
int KoPartAdaptor::viewCount()
{
return m_pDoc->viewCount();
}
QString KoPartAdaptor::view(int idx)
{
QList<KoView*> views = m_pDoc->views();
KoView *v = views.at(idx);
if (!v)
return QString();
return v->objectName();
}
void KoPartAdaptor::save()
{
m_pDoc->document()->save();
}
void KoPartAdaptor::saveAs(const QString & url)
{
m_pDoc->document()->saveAs(QUrl(url));
m_pDoc->document()->waitSaveComplete(); // see ReadWritePart
}
void KoPartAdaptor::setOutputMimeType(const QByteArray& mimetype)
{
m_pDoc->document()->setOutputMimeType(mimetype);
}
QString KoPartAdaptor::documentInfoAuthorName() const
{
return m_pDoc->document()->documentInfo()->authorInfo("creator");
}
QString KoPartAdaptor::documentInfoEmail() const
{
return m_pDoc->document()->documentInfo()->authorInfo("email");
}
QString KoPartAdaptor::documentInfoCompanyName() const
{
return m_pDoc->document()->documentInfo()->authorInfo("company");
}
QString KoPartAdaptor::documentInfoTelephone() const
{
debugMain << " Keep compatibility with calligra <= 1.3 : use documentInfoTelephoneWork";
return documentInfoTelephoneWork();
}
QString KoPartAdaptor::documentInfoTelephoneWork() const
{
return m_pDoc->document()->documentInfo()->authorInfo("telephone-work");
}
QString KoPartAdaptor::documentInfoTelephoneHome() const
{
return m_pDoc->document()->documentInfo()->authorInfo("telephone-home");
}
QString KoPartAdaptor::documentInfoFax() const
{
return m_pDoc->document()->documentInfo()->authorInfo("fax");
}
QString KoPartAdaptor::documentInfoCountry() const
{
return m_pDoc->document()->documentInfo()->authorInfo("country");
}
QString KoPartAdaptor::documentInfoPostalCode() const
{
return m_pDoc->document()->documentInfo()->authorInfo("postal-code");
}
QString KoPartAdaptor::documentInfoCity() const
{
return m_pDoc->document()->documentInfo()->authorInfo("city");
}
QString KoPartAdaptor::documentInfoInitial() const
{
return m_pDoc->document()->documentInfo()->authorInfo("initial");
}
QString KoPartAdaptor::documentInfoAuthorPosition() const
{
return m_pDoc->document()->documentInfo()->authorInfo("position");
}
QString KoPartAdaptor::documentInfoStreet() const
{
return m_pDoc->document()->documentInfo()->authorInfo("street");
}
QString KoPartAdaptor::documentInfoTitle() const
{
return m_pDoc->document()->documentInfo()->aboutInfo("title");
}
QString KoPartAdaptor::documentInfoAbstract() const
{
return m_pDoc->document()->documentInfo()->aboutInfo("comments");
}
QString KoPartAdaptor::documentInfoKeywords() const
{
return m_pDoc->document()->documentInfo()->aboutInfo("keywords");
}
QString KoPartAdaptor::documentInfoSubject() const
{
return m_pDoc->document()->documentInfo()->aboutInfo("subject");
}
void KoPartAdaptor::setDocumentInfoKeywords(const QString & text)
{
m_pDoc->document()->documentInfo()->setAboutInfo("keywords", text);
}
void KoPartAdaptor::setDocumentInfoSubject(const QString & text)
{
m_pDoc->document()->documentInfo()->setAboutInfo("subject", text);
}
void KoPartAdaptor::setDocumentInfoAuthorName(const QString & text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("creator", text);
}
void KoPartAdaptor::setDocumentInfoEmail(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("email", text);
}
void KoPartAdaptor::setDocumentInfoCompanyName(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("company", text);
}
void KoPartAdaptor::setDocumentInfoAuthorPosition(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("position", text);
}
void KoPartAdaptor::setDocumentInfoTelephone(const QString &text)
{
debugMain << "Keep compatibility with calligra <= 1.3 : use setDocumentInfoTelephoneWork";
setDocumentInfoTelephoneWork(text);
}
void KoPartAdaptor::setDocumentInfoTelephoneWork(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("telephone-work", text);
}
void KoPartAdaptor::setDocumentInfoTelephoneHome(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("telephone", text);
}
void KoPartAdaptor::setDocumentInfoFax(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("fax", text);
}
void KoPartAdaptor::setDocumentInfoCountry(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("country", text);
}
void KoPartAdaptor::setDocumentInfoTitle(const QString & text)
{
m_pDoc->document()->documentInfo()->setAboutInfo("title", text);
}
void KoPartAdaptor::setDocumentInfoPostalCode(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("postal-code", text);
}
void KoPartAdaptor::setDocumentInfoCity(const QString & text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("city", text);
}
void KoPartAdaptor::setDocumentInfoInitial(const QString & text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("initial", text);
}
void KoPartAdaptor::setDocumentInfoStreet(const QString &text)
{
m_pDoc->document()->documentInfo()->setAuthorInfo("street", text);
}
void KoPartAdaptor::setDocumentInfoAbstract(const QString &text)
{
m_pDoc->document()->documentInfo()->setAboutInfo("comments", text);
}
diff --git a/src/libs/main/KoPrintJob.cpp b/src/libs/main/KoPrintJob.cpp
index 0f0da763..cec4f0ad 100644
--- a/src/libs/main/KoPrintJob.cpp
+++ b/src/libs/main/KoPrintJob.cpp
@@ -1,61 +1,62 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* 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 "KoPrintJob.h"
#include <QWidget>
#include <QPainter>
KoPrintJob::KoPrintJob(QObject *parent)
: QObject(parent)
{
}
KoPrintJob::~KoPrintJob()
{
}
void KoPrintJob::startPrinting(RemovePolicy removePolicy)
{
if (removePolicy == DeleteWhenDone)
deleteLater();
}
QAbstractPrintDialog::PrintDialogOptions KoPrintJob::printDialogOptions() const
{
return QAbstractPrintDialog::PrintToFile |
QAbstractPrintDialog::PrintPageRange |
QAbstractPrintDialog::PrintCollateCopies |
QAbstractPrintDialog::DontUseSheet |
QAbstractPrintDialog::PrintShowPageSize;
}
bool KoPrintJob::canPrint()
{
if (! printer().isValid()) {
return false;
}
QPainter testPainter(&printer());
if (testPainter.isActive()) {
return true;
}
return false;
}
diff --git a/src/libs/main/KoPrintingDialog.cpp b/src/libs/main/KoPrintingDialog.cpp
index 5f19bd25..20b42e86 100644
--- a/src/libs/main/KoPrintingDialog.cpp
+++ b/src/libs/main/KoPrintingDialog.cpp
@@ -1,210 +1,211 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2011 Boudewijn Rempt <boud@kde.org>
*
* 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 "KoPrintingDialog.h"
#include "KoPrintingDialog_p.h"
#include "KoProgressUpdater.h"
//#include <KoZoomHandler.h>
//#include <KoShapeManager.h>
//#include <KoShape.h>
#include <KoProgressBar.h>
#include <QCoreApplication>
#include <MainDebug.h>
#include <klocalizedstring.h>
#include <QPainter>
#include <QPrinter>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QDialog>
#include <QThread>
class PrintDialog : public QDialog {
public:
PrintDialog(KoPrintingDialogPrivate *d, QWidget *parent)
: QDialog(parent)
{
setModal(true);
QGridLayout *grid = new QGridLayout(this);
setLayout(grid);
d->pageNumber = new QLabel(this);
d->pageNumber->setMinimumWidth(200);
grid->addWidget(d->pageNumber, 0, 0, 1, 2);
KoProgressBar *bar = new KoProgressBar(this);
d->progress = new KoProgressUpdater(bar);
grid->addWidget(bar, 1, 0, 1, 2);
d->button = new QPushButton(i18n("Stop"), this);
grid->addWidget(d->button, 2, 1);
grid->setColumnStretch(0, 1);
}
};
KoPrintingDialog::KoPrintingDialog(QWidget *parent)
: KoPrintJob(parent),
d(new KoPrintingDialogPrivate(this))
{
d->dialog = new PrintDialog(d, parent);
connect(d->button, SIGNAL(released()), this, SLOT(stopPressed())); // clazy:exclude=old-style-connect
}
KoPrintingDialog::~KoPrintingDialog()
{
d->stopPressed();
delete d;
}
/*
void KoPrintingDialog::setShapeManager(KoShapeManager *sm)
{
d->shapeManager = sm;
}
KoShapeManager *KoPrintingDialog::shapeManager() const
{
return d->shapeManager;
}
*/
void KoPrintingDialog::setPageRange(const QList<int> &pages)
{
if (d->index == 0) // can't change after we started
d->pageRange = pages;
}
QPainter & KoPrintingDialog::painter() const
{
if (d->painter == 0) {
d->painter = new QPainter(d->printer);
d->painter->save(); // state before page preparation (3)
}
return *d->painter;
}
bool KoPrintingDialog::isStopped() const
{
return d->stop;
}
void KoPrintingDialog::startPrinting(RemovePolicy removePolicy)
{
d->removePolicy = removePolicy;
d->pages = d->pageRange;
if (d->pages.isEmpty()) { // auto-fill from min/max
switch (d->printer->printRange()) {
case QPrinter::AllPages:
for (int i=documentFirstPage(); i <= documentLastPage(); i++)
d->pages.append(i);
break;
case QPrinter::PageRange:
for (int i=d->printer->fromPage(); i <= d->printer->toPage(); i++)
d->pages.append(i);
break;
case QPrinter::CurrentPage:
d->pages.append(documentCurrentPage());
break;
default:
return;
}
}
if (d->pages.isEmpty()) {
qWarning(/*30004*/) << "KoPrintingDialog::startPrinting: No pages to print, did you forget to call setPageRange()?";
return;
}
const bool blocking = property("blocking").toBool();
const bool noprogressdialog = property("noprogressdialog").toBool();
if (d->index == 0 && d->pages.count() > 0 && d->printer) {
if (!blocking && !noprogressdialog)
d->dialog->show();
d->stop = false;
delete d->painter;
d->painter = 0;
// d->zoomer.setZoom( 1.0 );
// d->zoomer.setDpi( d->printer->resolution(), d->printer->resolution() );
d->progress->start(100, i18n("Printing"));
if (d->printer->numCopies() > 1) {
QList<int> oldPages = d->pages;
if (d->printer->collateCopies()) { // means we print whole doc at once
for (int count = 1; count < d->printer->numCopies(); ++count)
d->pages.append(oldPages);
} else {
d->pages.clear();
foreach (int page, oldPages) {
for (int count = 1; count < d->printer->numCopies(); ++count)
d->pages.append(page);
}
}
}
if (d->printer->pageOrder() == QPrinter::LastPageFirst) {
const QList<int> pages = d->pages;
d->pages.clear();
QList<int>::ConstIterator iter = pages.end();
do {
--iter;
d->pages << *iter;
} while (iter != pages.begin());
}
d->resetValues();
foreach (int page, d->pages) {
d->index++;
d->updaters.append(d->progress->startSubtask()); // one per page
d->preparePage(page);
d->printPage(page);
if (!blocking) {
qApp->processEvents();
}
}
d->painter->end();
if (blocking) {
printingDone();
}
else {
d->printingDone();
}
d->stop = true;
d->resetValues();
}
}
QPrinter &KoPrintingDialog::printer()
{
return *d->printer;
}
void KoPrintingDialog::printPage(int, QPainter &)
{
}
QRectF KoPrintingDialog::preparePage(int)
{
return QRectF();
}
// have to include this because of Q_PRIVATE_SLOT
#include <moc_KoPrintingDialog.cpp>
diff --git a/src/libs/main/KoUndoStackAction.cpp b/src/libs/main/KoUndoStackAction.cpp
index bc991c0e..baee273e 100644
--- a/src/libs/main/KoUndoStackAction.cpp
+++ b/src/libs/main/KoUndoStackAction.cpp
@@ -1,56 +1,57 @@
/* This file is part of the KDE project
Copyright (C) 2011 Sven Langkamp <sven.langkamp@gmail.com>
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 "KoUndoStackAction.h"
#include <KoIcon.h>
#include <kundo2stack.h>
#include <klocalizedstring.h>
#include <kstandardshortcut.h>
KoUndoStackAction::KoUndoStackAction(KUndo2Stack* stack, Type type)
: QAction(stack)
, m_type(type)
{
if (m_type == UNDO) {
connect(this, &QAction::triggered, stack, &KUndo2QStack::undo);
connect(stack, &KUndo2QStack::canUndoChanged, this, &QAction::setEnabled);
connect(stack, &KUndo2QStack::undoTextChanged, this, &KoUndoStackAction::slotUndoTextChanged);
setIcon(koIcon("edit-undo"));
setText(i18n("Undo"));
setShortcuts(KStandardShortcut::undo());
setEnabled(stack->canUndo());
} else {
connect(this, &QAction::triggered, stack, &KUndo2QStack::redo);
connect(stack, &KUndo2QStack::canRedoChanged, this, &QAction::setEnabled);
connect(stack, &KUndo2QStack::redoTextChanged, this, &KoUndoStackAction::slotUndoTextChanged);
setIcon(koIcon("edit-redo"));
setText(i18n("Redo"));
setShortcuts(KStandardShortcut::redo());
setEnabled(stack->canRedo());
}
}
void KoUndoStackAction::slotUndoTextChanged(const QString& text)
{
QString actionText = (m_type == UNDO) ? i18n("Undo %1", text) : i18n("Redo %1", text);
setText(actionText);
}
diff --git a/src/libs/main/KoView.cpp b/src/libs/main/KoView.cpp
index f398538d..e5910ff0 100644
--- a/src/libs/main/KoView.cpp
+++ b/src/libs/main/KoView.cpp
@@ -1,443 +1,444 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
Copyright (C) 2010 Benjamin Port <port.benjamin@gmail.com>
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 "KoView.h"
// local directory
#include "KoView_p.h"
#include "KoPart.h"
#include "KoDockRegistry.h"
#include "KoDockFactoryBase.h"
#include "KoDocument.h"
#include "KoMainWindow.h"
#ifndef QT_NO_DBUS
#include "KoViewAdaptor.h"
#include <QDBusConnection>
#endif
#include "KoUndoStackAction.h"
#include "KoGlobal.h"
#include "KoPageLayout.h"
#include "KoPrintJob.h"
#include "KoDocumentInfo.h"
#include <KoIcon.h>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <MainDebug.h>
#include <kmessagebox.h>
#include <KoNetAccess.h>
#include <kselectaction.h>
#include <kconfiggroup.h>
#include <KSharedConfig>
#include <QStatusBar>
#include <QDockWidget>
#include <QApplication>
#include <QList>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QImage>
#include <QUrl>
#include <QPrintDialog>
//static
QString KoView::newObjectName()
{
static int s_viewIFNumber = 0;
QString name; name.setNum(s_viewIFNumber++); name.prepend("view_");
return name;
}
class KoViewPrivate
{
public:
KoViewPrivate() {
tempActiveWidget = 0;
documentDeleted = false;
actionAuthor = 0;
}
~KoViewPrivate() {
}
QPointer<KoDocument> document; // our KoDocument
QPointer<KoPart> part; // our part
QWidget *tempActiveWidget;
bool documentDeleted; // true when document gets deleted [can't use document==0
// since this only happens in ~QObject, and views
// get deleted by ~KoDocument].
// Hmm sorry for polluting the private class with such a big inner class.
// At the beginning it was a little struct :)
class StatusBarItem
{
public:
StatusBarItem() // for QValueList
: m_widget(0),
m_connected(false),
m_hidden(false) {}
StatusBarItem(QWidget * widget, int stretch, bool permanent)
: m_widget(widget),
m_stretch(stretch),
m_permanent(permanent),
m_connected(false),
m_hidden(false) {}
bool operator==(const StatusBarItem& rhs) {
return m_widget == rhs.m_widget;
}
bool operator!=(const StatusBarItem& rhs) {
return m_widget != rhs.m_widget;
}
QWidget * widget() const {
return m_widget;
}
void ensureItemShown(QStatusBar * sb) {
Q_ASSERT(m_widget);
if (!m_connected) {
if (m_permanent)
sb->addPermanentWidget(m_widget, m_stretch);
else
sb->addWidget(m_widget, m_stretch);
if(!m_hidden)
m_widget->show();
m_connected = true;
}
}
void ensureItemHidden(QStatusBar * sb) {
if (m_connected) {
m_hidden = m_widget->isHidden();
sb->removeWidget(m_widget);
m_widget->hide();
m_connected = false;
}
}
private:
QWidget * m_widget;
int m_stretch;
bool m_permanent;
bool m_connected;
bool m_hidden;
};
QList<StatusBarItem> statusBarItems; // Our statusbar items
bool inOperation; //in the middle of an operation (no screen refreshing)?
KSelectAction *actionAuthor; // Select action for author profile.
};
KoView::KoView(KoPart *part, KoDocument *document, QWidget *parent)
: QWidget(parent)
, d(new KoViewPrivate)
{
Q_ASSERT(document);
Q_ASSERT(part);
setObjectName(newObjectName());
#ifndef QT_NO_DBUS
new KoViewAdaptor(this);
QDBusConnection::sessionBus().registerObject('/' + objectName(), this);
#endif
d->document = document;
d->part = part;
setFocusPolicy(Qt::StrongFocus);
setupGlobalActions();
QStatusBar * sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document.data(), &KoDocument::statusBarMessage,
this, &KoView::slotActionStatusText);
connect(d->document.data(), &KoDocument::clearStatusBarMessage,
this, &KoView::slotClearStatusText);
}
#if 0
// add all plugins.
foreach(const QString & docker, KoDockRegistry::instance()->keys()) {
KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker);
qInfo()<<Q_FUNC_INFO<<factory->id();
if (mainWindow())
mainWindow()->createDockWidget(factory);
}
#endif
actionCollection()->addAssociatedWidget(this);
/**
* WARNING: This code changes the context of global shortcuts
* only. All actions added later will have the default
* context, which is Qt::WindowShortcut!
*/
foreach(QAction* action, actionCollection()->actions()) {
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
}
}
KoView::~KoView()
{
if (!d->documentDeleted) {
if (d->document) {
d->part->removeView(this);
}
}
delete d;
}
void KoView::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasImage()
|| event->mimeData()->hasUrls()) {
event->accept();
} else {
event->ignore();
}
}
void KoView::dropEvent(QDropEvent *event)
{
// we can drop a list of urls from, for instance dolphin
QVector<QImage> images;
if (event->mimeData()->hasImage()) {
QImage image = event->mimeData()->imageData().value<QImage>();
if (!image.isNull()) {
// apparently hasImage() && imageData().value<QImage>().isNull()
// can hold sometimes (Qt bug?).
images << image;
}
}
else if (event->mimeData()->hasUrls()) {
QList<QUrl> urls = event->mimeData()->urls();
foreach (const QUrl &url, urls) {
QImage image;
QUrl kurl(url);
// make sure we download the files before inserting them
if (!kurl.isLocalFile()) {
QString tmpFile;
if( KIO::NetAccess::download(kurl, tmpFile, this)) {
image.load(tmpFile);
KIO::NetAccess::removeTempFile(tmpFile);
} else {
KMessageBox::error(this, KIO::NetAccess::lastErrorString());
}
}
else {
image.load(kurl.toLocalFile());
}
if (!image.isNull()) {
images << image;
}
}
}
if (!images.isEmpty()) {
addImages(images, event->pos());
}
}
void KoView::addImages(const QVector<QImage> &, const QPoint &)
{
// override in your application
}
KoDocument *KoView::koDocument() const
{
return d->document;
}
void KoView::setDocumentDeleted()
{
d->documentDeleted = true;
}
void KoView::addStatusBarItem(QWidget * widget, int stretch, bool permanent)
{
KoViewPrivate::StatusBarItem item(widget, stretch, permanent);
QStatusBar * sb = statusBar();
if (sb) {
item.ensureItemShown(sb);
}
d->statusBarItems.append(item);
}
void KoView::removeStatusBarItem(QWidget *widget)
{
QStatusBar *sb = statusBar();
int itemCount = d->statusBarItems.count();
for (int i = itemCount-1; i >= 0; --i) {
KoViewPrivate::StatusBarItem &sbItem = d->statusBarItems[i];
if (sbItem.widget() == widget) {
if (sb) {
sbItem.ensureItemHidden(sb);
}
d->statusBarItems.removeOne(sbItem);
break;
}
}
}
KoPrintJob * KoView::createPrintJob()
{
warnMain << "Printing not implemented in this application";
return 0;
}
KoPrintJob * KoView::createPdfPrintJob()
{
return createPrintJob();
}
KoPageLayout KoView::pageLayout() const
{
return koDocument()->pageLayout();
}
void KoView::setPageLayout(const KoPageLayout &pageLayout)
{
koDocument()->setPageLayout(pageLayout);
}
QPrintDialog *KoView::createPrintDialog(KoPrintJob *printJob, QWidget *parent)
{
QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), parent);
printDialog->setOptionTabs(printJob->createOptionWidgets());
printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage());
printDialog->setEnabledOptions(printJob->printDialogOptions());
return printDialog;
}
void KoView::setupGlobalActions()
{
QAction *undo = actionCollection()->addAction("edit_undo", new KoUndoStackAction(d->document->undoStack(), KoUndoStackAction::UNDO));
QAction *redo = actionCollection()->addAction("edit_redo", new KoUndoStackAction(d->document->undoStack(), KoUndoStackAction::RED0));
actionCollection()->setDefaultShortcut(undo, QKeySequence::Undo);
actionCollection()->setDefaultShortcut(redo, QKeySequence::Redo);
d->actionAuthor = new KSelectAction(koIcon("user-identity"), i18n("Active Author Profile"), this);
connect(d->actionAuthor, static_cast<void (KSelectAction::*)(const QString&)> (&KSelectAction::triggered), this, &KoView::changeAuthorProfile);
actionCollection()->addAction("settings_active_author", d->actionAuthor);
slotUpdateAuthorProfileActions();
}
void KoView::changeAuthorProfile(const QString &profileName)
{
KConfigGroup appAuthorGroup( KSharedConfig::openConfig(), "Author");
if (profileName.isEmpty()) {
appAuthorGroup.writeEntry("active-profile", "");
} else if (profileName == i18nc("choice for author profile", "Anonymous")) {
appAuthorGroup.writeEntry("active-profile", "anonymous");
} else {
appAuthorGroup.writeEntry("active-profile", profileName);
}
appAuthorGroup.sync();
d->document->documentInfo()->updateParameters();
}
KoMainWindow * KoView::mainWindow() const
{
// It is possible (when embedded inside a Gemini window) that you have a KoMainWindow which
// is not the top level window. The code below ensures you can still get access to it, even
// in that case.
KoMainWindow* mw = dynamic_cast<KoMainWindow *>(window());
QWidget* parent = parentWidget();
while (!mw) {
mw = dynamic_cast<KoMainWindow*>(parent);
parent = parent->parentWidget();
if (!parent) {
break;
}
}
return mw;
}
QStatusBar * KoView::statusBar() const
{
KoMainWindow *mw = mainWindow();
return mw ? mw->statusBar() : 0;
}
void KoView::slotActionStatusText(const QString &text)
{
QStatusBar *sb = statusBar();
if (sb)
sb->showMessage(text);
}
void KoView::slotClearStatusText()
{
QStatusBar *sb = statusBar();
if (sb)
sb->clearMessage();
}
void KoView::slotUpdateAuthorProfileActions()
{
Q_ASSERT(d->actionAuthor);
if (!d->actionAuthor) {
return;
}
d->actionAuthor->clear();
d->actionAuthor->addAction(i18n("Default Author Profile"));
d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous"));
KConfigGroup authorGroup(KoGlobal::planConfig(), "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
foreach (const QString &profile , profiles) {
d->actionAuthor->addAction(profile);
}
KConfigGroup appAuthorGroup( KSharedConfig::openConfig(), "Author");
QString profileName = appAuthorGroup.readEntry("active-profile", "");
if (profileName == "anonymous") {
d->actionAuthor->setCurrentItem(1);
} else if (profiles.contains(profileName)) {
d->actionAuthor->setCurrentAction(profileName);
} else {
d->actionAuthor->setCurrentItem(0);
}
}
QList<QAction*> KoView::createChangeUnitActions(bool addPixelUnit)
{
UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this);
return unitActions->actions();
}
void KoView::guiActivateEvent(bool activated)
{
Q_UNUSED(activated);
}
diff --git a/src/libs/main/KoViewAdaptor.cpp b/src/libs/main/KoViewAdaptor.cpp
index 503f2e0a..65a2cae3 100644
--- a/src/libs/main/KoViewAdaptor.cpp
+++ b/src/libs/main/KoViewAdaptor.cpp
@@ -1,52 +1,53 @@
/* This file is part of the KDE project
Copyright (c) 2000 Simon Hausmann <hausmann@kde.org>
Copyright (C) 2006 Fredrik Edemar <f_edemar@linux.se>
$Id: KoViewAdaptor.cc 529520 2006-04-13 16:41:44Z mpfeiffer $
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 "KoViewAdaptor.h"
#include "KoView.h"
#include <kactioncollection.h>
#include <QAction>
#include <QList>
KoViewAdaptor::KoViewAdaptor(KoView *view)
: QDBusAbstractAdaptor(view)
{
setAutoRelaySignals(true);
m_pView = view;
}
KoViewAdaptor::~KoViewAdaptor()
{
}
QStringList KoViewAdaptor::actions()
{
QStringList tmp_actions;
QList<QAction*> lst = m_pView->actionCollection()->actions();
foreach(QAction* it, lst) {
if (it->isEnabled())
tmp_actions.append(it->objectName());
}
return tmp_actions;
}
diff --git a/src/libs/main/MainDebug.cpp b/src/libs/main/MainDebug.cpp
index 74296873..853ab3c7 100644
--- a/src/libs/main/MainDebug.cpp
+++ b/src/libs/main/MainDebug.cpp
@@ -1,34 +1,35 @@
/*
* Copyright (c) 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "MainDebug.h"
const QLoggingCategory &MAIN_LOG() \
{
static const QLoggingCategory category("calligra.plan.lib.main");
return category;
}
const QLoggingCategory &FILTER_LOG() \
{
static const QLoggingCategory category("calligra.plan.lib.filter");
return category;
}
diff --git a/src/libs/main/tests/filter_graph.cpp b/src/libs/main/tests/filter_graph.cpp
index 06a98a41..961f3753 100644
--- a/src/libs/main/tests/filter_graph.cpp
+++ b/src/libs/main/tests/filter_graph.cpp
@@ -1,119 +1,120 @@
/* This file is part of the KDE project
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
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 <QFile>
#include <QByteArray>
#include <QList>
#include <QPluginLoader>
#include "KoDocumentEntry.h"
#include "KoFilterEntry.h"
#include "KoFilterManager.h"
#include <MainDebug.h>
int main(int /*argc*/, char ** /*argv*/)
{
QByteArray output = "digraph filters {\n";
// The following code is shamelessly copied over from Calligra::Graph::buildGraph
// It wasn't feasible to do some serious changes in the lib for that tiny bit
// of duplicated code in a test file.
QList<QString> vertices; // to keep track of already inserted values, not performance critical
// Make sure that all available parts are added to the graph
const QList<KoDocumentEntry> parts(KoDocumentEntry::query());
QList<KoDocumentEntry>::ConstIterator partIt(parts.begin());
QList<KoDocumentEntry>::ConstIterator partEnd(parts.end());
while (partIt != partEnd) {
//kDebug() << ( *partIt ).service()->desktopEntryName();
QJsonObject metaData = (*partIt).metaData();
QStringList nativeMimeTypes = metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
nativeMimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
QStringList::ConstIterator it = nativeMimeTypes.constBegin();
QStringList::ConstIterator end = nativeMimeTypes.constEnd();
for (; it != end; ++it) {
QString key = *it;
//kDebug() <<"" << key;
if (!key.isEmpty()) {
output += " \"";
output += key.toLatin1();
output += "\" [shape=box, style=filled, fillcolor=lightblue];\n";
if (! vertices.contains(key))
vertices.append(key);
}
}
++partIt;
}
const QList<KoFilterEntry::Ptr> filters(KoFilterEntry::query()); // no constraint here - we want *all* :)
QList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin();
QList<KoFilterEntry::Ptr>::ConstIterator end = filters.end();
for (; it != end; ++it) {
qDebug() << "import" << (*it)->import << " export" << (*it)->export_;
// First add the "starting points"
QStringList::ConstIterator importIt = (*it)->import.constBegin();
QStringList::ConstIterator importEnd = (*it)->import.constEnd();
for (; importIt != importEnd; ++importIt) {
// already there?
if (! vertices.contains(*importIt)) {
vertices.append(*importIt);
output += " \"";
output += (*importIt).toLatin1();
output += "\";\n";
}
}
QStringList::ConstIterator exportIt = (*it)->export_.constBegin();
QStringList::ConstIterator exportEnd = (*it)->export_.constEnd();
for (; exportIt != exportEnd; ++exportIt) {
// First make sure the export vertex is in place
if (! vertices.contains(*exportIt)) {
output += " \"";
output += (*exportIt).toLatin1();
output += "\";\n";
vertices.append(*exportIt);
}
// Then create the appropriate edges
importIt = (*it)->import.constBegin();
for (; importIt != importEnd; ++importIt) {
output += " \"";
output += (*importIt).toLatin1();
output += "\" -> \"";
output += (*exportIt).toLatin1();
if (KoFilterManager::filterAvailable(*it))
output += "\";\n";
else
output += "\" [style=dotted];\n";
}
}
}
output += "}\n";
QFile f("graph.dot");
if (f.open(QIODevice::WriteOnly))
f.write(output);
f.close();
return 0;
}
diff --git a/src/libs/main/tests/filterchain_test.cpp b/src/libs/main/tests/filterchain_test.cpp
index d6038288..7ff6936c 100644
--- a/src/libs/main/tests/filterchain_test.cpp
+++ b/src/libs/main/tests/filterchain_test.cpp
@@ -1,91 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
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 "KoFilterChain.h"
#include "KoFilterManager.h"
#include <MainDebug.h>
int main(int /*argc*/, char ** /*argv*/)
{
CalligraFilter::Graph g("application/x-kspread");
g.dump();
g.setSourceMimeType("application/vnd.oasis.opendocument.text");
g.dump();
KoFilterManager *manager = new KoFilterManager(0);
qDebug() << "Trying to build some filter chains...";
QByteArray mimeType("foo/bar");
KoFilterChain::Ptr chain = g.chain(manager, mimeType);
if (!chain)
qDebug() << "Chain for 'foo/bar' is not available, OK";
else {
qCritical() << "Chain for 'foo/bar' is available!" << endl;
chain->dump();
}
mimeType = "text/csv";
chain = g.chain(manager, mimeType);
if (!chain)
qCritical() << "Chain for 'text/csv' is not available!" << endl;
else {
qDebug() << "Chain for 'text/csv' is available, OK";
chain->dump();
}
// Try to find the closest Calligra part
mimeType = "";
chain = g.chain(manager, mimeType);
if (!chain)
qDebug() << "It was already a Calligra part, OK";
else
qCritical() << "We really got a chain? ugh :}" << endl;
g.setSourceMimeType("text/csv");
mimeType = "";
chain = g.chain(manager, mimeType);
if (!chain)
qCritical() << "Hmm... why didn't we find a chain?" << endl;
else {
qDebug() << "Chain for 'text/csv' -> closest part is available ("
<< mimeType << "), OK" << endl;
chain->dump();
}
qDebug() << "Checking mimeFilter() for Import:";
QStringList list = KoFilterManager::mimeFilter("application/vnd.oasis.opendocument.text", KoFilterManager::Import);
Q_FOREACH(const QString& it, list)
qDebug() << "" << it;
qDebug() << "" << list.count() << " entries.";
qDebug() << "Checking mimeFilter() for Export:";
list = KoFilterManager::mimeFilter("application/vnd.oasis.opendocument.text", KoFilterManager::Export);
Q_FOREACH(const QString& it, list)
qDebug() << "" << it;
qDebug() << "" << list.count() << " entries.";
qDebug() << "Checking KoShell's mimeFilter():";
list = KoFilterManager::mimeFilter();
Q_FOREACH(const QString& it, list)
qDebug() << "" << it;
qDebug() << "" << list.count() << " entries.";
delete manager;
return 0;
}
diff --git a/src/libs/main/tests/priorityqueue_test.cpp b/src/libs/main/tests/priorityqueue_test.cpp
index fc77da0f..9447fb67 100644
--- a/src/libs/main/tests/priorityqueue_test.cpp
+++ b/src/libs/main/tests/priorityqueue_test.cpp
@@ -1,103 +1,104 @@
/* This file is part of the KDE project
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
Copyright (C) 2009 Thomas Zander <zander@kde.org>
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 "priorityqueue_test.h"
#include <PriorityQueue_p.h>
#include <QList>
#include <ctime>
#include <QTest>
struct Node
{
Node(unsigned int key) : m_key(key), m_index(0) {}
unsigned int key() const {
return m_key;
}
void setKey(unsigned int key) {
m_key = key;
}
int index() const {
return m_index;
}
void setIndex(int i) {
m_index = i;
}
private:
unsigned int m_key;
int m_index;
};
static const char* const keys[] =
{
"one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten",
"eleven", "twelve", 0
};
void PriorityQueue_test::testQueue()
{
QList<Node*> list;
QHash<QByteArray, Node*> dict;
CalligraFilter::PriorityQueue<Node> queue;
srand(time(0));
for (int i = 0; i < 12; ++i) {
Node *n = new Node(rand() % 20);
list.append(n);
queue.insert(n);
Node *n2 = new Node(*n);
dict.insert(keys[i], n2);
}
qDebug() << "##### Queue 1:";
queue.dump();
QCOMPARE((int) queue.count(), list.count());
QCOMPARE(queue.isEmpty(), false);
QCOMPARE(queue.extractMinimum()->index(), 0);
qDebug() << "##### Queue 2:";
CalligraFilter::PriorityQueue<Node> queue2(dict);
//queue2.dump();
Node *n = list.at(6);
qDebug() << "##### Decreasing node:" << n->key() << " at" << n->index();
n->setKey(2);
queue.keyDecreased(n);
queue.dump();
n = list.at(2);
qDebug() << "##### Decreasing node:" << n->key() << " at" << n->index();
n->setKey(0);
queue.keyDecreased(n);
queue.dump();
n = queue.extractMinimum();
while (n) {
queue.dump();
n = queue.extractMinimum();
}
}
QTEST_MAIN(PriorityQueue_test)
diff --git a/src/libs/models/kcalendar/kdatepicker.cpp b/src/libs/models/kcalendar/kdatepicker.cpp
index d320c1f1..c53832a6 100644
--- a/src/libs/models/kcalendar/kdatepicker.cpp
+++ b/src/libs/models/kcalendar/kdatepicker.cpp
@@ -1,673 +1,674 @@
/* -*- C++ -*-
This file is part of the KDE libraries
Copyright (C) 1997 Tim D. Gilman (tdgilman@best.org)
(C) 1998-2001 Mirko Boehm (mirko@kde.org)
(C) 2007 John Layt <john@layt.net>
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 "kdatepicker.h"
#include "kdatepicker_p.h"
#include "kdatetable.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <KLocalizedString>
#include <KPopupFrame>
#include <KNotification>
#include <QApplication>
#include <QComboBox>
#include <QFont>
#include <QFontDatabase>
#include <QLayout>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QStyle>
#include <QToolButton>
#include <QDoubleValidator>
#include <qdrawutil.h>
namespace KPlato
{
static QDate parseDateString(const QString &text)
{
QLocale::FormatType formats[] = { QLocale::LongFormat, QLocale::ShortFormat, QLocale::NarrowFormat };
QLocale locale;
QDate date;
for (int i = 0; i < 3; i++) {
date = locale.toDate(text, formats[i]);
if (date.isValid()) {
break;
}
}
return date;
}
class DatePickerValidator : public QValidator
{
public:
DatePickerValidator(KDatePicker *parent)
: QValidator(parent), picker(parent) {}
State validate(QString &text, int &) const Q_DECL_OVERRIDE
{
return parseDateString(text).isValid() ? QValidator::Acceptable : QValidator::Intermediate;
}
private:
KDatePicker *picker;
};
// Week numbers are defined by ISO 8601
// See http://www.merlyn.demon.co.uk/weekinfo.htm for details
KDatePickerPrivateYearSelector::KDatePickerPrivateYearSelector(
const QDate &currentDate, QWidget *parent)
: QLineEdit(parent), val(new QIntValidator(this)), result(0)
{
oldDate = currentDate;
setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
setFrame(false);
// TODO: Find a way to get that from QLocale
//val->setRange( calendar->year( calendar->earliestValidDate() ),
// calendar->year( calendar->latestValidDate() ) );
setValidator(val);
connect(this, &QLineEdit::returnPressed, this, &KDatePickerPrivateYearSelector::yearEnteredSlot);
}
void KDatePickerPrivateYearSelector::yearEnteredSlot()
{
bool ok;
int newYear;
// check if entered value is a number
newYear = text().toInt(&ok);
if (!ok) {
KNotification::beep();
return;
}
// check if new year will lead to a valid date
if (QDate(newYear, oldDate.month(), oldDate.day()).isValid()) {
result = newYear;
emit closeMe(1);
} else {
KNotification::beep();
}
}
int KDatePickerPrivateYearSelector::year()
{
return result;
}
void KDatePickerPrivateYearSelector::setYear(int year)
{
setText(QString::number(year));
}
class KDatePicker::KDatePickerPrivate
{
public:
KDatePickerPrivate(KDatePicker *q) :
q(q), closeButton(0L), selectWeek(0L), todayButton(0), navigationLayout(0)
{
}
void fillWeeksCombo();
QDate validDateInYearMonth(int year, int month);
/// the date table
KDatePicker *q;
QToolButton *closeButton;
QComboBox *selectWeek;
QToolButton *todayButton;
QBoxLayout *navigationLayout;
/// the year forward button
QToolButton *yearForward;
/// the year backward button
QToolButton *yearBackward;
/// the month forward button
QToolButton *monthForward;
/// the month backward button
QToolButton *monthBackward;
/// the button for selecting the month directly
QToolButton *selectMonth;
/// the button for selecting the year directly
QToolButton *selectYear;
/// the line edit to enter the date directly
QLineEdit *line;
/// the validator for the line edit:
DatePickerValidator *val;
/// the date table
KDateTable *table;
/// the widest month string in pixels:
QSize maxMonthRect;
/// the font size for the widget
int fontsize;
};
void KDatePicker::KDatePickerPrivate::fillWeeksCombo()
{
// every year can have a different number of weeks
// it could be that we had 53,1..52 and now 1..53 which is the same number but different
// so always fill with new values
// We show all week numbers for all weeks between first day of year to last day of year
// This of course can be a list like 53,1,2..52
const QDate thisDate = q->date();
const int thisYear = thisDate.year();
QDate day(thisDate.year(), 1, 1);
const QDate lastDayOfYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1);
selectWeek->clear();
// Starting from the first day in the year, loop through the year a week at a time
// adding an entry to the week combo for each week in the year
for (; day.isValid() && day <= lastDayOfYear; day = day.addDays(7)) {
// Get the ISO week number for the current day and what year that week is in
// e.g. 1st day of this year may fall in week 53 of previous year
int weekYear = thisYear;
const int week = day.weekNumber(&weekYear);
QString weekString = i18n("Week %1", week);
// show that this is a week from a different year
if (weekYear != thisYear) {
weekString += QLatin1Char('*');
}
// when the week is selected, go to the same weekday as the one
// that is currently selected in the date table
QDate targetDate = day.addDays(thisDate.dayOfWeek() - day.dayOfWeek());
selectWeek->addItem(weekString, targetDate);
// make sure that the week of the lastDayOfYear is always inserted: in Chinese calendar
// system, this is not always the case
if (day < lastDayOfYear &&
day.daysTo(lastDayOfYear) < 7 &&
lastDayOfYear.weekNumber() != day.weekNumber()) {
day = lastDayOfYear.addDays(-7);
}
}
}
QDate KDatePicker::KDatePickerPrivate::validDateInYearMonth(int year, int month)
{
QDate newDate;
// Try to create a valid date in this year and month
// First try the first of the month, then try last of month
if (QDate(year, month, 1).isValid()) {
newDate = QDate(year, month, 1);
} else if (QDate(year, month + 1, 1).isValid()) {
newDate = QDate(year, month + 1, 1).addDays(-1);
} else {
newDate = QDate::fromJulianDay(0);
}
return newDate;
}
KDatePicker::KDatePicker(QWidget *parent) : QFrame(parent), d(new KDatePickerPrivate(this))
{
initWidget(QDate::currentDate());
}
KDatePicker::KDatePicker(const QDate &date_, QWidget *parent)
: QFrame(parent), d(new KDatePickerPrivate(this))
{
initWidget(date_);
}
void KDatePicker::initWidget(const QDate &date_)
{
const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
QBoxLayout *topLayout = new QVBoxLayout(this);
topLayout->setSpacing(0);
topLayout->setMargin(0);
d->navigationLayout = new QHBoxLayout();
d->navigationLayout->setSpacing(0);
d->navigationLayout->setMargin(0);
topLayout->addLayout(d->navigationLayout);
d->navigationLayout->addStretch();
d->yearBackward = new QToolButton(this);
d->yearBackward->setAutoRaise(true);
d->navigationLayout->addWidget(d->yearBackward);
d->monthBackward = new QToolButton(this);
d->monthBackward ->setAutoRaise(true);
d->navigationLayout->addWidget(d->monthBackward);
d->navigationLayout->addSpacing(spacingHint);
d->selectMonth = new QToolButton(this);
d->selectMonth ->setAutoRaise(true);
d->navigationLayout->addWidget(d->selectMonth);
d->selectYear = new QToolButton(this);
d->selectYear->setCheckable(true);
d->selectYear->setAutoRaise(true);
d->navigationLayout->addWidget(d->selectYear);
d->navigationLayout->addSpacing(spacingHint);
d->monthForward = new QToolButton(this);
d->monthForward ->setAutoRaise(true);
d->navigationLayout->addWidget(d->monthForward);
d->yearForward = new QToolButton(this);
d->yearForward ->setAutoRaise(true);
d->navigationLayout->addWidget(d->yearForward);
d->navigationLayout->addStretch();
d->line = new QLineEdit(this);
d->val = new DatePickerValidator(this);
d->table = new KDateTable(this);
setFocusProxy(d->table);
d->fontsize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (d->fontsize == -1) {
d->fontsize = QFontInfo(QFontDatabase::systemFont(QFontDatabase::GeneralFont)).pointSize();
}
d->fontsize++; // Make a little bigger
d->selectWeek = new QComboBox(this); // read only week selection
d->selectWeek->setFocusPolicy(Qt::NoFocus);
d->todayButton = new QToolButton(this);
d->todayButton->setIcon(koIcon("go-jump-today"));
d->yearForward->setToolTip(i18n("Next year"));
d->yearBackward->setToolTip(i18n("Previous year"));
d->monthForward->setToolTip(i18n("Next month"));
d->monthBackward->setToolTip(i18n("Previous month"));
d->selectWeek->setToolTip(i18n("Select a week"));
d->selectMonth->setToolTip(i18n("Select a month"));
d->selectYear->setToolTip(i18n("Select a year"));
d->todayButton->setToolTip(i18n("Select the current day"));
// -----
setFontSize(d->fontsize);
d->line->setValidator(d->val);
d->line->installEventFilter(this);
if (QApplication::isRightToLeft()) {
d->yearForward->setIcon(koIcon("arrow-left-double"));
d->yearBackward->setIcon(koIcon("arrow-right-double"));
d->monthForward->setIcon(koIcon("arrow-left"));
d->monthBackward->setIcon(koIcon("arrow-right"));
} else {
d->yearForward->setIcon(koIcon("arrow-right-double"));
d->yearBackward->setIcon(koIcon("arrow-left-double"));
d->monthForward->setIcon(koIcon("arrow-right"));
d->monthBackward->setIcon(koIcon("arrow-left"));
}
connect(d->table, SIGNAL(dateChanged(QDate)), SLOT(dateChangedSlot(QDate)));
connect(d->table, &KDateTable::tableClicked, this, &KDatePicker::tableClickedSlot);
connect(d->monthForward, &QAbstractButton::clicked, this, &KDatePicker::monthForwardClicked);
connect(d->monthBackward, &QAbstractButton::clicked, this, &KDatePicker::monthBackwardClicked);
connect(d->yearForward, &QAbstractButton::clicked, this, &KDatePicker::yearForwardClicked);
connect(d->yearBackward, &QAbstractButton::clicked, this, &KDatePicker::yearBackwardClicked);
connect(d->selectWeek, SIGNAL(activated(int)), SLOT(weekSelected(int)));
connect(d->todayButton, &QAbstractButton::clicked, this, &KDatePicker::todayButtonClicked);
connect(d->selectMonth, &QAbstractButton::clicked, this, &KDatePicker::selectMonthClicked);
connect(d->selectYear, &QAbstractButton::toggled, this, &KDatePicker::selectYearClicked);
connect(d->line, &QLineEdit::returnPressed, this, &KDatePicker::lineEnterPressed);
topLayout->addWidget(d->table);
QBoxLayout *bottomLayout = new QHBoxLayout();
bottomLayout->setMargin(0);
bottomLayout->setSpacing(0);
topLayout->addLayout(bottomLayout);
bottomLayout->addWidget(d->todayButton);
bottomLayout->addWidget(d->line);
bottomLayout->addWidget(d->selectWeek);
d->table->setDate(date_);
dateChangedSlot(date_); // needed because table emits changed only when newDate != oldDate
}
KDatePicker::~KDatePicker()
{
delete d;
}
bool KDatePicker::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::KeyPress) {
QKeyEvent *k = (QKeyEvent *)e;
if ((k->key() == Qt::Key_PageUp) ||
(k->key() == Qt::Key_PageDown) ||
(k->key() == Qt::Key_Up) ||
(k->key() == Qt::Key_Down)) {
QApplication::sendEvent(d->table, e);
d->table->setFocus();
return true; // eat event
}
}
return QFrame::eventFilter(o, e);
}
void KDatePicker::resizeEvent(QResizeEvent *e)
{
QWidget::resizeEvent(e);
}
void KDatePicker::dateChangedSlot(const QDate &date_)
{
QLocale locale;
d->line->setText(locale.toString(date_, QLocale::ShortFormat));
d->selectMonth->setText(locale.standaloneMonthName(date_.month(), QLocale::LongFormat));
d->fillWeeksCombo();
// calculate the item num in the week combo box; normalize selected day so as if 1.1. is the first day of the week
QDate firstDay(date_.year(), 1, 1);
// If we cannot successfully create the 1st of the year, this can only mean that
// the 1st is before the earliest valid date in the current calendar system, so use
// the earliestValidDate as the first day.
// In particular covers the case of Gregorian where 1/1/-4713 is not a valid QDate
d->selectWeek->setCurrentIndex((date_.dayOfYear() + firstDay.dayOfWeek() - 2) / 7);
d->selectYear->setText(QString::number(date_.year()).rightJustified(4, QLatin1Char('0')));
emit dateChanged(date_);
}
void KDatePicker::tableClickedSlot()
{
emit dateSelected(date());
emit tableClicked();
}
const QDate &KDatePicker::date() const
{
return d->table->date();
}
bool KDatePicker::setDate(const QDate &date_)
{
// the table setDate does validity checking for us
// this also emits dateChanged() which then calls our dateChangedSlot()
return d->table->setDate(date_);
}
void KDatePicker::monthForwardClicked()
{
if (! setDate(date().addMonths(1))) {
KNotification::beep();
}
d->table->setFocus();
}
void KDatePicker::monthBackwardClicked()
{
if (! setDate(date().addMonths(-1))) {
KNotification::beep();
}
d->table->setFocus();
}
void KDatePicker::yearForwardClicked()
{
if (! setDate(d->table->date().addYears(1))) {
KNotification::beep();
}
d->table->setFocus();
}
void KDatePicker::yearBackwardClicked()
{
if (! setDate(d->table->date().addYears(-1))) {
KNotification::beep();
}
d->table->setFocus();
}
void KDatePicker::weekSelected(int index)
{
QDate targetDay = d->selectWeek->itemData(index).toDateTime().date();
if (! setDate(targetDay)) {
KNotification::beep();
}
d->table->setFocus();
}
void KDatePicker::selectMonthClicked()
{
QDate thisDate(date());
d->table->setFocus();
QMenu popup(d->selectMonth);
// Populate the pick list with all the month names, this may change by year
// JPL do we need to do something here for months that fall outside valid range?
const int monthsInYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1).month();
QLocale locale;
for (int m = 1; m <= monthsInYear; m++) {
popup.addAction(locale.standaloneMonthName(m))->setData(m);
}
QAction *item = popup.actions().value(thisDate.month() - 1);
// if this happens the above should already given an assertion
if (item) {
popup.setActiveAction(item);
}
// cancelled
if ((item = popup.exec(d->selectMonth->mapToGlobal(QPoint(0, 0)), item)) == 0) {
return;
}
// We need to create a valid date in the month selected so we can find out how many days are
// in the month.
QDate newDate(thisDate.year(), item->data().toInt(), 1);
// If we have succeeded in creating a date in the new month, then try to create the new date,
// checking we don't set a day after the last day of the month
newDate.setDate(newDate.year(), newDate.month(), qMin(thisDate.day(), newDate.daysInMonth()));
// Set the date, if it's invalid in any way then alert user and don't update
if (! setDate(newDate)) {
KNotification::beep();
}
}
void KDatePicker::selectYearClicked()
{
if (!d->selectYear->isChecked()) {
return;
}
QDate thisDate(date());
KPopupFrame *popup = new KPopupFrame(this);
KDatePickerPrivateYearSelector *picker = new KDatePickerPrivateYearSelector(date(), popup);
picker->resize(picker->sizeHint());
picker->setYear(thisDate.year());
picker->selectAll();
popup->setMainWidget(picker);
connect(picker, SIGNAL(closeMe(int)), popup, SLOT(close(int)));
picker->setFocus();
if (popup->exec(d->selectYear->mapToGlobal(QPoint(0, d->selectMonth->height())))) {
// We need to create a valid date in the year/month selected so we can find out how many
// days are in the month.
QDate newDate(picker->year(), thisDate.month(), 1);
// If we have succeeded in creating a date in the new month, then try to create the new
// date, checking we don't set a day after the last day of the month
newDate = QDate(newDate.year(), newDate.month(), qMin(thisDate.day(), newDate.daysInMonth()));
// Set the date, if it's invalid in any way then alert user and don't update
if (! setDate(newDate)) {
KNotification::beep();
}
}
delete popup;
d->selectYear->setChecked(false);
}
void KDatePicker::uncheckYearSelector()
{
d->selectYear->setChecked(false);
d->selectYear->update();
}
void KDatePicker::changeEvent(QEvent *event)
{
if (event && event->type() == QEvent::EnabledChange) {
if (isEnabled()) {
d->table->setFocus();
}
}
}
KDateTable *KDatePicker::dateTable() const
{
return d->table;
}
void KDatePicker::lineEnterPressed()
{
QDate newDate = parseDateString(d->line->text());
if (newDate.isValid()) {
emit dateEntered(newDate);
setDate(newDate);
d->table->setFocus();
} else {
KNotification::beep();
}
}
void KDatePicker::todayButtonClicked()
{
setDate(QDate::currentDate());
d->table->setFocus();
}
QSize KDatePicker::sizeHint() const
{
return QWidget::sizeHint();
}
void KDatePicker::setFontSize(int s)
{
QWidget *const buttons[] = {
d->selectMonth,
d->selectYear,
};
const int NoOfButtons = sizeof(buttons) / sizeof(buttons[0]);
int count;
QFont font;
QRect r;
// -----
d->fontsize = s;
for (count = 0; count < NoOfButtons; ++count) {
font = buttons[count]->font();
font.setPointSize(s);
buttons[count]->setFont(font);
}
d->table->setFontSize(s);
QFontMetrics metrics(d->selectMonth->fontMetrics());
QString longestMonth;
QLocale locale;
for (int i = 1;; ++i) {
QString str = locale.standaloneMonthName(i, QLocale::LongFormat);
if (str.isNull()) {
break;
}
r = metrics.boundingRect(str);
if (r.width() > d->maxMonthRect.width()) {
d->maxMonthRect.setWidth(r.width());
longestMonth = str;
}
if (r.height() > d->maxMonthRect.height()) {
d->maxMonthRect.setHeight(r.height());
}
}
QStyleOptionToolButton opt;
opt.initFrom(d->selectMonth);
opt.text = longestMonth;
// stolen from QToolButton
QSize textSize = metrics.size(Qt::TextShowMnemonic, longestMonth);
textSize.setWidth(textSize.width() + metrics.width(QLatin1Char(' ')) * 2);
int w = textSize.width();
int h = textSize.height();
opt.rect.setHeight(h); // PM_MenuButtonIndicator depends on the height
QSize metricBound = style()->sizeFromContents(
QStyle::CT_ToolButton, &opt, QSize(w, h), d->selectMonth
).expandedTo(QApplication::globalStrut());
d->selectMonth->setMinimumSize(metricBound);
}
int KDatePicker::fontSize() const
{
return d->fontsize;
}
void KDatePicker::setCloseButton(bool enable)
{
if (enable == (d->closeButton != 0L)) {
return;
}
if (enable) {
d->closeButton = new QToolButton(this);
d->closeButton->setAutoRaise(true);
const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
d->navigationLayout->addSpacing(spacingHint);
d->navigationLayout->addWidget(d->closeButton);
d->closeButton->setToolTip(i18n("Close"));
d->closeButton->setIcon(koIcon("window-close"));
connect(d->closeButton, &QAbstractButton::clicked,
topLevelWidget(), &QWidget::close);
} else {
delete d->closeButton;
d->closeButton = 0L;
}
updateGeometry();
}
bool KDatePicker::hasCloseButton() const
{
return (d->closeButton);
}
} //namespace KPlato
diff --git a/src/libs/models/kcalendar/kdatetable.cpp b/src/libs/models/kcalendar/kdatetable.cpp
index dd01bb5d..3620a390 100644
--- a/src/libs/models/kcalendar/kdatetable.cpp
+++ b/src/libs/models/kcalendar/kdatetable.cpp
@@ -1,1242 +1,1243 @@
/* -*- C++ -*-
This file is part of the KDE libraries
Copyright (C) 1997 Tim D. Gilman (tdgilman@best.org)
(C) 1998-2001 Mirko Boehm (mirko@kde.org)
(C) 2007 John Layt <john@layt.net>
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 "kdatetable.h"
#include "kdatepicker.h"
#include "kptdebug.h"
#include <kconfig.h>
#include <knotification.h>
#include <kstandardshortcut.h>
#include <kactioncollection.h>
#include <QLocale>
#include <QFontDatabase>
#include <QDate>
#include <QPen>
#include <QPainter>
#include <QActionEvent>
#include <QMenu>
#include <QHash>
#include <QToolTip>
#include <qdrawutil.h>
#include <cmath>
namespace KPlato
{
class KDateTable::KDateTablePrivate
{
public:
KDateTablePrivate(KDateTable *q): q(q)
{
m_popupMenuEnabled = false;
m_selectionmode = KDateTable::SingleSelection;
m_paintweeknumbers = false;
m_hoveredPos = -1;
m_model = 0;
m_grid = false;
}
~KDateTablePrivate()
{
qDeleteAll(customPaintingModes);
delete m_dateDelegate;
delete m_weekDayDelegate;
delete m_weekNumberDelegate;
}
void nextMonth();
void previousMonth();
void beginningOfMonth();
void endOfMonth();
void beginningOfWeek();
void endOfWeek();
KDateTable *q;
/**
* The currently selected date.
*/
QDate m_date;
/**
* The weekday number of the first day in the month [1..7].
*/
int m_weekDayFirstOfMonth;
/**
* Save the size of the largest used cell content.
*/
QRectF m_maxCell;
/**
* The font size of the displayed text.
*/
int fontsize;
bool m_popupMenuEnabled;
//----->
QHash <qint64, KDateTableDateDelegate*> customPaintingModes;
int m_hoveredPos;
KDateTableDataModel *m_model;
KDateTableDateDelegate *m_dateDelegate;
KDateTableWeekDayDelegate *m_weekDayDelegate;
KDateTableWeekNumberDelegate *m_weekNumberDelegate;
StyleOptionViewItem m_styleOptionDate;
StyleOptionHeader m_styleOptionWeekDay;
StyleOptionHeader m_styleOptionWeekNumber;
QList<QDate> m_selectedDates;
SelectionMode m_selectionmode;
bool m_paintweeknumbers;
bool m_grid;
};
KDateTable::KDateTable(const QDate& date, QWidget* parent)
: QWidget(parent),
d(new KDateTablePrivate(this))
{
if (!date.isValid()) {
debugPlan << "KDateTable ctor: WARNING: Given date is invalid, using current date.";
initWidget(QDate::currentDate()); // this initializes m_weekDayFirstOfMonth, m_numDaysThisMonth, numDaysPrevMonth
} else {
initWidget(date); // this initializes m_weekDayFirstOfMonth, m_numDaysThisMonth, numDaysPrevMonth
}
}
KDateTable::KDateTable(QWidget *parent)
: QWidget(parent),
d(new KDateTablePrivate(this))
{
initWidget(QDate::currentDate());
}
KDateTable::~KDateTable()
{
delete d;
}
void KDateTable::initWidget(const QDate &date)
{
setFontSize(10);
setFocusPolicy(Qt::StrongFocus);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
initAccels();
setAttribute(Qt::WA_Hover, true);
d->m_dateDelegate = new KDateTableDateDelegate( this );
d->m_weekDayDelegate = new KDateTableWeekDayDelegate( this );
d->m_weekNumberDelegate = new KDateTableWeekNumberDelegate( this );
d->m_styleOptionDate.initFrom( this );
d->m_styleOptionDate.displayAlignment = Qt::AlignCenter;
d->m_styleOptionWeekDay.initFrom( this );
d->m_styleOptionWeekDay.textAlignment = Qt::AlignCenter;
d->m_styleOptionWeekNumber.initFrom( this );
d->m_styleOptionWeekNumber.textAlignment = Qt::AlignCenter;
//setModel( new KDateTableDataModel( this ) );
setDate(date);
}
void KDateTable::setStyleOptionDate( const StyleOptionViewItem &so )
{
d->m_styleOptionDate = so;
}
void KDateTable::setStyleOptionWeekDay( const StyleOptionHeader &so )
{
d->m_styleOptionWeekDay = so;
}
void KDateTable::setStyleOptionWeekNumber( const StyleOptionHeader &so )
{
d->m_styleOptionWeekNumber = so;
}
void KDateTable::slotReset()
{
update();
}
void KDateTable::slotDataChanged( const QDate &start, const QDate &end )
{
Q_UNUSED(start);
Q_UNUSED(end);
update();
}
void KDateTable::setModel( KDateTableDataModel *model )
{
if ( d->m_model )
{
disconnect( d->m_model, &KDateTableDataModel::reset, this, &KDateTable::slotReset );
}
d->m_model = model;
if ( d->m_model )
{
connect( d->m_model, &KDateTableDataModel::reset, this, &KDateTable::slotReset );
}
update();
}
KDateTableDataModel *KDateTable::model() const
{
return d->m_model;
}
void KDateTable::setDateDelegate( KDateTableDateDelegate *delegate )
{
delete d->m_dateDelegate;
d->m_dateDelegate = delegate;
}
void KDateTable::setDateDelegate( const QDate &date, KDateTableDateDelegate *delegate )
{
delete d->customPaintingModes.take(date.toJulianDay());
d->customPaintingModes.insert(date.toJulianDay(), delegate);
}
void KDateTable::setWeekDayDelegate( KDateTableWeekDayDelegate *delegate )
{
delete d->m_weekDayDelegate;
d->m_weekDayDelegate = delegate;
}
void KDateTable::setWeekNumberDelegate( KDateTableWeekNumberDelegate *delegate )
{
delete d->m_weekNumberDelegate;
d->m_weekNumberDelegate = delegate;
}
void KDateTable::setWeekNumbersEnabled( bool enable )
{
d->m_paintweeknumbers = enable;
}
void KDateTable::setGridEnabled( bool enable )
{
d->m_grid = enable;
}
void KDateTable::initAccels()
{
KActionCollection* localCollection = new KActionCollection(this);
localCollection->addAssociatedWidget(this);
QAction* next = localCollection->addAction(QLatin1String("next"));
next->setShortcuts(KStandardShortcut::next());
connect(next, SIGNAL(triggered(bool)), SLOT(nextMonth()));
QAction* prior = localCollection->addAction(QLatin1String("prior"));
prior->setShortcuts(KStandardShortcut::prior());
connect(prior, SIGNAL(triggered(bool)), SLOT(previousMonth()));
QAction* beginMonth = localCollection->addAction(QLatin1String("beginMonth"));
beginMonth->setShortcuts(KStandardShortcut::home());
connect(beginMonth, SIGNAL(triggered(bool)), SLOT(beginningOfMonth()));
QAction* endMonth = localCollection->addAction(QLatin1String("endMonth"));
endMonth->setShortcuts(KStandardShortcut::end());
connect(endMonth, SIGNAL(triggered(bool)), SLOT(endOfMonth()));
QAction* beginWeek = localCollection->addAction(QLatin1String("beginWeek"));
beginWeek->setShortcuts(KStandardShortcut::beginningOfLine());
connect(beginWeek, SIGNAL(triggered(bool)), SLOT(beginningOfWeek()));
QAction* endWeek = localCollection->addAction("endWeek");
endWeek->setShortcuts(KStandardShortcut::endOfLine());
connect(endWeek, SIGNAL(triggered(bool)), SLOT(endOfWeek()));
localCollection->readSettings();
}
int KDateTable::posFromDate( const QDate &date )
{
int initialPosition = date.day();
int offset = (d->m_weekDayFirstOfMonth - QLocale().firstDayOfWeek() + 7) % 7;
// make sure at least one day of the previous month is visible.
// adjust this <1 if more days should be forced visible:
if ( offset < 1 ) {
offset += 7;
}
return initialPosition + offset;
}
QDate KDateTable::dateFromPos( int position )
{
int offset = (d->m_weekDayFirstOfMonth - QLocale().firstDayOfWeek() + 7) % 7;
// make sure at least one day of the previous month is visible.
// adjust this <1 if more days should be forced visible:
if ( offset < 1 ) {
offset += 7;
}
return QDate(d->m_date.year(), d->m_date.month(), 1).addDays(position - offset);
}
void KDateTable::paintEvent(QPaintEvent *e)
{
QPainter p(this);
const QRect &rectToUpdate = e->rect();
double cellWidth = width() / ( d->m_paintweeknumbers ? 8.0 : 7.0 );
double cellHeight = height() / 7.0;
int leftCol = (int)std::floor(rectToUpdate.left() / cellWidth);
int topRow = (int)std::floor(rectToUpdate.top() / cellHeight);
int rightCol = (int)std::ceil(rectToUpdate.right() / cellWidth);
int bottomRow = (int)std::ceil(rectToUpdate.bottom() / cellHeight);
bottomRow = qMin(bottomRow, 7 - 1);
rightCol = qMin(rightCol, ( d->m_paintweeknumbers ? 8 : 7 ) - 1);
if (layoutDirection() == Qt::RightToLeft) {
p.translate((( d->m_paintweeknumbers ? 8 : 7 ) - leftCol - 1) * cellWidth, topRow * cellHeight);
} else {
p.translate(leftCol * cellWidth, topRow * cellHeight);
}
for (int i = leftCol; i <= rightCol; ++i) {
for (int j = topRow; j <= bottomRow; ++j) {
paintCell(&p, j, i);
p.translate(0, cellHeight);
}
if (layoutDirection() == Qt::RightToLeft) {
p.translate(-cellWidth, 0);
} else {
p.translate(cellWidth, 0);
}
p.translate(0, -cellHeight * (bottomRow - topRow + 1));
}
}
void KDateTable::paintCell(QPainter *painter, int row, int column)
{
//debugPlan;
double w = (width() / ( d->m_paintweeknumbers ? 8.0 : 7.0 )) - 1;
double h = (height() / 7.0) - 1;
QRectF rect( 0, 0, w, h );
QSizeF cell;
if ( row == 0 ) {
if (column == 0 && d->m_paintweeknumbers ) {
// paint something in the corner??
/* painter->setPen(palette().color(QPalette::Text));
painter->drawRect( rect );*/
return;
}
// we are drawing the headline (weekdays)
d->m_styleOptionWeekDay.rectF = rect;
d->m_styleOptionWeekDay.state = QStyle::State_None;
int col = d->m_paintweeknumbers ? column - 1 : column;
int day = col + QLocale().firstDayOfWeek();
if (day >= 8 ) {
day -= 7;
}
if ( d->m_weekDayDelegate ) {
cell = d->m_weekDayDelegate->paint( painter, d->m_styleOptionWeekDay, day, d->m_model ).size();
}
} else {
if ( d->m_paintweeknumbers && column == 0 ) {
d->m_styleOptionWeekNumber.rectF = rect;
d->m_styleOptionWeekNumber.state = QStyle::State_None;
int pos = 7 * (row-1);
QDate pCellDate = dateFromPos( pos );
if ( d->m_weekNumberDelegate ) {
cell = d->m_weekNumberDelegate->paint( painter, d->m_styleOptionWeekNumber, pCellDate.weekNumber(), d->m_model ).size();
}
} else {
// draw the dates
int col = d->m_paintweeknumbers ? column - 1 : column;
int pos = 7 * (row-1) + col;
if ( d->m_grid ) {
painter->save();
// TODO: do not hardcode color!
QPen pen( "lightgrey" );
pen.setWidthF( 0.5 );
painter->setPen( pen );
double pw = painter->pen().width();
if ( col > 0 ) {
painter->drawLine( rect.topLeft(), rect.bottomLeft() );
}
if ( row > 1 ) {
painter->drawLine( rect.topLeft(), rect.topRight() );
}
rect = rect.adjusted(pw, pw, 0, 0 );
painter->restore();
//debugPlan<<d->m_grid<<" "<<pw<<" "<<rect;
}
d->m_styleOptionDate.rectF = rect;
d->m_styleOptionDate.state = QStyle::State_None;
QDate pCellDate = dateFromPos( pos );
if( pCellDate.month() == d->m_date.month() ) {
d->m_styleOptionDate.state |= QStyle::State_Active;
}
if ( d->m_selectedDates.contains( pCellDate ) ) {
d->m_styleOptionDate.state |= QStyle::State_Selected;
}
if ( isEnabled() ) {
d->m_styleOptionDate.state |= QStyle::State_Enabled;
if (pos == d->m_hoveredPos) {
d->m_styleOptionDate.state |= QStyle::State_MouseOver;
}
}
if ( pCellDate == d->m_date ) {
d->m_styleOptionDate.state |= QStyle::State_Active;
if ( d->m_selectionmode != SingleSelection && hasFocus() ) {
d->m_styleOptionDate.state |= QStyle::State_HasFocus;
}
}
KDateTableDateDelegate *del = d->customPaintingModes.value( pCellDate.toJulianDay() );
if ( del == 0 ) {
del = d->m_dateDelegate;
}
if ( del ) {
//debugPlan<<del;
cell = del->paint( painter, d->m_styleOptionDate, pCellDate, d->m_model ).size();
} else {
warnPlan<<"No delegate!";
}
}
}
// If the day cell we just drew is bigger than the current max cell sizes,
// then adjust the max to the current cell
if (cell.width() > d->m_maxCell.width()) {
d->m_maxCell.setWidth(cell.width());
}
if (cell.height() > d->m_maxCell.height()) {
d->m_maxCell.setHeight(cell.height());
}
}
void KDateTable::KDateTablePrivate::nextMonth()
{
// setDate does validity checking for us
q->setDate(m_date.addMonths(1));
}
void KDateTable::KDateTablePrivate::previousMonth()
{
// setDate does validity checking for us
q->setDate(m_date.addMonths(-1));
}
void KDateTable::KDateTablePrivate::beginningOfMonth()
{
// setDate does validity checking for us
q->setDate(QDate(m_date.year(), m_date.month(), 1));
}
void KDateTable::KDateTablePrivate::endOfMonth()
{
// setDate does validity checking for us
q->setDate(QDate(m_date.year(), m_date.month() + 1, 0));
}
void KDateTable::KDateTablePrivate::beginningOfWeek()
{
// setDate does validity checking for us
q->setDate(m_date.addDays(1 - m_date.dayOfWeek()));
}
void KDateTable::KDateTablePrivate::endOfWeek()
{
// setDate does validity checking for us
q->setDate(m_date.addDays(7 - m_date.dayOfWeek()));
}
void KDateTable::keyPressEvent( QKeyEvent *e )
{
QDate cd = d->m_date;
switch( e->key() ) {
case Qt::Key_Up:
// setDate does validity checking for us
setDate(d->m_date.addDays(-7));
break;
case Qt::Key_Down:
// setDate does validity checking for us
setDate(d->m_date.addDays(7));
break;
case Qt::Key_Left:
// setDate does validity checking for us
setDate(d->m_date.addDays(-1));
break;
case Qt::Key_Right:
// setDate does validity checking for us
setDate(d->m_date.addDays(1));
break;
case Qt::Key_Minus:
// setDate does validity checking for us
setDate(d->m_date.addDays(-1));
break;
case Qt::Key_Plus:
// setDate does validity checking for us
setDate(d->m_date.addDays(1));
break;
case Qt::Key_N:
// setDate does validity checking for us
setDate(QDate::currentDate());
break;
case Qt::Key_Return:
case Qt::Key_Enter:
emit tableClicked();
break;
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_Meta:
case Qt::Key_Shift:
// Don't beep for modifiers
break;
default:
if (!e->modifiers()) { // hm
KNotification::beep();
}
}
switch( e->key() ) {
case Qt::Key_Down:
case Qt::Key_Up:
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Minus:
case Qt::Key_Plus: {
if ( d->m_selectionmode == ExtendedSelection ) {
if ( e->modifiers() & Qt::ShiftModifier ) {
int inc = cd > d->m_date ? 1 : -1;
for ( QDate dd = d->m_date; dd != cd; dd = dd.addDays( inc ) ) {
if ( ! d->m_selectedDates.contains( dd ) ) {
d->m_selectedDates << dd;
}
}
} else if ( e->modifiers() & Qt::ControlModifier ) {
// keep selection, just move on
} else {
d->m_selectedDates.clear();
}
}
break;}
case Qt::Key_Space:
case Qt::Key_Select:
if ( d->m_selectionmode == ExtendedSelection ) {
if ( e->modifiers() & Qt::ControlModifier ) {
if ( d->m_selectedDates.contains( d->m_date ) ) {
d->m_selectedDates.removeAt( d->m_selectedDates.indexOf( d->m_date ) );
} else {
d->m_selectedDates << d->m_date;
}
} else if ( ! d->m_selectedDates.contains( d->m_date ) ) {
d->m_selectedDates << d->m_date;
}
update();
}
break;
case Qt::Key_Menu:
if ( d->m_popupMenuEnabled )
{
QMenu *menu = new QMenu();
if ( d->m_selectionmode == ExtendedSelection ) {
emit aboutToShowContextMenu( menu, d->m_selectedDates );
} else {
menu->setTitle( QLocale().toString(d->m_date, QLocale::ShortFormat) );
emit aboutToShowContextMenu( menu, d->m_date );
}
if ( menu->isEmpty() ) {
delete menu;
} else {
int p = posFromDate( d->m_date ) - 1;
int col = p % 7;
int row = p / 7;
QPoint pos = geometry().topLeft();
QSize size = geometry().size();
int sx = size.width() / 8;
int sy = size.height() / 7;
pos = QPoint( pos.x() + sx + sx / 2 + sx * col, pos.y() + sy + sy * row );
debugPlan<<pos<<p<<col<<row;
menu->popup(mapToGlobal(pos));
}
}
break;
}
}
void KDateTable::setFontSize(int size)
{
QFontMetricsF metrics(fontMetrics());
QRectF rect;
// ----- store rectangles:
d->fontsize = size;
// ----- find largest day name:
d->m_maxCell.setWidth(0);
d->m_maxCell.setHeight(0);
QLocale locale;
for (int weekday = 1; weekday <= 7; ++weekday) {
rect = metrics.boundingRect(locale.dayName(weekday, QLocale::ShortFormat));
d->m_maxCell.setWidth(qMax(d->m_maxCell.width(), rect.width()));
d->m_maxCell.setHeight(qMax(d->m_maxCell.height(), rect.height()));
}
// ----- compare with a real wide number and add some space:
rect = metrics.boundingRect(QStringLiteral("88"));
d->m_maxCell.setWidth(qMax(d->m_maxCell.width() + 2, rect.width()));
d->m_maxCell.setHeight(qMax(d->m_maxCell.height() + 4, rect.height()));
}
void KDateTable::wheelEvent ( QWheelEvent * e )
{
setDate(d->m_date.addMonths( -(int)(e->delta()/120)) );
e->accept();
}
bool KDateTable::event( QEvent *ev )
{
switch (ev->type()) {
case QEvent::HoverMove: {
QHoverEvent *e = static_cast<QHoverEvent *>(ev);
const int row = e->pos().y() * 7 / height();
int col;
if (layoutDirection() == Qt::RightToLeft) {
col = (d->m_paintweeknumbers ? 8 : 7) - (e->pos().x() * (d->m_paintweeknumbers ? 8 : 7) / width()) - 1;
} else {
col = e->pos().x() * (d->m_paintweeknumbers ? 8 : 7) / width();
}
const int pos = row < 1 ? -1 : ((d->m_paintweeknumbers ? 8 : 7) * (row - 1)) + col;
if (pos != d->m_hoveredPos) {
d->m_hoveredPos = pos;
update();
}
break;
}
case QEvent::HoverLeave:
if (d->m_hoveredPos != -1) {
d->m_hoveredPos = -1;
update();
}
break;
case QEvent::ToolTip: {
//debugPlan<<"Tooltip";
QHelpEvent *e = static_cast<QHelpEvent*>( ev );
double cellWidth = width() / ( d->m_paintweeknumbers ? 8.0 : 7.0 );
double cellHeight = height() / 7.0;
int column = (int)std::floor(e->pos().x() / cellWidth);
int row = (int)std::floor(e->pos().y() / cellHeight);
QString text;
if ( row == 0 ) {
if (column == 0 && d->m_paintweeknumbers ) {
// corner
} else {
// we are drawing the headline (weekdays)
int col = d->m_paintweeknumbers ? column - 1 : column;
int day = col + QLocale().firstDayOfWeek();
if (day >= 8 ) {
day -= 7;
}
if ( d->m_weekDayDelegate ) {
text = d->m_weekDayDelegate->data( day, Qt::ToolTipRole, d->m_model ).toString();
}
}
} else {
if ( d->m_paintweeknumbers && column == 0 ) {
int pos = 7 * (row-1);
QDate pCellDate = dateFromPos( pos );
if ( d->m_weekNumberDelegate ) {
text = d->m_weekNumberDelegate->data( pCellDate.weekNumber(), Qt::ToolTipRole, d->m_model ).toString();
}
} else {
// draw the dates
int col = d->m_paintweeknumbers ? column - 1 : column;
int pos=7*(row-1)+col;
QDate pCellDate = dateFromPos( pos );
if ( d->m_dateDelegate ) {
text = d->m_dateDelegate->data( pCellDate, Qt::ToolTipRole, d->m_model ).toString();
}
}
}
//debugPlan<<row<<column<<text;
if ( text.isEmpty() ) {
QToolTip::hideText();
} else {
QToolTip::showText( e->globalPos(), text );
}
e->accept();
return true;
break;
}
default:
break;
}
return QWidget::event(ev);
}
void KDateTable::mousePressEvent(QMouseEvent *e)
{
if(e->type()!=QEvent::MouseButtonPress) { // the KDatePicker only reacts on mouse press events:
return;
}
if(!isEnabled()) {
KNotification::beep();
return;
}
int row, col, pos;
QPoint mouseCoord = e->pos();
row = mouseCoord.y() * 7 / height();
if (layoutDirection() == Qt::RightToLeft) {
col = ( d->m_paintweeknumbers ? 8 : 7 ) - (mouseCoord.x() * ( d->m_paintweeknumbers ? 8 : 7 ) / width()) - 1;
} else {
col = mouseCoord.x() * ( d->m_paintweeknumbers ? 8 : 7 ) / width();
}
//debugPlan<<d->m_maxCell<<", "<<size()<<row<<", "<<col<<", "<<mouseCoord;
if ( d->m_paintweeknumbers ) {
--col;
}
if (row < 1 || col < 0) { // the user clicked on the frame of the table
return;
}
// Rows and columns are zero indexed. The (row - 1) below is to avoid counting
// the row with the days of the week in the calculation.
// new position and date
pos = (7 * (row - 1)) + col;
QDate clickedDate = dateFromPos( pos );
if ( d->m_selectionmode != ExtendedSelection || e->button() != Qt::RightButton || ! d->m_selectedDates.contains( clickedDate ) )
{
switch ( d->m_selectionmode )
{
case SingleSelection:
break;
case ExtendedSelection:
//debugPlan<<"extended "<<e->modifiers()<<", "<<clickedDate;
if ( e->modifiers() & Qt::ShiftModifier )
{
if ( d->m_selectedDates.isEmpty() )
{
d->m_selectedDates << clickedDate;
}
else if ( d->m_date != clickedDate )
{
QDate dt = d->m_date;
int nxt = dt < clickedDate ? 1 : -1;
if ( d->m_selectedDates.contains( clickedDate ) )
{
d->m_selectedDates.removeAt( d->m_selectedDates.indexOf( clickedDate ) );
}
while ( dt != clickedDate )
{
if ( ! d->m_selectedDates.contains( dt ) )
{
d->m_selectedDates << dt;
}
dt = dt.addDays( nxt );
}
d->m_selectedDates << clickedDate;
}
else
{
break; // selection not changed
}
}
else if ( e->modifiers() & Qt::ControlModifier )
{
if ( d->m_selectedDates.contains( clickedDate ) )
{
d->m_selectedDates.removeAt( d->m_selectedDates.indexOf( clickedDate ) );
}
else
{
d->m_selectedDates << clickedDate;
}
}
else
{
d->m_selectedDates.clear();
d->m_selectedDates << clickedDate;
}
emit selectionChanged( d->m_selectedDates );
break;
default: break;
}
// set the new date. If it is in the previous or next month, the month will
// automatically be changed, no need to do that manually...
// validity checking done inside setDate
setDate( clickedDate );
// This could be optimized to only call update over the regions
// of old and new cell, but 99% of times there is also a call to
// setDate that already calls update() so no need to optimize that
// much here
update();
}
emit tableClicked();
if (e->button() == Qt::RightButton && d->m_popupMenuEnabled ) {
QMenu *menu = new QMenu();
if ( d->m_selectionmode == ExtendedSelection ) {
emit aboutToShowContextMenu( menu, d->m_selectedDates );
} else {
menu->setTitle( QLocale().toString(clickedDate, QLocale::ShortFormat) );
emit aboutToShowContextMenu( menu, clickedDate );
}
menu->popup(e->globalPos());
}
}
bool KDateTable::setDate(const QDate& date_)
{
if (!date_.isValid()) {
debugPlan << "KDateTable::setDate: refusing to set invalid date.";
return false;
}
if (d->m_date != date_) {
const QDate oldDate = d->m_date;
d->m_date = date_;
if (oldDate.year() != date_.year() || oldDate.month() != date_.month()) {
QDate dt(date_.year(), date_.month(), 1);
d->m_weekDayFirstOfMonth = dt.dayOfWeek();
}
emit dateChanged(oldDate, date_);
emit dateChanged(date_);
}
if ( d->m_selectionmode == KDateTable::SingleSelection )
{
d->m_selectedDates.clear();
d->m_selectedDates << date_;
emit selectionChanged( d->m_selectedDates );
}
update();
return true;
}
const QDate &KDateTable::date() const
{
return d->m_date;
}
void KDateTable::focusInEvent( QFocusEvent *e )
{
QWidget::focusInEvent( e );
}
void KDateTable::focusOutEvent( QFocusEvent *e )
{
QWidget::focusOutEvent( e );
}
QSize KDateTable::sizeHint() const
{
if(d->m_maxCell.height() > 0 && d->m_maxCell.width() > 0) {
int s = d->m_paintweeknumbers ? 8 : 7;
return QSize(qRound(d->m_maxCell.width() * s),
(qRound(d->m_maxCell.height() + 2) * s));
} else {
debugPlan << "KDateTable::sizeHint: obscure failure - ";
return QSize(-1, -1);
}
}
void KDateTable::setPopupMenuEnabled( bool enable )
{
d->m_popupMenuEnabled=enable;
}
bool KDateTable::popupMenuEnabled() const
{
return d->m_popupMenuEnabled;
}
void KDateTable::setCustomDatePainting(const QDate &date, const QColor &fgColor, BackgroundMode bgMode, const QColor &bgColor)
{
KDateTableCustomDateDelegate *del = new KDateTableCustomDateDelegate();
del->fgColor = fgColor;
del->bgMode = bgMode;
del->bgColor = bgColor;
setDateDelegate( date, del );
update();
}
void KDateTable::unsetCustomDatePainting(const QDate &date)
{
d->customPaintingModes.remove(date.toJulianDay());
}
void KDateTable::setSelectionMode( SelectionMode mode )
{
d->m_selectionmode = mode;
}
//-----------------------
KDateTableDataModel::KDateTableDataModel( QObject *parent )
: QObject( parent )
{
}
KDateTableDataModel::~KDateTableDataModel()
{
}
QVariant KDateTableDataModel::data( const QDate &date, int role, int dataType ) const
{
Q_UNUSED(date);
Q_UNUSED(role);
Q_UNUSED(dataType);
return QVariant();
}
QVariant KDateTableDataModel::weekDayData( int day, int role ) const
{
Q_UNUSED(day);
Q_UNUSED(role);
return QVariant();
}
QVariant KDateTableDataModel::weekNumberData( int week, int role ) const
{
Q_UNUSED(week);
Q_UNUSED(role);
return QVariant();
}
//-------------
KDateTableDateDelegate::KDateTableDateDelegate( QObject *parent )
: QObject( parent )
{
}
QVariant KDateTableDateDelegate::data( const QDate &date, int role, KDateTableDataModel *model )
{
//debugPlan<<date<<role<<model;
if ( model == 0 ) {
return QVariant();
}
return model->data( date, role );
}
QRectF KDateTableDateDelegate::paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model )
{
//debugPlan<<date;
painter->save();
QRectF r;
QPalette palette = option.palette;
if ( option.state & QStyle::State_Enabled && option.state & QStyle::State_Active ) {
palette.setCurrentColorGroup( QPalette::Active );
} else {
palette.setCurrentColorGroup( QPalette::Inactive );
}
// TODO: honor QStyle::State_MouseOver, and perhaps switch to style()->drawPrimitive(QStyle::PE_PanelItemViewItem, ...
QFont font = option.font;
QColor textColor = palette.text().color();
QBrush bg( palette.base() );
Qt::Alignment align = option.displayAlignment;
QString text = QLocale().toString(date.day());
if ( model )
{
QVariant v = model->data( date, Qt::ForegroundRole );
if ( v.isValid() )
{
textColor = v.value<QColor>();
}
v = model->data( date, Qt::BackgroundRole );
if ( v.isValid() )
{
bg.setColor( v.value<QColor>() );
}
v = model->data( date );
if ( v.isValid() )
{
text = v.toString();
}
v = model->data( date, Qt::TextAlignmentRole );
if ( v.isValid() )
{
align = (Qt::Alignment)v.toInt();
}
v = model->data( date, Qt::FontRole );
if ( v.isValid() )
{
font = v.value<QFont>();
}
}
QPen pen = painter->pen();
pen.setColor( textColor );
if ( option.state & QStyle::State_Selected ) {
bg = palette.highlight();
}
painter->fillRect( option.rectF, bg );
painter->setBrush( bg );
if ( option.state & QStyle::State_HasFocus ) {
painter->setPen( palette.text().color() );
painter->setPen( Qt::DotLine );
painter->drawRect( option.rectF );
} else if ( date == QDate::currentDate() ) {
painter->setPen( palette.text().color() );
painter->drawRect( option.rectF );
}
if ( option.state & QStyle::State_Selected ) {
pen.setColor( palette.highlightedText().color() );
}
painter->setFont( font );
painter->setPen( pen );
painter->drawText( option.rectF, align, text, &r );
painter->restore();
return r;
}
//---------
KDateTableCustomDateDelegate::KDateTableCustomDateDelegate( QObject *parent )
: KDateTableDateDelegate( parent )
{
}
QRectF KDateTableCustomDateDelegate::paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model )
{
//debugPlan<<date;
painter->save();
QRectF r;
bool paintRect=true;
QBrush bg(option.palette.base());
if( (option.state & QStyle::State_Active) == 0 )
{ // we are either
// ° painting a day of the previous month or
// ° painting a day of the following month
// TODO: don't hardcode gray here! Use a color with less contrast to the background than normal text.
painter->setPen( option.palette.color(QPalette::Mid) );
// painter->setPen(gray);
}
else
{ // paint a day of the current month
if (bgMode != KDateTable::NoBgMode)
{
QBrush oldbrush=painter->brush();
painter->setBrush( bgColor );
switch(bgMode)
{
case(KDateTable::CircleMode) : painter->drawEllipse(option.rectF);break;
case(KDateTable::RectangleMode) : painter->drawRect(option.rectF);break;
case(KDateTable::NoBgMode) : // Should never be here, but just to get one
// less warning when compiling
default: break;
}
painter->setBrush( oldbrush );
paintRect=false;
}
painter->setPen( fgColor );
QPen pen=painter->pen();
if ( option.state & QStyle::State_Selected )
{
// draw the currently selected date
//debugPlan<<"selected: "<<date;
if ( option.state & QStyle::State_Enabled )
{
//debugPlan<<"enabled & selected: "<<date;
painter->setPen(option.palette.color(QPalette::Highlight));
painter->setBrush(option.palette.color(QPalette::Highlight));
}
else
{
//debugPlan<<"disabled & selected: "<<date;
painter->setPen(option.palette.color(QPalette::Text));
painter->setBrush(option.palette.color(QPalette::Text));
}
pen=option.palette.color(QPalette::HighlightedText);
}
else
{
painter->setBrush(option.palette.color(QPalette::Background));
painter->setPen(option.palette.color(QPalette::Background));
}
if ( date == QDate::currentDate() )
{
painter->setPen(option.palette.color(QPalette::Text));
}
if ( paintRect )
{
painter->drawRect(option.rectF);
}
painter->setPen(pen);
QString text = QLocale().toString(date.day());
if ( model )
{
QVariant v = model->data( date );
if ( v.isValid() )
{
text = v.toString();
}
}
painter->drawText(option.rectF, Qt::AlignCenter, text, &r);
}
painter->restore();
return r;
}
//---------
KDateTableWeekDayDelegate::KDateTableWeekDayDelegate( QObject *parent )
: QObject( parent )
{
}
QVariant KDateTableWeekDayDelegate::data( int day, int role, KDateTableDataModel *model )
{
//debugPlan<<day<<role<<model;
if ( model == 0 ) {
return QVariant();
}
return model->weekDayData( day, role );
}
QRectF KDateTableWeekDayDelegate::paint( QPainter *painter, const StyleOptionHeader &option, int daynum, KDateTableDataModel *model )
{
//debugPlan<<daynum;
painter->save();
QPalette palette = option.palette;
if ( option.state & QStyle::State_Active ) {
palette.setCurrentColorGroup( QPalette::Active );
} else {
palette.setCurrentColorGroup( QPalette::Inactive );
}
QRectF rect;
QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
// font.setBold(true);
painter->setFont(font);
QColor titleColor( palette.button().color() );
QColor textColor( palette.buttonText().color() );
painter->setPen(titleColor);
painter->setBrush(titleColor);
painter->drawRect(option.rectF);
QString value;
if ( model ) {
QVariant v = model->weekDayData( daynum, Qt::DisplayRole );
if ( v.isValid() ) {
value = v.toString();
}
}
if (value.isEmpty()) {
value = QLocale().dayName(daynum, QLocale::ShortFormat);
}
//debugPlan<<daynum<<": "<<value;
painter->setPen( textColor );
painter->drawText(option.rectF, option.textAlignment, value, &rect);
// painter->setPen( palette.color(QPalette::Text) );
// painter->drawLine(QPointF(0, option.rectF.height()), QPointF(option.rectF.width(), option.rectF.height()));
painter->restore();
return rect;
}
//---------
KDateTableWeekNumberDelegate::KDateTableWeekNumberDelegate( QObject *parent )
: QObject( parent )
{
}
QVariant KDateTableWeekNumberDelegate::data( int week, int role, KDateTableDataModel *model )
{
//debugPlan<<week<<role<<model;
if ( model == 0 ) {
return QVariant();
}
return model->weekNumberData( week, role );
}
QRectF KDateTableWeekNumberDelegate::paint( QPainter *painter, const StyleOptionHeader &option, int week, KDateTableDataModel *model )
{
//debugPlan;
painter->save();
QRectF result;
QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
painter->setFont(font);
QColor titleColor( option.palette.button().color() );
QColor textColor( option.palette.buttonText().color() );
painter->setPen(titleColor);
painter->setBrush(titleColor);
painter->drawRect(option.rectF);
painter->setPen(textColor);
QString value = QString("%1").arg( week );
if ( model ) {
QVariant v = model->weekNumberData( week, Qt::DisplayRole );
if ( v.isValid() ) {
value = v.toString();
}
}
painter->drawText(option.rectF, option.textAlignment, value, &result);
// painter->setPen(option.palette.color(QPalette::Text));
// painter->drawLine(QPointF(option.rectF.width(), 0), QPointF(option.rectF.width(), option.rectF.height()));
painter->restore();
return result;
}
} //namespace KPlato
#include "moc_kdatetable.cpp"
diff --git a/src/libs/models/kptaccountsmodel.cpp b/src/libs/models/kptaccountsmodel.cpp
index ea5b1ae4..276d5c03 100644
--- a/src/libs/models/kptaccountsmodel.cpp
+++ b/src/libs/models/kptaccountsmodel.cpp
@@ -1,1157 +1,1158 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net>
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 "kptaccountsmodel.h"
#include "kptglobal.h"
#include "kptlocale.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptaccount.h"
#include "kptdatetime.h"
#include "kptschedule.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <KLocalizedString>
namespace KPlato
{
//--------------------------------------
AccountModel::AccountModel()
: QObject(),
m_project( 0 )
{
}
const QMetaEnum AccountModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
int AccountModel::propertyCount() const
{
return columnMap().keyCount();
}
QVariant AccountModel::data( const Account *a, int property, int role ) const
{
QVariant result;
if ( a == 0 ) {
return QVariant();
}
switch ( property ) {
case AccountModel::Name: result = name( a, role ); break;
case AccountModel::Description: result = description( a, role ); break;
default:
debugPlan<<"data: invalid display value column"<<property;
return QVariant();
}
return result;
}
QVariant AccountModel::name( const Account *a, int role ) const
{
//debugPlan<<a->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return a->name();
case Qt::ToolTipRole:
if ( a->isDefaultAccount() ) {
return xi18nc( "1=account name", "%1 (Default account)", a->name() );
}
return a->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::CheckStateRole:
if ( a->isDefaultAccount() ) {
return m_project && m_project->isBaselined() ? Qt::PartiallyChecked : Qt::Checked;
}
return m_project && m_project->isBaselined() ? QVariant() : Qt::Unchecked;
case Qt::DecorationRole:
if ( a->isBaselined() ) {
return koIcon("view-time-schedule-baselined");
}
break;
}
return QVariant();
}
QVariant AccountModel::description( const Account *a, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return a->description();
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant AccountModel::headerData( int property, int role ) const
{
if ( role == Qt::DisplayRole ) {
switch ( property ) {
case AccountModel::Name: return i18n( "Name" );
case AccountModel::Description: return i18n( "Description" );
default: return QVariant();
}
}
if ( role == Qt::TextAlignmentRole ) {
return QVariant();
}
if ( role == Qt::ToolTipRole ) {
switch ( property ) {
case AccountModel::Name: return ToolTip::accountName();
case AccountModel::Description: return ToolTip::accountDescription();
default: return QVariant();
}
}
return QVariant();
}
//----------------------------------------
AccountItemModel::AccountItemModel( QObject *parent )
: ItemModelBase( parent ),
m_account( 0 )
{
}
AccountItemModel::~AccountItemModel()
{
}
const QMetaEnum AccountItemModel::columnMap() const
{
return m_model.columnMap();
}
void AccountItemModel::slotAccountToBeInserted( const Account *parent, int row )
{
//debugPlan<<parent->name();
Q_ASSERT( m_account == 0 );
m_account = const_cast<Account*>(parent);
beginInsertRows( index( parent ), row, row );
}
void AccountItemModel::slotAccountInserted( const Account *account )
{
//debugPlan<<account->name();
Q_ASSERT( account->parent() == m_account ); Q_UNUSED( account );
endInsertRows();
m_account = 0;
}
void AccountItemModel::slotAccountToBeRemoved( const Account *account )
{
//debugPlan<<account->name();
Q_ASSERT( m_account == 0 );
m_account = const_cast<Account*>(account);
int row = index( account ).row();
beginRemoveRows( index( account->parent() ), row, row );
}
void AccountItemModel::slotAccountRemoved( const Account *account )
{
//debugPlan<<account->name();
Q_ASSERT( account == m_account ); Q_UNUSED( account );
endRemoveRows();
m_account = 0;
}
void AccountItemModel::setProject( Project *project )
{
if ( m_project ) {
Accounts *acc = &( m_project->accounts() );
disconnect( acc , &Accounts::changed, this, &AccountItemModel::slotAccountChanged );
disconnect( acc, &Accounts::accountAdded, this, &AccountItemModel::slotAccountInserted );
disconnect( acc, &Accounts::accountToBeAdded, this, &AccountItemModel::slotAccountToBeInserted );
disconnect( acc, &Accounts::accountRemoved, this, &AccountItemModel::slotAccountRemoved );
disconnect( acc, &Accounts::accountToBeRemoved, this, &AccountItemModel::slotAccountToBeRemoved );
}
m_project = project;
m_model.m_project = project;
if ( project ) {
Accounts *acc = &( project->accounts() );
debugPlan<<acc;
connect( acc, &Accounts::changed, this, &AccountItemModel::slotAccountChanged );
connect( acc, &Accounts::accountAdded, this, &AccountItemModel::slotAccountInserted );
connect( acc, &Accounts::accountToBeAdded, this, &AccountItemModel::slotAccountToBeInserted );
connect( acc, &Accounts::accountRemoved, this, &AccountItemModel::slotAccountRemoved );
connect( acc, &Accounts::accountToBeRemoved, this, &AccountItemModel::slotAccountToBeRemoved );
}
}
Qt::ItemFlags AccountItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = ItemModelBase::flags( index );
if ( ! m_readWrite ) {
return flags &= ~Qt::ItemIsEditable;
}
if ( ! index.isValid() || ! m_project ) {
return flags;
}
Account *a = account( index );
if ( a ) {
switch ( index.column() ) {
case AccountModel::Name: {
if ( ! a->isBaselined() ) {
flags |= Qt::ItemIsEditable;
flags |= Qt::ItemIsUserCheckable;
}
break;
}
default: flags |= Qt::ItemIsEditable; break;
}
}
return flags;
}
QModelIndex AccountItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() || m_project == 0 ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
Account *a = account( index );
if ( a == 0 ) {
return QModelIndex();
}
Account *par = a->parent();
if ( par ) {
a = par->parent();
int row = -1;
if ( a ) {
row = a->accountList().indexOf( par );
} else {
row = m_project->accounts().accountList().indexOf( par );
}
//debugPlan<<par->name()<<":"<<row;
return createIndex( row, 0, par );
}
return QModelIndex();
}
QModelIndex AccountItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
Account *par = account( parent );
if ( par == 0 ) {
if ( row < m_project->accounts().accountList().count() ) {
return createIndex( row, column, m_project->accounts().accountList().at( row ) );
}
} else if ( row < par->accountList().count() ) {
return createIndex( row, column, par->accountList().at( row ) );
}
return QModelIndex();
}
QModelIndex AccountItemModel::index( const Account *account, int column ) const
{
Account *a = const_cast<Account*>(account);
if ( m_project == 0 || account == 0 ) {
return QModelIndex();
}
int row = -1;
Account *par = a->parent();
if ( par == 0 ) {
row = m_project->accounts().accountList().indexOf( a );
} else {
row = par->accountList().indexOf( a );
}
if ( row == -1 ) {
return QModelIndex();
}
return createIndex( row, column, a );
}
int AccountItemModel::columnCount( const QModelIndex & ) const
{
return m_model.propertyCount();
}
int AccountItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 ) {
return 0;
}
Account *par = account( parent );
if ( par == 0 ) {
return m_project->accounts().accountList().count();
}
return par->accountList().count();
}
bool AccountItemModel::setName( Account *a, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() != a->name() ) {
emit executeCommand( new RenameAccountCmd( a, value.toString(), kundo2_i18n( "Modify account name" ) ) );
}
return true;
case Qt::CheckStateRole: {
switch ( value.toInt() ) {
case Qt::Unchecked:
if ( a->isDefaultAccount() ) {
emit executeCommand( new ModifyDefaultAccountCmd( m_project->accounts(), a, 0, kundo2_i18n( "De-select as default account" ) ) );
return true;
}
break;
case Qt::Checked:
if ( ! a->isDefaultAccount() ) {
emit executeCommand( new ModifyDefaultAccountCmd( m_project->accounts(), m_project->accounts().defaultAccount(), a, kundo2_i18n( "Select as default account" ) ) );
return true;
}
break;
default: break;
}
}
default: break;
}
return false;
}
bool AccountItemModel::setDescription( Account *a, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() != a->description() ) {
emit executeCommand( new ModifyAccountDescriptionCmd( a, value.toString(), kundo2_i18n( "Modify account description" ) ) );
}
return true;
}
return false;
}
QVariant AccountItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
Account *a = account( index );
if ( a == 0 ) {
return QVariant();
}
result = m_model.data( a, index.column(), role );
return result;
}
bool AccountItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags( index ) &( Qt::ItemIsEditable | Qt::CheckStateRole ) ) == 0 ) {
Q_ASSERT( true );
return false;
}
Account *a = account( index );
debugPlan<<a->name()<<value<<role;
switch (index.column()) {
case AccountModel::Name: return setName( a, value, role );
case AccountModel::Description: return setDescription( a, value, role );
default:
qWarning("data: invalid display value column %d", index.column());
return false;
}
return false;
}
QVariant AccountItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
return m_model.headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
Account *AccountItemModel::account( const QModelIndex &index ) const
{
return static_cast<Account*>( index.internalPointer() );
}
void AccountItemModel::slotAccountChanged( Account *account )
{
Account *par = account->parent();
if ( par ) {
int row = par->accountList().indexOf( account );
emit dataChanged( createIndex( row, 0, account ), createIndex( row, columnCount() - 1, account ) );
} else {
int row = m_project->accounts().accountList().indexOf( account );
emit dataChanged( createIndex( row, 0, account ), createIndex( row, columnCount() - 1, account ) );
}
}
QModelIndex AccountItemModel::insertAccount( Account *account, Account *parent, int index )
{
debugPlan;
if ( account->name().isEmpty() || m_project->accounts().findAccount( account->name() ) ) {
QString s = parent == 0 ? account->name() : parent->name();
account->setName( m_project->accounts().uniqueId( s ) );
//m_project->accounts().insertId( account );
}
emit executeCommand( new AddAccountCmd( *m_project, account, parent, index, kundo2_i18n( "Add account" ) ) );
int row = -1;
if ( parent ) {
row = parent->accountList().indexOf( account );
} else {
row = m_project->accounts().accountList().indexOf( account );
}
if ( row != -1 ) {
//debugPlan<<"Inserted:"<<account->name();
return createIndex( row, 0, account );
}
debugPlan<<"Can't find"<<account->name();
return QModelIndex();
}
void AccountItemModel::removeAccounts( QList<Account*> lst )
{
MacroCommand *cmd = 0;
KUndo2MagicString c = kundo2_i18np( "Delete Account", "Delete %1 Accounts", lst.count() );
while ( ! lst.isEmpty() ) {
bool del = true;
Account *acc = lst.takeFirst();
foreach ( Account *a, lst ) {
if ( acc->isChildOf( a ) ) {
del = false; // acc will be deleted when a is deleted
break;
}
}
if ( del ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new RemoveAccountCmd( *m_project, acc ) );
}
}
if ( cmd )
emit executeCommand( cmd );
}
//----------------------------------------
CostBreakdownItemModel::CostBreakdownItemModel( QObject *parent )
: ItemModelBase( parent ),
m_manager( 0 ),
m_cumulative( false ),
m_periodtype( Period_Day ),
m_startmode( StartMode_Project ),
m_endmode( EndMode_Project ),
m_showmode( ShowMode_Both )
{
m_format = QString( "%1 [%2]" );
}
CostBreakdownItemModel::~CostBreakdownItemModel()
{
}
const QMetaEnum CostBreakdownItemModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
int CostBreakdownItemModel::propertyCount() const
{
return columnMap().keyCount();
}
void CostBreakdownItemModel::slotAccountToBeInserted( const Account *parent, int row )
{
//debugPlan<<parent->name();
beginInsertRows( index( parent ), row, row );
}
void CostBreakdownItemModel::slotAccountInserted( const Account *account )
{
Q_UNUSED(account);
//debugPlan<<account->name();
endInsertRows();
}
void CostBreakdownItemModel::slotAccountToBeRemoved( const Account *account )
{
//debugPlan<<account->name();
int row = index( account ).row();
beginRemoveRows( index( account->parent() ), row, row );
}
void CostBreakdownItemModel::slotAccountRemoved( const Account *account )
{
Q_UNUSED(account);
//debugPlan<<account->name();
endRemoveRows();
}
void CostBreakdownItemModel::slotDataChanged()
{
fetchData();
QMap<Account*, EffortCostMap>::const_iterator it;
for (it = m_plannedCostMap.constBegin(); it != m_plannedCostMap.constEnd(); ++it) {
QModelIndex idx1 = index(it.key());
QModelIndex idx2 = index( idx1.row(), columnCount() - 1, parent( idx1 ) );
//debugPlan<<a->name()<<idx1<<idx2;
emit dataChanged( idx1, idx2 );
}
}
void CostBreakdownItemModel::setProject( Project *project )
{
if ( m_project ) {
Accounts *acc = &( m_project->accounts() );
disconnect( acc , &Accounts::changed, this, &CostBreakdownItemModel::slotAccountChanged );
disconnect( acc, &Accounts::accountAdded, this, &CostBreakdownItemModel::slotAccountInserted );
disconnect( acc, &Accounts::accountToBeAdded, this, &CostBreakdownItemModel::slotAccountToBeInserted );
disconnect( acc, &Accounts::accountRemoved, this, &CostBreakdownItemModel::slotAccountRemoved );
disconnect( acc, &Accounts::accountToBeRemoved, this, &CostBreakdownItemModel::slotAccountToBeRemoved );
disconnect(m_project, &Project::aboutToBeDeleted, this, &CostBreakdownItemModel::projectDeleted);
disconnect( m_project, &Project::nodeChanged, this, &CostBreakdownItemModel::slotDataChanged );
disconnect( m_project, &Project::nodeAdded, this, &CostBreakdownItemModel::slotDataChanged );
disconnect( m_project, &Project::nodeRemoved, this, &CostBreakdownItemModel::slotDataChanged );
disconnect( m_project, &Project::resourceChanged, this, &CostBreakdownItemModel::slotDataChanged );
disconnect( m_project, &Project::resourceAdded, this, &CostBreakdownItemModel::slotDataChanged );
disconnect( m_project, &Project::resourceRemoved, this, &CostBreakdownItemModel::slotDataChanged );
}
m_project = project;
if ( project ) {
Accounts *acc = &( project->accounts() );
debugPlan<<acc;
connect(acc, &Accounts::changed, this, &CostBreakdownItemModel::slotAccountChanged );
connect(acc, &Accounts::accountAdded, this, &CostBreakdownItemModel::slotAccountInserted );
connect(acc, &Accounts::accountToBeAdded, this, &CostBreakdownItemModel::slotAccountToBeInserted );
connect(acc, &Accounts::accountRemoved, this, &CostBreakdownItemModel::slotAccountRemoved );
connect(acc, &Accounts::accountToBeRemoved, this, &CostBreakdownItemModel::slotAccountToBeRemoved );
connect(m_project, &Project::aboutToBeDeleted, this, &CostBreakdownItemModel::projectDeleted);
connect(m_project, &Project::nodeChanged, this, &CostBreakdownItemModel::slotDataChanged );
connect(m_project, &Project::nodeAdded, this, &CostBreakdownItemModel::slotDataChanged );
connect(m_project, &Project::nodeRemoved, this, &CostBreakdownItemModel::slotDataChanged );
connect(m_project, &Project::resourceChanged, this, &CostBreakdownItemModel::slotDataChanged);
connect(m_project, &Project::resourceAdded, this, &CostBreakdownItemModel::slotDataChanged );
connect(m_project, &Project::resourceRemoved, this, &CostBreakdownItemModel::slotDataChanged );
}
}
void CostBreakdownItemModel::setScheduleManager( ScheduleManager *sm )
{
debugPlan<<m_project<<m_manager<<sm;
if ( m_manager != sm ) {
beginResetModel();
m_manager = sm;
fetchData();
endResetModel();
}
}
long CostBreakdownItemModel::id() const
{
return m_manager == 0 ? -1 : m_manager->scheduleId();
}
EffortCostMap CostBreakdownItemModel::fetchPlannedCost( Account *account )
{
EffortCostMap ec;
ec = account->plannedCost( id() );
m_plannedCostMap.insert( account, ec );
QDate s = ec.startDate();
if ( ! m_plannedStart.isValid() || s < m_plannedStart ) {
m_plannedStart = s;
}
QDate e = ec.endDate();
if ( ! m_plannedEnd.isValid() || e > m_plannedEnd ) {
m_plannedEnd = e;
}
return ec;
}
EffortCostMap CostBreakdownItemModel::fetchActualCost( Account *account )
{
debugPlan<<account->name();
EffortCostMap ec;
ec = account->actualCost( id() );
m_actualCostMap.insert( account, ec );
QDate s = ec.startDate();
if ( ! m_actualStart.isValid() || s < m_actualStart ) {
m_actualStart = s;
}
QDate e = ec.endDate();
if ( ! m_actualEnd.isValid() || e > m_actualEnd ) {
m_actualEnd = e;
}
debugPlan<<account->name()<<ec.totalEffort().toDouble(Duration::Unit_h)<<ec.totalCost();
return ec;
}
void CostBreakdownItemModel::fetchData()
{
//debugPlan<<m_start<<m_end;
m_plannedCostMap.clear();
m_plannedStart = m_plannedEnd = QDate();
m_actualStart = m_actualEnd = QDate();
if ( m_project == 0 || m_manager == 0 ) {
return;
}
foreach ( Account *a, m_project->accounts().allAccounts() ) {
fetchPlannedCost( a );
fetchActualCost( a );
}
}
QModelIndex CostBreakdownItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() || m_project == 0 ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
Account *a = account( index );
if ( a == 0 ) {
return QModelIndex();
}
Account *par = a->parent();
if ( par ) {
a = par->parent();
int row = -1;
if ( a ) {
row = a->accountList().indexOf( par );
} else {
row = m_project->accounts().accountList().indexOf( par );
}
//debugPlan<<par->name()<<":"<<row;
return createIndex( row, 0, par );
}
return QModelIndex();
}
QModelIndex CostBreakdownItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
Account *par = account( parent );
if ( par == 0 ) {
if ( row < m_project->accounts().accountList().count() ) {
return createIndex( row, column, m_project->accounts().accountList().at( row ) );
}
} else if ( row < par->accountList().count() ) {
return createIndex( row, column, par->accountList().at( row ) );
}
return QModelIndex();
}
QModelIndex CostBreakdownItemModel::index( const Account *account ) const
{
Account *a = const_cast<Account*>(account);
if ( m_project == 0 || account == 0 ) {
return QModelIndex();
}
int row = -1;
Account *par = a->parent();
if ( par == 0 ) {
row = m_project->accounts().accountList().indexOf( a );
} else {
row = par->accountList().indexOf( a );
}
if ( row == -1 ) {
return QModelIndex();
}
return createIndex( row, 0, a );
}
int CostBreakdownItemModel::columnCount( const QModelIndex & ) const
{
int c = propertyCount();
if ( startDate().isValid() && endDate().isValid() ) {
switch ( m_periodtype ) {
case Period_Day: {
c += startDate().daysTo( endDate()) + 1;
break;
}
case Period_Week: {
int days = QLocale().firstDayOfWeek() - startDate().dayOfWeek();
if ( days > 0 ) {
days -= 7;
}
QDate start = startDate().addDays( days );
c += (start.daysTo( endDate() ) / 7) + 1;
break;
}
case Period_Month: {
int days = startDate().daysInMonth() - startDate().day() + 1;
for ( QDate d = startDate(); d < endDate(); d = d.addDays( days ) ) {
++c;
days = qMin( d.daysTo( endDate() ), static_cast<qint64>(d.daysInMonth()) );
}
break;
}
}
}
return c;
}
int CostBreakdownItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 ) {
return 0;
}
Account *par = account( parent );
if ( par == 0 ) {
return m_project->accounts().accountList().count();
}
return par->accountList().count();
}
QString CostBreakdownItemModel::formatMoney( double cost1, double cost2 ) const
{
if ( m_showmode == ShowMode_Planned ) {
return m_project->locale()->formatMoney( cost1, "", 0 );
}
if ( m_showmode == ShowMode_Actual ) {
return m_project->locale()->formatMoney( cost2, "", 0 );
}
if ( m_showmode == ShowMode_Both ) {
return QString(m_format).arg(m_project->locale()->formatMoney( cost2, "", 0), m_project->locale()->formatMoney(cost1, "", 0));
}
if ( m_showmode == ShowMode_Deviation ) {
return m_project->locale()->formatMoney( cost1 - cost2, "", 0 );
}
return "";
}
QVariant CostBreakdownItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
Account *a = account( index );
if ( a == 0 ) {
return QVariant();
}
if ( role == Qt::DisplayRole ) {
switch ( index.column() ) {
case Name: return a->name();
case Description: return a->description();
case Total: {
return formatMoney( m_plannedCostMap.value( a ).totalCost(), m_actualCostMap.value( a ).totalCost() );
}
case Planned:
return m_project->locale()->formatMoney( m_plannedCostMap.value( a ).totalCost(), "", 0 );
case Actual:
return m_project->locale()->formatMoney( m_actualCostMap.value( a ).totalCost(), "", 0 );
default: {
int col = index.column() - propertyCount();
EffortCostMap pc = m_plannedCostMap.value( a );
EffortCostMap ac = m_actualCostMap.value( a );
switch ( m_periodtype ) {
case Period_Day: {
double planned = 0.0;
if ( m_cumulative ) {
planned = pc.costTo( startDate().addDays( col ) );
} else {
planned = pc.costOnDate( startDate().addDays( col ) );
}
double actual = 0.0;
if ( m_cumulative ) {
actual = ac.costTo( startDate().addDays( col ) );
} else {
actual = ac.costOnDate( startDate().addDays( col ) );
}
return formatMoney( planned, actual );
}
case Period_Week: {
int days = QLocale().firstDayOfWeek() - startDate().dayOfWeek();
if ( days > 0 ) {
days -= 7; ;
}
QDate start = startDate().addDays( days );
int week = col;
double planned = 0.0;
if ( m_cumulative ) {
planned = pc.costTo( start.addDays( ++week * 7 ) );
} else {
planned = week == 0 ? pc.cost( startDate(), startDate().daysTo( start.addDays( 7 ) ) ) : pc.cost( start.addDays( week * 7 ) );
}
double actual = 0.0;
if ( m_cumulative ) {
actual = ac.costTo( start.addDays( ++week * 7 ) );
} else {
actual = week == 0 ? ac.cost( startDate(), startDate().daysTo( start.addDays( 7 ) ) ) : ac.cost( start.addDays( week * 7 ) );
}
return formatMoney( planned, actual );
}
case Period_Month: {
int days = startDate().daysInMonth() - startDate().day() + 1;
QDate start = startDate();
for ( int i = 0; i < col; ++i ) {
start = start.addDays( days );
days = start.daysInMonth();
}
int planned = 0.0;
if ( m_cumulative ) {
planned = pc.costTo( start.addDays( start.daysInMonth() - start.day() + 1 ) );
} else {
planned = pc.cost( start, start.daysInMonth() - start.day() + 1);
}
int actual = 0.0;
if ( m_cumulative ) {
actual = ac.costTo( start.addDays( start.daysInMonth() - start.day() + 1 ) );
} else {
actual = ac.cost( start, start.daysInMonth() - start.day() + 1);
}
return formatMoney( planned, actual );
}
default:
return 0.0;
break;
}
}
}
} else if ( role == Qt::ToolTipRole ) {
switch ( index.column() ) {
case Name: return a->name();
case Description: return a->description();
case Total: {
double act = m_actualCostMap.value( a ).totalCost();
double pl = m_plannedCostMap.value( a ).totalCost();
return i18n( "Actual total cost: %1, planned total cost: %2", m_project->locale()->formatMoney( act, "", 0 ), m_project->locale()->formatMoney( pl, "", 0 ) );
}
case Planned:
case Actual:
default: break;
}
} else if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
} else {
switch ( index.column() ) {
case Name:
case Description:
case Planned:
case Actual: return QVariant();
default: {
return cost( a, index.column() - propertyCount(), role );
}
}
}
return QVariant();
}
QVariant CostBreakdownItemModel::cost( const Account *a, int offset, int role ) const
{
EffortCostMap costmap;
if ( role == Role::Planned ) {
costmap = m_plannedCostMap.value( const_cast<Account*>( a ) );
} else if ( role == Role::Actual ) {
costmap = m_actualCostMap.value( const_cast<Account*>( a ) );
} else {
return QVariant();
}
double cost = 0.0;
switch ( m_periodtype ) {
case Period_Day: {
if ( m_cumulative ) {
cost = costmap.costTo( startDate().addDays( offset ) );
} else {
cost = costmap.costOnDate( startDate().addDays( offset ) );
}
break;
}
case Period_Week: {
int days = QLocale().firstDayOfWeek() - startDate().dayOfWeek();
if ( days > 0 ) {
days -= 7; ;
}
QDate start = startDate().addDays( days );
int week = offset;
if ( m_cumulative ) {
cost = costmap.costTo( start.addDays( ++week * 7 ) );
} else {
cost = week == 0 ? costmap.cost( startDate(), startDate().daysTo( start.addDays( 7 ) ) ) : costmap.cost( start.addDays( week * 7 ) );
}
break;
}
case Period_Month: {
int days = startDate().daysInMonth() - startDate().day() + 1;
QDate start = startDate();
for ( int i = 0; i < offset; ++i ) {
start = start.addDays( days );
days = start.daysInMonth();
}
if ( m_cumulative ) {
cost = costmap.costTo( start.addDays( start.daysInMonth() - start.day() + 1 ) );
} else {
cost = costmap.cost( start, start.daysInMonth() - start.day() + 1);
}
break;
}
default:
break;
}
return cost;
}
int CostBreakdownItemModel::periodType() const
{
return m_periodtype;
}
void CostBreakdownItemModel::setPeriodType( int period )
{
if ( m_periodtype != period ) {
beginResetModel();
m_periodtype = period;
endResetModel();
}
}
int CostBreakdownItemModel::startMode() const
{
return m_startmode;
}
void CostBreakdownItemModel::setStartMode( int mode )
{
beginResetModel();
m_startmode = mode;
endResetModel();
}
int CostBreakdownItemModel::endMode() const
{
return m_endmode;
}
void CostBreakdownItemModel::setEndMode( int mode )
{
beginResetModel();
m_endmode = mode;
endResetModel();
}
QDate CostBreakdownItemModel::startDate() const
{
if ( m_project == 0 || m_manager == 0 ) {
return m_start;
}
switch ( m_startmode ) {
case StartMode_Project: {
QDate d = m_project->startTime( id() ).date();
if ( m_plannedStart.isValid() && m_plannedStart < d ) {
d = m_plannedStart;
}
if ( m_actualStart.isValid() && m_actualStart < d ) {
d = m_actualStart;
}
return d;
}
default: break;
}
return m_start;
}
void CostBreakdownItemModel::setStartDate( const QDate &date )
{
beginResetModel();
m_start = date;
endResetModel();
}
QDate CostBreakdownItemModel::endDate() const
{
if ( m_project == 0 || m_manager == 0 ) {
return m_end;
}
switch ( m_endmode ) {
case EndMode_Project: {
QDate d = m_project->endTime( id() ).date();
if ( m_plannedEnd.isValid() && m_plannedEnd > d ) {
d = m_plannedEnd;
}
if ( m_actualEnd.isValid() && m_actualEnd > d ) {
d = m_actualEnd;
}
return d;
}
case EndMode_CurrentDate: return QDate::currentDate();
default: break;
}
return m_end;
}
void CostBreakdownItemModel::setEndDate( const QDate &date )
{
beginResetModel();
m_end = date;
endResetModel();
}
bool CostBreakdownItemModel::cumulative() const
{
return m_cumulative;
}
void CostBreakdownItemModel::setCumulative( bool on )
{
beginResetModel();
m_cumulative = on;
endResetModel();
}
int CostBreakdownItemModel::showMode() const
{
return m_showmode;
}
void CostBreakdownItemModel::setShowMode( int show )
{
m_showmode = show;
}
QVariant CostBreakdownItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
switch (section) {
case Name: return i18n( "Name" );
case Description: return i18n( "Description" );
case Total: return i18n( "Total" );
case Planned: return i18n("Planned");
case Actual: return i18n("Actual");
default: break;
}
int col = section - propertyCount();
switch ( m_periodtype ) {
case Period_Day: {
return startDate().addDays( col ).toString( Qt::ISODate );
}
case Period_Week: {
return startDate().addDays( ( col ) * 7 ).weekNumber();
}
case Period_Month: {
int days = startDate().daysInMonth() - startDate().day() + 1;
QDate start = startDate();
for ( int i = 0; i < col; ++i ) {
start = start.addDays( days );
days = start.daysInMonth();
}
return QDate::shortMonthName( start.month() );
}
default:
return section;
break;
}
return QVariant();
}
if ( role == Qt::EditRole ) {
switch (section) {
case Name: return QStringLiteral( "Name" );
case Description: return QStringLiteral( "Description" );
case Total: return QStringLiteral( "Total" );
case Planned: return QStringLiteral("Planned");
case Actual: return QStringLiteral("Actual");
default: break;
}
int col = section - propertyCount();
switch ( m_periodtype ) {
case Period_Day: {
return startDate().addDays( col );
}
case Period_Week: {
return startDate().addDays( ( col ) * 7 ).weekNumber();
}
case Period_Month: {
int days = startDate().daysInMonth() - startDate().day() + 1;
QDate start = startDate();
for ( int i = 0; i < col; ++i ) {
start = start.addDays( days );
days = start.daysInMonth();
}
return start.month();
}
default:
return section;
break;
}
return QVariant();
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case Name: return ToolTip::accountName();
case Description: return ToolTip::accountDescription();
case Total: return i18n( "The total cost for the account shown as: Actual cost [ Planned cost ]" );
case Planned:
case Actual:
default: return QVariant();
}
}
if ( role == Qt::TextAlignmentRole ) {
switch ( section ) {
case Name: return QVariant();
case Description: return QVariant();
default: return (int)(Qt::AlignRight|Qt::AlignVCenter);
}
return QVariant();
}
}
return ItemModelBase::headerData(section, orientation, role);
}
Account *CostBreakdownItemModel::account( const QModelIndex &index ) const
{
return static_cast<Account*>( index.internalPointer() );
}
void CostBreakdownItemModel::slotAccountChanged( Account *account )
{
Q_UNUSED(account);
fetchData();
QMap<Account*, EffortCostMap>::const_iterator it;
for (it = m_plannedCostMap.constBegin(); it != m_plannedCostMap.constEnd(); ++it) {
QModelIndex idx1 = index(it.key() );
QModelIndex idx2 = index( idx1.row(), columnCount() - 1, parent( idx1 ) );
//debugPlan<<a->name()<<idx1<<idx2;
emit dataChanged( idx1, idx2 );
}
}
} // namespace KPlato
diff --git a/src/libs/models/kptcalendarmodel.cpp b/src/libs/models/kptcalendarmodel.cpp
index 9b9ee95d..7365eaf2 100644
--- a/src/libs/models/kptcalendarmodel.cpp
+++ b/src/libs/models/kptcalendarmodel.cpp
@@ -1,1477 +1,1478 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2012 Dag Andersen <danders@get2net>
* Copyright (C) 2017 Dag Andersen <danders@get2net>
*
* 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 "kptcalendarmodel.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptdatetime.h"
#include "kcalendar/kdatetable.h"
#include "kptdebug.h"
#include <QMimeData>
#include <QPainter>
#include <QLocale>
#include <QTimeZone>
#include <KFormat>
#ifdef HAVE_KHOLIDAYS
#include <KHolidays/HolidayRegion>
#endif
namespace KPlato
{
//-----------------------------------------
CalendarDayItemModelBase::CalendarDayItemModelBase( QObject *parent )
: ItemModelBase( parent ),
m_calendar( 0 )
{
}
CalendarDayItemModelBase::~CalendarDayItemModelBase()
{
}
void CalendarDayItemModelBase::slotCalendarToBeRemoved( const Calendar *calendar )
{
if ( calendar && calendar == m_calendar ) {
setCalendar( 0 );
}
}
void CalendarDayItemModelBase::setCalendar( Calendar *calendar )
{
m_calendar = calendar;
}
void CalendarDayItemModelBase::setProject( Project *project )
{
beginResetModel();
setCalendar( 0 );
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &CalendarDayItemModelBase::projectDeleted);
disconnect( m_project, &Project::calendarToBeRemoved, this, &CalendarDayItemModelBase::slotCalendarToBeRemoved);
}
m_project = project;
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &CalendarDayItemModelBase::projectDeleted);
connect( m_project, &Project::calendarToBeRemoved, this, &CalendarDayItemModelBase::slotCalendarToBeRemoved);
}
endResetModel();
}
//-------------------------------------
CalendarItemModel::CalendarItemModel( QObject *parent )
: ItemModelBase( parent ),
m_calendar( 0 )
{
}
CalendarItemModel::~CalendarItemModel()
{
}
const QMetaEnum CalendarItemModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
void CalendarItemModel::slotCalendarToBeInserted( const Calendar *parent, int row )
{
//debugPlan<<(parent?parent->name():"Top level")<<","<<row;
Q_ASSERT( m_calendar == 0 );
m_calendar = const_cast<Calendar *>(parent);
beginInsertRows( index( parent ), row, row );
}
void CalendarItemModel::slotCalendarInserted( const Calendar *calendar )
{
//debugPlan<<calendar->name();
Q_ASSERT( calendar->parentCal() == m_calendar );
#ifdef NDEBUG
Q_UNUSED(calendar)
#endif
endInsertRows();
m_calendar = 0;
}
void CalendarItemModel::slotCalendarToBeRemoved( const Calendar *calendar )
{
//debugPlan<<calendar->name();
int row = index( calendar ).row();
beginRemoveRows( index( calendar->parentCal() ), row, row );
}
void CalendarItemModel::slotCalendarRemoved( const Calendar * )
{
//debugPlan<<calendar->name();
endRemoveRows();
}
void CalendarItemModel::setProject( Project *project )
{
beginResetModel();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &CalendarItemModel::projectDeleted);
disconnect( m_project , &Project::calendarChanged, this, &CalendarItemModel::slotCalendarChanged);
disconnect( m_project, &Project::calendarAdded, this, &CalendarItemModel::slotCalendarInserted);
disconnect( m_project, &Project::calendarToBeAdded, this, &CalendarItemModel::slotCalendarToBeInserted);
disconnect( m_project, &Project::calendarRemoved, this, &CalendarItemModel::slotCalendarRemoved);
disconnect( m_project, &Project::calendarToBeRemoved, this, &CalendarItemModel::slotCalendarToBeRemoved);
}
m_project = project;
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &CalendarItemModel::projectDeleted);
connect( m_project, &Project::calendarChanged, this, &CalendarItemModel::slotCalendarChanged);
connect( m_project, &Project::calendarAdded, this, &CalendarItemModel::slotCalendarInserted);
connect( m_project, &Project::calendarToBeAdded, this, &CalendarItemModel::slotCalendarToBeInserted);
connect( m_project, &Project::calendarRemoved, this, &CalendarItemModel::slotCalendarRemoved);
connect( m_project, &Project::calendarToBeRemoved, this, &CalendarItemModel::slotCalendarToBeRemoved);
}
endResetModel();
}
Qt::ItemFlags CalendarItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = ItemModelBase::flags( index );
if ( !m_readWrite ) {
return flags &= ~Qt::ItemIsEditable;
}
flags |= Qt::ItemIsDropEnabled;
if ( !index.isValid() ) {
return flags;
}
Calendar *c = calendar(index);
if (!c || c->isShared()) {
if (index.column() == Name) {
flags |= Qt::ItemIsUserCheckable;
}
return flags;
}
flags |= Qt::ItemIsDragEnabled;
if ( calendar ( index ) ) {
switch ( index.column() ) {
case Name:
flags |= ( Qt::ItemIsEditable | Qt::ItemIsUserCheckable );
break;
case Scope:
flags &= ~Qt::ItemIsEditable;
break;
case TimeZone:
if ( parent( index ).isValid() ) {
flags &= ~Qt::ItemIsEditable;
} else {
flags |= Qt::ItemIsEditable;
}
break;
#ifdef HAVE_KHOLIDAYS
case HolidayRegion:
flags |= Qt::ItemIsEditable;
break;
#endif
default:
flags |= Qt::ItemIsEditable;
break;
}
}
return flags;
}
QModelIndex CalendarItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() || m_project == 0 ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
Calendar *a = calendar( index );
if ( a == 0 ) {
return QModelIndex();
}
Calendar *par = a->parentCal();
if ( par ) {
a = par->parentCal();
int row = -1;
if ( a ) {
row = a->indexOf( par );
} else {
row = m_project->indexOf( par );
}
//debugPlan<<par->name()<<":"<<row;
return createIndex( row, 0, par );
}
return QModelIndex();
}
QModelIndex CalendarItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
Calendar *par = calendar( parent );
if ( par == 0 ) {
if ( row < m_project->calendars().count() ) {
return createIndex( row, column, m_project->calendars().at( row ) );
}
} else if ( row < par->calendars().count() ) {
return createIndex( row, column, par->calendars().at( row ) );
}
return QModelIndex();
}
QModelIndex CalendarItemModel::index( const Calendar *calendar, int column ) const
{
if ( m_project == 0 || calendar == 0 ) {
return QModelIndex();
}
Calendar *a = const_cast<Calendar*>(calendar);
int row = -1;
Calendar *par = a->parentCal();
if ( par == 0 ) {
row = m_project->calendars().indexOf( a );
} else {
row = par->indexOf( a );
}
if ( row == -1 ) {
return QModelIndex();
}
return createIndex( row, column, a );
}
int CalendarItemModel::columnCount( const QModelIndex & ) const
{
return columnMap().keyCount();
}
int CalendarItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 ) {
return 0;
}
Calendar *par = calendar( parent );
if ( par == 0 ) {
return m_project->calendars().count();
}
return par->calendars().count();
}
QVariant CalendarItemModel::name( const Calendar *a, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return a->name();
case Qt::ToolTipRole:
if ( a->isDefault() ) {
return xi18nc( "1=calendar name", "%1 (Default calendar)", a->name() );
}
return a->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::CheckStateRole:
return a->isDefault() ? Qt::Checked : Qt::Unchecked;
}
return QVariant();
}
QVariant CalendarItemModel::scope( const Calendar *a, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
return a->isShared() ? i18n("Shared") : i18n("Local");
case Qt::EditRole:
return a->isShared() ? "Shared" : "Local";
case Qt::ToolTipRole:
if ( !a->isShared() ) {
return xi18nc( "@info:tooltip 1=calendar name", "%1 is a <emphasis>Local</emphasis> calendar", a->name() );
}
return xi18nc( "@info:tooltip 1=calendar name", "%1 is a <emphasis>Shared</emphasis> calendar", a->name() );
case Role::EnumList:
return QStringList() << i18n("Shared") << i18n("Local");
case Role::EnumListValue:
return a->isShared() ? 0 : 1;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool CalendarItemModel::setName( Calendar *a, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() != a->name() ) {
emit executeCommand( new CalendarModifyNameCmd( a, value.toString(), kundo2_i18n( "Modify calendar name" ) ) );
return true;
}
break;
case Qt::CheckStateRole: {
switch ( value.toInt() ) {
case Qt::Unchecked:
if ( a->isDefault() ) {
emit executeCommand( new ProjectModifyDefaultCalendarCmd( m_project, 0, kundo2_i18n( "De-select as default calendar" ) ) );
return true;
}
break;
case Qt::Checked:
if ( ! a->isDefault() ) {
emit executeCommand( new ProjectModifyDefaultCalendarCmd( m_project, a, kundo2_i18n( "Select as default calendar" ) ) );
return true;
}
break;
default: break;
}
}
default: break;
}
return false;
}
QVariant CalendarItemModel::timeZone( const Calendar *a, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return i18n( a->timeZone().id() );
case Role::EnumList: {
QStringList lst;
foreach ( const QByteArray &id, QTimeZone::availableTimeZoneIds() ) {
lst << i18n( id );
}
lst.sort();
return lst;
}
case Role::EnumListValue: {
QStringList lst = timeZone( a, Role::EnumList ).toStringList();
return lst.indexOf( i18n ( a->timeZone().id() ) );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool CalendarItemModel::setTimeZone( Calendar *a, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
if ( timeZone( a, Role::EnumListValue ) == value.toInt() ) {
return false;
}
QStringList lst = timeZone( a, Role::EnumList ).toStringList();
QString name = lst.value( value.toInt() );
QTimeZone tz;
foreach ( const QByteArray &id, QTimeZone::availableTimeZoneIds() ) {
if ( name == i18n( id ) ) {
tz = QTimeZone( id );
break;
}
}
if ( !tz.isValid() ) {
return false;
}
emit executeCommand( new CalendarModifyTimeZoneCmd( a, tz, kundo2_i18n( "Modify calendar timezone" ) ) );
return true;
}
}
return false;
}
#ifdef HAVE_KHOLIDAYS
QVariant CalendarItemModel::holidayRegion( const Calendar *a, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
if (a->holidayRegionCode().isEmpty() || !a->holidayRegion()->isValid()) {
return i18n("None");
}
if (a->holidayRegionCode() == "Default") {
return i18n("Default");
}
return a->holidayRegion()->name();
case Qt::EditRole:
if (a->holidayRegionCode().isEmpty()) {
return "None";
}
return a->holidayRegionCode();
case Qt::ToolTipRole:
if (!a->holidayRegion()->isValid()) {
return xi18nc("@info:tooltip", "No holidays");
} else if (a->holidayRegionCode() == "Default") {
return xi18nc("@info:tooltip", "Default region: <emphasis>%1</emphasis>", a->holidayRegion()->name());
}
return a->holidayRegion()->description();
case Role::EnumList: {
QStringList lst;
lst << i18n("None") << i18n("Default");
foreach(const QString &code, a->holidayRegionCodes()) {
lst << KHolidays::HolidayRegion::name(code);
}
return lst;
}
case Role::EnumListValue: {
if (!a->holidayRegion()->isValid()) {
return 0; // None
}
if (a->holidayRegionCode() == "Default") {
return 1;
}
return a->holidayRegionCodes().indexOf(a->holidayRegionCode()) + 2;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool CalendarItemModel::setHolidayRegion( Calendar *a, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
QString code = "None";
if (value.toInt() == 1) {
code = "Default";
} else if (value.toInt() > 1) {
code = a->holidayRegionCodes().value(value.toInt() - 2);
}
if (a->holidayRegionCode() == code || (code == "None" && a->holidayRegionCode().isEmpty())) {
return false;
}
emit executeCommand(new CalendarModifyHolidayRegionCmd(a, code, kundo2_i18n("Modify calendar holiday region")));
return true;
}
}
return false;
}
#endif
QVariant CalendarItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
Calendar *a = calendar( index );
if ( a == 0 ) {
return QVariant();
}
switch ( index.column() ) {
case Name: result = name( a, role ); break;
case Scope: result = scope( a, role ); break;
case TimeZone: result = timeZone( a, role ); break;
#ifdef HAVE_KHOLIDAYS
case HolidayRegion: result = holidayRegion( a, role ); break;
#endif
default:
debugPlan<<"data: invalid display value column"<<index.column();
return QVariant();
}
return result;
}
bool CalendarItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags( index ) &( Qt::ItemIsEditable | Qt::CheckStateRole ) ) == 0 ) {
Q_ASSERT( true );
return false;
}
Calendar *a = calendar( index );
switch (index.column()) {
case Name: return setName( a, value, role );
case Scope: return false;
case TimeZone: return setTimeZone( a, value, role );
#ifdef HAVE_KHOLIDAYS
case HolidayRegion: return setHolidayRegion( a, value, role );
#endif
default:
warnPlan<<"data: invalid display value column "<<index.column();
return false;
}
return false;
}
QVariant CalendarItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case Name: return xi18nc( "@title:column", "Name" );
case Scope: return xi18nc( "@title:column", "Scope" );
case TimeZone: return xi18nc( "@title:column", "Timezone" );
#ifdef HAVE_KHOLIDAYS
case HolidayRegion: return xi18nc( "@title:column", "Holiday Region" );
#endif
default: return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
default: return QVariant();
}
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case Name: return ToolTip::calendarName();
case Scope: return QVariant();
case TimeZone: return ToolTip::calendarTimeZone();
#ifdef HAVE_KHOLIDAYS
case HolidayRegion: return xi18nc("@info:tooltip", "The holiday region");
#endif
default: return QVariant();
}
}
return ItemModelBase::headerData(section, orientation, role);
}
Calendar *CalendarItemModel::calendar( const QModelIndex &index ) const
{
return static_cast<Calendar*>( index.internalPointer() );
}
void CalendarItemModel::slotCalendarChanged( Calendar *calendar )
{
Calendar *par = calendar->parentCal();
if ( par ) {
int row = par->indexOf( calendar );
emit dataChanged( createIndex( row, 0, calendar ), createIndex( row, columnCount() - 1, calendar ) );
} else {
int row = m_project->indexOf( calendar );
emit dataChanged( createIndex( row, 0, calendar ), createIndex( row, columnCount() - 1, calendar ) );
}
}
Qt::DropActions CalendarItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList CalendarItemModel::mimeTypes() const
{
return QStringList() << "application/x-vnd.kde.plan.calendarid.internal";
}
QMimeData *CalendarItemModel::mimeData( const QModelIndexList & indexes ) const
{
QMimeData *m = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QList<int> rows;
foreach (const QModelIndex &index, indexes) {
if ( index.isValid() && !rows.contains( index.row() ) ) {
debugPlan<<index.row();
Calendar *c = calendar( index );
if ( c ) {
stream << c->id();
}
}
}
m->setData("application/x-vnd.kde.plan.calendarid.internal", encodedData);
return m;
}
bool CalendarItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent )
{
debugPlan<<action<<row;
if (action == Qt::IgnoreAction) {
return true;
}
if ( !data->hasFormat( "application/x-vnd.kde.plan.calendarid.internal" ) ) {
return false;
}
if ( action == Qt::MoveAction ) {
debugPlan<<"MoveAction";
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.calendarid.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
Calendar *par = 0;
if ( parent.isValid() ) {
par = calendar( parent );
}
MacroCommand *cmd = 0;
QList<Calendar*> lst = calendarList( stream );
foreach ( Calendar *c, lst ) {
if ( c->parentCal() != par ) {
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Re-parent calendar" ) );
cmd->addCommand( new CalendarModifyParentCmd( m_project, c, par ) );
} else {
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Move calendar" ) );
cmd->addCommand( new CalendarMoveCmd( m_project, c, row, par ) );
}
}
if ( cmd ) {
emit executeCommand( cmd );
return true;
}
//debugPlan<<row<<","<<column<<" parent="<<parent.row()<<","<<parent.column()<<":"<<par->name();
}
return false;
}
QList<Calendar*> CalendarItemModel::calendarList( QDataStream &stream ) const
{
QList<Calendar*> lst;
while (!stream.atEnd()) {
QString id;
stream >> id;
Calendar *c = m_project->findCalendar( id );
if ( c ) {
lst << c;
}
}
return lst;
}
bool CalendarItemModel::dropAllowed( Calendar *on, const QMimeData *data )
{
debugPlan<<on<<data->hasFormat("application/x-vnd.kde.plan.calendarid.internal");
if ( !data->hasFormat("application/x-vnd.kde.plan.calendarid.internal") ) {
return false;
}
if ( on == 0 && ! ( flags( QModelIndex() ) & (int)Qt::ItemIsDropEnabled ) ) {
return false;
}
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.calendarid.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QList<Calendar*> lst = calendarList( stream );
foreach ( Calendar *c, lst ) {
if ( (flags( index( c ) ) & (int)Qt::ItemIsDropEnabled) == 0 ) {
return false;
}
if ( on != 0 && on == c->parentCal() ) {
return false;
}
if ( on != 0 && ( on == c || on->isChildOf( c ) ) ) {
return false;
}
}
return true;
}
QModelIndex CalendarItemModel::insertCalendar ( Calendar *calendar, int pos, Calendar *parent )
{
//debugPlan<<calendar<<pos<<parent;
emit executeCommand( new CalendarAddCmd( m_project, calendar, pos, parent, kundo2_i18n( "Add calendar" ) ) );
int row = -1;
if ( parent ) {
row = parent->indexOf( calendar );
} else {
row = m_project->indexOf( calendar );
}
if ( row != -1 ) {
//debugPlan<<"Inserted:"<<calendar->name()<<"row="<<row;
return createIndex( row, 0, calendar );
}
return QModelIndex();
}
void CalendarItemModel::removeCalendar( QList<Calendar *> /*lst*/ )
{
}
void CalendarItemModel::removeCalendar( Calendar *calendar )
{
if ( calendar == 0 ) {
return;
}
emit executeCommand( new CalendarRemoveCmd( m_project, calendar, kundo2_i18n( "Delete calendar" ) ) );
}
//------------------------------------------
CalendarDayItemModel::CalendarDayItemModel( QObject *parent )
: CalendarDayItemModelBase( parent )
{
}
CalendarDayItemModel::~CalendarDayItemModel()
{
}
void CalendarDayItemModel::slotWorkIntervalAdded( CalendarDay *day, TimeInterval *ti )
{
Q_UNUSED(ti);
//debugPlan<<day<<","<<ti;
int c = m_calendar->indexOfWeekday( day );
if ( c == -1 ) {
return;
}
emit dataChanged( createIndex( 0, c, day ), createIndex( 0, c, day ) );
}
void CalendarDayItemModel::slotWorkIntervalRemoved( CalendarDay *day, TimeInterval *ti )
{
Q_UNUSED(ti);
int c = m_calendar->indexOfWeekday( day );
if ( c == -1 ) {
return;
}
emit dataChanged( createIndex( 0, c, day ), createIndex( 0, c, day ) );
}
void CalendarDayItemModel::slotDayChanged( CalendarDay *day )
{
int c = m_calendar->indexOfWeekday( day );
if ( c == -1 ) {
return;
}
debugPlan<<day<<", "<<c;
emit dataChanged( createIndex( 0, c, day ), createIndex( 0, c, day ) );
}
void CalendarDayItemModel::slotTimeIntervalChanged( TimeInterval *ti )
{
Q_UNUSED(ti);
/* CalendarDay *d = parentDay( ti );
if ( d == 0 ) {
return;
}
int row = d->indexOf( ti );
emit dataChanged( createIndex( row, 0, ti ), createIndex( row, columnCount() - 1, ti ) );*/
}
void CalendarDayItemModel::setCalendar( Calendar *calendar )
{
beginResetModel();
//debugPlan<<m_calendar<<" -->"<<calendar;
if ( m_calendar ) {
disconnect(m_calendar, static_cast<void (Calendar::*)(CalendarDay*)>(&Calendar::changed), this, &CalendarDayItemModel::slotDayChanged);
disconnect(m_calendar, static_cast<void (Calendar::*)(TimeInterval*)>(&Calendar::changed), this, &CalendarDayItemModel::slotTimeIntervalChanged);
disconnect(m_calendar, &Calendar::workIntervalAdded, this, &CalendarDayItemModel::slotWorkIntervalAdded);
disconnect(m_calendar, &Calendar::workIntervalRemoved, this, &CalendarDayItemModel::slotWorkIntervalRemoved);
}
m_calendar = calendar;
if ( calendar ) {
connect(m_calendar, static_cast<void (Calendar::*)(CalendarDay*)>(&Calendar::changed), this, &CalendarDayItemModel::slotDayChanged);
connect(m_calendar, static_cast<void (Calendar::*)(TimeInterval*)>(&Calendar::changed), this, &CalendarDayItemModel::slotTimeIntervalChanged);
connect(m_calendar, &Calendar::workIntervalAdded, this, &CalendarDayItemModel::slotWorkIntervalAdded);
connect(m_calendar, &Calendar::workIntervalRemoved, this, &CalendarDayItemModel::slotWorkIntervalRemoved);
}
endResetModel();
}
Qt::ItemFlags CalendarDayItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = ItemModelBase::flags( index );
if ( !m_readWrite ) {
return flags &= ~Qt::ItemIsEditable;
}
return flags |= Qt::ItemIsEditable;
}
QModelIndex CalendarDayItemModel::parent( const QModelIndex &index ) const
{
Q_UNUSED(index);
return QModelIndex();
}
bool CalendarDayItemModel::hasChildren( const QModelIndex &parent ) const
{
//debugPlan<<parent.internalPointer()<<":"<<parent.row()<<","<<parent.column();
if ( m_project == 0 || m_calendar == 0 ) {
return false;
}
return ! parent.isValid();
}
QModelIndex CalendarDayItemModel::index( int row, int column, const QModelIndex &par ) const
{
if ( m_project == 0 || m_calendar == 0 ) {
return QModelIndex();
}
if ( par.isValid() ) {
return QModelIndex();
}
CalendarDay *d = m_calendar->weekday( column + 1 ); // weekdays are 1..7
if ( d == 0 ) {
return QModelIndex();
}
return createIndex( row, column, d );
}
QModelIndex CalendarDayItemModel::index( const CalendarDay *d) const
{
if ( m_project == 0 || m_calendar == 0 ) {
return QModelIndex();
}
int col = m_calendar->indexOfWeekday( d );
if ( col == -1 ) {
return QModelIndex();
}
return createIndex( 0, col, const_cast<CalendarDay*>( d ) );
}
int CalendarDayItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return 7;
}
int CalendarDayItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 || m_calendar == 0 || parent.isValid() ) {
return 0;
}
return 1;
}
QVariant CalendarDayItemModel::name( int weekday, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
if ( weekday >= 1 && weekday <= 7 ) {
return QLocale().dayName( weekday, QLocale::ShortFormat );
}
break;
case Qt::ToolTipRole:
if ( weekday >= 1 && weekday <= 7 ) {
return QLocale().dayName( weekday, QLocale::LongFormat );
}
break;
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CalendarDayItemModel::dayState( const CalendarDay *d, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
switch ( d->state() ) {
case CalendarDay::Undefined: return i18nc( "Undefined", "U" );
case CalendarDay::NonWorking: return i18nc( "NonWorking", "NW" );
case CalendarDay::Working: return i18nc( "Working", "W" );
}
break;
case Qt::ToolTipRole:
return CalendarDay::stateToString( d->state(), true );
case Role::EnumList: {
QStringList lst = CalendarDay::stateList( true );
return lst;
}
case Qt::EditRole:
case Role::EnumListValue: {
return d->state();
}
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::EditorType:
return Delegate::EnumEditor;
}
return QVariant();
}
bool CalendarDayItemModel::setDayState( CalendarDay *d, const QVariant &value, int role )
{
//debugPlan;
switch ( role ) {
case Qt::EditRole:
int v = value.toInt();
emit executeCommand( new CalendarModifyStateCmd( m_calendar, d, static_cast<CalendarDay::State>( v ), kundo2_i18n( "Modify calendar state" ) ) );
return true;
}
return false;
}
QVariant CalendarDayItemModel::workDuration( const CalendarDay *day, int role ) const
{
//debugPlan<<day->date()<<","<<role;
switch ( role ) {
case Qt::DisplayRole: {
if ( day->state() == CalendarDay::Working ) {
return QLocale().toString( day->workDuration().toDouble( Duration::Unit_h ), 'f', 1 );
}
return QVariant();
}
case Qt::ToolTipRole: {
if ( day->state() == CalendarDay::Working ) {
QLocale locale;
QStringList tip;
foreach ( TimeInterval *i, day->timeIntervals() ) {
tip << i18nc( "1=time 2=The number of hours of work duration (non integer)", "%1, %2 hours", locale.toString( i->startTime(), QLocale::ShortFormat ), locale.toString( i->hours(), 'f', 2 ) );
}
return tip.join( "\n" );
}
return QVariant();
}
case Qt::EditRole:
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
}
return QVariant();
}
QVariant CalendarDayItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
if ( ! index.isValid() ) {
return result;
}
CalendarDay *d = day( index );
if ( d == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole: {
switch ( d->state() ) {
case CalendarDay::Working:
result = workDuration( d, role );
break;
case CalendarDay::NonWorking:
result = dayState( d, role );
break;
default: {
// Return parent value (if any)
for ( Calendar *c = m_calendar->parentCal(); c != 0; c = c->parentCal() ) {
d = c->weekday( index.column() + 1 );
Q_ASSERT( d );
if ( d->state() == CalendarDay::Working ) {
return workDuration( d, role );
}
if ( d->state() == CalendarDay::NonWorking ) {
return dayState( d, role );
}
}
break;
}
}
break;
}
case Qt::ToolTipRole: {
if ( d->state() == CalendarDay::Undefined ) {
return xi18nc( "@info:tooltip", "Undefined" );
}
if ( d->state() == CalendarDay::NonWorking ) {
return xi18nc( "@info:tooltip", "Non-working" );
}
QLocale locale;
KFormat format(locale);
QStringList tip;
foreach ( TimeInterval *i, d->timeIntervals() ) {
tip << xi18nc( "@info:tooltip 1=time 2=The work duration (non integer)", "%1, %2", locale.toString( i->startTime(), QLocale::ShortFormat ), format.formatDuration( i->second ) );
}
return tip.join( "<nl/>" );
}
case Qt::FontRole: {
if ( d->state() != CalendarDay::Undefined ) {
return QVariant();
}
// If defined in parent, return italic
for ( Calendar *c = m_calendar->parentCal(); c != 0; c = c->parentCal() ) {
d = c->weekday( index.column() + 1 );
Q_ASSERT( d );
if ( d->state() != CalendarDay::Undefined ) {
QFont f;
f.setItalic( true );
return f;
}
}
break;
}
}
return result;
}
bool CalendarDayItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
return ItemModelBase::setData( index, value, role );
}
QVariant CalendarDayItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
return name( section + 1, role );
default:
return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
default: return Qt::AlignCenter;
}
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
/* case 0: return ToolTip::Calendar Name;*/
default: return QVariant();
}
}
return ItemModelBase::headerData(section, orientation, role);
}
CalendarDay *CalendarDayItemModel::day( const QModelIndex &index ) const
{
return static_cast<CalendarDay*>( index.internalPointer() );
}
QAbstractItemDelegate *CalendarDayItemModel::createDelegate( int column, QWidget *parent ) const
{
Q_UNUSED(parent);
switch ( column ) {
default: return 0;
}
return 0;
}
//-----------------------
DateTableDataModel::DateTableDataModel( QObject *parent )
: KDateTableDataModel( parent ),
m_calendar( 0 )
{
}
void DateTableDataModel::setCalendar( Calendar *calendar )
{
if ( m_calendar ) {
disconnect(m_calendar, &Calendar::dayAdded, this, &KDateTableDataModel::reset);
disconnect(m_calendar, &Calendar::dayRemoved, this, &KDateTableDataModel::reset);
disconnect(m_calendar, static_cast<void (Calendar::*)(KPlato::CalendarDay*)>(&Calendar::changed), this, &DateTableDataModel::reset);
}
m_calendar = calendar;
if ( m_calendar ) {
connect( m_calendar, &Calendar::dayAdded, this, &KDateTableDataModel::reset);
connect( m_calendar, &Calendar::dayRemoved, this, &KDateTableDataModel::reset);
connect(m_calendar, static_cast<void (Calendar::*)(KPlato::CalendarDay*)>(&Calendar::changed), this, &DateTableDataModel::reset);
}
emit reset();
}
QVariant DateTableDataModel::data( const Calendar &cal, const QDate &date, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
CalendarDay *day = cal.findDay( date );
if ( day == 0 || day->state() == CalendarDay::Undefined ) {
#ifdef HAVE_KHOLIDAYS
if (cal.isHoliday(date)) {
return i18nc( "NonWorking", "NW" );
}
#endif
if ( cal.parentCal() ) {
return data( *( cal.parentCal() ), date, role );
}
return "";
}
if ( day->state() == CalendarDay::NonWorking ) {
return i18nc( "NonWorking", "NW" );
}
double v;
v = day->workDuration().toDouble( Duration::Unit_h );
return QLocale().toString( v, 'f', 1 );
}
case Qt::TextAlignmentRole:
return (uint)( Qt::AlignHCenter | Qt::AlignBottom );
case Qt::FontRole: {
CalendarDay *day = cal.findDay( date );
if ( day && day->state() != CalendarDay::Undefined ) {
if ( &cal != m_calendar ) {
QFont f;
f.setItalic( true );
return f;
}
return QVariant();
}
if ( cal.parentCal() ) {
return data( *( cal.parentCal() ), date, role );
}
break;
}
default:
break;
}
return QVariant();
}
QVariant DateTableDataModel::data( const QDate &date, int role, int dataType ) const
{
//debugPlan<<date<<role<<dataType;
if ( role == Qt::ToolTipRole ) {
if ( m_calendar == 0 ) {
return QVariant();
}
CalendarDay *day = m_calendar->findDay( date );
if ( day == 0 || day->state() == CalendarDay::Undefined ) {
#ifdef HAVE_KHOLIDAYS
if (m_calendar->isHoliday(date)) {
return xi18nc( "@info:tooltip", "Holiday" );
}
#endif
return xi18nc( "@info:tooltip", "Undefined" );
}
if ( day->state() == CalendarDay::NonWorking ) {
return xi18nc( "@info:tooltip", "Non-working" );
}
QLocale locale;
KFormat format(locale);
QStringList tip;
foreach ( TimeInterval *i, day->timeIntervals() ) {
tip << xi18nc( "@info:tooltip 1=time 2=The work duration (non integer)", "%1, %2", locale.toString( i->startTime(), QLocale::ShortFormat ), format.formatDuration( i->second ) );
}
return tip.join( "\n" );
}
switch ( dataType ) {
case -1: { //default (date)
switch ( role ) {
case Qt::DisplayRole: {
return QVariant();
}
case Qt::TextAlignmentRole:
return (uint)Qt::AlignLeft | Qt::AlignTop;
case Qt::FontRole:
break;//return QFont( "Helvetica", 6 );
case Qt::BackgroundRole:
break;//return QColor( "red" );
default:
break;
}
break;
}
case 0: {
if ( m_calendar == 0 ) {
return "";
}
return data( *m_calendar, date, role );
}
default:
break;
}
return QVariant();
}
QVariant DateTableDataModel::weekDayData( int day, int role ) const
{
Q_UNUSED(day);
Q_UNUSED(role);
return QVariant();
}
QVariant DateTableDataModel::weekNumberData( int week, int role ) const
{
Q_UNUSED(week);
Q_UNUSED(role);
return QVariant();
}
//-------------
DateTableDateDelegate::DateTableDateDelegate( QObject *parent )
: KDateTableDateDelegate( parent )
{
}
QRectF DateTableDateDelegate::paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model )
{
//debugPlan<<date;
QRectF r;
StyleOptionViewItem style = option;
style.font.setPointSize( style.font.pointSize() - 2 );
//debugPlan<<" fonts: "<<option.font.pointSize()<<style.font.pointSize();
r = KDateTableDateDelegate::paint( painter, style, date, model );
if ( model == 0 ) {
return r;
}
painter->save();
painter->translate( r.width(), 0.0 );
QRectF rect( 1, 1, option.rectF.right() - r.width(), option.rectF.bottom() );
//debugPlan<<" rects: "<<r<<rect;
QString text = model->data( date, Qt::DisplayRole, 0 ).toString();
int align = model->data( date, Qt::TextAlignmentRole, 0 ).toInt();
QFont f = option.font;
QVariant v = model->data( date, Qt::FontRole, 0 );
if ( v.isValid() ) {
f = v.value<QFont>();
}
painter->setFont( f );
if ( option.state & QStyle::State_Selected ) {
painter->setPen( option.palette.highlightedText().color() );
} else {
painter->setPen( option.palette.color( QPalette::Text ) );
}
painter->drawText(rect, align, text, &r);
painter->restore();
return r;
}
//-------------------------------------
CalendarExtendedItemModel::CalendarExtendedItemModel( QObject *parent )
: CalendarItemModel( parent )
{
}
Qt::ItemFlags CalendarExtendedItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = CalendarItemModel::flags( index );
if ( ! m_readWrite || ! index.isValid() || calendar( index ) == 0 ) {
return flags;
}
return flags |= Qt::ItemIsEditable;
}
QModelIndex CalendarExtendedItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
Calendar *par = calendar( parent );
if ( par == 0 ) {
if ( row < m_project->calendars().count() ) {
return createIndex( row, column, m_project->calendars().at( row ) );
}
} else if ( row < par->calendars().count() ) {
return createIndex( row, column, par->calendars().at( row ) );
}
return QModelIndex();
}
int CalendarExtendedItemModel::columnCount( const QModelIndex & ) const
{
return CalendarItemModel::columnCount() + 2; // weekdays + date
}
QVariant CalendarExtendedItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
Calendar *a = calendar( index );
if ( a == 0 ) {
return QVariant();
}
int col = index.column() - CalendarItemModel::columnCount( index );
if ( col < 0 ) {
return CalendarItemModel::data( index, role );
}
switch ( col ) {
default:
debugPlan<<"Fetching data from weekdays and date is not supported";
break;
}
return result;
}
bool CalendarExtendedItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
int col = index.column() - CalendarItemModel::columnCount( index );
if ( col < 0 ) {
return CalendarItemModel::setData( index, value, role );
}
if ( ( flags( index ) &( Qt::ItemIsEditable ) ) == 0 ) {
return false;
}
Calendar *cal = calendar( index );
if ( cal == 0 || col > 2 ) {
return false;
}
switch ( col ) {
case 0: { // weekday
if ( value.type() != QVariant::List ) {
return false;
}
QVariantList lst = value.toList();
if ( lst.count() < 2 ) {
return false;
}
int wd = CalendarWeekdays::dayOfWeek( lst.at( 0 ).toString() );
if ( wd < 1 || wd > 7 ) {
return false;
}
CalendarDay *day = new CalendarDay();
if ( lst.count() == 2 ) {
QString state = lst.at( 1 ).toString();
if ( state == "NonWorking" ) {
day->setState( CalendarDay::NonWorking );
} else if ( state == "Undefined" ) {
day->setState( CalendarDay::Undefined );
} else {
delete day;
return false;
}
CalendarModifyWeekdayCmd *cmd = new CalendarModifyWeekdayCmd( cal, wd, day, kundo2_i18n( "Modify calendar weekday" ) );
emit executeCommand( cmd );
return true;
}
if ( lst.count() % 2 == 0 ) {
delete day;
return false;
}
day->setState( CalendarDay::Working );
for ( int i = 1; i < lst.count(); i = i + 2 ) {
QTime t1 = lst.at( i ).toTime();
QTime t2 = lst.at( i + 1 ).toTime();
int length = t1.msecsTo( t2 );
if ( t1 == QTime( 0, 0, 0 ) && t2 == t1 ) {
length = 24 * 60 * 60 *1000;
} else if ( length < 0 && t2 == QTime( 0, 0, 0 ) ) {
length += 24 * 60 * 60 *1000;
} else if ( length == 0 || ( length < 0 && t2 != QTime( 0, 0, 0 ) ) ) {
delete day;
return false;
}
length = qAbs( length );
day->addInterval( t1, length );
}
CalendarModifyWeekdayCmd *cmd = new CalendarModifyWeekdayCmd( cal, wd, day, kundo2_i18n( "Modify calendar weekday" ) );
emit executeCommand( cmd );
return true;
}
case 1: { // day
if ( value.type() != QVariant::List ) {
return false;
}
CalendarDay *day = new CalendarDay();
QVariantList lst = value.toList();
if ( lst.count() < 2 ) {
return false;
}
day->setDate( lst.at( 0 ).toDate() );
if ( ! day->date().isValid() ) {
delete day;
return false;
}
if ( lst.count() == 2 ) {
QString state = lst.at( 1 ).toString();
if ( state == "NonWorking" ) {
day->setState( CalendarDay::NonWorking );
} else if ( state == "Undefined" ) {
day->setState( CalendarDay::Undefined );
} else {
delete day;
return false;
}
CalendarModifyDayCmd *cmd = new CalendarModifyDayCmd( cal, day, kundo2_i18n( "Modify calendar date" ) );
emit executeCommand( cmd );
return true;
}
if ( lst.count() % 2 == 0 ) {
delete day;
return false;
}
day->setState( CalendarDay::Working );
for ( int i = 1; i < lst.count(); i = i + 2 ) {
QTime t1 = lst.at( i ).toTime();
QTime t2 = lst.at( i + 1 ).toTime();
int length = t1.msecsTo( t2 );
if ( t1 == QTime( 0, 0, 0 ) && t2 == t1 ) {
length = 24 * 60 * 60 *1000;
} else if ( length < 0 && t2 == QTime( 0, 0, 0 ) ) {
length += 24 * 60 * 60 *1000;
} else if ( length == 0 || ( length < 0 && t2 != QTime( 0, 0, 0 ) ) ) {
delete day;
return false;
}
length = qAbs( length );
day->addInterval( t1, length );
}
CalendarModifyDayCmd *cmd = new CalendarModifyDayCmd( cal, day, kundo2_i18n( "Modify calendar date" ) );
emit executeCommand( cmd );
return true;
}
}
return false;
}
QVariant CalendarExtendedItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
int col = section - CalendarItemModel::columnCount();
if ( col < 0 ) {
return CalendarItemModel::headerData( section, orientation, role );
}
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
switch ( col ) {
case 0: return xi18nc( "@title:column", "Weekday" );
case 1: return xi18nc( "@title:column", "Date" );
default: return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
switch ( col ) {
default: return QVariant();
}
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
default: return QVariant();
}
}
return QVariant();
}
int CalendarExtendedItemModel::columnNumber(const QString& name) const
{
QStringList lst;
lst << "Weekday"
<< "Date";
if ( lst.contains( name ) ) {
return lst.indexOf( name ) + CalendarItemModel::columnCount();
}
return CalendarItemModel::columnMap().keyToValue( name.toUtf8() );
}
} // namespace KPlato
diff --git a/src/libs/models/kptcommonstrings.cpp b/src/libs/models/kptcommonstrings.cpp
index 1f6e8968..ebc2cbdc 100644
--- a/src/libs/models/kptcommonstrings.cpp
+++ b/src/libs/models/kptcommonstrings.cpp
@@ -1,188 +1,189 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011 Dag Andersen <danders@get2net.dk>
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 "kptcommonstrings.h"
#include <kundo2magicstring.h>
#include <KLocalizedString>
namespace KPlato
{
QString ToolTip::nodeName() { return xi18nc( "@info:tooltip", "The name of the task" ); }
QString ToolTip::nodeType() { return xi18nc( "@info:tooltip", "Task type" ); }
QString ToolTip::nodeResponsible() { return xi18nc( "@info:tooltip", "The person responsible for this task" ); }
QString ToolTip::allocation() { return xi18nc( "@info:tooltip", "List of resource allocations" ); }
QString ToolTip::nodeConstraint() { return xi18nc( "@info:tooltip", "The timing constraint type" ); }
QString ToolTip::nodeConstraintStart() { return xi18nc( "@info:tooltip", "Constraint start time" ); }
QString ToolTip::nodeConstraintEnd() { return xi18nc( "@info:tooltip", "Constraint end time" ); }
QString ToolTip::nodeDescription() { return xi18nc( "@info:tooltip", "Task notes" ); }
QString ToolTip::nodeWBS() { return xi18nc( "@info:tooltip", "Work Breakdown Structure Code" ); }
QString ToolTip::nodeLevel() { return xi18nc( "@info:tooltip", "Node level" ); }
QString ToolTip::nodeRisk() { return xi18nc( "@info:tooltip", "Risk controls the PERT distribution used when calculating the actual estimate for this task" ); }
QString ToolTip::nodeRunningAccount() { return xi18nc( "@info:tooltip", "Account for running costs" ); }
QString ToolTip::nodeStartupAccount() { return xi18nc( "@info:tooltip", "Account for cost incurred at startup of the task" ); }
QString ToolTip::nodeStartupCost() { return xi18nc( "@info:tooltip", "The cost incurred at startup of the task" ); }
QString ToolTip::nodeShutdownAccount() { return xi18nc( "@info:tooltip", "Account for cost incurred at shutdown of the task" ); }
QString ToolTip::nodeShutdownCost() { return xi18nc( "@info:tooltip", "The cost incurred at shutdown of the task" ); }
QString ToolTip::nodeStartTime() { return xi18nc( "@info:tooltip", "Planned start time" ); }
QString ToolTip::nodeEndTime() { return xi18nc( "@info:tooltip", "Planned finish time" ); }
QString ToolTip::nodeEarlyStart() { return xi18nc( "@info:tooltip", "Earliest start time allowed by dependencies" ); }
QString ToolTip::nodeEarlyFinish() { return xi18nc( "@info:tooltip", "Earliest finish time allowed by dependencies" ); }
QString ToolTip::nodeLateStart() { return xi18nc( "@info:tooltip", "Latest start time allowed by dependencies" ); }
QString ToolTip::nodeLateFinish() { return xi18nc( "@info:tooltip", "Latest finish time allowed by dependencies" ); }
QString ToolTip::nodeDuration() { return xi18nc( "@info:tooltip", "The planned duration" ); }
QString ToolTip::nodeVarianceDuration() { return xi18nc( "@info:tooltip", "The variance of the duration" ); }
QString ToolTip::nodeOptimisticDuration() { return xi18nc( "@info:tooltip", "The optimistic duration" ); }
QString ToolTip::nodePessimisticDuration() { return xi18nc( "@info:tooltip", "The pessimistic duration" ); }
QString ToolTip::nodePositiveFloat() { return xi18nc( "@info:tooltip", "The duration by which a tasks start can be delayed without affecting the project completion time" ); }
QString ToolTip::nodeNegativeFloat() { return xi18nc( "@info:tooltip", "The duration by which the duration of a task or path has to be reduced in order to fulfill a timing constraint" ); }
QString WhatsThis::nodeNegativeFloat() { return xi18nc( "@info:whatsthis", "Negative float is the duration by which the duration of a task or path has to be reduced in order to fulfill a timing constraint." ); }
QString ToolTip::nodeFreeFloat() { return xi18nc( "@info:tooltip", "The duration by which a task can be delayed or extended without affecting the start of any succeeding task" ); }
QString WhatsThis::nodeFreeFloat() { return xi18nc( "@info:whatsthis", "Free float is the duration by which a task can be delayed or extended without affecting the start of any succeeding task." ); }
QString ToolTip::nodeStartFloat() { return xi18nc( "@info:tooltip", "The duration from Early Start to Late Start" ); }
QString WhatsThis::nodeStartFloat() { return xi18nc( "@info:whatsthis", "Start float is the duration from Early Start to Late Start." ); }
QString ToolTip::nodeFinishFloat() { return xi18nc( "@info:tooltip", "The duration from Early Finish to Late Finish" ); }
QString WhatsThis::nodeFinishFloat() { return xi18nc( "@info:whatsthis", "Finish float is the duration from Early Finish to Late Finish." ); }
QString ToolTip::nodeAssignment() { return xi18nc( "@info:tooltip", "The resources assigned to the task" ); }
QString ToolTip::nodeStatus() { return xi18nc( "@info:tooltip", "Task status" ); }
QString ToolTip::nodeCompletion() { return xi18nc( "@info:tooltip", "Task completion" ); }
QString ToolTip::nodePlannedEffortTo() { return xi18nc( "@info:tooltip", "Planned effort" ); }
QString ToolTip::nodeActualEffortTo() { return xi18nc( "@info:tooltip", "Actual effort" ); }
QString ToolTip::nodeRemainingEffort() { return xi18nc( "@info:tooltip", "Remaining effort" ); }
QString ToolTip::nodePlannedCostTo() { return xi18nc( "@info:tooltip", "Planned cost" ); }
QString ToolTip::nodeActualCostTo() { return xi18nc( "@info:tooltip", "Actual cost" ); }
QString ToolTip::completionStartedTime() { return xi18nc( "@info:tooltip", "Time when task was actually started" ); }
QString ToolTip::completionStarted() { return xi18nc( "@info:tooltip", "Shows if the task is started" ); }
QString ToolTip::completionFinishedTime() { return xi18nc( "@info:tooltip", "Time when task was actually finished" ); }
QString ToolTip::completionFinished() { return xi18nc( "@info:tooltip", "Shows if the task is finished" ); }
QString ToolTip::completionStatusNote() { return xi18nc( "@info:tooltip", "Status Note" ); }
QString ToolTip::estimateExpected() { return xi18nc( "@info:tooltip", "Calculated expected estimate" ); }
QString ToolTip::estimateVariance() { return xi18nc( "@info:tooltip", "Calculated estimate variance" ); }
QString ToolTip::estimateOptimistic() { return xi18nc( "@info:tooltip", "Optimistic estimate" ); }
QString ToolTip::estimatePessimistic() { return xi18nc( "@info:tooltip", "Pessimistic estimate" ); }
QString ToolTip::estimateType() { return xi18nc( "@info:tooltip", "Type of estimate" ); }
QString ToolTip::estimateCalendar() { return xi18nc( "@info:tooltip", "The calendar used when estimate type is Duration" ); }
QString ToolTip::estimate() { return xi18nc( "@info:tooltip", "The most likely estimate" ); }
QString ToolTip::optimisticRatio() { return xi18nc( "@info:tooltip", "Optimistic estimate" ); }
QString ToolTip::pessimisticRatio() { return xi18nc( "@info:tooltip", "Pessimistic estimate" ); }
QString ToolTip::riskType() { return xi18nc( "@info:tooltip", "Type of risk" ); }
QString ToolTip::nodeSchedulingStatus() { return xi18nc( "@info:tooltip", "Shows the tasks scheduling status" ); }
QString ToolTip::nodeNotScheduled() { return xi18nc( "@info:tooltip", "The task has not been scheduled" ); }
QString ToolTip::nodeAssignmentMissing() { return i18n( "An effort has been estimated, but no resource has been assigned" ); }
QString ToolTip::nodeResourceOverbooked() { return xi18nc( "@info:tooltip", "A resource assigned to this task is overbooked" ); }
QString ToolTip::nodeResourceUnavailable() { return xi18nc( "@info:tooltip", "A resource assigned to this task is not available" ); }
QString ToolTip::nodeConstraintsError() { return xi18nc( "@info:tooltip", "A timing constraint could not be met" ); }
QString ToolTip::nodeEffortNotMet() { return xi18nc( "@info:tooltip", "The assigned resource could not meet the estimated effort" ); }
QString ToolTip::nodeSchedulingError() { return xi18nc( "@info:tooltip", "A scheduling error occurred" ); }
QString ToolTip::nodeBCWS() { return xi18nc( "@info:tooltip", "Budgeted Cost of Work Scheduled" ); }
QString ToolTip::nodeBCWP() { return xi18nc( "@info:tooltip", "Budgeted Cost of Work Performed" ); }
QString ToolTip::nodeACWP() { return xi18nc( "@info:tooltip", "Actual Cost of Work Performed" ); }
QString ToolTip::nodePerformanceIndex() { return i18n( "Schedule performance index (BCWP/BCWS)" ); }
QString ToolTip::resourceName() { return xi18nc( "@info:tooltip", "The name of the resource or resource group" ); }
QString ToolTip::resourceScope() { return xi18nc( "@info:tooltip", "The scope of the resource or resource group" ); }
QString ToolTip::resourceType() { return xi18nc( "@info:tooltip", "The type of the resource or resource group" ); }
QString ToolTip::resourceInitials() { return xi18nc( "@info:tooltip", "The initials of the resource" ); }
QString ToolTip::resourceEMail() { return xi18nc( "@info:tooltip", "The e-mail address of the resource" ); }
QString ToolTip::resourceCalendar() { return xi18nc( "@info:tooltip", "The calendar defines when the resource is working" ); }
QString ToolTip::resourceUnits() { return xi18nc( "@info:tooltip", "The maximum load that can be assigned" ); }
QString ToolTip::resourceAvailableFrom() { return xi18nc( "@info:tooltip", "Defines when the resource is available to the project" ); }
QString ToolTip::resourceAvailableUntil() { return xi18nc( "@info:tooltip", "Defines when the resource is available to the project" ); }
QString ToolTip::resourceNormalRate() { return xi18nc( "@info:tooltip", "The cost pr hour, normal hours" ); }
QString ToolTip::resourceOvertimeRate() { return xi18nc( "@info:tooltip", "The cost pr hour, overtime hours" ); }
QString ToolTip::resourceFixedCost() { return xi18nc( "@info:tooltip", "The fixed cost" ); }
QString ToolTip::resourceAccount() { return xi18nc( "@info:tooltip", "The account where the resource cost is accumulated" ); }
QString ToolTip::accountName() { return xi18nc( "@info:tooltip", "The name of the account" ); }
QString ToolTip::accountDescription() { return xi18nc( "@info:tooltip", "The description of the account" ); }
QString ToolTip::scheduleName() { return xi18nc( "@info:tooltip", "The name of the schedule" ); }
QString ToolTip::scheduleState() { return xi18nc( "@info:tooltip", "The schedules state" ); }
QString ToolTip::scheduleOverbooking() { return xi18nc( "@info:tooltip", "Controls resource overbooking when scheduling" ); }
QString WhatsThis::scheduleOverbooking() { return xi18nc( "@info:whatsthis",
"<para>Controls resource overbooking when scheduling.</para>"
"<para>If overbooking is allowed, a resource may be booked (on working days) to work more than it is available. This can happen if the resource is allocated to multiple tasks or are booked on other projects.</para>"
"<para>If overbooking is to be avoided, resources will not be booked more than they are available. On resource conflict, tasks will be delayed until the resource is available.</para>"
);
}
QString ToolTip::scheduleDistribution() { return xi18nc( "@info:tooltip", "The distribution to be used during scheduling" ); }
QString WhatsThis::scheduleDistribution() { return xi18nc( "@info:whatsthis",
"<para>The distribution to be used during scheduling</para>"
"<para>If distribution is 'None', the tasks estimate is used as is during scheduling.</para>"
"<para>If distribution is 'PERT', the estimate used is calculated based on the entered optimistic- (O), pessimistic-(P) and most likely (M) estimate. The formula used for this is (O + 4 * M + P) / 6.</para>"
);
}
QString ToolTip::scheduleCalculate() { return xi18nc( "@info:tooltip", "Defines the schedules to be calculated" ); }
QString ToolTip::scheduleStart() { return xi18nc( "@info:tooltip", "The scheduled start time" ); }
QString ToolTip::scheduleFinish() { return xi18nc( "@info:tooltip", "The scheduled finish time" ); }
QString ToolTip::schedulingDirection() { return xi18nc( "@info:tooltip", "The scheduling direction" ); }
QString WhatsThis::schedulingDirection() { return xi18nc( "@info:whatsthis",
"<para>The scheduling direction.</para>"
"<para>If direction is Forward, the project is scheduled starting at the projects earliest start time specified in the main project dialog.</para>"
"<para>If direction is Backward, the project is scheduled starting at the projects latest finish time specified in the main project dialog.</para>"
);
}
QString ToolTip::scheduleScheduler() { return xi18nc( "@info:tooltip", "The scheduler used for calculating the project schedule" ); }
QString WhatsThis::scheduleScheduler() { return xi18nc( "@info:whatsthis",
"<para>The scheduler used for calculating the project schedule.</para>"
"<para>The default built-in scheduler is the Network Scheduler.</para>"
"<para>Other schedulers presently available is RCPS if libRCPS is installed on your system. "
"RCPS is a genetics based resource constrained project scheduler.</para>"
);
}
QString ToolTip::scheduleGranularity() { return xi18nc( "@info:tooltip", "The granularity used when calculating the project schedule" ); }
QString ToolTip::documentUrl() { return xi18nc( "@info:tooltip", "The url of the document" ); }
QString ToolTip::documentType() { return xi18nc( "@info:tooltip", "The type of the document" ); }
QString ToolTip::documentStatus() { return xi18nc( "@info:tooltip", "The status of the document" ); }
QString ToolTip::documentSendAs() { return xi18nc( "@info:tooltip", "Defines how this document is sent" ); }
QString ToolTip::calendarName() { return xi18nc( "@info:tooltip", "The name of the calendar" ); }
QString ToolTip::calendarTimeZone() { return xi18nc( "@info:tooltip", "The timezone of the calendar" ); }
QString ToolTip::relationParent() { return xi18nc( "@info:tooltip", "The name of the required task" ); }
QString ToolTip::relationChild() { return xi18nc( "@info:tooltip", "The name of the dependent task" ); }
QString ToolTip::relationType() { return xi18nc( "@info:tooltip", "The type of relation" ); }
QString ToolTip::relationLag() { return xi18nc( "@info:tooltip", "The relations time lag" ); }
// Work around string freeze
KUndo2MagicString UndoText::removeDocument() { return kundo2_i18n( "Remove document" ); }
} //namespace KPlato
diff --git a/src/libs/models/kptdocumentmodel.cpp b/src/libs/models/kptdocumentmodel.cpp
index fc3b1ffd..34222581 100644
--- a/src/libs/models/kptdocumentmodel.cpp
+++ b/src/libs/models/kptdocumentmodel.cpp
@@ -1,579 +1,580 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptdocumentmodel.h"
#include "kptdocuments.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QMimeData>
namespace KPlato
{
QVariant DocumentModel::url( const Document *doc, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return doc->url().url();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant DocumentModel::name( const Document *doc, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return doc->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool DocumentModel::setName( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
doc->setName( value.toString() );
return true;
default:
break;
}
return false;
}
QVariant DocumentModel::type( const Document *doc, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return Document::typeToString( doc->type(), true );
case Role::EnumList:
return Document::typeList( true );
case Qt::EditRole:
case Role::EnumListValue:
return (int)doc->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
default:
break;
}
return QVariant();
}
bool DocumentModel::setType( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
doc->setType( static_cast<Document::Type>( value.toInt() ) );
return true;
default:
break;
}
return false;
}
QVariant DocumentModel::status( const Document *doc, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole: {
return doc->status();
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant DocumentModel::sendAs( const Document *doc, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return Document::sendAsToString( doc->sendAs(), true );
case Role::EnumList:
return Document::sendAsList( true );
case Qt::EditRole:
case Role::EnumListValue:
return (int)doc->sendAs();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
default:
break;
}
return QVariant();
}
bool DocumentModel::setSendAs( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
doc->setSendAs( static_cast<Document::SendAs>( value.toInt() ) );
return true;
default:
break;
}
return false;
}
QVariant DocumentModel::data( const Document *doc, int property, int role ) const
{
QVariant result;
switch ( property ) {
case Property_Url: result = url( doc, role ); break;
case Property_Name: result = name( doc, role ); break;
case Property_Type: result = type( doc, role ); break;
case Property_SendAs: result = sendAs( doc, role ); break;
case Property_Status: result = status( doc, role ); break;
default:
//debugPlan<<"Invalid property number: "<<property;
return result;
}
return result;
}
int DocumentModel::propertyCount()
{
return 5;
}
bool DocumentModel::setData( Document *doc, int property, const QVariant & /*value*/, int role )
{
Q_UNUSED(doc);
Q_UNUSED(property);
Q_UNUSED(role);
switch ( property ) {
//case 0: result = url( doc, role ); break;
//case 1: return setType( doc, value, role );
//case 2: result = status( doc, role ); break;
default:
//debugPlan<<"Invalid property number: "<<property;
break;
}
return false;
}
QVariant DocumentModel::headerData( int section, int role )
{
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case Property_Url: return i18n( "Url" );
case Property_Name: return i18n( "Name" );
case Property_Type: return i18n( "Type" );
case Property_SendAs: return i18n( "Send As" );
case Property_Status: return i18n( "Status" );
default: return QVariant();
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case Property_Url: return ToolTip::documentUrl();
case Property_Name: return QVariant(); //TODO
case Property_Type: return ToolTip::documentType();
case Property_SendAs: return ToolTip::documentSendAs();
case Property_Status: return ToolTip::documentStatus();
default: return QVariant();
}
}
return QVariant();
}
//----------------------------
DocumentItemModel::DocumentItemModel( QObject *parent )
: ItemModelBase( parent ),
m_documents( 0 )
{
}
DocumentItemModel::~DocumentItemModel()
{
}
void DocumentItemModel::slotDocumentToBeInserted( Documents *parent, int row )
{
if ( parent == m_documents ) {
beginInsertRows( QModelIndex(), row, row );
}
}
void DocumentItemModel::slotDocumentInserted( Document *doc )
{
if ( m_documents->contains( doc ) ) {
endInsertRows();
}
}
void DocumentItemModel::slotDocumentToBeRemoved( Document *doc )
{
if ( m_documents->contains( doc ) ) {
int row = m_documents->indexOf( doc );
beginRemoveRows( QModelIndex(), row, row );
}
}
void DocumentItemModel::slotDocumentRemoved( Document *doc )
{
Q_UNUSED(doc);
//FIXME
endRemoveRows();
}
void DocumentItemModel::setDocuments( Documents *docs )
{
beginResetModel();
//debugPlan<<m_documents<<docs;
if ( m_documents ) {
}
m_documents = docs;
if ( m_documents ) {
}
endResetModel();
}
Documents *DocumentItemModel::documents() const
{
return m_documents;
}
Qt::ItemFlags DocumentItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( !index.isValid() ) {
if ( m_readWrite ) {
flags |= Qt::ItemIsDropEnabled;
}
return flags;
}
//debugPlan<<index<<m_readWrite;
if ( m_readWrite ) {
flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
switch ( index.column() ) {
case DocumentModel::Property_Url: // url
flags &= ~Qt::ItemIsEditable; // wee need a full path
break;
case DocumentModel::Property_Name: // name
flags |= Qt::ItemIsEditable;
break;
case DocumentModel::Property_Type: // type
flags |= Qt::ItemIsEditable;
break;
case DocumentModel::Property_SendAs: // sendAs
flags |= Qt::ItemIsEditable;
break;
case DocumentModel::Property_Status: // status
flags &= ~Qt::ItemIsEditable;
break;
default:
flags &= ~Qt::ItemIsEditable;
}
}
return flags;
}
QModelIndex DocumentItemModel::parent( const QModelIndex &/*index*/ ) const
{
return QModelIndex();
}
QModelIndex DocumentItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return QModelIndex();
}
if ( m_documents == 0 || column < 0 || column >= columnCount() || row < 0 ) {
//debugPlan<<"No index for"<<row<<","<<column;
return QModelIndex();
}
if ( row >= m_documents->count() ) {
return QModelIndex();
}
return createIndex(row, column );
}
QModelIndex DocumentItemModel::index( const Document *doc ) const
{
if ( m_documents == 0 || ! doc->isValid() ) {
return QModelIndex();
}
if ( ! m_documents->contains( doc ) ) {
return QModelIndex();
}
return createIndex( m_documents->indexOf( doc ), 0 );
}
bool DocumentItemModel::setUrl( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( QUrl( value.toString() ) == doc->url() ) {
return false;
}
//m_part->addCommand( new DocumentModifyUrlCmd( *doc, value.toString(), "Modify Document Url" ) );
return true;
}
return false;
}
bool DocumentItemModel::setName( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
//m_part->addCommand( new DocumentModifyTypeCmd( *doc, value.toString(), "Modify Document Type" ) );
return m_model.setName( doc, value, role );
}
return false;
}
bool DocumentItemModel::setType( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toInt() == doc->type() ) {
return false;
}
m_model.setType( doc, value, role );
//m_part->addCommand( new DocumentModifyTypeCmd( *doc, value.toString(), "Modify Document Type" ) );
return true;
}
return false;
}
bool DocumentItemModel::setSendAs( Document *doc, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toInt() == doc->sendAs() ) {
return false;
}
m_model.setSendAs( doc, value, role );
//m_part->addCommand( new DocumentModifyTypeCmd( *doc, value.toString(), "Modify Document Type" ) );
return true;
}
return false;
}
QVariant DocumentItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
const Document *doc = document( index );
if ( doc ) {
result = m_model.data( doc, index.column(), role );
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return result;
}
bool DocumentItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags(index) & Qt::ItemIsEditable ) == 0 || role != Qt::EditRole ) {
return false;
}
bool result = false;
Document *doc = document( index );
switch (index.column()) {
case DocumentModel::Property_Url:
result = setUrl( doc, value, role );
break;
case DocumentModel::Property_Name:
result = setName( doc, value, role );
break;
case DocumentModel::Property_Type:
result = setType( doc, value, role );
break;
case DocumentModel::Property_SendAs:
result = setSendAs( doc, value, role );
break;
default:
qWarning("data: invalid display value column %d", index.column());
break;
}
if ( result ) {
emit dataChanged( index, index );
}
return result;
}
QVariant DocumentItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
return m_model.headerData( section, role );
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case DocumentModel::Property_Type: return Qt::AlignCenter;
case DocumentModel::Property_SendAs: return Qt::AlignCenter;
default: return QVariant();
}
}
}
if ( role == Qt::ToolTipRole ) {
return DocumentModel::headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QAbstractItemDelegate *DocumentItemModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
//case 0: return new KUrlDelegate( parent ); //???????
case DocumentModel::Property_Type: { debugPlan<< column; return new EnumDelegate( parent ); }
case DocumentModel::Property_SendAs: { debugPlan<< column; return new EnumDelegate( parent ); }
default: break;
}
return 0;
}
int DocumentItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
//debugPlan<<m_model.propertyCount();
return m_model.propertyCount();
}
int DocumentItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_documents == 0 || parent.isValid() ) {
//debugPlan<<parent;
return 0;
}
//debugPlan<<parent<<": "<<m_documents->count();
return m_documents->count();
}
Qt::DropActions DocumentItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList DocumentItemModel::mimeTypes() const
{
return QStringList() << "application/x-vnd.kde.plan.documentitemmodel.internal";
}
QMimeData *DocumentItemModel::mimeData( const QModelIndexList & indexes ) const
{
QMimeData *m = new QMimeData();
QByteArray encodedData;
//QDataStream stream(&encodedData, QIODevice::WriteOnly);
//QList<int> rows;
foreach (const QModelIndex &index, indexes) {
Q_UNUSED(index);
m->setData("application/x-vnd.kde.plan.documentitemmodel.internal", encodedData);
}
return m;
}
bool DocumentItemModel::dropAllowed( const QModelIndex &index, int dropIndicatorPosition, const QMimeData *data )
{
Q_UNUSED(index);
Q_UNUSED(dropIndicatorPosition);
Q_UNUSED(data);
//debugPlan;
return true;
}
bool DocumentItemModel::dropAllowed( Document *on, const QMimeData *data )
{
Q_UNUSED(on)
if ( !data->hasFormat("application/x-vnd.kde.plan.documentitemmodel.internal") ) {
return false;
}
return true;
}
bool DocumentItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent )
{
Q_UNUSED(row);
Q_UNUSED(parent);
//debugPlan<<action;
if (action == Qt::IgnoreAction) {
return true;
}
if ( !data->hasFormat( "application/x-vnd.kde.plan.documentitemmodel.internal" ) ) {
return false;
}
return false;
}
Document *DocumentItemModel::document( const QModelIndex &index ) const
{
if ( m_documents == 0 ) {
return 0;
}
return m_documents->value( index.row() );
}
void DocumentItemModel::slotDocumentChanged( Document *doc )
{
if ( m_documents == 0 ) {
return;
}
int row = m_documents->indexOf( doc );
if ( row == -1 ) {
return;
}
emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount() - 1 ) );
}
QModelIndex DocumentItemModel::insertDocument( Document *doc, Document *after )
{
Q_UNUSED(after);
// m_part->addCommand( new DocumentAddCmd( doc, after, kundo2_i18n( "Add Document") ) );
int row = m_documents->indexOf( doc );
if ( row == -1 ) {
return QModelIndex();
}
return createIndex( row, 0 );
}
} //namespace KPlato
diff --git a/src/libs/models/kptdurationspinbox.cpp b/src/libs/models/kptdurationspinbox.cpp
index b8b24945..01bb22f3 100644
--- a/src/libs/models/kptdurationspinbox.cpp
+++ b/src/libs/models/kptdurationspinbox.cpp
@@ -1,265 +1,266 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
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 <kptdurationspinbox.h>
#include "kptnode.h"
#include <QLineEdit>
#include <QDoubleValidator>
#include <QKeyEvent>
#include <QLocale>
#include <math.h>
#include <limits.h>
namespace KPlato
{
DurationSpinBox::DurationSpinBox(QWidget *parent)
: QDoubleSpinBox(parent),
m_unit( Duration::Unit_d ),
m_minunit( Duration::Unit_h ),
m_maxunit( Duration::Unit_Y )
{
setUnit( Duration::Unit_h );
setMaximum(140737488355328.0); //Hmmmm
connect( lineEdit(), &QLineEdit::textChanged, this, &DurationSpinBox::editorTextChanged );
}
void DurationSpinBox::setUnit( Duration::Unit unit )
{
if ( unit < m_maxunit ) {
m_maxunit = unit;
} else if ( unit > m_minunit ) {
m_minunit = unit;
}
m_unit = unit;
setValue( value() );
}
void DurationSpinBox::setMaximumUnit( Duration::Unit unit )
{
//NOTE Year = 0, Milliseconds = 7 !!!
m_maxunit = unit;
if ( m_minunit < unit ) {
m_minunit = unit;
}
if ( m_unit < unit ) {
setUnit( unit );
emit unitChanged( m_unit );
}
}
void DurationSpinBox::setMinimumUnit( Duration::Unit unit )
{
//NOTE Year = 0, Milliseconds = 7 !!!
m_minunit = unit;
if ( m_maxunit > unit ) {
m_maxunit = unit;
}
if ( m_unit > unit ) {
setUnit( unit );
emit unitChanged( m_unit );
}
}
void DurationSpinBox::stepUnitUp()
{
//debugPlan<<m_unit<<">"<<m_maxunit;
if ( m_unit > m_maxunit ) {
setUnit( static_cast<Duration::Unit>(m_unit - 1) );
// line may change length, make sure cursor stays within unit
lineEdit()->setCursorPosition( lineEdit()->displayText().length() - suffix().length() );
emit unitChanged( m_unit );
}
}
void DurationSpinBox::stepUnitDown()
{
//debugPlan<<m_unit<<"<"<<m_minunit;
if ( m_unit < m_minunit ) {
setUnit( static_cast<Duration::Unit>(m_unit + 1) );
// line may change length, make sure cursor stays within unit
lineEdit()->setCursorPosition( lineEdit()->displayText().length() - suffix().length() );
emit unitChanged( m_unit );
}
}
void DurationSpinBox::stepBy( int steps )
{
//debugPlan<<steps;
int cpos = lineEdit()->cursorPosition();
if ( isOnUnit() ) {
// we are in unit
if ( steps > 0 ) {
stepUnitUp();
} else if ( steps < 0 ) {
stepUnitDown();
}
lineEdit()->setCursorPosition( cpos );
return;
}
QDoubleSpinBox::stepBy( steps );
// QDoubleSpinBox selects the whole text and the cursor might end up at the end (in the unit field)
lineEdit()->setCursorPosition( cpos ); // also deselects
}
QAbstractSpinBox::StepEnabled DurationSpinBox::stepEnabled () const
{
if ( isOnUnit() ) {
if ( m_unit >= m_minunit ) {
//debugPlan<<"inside unit, up"<<m_unit<<m_minunit<<m_maxunit;
return QAbstractSpinBox::StepUpEnabled;
}
if ( m_unit <= m_maxunit ) {
//debugPlan<<"inside unit, down"<<m_unit<<m_minunit<<m_maxunit;
return QAbstractSpinBox::StepDownEnabled;
}
//debugPlan<<"inside unit, up|down"<<m_unit<<m_minunit<<m_maxunit;
return QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
}
return QDoubleSpinBox::stepEnabled();
}
bool DurationSpinBox::isOnUnit() const
{
int pos = lineEdit()->cursorPosition();
return ( pos <= text().size() - suffix().size() ) &&
( pos > text().size() - suffix().size() - Duration::unitToString( m_unit, true ).size() );
}
void DurationSpinBox::keyPressEvent( QKeyEvent * event )
{
//debugPlan<<lineEdit()->cursorPosition()<<","<<(text().size() - Duration::unitToString( m_unit, true ).size())<<""<<event->text().isEmpty();
if ( isOnUnit() ) {
// we are in unit
switch (event->key()) {
case Qt::Key_Up:
event->accept();
stepBy( 1 );
return;
case Qt::Key_Down:
event->accept();
stepBy( -1 );
return;
default:
break;
}
}
QDoubleSpinBox::keyPressEvent(event);
}
// handle unit, QDoubleSpinBox handles value, signals etc
void DurationSpinBox::editorTextChanged( const QString &text ) {
//debugPlan<<text;
QString s = text;
int pos = lineEdit()->cursorPosition();
if ( validate( s, pos ) == QValidator::Acceptable ) {
s = extractUnit( s );
if ( ! s.isEmpty() ) {
updateUnit( (Duration::Unit)Duration::unitList( true ).indexOf( s ) );
}
}
}
double DurationSpinBox::valueFromText( const QString & text ) const
{
QString s = extractValue( text );
bool ok = false;
double v = QLocale().toDouble( s, &ok );
if ( ! ok ) {
v = QDoubleSpinBox::valueFromText( s );
}
return v;
}
QString DurationSpinBox::textFromValue ( double value ) const
{
QString s = QLocale().toString( qMin( qMax( minimum(), value ), maximum() ), 'f', decimals() );
s += Duration::unitToString( m_unit, true );
//debugPlan<<2<<value<<s;
return s;
}
QValidator::State DurationSpinBox::validate ( QString & input, int & pos ) const
{
//debugPlan<<input;
QDoubleValidator validator( minimum(), maximum(), decimals(), 0 );
if ( input.isEmpty() ) {
return validator.validate ( input, pos );
}
QString s = extractUnit( input );
if ( s.isEmpty() ) {
return validator.validate ( input, pos );
}
int idx = Duration::unitList( true ).indexOf( s );
if ( idx < m_maxunit || idx > m_minunit ) {
return QValidator::Invalid;
}
s = extractValue( input );
int p = 0;
return validator.validate ( s, p ); // pos doesn't matter
}
QString DurationSpinBox::extractUnit ( const QString &text ) const
{
//debugPlan<<text;
QString s;
for ( int i = text.length() - 1; i >= 0; --i ) {
QChar c = text[ i ];
if ( ! c.isLetter() ) {
break;
}
s.prepend( c );
}
if ( Duration::unitList( true ).contains( s ) ) {
return s;
}
return QString();
}
QString DurationSpinBox::extractValue ( const QString &text ) const
{
//debugPlan<<text;
QString s = extractUnit( text );
if ( Duration::unitList( true ).contains( s ) ) {
return text.left( text.length() - s.length() );
}
return text;
}
void DurationSpinBox::updateUnit( Duration::Unit unit )
{
if ( unit < m_maxunit ) {
m_unit = m_maxunit;
} else if ( unit > m_minunit ) {
m_unit = m_minunit;
}
if ( m_unit != unit ) {
m_unit = unit;
emit unitChanged( unit );
}
}
} //namespace KPlato
diff --git a/src/libs/models/kptflatproxymodel.cpp b/src/libs/models/kptflatproxymodel.cpp
index 9a6906f6..0560ff25 100644
--- a/src/libs/models/kptflatproxymodel.cpp
+++ b/src/libs/models/kptflatproxymodel.cpp
@@ -1,457 +1,458 @@
/* This file is part of the KDE project
Copyright (C) 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptflatproxymodel.h"
#include "kptglobal.h"
#include <KLocalizedString>
#include <QModelIndex>
#include <QPersistentModelIndex>
#include <QItemSelection>
#include "kptdebug.h"
namespace KPlato
{
FlatProxyModel::FlatProxyModel(QObject *parent)
: QAbstractProxyModel( parent )
{
}
void FlatProxyModel::sourceModelDestroyed()
{
m_sourceIndexList.clear();
}
void FlatProxyModel::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right)
{
emit dataChanged( mapFromSource( source_top_left ), mapFromSource( source_bottom_right ) );
}
void FlatProxyModel::sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end)
{
emit headerDataChanged(orientation, start, end);
}
void FlatProxyModel::sourceReset()
{
beginResetModel();
initiateMaps();
endResetModel();
}
void FlatProxyModel::sourceLayoutAboutToBeChanged()
{
emit layoutAboutToBeChanged();
}
void FlatProxyModel::sourceLayoutChanged()
{
initiateMaps();
emit layoutChanged();
}
void FlatProxyModel::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
{
Q_UNUSED(source_parent);
Q_UNUSED(start);
Q_UNUSED(end);
beginResetModel();
}
void FlatProxyModel::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
{
Q_UNUSED(source_parent);
Q_UNUSED(start);
Q_UNUSED(end);
initiateMaps();
endResetModel();
}
void FlatProxyModel::sourceRowsAboutToBeRemoved( const QModelIndex &source_parent, int start, int end )
{
Q_UNUSED(source_parent);
Q_UNUSED(start);
Q_UNUSED(end);
beginResetModel();
}
void FlatProxyModel::sourceRowsRemoved( const QModelIndex &source_parent, int start, int end )
{
Q_UNUSED(source_parent);
Q_UNUSED(start);
Q_UNUSED(end);
initiateMaps();
endResetModel();
}
void FlatProxyModel::sourceRowsAboutToBeMoved(const QModelIndex &source_parent, int start, int end, const QModelIndex &destParent, int destStart)
{
Q_UNUSED(source_parent);
Q_UNUSED(start);
Q_UNUSED(end);
Q_UNUSED(destParent);
Q_UNUSED(destStart);
beginResetModel();
}
void FlatProxyModel::sourceRowsMoved(const QModelIndex &source_parent, int start, int end, const QModelIndex &destParent, int destStart)
{
Q_UNUSED(source_parent);
Q_UNUSED(start);
Q_UNUSED(end);
Q_UNUSED(destParent);
Q_UNUSED(destStart);
initiateMaps();
endResetModel();
}
void FlatProxyModel::setSourceModel(QAbstractItemModel *model)
{
if ( sourceModel() ) {
disconnect(sourceModel(), &QAbstractItemModel::dataChanged,
this, &FlatProxyModel::sourceDataChanged);
disconnect(sourceModel(), &QAbstractItemModel::headerDataChanged,
this, &FlatProxyModel::sourceHeaderDataChanged);
disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted,
this, &FlatProxyModel::sourceRowsAboutToBeInserted);
disconnect(sourceModel(), &QAbstractItemModel::rowsInserted,
this, &FlatProxyModel::sourceRowsInserted);
disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved,
this, &FlatProxyModel::sourceRowsAboutToBeRemoved);
disconnect(sourceModel(), &QAbstractItemModel::rowsRemoved,
this, &FlatProxyModel::sourceRowsRemoved);
disconnect(sourceModel(), &QAbstractItemModel::layoutAboutToBeChanged,
this, &FlatProxyModel::sourceLayoutAboutToBeChanged);
disconnect(sourceModel(), &QAbstractItemModel::layoutChanged,
this, &FlatProxyModel::sourceLayoutChanged);
disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &FlatProxyModel::sourceReset);
connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeMoved,
this, &FlatProxyModel::sourceRowsAboutToBeMoved);
connect(sourceModel(), &QAbstractItemModel::rowsMoved,
this, &FlatProxyModel::sourceRowsMoved);
}
QAbstractProxyModel::setSourceModel(model);
connect(sourceModel(), &QAbstractItemModel::dataChanged,
this, &FlatProxyModel::sourceDataChanged);
connect(sourceModel(), &QAbstractItemModel::headerDataChanged,
this, &FlatProxyModel::sourceHeaderDataChanged);
connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted,
this, &FlatProxyModel::sourceRowsAboutToBeInserted);
connect(sourceModel(), &QAbstractItemModel::rowsInserted,
this, &FlatProxyModel::sourceRowsInserted);
connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved,
this, &FlatProxyModel::sourceRowsAboutToBeRemoved);
connect(sourceModel(), &QAbstractItemModel::rowsRemoved,
this, &FlatProxyModel::sourceRowsRemoved);
connect(sourceModel(), &QAbstractItemModel::layoutAboutToBeChanged,
this, &FlatProxyModel::sourceLayoutAboutToBeChanged);
connect(sourceModel(), &QAbstractItemModel::layoutChanged,
this, &FlatProxyModel::sourceLayoutChanged);
connect(sourceModel(), &QAbstractItemModel::modelReset, this, &FlatProxyModel::sourceReset);
connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeMoved,
this, &FlatProxyModel::sourceRowsAboutToBeMoved);
connect(sourceModel(), &QAbstractItemModel::rowsMoved,
this, &FlatProxyModel::sourceRowsMoved);
beginResetModel();
initiateMaps();
endResetModel();
}
QModelIndex FlatProxyModel::index(int row, int column, const QModelIndex &parent) const
{
if ( parent.isValid() ) {
return QModelIndex();
}
return createIndex( row, column );
}
QModelIndex FlatProxyModel::parent(const QModelIndex &child) const
{
Q_UNUSED(child);
return QModelIndex();
}
int FlatProxyModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_sourceIndexList.count();
}
int FlatProxyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
if ( sourceModel() == 0 ) {
return 0;
}
return sourceModel()->columnCount() + 1;
}
bool FlatProxyModel::hasChildren(const QModelIndex &parent) const
{
return rowCount( parent ) > 0;
}
QVariant FlatProxyModel::data(const QModelIndex &index, int role) const
{
if ( sourceModel() == 0 || !index.isValid()) {
debugPlan<<"No source model || invalid index";
return QVariant();
}
QModelIndex source_index;
int col = index.column() - sourceModel()->columnCount();
if ( col < 0 ) {
source_index = mapToSource(index);
//debugPlan<<"source column"<<col<<sourceModel()->columnCount();
} else {
source_index = mapToSource( this->index( index.row(), 0 ) );
//debugPlan<<"proxy column"<<col<<sourceModel()->columnCount();
}
if ( !source_index.isValid() ) {
debugPlan<<"index valid but source index not valid:"<<index;
return QVariant();
}
QVariant r;
if ( col < 0 ) {
r = sourceModel()->data(source_index, role);
} else if ( col == 0 ) {
if ( role == Role::ColumnTag ) {
r = headerData( col, Qt::Horizontal, role );
} else {
source_index = source_index.parent();
if ( source_index.isValid() ) {
r = sourceModel()->data(source_index, role);
}
}
}
//debugPlan<<index<<r;
return r;
}
bool FlatProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if ( sourceModel() == 0 ) {
return false;
}
QModelIndex source_index = mapToSource(index);
if (index.isValid() && !source_index.isValid()) {
return false;
}
return sourceModel()->setData(source_index, value, role);
}
QVariant FlatProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if ( sourceModel() == 0 ) {
return QVariant();
}
int sec = section - sourceModel()->columnCount();
if ( sec < 0 ) {
return sourceModel()->headerData(section, orientation, role);
}
if ( sec == 0 ) {
return role == Role::ColumnTag ? "Parent" : i18n( "Parent" );
}
return QVariant();
}
bool FlatProxyModel::setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, int role)
{
if ( sourceModel() == 0 ) {
return false;
}
//TODO
return sourceModel()->setHeaderData(section, orientation, value, role);
}
QMimeData *FlatProxyModel::mimeData(const QModelIndexList &indexes) const
{
if ( sourceModel() == 0 ) {
return 0;
}
QModelIndexList source_indexes;
for (int i = 0; i < indexes.count(); ++i) {
source_indexes << mapToSource(indexes.at(i));
}
return sourceModel()->mimeData(source_indexes);
}
QStringList FlatProxyModel::mimeTypes() const
{
if ( sourceModel() == 0 ) {
return QStringList();
}
return sourceModel()->mimeTypes();
}
Qt::DropActions FlatProxyModel::supportedDropActions() const
{
if ( sourceModel() == 0 ) {
return 0;
}
return sourceModel()->supportedDropActions();
}
bool FlatProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
if ( sourceModel() == 0 ) {
return false;
}
if ((row == -1) && (column == -1))
return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent));
int source_destination_row = -1;
int source_destination_column = -1;
QModelIndex source_parent;
if (row == rowCount(parent)) {
source_parent = mapToSource(parent);
source_destination_row = sourceModel()->rowCount(source_parent);
} else {
QModelIndex proxy_index = index(row, column, parent);
QModelIndex source_index = mapToSource(proxy_index);
source_destination_row = source_index.row();
source_destination_column = source_index.column();
source_parent = source_index.parent();
}
return sourceModel()->dropMimeData(data, action, source_destination_row,
source_destination_column, source_parent);
}
bool FlatProxyModel::insertRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(count);
Q_UNUSED(parent);
return false;
}
bool FlatProxyModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(count);
Q_UNUSED(parent);
//TODO
return false;
}
/*!
Returns the source model index corresponding to the given \a
proxyIndex from the sorting filter model.
\sa mapFromSource()
*/
QModelIndex FlatProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if ( ! proxyIndex.isValid() ) {
return QModelIndex();
}
QModelIndex source_index = m_sourceIndexList.value( proxyIndex.row() );
if ( proxyIndex.column() != 0 ) {
source_index = sourceModel()->index( source_index.row(), proxyIndex.column(), source_index.parent() );
}
//debugPlan<<proxyIndex<<"->"<<source_index;
return source_index;
}
/*!
Returns the model index in the FlatProxyModel given the \a
sourceIndex from the source model.
\sa mapToSource()
*/
QModelIndex FlatProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if ( ! sourceIndex.isValid() ) {
return QModelIndex();
}
QPersistentModelIndex idx = sourceIndex;
if ( idx.column() != 0 ) {
// we only map indices with column 0
idx = sourceModel()->index( idx.row(), 0, idx.parent() );
}
QModelIndex proxy_index = index( m_sourceIndexList.indexOf( idx ), sourceIndex.column() );
//debugPlan<<sourceIndex<<"->"<<proxy_index;
Q_ASSERT(proxy_index.model() == this);
return proxy_index;
}
QItemSelection FlatProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const
{
return QAbstractProxyModel::mapSelectionToSource(proxySelection);
}
QItemSelection FlatProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const
{
return QAbstractProxyModel::mapSelectionFromSource(sourceSelection);
}
void FlatProxyModel::initiateMaps( const QModelIndex &sourceParent )
{
if ( ! sourceParent.isValid() ) {
m_sourceIndexList.clear();
m_sourceIndexMap.clear();
}
QAbstractItemModel *m = sourceModel();
if ( m == 0 ) {
debugPlan<<"No source model";
return;
}
int count = m->rowCount( sourceParent );
for ( int row = 0; row < count; ++row ) {
QPersistentModelIndex idx = m->index( row, 0, sourceParent );
//debugPlan<<"map:"<<sourceParent<<row<<idx;
if ( idx.isValid() ) { // fail safe
m_sourceIndexList.append( idx );
m_sourceIndexMap.insert( idx.parent(), idx );
initiateMaps( idx );
}
}
//debugPlan<<"source index list="<<m_sourceIndexList;
}
} // namespace KPlato
diff --git a/src/libs/models/kptitemmodelbase.cpp b/src/libs/models/kptitemmodelbase.cpp
index 21854369..d816cd1d 100644
--- a/src/libs/models/kptitemmodelbase.cpp
+++ b/src/libs/models/kptitemmodelbase.cpp
@@ -1,741 +1,742 @@
/* This file is part of the KDE project
Copyright (C) 2006 - 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptitemmodelbase.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptdurationspinbox.h"
#include "kptresourcemodel.h"
#include "kptresourceallocationmodel.h"
#include "kptdebug.h"
#include <QApplication>
#include <QComboBox>
#include <QKeyEvent>
#include <QModelIndex>
#include <QItemSelection>
#include <QStyleOptionViewItem>
#include <QTimeEdit>
#include <QPainter>
#include <QToolTip>
#include <QTreeView>
#include <QStylePainter>
#include <QMimeData>
#include <kcombobox.h>
#include <klineedit.h>
namespace KPlato
{
//--------------------------------------
bool ItemDelegate::eventFilter(QObject *object, QEvent *event)
{
QWidget *editor = ::qobject_cast<QWidget*>(object);
if (!editor) {
return false;
}
m_lastHint = Delegate::NoHint;
if (event->type() == QEvent::KeyPress) {
QKeyEvent *e = static_cast<QKeyEvent *>(event);
if ( e->modifiers() & Qt::AltModifier && e->modifiers() & Qt::ControlModifier ) {
switch ( e->key() ) {
case Qt::Key_Left:
m_lastHint = Delegate::EditLeftItem;
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::NoHint );
return true;
case Qt::Key_Right:
m_lastHint = Delegate::EditRightItem;
emit commitData(editor);
emit closeEditor( editor, QAbstractItemDelegate::NoHint );
return true;
case Qt::Key_Down:
m_lastHint = Delegate::EditDownItem;
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::NoHint );
return true;
case Qt::Key_Up:
m_lastHint = Delegate::EditUpItem;
emit commitData(editor);
emit closeEditor(editor, QAbstractItemDelegate::NoHint );
return true;
default:
break;
}
}
}
return QStyledItemDelegate::eventFilter( object, event );
}
QSize ItemDelegate::sizeHint( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
// 18 is a bit arbitrary, it gives (most?) editors a usable size
QSize s = QStyledItemDelegate::sizeHint( option, index );
return QSize( s.width(), qMax( s.height(), 18 ) );
}
//----------------------
CheckStateItemDelegate::CheckStateItemDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
bool CheckStateItemDelegate::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
{
Q_ASSERT(event);
Q_ASSERT(model);
debugPlan;
Qt::ItemFlags flags = model->flags(index);
if ( ! ( option.state & QStyle::State_Enabled ) || ! ( flags & Qt::ItemIsEnabled ) ) {
return false;
}
// make sure that we have a check state
QVariant value = index.data( Qt::EditRole );
if ( ! value.isValid() ) {
return false;
}
QStyle *style = QApplication::style();
// make sure that we have the right event type
if ( ( event->type() == QEvent::MouseButtonRelease ) || ( event->type() == QEvent::MouseButtonDblClick ) || ( event->type() == QEvent::MouseButtonPress ) ) {
QStyleOptionViewItem viewOpt( option );
initStyleOption( &viewOpt, index );
QRect checkRect = style->subElementRect( QStyle::SE_ItemViewItemDecoration, &viewOpt, 0 );
QMouseEvent *me = static_cast<QMouseEvent*>( event );
if ( me->button() != Qt::LeftButton || ! checkRect.contains( me->pos() ) ) {
return false;
}
if ( ( event->type() == QEvent::MouseButtonPress ) || ( event->type() == QEvent::MouseButtonDblClick ) ) {
return true;
}
} else if ( event->type() == QEvent::KeyPress ) {
if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space && static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select) {
return false;
}
} else {
return false;
}
Qt::CheckState state = ( static_cast<Qt::CheckState>( value.toInt() ) == Qt::Checked ? Qt::Unchecked : Qt::Checked );
return model->setData(index, state, Qt::CheckStateRole);
}
//----------------------
DateTimeCalendarDelegate::DateTimeCalendarDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *DateTimeCalendarDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QDateTimeEdit *editor = new QDateTimeEdit(parent);
editor->setCalendarPopup( true );
editor->installEventFilter(const_cast<DateTimeCalendarDelegate*>(this));
return editor;
}
void DateTimeCalendarDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QDateTime value = index.model()->data(index, Qt::EditRole).toDateTime();
QDateTimeEdit *e = static_cast<QDateTimeEdit*>(editor);
e->setDateTime( value );
}
void DateTimeCalendarDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QDateTimeEdit *e = static_cast<QDateTimeEdit*>(editor);
model->setData( index, e->dateTime(), Qt::EditRole );
}
void DateTimeCalendarDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
debugPlan<<editor<<":"<<option.rect<<","<<editor->sizeHint();
QRect r = option.rect;
//r.setHeight(r.height() 50);
editor->setGeometry(r);
}
//-----------------------------
ProgressBarDelegate::ProgressBarDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
ProgressBarDelegate::~ProgressBarDelegate()
{
}
void ProgressBarDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index ) const
{
QStyle *style;
QStyleOptionViewItem opt = option;
initStyleOption( &opt, index );
style = opt.widget ? opt.widget->style() : QApplication::style();
style->drawPrimitive( QStyle::PE_PanelItemViewItem, &opt, painter );
if ( !( opt.state & QStyle::State_Editing ) ) {
bool ok = false;
(void) index.data().toInt(&ok);
if ( ok ) {
QStyleOptionProgressBar pbOption;
pbOption.QStyleOption::operator=( option );
initStyleOptionProgressBar( &pbOption, index );
style->drawControl( QStyle::CE_ProgressBar, &pbOption, painter );
// Draw focus, copied from qt
if (opt.state & QStyle::State_HasFocus) {
painter->save();
QStyleOptionFocusRect o;
o.QStyleOption::operator=( opt );
o.rect = style->subElementRect( QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget );
o.state |= QStyle::State_KeyboardFocusChange;
o.state |= QStyle::State_Item;
QPalette::ColorGroup cg = ( opt.state & QStyle::State_Enabled )
? QPalette::Normal : QPalette::Disabled;
o.backgroundColor = opt.palette.color( cg, ( opt.state & QStyle::State_Selected )
? QPalette::Highlight : QPalette::Window );
style->drawPrimitive( QStyle::PE_FrameFocusRect, &o, painter, opt.widget );
//debugPlan<<"Focus"<<o.rect<<opt.rect<<pbOption.rect;
painter->restore();
}
} else {
EnumDelegate del;
del.paint( painter, option, index );
}
}
}
QSize ProgressBarDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
QStyleOptionViewItem opt = option;
// initStyleOption( &opt, index );
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
QStyleOptionProgressBar pbOption;
pbOption.QStyleOption::operator=( option );
initStyleOptionProgressBar( &pbOption, index );
return style->sizeFromContents( QStyle::CT_ProgressBar, &pbOption, QSize(), opt.widget );
}
void ProgressBarDelegate::initStyleOptionProgressBar( QStyleOptionProgressBar *option, const QModelIndex &index ) const
{
option->rect.adjust( 0, 1, 0, -1 );
option->minimum = 0;
int max = index.data( Role::Maximum ).toInt();
option->maximum = max > option->minimum ? max : option->minimum + 100;
option->progress = index.data().toInt();
option->text = QString::number( ( option->progress * 100 ) / ( option->maximum - option->minimum ) ) + QLatin1Char( '%' );
option->textAlignment = Qt::AlignCenter;
option->textVisible = true;
}
QWidget *ProgressBarDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const
{
Slider *slider = new Slider( parent );
slider->setRange( 0, 100 );
slider->setOrientation( Qt::Horizontal );
//debugPlan<<slider->minimumSizeHint()<<slider->minimumSize();
return slider;
}
void ProgressBarDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
{
QSlider *slider = static_cast<QSlider *>( editor );
slider->setValue( index.data( Qt::EditRole ).toInt() );
}
void ProgressBarDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
{
QSlider *slider = static_cast<QSlider *>( editor );
model->setData( index, slider->value() );
}
void ProgressBarDelegate::updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & ) const
{
editor->setGeometry( option.rect );
//debugPlan<<editor->minimumSizeHint()<<editor->minimumSize()<<editor->geometry()<<editor->size();
}
Slider::Slider( QWidget *parent )
: QSlider( parent )
{
connect( this, &QAbstractSlider::valueChanged, this, &Slider::updateTip );
}
void Slider::updateTip( int value )
{
QPoint p;
p.setY( height() / 2 );
p.setX( style()->sliderPositionFromValue ( minimum(), maximum(), value, width() ) );
QString text = QString::number( value ) + QLatin1Char( '%' );
QToolTip::showText( mapToGlobal( p ), text, this );
}
//--------------------------------------
// Hmmm, a bit hacky, but this makes it possible to use index specific editors...
SelectorDelegate::SelectorDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *SelectorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex & index ) const
{
switch ( index.model()->data( index, Role::EditorType ).toInt() ) {
case Delegate::EnumEditor: {
QComboBox *editor = new KComboBox(parent);
editor->installEventFilter(const_cast<SelectorDelegate*>(this));
return editor;
}
case Delegate::TimeEditor: {
QTimeEdit *editor = new QTimeEdit(parent);
editor->installEventFilter(const_cast<SelectorDelegate*>(this));
return editor;
}
}
return 0; // FIXME: What to do?
}
void SelectorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
switch ( index.model()->data( index, Role::EditorType ).toInt() ) {
case Delegate::EnumEditor: {
QStringList lst = index.model()->data( index, Role::EnumList ).toStringList();
int value = index.model()->data(index, Role::EnumListValue).toInt();
QComboBox *box = static_cast<QComboBox*>(editor);
box->addItems( lst );
box->setCurrentIndex( value );
return;
}
case Delegate::TimeEditor:
QTime value = index.model()->data(index, Qt::EditRole).toTime();
QTimeEdit *e = static_cast<QTimeEdit*>(editor);
e->setMinimumTime( index.model()->data( index, Role::Minimum ).toTime() );
e->setMaximumTime( index.model()->data( index, Role::Maximum ).toTime() );
e->setTime( value );
return;
}
}
void SelectorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
switch ( index.model()->data( index, Role::EditorType ).toInt() ) {
case Delegate::EnumEditor: {
QComboBox *box = static_cast<QComboBox*>(editor);
int value = box->currentIndex();
model->setData( index, value, Qt::EditRole );
return;
}
case Delegate::TimeEditor: {
QTimeEdit *e = static_cast<QTimeEdit*>(editor);
model->setData( index, e->time(), Qt::EditRole );
return;
}
}
}
void SelectorDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
QRect r = option.rect;
editor->setGeometry(r);
}
EnumDelegate::EnumDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QComboBox *editor = new KComboBox(parent);
editor->installEventFilter(const_cast<EnumDelegate*>(this));
return editor;
}
void EnumDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QStringList lst = index.model()->data( index, Role::EnumList ).toStringList();
int value = index.model()->data(index, Role::EnumListValue).toInt();
QComboBox *box = static_cast<QComboBox*>(editor);
box->addItems( lst );
box->setCurrentIndex( value );
}
void EnumDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QComboBox *box = static_cast<QComboBox*>(editor);
int value = box->currentIndex();
model->setData( index, value, Qt::EditRole );
}
void EnumDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
debugPlan<<editor<<":"<<option.rect<<","<<editor->sizeHint();
QRect r = option.rect;
//r.setHeight(r.height() 50);
editor->setGeometry(r);
}
//---------------------------
RequieredResourceDelegate::RequieredResourceDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *RequieredResourceDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &index) const
{
if ( index.data( Qt::CheckStateRole ).toInt() == Qt::Unchecked ) {
return 0;
}
TreeComboBox *editor = new TreeComboBox(parent);
editor->installEventFilter(const_cast<RequieredResourceDelegate*>(this));
ResourceItemSFModel *m = new ResourceItemSFModel( editor );
editor->setModel( m );
return editor;
}
void RequieredResourceDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
TreeComboBox *box = static_cast<TreeComboBox*>(editor);
ResourceItemSFModel *pm = static_cast<ResourceItemSFModel*>( box->model() );
ResourceItemModel *rm = qobject_cast<ResourceItemModel*>( pm->sourceModel() );
Q_ASSERT( rm );
const ResourceAllocationItemModel *model = qobject_cast<const ResourceAllocationItemModel*>( index.model() );
Q_ASSERT( model );
rm->setProject( model->project() );
pm->addFilteredResource( model->resource( index ) );
QItemSelectionModel *sm = box->view()->selectionModel();
sm->clearSelection();
foreach ( const Resource *r, model->required( index ) ) {
QModelIndex i = pm->mapFromSource( rm->index( r ) );
sm->select( i, QItemSelectionModel::Select | QItemSelectionModel::Rows );
}
box->setCurrentIndexes( sm->selectedRows() );
box->view()->expandAll();
}
void RequieredResourceDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
TreeComboBox *box = static_cast<TreeComboBox*>(editor);
QAbstractProxyModel *pm = static_cast<QAbstractProxyModel*>( box->model() );
ResourceItemModel *rm = qobject_cast<ResourceItemModel*>( pm->sourceModel() );
QList<Resource*> lst;
foreach ( const QModelIndex &i, box->currentIndexes() ) {
lst << rm->resource( pm->mapToSource( i ) );
}
ResourceAllocationItemModel *mdl = qobject_cast<ResourceAllocationItemModel*>( model );
Q_ASSERT( mdl );
mdl->setRequired( index, lst );
}
void RequieredResourceDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
debugPlan<<editor<<":"<<option.rect<<","<<editor->sizeHint();
QRect r = option.rect;
r.setWidth( qMax( 100, r.width() ) );
editor->setGeometry(r);
}
//-------------------------------
DurationSpinBoxDelegate::DurationSpinBoxDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *DurationSpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
DurationSpinBox *editor = new DurationSpinBox(parent);
editor->installEventFilter(const_cast<DurationSpinBoxDelegate*>(this));
return editor;
}
void DurationSpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
DurationSpinBox *dsb = static_cast<DurationSpinBox*>(editor);
// dsb->setScales( index.model()->data( index, Role::DurationScales ) );
dsb->setMinimumUnit( (Duration::Unit)(index.data( Role::Minimum ).toInt()) );
dsb->setMaximumUnit( (Duration::Unit)(index.data( Role::Maximum ).toInt()) );
dsb->setUnit( (Duration::Unit)( index.model()->data( index, Role::DurationUnit ).toInt() ) );
dsb->setValue( index.model()->data( index, Qt::EditRole ).toDouble() );
}
void DurationSpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
DurationSpinBox *dsb = static_cast<DurationSpinBox*>(editor);
QVariantList lst;
lst << QVariant( dsb->value() ) << QVariant( (int)( dsb->unit() ) );
model->setData( index, QVariant( lst ), Qt::EditRole );
}
void DurationSpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
debugPlan<<editor<<":"<<option.rect<<","<<editor->sizeHint();
QRect r = option.rect;
//r.setHeight(r.height() + 50);
editor->setGeometry(r);
}
//---------------------------
SpinBoxDelegate::SpinBoxDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->installEventFilter(const_cast<SpinBoxDelegate*>(this));
return editor;
}
void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
int min = index.model()->data(index, Role::Minimum).toInt();
int max = index.model()->data(index, Role::Maximum).toInt();
QSpinBox *box = static_cast<QSpinBox*>(editor);
box->setRange( min, max );
box->setValue( value );
}
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *box = static_cast<QSpinBox*>(editor);
model->setData( index, box->value(), Qt::EditRole );
}
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
debugPlan<<editor<<":"<<option.rect<<","<<editor->sizeHint();
QRect r = option.rect;
//r.setHeight(r.height() + 50);
editor->setGeometry(r);
}
//---------------------------
DoubleSpinBoxDelegate::DoubleSpinBoxDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *DoubleSpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
editor->installEventFilter(const_cast<DoubleSpinBoxDelegate*>(this));
return editor;
}
void DoubleSpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
double value = index.model()->data(index, Qt::EditRole).toDouble();
double min = 0.0;//index.model()->data(index, Role::Minimum).toInt();
double max = 24.0;//index.model()->data(index, Role::Maximum).toInt();
QDoubleSpinBox *box = static_cast<QDoubleSpinBox*>(editor);
box->setDecimals( 1 );
box->setRange( min, max );
box->setValue( value );
box->selectAll();
}
void DoubleSpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QDoubleSpinBox *box = static_cast<QDoubleSpinBox*>(editor);
model->setData( index, box->value(), Qt::EditRole );
}
void DoubleSpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
QRect r = option.rect;
editor->setGeometry(r);
}
//---------------------------
MoneyDelegate::MoneyDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *MoneyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
KLineEdit *editor = new KLineEdit(parent);
//TODO: validator
editor->installEventFilter(const_cast<MoneyDelegate*>(this));
return editor;
}
void MoneyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString value = index.model()->data(index, Qt::EditRole).toString();
KLineEdit *e = static_cast<KLineEdit*>(editor);
e->setText( value );
}
void MoneyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
KLineEdit *e = static_cast<KLineEdit*>(editor);
model->setData( index, e->text(), Qt::EditRole );
}
void MoneyDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
QRect r = option.rect;
editor->setGeometry(r);
}
//---------------------------
TimeDelegate::TimeDelegate( QObject *parent )
: ItemDelegate( parent )
{
}
QWidget *TimeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QTimeEdit *editor = new QTimeEdit(parent);
editor->installEventFilter(const_cast<TimeDelegate*>(this));
return editor;
}
void TimeDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QTime value = index.model()->data(index, Qt::EditRole).toTime();
QTimeEdit *e = static_cast<QTimeEdit*>(editor);
e->setMinimumTime( index.model()->data( index, Role::Minimum ).toTime() );
e->setMaximumTime( index.model()->data( index, Role::Maximum ).toTime() );
e->setTime( value );
}
void TimeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QTimeEdit *e = static_cast<QTimeEdit*>(editor);
model->setData( index, e->time(), Qt::EditRole );
}
void TimeDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
QRect r = option.rect;
editor->setGeometry(r);
}
//--------------------------
ItemModelBase::ItemModelBase( QObject *parent )
: QAbstractItemModel( parent ),
m_project(0),
m_manager( 0 ),
m_readWrite( false )//part->isReadWrite() )
{
}
ItemModelBase::~ItemModelBase()
{
}
void ItemModelBase::setProject( Project *project )
{
m_project = project;
}
void ItemModelBase::setScheduleManager( ScheduleManager *sm )
{
m_manager = sm;
}
void ItemModelBase::slotLayoutChanged()
{
debugPlan;
emit layoutAboutToBeChanged();
emit layoutChanged();
}
void ItemModelBase::slotLayoutToBeChanged()
{
debugPlan;
emit layoutAboutToBeChanged();
}
bool ItemModelBase::dropAllowed( const QModelIndex &index, int, const QMimeData *data )
{
if ( flags( index ) & Qt::ItemIsDropEnabled ) {
foreach ( const QString &s, data->formats() ) {
if ( mimeTypes().contains( s ) ) {
return true;
}
}
}
return false;
}
QVariant ItemModelBase::data( const QModelIndex &index, int role ) const
{
if ( index.isValid() && role == Role::ColumnTag ) {
return columnMap().key( index.column() );
}
return QVariant();
}
QVariant ItemModelBase::headerData( int section, Qt::Orientation orientation, int role ) const
{
Q_UNUSED(orientation);
if ( role == Role::ColumnTag ) {
return columnMap().key( section );
}
return QVariant();
}
bool ItemModelBase::setData( const QModelIndex &index, const QVariant &value, int role )
{
Q_UNUSED(index);
if ( role == Role::ReadWrite ) {
setReadWrite( value.toBool() );
return true;
}
return false;
}
void ItemModelBase::projectDeleted()
{
setProject(0);
}
} //namespace KPlato
diff --git a/src/libs/models/kptnodechartmodel.cpp b/src/libs/models/kptnodechartmodel.cpp
index 8d753c3b..10d1f34d 100644
--- a/src/libs/models/kptnodechartmodel.cpp
+++ b/src/libs/models/kptnodechartmodel.cpp
@@ -1,549 +1,550 @@
/* This file is part of the Calligra project
* Copyright (c) 2008, 2012 Dag Andersen <danders@get2net.dk>
*
* 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 "kptnodechartmodel.h"
#include "kptlocale.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptresource.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QVariant>
#include <QPen>
#include <KChartGlobal>
#include <KChartPalette>
namespace KPlato
{
ChartItemModel::ChartItemModel( QObject *parent )
: ItemModelBase( parent ),
m_localizeValues( false )
{
}
QModelIndex ChartItemModel::parent( const QModelIndex &index ) const
{
Q_UNUSED(index);
return QModelIndex();
}
const QMetaEnum ChartItemModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
int ChartItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return columnMap().keyCount();
}
int ChartItemModel::rowCount( const QModelIndex &parent ) const
{
return parent.isValid() ? 0 : startDate().daysTo( endDate() ) + 1;
}
QModelIndex ChartItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || row < 0 || column < 0 ) {
//debugPlan<<"No project"<<m_project<<" or illegal row, column"<<row<<column;
return QModelIndex();
}
if ( parent.isValid() ) {
return QModelIndex();
}
return createIndex( row, column );
}
double ChartItemModel::bcwsEffort( int day ) const
{
return m_bcws.hoursTo( startDate().addDays( day ) );
}
double ChartItemModel::bcwpEffort( int day ) const
{
double res = 0.0;
QDate date = startDate().addDays( day );
if ( m_bcws.days().contains( date ) ) {
res = m_bcws.bcwpEffort( date );
} else if ( date > m_bcws.endDate() ) {
res = m_bcws.bcwpEffort( date );
}
return res;
}
double ChartItemModel::acwpEffort( int day ) const
{
return m_acwp.hoursTo( startDate().addDays( day ) );
}
double ChartItemModel::bcwsCost( int day ) const
{
return m_bcws.costTo( startDate().addDays( day ) );
}
double ChartItemModel::bcwpCost( int day ) const
{
double res = 0.0;
QDate date = startDate().addDays( day );
if ( m_bcws.days().contains( date ) ) {
res = m_bcws.bcwpCost( date );
} else if ( date > m_bcws.endDate() ) {
res = m_bcws.bcwpCost( m_bcws.endDate() );
}
return res;
}
double ChartItemModel::acwpCost( int day ) const
{
return m_acwp.costTo( startDate().addDays( day ) );
}
double ChartItemModel::spiEffort( int day ) const
{
double p = bcwpEffort( day );
double s = bcwsEffort( day );
return s == 0.0 ? 0.0 : p / s;
}
double ChartItemModel::spiCost( int day ) const
{
double p = bcwpCost( day );
double s = bcwsCost( day );
return s == 0.0 ? 0.0 : p / s;
}
double ChartItemModel::cpiEffort( int day ) const
{
double p = bcwpEffort( day );
double a = acwpEffort( day );
return a == 0.0 ? 0.0 : p / a;
}
double ChartItemModel::cpiCost( int day ) const
{
double p = bcwpCost( day );
double a = acwpCost( day );
return a == 0.0 ? 0.0 : p / a;
}
QVariant ChartItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
if ( role == Qt::DisplayRole ) {
if ( ! m_localizeValues ) {
return data( index, Qt::EditRole );
} else {
QLocale locale;
// TODO: temporary workaround while KLocale/money logic still used
Locale *planLocale;
Locale *tmpPlanLocale = 0;
if (project()) {
planLocale = project()->locale();
} else {
tmpPlanLocale = new Locale();
planLocale = tmpPlanLocale;
}
switch ( index.column() ) {
case BCWSCost: result = planLocale->formatMoney( bcwsCost( index.row() ), QString(), 0 ); break;
case BCWPCost: result = planLocale->formatMoney( bcwpCost( index.row() ), QString(), 0 ); break;
case ACWPCost: result = planLocale->formatMoney( acwpCost( index.row() ), QString(), 0 ); break;
case BCWSEffort: result = locale.toString( bcwsEffort( index.row() ), 'f', 0 ); break;
case BCWPEffort: result = locale.toString( bcwpEffort( index.row() ), 'f', 0 ); break;
case ACWPEffort: result = locale.toString( acwpEffort( index.row() ), 'f', 0 ); break;
case SPICost: result = locale.toString( spiCost( index.row() ), 'f', 2 ); break;
case CPICost: result = locale.toString( cpiCost( index.row() ), 'f', 2 ); break;
case SPIEffort: result = locale.toString( spiEffort( index.row() ), 'f', 2 ); break;
case CPIEffort: result = locale.toString( cpiEffort( index.row() ), 'f', 2 ); break;
default: break;
}
delete tmpPlanLocale;
}
//debugPlan<<index<<role<<result;
return result;
} else if ( role == Qt::EditRole ) {
switch ( index.column() ) {
case BCWSCost: result = bcwsCost( index.row() ); break;
case BCWPCost: result = bcwpCost( index.row() ); break;
case ACWPCost: result = acwpCost( index.row() ); break;
case BCWSEffort: result = bcwsEffort( index.row() ); break;
case BCWPEffort: result = bcwpEffort( index.row() ); break;
case ACWPEffort: result = acwpEffort( index.row() ); break;
case SPICost: result = spiCost( index.row() ); break;
case CPICost: result = cpiCost( index.row() ); break;
case SPIEffort: result = spiEffort( index.row() ); break;
case CPIEffort: result = cpiEffort( index.row() ); break;
default: break;
}
//debugPlan<<index<<role<<result;
return result;
} else if ( role == Qt::ForegroundRole ) {
double v = 0.0;
switch ( index.column() ) {
case SPICost: v = spiCost( index.row() ); break;
case CPICost: v = cpiCost( index.row() ); break;
case SPIEffort: v = spiEffort( index.row() ); break;
case CPIEffort: v = cpiEffort( index.row() ); break;
default: break;
}
if ( v > 0.0 && v < 1.0 ) {
result = QBrush( Qt::red );
}
return result;
} else if ( role == KChart::DatasetBrushRole ) {
return headerData( index.column(), Qt::Horizontal, role );
} else if ( role == KChart::DatasetPenRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
//debugPlan<<index<<role<<result;
return result;
}
QVariant ChartItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
QVariant result;
if ( role == Qt::DisplayRole ) {
if ( orientation == Qt::Horizontal ) {
switch ( section ) {
case BCWSCost: return i18nc( "Cost based Budgeted Cost of Work Scheduled", "BCWS Cost" );
case BCWPCost: return i18nc( "Cost based Budgeted Cost of Work Performed", "BCWP Cost" );
case ACWPCost: return i18nc( "Cost based Actual Cost of Work Performed", "ACWP Cost" );
case BCWSEffort: return i18nc( "Effort based Budgeted Cost of Work Scheduled", "BCWS Effort" );
case BCWPEffort: return i18nc( "Effort based Budgeted Cost of Work Performed", "BCWP Effort" );
case ACWPEffort: return i18nc( "Effort based Actual Cost of Work Performed", "ACWP Effort" );
case SPICost: return i18nc( "Cost based Schedule Performance Index", "SPI Cost" );
case CPICost: return i18nc( "Cost based Cost Performance Index", "CPI Cost" );
case SPIEffort: return i18nc( "Effort based Schedule Performance Index", "SPI Effort" );
case CPIEffort: return i18nc( "Effort based Cost Performance Index", "CPI Effort" );
default: return QVariant();
}
} else {
return startDate().addDays( section ).toString( i18nc( "Date format used as chart axis labels. Must follow QDate specification.", "MM.dd" ) );
}
} else if ( role == Qt::ToolTipRole ) {
if ( orientation == Qt::Horizontal ) {
switch ( section ) {
case BCWSCost: return xi18nc( "@info:tooltip", "Cost based Budgeted Cost of Work Scheduled" );
case BCWPCost: return xi18nc( "@info:tooltip", "Cost based Budgeted Cost of Work Performed" );
case ACWPCost: return xi18nc( "@info:tooltip", "Cost based Actual Cost of Work Performed" );
case BCWSEffort: return xi18nc( "@info:tooltip", "Effort based Budgeted Cost of Work Scheduled" );
case BCWPEffort: return xi18nc( "@info:tooltip", "Effort based Budgeted Cost of Work Performed" );
case ACWPEffort: return xi18nc( "@info:tooltip", "Effort based Actual Cost of Work Performed" );
case SPICost: return xi18nc( "@info:tooltip", "Cost based Schedule Performance Index (BCWP/BCWS)" );
case CPICost: return xi18nc( "@info:tooltip", "Cost based Cost Performance Index (BCWP/ACWS)" );
case SPIEffort: return xi18nc( "@info:tooltip", "Effort based Schedule Performance Index (BCWP/BCWS)" );
case CPIEffort: return xi18nc( "@info:tooltip", "Effort based Cost Performance Index (BCWP/ACWS)" );
default: return QVariant();
}
} else {
return QLocale().toString( startDate().addDays( section ), QLocale::ShortFormat );
}
} else if ( role == Qt::EditRole ) {
if ( orientation == Qt::Horizontal ) {
switch ( section ) {
case BCWSCost: return "BCWS Cost";
case BCWPCost: return "BCWP Cost";
case ACWPCost: return "ACWP Cost";
case BCWSEffort: return "BCWS Effort";
case BCWPEffort: return "BCWP Effort";
case ACWPEffort: return "ACWP Effort";
case SPICost: return "SPI Cost";
case CPICost: return "CPI Cost";
case SPIEffort: return "SPI Effort";
case CPIEffort: return "CPI Effort";
default: return QVariant();
}
} else {
return startDate().addDays( section );
}
#ifdef PLAN_CHART_DEBUG
} else if ( role == Qt::BackgroundRole ) {
if ( orientation == Qt::Vertical ) {
if ( startDate().addDays( section ) == QDate::currentDate() ) {
return QBrush( Qt::red );
}
}
#endif
} else if ( role == KChart::DatasetBrushRole ) {
if ( orientation == Qt::Horizontal ) {
return KChart::Palette::defaultPalette().getBrush( section );
}
} else if ( role == KChart::DatasetPenRole ) {
QPen p;
p.setBrush( headerData( section, orientation, KChart::DatasetBrushRole ).value<QBrush>() );
result = p;
//debugPlan<<section<<"DatasetPenRole"<<result;
return result;
}
return ItemModelBase::headerData(section, orientation, role);
}
void ChartItemModel::setProject( Project *project )
{
beginResetModel();
m_bcws.clear();
m_acwp.clear();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ChartItemModel::projectDeleted);
disconnect( m_project, SIGNAL(projectCalculated(ScheduleManager*)), this, SLOT(setScheduleManager(ScheduleManager*)) );
disconnect( m_project, &Project::nodeRemoved, this, &ChartItemModel::slotNodeRemoved );
disconnect( m_project, &Project::nodeChanged, this, &ChartItemModel::slotNodeChanged );
disconnect( m_project, SIGNAL(resourceRemoved(const KPlato::Resource*)), this, SLOT(slotResourceChanged(const KPlato::Resource*)) );
disconnect( m_project, SIGNAL(resourceChanged(KPlato::Resource*)), this, SLOT(slotResourceChanged(KPlato::Resource*)) );
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ChartItemModel::projectDeleted);
connect( m_project, SIGNAL(projectCalculated(ScheduleManager*)), this, SLOT(setScheduleManager(ScheduleManager*)) );
connect( m_project, &Project::nodeRemoved, this, &ChartItemModel::slotNodeRemoved );
connect( m_project, &Project::nodeChanged, this, &ChartItemModel::slotNodeChanged );
connect( m_project, SIGNAL(resourceRemoved(const KPlato::Resource*)), this, SLOT(slotResourceChanged(const KPlato::Resource*)) );
connect( m_project, SIGNAL(resourceChanged(KPlato::Resource*)), this, SLOT(slotResourceChanged(KPlato::Resource*)) );
}
endResetModel();
}
void ChartItemModel::setScheduleManager( ScheduleManager *sm )
{
beginResetModel();
m_manager = sm;
calculate();
endResetModel();
}
void ChartItemModel::setNodes( const QList<Node*> &nodes )
{
beginResetModel();
debugPlan<<nodes;
m_nodes = nodes;
calculate();
endResetModel();
}
void ChartItemModel::addNode( Node *node )
{
beginResetModel();
m_nodes.append( node );
calculate();
endResetModel();
}
void ChartItemModel::clearNodes()
{
beginResetModel();
m_nodes.clear();
calculate();
endResetModel();
}
void ChartItemModel::slotNodeRemoved( Node *node )
{
if ( m_nodes.contains( node ) ) {
beginResetModel();
m_nodes.removeAt( m_nodes.indexOf( node ) );
calculate();
endResetModel();
}
}
void ChartItemModel::slotNodeChanged( Node *node )
{
//debugPlan<<this<<node;
if ( m_nodes.contains( node ) ) {
beginResetModel();
calculate();
endResetModel();
return;
}
foreach ( Node *n, m_nodes ) {
if ( node->isChildOf( n ) ) {
beginResetModel();
calculate();
endResetModel();
return;
}
}
}
void ChartItemModel::slotResourceChanged( Resource* )
{
beginResetModel();
calculate();
endResetModel();
}
void ChartItemModel::slotResourceChanged( const Resource* )
{
beginResetModel();
calculate();
endResetModel();
}
QDate ChartItemModel::startDate() const
{
QDate d = m_bcws.startDate();
if ( m_acwp.startDate().isValid() ) {
if ( ! d.isValid() || d > m_acwp.startDate() ) {
d = m_acwp.startDate();
}
}
return d;
}
QDate ChartItemModel::endDate() const
{
return qMax( m_bcws.endDate(), m_acwp.endDate() );
}
void ChartItemModel::calculate()
{
//debugPlan<<m_project<<m_manager<<m_nodes;
m_bcws.clear();
m_acwp.clear();
if ( m_manager ) {
if ( m_project ) {
foreach ( Node *n, m_nodes ) {
bool skip = false;
foreach ( Node *p, m_nodes ) {
if ( n->isChildOf( p ) ) {
skip = true;
break;
}
}
if ( ! skip ) {
m_bcws += n->bcwpPrDay( m_manager->scheduleId(), ECCT_EffortWork );
m_acwp += n->acwp( m_manager->scheduleId() );
}
}
}
}
//debugPlan<<"bcwp"<<m_bcws;
//debugPlan<<"acwp"<<m_acwp;
}
void ChartItemModel::setLocalizeValues( bool on )
{
m_localizeValues = on;
}
//-------------------------
PerformanceDataCurrentDateModel::PerformanceDataCurrentDateModel( QObject *parent )
: ChartItemModel( parent )
{
setLocalizeValues( true );
}
int PerformanceDataCurrentDateModel::rowCount( const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return 0;
}
return 2;
}
int PerformanceDataCurrentDateModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return 5;
}
QModelIndex PerformanceDataCurrentDateModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return QModelIndex();
}
return createIndex( row, column );
}
QVariant PerformanceDataCurrentDateModel::data(const QModelIndex &idx, int role) const
{
return ChartItemModel::data( mapIndex( idx ), role );
}
QVariant PerformanceDataCurrentDateModel::headerData( int section, Qt::Orientation o, int role ) const
{
if ( role == Qt::DisplayRole ) {
if ( o == Qt::Horizontal ) {
switch ( section ) {
case 0: return xi18nc( "@title:column Budgeted Cost of Work Scheduled", "BCWS" );
case 1: return xi18nc( "@title:column Budgeted Cost of Work Performed", "BCWP" );
case 2: return xi18nc( "@title:column Actual Cost of Work Performed", "ACWP" );
case 3: return xi18nc( "@title:column Schedule Performance Index", "SPI" );
case 4: return xi18nc( "@title:column Cost Performance Index", "CPI" );
default: break;
}
} else {
switch ( section ) {
case 0: return xi18nc( "@title:column", "Cost:" );
case 1: return xi18nc( "@title:column", "Effort:" );
default: break;
}
}
} else if ( role == Qt::ToolTipRole ) {
if ( o == Qt::Horizontal ) {
switch ( section ) {
case 0: return xi18nc( "@info:tooltip", "Budgeted Cost of Work Scheduled" );
case 1: return xi18nc( "@info:tooltip", "Budgeted Cost of Work Performed" );
case 2: return xi18nc( "@info:tooltip", "Actual Cost of Work Performed" );
case 3: return xi18nc( "@info:tooltip", "Schedule Performance Index" );
case 4: return xi18nc( "@info:tooltip", "Cost Performance Index" );
default: break;
}
} else {
switch ( section ) {
case 0: return xi18nc( "@info:tooltip", "Performance indicators based on cost" );
case 1: return xi18nc( "@info:tooltip", "Performance indicators based on effort" );
default: break;
}
}
}
return QVariant();
}
QModelIndex PerformanceDataCurrentDateModel::mapIndex( const QModelIndex &idx ) const
{
if ( ! startDate().isValid() ) {
return QModelIndex();
}
int row = startDate().daysTo( QDate::currentDate() );
if ( row < 0 ) {
return QModelIndex();
}
int column = -1;
switch ( idx.column() ) {
case 0: column = idx.row() == 0 ? BCWSCost : BCWSEffort; break; // BCWS
case 1: column = idx.row() == 0 ? BCWPCost : BCWPEffort; break; // BCWP
case 2: column = idx.row() == 0 ? ACWPCost : ACWPEffort; break; // ACWP
case 3: column = idx.row() == 0 ? SPICost : SPIEffort; break; // SPI
case 4: column = idx.row() == 0 ? CPICost : CPIEffort; break; // CPI
default: break;
}
if ( column < 0 ) {
return QModelIndex();
}
return ChartItemModel::index( row, column );
}
} //namespace KPlato
diff --git a/src/libs/models/kptnodeitemmodel.cpp b/src/libs/models/kptnodeitemmodel.cpp
index a4d906aa..1c8dddef 100644
--- a/src/libs/models/kptnodeitemmodel.cpp
+++ b/src/libs/models/kptnodeitemmodel.cpp
@@ -1,5214 +1,5215 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2009, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptnodeitemmodel.h"
#include "kptglobal.h"
#include "kptlocale.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptduration.h"
#include "kptproject.h"
#include "kptnode.h"
#include "kpttaskcompletedelegate.h"
#include "kptxmlloaderobject.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoOdf.h>
#include <KoOdfWriteStore.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoXmlNS.h>
#include <KoIcon.h>
#include <QMimeData>
#include <QMimeDatabase>
#include <QModelIndex>
#include <QByteArray>
#include <QHash>
#include <krichtextwidget.h>
#include <KGanttGlobal>
#include <math.h>
namespace KPlato
{
//--------------------------------------
NodeModel::NodeModel()
: QObject(),
m_project( 0 ),
m_manager( 0 ),
m_now( QDate::currentDate() ),
m_prec( 1 )
{
}
const QMetaEnum NodeModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
void NodeModel::setProject( Project *project )
{
debugPlan<<m_project<<"->"<<project;
m_project = project;
}
void NodeModel::setManager( ScheduleManager *sm )
{
debugPlan<<m_manager<<"->"<<sm;
m_manager = sm;
}
QVariant NodeModel::name( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return node->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::DecorationRole:
if ( node->isBaselined() ) {
return koIcon("view-time-schedule-baselined");
}
break;
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task:
return static_cast<const Task*>( node )->completion().isFinished() ? m_project->config().taskFinishedColor() : m_project->config().taskNormalColor();
case Node::Type_Milestone:
return static_cast<const Task*>( node )->completion().isFinished() ? m_project->config().milestoneFinishedColor() : m_project->config().milestoneNormalColor();
case Node::Type_Summarytask:
return m_project->config().summaryTaskLevelColor( node->level() );
default:
break;
}
break;
}
}
return QVariant();
}
QVariant NodeModel::leader( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return node->leader();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::allocation( const Node *node, int role ) const
{
if ( node->type() == Node::Type_Task ) {
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return node->requests().requestNameList().join( "," );
case Qt::ToolTipRole: {
QMap<QString, QStringList> lst;
foreach ( ResourceRequest *rr, node->requests().resourceRequests( false ) ) {
QStringList sl;
foreach( Resource *r, rr->requiredResources() ) {
sl << r->name();
}
lst.insert( rr->resource()->name(), sl );
}
if ( lst.isEmpty() ) {
return xi18nc( "@info:tooltip", "No resources has been allocated" );
}
QStringList sl;
for ( QMap<QString, QStringList>::ConstIterator it = lst.constBegin(); it != lst.constEnd(); ++it ) {
if ( it.value().isEmpty() ) {
sl << it.key();
} else {
sl << xi18nc( "@info:tooltip 1=resource name, 2=list of required resources", "%1 (%2)", it.key(), it.value().join(", ") );
}
}
if ( sl.count() == 1 ) {
return xi18nc( "@info:tooltip 1=resource name", "Allocated resource:<nl/>%1", sl.first() );
}
KLocalizedString ks = kxi18nc( "@info:tooltip 1=list of resources", "Allocated resources:<nl/>%1");
// Hack to get around ks escaping '<' and '>'
QString s = ks.subs(sl.join("#¤#")).toString();
return s.replace("#¤#", "<br/>");
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
}
return QVariant();
}
QVariant NodeModel::description( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
KRichTextWidget w( node->description(), 0 );
w.switchToPlainText();
QString s = w.textOrHtml();
int i = s.indexOf( '\n' );
s = s.left( i );
if ( i > 0 ) {
s += "...";
}
return s;
}
case Qt::ToolTipRole: {
KRichTextWidget w( node->description(), 0 );
w.switchToPlainText();
if ( w.textOrHtml().isEmpty() ) {
return QVariant();
}
return node->description();
}
case Qt::EditRole:
return node->description();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::type( const Node *node, int role ) const
{
//debugPlan<<node->name()<<", "<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return node->typeToString( true );
case Qt::EditRole:
return node->type();
case Qt::TextAlignmentRole:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::constraint( const Node *node, int role ) const
{
if ( node->type() == Node::Type_Project ) {
switch ( role ) {
case Qt::DisplayRole:
return i18n( "Target times" );
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Earliest start and latest finish" );
case Role::EnumList:
case Qt::EditRole:
case Role::EnumListValue:
return QVariant();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
} else if ( node->type() != Node::Type_Summarytask ) {
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return node->constraintToString( true );
case Role::EnumList:
return Node::constraintList( true );
case Qt::EditRole:
return node->constraint();
case Role::EnumListValue:
return (int)node->constraint();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
}
return QVariant();
}
QVariant NodeModel::constraintStartTime( const Node *node, int role ) const
{
if ( node->type() == Node::Type_Project ) {
switch ( role ) {
case Qt::DisplayRole: {
return QLocale().toString( node->constraintStartTime(), QLocale::ShortFormat );
}
case Qt::ToolTipRole: {
return QLocale().toString( node->constraintStartTime(), QLocale::LongFormat );
}
case Qt::EditRole:
return node->constraintStartTime();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
} else if ( node->type() != Node::Type_Summarytask ) {
switch ( role ) {
case Qt::DisplayRole: {
QString s = QLocale().toString( node->constraintStartTime(), QLocale::ShortFormat );
switch ( node->constraint() ) {
case Node::StartNotEarlier:
case Node::MustStartOn:
case Node::FixedInterval:
return s;
default:
break;
}
return QString( "(%1)" ).arg( s );
}
case Qt::ToolTipRole: {
int c = node->constraint();
if ( c == Node::MustStartOn || c == Node::StartNotEarlier || c == Node::FixedInterval ) {
return QLocale().toString( node->constraintStartTime(), QLocale::LongFormat );
}
break;
}
case Qt::EditRole:
return node->constraintStartTime();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
}
return QVariant();
}
QVariant NodeModel::constraintEndTime( const Node *node, int role ) const
{
if ( node->type() == Node::Type_Project ) {
switch ( role ) {
case Qt::DisplayRole: {
return QLocale().toString( node->constraintEndTime(), QLocale::ShortFormat );
}
case Qt::ToolTipRole: {
return QLocale().toString( node->constraintEndTime(), QLocale::LongFormat );
}
case Qt::EditRole:
return node->constraintEndTime();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
} else if ( node->type() != Node::Type_Summarytask ) {
switch ( role ) {
case Qt::DisplayRole: {
QString s = QLocale().toString( node->constraintEndTime(), QLocale::ShortFormat );
switch ( node->constraint() ) {
case Node::FinishNotLater:
case Node::MustFinishOn:
case Node::FixedInterval:
return s;
default:
break;
}
return QString( "(%1)" ).arg( s );
}
case Qt::ToolTipRole: {
int c = node->constraint();
if ( c == Node::FinishNotLater || c == Node::MustFinishOn || c == Node::FixedInterval ) {
return QLocale().toString( node->constraintEndTime(), QLocale::LongFormat );
}
break;
}
case Qt::EditRole:
return node->constraintEndTime();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
}
return QVariant();
}
QVariant NodeModel::estimateType( const Node *node, int role ) const
{
if ( node->estimate() == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
return node->estimate()->typeToString( true );
}
return QString();
case Role::EnumList:
return Estimate::typeToStringList( true );
case Qt::EditRole:
if ( node->type() == Node::Type_Task ) {
return node->estimate()->typeToString();
}
return QString();
case Role::EnumListValue:
return (int)node->estimate()->type();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::estimateCalendar( const Node *node, int role ) const
{
if ( node->estimate() == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task ) {
if ( node->estimate()->calendar() ) {
return node->estimate()->calendar()->name();
}
return i18n( "None" );
}
return QString();
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
if ( node->estimate()->type() == Estimate::Type_Effort ) {
return xi18nc( "@info:tooltip", "Not applicable, estimate type is Effort" );
}
if ( node->estimate()->calendar() ) {
return node->estimate()->calendar()->name();
}
return QVariant();
}
return QString();
case Role::EnumList:
{
QStringList lst; lst << i18n( "None" );
const Node *n = const_cast<Node*>( node )->projectNode();
if ( n ) {
lst += static_cast<const Project*>( n )->calendarNames();
}
return lst;
}
case Qt::EditRole:
if ( node->type() == Node::Type_Task ) {
if ( node->estimate()->calendar() == 0 ) {
return i18n( "None" );
}
return node->estimate()->calendar()->name();
}
return QString();
case Role::EnumListValue:
{
if ( node->estimate()->calendar() == 0 ) {
return 0;
}
QStringList lst;
const Node *n = const_cast<Node*>( node )->projectNode();
if ( n ) {
lst = static_cast<const Project*>( n )->calendarNames();
}
return lst.indexOf( node->estimate()->calendar()->name() ) + 1;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::estimate( const Node *node, int role ) const
{
if ( node->estimate() == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
Duration::Unit unit = node->estimate()->unit();
QString s = QLocale().toString( node->estimate()->expectedEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true );
if ( node->constraint() == Node::FixedInterval && node->estimate()->type() == Estimate::Type_Duration ) {
s = '(' + s + ')';
}
return s;
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
QString s = QLocale().toString( node->estimate()->expectedEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true );
Estimate::Type t = node->estimate()->type();
if ( node->constraint() == Node::FixedInterval && t == Estimate::Type_Duration ) {
s = xi18nc( "@info:tooltip", "Not applicable, constraint is Fixed Interval" );
} else if ( t == Estimate::Type_Effort ) {
s = xi18nc( "@info:tooltip", "Estimated effort: %1", s );
} else {
s = xi18nc( "@info:tooltip", "Estimated duration: %1", s );
}
return s;
}
break;
case Qt::EditRole:
return node->estimate()->expectedEstimate();
case Role::DurationUnit:
return static_cast<int>( node->estimate()->unit() );
case Role::Minimum:
return m_project->config().minimumDurationUnit();
case Role::Maximum:
return m_project->config().maximumDurationUnit();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::optimisticRatio( const Node *node, int role ) const
{
if ( node->estimate() == 0 || node->type() == Node::Type_Summarytask || node->type() == Node::Type_Milestone ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task && node->constraint() == Node::FixedInterval && node->estimate()->type() == Estimate::Type_Duration ) {
QString s = QString::number( node->estimate()->optimisticRatio() );
s = '(' + s + ')';
return s;
}
if ( node->estimate() ) {
return node->estimate()->optimisticRatio();
}
break;
case Qt::EditRole:
if ( node->estimate() ) {
return node->estimate()->optimisticRatio();
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
QString s = QLocale().toString( node->estimate()->optimisticEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true );
Estimate::Type t = node->estimate()->type();
if ( node->constraint() == Node::FixedInterval && t == Estimate::Type_Duration ) {
s = xi18nc( "@info:tooltip", "Not applicable, constraint is Fixed Interval" );
} else if ( t == Estimate::Type_Effort ) {
s = xi18nc( "@info:tooltip", "Optimistic effort: %1", s );
} else {
s = xi18nc( "@info:tooltip", "Optimistic duration: %1", s );
}
return s;
}
break;
case Role::Minimum:
return -99;
case Role::Maximum:
return 0;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::pessimisticRatio( const Node *node, int role ) const
{
if ( node->estimate() == 0 || node->type() == Node::Type_Summarytask || node->type() == Node::Type_Milestone ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task && node->constraint() == Node::FixedInterval && node->estimate()->type() == Estimate::Type_Duration ) {
QString s = QString::number( node->estimate()->pessimisticRatio() );
s = '(' + s + ')';
return s;
}
if ( node->estimate() ) {
return node->estimate()->pessimisticRatio();
}
break;
case Qt::EditRole:
if ( node->estimate() ) {
return node->estimate()->pessimisticRatio();
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
QString s = QLocale().toString( node->estimate()->pessimisticEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true );
Estimate::Type t = node->estimate()->type();
if ( node->constraint() == Node::FixedInterval && t == Estimate::Type_Duration ) {
s = xi18nc( "@info:tooltip", "Not applicable, constraint is Fixed Interval" );
} else if ( t == Estimate::Type_Effort ) {
s = xi18nc( "@info:tooltip", "Pessimistic effort: %1", s );
} else {
s = xi18nc( "@info:tooltip", "Pessimistic duration: %1", s );
}
return s;
}
break;
case Role::Minimum:
return 0;
case Role::Maximum:
return INT_MAX;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::riskType( const Node *node, int role ) const
{
if ( node->estimate() == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
return node->estimate()->risktypeToString( true );
}
return QString();
case Role::EnumList:
return Estimate::risktypeToStringList( true );
case Qt::EditRole:
if ( node->type() == Node::Type_Task ) {
return node->estimate()->risktypeToString();
}
return QString();
case Role::EnumListValue:
return (int)node->estimate()->risktype();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::runningAccount( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task ) {
Account *a = node->runningAccount();
return a == 0 ? i18n( "None" ) : a->name();
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Account *a = node->runningAccount();
return a ? xi18nc( "@info:tooltip", "Account for resource cost: %1", a->name() )
: xi18nc( "@info:tooltip", "Account for resource cost" );
}
break;
case Role::EnumListValue:
case Qt::EditRole: {
Account *a = node->runningAccount();
return a == 0 ? 0 : ( m_project->accounts().costElements().indexOf( a->name() ) + 1 );
}
case Role::EnumList: {
QStringList lst;
lst << i18n("None");
lst += m_project->accounts().costElements();
return lst;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::startupAccount( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
Account *a = node->startupAccount();
//debugPlan<<node->name()<<": "<<a;
return a == 0 ? i18n( "None" ) : a->name();
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
Account *a = node->startupAccount();
//debugPlan<<node->name()<<": "<<a;
return a ? xi18nc( "@info:tooltip", "Account for task startup cost: %1", a->name() )
: xi18nc( "@info:tooltip", "Account for task startup cost" );
}
break;
case Role::EnumListValue:
case Qt::EditRole: {
Account *a = node->startupAccount();
return a == 0 ? 0 : ( m_project->accounts().costElements().indexOf( a->name() ) + 1 );
}
case Role::EnumList: {
QStringList lst;
lst << i18n("None");
lst += m_project->accounts().costElements();
return lst;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::startupCost( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
return m_project->locale()->formatMoney( node->startupCost() );
}
break;
case Qt::EditRole:
return node->startupCost();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::shutdownAccount( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
Account *a = node->shutdownAccount();
return a == 0 ? i18n( "None" ) : a->name();
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
Account *a = node->shutdownAccount();
return a ? xi18nc( "@info:tooltip", "Account for task shutdown cost: %1", a->name() )
: xi18nc( "@info:tooltip", "Account for task shutdown cost" );
}
break;
case Role::EnumListValue:
case Qt::EditRole: {
Account *a = node->shutdownAccount();
return a == 0 ? 0 : ( m_project->accounts().costElements().indexOf( a->name() ) + 1 );
}
case Role::EnumList: {
QStringList lst;
lst << i18n("None");
lst += m_project->accounts().costElements();
return lst;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::shutdownCost( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) {
return m_project->locale()->formatMoney( node->shutdownCost() );
}
break;
case Qt::EditRole:
return node->shutdownCost();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::startTime( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( node->startTime( id() ), QLocale::ShortFormat );
case Qt::ToolTipRole:
//debugPlan<<node->name()<<", "<<role;
return xi18nc( "@info:tooltip", "Scheduled start: %1", QLocale().toString( node->startTime( id() ), QLocale::LongFormat ) );
case Qt::EditRole:
return node->startTime( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::endTime( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( node->endTime( id() ), QLocale::ShortFormat );
case Qt::ToolTipRole:
//debugPlan<<node->name()<<", "<<role;
return xi18nc( "@info:tooltip", "Scheduled finish: %1", QLocale().toString( node->endTime( id() ), QLocale::LongFormat ) );
case Qt::EditRole:
return node->endTime( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::duration( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
double v = node->duration( id() ).toDouble( unit );
return QVariant(QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ));
} else if ( node->type() == Node::Type_Project ) {
Duration::Unit unit = Duration::Unit_d;
double v = node->duration( id() ).toDouble( unit );
return QVariant(QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ));
}
break;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
double v = node->duration( id() ).toDouble( unit );
return xi18nc( "@info:tooltip", "Scheduled duration: %1", QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ) );
} else if ( node->type() == Node::Type_Project ) {
Duration::Unit unit = Duration::Unit_d;
double v = node->duration( id() ).toDouble( unit );
return xi18nc( "@info:tooltip", "Scheduled duration: %1", QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ) );
}
break;
case Qt::EditRole: {
return node->duration( id() ).toDouble( Duration::Unit_h );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::varianceDuration( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
double v = node->variance( id(), unit );
return QLocale().toString( v, 'f', 2 );
}
break;
case Qt::EditRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
return node->variance( id(), unit );
}
return 0.0;
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Duration::Unit unit = node->estimate()->unit();
double v = node->variance( id(), unit );
return xi18nc( "@info:tooltip", "PERT duration variance: %1", QLocale().toString( v ,'f', 2 ) );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::varianceEstimate( const Estimate *est, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
double v = est->variance( unit );
//debugPlan<<node->name()<<": "<<v<<" "<<unit<<" : "<<scales;
return QLocale().toString( v, 'f', 2 );
}
case Qt::EditRole: {
if ( est == 0 ) {
return 0.0;
}
return est->variance( est->unit() );
}
case Qt::ToolTipRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
double v = est->variance( unit );
return xi18nc( "@info:tooltip", "PERT estimate variance: %1", QLocale().toString( v, 'f', 2 ) + Duration::unitToString( unit, true ) );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::optimisticDuration( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( node->type() != Node::Type_Task ) {
return QVariant();
}
Duration d = node->duration( id() );
d = ( d * ( 100 + node->estimate()->optimisticRatio() ) ) / 100;
Duration::Unit unit = node->estimate()->unit();
double v = d.toDouble( unit );
//debugPlan<<node->name()<<": "<<v<<" "<<unit<<" : "<<scales;
return QVariant(QLocale().toString( v,'f', m_prec ) + Duration::unitToString( unit, true ));
break;
}
case Qt::EditRole: {
if ( node->type() != Node::Type_Task ) {
return 0.0;
}
Duration d = node->duration( id() );
d = ( d * ( 100 + node->estimate()->optimisticRatio() ) ) / 100;
Duration::Unit unit = node->estimate()->unit();
return d.toDouble( unit );
}
case Qt::ToolTipRole: {
if ( node->type() != Node::Type_Task ) {
return QVariant();
}
Duration d = node->duration( id() );
d = ( d * ( 100 + node->estimate()->optimisticRatio() ) ) / 100;
Duration::Unit unit = node->estimate()->unit();
double v = d.toDouble( unit );
//debugPlan<<node->name()<<": "<<v<<" "<<unit<<" : "<<scales;
return xi18nc( "@info:tooltip", "PERT optimistic duration: %1", QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ) );
break;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::optimisticEstimate( const Estimate *est, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
return QVariant(QLocale().toString( est->optimisticEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true ));
break;
}
case Qt::EditRole: {
if ( est == 0 ) {
return 0.0;
}
return est->optimisticEstimate();
}
case Qt::ToolTipRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
return xi18nc( "@info:tooltip", "Optimistic estimate: %1", QLocale().toString( est->optimisticEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true ) );
break;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::pertExpected( const Estimate *est, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
double v = Estimate::scale( est->pertExpected(), unit, est->scales() );
return QVariant(QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ));
}
case Qt::EditRole: {
if ( est == 0 ) {
return 0.0;
}
return Estimate::scale( est->pertExpected(), est->unit(), est->scales() );
}
case Qt::ToolTipRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
double v = Estimate::scale( est->pertExpected(), unit, est->scales() );
return xi18nc( "@info:tooltip", "PERT expected estimate: %1", QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ) );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::pessimisticDuration( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( node->type() != Node::Type_Task ) {
return QVariant();
}
Duration d = node->duration( id() );
d = ( d * ( 100 + node->estimate()->pessimisticRatio() ) ) / 100;
Duration::Unit unit = node->estimate()->unit();
double v = d.toDouble( unit );
//debugPlan<<node->name()<<": "<<v<<" "<<unit<<" : "<<scales;
return QVariant(QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ));
break;
}
case Qt::EditRole: {
if ( node->type() != Node::Type_Task ) {
return 0.0;
}
Duration d = node->duration( id() );
d = ( d * ( 100 + node->estimate()->pessimisticRatio() ) ) / 100;
return d.toDouble( node->estimate()->unit() );
}
case Qt::ToolTipRole: {
if ( node->type() != Node::Type_Task ) {
return QVariant();
}
Duration d = node->duration( id() );
d = ( d * ( 100 + node->estimate()->pessimisticRatio() ) ) / 100;
Duration::Unit unit = node->estimate()->unit();
double v = d.toDouble( unit );
//debugPlan<<node->name()<<": "<<v<<" "<<unit<<" : "<<scales;
return xi18nc( "@info:tooltip", "PERT pessimistic duration: %1", QLocale().toString( v, 'f', m_prec ) + Duration::unitToString( unit, true ) );
break;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::pessimisticEstimate( const Estimate *est, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
return QVariant(QLocale().toString( est->pessimisticEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true ));
break;
}
case Qt::EditRole: {
if ( est == 0 ) {
return 0.0;
}
return est->pessimisticEstimate();
}
case Qt::ToolTipRole: {
if ( est == 0 ) {
return QVariant();
}
Duration::Unit unit = est->unit();
return xi18nc( "@info:tooltip", "Pessimistic estimate: %1", QLocale().toString( est->pessimisticEstimate(), 'f', m_prec ) + Duration::unitToString( unit, true ) );
break;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::earlyStart( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( t->earlyStart( id() ), QLocale::ShortFormat );
case Qt::ToolTipRole:
return QLocale().toString( t->earlyStart( id() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
return t->earlyStart( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::earlyFinish( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( t->earlyFinish( id() ), QLocale::ShortFormat );
case Qt::ToolTipRole:
return QLocale().toString( t->earlyFinish( id() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
return t->earlyFinish( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::lateStart( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( t->lateStart( id() ), QLocale::ShortFormat );
case Qt::ToolTipRole:
return QLocale().toString( t->lateStart( id() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
return t->lateStart( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::lateFinish( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( t->lateFinish( id() ), QLocale::ShortFormat );
case Qt::ToolTipRole:
return QLocale().toString( t->lateFinish( id() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
return t->lateFinish( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::positiveFloat( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return t->positiveFloat( id() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return t->positiveFloat( id() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
return t->positiveFloat( id() ).toDouble( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::freeFloat( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return t->freeFloat( id() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return t->freeFloat( id() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
return t->freeFloat( id() ).toDouble( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::negativeFloat( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return t->negativeFloat( id() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return t->negativeFloat( id() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
return t->negativeFloat( id() ).toDouble( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::startFloat( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return t->startFloat( id() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return t->startFloat( id() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
return t->startFloat( id() ).toDouble( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::finishFloat( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return t->finishFloat( id() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return t->finishFloat( id() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
return t->finishFloat( id() ).toDouble( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::assignedResources( const Node *node, int role ) const
{
if ( node->type() != Node::Type_Task ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return node->assignedNameList( id() ).join(",");
case Qt::ToolTipRole: {
QStringList lst = node->assignedNameList( id() );
if ( ! lst.isEmpty() ) {
return xi18nc( "@info:tooltip 1=list of resources", "Assigned resources:<nl/>%1", node->assignedNameList( id() ).join("<nl/>") );
}
break;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::completed( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
return t->completion().percentFinished();
case Qt::EditRole:
return t->completion().percentFinished();
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Task is %1% completed", t->completion().percentFinished() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::status( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole: {
int st = t->state( id() );
if ( st & Node::State_NotScheduled ) {
return SchedulingState::notScheduled();
}
if ( st & Node::State_Finished ) {
if ( st & Node::State_FinishedLate ) {
return i18n( "Finished late" );
}
if ( st & Node::State_FinishedEarly ) {
return i18n( "Finished early" );
}
return i18n( "Finished" );
}
if ( st & Node::State_Running ) {
if ( st & Node::State_Late ) {
return i18n( "Running late" );
}
return i18n( "Running" );
}
if ( st & Node::State_Started ) {
if ( st & Node::State_StartedLate ) {
return i18n( "Started late" );
}
if ( st & Node::State_StartedEarly ) {
return i18n( "Started early" );
}
if ( st & Node::State_Late ) {
return i18n( "Running late" );
}
return i18n( "Started" );
}
if ( st & Node::State_ReadyToStart ) {
if ( st & Node::State_Late ) {
return i18n( "Not started" );
}
return i18n( "Can start" );
}
if ( st & Node::State_NotReadyToStart ) {
if ( st & Node::State_Late ) {
return i18n( "Delayed" );
}
return i18n( "Cannot start" );
}
return i18n( "Not started" );
break;
}
case Qt::ToolTipRole: {
int st = t->state( id() );
if ( st & Node::State_NotScheduled ) {
return SchedulingState::notScheduled();
}
if ( st & Node::State_Finished ) {
if ( st & Node::State_FinishedLate ) {
Duration d = t->completion().finishTime() - t->endTime( id() );
return xi18nc( "@info:tooltip", "Finished %1 late", d.toString( Duration::Format_i18nDay ) );
}
if ( st & Node::State_FinishedEarly ) {
Duration d = t->endTime( id() ) - t->completion().finishTime();
return xi18nc( "@info:tooltip", "Finished %1 early", d.toString( Duration::Format_i18nDay ) );
}
return xi18nc( "@info:tooltip", "Finished" );
}
if ( st & Node::State_Started ) {
if ( st & Node::State_StartedLate ) {
Duration d = t->completion().startTime() - t->startTime( id() );
return xi18nc( "@info:tooltip", "Started %1 late", d.toString( Duration::Format_i18nDay ) );
}
if ( st & Node::State_StartedEarly ) {
Duration d = t->startTime( id() ) - t->completion().startTime();
return xi18nc( "@info:tooltip", "Started %1 early", d.toString( Duration::Format_i18nDay ) );
}
return xi18nc( "@info:tooltip", "Started" );
}
if ( st & Node::State_Running ) {
return xi18nc( "@info:tooltip", "Running" );
}
if ( st & Node::State_ReadyToStart ) {
return xi18nc( "@info:tooltip", "Can start" );
}
if ( st & Node::State_NotReadyToStart ) {
QStringList names;
// TODO: proxy relations
foreach ( Relation *r, node->dependParentNodes() ) {
switch ( r->type() ) {
case Relation::FinishFinish:
case Relation::FinishStart:
if ( ! static_cast<Task*>( r->parent() )->completion().isFinished() ) {
if ( ! names.contains( r->parent()->name() ) ) {
names << r->parent()->name();
}
}
break;
case Relation::StartStart:
if ( ! static_cast<Task*>( r->parent() )->completion().isStarted() ) {
if ( ! names.contains( r->parent()->name() ) ) {
names << r->parent()->name();
}
}
break;
}
}
return names.isEmpty()
? xi18nc( "@info:tooltip", "Cannot start" )
: xi18nc( "@info:tooltip 1=list of task names", "Cannot start, waiting for:<nl/>%1", names.join( "<nl/>" ) );
}
return xi18nc( "@info:tooltip", "Not started" );
break;
}
case Qt::EditRole:
return t->state( id() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::startedTime( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
if ( t->completion().isStarted() ) {
return QLocale().toString( t->completion().startTime(), QLocale::ShortFormat );
}
break;
case Qt::ToolTipRole:
if ( t->completion().isStarted() ) {
return xi18nc( "@info:tooltip", "Actual start: %1", QLocale().toString( t->completion().startTime().date(), QLocale::LongFormat ) );
}
break;
case Qt::EditRole:
if ( t->completion().isStarted() ) {
return t->completion().startTime();
}
return QDateTime::currentDateTime();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::isStarted( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return t->completion().isStarted();
case Qt::ToolTipRole:
if ( t->completion().isStarted() ) {
return xi18nc( "@info:tooltip", "The task started at: %1", QLocale().toString( t->completion().startTime().date(), QLocale::LongFormat ) );
}
return xi18nc( "@info:tooltip", "The task is not started" );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::finishedTime( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
if ( t->completion().isFinished() ) {
return QLocale().toString( t->completion().finishTime(), QLocale::ShortFormat );
}
break;
case Qt::ToolTipRole:
if ( t->completion().isFinished() ) {
return xi18nc( "@info:tooltip", "Actual finish: %1", QLocale().toString( t->completion().finishTime(), QLocale::LongFormat ) );
}
break;
case Qt::EditRole:
if ( t->completion().isFinished() ) {
return t->completion().finishTime();
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::isFinished( const Node *node, int role ) const
{
if ( ! ( node->type() == Node::Type_Task || node->type() == Node::Type_Milestone ) ) {
return QVariant();
}
const Task *t = static_cast<const Task*>( node );
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return t->completion().isFinished();
case Qt::ToolTipRole:
if ( t->completion().isFinished() ) {
return xi18nc( "@info:tooltip", "The task finished at: %1", QLocale().toString( t->completion().finishTime().date(), QLocale::LongFormat ) );
}
return xi18nc( "@info:tooltip", "The task is not finished" );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::plannedEffortTo( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->plannedEffortTo( m_now, id() ).format();
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Planned effort until %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), node->plannedEffortTo( m_now, id() ).toString( Duration::Format_i18nHour ) );
case Qt::EditRole:
return node->plannedEffortTo( m_now, id() ).toDouble( Duration::Unit_h );
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::actualEffortTo( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->actualEffortTo( m_now ).format();
case Qt::ToolTipRole:
//debugPlan<<m_now<<node;
return xi18nc( "@info:tooltip", "Actual effort used up to %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), node->actualEffortTo( m_now ).toString( Duration::Format_i18nHour ) );
case Qt::EditRole:
return node->actualEffortTo( m_now ).toDouble( Duration::Unit_h );
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::remainingEffort( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t ) {
return t->completion().remainingEffort().format();
}
break;
}
case Qt::ToolTipRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t ) {
return xi18nc( "@info:tooltip", "Remaining effort: %1", t->completion().remainingEffort().toString( Duration::Format_i18nHour ) );
}
break;
}
case Qt::EditRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t == 0 ) {
return QVariant();
}
return t->completion().remainingEffort().toDouble( Duration::Unit_h );
}
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::plannedCostTo( const Node *node, int role ) const
{
Locale *l = m_project->locale();
switch ( role ) {
case Qt::DisplayRole:
return l->formatMoney( node->plannedCostTo( m_now, id() ) );
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Planned cost until %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), l->formatMoney( node->plannedCostTo( m_now, id() ) ) );
case Qt::EditRole:
return node->plannedCostTo( m_now );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::actualCostTo( const Node *node, int role ) const
{
Locale *l = m_project->locale();
switch ( role ) {
case Qt::DisplayRole:
return l->formatMoney( node->actualCostTo( id(), m_now ).cost() );
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Actual cost until %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), l->formatMoney( node->actualCostTo( id(), m_now ).cost() ) );
case Qt::EditRole:
return node->actualCostTo( id(), m_now ).cost();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::note( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
if ( node->type() == Node::Type_Task ) {
Node *n = const_cast<Node*>( node );
return static_cast<Task*>( n )->completion().note();
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::nodeSchedulingStatus( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->schedulingStatus( id(), true ).value( 0 );
case Qt::EditRole:
return node->schedulingStatus( id(), false ).value( 0 );
case Qt::ToolTipRole:
return node->schedulingStatus( id(), true ).join( "\n" );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::resourceIsMissing( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->resourceError( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->resourceError( id() );
case Qt::ToolTipRole:
if ( node->resourceError( id() ) ) {
return xi18nc( "@info:tooltip", "Resource allocation is expected when the task estimate type is <emphasis>Effort</emphasis>" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::resourceIsOverbooked( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->resourceOverbooked( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->resourceOverbooked( id() );
case Qt::ToolTipRole:
if ( node->resourceOverbooked( id() ) ) {
return xi18nc( "@info:tooltip", "A resource has been overbooked" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::resourceIsNotAvailable( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->resourceNotAvailable( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->resourceNotAvailable( id() );
case Qt::ToolTipRole:
if ( node->resourceNotAvailable( id() ) ) {
return xi18nc( "@info:tooltip", "No resource is available for this task" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::schedulingConstraintsError( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->constraintError( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->constraintError( id() );
case Qt::ToolTipRole:
if ( node->constraintError( id() ) ) {
return xi18nc( "@info:tooltip", "Failed to comply with a timing constraint" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::nodeIsNotScheduled( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->notScheduled( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->notScheduled( id() );
case Qt::ToolTipRole:
if ( node->notScheduled( id() ) ) {
return xi18nc( "@info:tooltip", "This task has not been scheduled" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::effortNotMet( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->effortMetError( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->effortMetError( id() );
case Qt::ToolTipRole:
if ( node->effortMetError( id() ) ) {
return xi18nc( "@info:tooltip", "The assigned resources cannot deliver the required estimated effort" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::schedulingError( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( node->schedulingError( id() ) ) {
return i18n( "Error" );
}
break;
case Qt::EditRole:
return node->schedulingError( id() );
case Qt::ToolTipRole:
if ( node->schedulingError( id() ) ) {
return xi18nc( "@info:tooltip", "Scheduling error" );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskErrorColor();
case Node::Type_Milestone: return m_project->config().milestoneErrorColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::wbsCode( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return node->wbsCode();
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Work breakdown structure code: %1", node->wbsCode() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case SortableRole:
return node->wbsCode(true);
}
return QVariant();
}
QVariant NodeModel::nodeLevel( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return node->level();
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Task level: %1", node->level() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::nodeBCWS( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return m_project->locale()->formatMoney( node->bcws( m_now, id() ), QString(), 0 );
case Qt::EditRole:
return node->bcws( m_now, id() );
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Budgeted Cost of Work Scheduled at %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), m_project->locale()->formatMoney( node->bcws( m_now, id() ), QString(), 0 ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::nodeBCWP( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return m_project->locale()->formatMoney( node->bcwp( id() ), QString(), 0 );
case Qt::EditRole:
return node->bcwp( id() );
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Budgeted Cost of Work Performed at %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), m_project->locale()->formatMoney( node->bcwp( id() ), QString(), 0 ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::nodeACWP( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return m_project->locale()->formatMoney( node->acwp( m_now, id() ).cost(), QString(), 0 );
case Qt::EditRole:
return node->acwp( m_now, id() ).cost();
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Actual Cost of Work Performed at %1: %2", QLocale().toString( m_now, QLocale::ShortFormat ), m_project->locale()->formatMoney( node->acwp( m_now, id() ).cost() ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::nodePerformanceIndex( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( node->schedulePerformanceIndex( m_now, id() ), 'f', 2 );
case Qt::EditRole:
return node->schedulePerformanceIndex( m_now, id() );
case Qt::ToolTipRole:
return xi18nc( "@info:tooltip", "Schedule Performance Index at %1: %2", m_now.toString(), QLocale().toString( node->schedulePerformanceIndex( m_now, id() ), 'f', 2 ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::ForegroundRole:
return QColor(node->schedulePerformanceIndex( m_now, id() ) < 1.0 ? Qt::red : Qt::black);
}
return QVariant();
}
QVariant NodeModel::nodeIsCritical( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return node->isCritical( id() );
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskNormalColor();
case Node::Type_Milestone: return m_project->config().milestoneNormalColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::nodeInCriticalPath( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return node->inCriticalPath( id() );
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Foreground: {
if ( ! m_project ) {
break;
}
switch ( node->type() ) {
case Node::Type_Task: return m_project->config().taskNormalColor();
case Node::Type_Milestone: return m_project->config().milestoneNormalColor();
default:
break;
}
}
}
return QVariant();
}
QVariant NodeModel::wpOwnerName( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t == 0 ) {
return QVariant();
}
if ( t->wpTransmitionStatus() == WorkPackage::TS_None ) {
return xi18nc( "Not available", "NA" );
}
return t->wpOwnerName();
}
case Qt::ToolTipRole: {
const Task *task = dynamic_cast<const Task*>( node );
if ( task == 0 ) {
return QVariant();
}
int sts = task->wpTransmitionStatus();
QString t = wpTransmitionTime( node, Qt::DisplayRole ).toString();
if ( sts == WorkPackage::TS_Send ) {
return xi18nc( "@info:tooltip", "Latest work package sent to %1 at %2", static_cast<const Task*>( node )->wpOwnerName(), t );
}
if ( sts == WorkPackage::TS_Receive ) {
return xi18nc( "@info:tooltip", "Latest work package received from %1 at %2", static_cast<const Task*>( node )->wpOwnerName(), t );
}
return xi18nc( "@info:tooltip", "Not available" );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::wpTransmitionStatus( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t == 0 ) {
return QVariant();
}
if ( t->wpTransmitionStatus() == WorkPackage::TS_None ) {
return xi18nc( "Not available", "NA" );
}
return WorkPackage::transmitionStatusToString( t->wpTransmitionStatus(), true );
}
case Qt::EditRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t == 0 ) {
return QVariant();
}
return WorkPackage::transmitionStatusToString( t->wpTransmitionStatus(), false );
}
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::wpTransmitionTime( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole: {
const Task *t = dynamic_cast<const Task*>( node );
if ( t == 0 ) {
return QVariant();
}
if ( t->wpTransmitionStatus() == WorkPackage::TS_None ) {
return xi18nc( "Not available", "NA" );
}
return QLocale().toString( t->wpTransmitionTime(), QLocale::ShortFormat );
}
case Qt::ToolTipRole: {
const Task *task = dynamic_cast<const Task*>( node );
if ( task == 0 ) {
return QVariant();
}
int sts = task->wpTransmitionStatus();
QString t = wpTransmitionTime( node, Qt::DisplayRole ).toString();
if ( sts == WorkPackage::TS_Send ) {
return xi18nc( "@info:tooltip", "Latest work package sent: %1", t );
}
if ( sts == WorkPackage::TS_Receive ) {
return xi18nc( "@info:tooltip", "Latest work package received: %1", t );
}
return xi18nc( "@info:tooltip", "Not available" );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant NodeModel::data( const Node *n, int property, int role ) const
{
QVariant result;
switch ( property ) {
// Edited by user
case NodeName: result = name( n, role ); break;
case NodeType: result = type( n, role ); break;
case NodeResponsible: result = leader( n, role ); break;
case NodeAllocation: result = allocation( n, role ); break;
case NodeEstimateType: result = estimateType( n, role ); break;
case NodeEstimateCalendar: result = estimateCalendar( n, role ); break;
case NodeEstimate: result = estimate( n, role ); break;
case NodeOptimisticRatio: result = optimisticRatio( n, role ); break;
case NodePessimisticRatio: result = pessimisticRatio( n, role ); break;
case NodeRisk: result = riskType( n, role ); break;
case NodeConstraint: result = constraint( n, role ); break;
case NodeConstraintStart: result = constraintStartTime( n, role ); break;
case NodeConstraintEnd: result = constraintEndTime( n, role ); break;
case NodeRunningAccount: result = runningAccount( n, role ); break;
case NodeStartupAccount: result = startupAccount( n, role ); break;
case NodeStartupCost: result = startupCost( n, role ); break;
case NodeShutdownAccount: result = shutdownAccount( n, role ); break;
case NodeShutdownCost: result = shutdownCost( n, role ); break;
case NodeDescription: result = description( n, role ); break;
// Based on edited values
case NodeExpected: result = pertExpected( n->estimate(), role ); break;
case NodeVarianceEstimate: result = varianceEstimate( n->estimate(), role ); break;
case NodeOptimistic: result = optimisticEstimate( n->estimate(), role ); break;
case NodePessimistic: result = pessimisticEstimate( n->estimate(), role ); break;
// After scheduling
case NodeStartTime: result = startTime( n, role ); break;
case NodeEndTime: result = endTime( n, role ); break;
case NodeEarlyStart: result = earlyStart( n, role ); break;
case NodeEarlyFinish: result = earlyFinish( n, role ); break;
case NodeLateStart: result = lateStart( n, role ); break;
case NodeLateFinish: result = lateFinish( n, role ); break;
case NodePositiveFloat: result = positiveFloat( n, role ); break;
case NodeFreeFloat: result = freeFloat( n, role ); break;
case NodeNegativeFloat: result = negativeFloat( n, role ); break;
case NodeStartFloat: result = startFloat( n, role ); break;
case NodeFinishFloat: result = finishFloat( n, role ); break;
case NodeAssignments: result = assignedResources( n, role ); break;
// Based on scheduled values
case NodeDuration: result = duration( n, role ); break;
case NodeVarianceDuration: result = varianceDuration( n, role ); break;
case NodeOptimisticDuration: result = optimisticDuration( n, role ); break;
case NodePessimisticDuration: result = pessimisticDuration( n, role ); break;
// Completion
case NodeStatus: result = status( n, role ); break;
case NodeCompleted: result = completed( n, role ); break;
case NodePlannedEffort: result = plannedEffortTo( n, role ); break;
case NodeActualEffort: result = actualEffortTo( n, role ); break;
case NodeRemainingEffort: result = remainingEffort( n, role ); break;
case NodePlannedCost: result = plannedCostTo( n, role ); break;
case NodeActualCost: result = actualCostTo( n, role ); break;
case NodeActualStart: result = startedTime( n, role ); break;
case NodeStarted: result = isStarted( n, role ); break;
case NodeActualFinish: result = finishedTime( n, role ); break;
case NodeFinished: result = isFinished( n, role ); break;
case NodeStatusNote: result = note( n, role ); break;
// Scheduling errors
case NodeSchedulingStatus: result = nodeSchedulingStatus( n, role ); break;
case NodeNotScheduled: result = nodeIsNotScheduled( n, role ); break;
case NodeAssignmentMissing: result = resourceIsMissing( n, role ); break;
case NodeResourceOverbooked: result = resourceIsOverbooked( n, role ); break;
case NodeResourceUnavailable: result = resourceIsNotAvailable( n, role ); break;
case NodeConstraintsError: result = schedulingConstraintsError( n, role ); break;
case NodeEffortNotMet: result = effortNotMet( n, role ); break;
case NodeSchedulingError: result = schedulingError( n, role ); break;
case NodeWBSCode: result = wbsCode( n, role ); break;
case NodeLevel: result = nodeLevel( n, role ); break;
// Performance
case NodeBCWS: result = nodeBCWS( n, role ); break;
case NodeBCWP: result = nodeBCWP( n, role ); break;
case NodeACWP: result = nodeACWP( n, role ); break;
case NodePerformanceIndex: result = nodePerformanceIndex( n, role ); break;
case NodeCritical: result = nodeIsCritical( n, role ); break;
case NodeCriticalPath: result = nodeInCriticalPath( n, role ); break;
case WPOwnerName: result = wpOwnerName( n, role ); break;
case WPTransmitionStatus: result = wpTransmitionStatus( n, role ); break;
case WPTransmitionTime: result = wpTransmitionTime( n, role ); break;
default:
//debugPlan<<"Invalid property number: "<<property;
return result;
}
return result;
}
int NodeModel::propertyCount() const
{
return columnMap().keyCount();
}
KUndo2Command *NodeModel::setData( Node *node, int property, const QVariant & value, int role )
{
switch ( property ) {
case NodeModel::NodeName: return setName( node, value, role );
case NodeModel::NodeType: return setType( node, value, role );
case NodeModel::NodeResponsible: return setLeader( node, value, role );
case NodeModel::NodeAllocation: return setAllocation( node, value, role );
case NodeModel::NodeEstimateType: return setEstimateType( node, value, role );
case NodeModel::NodeEstimateCalendar: return setEstimateCalendar( node, value, role );
case NodeModel::NodeEstimate: return setEstimate( node, value, role );
case NodeModel::NodeOptimisticRatio: return setOptimisticRatio( node, value, role );
case NodeModel::NodePessimisticRatio: return setPessimisticRatio( node, value, role );
case NodeModel::NodeRisk: return setRiskType( node, value, role );
case NodeModel::NodeConstraint: return setConstraint( node, value, role );
case NodeModel::NodeConstraintStart: return setConstraintStartTime( node, value, role );
case NodeModel::NodeConstraintEnd: return setConstraintEndTime( node, value, role );
case NodeModel::NodeRunningAccount: return setRunningAccount( node, value, role );
case NodeModel::NodeStartupAccount: return setStartupAccount( node, value, role );
case NodeModel::NodeStartupCost: return setStartupCost( node, value, role );
case NodeModel::NodeShutdownAccount: return setShutdownAccount( node, value, role );
case NodeModel::NodeShutdownCost: return setShutdownCost( node, value, role );
case NodeModel::NodeDescription: return setDescription( node, value, role );
case NodeModel::NodeCompleted: return setCompletion( node, value, role );
case NodeModel::NodeActualEffort: return setActualEffort( node, value, role );
case NodeModel::NodeRemainingEffort: return setRemainingEffort( node, value, role );
case NodeModel::NodeActualStart: return setStartedTime( node, value, role );
case NodeModel::NodeActualFinish: return setFinishedTime( node, value, role );
default:
qWarning("data: invalid display value column %d", property);
return 0;
}
return 0;
}
QVariant NodeModel::headerData( int section, int role )
{
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case NodeName: return xi18nc( "@title:column", "Name" );
case NodeType: return xi18nc( "@title:column", "Type" );
case NodeResponsible: return xi18nc( "@title:column", "Responsible" );
case NodeAllocation: return xi18nc( "@title:column", "Allocation" );
case NodeEstimateType: return xi18nc( "@title:column", "Estimate Type" );
case NodeEstimateCalendar: return xi18nc( "@title:column", "Calendar" );
case NodeEstimate: return xi18nc( "@title:column", "Estimate" );
case NodeOptimisticRatio: return xi18nc( "@title:column", "Optimistic" ); // Ratio
case NodePessimisticRatio: return xi18nc( "@title:column", "Pessimistic" ); // Ratio
case NodeRisk: return xi18nc( "@title:column", "Risk" );
case NodeConstraint: return xi18nc( "@title:column", "Constraint" );
case NodeConstraintStart: return xi18nc( "@title:column", "Constraint Start" );
case NodeConstraintEnd: return xi18nc( "@title:column", "Constraint End" );
case NodeRunningAccount: return xi18nc( "@title:column", "Running Account" );
case NodeStartupAccount: return xi18nc( "@title:column", "Startup Account" );
case NodeStartupCost: return xi18nc( "@title:column", "Startup Cost" );
case NodeShutdownAccount: return xi18nc( "@title:column", "Shutdown Account" );
case NodeShutdownCost: return xi18nc( "@title:column", "Shutdown Cost" );
case NodeDescription: return xi18nc( "@title:column", "Description" );
// Based on edited values
case NodeExpected: return xi18nc( "@title:column", "Expected" );
case NodeVarianceEstimate: return xi18nc( "@title:column", "Variance (Est)" );
case NodeOptimistic: return xi18nc( "@title:column", "Optimistic" );
case NodePessimistic: return xi18nc( "@title:column", "Pessimistic" );
// After scheduling
case NodeStartTime: return xi18nc( "@title:column", "Start Time" );
case NodeEndTime: return xi18nc( "@title:column", "End Time" );
case NodeEarlyStart: return xi18nc( "@title:column", "Early Start" );
case NodeEarlyFinish: return xi18nc( "@title:column", "Early Finish" );
case NodeLateStart: return xi18nc( "@title:column", "Late Start" );
case NodeLateFinish: return xi18nc( "@title:column", "Late Finish" );
case NodePositiveFloat: return xi18nc( "@title:column", "Positive Float" );
case NodeFreeFloat: return xi18nc( "@title:column", "Free Float" );
case NodeNegativeFloat: return xi18nc( "@title:column", "Negative Float" );
case NodeStartFloat: return xi18nc( "@title:column", "Start Float" );
case NodeFinishFloat: return xi18nc( "@title:column", "Finish Float" );
case NodeAssignments: return xi18nc( "@title:column", "Assignments" );
// Based on scheduled values
case NodeDuration: return xi18nc( "@title:column", "Duration" );
case NodeVarianceDuration: return xi18nc( "@title:column", "Variance (Dur)" );
case NodeOptimisticDuration: return xi18nc( "@title:column", "Optimistic (Dur)" );
case NodePessimisticDuration: return xi18nc( "@title:column", "Pessimistic (Dur)" );
// Completion
case NodeStatus: return xi18nc( "@title:column", "Status" );
// xgettext: no-c-format
case NodeCompleted: return xi18nc( "@title:column", "% Completed" );
case NodePlannedEffort: return xi18nc( "@title:column", "Planned Effort" );
case NodeActualEffort: return xi18nc( "@title:column", "Actual Effort" );
case NodeRemainingEffort: return xi18nc( "@title:column", "Remaining Effort" );
case NodePlannedCost: return xi18nc( "@title:column", "Planned Cost" );
case NodeActualCost: return xi18nc( "@title:column", "Actual Cost" );
case NodeActualStart: return xi18nc( "@title:column", "Actual Start" );
case NodeStarted: return xi18nc( "@title:column", "Started" );
case NodeActualFinish: return xi18nc( "@title:column", "Actual Finish" );
case NodeFinished: return xi18nc( "@title:column", "Finished" );
case NodeStatusNote: return xi18nc( "@title:column", "Status Note" );
// Scheduling errors
case NodeSchedulingStatus: return xi18nc( "@title:column", "Scheduling Status" );
case NodeNotScheduled: return xi18nc( "@title:column", "Not Scheduled" );
case NodeAssignmentMissing: return xi18nc( "@title:column", "Assignment Missing" );
case NodeResourceOverbooked: return xi18nc( "@title:column", "Resource Overbooked" );
case NodeResourceUnavailable: return xi18nc( "@title:column", "Resource Unavailable" );
case NodeConstraintsError: return xi18nc( "@title:column", "Constraints Error" );
case NodeEffortNotMet: return xi18nc( "@title:column", "Effort Not Met" );
case NodeSchedulingError: return xi18nc( "@title:column", "Scheduling Error" );
case NodeWBSCode: return xi18nc( "@title:column", "WBS Code" );
case NodeLevel: return xi18nc( "@title:column Node level", "Level" );
// Performance
case NodeBCWS: return xi18nc( "@title:column Budgeted Cost of Work Scheduled", "BCWS" );
case NodeBCWP: return xi18nc( "@title:column Budgeted Cost of Work Performed", "BCWP" );
case NodeACWP: return xi18nc( "@title:column Actual Cost of Work Performed", "ACWP" );
case NodePerformanceIndex: return xi18nc( "@title:column Schedule Performance Index", "SPI" );
case NodeCritical: return xi18nc( "@title:column", "Critical" );
case NodeCriticalPath: return xi18nc( "@title:column", "Critical Path" );
// Work package handling
case WPOwnerName: return xi18nc( "@title:column", "Owner" );
case WPTransmitionStatus: return xi18nc( "@title:column", "Status" );
case WPTransmitionTime: return xi18nc( "@title:column", "Time" );
default: return QVariant();
}
}
if ( role == Qt::EditRole ) {
switch ( section ) {
case NodeName: return "Name";
case NodeType: return "Type";
case NodeResponsible: return "Responsible";
case NodeAllocation: return "Allocation";
case NodeEstimateType: return "Estimate Type";
case NodeEstimateCalendar: return "Calendar";
case NodeEstimate: return "Estimate";
case NodeOptimisticRatio: return "Optimistic"; // Ratio
case NodePessimisticRatio: return "Pessimistic"; // Ratio
case NodeRisk: return "Risk";
case NodeConstraint: return "Constraint";
case NodeConstraintStart: return "Constraint Start";
case NodeConstraintEnd: return "Constraint End";
case NodeRunningAccount: return "Running Account";
case NodeStartupAccount: return "Startup Account";
case NodeStartupCost: return "Startup Cost";
case NodeShutdownAccount: return "Shutdown Account";
case NodeShutdownCost: return "Shutdown Cost";
case NodeDescription: return "Description";
// Based on edited values
case NodeExpected: return "Expected";
case NodeVarianceEstimate: return "Variance (Est)";
case NodeOptimistic: return "Optimistic";
case NodePessimistic: return "Pessimistic";
// After scheduling
case NodeStartTime: return "Start Time";
case NodeEndTime: return "End Time";
case NodeEarlyStart: return "Early Start";
case NodeEarlyFinish: return "Early Finish";
case NodeLateStart: return "Late Start";
case NodeLateFinish: return "Late Finish";
case NodePositiveFloat: return "Positive Float";
case NodeFreeFloat: return "Free Float";
case NodeNegativeFloat: return "Negative Float";
case NodeStartFloat: return "Start Float";
case NodeFinishFloat: return "Finish Float";
case NodeAssignments: return "Assignments";
// Based on scheduled values
case NodeDuration: return "Duration";
case NodeVarianceDuration: return "Variance (Dur)";
case NodeOptimisticDuration: return "Optimistic (Dur)";
case NodePessimisticDuration: return "Pessimistic (Dur)";
// Completion
case NodeStatus: return "Status";
// xgettext: no-c-format
case NodeCompleted: return "% Completed";
case NodePlannedEffort: return "Planned Effort";
case NodeActualEffort: return "Actual Effort";
case NodeRemainingEffort: return "Remaining Effort";
case NodePlannedCost: return "Planned Cost";
case NodeActualCost: return "Actual Cost";
case NodeActualStart: return "Actual Start";
case NodeStarted: return "Started";
case NodeActualFinish: return "Actual Finish";
case NodeFinished: return "Finished";
case NodeStatusNote: return "Status Note";
// Scheduling errors
case NodeSchedulingStatus: return "Scheduling Status";
case NodeNotScheduled: return "Not Scheduled";
case NodeAssignmentMissing: return "Assignment Missing";
case NodeResourceOverbooked: return "Resource Overbooked";
case NodeResourceUnavailable: return "Resource Unavailable";
case NodeConstraintsError: return "Constraints Error";
case NodeEffortNotMet: return "Effort Not Met";
case NodeSchedulingError: return "Scheduling Error";
case NodeWBSCode: return "WBS Code";
case NodeLevel: return "Level";
// Performance
case NodeBCWS: return "BCWS";
case NodeBCWP: return "BCWP";
case NodeACWP: return "ACWP";
case NodePerformanceIndex: return "SPI";
case NodeCritical: return "Critical";
case NodeCriticalPath: return "Critical Path";
// Work package handling
case WPOwnerName: return "Owner";
case WPTransmitionStatus: return "Status";
case WPTransmitionTime: return "Time";
default: return QVariant();
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case NodeName: return ToolTip::nodeName();
case NodeType: return ToolTip::nodeType();
case NodeResponsible: return ToolTip::nodeResponsible();
case NodeAllocation: return ToolTip::allocation();
case NodeEstimateType: return ToolTip::estimateType();
case NodeEstimateCalendar: return ToolTip::estimateCalendar();
case NodeEstimate: return ToolTip::estimate();
case NodeOptimisticRatio: return ToolTip::optimisticRatio();
case NodePessimisticRatio: return ToolTip::pessimisticRatio();
case NodeRisk: return ToolTip::riskType();
case NodeConstraint: return ToolTip::nodeConstraint();
case NodeConstraintStart: return ToolTip::nodeConstraintStart();
case NodeConstraintEnd: return ToolTip::nodeConstraintEnd();
case NodeRunningAccount: return ToolTip::nodeRunningAccount();
case NodeStartupAccount: return ToolTip::nodeStartupAccount();
case NodeStartupCost: return ToolTip::nodeStartupCost();
case NodeShutdownAccount: return ToolTip::nodeShutdownAccount();
case NodeShutdownCost: return ToolTip::nodeShutdownCost();
case NodeDescription: return ToolTip::nodeDescription();
// Based on edited values
case NodeExpected: return ToolTip::estimateExpected();
case NodeVarianceEstimate: return ToolTip::estimateVariance();
case NodeOptimistic: return ToolTip::estimateOptimistic();
case NodePessimistic: return ToolTip::estimatePessimistic();
// After scheduling
case NodeStartTime: return ToolTip::nodeStartTime();
case NodeEndTime: return ToolTip::nodeEndTime();
case NodeEarlyStart: return ToolTip::nodeEarlyStart();
case NodeEarlyFinish: return ToolTip::nodeEarlyFinish();
case NodeLateStart: return ToolTip::nodeLateStart();
case NodeLateFinish: return ToolTip::nodeLateFinish();
case NodePositiveFloat: return ToolTip::nodePositiveFloat();
case NodeFreeFloat: return ToolTip::nodeFreeFloat();
case NodeNegativeFloat: return ToolTip::nodeNegativeFloat();
case NodeStartFloat: return ToolTip::nodeStartFloat();
case NodeFinishFloat: return ToolTip::nodeFinishFloat();
case NodeAssignments: return ToolTip::nodeAssignment();
// Based on scheduled values
case NodeDuration: return ToolTip::nodeDuration();
case NodeVarianceDuration: return ToolTip::nodeVarianceDuration();
case NodeOptimisticDuration: return ToolTip::nodeOptimisticDuration();
case NodePessimisticDuration: return ToolTip::nodePessimisticDuration();
// Completion
case NodeStatus: return ToolTip::nodeStatus();
case NodeCompleted: return ToolTip::nodeCompletion();
case NodePlannedEffort: return ToolTip::nodePlannedEffortTo();
case NodeActualEffort: return ToolTip::nodeActualEffortTo();
case NodeRemainingEffort: return ToolTip::nodeRemainingEffort();
case NodePlannedCost: return ToolTip::nodePlannedCostTo();
case NodeActualCost: return ToolTip::nodeActualCostTo();
case NodeActualStart: return ToolTip::completionStartedTime();
case NodeStarted: return ToolTip::completionStarted();
case NodeActualFinish: return ToolTip::completionFinishedTime();
case NodeFinished: return ToolTip::completionFinished();
case NodeStatusNote: return ToolTip::completionStatusNote();
// Scheduling errors
case NodeSchedulingStatus: return ToolTip::nodeSchedulingStatus();
case NodeNotScheduled: return ToolTip::nodeNotScheduled();
case NodeAssignmentMissing: return ToolTip::nodeAssignmentMissing();
case NodeResourceOverbooked: return ToolTip::nodeResourceOverbooked();
case NodeResourceUnavailable: return ToolTip::nodeResourceUnavailable();
case NodeConstraintsError: return ToolTip::nodeConstraintsError();
case NodeEffortNotMet: return ToolTip::nodeEffortNotMet();
case NodeSchedulingError: return ToolTip::nodeSchedulingError();
case NodeWBSCode: return ToolTip::nodeWBS();
case NodeLevel: return ToolTip::nodeLevel();
// Performance
case NodeBCWS: return ToolTip::nodeBCWS();
case NodeBCWP: return ToolTip::nodeBCWP();
case NodeACWP: return ToolTip::nodeACWP();
case NodePerformanceIndex: return ToolTip::nodePerformanceIndex();
// Work package handling FIXME
case WPOwnerName: return xi18nc( "@info:tooltip", "Work package owner" );
case WPTransmitionStatus: return xi18nc( "@info:tooltip", "Work package status" );
case WPTransmitionTime: return xi18nc( "@info:tooltip", "Work package send/receive time" );
default: return QVariant();
}
}
if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case NodeName:
case NodeType:
case NodeResponsible:
case NodeAllocation:
case NodeEstimateType:
case NodeEstimateCalendar:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeEstimate:
case NodeOptimisticRatio:
case NodePessimisticRatio:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
case NodeRisk:
case NodeConstraint:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeConstraintStart:
case NodeConstraintEnd:
case NodeRunningAccount:
case NodeStartupAccount:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeStartupCost:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
case NodeShutdownAccount:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeShutdownCost:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
case NodeDescription:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
// Based on edited values
case NodeExpected:
case NodeVarianceEstimate:
case NodeOptimistic:
case NodePessimistic:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
// After scheduling
case NodeStartTime:
case NodeEndTime:
case NodeEarlyStart:
case NodeEarlyFinish:
case NodeLateStart:
case NodeLateFinish:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodePositiveFloat:
case NodeFreeFloat:
case NodeNegativeFloat:
case NodeStartFloat:
case NodeFinishFloat:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
case NodeAssignments:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
// Based on scheduled values
case NodeDuration:
case NodeVarianceDuration:
case NodeOptimisticDuration:
case NodePessimisticDuration:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
// Completion
case NodeStatus:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeCompleted:
return (int)(Qt::AlignCenter); // special, presented as a bar
case NodePlannedEffort:
case NodeActualEffort:
case NodeRemainingEffort:
case NodePlannedCost:
case NodeActualCost:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
case NodeActualStart:
case NodeStarted:
case NodeActualFinish:
case NodeFinished:
case NodeStatusNote:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
// Scheduling errors
case NodeSchedulingStatus:
case NodeNotScheduled:
case NodeAssignmentMissing:
case NodeResourceOverbooked:
case NodeResourceUnavailable:
case NodeConstraintsError:
case NodeEffortNotMet:
case NodeSchedulingError:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeWBSCode:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case NodeLevel:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
// Performance
case NodeBCWS:
case NodeBCWP:
case NodeACWP:
case NodePerformanceIndex:
return (int)(Qt::AlignRight|Qt::AlignVCenter); // number
case NodeCritical:
case NodeCriticalPath:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case WPOwnerName:
case WPTransmitionStatus:
case WPTransmitionTime:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
default:
return QVariant();
}
}
if ( role == Qt::WhatsThisRole ) {
switch ( section ) {
case NodeNegativeFloat: return WhatsThis::nodeNegativeFloat();
default: return QVariant();
}
}
return QVariant();
}
KUndo2Command *NodeModel::setName( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
if ( value.toString() == node->name() ) {
return 0;
}
KUndo2MagicString s = kundo2_i18n( "Modify name" );
switch ( node->type() ) {
case Node::Type_Task: s = kundo2_i18n( "Modify task name" ); break;
case Node::Type_Milestone: s = kundo2_i18n( "Modify milestone name" ); break;
case Node::Type_Summarytask: s = kundo2_i18n( "Modify summarytask name" ); break;
case Node::Type_Project: s = kundo2_i18n( "Modify project name" ); break;
}
return new NodeModifyNameCmd( *node, value.toString(), s );
}
}
return 0;
}
KUndo2Command *NodeModel::setLeader( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
if ( value.toString() != node->leader() ) {
return new NodeModifyLeaderCmd( *node, value.toString(), kundo2_i18n( "Modify responsible" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setAllocation( Node */*node*/, const QVariant &/*value*/, int /*role*/ )
{
return 0;
}
KUndo2Command *NodeModel::setDescription( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() == node->description() ) {
return 0;
}
return new NodeModifyDescriptionCmd( *node, value.toString(), kundo2_i18n( "Modify task description" ) );
}
return 0;
}
KUndo2Command *NodeModel::setType( Node *, const QVariant &, int )
{
return 0;
}
KUndo2Command *NodeModel::setConstraint( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Node::ConstraintType v;
QStringList lst = node->constraintList( false );
if ( lst.contains( value.toString() ) ) {
v = Node::ConstraintType( lst.indexOf( value.toString() ) );
} else {
v = Node::ConstraintType( value.toInt() );
}
//debugPlan<<v;
if ( v != node->constraint() ) {
return new NodeModifyConstraintCmd( *node, v, kundo2_i18n( "Modify constraint type" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setConstraintStartTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
QDateTime dt = value.toDateTime();
dt.setTime( QTime( dt.time().hour(), dt.time().minute(), 0 ) ); // reset possible secs/msecs
if ( dt != node->constraintStartTime() ) {
return new NodeModifyConstraintStartTimeCmd( *node, dt, kundo2_i18n( "Modify constraint start time" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setConstraintEndTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
QDateTime dt = value.toDateTime();
dt.setTime( QTime( dt.time().hour(), dt.time().minute(), 0 ) ); // reset possible secs/msecs
if ( dt != node->constraintEndTime() ) {
return new NodeModifyConstraintEndTimeCmd( *node, dt, kundo2_i18n( "Modify constraint end time" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setEstimateType( Node *node, const QVariant &value, int role )
{
if ( node->estimate() == 0 ) {
return 0;
}
switch ( role ) {
case Qt::EditRole: {
Estimate::Type v;
QStringList lst = node->estimate()->typeToStringList( false );
if ( lst.contains( value.toString() ) ) {
v = Estimate::Type( lst.indexOf( value.toString() ) );
} else {
v = Estimate::Type( value.toInt() );
}
if ( v != node->estimate()->type() ) {
return new ModifyEstimateTypeCmd( *node, node->estimate()->type(), v, kundo2_i18n( "Modify estimate type" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setEstimateCalendar( Node *node, const QVariant &value, int role )
{
if ( node->estimate() == 0 ) {
return 0;
}
switch ( role ) {
case Qt::EditRole: {
Calendar *c = 0;
Calendar *old = node->estimate()->calendar();
if ( value.toInt() > 0 ) {
QStringList lst = estimateCalendar( node, Role::EnumList ).toStringList();
if ( value.toInt() < lst.count() ) {
c = m_project->calendarByName( lst.at( value.toInt() ) );
}
}
if ( c != old ) {
return new ModifyEstimateCalendarCmd( *node, old, c, kundo2_i18n( "Modify estimate calendar" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setEstimate( Node *node, const QVariant &value, int role )
{
if ( node->estimate() == 0 ) {
return 0;
}
switch ( role ) {
case Qt::EditRole: {
double d;
Duration::Unit unit;
if ( value.toList().count() == 2 ) {
d = value.toList()[0].toDouble();
unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
} else if ( value.canConvert<QString>() ) {
bool ok = Duration::valueFromString( value.toString(), d, unit );
if ( ! ok ) {
return 0;
}
} else {
return 0;
}
//debugPlan<<d<<","<<unit<<" ->"<<value.toList()[1].toInt();
MacroCommand *cmd = 0;
if ( d != node->estimate()->expectedEstimate() ) {
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Modify estimate" ) );
cmd->addCommand( new ModifyEstimateCmd( *node, node->estimate()->expectedEstimate(), d ) );
}
if ( unit != node->estimate()->unit() ) {
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Modify estimate" ) );
cmd->addCommand( new ModifyEstimateUnitCmd( *node, node->estimate()->unit(), unit ) );
}
if ( cmd ) {
return cmd;
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setOptimisticRatio( Node *node, const QVariant &value, int role )
{
if ( node->estimate() == 0 ) {
return 0;
}
switch ( role ) {
case Qt::EditRole:
if ( value.toInt() != node->estimate()->optimisticRatio() ) {
return new EstimateModifyOptimisticRatioCmd( *node, node->estimate()->optimisticRatio(), value.toInt(), kundo2_i18n( "Modify optimistic estimate" ) );
}
break;
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setPessimisticRatio( Node *node, const QVariant &value, int role )
{
if ( node->estimate() == 0 ) {
return 0;
}
switch ( role ) {
case Qt::EditRole:
if ( value.toInt() != node->estimate()->pessimisticRatio() ) {
return new EstimateModifyPessimisticRatioCmd( *node, node->estimate()->pessimisticRatio(), value.toInt(), kundo2_i18n( "Modify pessimistic estimate" ) );
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setRiskType( Node *node, const QVariant &value, int role )
{
if ( node->estimate() == 0 ) {
return 0;
}
switch ( role ) {
case Qt::EditRole: {
int val = 0;
QStringList lst = node->estimate()->risktypeToStringList( false );
if ( lst.contains( value.toString() ) ) {
val = lst.indexOf( value.toString() );
} else {
val = value.toInt();
}
if ( val != node->estimate()->risktype() ) {
Estimate::Risktype v = Estimate::Risktype( val );
return new EstimateModifyRiskCmd( *node, node->estimate()->risktype(), v, kundo2_i18n( "Modify risk type" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setRunningAccount( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
//debugPlan<<node->name();
QStringList lst = runningAccount( node, Role::EnumList ).toStringList();
if ( value.toInt() < lst.count() ) {
Account *a = m_project->accounts().findAccount( lst.at( value.toInt() ) );
Account *old = node->runningAccount();
if ( old != a ) {
return new NodeModifyRunningAccountCmd( *node, old, a, kundo2_i18n( "Modify running account" ) );
}
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setStartupAccount( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
//debugPlan<<node->name();
QStringList lst = startupAccount( node, Role::EnumList ).toStringList();
if ( value.toInt() < lst.count() ) {
Account *a = m_project->accounts().findAccount( lst.at( value.toInt() ) );
Account *old = node->startupAccount();
//debugPlan<<(value.toInt())<<";"<<(lst.at( value.toInt()))<<":"<<a;
if ( old != a ) {
return new NodeModifyStartupAccountCmd( *node, old, a, kundo2_i18n( "Modify startup account" ) );
}
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setStartupCost( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
double v = value.toDouble();
if ( v != node->startupCost() ) {
return new NodeModifyStartupCostCmd( *node, v, kundo2_i18n( "Modify startup cost" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setShutdownAccount( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
//debugPlan<<node->name();
QStringList lst = shutdownAccount( node, Role::EnumList ).toStringList();
if ( value.toInt() < lst.count() ) {
Account *a = m_project->accounts().findAccount( lst.at( value.toInt() ) );
Account *old = node->shutdownAccount();
if ( old != a ) {
return new NodeModifyShutdownAccountCmd( *node, old, a, kundo2_i18n( "Modify shutdown account" ) );
}
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setShutdownCost( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
double v = value.toDouble();
if ( v != node->shutdownCost() ) {
return new NodeModifyShutdownCostCmd( *node, v, kundo2_i18n( "Modify shutdown cost" ) );
}
break;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setCompletion( Node */*node*/, const QVariant &/*value*/, int /*role*/ )
{
return 0;
}
KUndo2Command *NodeModel::setRemainingEffort( Node *node, const QVariant &value, int role )
{
if ( role == Qt::EditRole && node->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( node );
double d( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration dur( d, unit );
return new ModifyCompletionRemainingEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify remaining effort" ) );
}
return 0;
}
KUndo2Command *NodeModel::setActualEffort( Node *node, const QVariant &value, int role )
{
if ( role == Qt::EditRole && node->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( node );
double d( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration dur( d, unit );
return new ModifyCompletionActualEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify actual effort" ) );
}
return 0;
}
KUndo2Command *NodeModel::setStartedTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Task *t = qobject_cast<Task*>( node );
if ( t == 0 ) {
return 0;
}
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify actual start time" ) );
if ( ! t->completion().isStarted() ) {
m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) );
}
m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->type() == Node::Type_Milestone ) {
m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->completion().percentFinished() < 100 ) {
Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration );
m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) );
}
}
return m;
}
default:
break;
}
return 0;
}
KUndo2Command *NodeModel::setFinishedTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Task *t = qobject_cast<Task*>( node );
if ( t == 0 ) {
return 0;
}
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify actual finish time" ) );
if ( ! t->completion().isFinished() ) {
m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) );
if ( t->completion().percentFinished() < 100 ) {
Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration );
m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) );
}
}
m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->type() == Node::Type_Milestone ) {
m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) );
}
return m;
}
default:
break;
}
return 0;
}
//----------------------------
NodeItemModel::NodeItemModel( QObject *parent )
: ItemModelBase( parent ),
m_node( 0 ),
m_projectshown( false )
{
setReadOnly( NodeModel::NodeDescription, true );
}
NodeItemModel::~NodeItemModel()
{
}
void NodeItemModel::setShowProject( bool on )
{
beginResetModel();
m_projectshown = on;
endResetModel();
emit projectShownChanged( on );
}
void NodeItemModel::slotNodeToBeInserted( Node *parent, int row )
{
//debugPlan<<parent->name()<<"; "<<row;
Q_ASSERT( m_node == 0 );
m_node = parent;
beginInsertRows( index( parent ), row, row );
}
void NodeItemModel::slotNodeInserted( Node *node )
{
//debugPlan<<node->parentNode()->name()<<"-->"<<node->name();
Q_ASSERT( node->parentNode() == m_node );
endInsertRows();
m_node = 0;
emit nodeInserted( node );
}
void NodeItemModel::slotNodeToBeRemoved( Node *node )
{
//debugPlan<<node->name();
Q_ASSERT( m_node == 0 );
m_node = node;
int row = index( node ).row();
beginRemoveRows( index( node->parentNode() ), row, row );
}
void NodeItemModel::slotNodeRemoved( Node *node )
{
//debugPlan<<node->name();
Q_ASSERT( node == m_node );
#ifdef NDEBUG
Q_UNUSED(node)
#endif
endRemoveRows();
m_node = 0;
}
void NodeItemModel::slotNodeToBeMoved( Node *node, int pos, Node *newParent, int newPos )
{
//debugPlan<<node->parentNode()->name()<<pos<<":"<<newParent->name()<<newPos;
beginMoveRows( index( node->parentNode() ), pos, pos, index( newParent ), newPos );
}
void NodeItemModel::slotNodeMoved( Node *node )
{
Q_UNUSED( node );
//debugPlan<<node->parentNode()->name()<<node->parentNode()->indexOf( node );
endMoveRows();
}
void NodeItemModel::slotLayoutChanged()
{
//debugPlan<<node->name();
emit layoutAboutToBeChanged();
emit layoutChanged();
}
void NodeItemModel::slotProjectCalculated(ScheduleManager *sm)
{
debugPlan<<m_manager<<sm;
if ( sm && sm == m_manager ) {
slotLayoutChanged();
}
}
void NodeItemModel::slotWbsDefinitionChanged()
{
debugPlan;
if ( m_project == 0 ) {
return;
}
if ( m_projectshown ) {
QModelIndex idx = createIndex( 0, NodeModel::NodeWBSCode, m_project );
emit dataChanged( idx, idx );
}
foreach ( Node *n, m_project->allNodes() ) {
int row = n->parentNode()->indexOf( n );
QModelIndex idx = createIndex( row, NodeModel::NodeWBSCode, n );
emit dataChanged( idx, idx );
}
}
void NodeItemModel::setProject( Project *project )
{
beginResetModel();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &NodeItemModel::projectDeleted);
disconnect( m_project, SIGNAL(localeChanged()), this, SLOT(slotLayoutChanged()));
disconnect( m_project, &Project::wbsDefinitionChanged, this, &NodeItemModel::slotWbsDefinitionChanged);
disconnect( m_project, &Project::nodeChanged, this, &NodeItemModel::slotNodeChanged);
disconnect( m_project, &Project::nodeToBeAdded, this, &NodeItemModel::slotNodeToBeInserted);
disconnect( m_project, &Project::nodeToBeRemoved, this, &NodeItemModel::slotNodeToBeRemoved);
disconnect( m_project, &Project::nodeToBeMoved, this, &NodeItemModel::slotNodeToBeMoved);
disconnect( m_project, &Project::nodeMoved, this, &NodeItemModel::slotNodeMoved);
disconnect( m_project, &Project::nodeAdded, this, &NodeItemModel::slotNodeInserted);
disconnect( m_project, &Project::nodeRemoved, this, &NodeItemModel::slotNodeRemoved);
disconnect( m_project, &Project::projectCalculated, this, &NodeItemModel::slotProjectCalculated);
}
m_project = project;
debugPlan<<this<<m_project<<"->"<<project;
m_nodemodel.setProject( project );
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &NodeItemModel::projectDeleted);
connect( m_project, SIGNAL(localeChanged()), this, SLOT(slotLayoutChanged()));
connect( m_project, &Project::wbsDefinitionChanged, this, &NodeItemModel::slotWbsDefinitionChanged);
connect( m_project, &Project::nodeChanged, this, &NodeItemModel::slotNodeChanged);
connect( m_project, &Project::nodeToBeAdded, this, &NodeItemModel::slotNodeToBeInserted);
connect( m_project, &Project::nodeToBeRemoved, this, &NodeItemModel::slotNodeToBeRemoved);
connect( m_project, &Project::nodeToBeMoved, this, &NodeItemModel::slotNodeToBeMoved);
connect( m_project, &Project::nodeMoved, this, &NodeItemModel::slotNodeMoved);
connect( m_project, &Project::nodeAdded, this, &NodeItemModel::slotNodeInserted);
connect( m_project, &Project::nodeRemoved, this, &NodeItemModel::slotNodeRemoved);
connect( m_project, &Project::projectCalculated, this, &NodeItemModel::slotProjectCalculated);
}
endResetModel();
}
void NodeItemModel::setScheduleManager( ScheduleManager *sm )
{
beginResetModel();
if (sm == m_nodemodel.manager()) {
endResetModel();
return;
}
if ( m_nodemodel.manager() ) {
}
m_nodemodel.setManager( sm );
ItemModelBase::setScheduleManager( sm );
if ( sm ) {
}
debugPlan<<this<<sm;
endResetModel();
}
Qt::ItemFlags NodeItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( !index.isValid() ) {
if ( m_readWrite ) {
flags |= Qt::ItemIsDropEnabled;
}
return flags;
}
if ( isColumnReadOnly( index.column() ) ) {
//debugPlan<<"Column is readonly:"<<index.column();
return flags;
}
Node *n = node( index );
if ( m_readWrite && n != 0 ) {
bool baselined = n->isBaselined();
flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
switch ( index.column() ) {
case NodeModel::NodeName: // name
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeType: break; // Node type
case NodeModel::NodeResponsible: // Responsible
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeAllocation: // allocation
if ( n->type() == Node::Type_Task ) {
flags |= Qt::ItemIsEditable;
}
break;
case NodeModel::NodeEstimateType: // estimateType
{
if ( ! baselined && ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeEstimate: // estimate
{
if ( ! baselined && ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeOptimisticRatio: // optimisticRatio
case NodeModel::NodePessimisticRatio: // pessimisticRatio
{
if ( ! baselined && n->type() == Node::Type_Task ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeEstimateCalendar:
{
if ( ! baselined && n->type() == Node::Type_Task )
{
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeRisk: // risktype
{
if ( ! baselined && n->type() == Node::Type_Task ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeConstraint: // constraint type
if ( ! baselined && ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
flags |= Qt::ItemIsEditable;
}
break;
case NodeModel::NodeConstraintStart: { // constraint start
if ( ! baselined && n->type() == Node::Type_Project ) {
flags |= Qt::ItemIsEditable;
break;
}
if ( ! baselined && ! ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
break;
}
flags |= Qt::ItemIsEditable;
break;
}
case NodeModel::NodeConstraintEnd: { // constraint end
if ( ! baselined && n->type() == Node::Type_Project ) {
flags |= Qt::ItemIsEditable;
break;
}
if ( ! baselined && ! ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
break;
}
flags |= Qt::ItemIsEditable;
break;
}
case NodeModel::NodeRunningAccount: // running account
if ( ! baselined && n->type() == Node::Type_Task ) {
flags |= Qt::ItemIsEditable;
}
break;
case NodeModel::NodeStartupAccount: // startup account
case NodeModel::NodeStartupCost: // startup cost
case NodeModel::NodeShutdownAccount: // shutdown account
case NodeModel::NodeShutdownCost: { // shutdown cost
if ( ! baselined && ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeDescription: // description
flags |= Qt::ItemIsEditable;
break;
default:
break;
}
Task *t = static_cast<Task*>( n );
if ( manager() && t->isScheduled( id() ) ) {
if ( ! t->completion().isStarted() ) {
switch ( index.column() ) {
case NodeModel::NodeActualStart:
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeActualFinish:
if ( t->type() == Node::Type_Milestone ) {
flags |= Qt::ItemIsEditable;
}
break;
case NodeModel::NodeCompleted:
if ( t->state() & Node::State_ReadyToStart ) {
flags |= Qt::ItemIsEditable;
}
break;
default: break;
}
} else if ( ! t->completion().isFinished() ) {
switch ( index.column() ) {
case NodeModel::NodeActualFinish:
case NodeModel::NodeCompleted:
case NodeModel::NodeRemainingEffort:
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeActualEffort:
if ( t->completion().entrymode() == Completion::EnterEffortPerTask || t->completion().entrymode() == Completion::EnterEffortPerResource ) {
flags |= Qt::ItemIsEditable;
}
break;
default: break;
}
}
}
}
return flags;
}
QModelIndex NodeItemModel::parent( const QModelIndex &index ) const
{
if ( ! index.isValid() ) {
return QModelIndex();
}
Node *n = node( index );
if ( n == 0 || n == m_project ) {
return QModelIndex();
}
Node *p = n->parentNode();
if ( p == m_project ) {
return m_projectshown ? createIndex( 0, 0, p ) : QModelIndex();
}
int row = p->parentNode()->indexOf( p );
if ( row == -1 ) {
return QModelIndex();
}
return createIndex( row, 0, p );
}
QModelIndex NodeItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
Q_ASSERT( parent.model() == this );
}
//debugPlan<<parent<<row<<column;
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
//debugPlan<<m_project<<parent<<"No index for"<<row<<","<<column;
return QModelIndex();
}
if ( m_projectshown && ! parent.isValid() ) {
return createIndex( row, column, m_project );
}
Node *p = node( parent );
if ( row >= p->numChildren() ) {
errorPlan<<p->name()<<" row too high"<<row<<","<<column;
return QModelIndex();
}
// now get the internal pointer for the index
Node *n = p->childNode( row );
QModelIndex idx = createIndex(row, column, n);
//debugPlan<<idx;
return idx;
}
QModelIndex NodeItemModel::index( const Node *node, int column ) const
{
if ( m_project == 0 || node == 0 ) {
return QModelIndex();
}
Node *par = node->parentNode();
if ( par ) {
//debugPlan<<par<<"-->"<<node;
return createIndex( par->indexOf( node ), column, const_cast<Node*>(node) );
}
if ( m_projectshown && node == m_project ) {
return createIndex( 0, column, m_project );
}
//debugPlan<<node;
return QModelIndex();
}
bool NodeItemModel::setType( Node *, const QVariant &, int )
{
return false;
}
bool NodeItemModel::setAllocation( Node *node, const QVariant &value, int role )
{
Task *task = qobject_cast<Task*>( node );
if ( task == 0 ) {
return false;
}
switch ( role ) {
case Qt::EditRole:
{
MacroCommand *cmd = 0;
QStringList res = m_project->resourceNameList();
QStringList req = node->requestNameList();
QStringList alloc;
foreach ( const QString &s, value.toString().split( QRegExp(" *, *"), QString::SkipEmptyParts ) ) {
alloc << s.trimmed();
}
// first add all new resources (to "default" group)
ResourceGroup *pargr = m_project->groupByName( i18n( "Resources" ) );
foreach ( const QString &s, alloc ) {
Resource *r = m_project->resourceByName( s.trimmed() );
if ( r != 0 ) {
continue;
}
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Add resource" ) );
if ( pargr == 0 ) {
pargr = new ResourceGroup();
pargr->setName( i18n( "Resources" ) );
cmd->addCommand( new AddResourceGroupCmd( m_project, pargr ) );
//debugPlan<<"add group:"<<pargr->name();
}
r = new Resource();
r->setName( s.trimmed() );
cmd->addCommand( new AddResourceCmd( pargr, r ) );
//debugPlan<<"add resource:"<<r->name();
emit executeCommand( cmd );
cmd = 0;
}
KUndo2MagicString c = kundo2_i18n( "Modify resource allocations" );
// Handle deleted requests
foreach ( const QString &s, req ) {
// if a request is not in alloc, it must have been be removed by the user
if ( alloc.indexOf( s ) == -1 ) {
// remove removed resource request
ResourceRequest *r = node->resourceRequest( s );
if ( r ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
//debugPlan<<"delete request:"<<r->resource()->name()<<" group:"<<r->parent()->group()->name();
cmd->addCommand( new RemoveResourceRequestCmd( r->parent(), r ) );
}
}
}
// Handle new requests
QHash<ResourceGroup*, ResourceGroupRequest*> groupmap;
foreach ( const QString &s, alloc ) {
// if an allocation is not in req, it must be added
if ( req.indexOf( s ) == -1 ) {
ResourceGroup *pargr = 0;
Resource *r = m_project->resourceByName( s );
if ( r == 0 ) {
// Handle request to non existing resource
pargr = m_project->groupByName( i18n( "Resources" ) );
if ( pargr == 0 ) {
pargr = new ResourceGroup();
pargr->setName( i18n( "Resources" ) );
cmd->addCommand( new AddResourceGroupCmd( m_project, pargr ) );
//debugPlan<<"add group:"<<pargr->name();
}
r = new Resource();
r->setName( s );
cmd->addCommand( new AddResourceCmd( pargr, r ) );
//debugPlan<<"add resource:"<<r->name();
emit executeCommand( cmd );
cmd = 0;
} else {
pargr = r->parentGroup();
//debugPlan<<"add '"<<r->name()<<"' to group:"<<pargr;
}
// add request
ResourceGroupRequest *g = node->resourceGroupRequest( pargr );
if ( g == 0 ) {
g = groupmap.value( pargr );
}
if ( g == 0 ) {
// create a group request
if ( cmd == 0 ) cmd = new MacroCommand( c );
g = new ResourceGroupRequest( pargr );
cmd->addCommand( new AddResourceGroupRequestCmd( *task, g ) );
groupmap.insert( pargr, g );
//debugPlan<<"add group request:"<<g;
}
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new AddResourceRequestCmd( g, new ResourceRequest( r, r->units() ) ) );
//debugPlan<<"add request:"<<r->name()<<" group:"<<g;
}
}
if ( cmd ) {
emit executeCommand( cmd );
return true;
}
}
}
return false;
}
bool NodeItemModel::setCompletion( Node *node, const QVariant &value, int role )
{
debugPlan<<node->name()<<value<<role;
if ( role != Qt::EditRole ) {
return 0;
}
if ( node->type() == Node::Type_Task ) {
Completion &c = static_cast<Task*>( node )->completion();
QDateTime dt = QDateTime::currentDateTime();
QDate date = dt.date();
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify completion" ) );
if ( ! c.isStarted() ) {
m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionStartedCmd( c, true ) );
}
m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, value.toInt() ) );
if ( value.toInt() == 100 ) {
m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionFinishedCmd( c, true ) );
}
emit executeCommand( m ); // also adds a new entry if necessary
if ( c.entrymode() == Completion::EnterCompleted ) {
Duration planned = static_cast<Task*>( node )->plannedEffort( m_nodemodel.id() );
Duration actual = ( planned * value.toInt() ) / 100;
debugPlan<<planned.toString()<<value.toInt()<<actual.toString();
NamedCommand *cmd = new ModifyCompletionActualEffortCmd( c, date, actual );
cmd->execute();
m->addCommand( cmd );
cmd = new ModifyCompletionRemainingEffortCmd( c, date, planned - actual );
cmd->execute();
m->addCommand( cmd );
}
return true;
}
if ( node->type() == Node::Type_Milestone ) {
Completion &c = static_cast<Task*>( node )->completion();
if ( value.toInt() > 0 ) {
QDateTime dt = QDateTime::currentDateTime();
QDate date = dt.date();
MacroCommand *m = new MacroCommand( kundo2_i18n( "Set finished" ) );
m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionStartedCmd( c, true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionFinishedCmd( c, true ) );
m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, 100 ) );
emit executeCommand( m ); // also adds a new entry if necessary
return true;
}
return false;
}
return false;
}
QVariant NodeItemModel::data( const QModelIndex &index, int role ) const
{
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
Node *n = node( index );
if ( role == Role::Object ) {
return n ? QVariant::fromValue( static_cast<QObject*>( n ) ) : QVariant();
}
QVariant result;
if ( n != 0 ) {
result = m_nodemodel.data( n, index.column(), role );
//debugPlan<<n->name()<<": "<<index.column()<<", "<<role<<result;
}
if ( role == Qt::EditRole ) {
switch ( index.column() ) {
case NodeModel::NodeActualStart:
case NodeModel::NodeActualFinish:
if ( ! result.isValid() ) {
return QDateTime::currentDateTime();
}
break;
}
}
return result;
}
bool NodeItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags(index) &Qt::ItemIsEditable ) == 0 || role != Qt::EditRole ) {
warnPlan<<index<<value<<role;
return false;
}
Node *n = node( index );
if ( n ) {
switch ( index.column() ) {
case NodeModel::NodeCompleted: return setCompletion( n, value, role );
case NodeModel::NodeAllocation: return setAllocation( n, value, role );
default: {
KUndo2Command *c = m_nodemodel.setData( n, index.column(), value, role );
if ( c ) {
emit executeCommand( c );
return true;
}
break;
}
}
}
return false;
}
QVariant NodeItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if (role == Qt::DisplayRole || role == Qt::TextAlignmentRole || role == Qt::EditRole) {
return m_nodemodel.headerData(section, role);
}
}
if ( role == Qt::ToolTipRole ) {
return NodeModel::headerData( section, role );
} else if ( role == Qt::WhatsThisRole ) {
return NodeModel::headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QAbstractItemDelegate *NodeItemModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
//case NodeModel::NodeAllocation: return new ??Delegate( parent );
case NodeModel::NodeEstimateType: return new EnumDelegate( parent );
case NodeModel::NodeEstimateCalendar: return new EnumDelegate( parent );
case NodeModel::NodeEstimate: return new DurationSpinBoxDelegate( parent );
case NodeModel::NodeOptimisticRatio: return new SpinBoxDelegate( parent );
case NodeModel::NodePessimisticRatio: return new SpinBoxDelegate( parent );
case NodeModel::NodeRisk: return new EnumDelegate( parent );
case NodeModel::NodeConstraint: return new EnumDelegate( parent );
case NodeModel::NodeConstraintStart: return new DateTimeCalendarDelegate( parent );
case NodeModel::NodeConstraintEnd: return new DateTimeCalendarDelegate( parent );
case NodeModel::NodeRunningAccount: return new EnumDelegate( parent );
case NodeModel::NodeStartupAccount: return new EnumDelegate( parent );
// case NodeModel::NodeStartupCost: return new MoneyDelegate( parent );
case NodeModel::NodeShutdownAccount: return new EnumDelegate( parent );
// case NodeModel::NodeShutdownCost: return new MoneyDelegate( parent );
case NodeModel::NodeCompleted: return new TaskCompleteDelegate( parent );
case NodeModel::NodeRemainingEffort: return new DurationSpinBoxDelegate( parent );
case NodeModel::NodeActualEffort: return new DurationSpinBoxDelegate( parent );
default: return 0;
}
return 0;
}
int NodeItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return m_nodemodel.propertyCount();
}
int NodeItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_projectshown && ! parent.isValid() ) {
return m_project == 0 ? 0 : 1;
}
Node *p = node( parent );
return p == 0 ? 0 : p->numChildren();
}
Qt::DropActions NodeItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList NodeItemModel::mimeTypes() const
{
return QStringList() << "application/x-vnd.kde.plan.nodeitemmodel.internal"
<< "application/x-vnd.kde.plan.resourceitemmodel.internal"
<< "application/x-vnd.kde.plan.project"
<< "text/uri-list";
}
QMimeData *NodeItemModel::mimeData( const QModelIndexList & indexes ) const
{
QMimeData *m = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QList<int> rows;
foreach (const QModelIndex &index, indexes) {
if ( index.isValid() && !rows.contains( index.row() ) ) {
//debugPlan<<index.row();
Node *n = node( index );
if ( n ) {
rows << index.row();
stream << n->id();
}
}
}
m->setData("application/x-vnd.kde.plan.nodeitemmodel.internal", encodedData);
return m;
}
bool NodeItemModel::dropAllowed( const QModelIndex &index, int dropIndicatorPosition, const QMimeData *data )
{
debugPlan;
if ( m_projectshown && ! index.isValid() ) {
return false;
}
Node *dn = node( index ); // returns project if ! index.isValid()
if ( dn == 0 ) {
errorPlan<<"no node (or project) to drop on!";
return false; // hmmm
}
if ( data->hasFormat("application/x-vnd.kde.plan.resourceitemmodel.internal") ) {
switch ( dropIndicatorPosition ) {
case ItemModelBase::OnItem:
if ( index.column() == NodeModel::NodeAllocation ) {
debugPlan<<"resource:"<<index<<(dn->type() == Node::Type_Task);
return dn->type() == Node::Type_Task;
} else if ( index.column() == NodeModel::NodeResponsible ) {
debugPlan<<"resource:"<<index<<true;
return true;
}
break;
default:
break;
}
} else if ( data->hasFormat( "application/x-vnd.kde.plan.nodeitemmodel.internal")
|| data->hasFormat( "application/x-vnd.kde.plan.project" )
|| data->hasUrls() )
{
switch ( dropIndicatorPosition ) {
case ItemModelBase::AboveItem:
case ItemModelBase::BelowItem:
// dn == sibling, if not project
if ( dn == m_project ) {
return dropAllowed( dn, data );
}
return dropAllowed( dn->parentNode(), data );
case ItemModelBase::OnItem:
// dn == new parent
return dropAllowed( dn, data );
default:
break;
}
} else {
debugPlan<<"Unknown mimetype";
}
return false;
}
QList<Resource*> NodeItemModel::resourceList( QDataStream &stream )
{
QList<Resource*> lst;
while (!stream.atEnd()) {
QString id;
stream >> id;
debugPlan<<"id"<<id;
Resource *r = m_project->findResource( id );
if ( r ) {
lst << r;
}
}
debugPlan<<lst;
return lst;
}
bool NodeItemModel::dropAllowed( Node *on, const QMimeData *data )
{
if ( ! m_projectshown && on == m_project ) {
return true;
}
if ( on->isBaselined() && on->type() != Node::Type_Summarytask ) {
return false;
}
if ( data->hasFormat( "application/x-vnd.kde.plan.nodeitemmodel.internal" ) ) {
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.nodeitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QList<Node*> lst = nodeList( stream );
foreach ( Node *n, lst ) {
if ( n->type() == Node::Type_Project || on == n || on->isChildOf( n ) ) {
return false;
}
}
lst = removeChildNodes( lst );
foreach ( Node *n, lst ) {
if ( ! m_project->canMoveTask( n, on ) ) {
return false;
}
}
}
return true;
}
QList<Node*> NodeItemModel::nodeList( QDataStream &stream )
{
QList<Node*> lst;
while (!stream.atEnd()) {
QString id;
stream >> id;
Node *node = m_project->findNode( id );
if ( node ) {
lst << node;
}
}
return lst;
}
QList<Node*> NodeItemModel::removeChildNodes( const QList<Node*> &nodes )
{
QList<Node*> lst;
foreach ( Node *node, nodes ) {
bool ins = true;
foreach ( Node *n, lst ) {
if ( node->isChildOf( n ) ) {
//debugPlan<<node->name()<<" is child of"<<n->name();
ins = false;
break;
}
}
if ( ins ) {
//debugPlan<<" insert"<<node->name();
lst << node;
}
}
QList<Node*> nl = lst;
QList<Node*> nlst = lst;
foreach ( Node *node, nl ) {
foreach ( Node *n, nlst ) {
if ( n->isChildOf( node ) ) {
//debugPlan<<n->name()<<" is child of"<<node->name();
int i = nodes.indexOf( n );
lst.removeAt( i );
}
}
}
return lst;
}
bool NodeItemModel::dropResourceMimeData( const QMimeData *data, Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex &parent )
{
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.resourceitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
Node *n = node( parent );
debugPlan<<n<<parent;
if ( n == 0 ) {
return true;
}
debugPlan<<n->name();
if ( parent.column() == NodeModel::NodeResponsible ) {
QString s;
foreach ( Resource *r, resourceList( stream ) ) {
s += r->name();
}
if ( ! s.isEmpty() ) {
if ( action == Qt::CopyAction && ! n->leader().isEmpty() ) {
s += ',' + n->leader();
}
KUndo2Command *cmd = m_nodemodel.setLeader( n, s, Qt::EditRole );
if ( cmd ) {
emit executeCommand( cmd );
}
debugPlan<<s;
}
return true;
}
if ( n->type() == Node::Type_Task ) {
QList<Resource*> lst = resourceList( stream );
if ( action == Qt::CopyAction ) {
lst += static_cast<Task*>( n )->requestedResources();
}
KUndo2Command *cmd = createAllocationCommand( static_cast<Task&>( *n ), lst );
if ( cmd ) {
emit executeCommand( cmd );
}
return true;
}
return true;
}
bool NodeItemModel::dropProjectMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent )
{
Node *n = node( parent );
if ( n == 0 ) {
n = m_project;
}
debugPlan<<n<<action<<row<<parent;
KoXmlDocument doc;
doc.setContent( data->data( "application/x-vnd.kde.plan.project" ) );
KoXmlElement element = doc.documentElement().namedItem( "project" ).toElement();
Project project;
XMLLoaderObject status;
status.setVersion( doc.documentElement().attribute( "version", PLAN_FILE_SYNTAX_VERSION ) );
status.setProject( &project );
if ( ! project.load( element, status ) ) {
debugPlan<<"Failed to load project";
return false;
}
project.generateUniqueNodeIds();
KUndo2Command *cmd = new InsertProjectCmd( project, n, n->childNode( row - 1 ), kundo2_i18nc("1=project or task name", "Insert %1", project.name() ) );
emit executeCommand( cmd );
return true;
}
bool NodeItemModel::dropUrlMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
if ( data->hasUrls() ) {
QList<QUrl> urls = data->urls();
debugPlan<<urls;
foreach ( const QUrl &url, urls ) {
const QMimeType mime = QMimeDatabase().mimeTypeForUrl( url );
debugPlan<<url<<mime.name();
if ( mime.inherits( "application/x-vnd.kde.plan" ) ) {
importProjectFile( url, action, row, column, parent );
}
}
return true;
}
return false;
}
bool NodeItemModel::importProjectFile( const QUrl &url, Qt::DropAction /*action*/, int row, int /*column*/, const QModelIndex &parent )
{
if ( ! url.isLocalFile() ) {
debugPlan<<"TODO: download if url not local";
return false;
}
KoStore *store = KoStore::createStore( url.path(), KoStore::Read, "", KoStore::Auto );
if ( store->bad() ) {
// d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file );
debugPlan<<"bad store"<<url.toDisplayString();
delete store;
// QApplication::restoreOverrideCursor();
return false;
}
if ( ! store->open( "root" ) ) { // maindoc.xml
debugPlan<<"No root"<<url.toDisplayString();
delete store;
return false;
}
KoXmlDocument doc;
doc.setContent( store->device() );
KoXmlElement element = doc.documentElement().namedItem( "project" ).toElement();
Project project;
XMLLoaderObject status;
status.setVersion( doc.documentElement().attribute( "version", PLAN_FILE_SYNTAX_VERSION ) );
status.setProject( &project );
if ( ! project.load( element, status ) ) {
debugPlan<<"Failed to load project from:"<<url;
return false;
}
project.generateUniqueNodeIds();
Node *n = node( parent );
debugPlan<<n<<parent;
if ( n == 0 ) {
n = m_project;
}
KUndo2Command *cmd = new InsertProjectCmd( project, n, n->childNode( row - 1 ), kundo2_i18n( "Insert %1", url.fileName() ) );
emit executeCommand( cmd );
return true;
}
KUndo2Command *NodeItemModel::createAllocationCommand( Task &task, const QList<Resource*> &lst )
{
MacroCommand *cmd = new MacroCommand( kundo2_i18n( "Modify resource allocations" ) );
QHash<ResourceGroup*, ResourceGroupRequest*> groups;
foreach ( Resource *r, lst ) {
if ( ! groups.contains( r->parentGroup() ) && task.resourceGroupRequest( r->parentGroup() ) == 0 ) {
ResourceGroupRequest *gr = new ResourceGroupRequest( r->parentGroup() );
groups[ r->parentGroup() ] = gr;
cmd->addCommand( new AddResourceGroupRequestCmd( task, gr ) );
}
}
QList<Resource*> resources = task.requestedResources();
foreach ( Resource *r, lst ) {
if ( resources.contains( r ) ) {
continue;
}
ResourceGroupRequest *gr = groups.value( r->parentGroup() );
if ( gr == 0 ) {
gr = task.resourceGroupRequest( r->parentGroup() );
}
if ( gr == 0 ) {
errorPlan<<"No group request found, cannot add resource request:"<<r->name();
continue;
}
cmd->addCommand( new AddResourceRequestCmd( gr, new ResourceRequest( r, 100 ) ) );
}
foreach ( Resource *r, resources ) {
if ( ! lst.contains( r ) ) {
ResourceGroupRequest *gr = task.resourceGroupRequest( r->parentGroup() );
ResourceRequest *rr = task.requests().find( r );
if ( gr && rr ) {
cmd->addCommand( new RemoveResourceRequestCmd( gr, rr ) );
}
}
}
if ( cmd->isEmpty() ) {
delete cmd;
return 0;
}
return cmd;
}
bool NodeItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
debugPlan<<action;
if (action == Qt::IgnoreAction) {
return true;
}
if ( data->hasFormat( "application/x-vnd.kde.plan.resourceitemmodel.internal" ) ) {
return dropResourceMimeData( data, action, row, column, parent );
}
if ( data->hasFormat( "application/x-vnd.kde.plan.nodeitemmodel.internal" ) ) {
if ( action == Qt::MoveAction ) {
//debugPlan<<"MoveAction";
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.nodeitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
Node *par = 0;
if ( parent.isValid() ) {
par = node( parent );
} else {
par = m_project;
}
QList<Node*> lst = nodeList( stream );
QList<Node*> nodes = removeChildNodes( lst ); // children goes with their parent
foreach ( Node *n, nodes ) {
if ( ! m_project->canMoveTask( n, par ) ) {
//debugPlan<<"Can't move task:"<<n->name();
return false;
}
}
int offset = 0;
MacroCommand *cmd = 0;
foreach ( Node *n, nodes ) {
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Move tasks" ) );
// append nodes if dropped *on* another node, insert if dropped *after*
int pos = row == -1 ? -1 : row + offset;
if ( pos >= 0 && n->parentNode() == par && par->indexOf( n ) < pos ) {
--pos;
}
if ( n->parentNode() == par ) {
// avoid drop into the same position, QAbstractItemModel does not like it
int crow = par->indexOf( n );
if ( ( ( pos == -1 ) && ( crow == par->numChildren() - 1 ) ) || ( pos == crow ) ) {
delete cmd;
cmd = 0;
continue;
}
}
cmd->addCommand( new NodeMoveCmd( m_project, n, par, pos ) );
offset++;
}
if ( cmd ) {
emit executeCommand( cmd );
}
//debugPlan<<row<<","<<column<<" parent="<<parent.row()<<","<<parent.column()<<":"<<par->name();
return true;
}
}
if ( data->hasFormat( "application/x-vnd.kde.plan.project" ) ) {
debugPlan;
return dropProjectMimeData( data, action, row, column, parent );
}
if ( data->hasUrls() ) {
return dropUrlMimeData( data, action, row, column, parent );
}
return false;
}
Node *NodeItemModel::node( const QModelIndex &index ) const
{
Node *n = m_project;
if ( index.isValid() ) {
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
n = static_cast<Node*>( index.internalPointer() );
Q_ASSERT( n );
}
return n;
}
void NodeItemModel::slotNodeChanged( Node *node )
{
if ( node == 0 || ( ! m_projectshown && node->type() == Node::Type_Project ) ) {
return;
}
if ( node->type() == Node::Type_Project ) {
emit dataChanged( createIndex( 0, 0, node ), createIndex( 0, columnCount()-1, node ) );
return;
}
int row = node->parentNode()->findChildNode( node );
Q_ASSERT( row >= 0 );
emit dataChanged( createIndex( row, 0, node ), createIndex( row, columnCount()-1, node ) );
}
QModelIndex NodeItemModel::insertTask( Node *node, Node *after )
{
MacroCommand *cmd = new MacroCommand( kundo2_i18n( "Add task" ) );
cmd->addCommand( new TaskAddCmd( m_project, node, after ) );
if ( m_project && node->type() == Node::Type_Task ) {
QHash<ResourceGroup*, ResourceGroupRequest*> groups;
foreach ( Resource *r, m_project->autoAllocateResources() ) {
if ( ! groups.contains( r->parentGroup() ) ) {
ResourceGroupRequest *gr = new ResourceGroupRequest( r->parentGroup() );
cmd->addCommand( new AddResourceGroupRequestCmd( static_cast<Task&>(*node), gr ) );
groups[ r->parentGroup() ] = gr;
}
ResourceRequest *rr = new ResourceRequest( r, 100 );
cmd->addCommand( new AddResourceRequestCmd( groups[ r->parentGroup() ], rr ) );
}
}
emit executeCommand( cmd );
int row = -1;
if ( node->parentNode() ) {
row = node->parentNode()->indexOf( node );
}
if ( row != -1 ) {
//debugPlan<<"Inserted: "<<node->name()<<"; "<<row;
return createIndex( row, 0, node );
}
//debugPlan<<"Can't find "<<node->name();
return QModelIndex();
}
QModelIndex NodeItemModel::insertSubtask( Node *node, Node *parent )
{
emit executeCommand( new SubtaskAddCmd( m_project, node, parent, kundo2_i18n( "Add sub-task" ) ) );
int row = -1;
if ( node->parentNode() ) {
row = node->parentNode()->indexOf( node );
}
if ( row != -1 ) {
//debugPlan<<node->parentNode()<<" inserted: "<<node->name()<<"; "<<row;
return createIndex( row, 0, node );
}
//debugPlan<<"Can't find "<<node->name();
return QModelIndex();
}
int NodeItemModel::sortRole( int column ) const
{
int v = Qt::DisplayRole;
switch ( column ) {
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime:
case NodeModel::NodeActualStart:
case NodeModel::NodeActualFinish:
case NodeModel::NodeEarlyStart:
case NodeModel::NodeEarlyFinish:
case NodeModel::NodeLateStart:
case NodeModel::NodeLateFinish:
case NodeModel::NodeConstraintStart:
case NodeModel::NodeConstraintEnd:
v = Qt::EditRole;
break;
case NodeModel::NodeWBSCode:
v = NodeModel::SortableRole;
break;
default:
break;
}
debugPlan<<column<<v;
return v;
}
//------------------------------------------------
GanttItemModel::GanttItemModel( QObject *parent )
: NodeItemModel( parent ),
m_showSpecial( false )
{
}
GanttItemModel::~GanttItemModel()
{
QList<void*> lst = parentmap.values();
while ( ! lst.isEmpty() )
delete (int*)(lst.takeFirst());
}
int GanttItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_showSpecial ) {
if (parentmap.values().contains(parent.internalPointer())) { // clazy:exclude=container-anti-pattern
return 0;
}
Node *n = node( parent );
if ( n && n->type() == Node::Type_Task ) {
return 5; // the task + early start + late finish ++
}
}
return NodeItemModel::rowCount( parent );
}
QModelIndex GanttItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_showSpecial && parent.isValid() ) {
Node *p = node( parent );
if ( p->type() == Node::Type_Task ) {
void *v = 0;
foreach ( void *i, parentmap.values( p ) ) { // clazy:exclude=container-anti-pattern
if ( *( (int*)( i ) ) == row ) {
v = i;
break;
}
}
if ( v == 0 ) {
v = new int( row );
const_cast<GanttItemModel*>( this )->parentmap.insertMulti( p, v );
}
return createIndex( row, column, v );
}
}
return NodeItemModel::index( row, column, parent );
}
QModelIndex GanttItemModel::parent( const QModelIndex &idx ) const
{
if ( m_showSpecial ) {
QList<Node*> lst = parentmap.keys( idx.internalPointer() );
if ( ! lst.isEmpty() ) {
Q_ASSERT( lst.count() == 1 );
return index( lst.first() );
}
}
return NodeItemModel::parent( idx );
}
QVariant GanttItemModel::data( const QModelIndex &index, int role ) const
{
if ( ! index.isValid() ) {
return QVariant();
}
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
QModelIndex idx = index;
QList<Node*> lst;
if ( m_showSpecial ) {
lst = parentmap.keys( idx.internalPointer() );
}
if ( ! lst.isEmpty() ) {
Q_ASSERT( lst.count() == 1 );
int row = *((int*)(idx.internalPointer()));
Node *n = lst.first();
if ( role == SpecialItemTypeRole ) {
return row; // 0=task, 1=early start, 2=late finish...
}
switch ( row ) {
case 0: // the task
if ( idx.column() == NodeModel::NodeType && role == KGantt::ItemTypeRole ) {
switch ( n->type() ) {
case Node::Type_Task: return KGantt::TypeTask;
default: break;
}
}
break;
case 1: { // early start
if ( role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole ) {
return QVariant();
}
switch ( idx.column() ) {
case NodeModel::NodeName: return "Early Start";
case NodeModel::NodeType: return KGantt::TypeEvent;
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime: return n->earlyStart( id() );
default: break;
}
return QVariant();
}
case 2: { // late finish
if ( role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole ) {
return QVariant();
}
switch ( idx.column() ) {
case NodeModel::NodeName: return "Late Finish";
case NodeModel::NodeType: return KGantt::TypeEvent;
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime: return n->lateFinish( id() );
default: break;
}
return QVariant();
}
case 3: { // late start
if ( role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole ) {
return QVariant();
}
switch ( idx.column() ) {
case NodeModel::NodeName: return "Late Start";
case NodeModel::NodeType: return KGantt::TypeEvent;
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime: return n->lateStart( id() );
default: break;
}
return QVariant();
}
case 4: { // early finish
if ( role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole ) {
return QVariant();
}
switch ( idx.column() ) {
case NodeModel::NodeName: return "Early Finish";
case NodeModel::NodeType: return KGantt::TypeEvent;
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime: return n->earlyFinish( id() );
default: break;
}
return QVariant();
}
default: return QVariant();
}
idx = createIndex( idx.row(), idx.column(), n );
} else {
if ( role == SpecialItemTypeRole ) {
return 0; // task of some type
}
if ( idx.column() == NodeModel::NodeType && role == KGantt::ItemTypeRole ) {
QModelIndex notScheduled = idx.sibling(idx.row(), NodeModel::NodeNotScheduled);
if (notScheduled.data(Qt::EditRole).toBool()) {
return QVariant();
}
QVariant result = NodeItemModel::data( idx, Qt::EditRole );
switch ( result.toInt() ) {
case Node::Type_Project: return KGantt::TypeSummary;
case Node::Type_Summarytask: return KGantt::TypeSummary;
case Node::Type_Milestone: return KGantt::TypeEvent;
default: return m_showSpecial ? KGantt::TypeMulti : KGantt::TypeTask;
}
}
}
return NodeItemModel::data( idx, role );
}
//----------------------------
MilestoneItemModel::MilestoneItemModel( QObject *parent )
: ItemModelBase( parent )
{
}
MilestoneItemModel::~MilestoneItemModel()
{
}
QList<Node*> MilestoneItemModel::mileStones() const
{
QList<Node*> lst;
foreach( Node* n, m_nodemap ) {
if ( n->type() == Node::Type_Milestone ) {
lst << n;
}
}
return lst;
}
void MilestoneItemModel::slotNodeToBeInserted( Node *parent, int row )
{
Q_UNUSED(parent);
Q_UNUSED(row);
}
void MilestoneItemModel::slotNodeInserted( Node *node )
{
Q_UNUSED(node);
resetModel();
}
void MilestoneItemModel::slotNodeToBeRemoved( Node *node )
{
Q_UNUSED(node);
//debugPlan<<node->name();
/* int row = m_nodemap.values().indexOf( node );
if ( row != -1 ) {
Q_ASSERT( m_nodemap.contains( node->wbsCode() ) );
Q_ASSERT( m_nodemap.keys().indexOf( node->wbsCode() ) == row );
beginRemoveRows( QModelIndex(), row, row );
m_nodemap.remove( node->wbsCode() );
endRemoveRows();
}*/
}
void MilestoneItemModel::slotNodeRemoved( Node *node )
{
Q_UNUSED(node);
resetModel();
//endRemoveRows();
}
void MilestoneItemModel::slotLayoutChanged()
{
//debugPlan<<node->name();
emit layoutAboutToBeChanged();
emit layoutChanged();
}
void MilestoneItemModel::slotNodeToBeMoved( Node *node, int pos, Node *newParent, int newPos )
{
Q_UNUSED( node );
Q_UNUSED( pos );
Q_UNUSED( newParent );
Q_UNUSED( newPos );
}
void MilestoneItemModel::slotNodeMoved( Node *node )
{
Q_UNUSED( node );
resetModel();
}
void MilestoneItemModel::setProject( Project *project )
{
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &MilestoneItemModel::projectDeleted);
disconnect( m_project, SIGNAL(localeChanged()), this, SLOT(slotLayoutChanged()));
disconnect( m_project, &Project::wbsDefinitionChanged, this, &MilestoneItemModel::slotWbsDefinitionChanged);
disconnect( m_project, &Project::nodeChanged, this, &MilestoneItemModel::slotNodeChanged);
disconnect( m_project, &Project::nodeToBeAdded, this, &MilestoneItemModel::slotNodeToBeInserted);
disconnect( m_project, &Project::nodeToBeRemoved, this, &MilestoneItemModel::slotNodeToBeRemoved);
disconnect(m_project, &Project::nodeToBeMoved, this, &MilestoneItemModel::slotNodeToBeMoved);
disconnect(m_project, &Project::nodeMoved, this, &MilestoneItemModel::slotNodeMoved);
disconnect( m_project, &Project::nodeAdded, this, &MilestoneItemModel::slotNodeInserted);
disconnect( m_project, &Project::nodeRemoved, this, &MilestoneItemModel::slotNodeRemoved);
}
m_project = project;
//debugPlan<<m_project<<"->"<<project;
m_nodemodel.setProject( project );
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &MilestoneItemModel::projectDeleted);
connect( m_project, SIGNAL(localeChanged()), this, SLOT(slotLayoutChanged()));
connect( m_project, &Project::wbsDefinitionChanged, this, &MilestoneItemModel::slotWbsDefinitionChanged);
connect( m_project, &Project::nodeChanged, this, &MilestoneItemModel::slotNodeChanged);
connect( m_project, &Project::nodeToBeAdded, this, &MilestoneItemModel::slotNodeToBeInserted);
connect( m_project, &Project::nodeToBeRemoved, this, &MilestoneItemModel::slotNodeToBeRemoved);
connect(m_project, &Project::nodeToBeMoved, this, &MilestoneItemModel::slotNodeToBeMoved);
connect(m_project, &Project::nodeMoved, this, &MilestoneItemModel::slotNodeMoved);
connect( m_project, &Project::nodeAdded, this, &MilestoneItemModel::slotNodeInserted);
connect( m_project, &Project::nodeRemoved, this, &MilestoneItemModel::slotNodeRemoved);
}
resetModel();
}
void MilestoneItemModel::setScheduleManager( ScheduleManager *sm )
{
if ( m_nodemodel.manager() ) {
}
m_nodemodel.setManager( sm );
ItemModelBase::setScheduleManager( sm );
if ( sm ) {
}
//debugPlan<<sm;
resetModel();
}
bool MilestoneItemModel::resetData()
{
int cnt = m_nodemap.count();
m_nodemap.clear();
if ( m_project != 0 ) {
foreach ( Node *n, m_project->allNodes() ) {
m_nodemap.insert( n->wbsCode(true), n );
}
}
return cnt != m_nodemap.count();
}
void MilestoneItemModel::resetModel()
{
beginResetModel();
resetData();
endResetModel();
}
Qt::ItemFlags MilestoneItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( !index.isValid() ) {
if ( m_readWrite ) {
flags |= Qt::ItemIsDropEnabled;
}
return flags;
}
if ( m_readWrite ) {
flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
switch ( index.column() ) {
case NodeModel::NodeName: // name
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeType: break; // Node type
case NodeModel::NodeResponsible: // Responsible
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeConstraint: // constraint type
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeConstraintStart: { // constraint start
Node *n = node( index );
if ( n == 0 )
break;
int c = n->constraint();
if ( c == Node::MustStartOn || c == Node::StartNotEarlier || c == Node::FixedInterval ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeConstraintEnd: { // constraint end
Node *n = node( index );
if ( n == 0 )
break;
int c = n->constraint();
if ( c == Node::MustFinishOn || c == Node::FinishNotLater || c == Node::FixedInterval ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeStartupAccount: // startup account
case NodeModel::NodeStartupCost: // startup cost
case NodeModel::NodeShutdownAccount: // shutdown account
case NodeModel::NodeShutdownCost: { // shutdown cost
Node *n = node( index );
if ( n && (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) ) {
flags |= Qt::ItemIsEditable;
}
break;
}
case NodeModel::NodeDescription: // description
break;
default:
flags &= ~Qt::ItemIsEditable;
}
}
return flags;
}
QModelIndex MilestoneItemModel::parent( const QModelIndex &index ) const
{
Q_UNUSED(index);
return QModelIndex();
}
QModelIndex MilestoneItemModel::index( int row, int column, const QModelIndex &parent ) const
{
//debugPlan<<parent<<row<<", "<<m_nodemap.count();
if ( m_project == 0 || row < 0 || column < 0 ) {
//debugPlan<<"No project"<<m_project<<" or illegal row, column"<<row<<column;
return QModelIndex();
}
if ( parent.isValid() || row >= m_nodemap.count() ) {
//debugPlan<<"No index for"<<parent<<row<<","<<column;
return QModelIndex();
}
return createIndex( row, column, m_nodemap.values().at( row ) ); // clazy:exclude=container-anti-pattern
}
QModelIndex MilestoneItemModel::index( const Node *node ) const
{
if ( m_project == 0 || node == 0 ) {
return QModelIndex();
}
return createIndex( m_nodemap.values().indexOf( const_cast<Node*>( node ) ), 0, const_cast<Node*>(node) ); // clazy:exclude=container-anti-pattern
}
QVariant MilestoneItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
Node *n = node( index );
if ( n != 0 ) {
if ( index.column() == NodeModel::NodeType && role == KGantt::ItemTypeRole ) {
result = m_nodemodel.data( n, index.column(), Qt::EditRole );
switch ( result.toInt() ) {
case Node::Type_Summarytask: return KGantt::TypeSummary;
case Node::Type_Milestone: return KGantt::TypeEvent;
default: return KGantt::TypeTask;
}
return result;
}
}
result = m_nodemodel.data( n, index.column(), role );
return result;
}
bool MilestoneItemModel::setData( const QModelIndex &index, const QVariant &/*value*/, int role )
{
if ( ( flags(index) &Qt::ItemIsEditable ) == 0 || role != Qt::EditRole ) {
return false;
}
// Node *n = node( index );
switch (index.column()) {
default:
qWarning("data: invalid display value column %d", index.column());
return false;
}
return false;
}
QVariant MilestoneItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if (role == Qt::DisplayRole || role == Qt::TextAlignmentRole || role == Qt::EditRole) {
return m_nodemodel.headerData(section, role);
}
}
if ( role == Qt::ToolTipRole ) {
return NodeModel::headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QAbstractItemDelegate *MilestoneItemModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
case NodeModel::NodeEstimateType: return new EnumDelegate( parent );
case NodeModel::NodeEstimateCalendar: return new EnumDelegate( parent );
case NodeModel::NodeEstimate: return new DurationSpinBoxDelegate( parent );
case NodeModel::NodeOptimisticRatio: return new SpinBoxDelegate( parent );
case NodeModel::NodePessimisticRatio: return new SpinBoxDelegate( parent );
case NodeModel::NodeRisk: return new EnumDelegate( parent );
case NodeModel::NodeConstraint: return new EnumDelegate( parent );
case NodeModel::NodeRunningAccount: return new EnumDelegate( parent );
case NodeModel::NodeStartupAccount: return new EnumDelegate( parent );
case NodeModel::NodeStartupCost: return new MoneyDelegate( parent );
case NodeModel::NodeShutdownAccount: return new EnumDelegate( parent );
case NodeModel::NodeShutdownCost: return new MoneyDelegate( parent );
case NodeModel::NodeCompleted: return new TaskCompleteDelegate( parent );
case NodeModel::NodeRemainingEffort: return new DurationSpinBoxDelegate( parent );
case NodeModel::NodeActualEffort: return new DurationSpinBoxDelegate( parent );
default: return 0;
}
return 0;
}
int MilestoneItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return m_nodemodel.propertyCount();
}
int MilestoneItemModel::rowCount( const QModelIndex &parent ) const
{
//debugPlan<<parent;
if ( parent.isValid() ) {
return 0;
}
//debugPlan<<m_nodemap.count();
return m_nodemap.count();
}
Qt::DropActions MilestoneItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList MilestoneItemModel::mimeTypes() const
{
return QStringList();
}
QMimeData *MilestoneItemModel::mimeData( const QModelIndexList & indexes ) const
{
QMimeData *m = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QList<int> rows;
foreach (const QModelIndex &index, indexes) {
if ( index.isValid() && !rows.contains( index.row() ) ) {
//debugPlan<<index.row();
Node *n = node( index );
if ( n ) {
rows << index.row();
stream << n->id();
}
}
}
m->setData("application/x-vnd.kde.plan.nodeitemmodel.internal", encodedData);
return m;
}
bool MilestoneItemModel::dropAllowed( const QModelIndex &index, int dropIndicatorPosition, const QMimeData *data )
{
//debugPlan;
Node *dn = node( index );
if ( dn == 0 ) {
errorPlan<<"no node to drop on!";
return false; // hmmm
}
switch ( dropIndicatorPosition ) {
case ItemModelBase::AboveItem:
case ItemModelBase::BelowItem:
// dn == sibling
return dropAllowed( dn->parentNode(), data );
case ItemModelBase::OnItem:
// dn == new parent
return dropAllowed( dn, data );
default:
break;
}
return false;
}
bool MilestoneItemModel::dropAllowed( Node *on, const QMimeData *data )
{
if ( !data->hasFormat("application/x-vnd.kde.plan.nodeitemmodel.internal") ) {
return false;
}
if ( on == m_project ) {
return true;
}
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.nodeitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QList<Node*> lst = nodeList( stream );
foreach ( Node *n, lst ) {
if ( on == n || on->isChildOf( n ) ) {
return false;
}
}
lst = removeChildNodes( lst );
foreach ( Node *n, lst ) {
if ( ! m_project->canMoveTask( n, on ) ) {
return false;
}
}
return true;
}
QList<Node*> MilestoneItemModel::nodeList( QDataStream &stream )
{
QList<Node*> lst;
while (!stream.atEnd()) {
QString id;
stream >> id;
Node *node = m_project->findNode( id );
if ( node ) {
lst << node;
}
}
return lst;
}
QList<Node*> MilestoneItemModel::removeChildNodes( const QList<Node*> &nodes )
{
QList<Node*> lst;
foreach ( Node *node, nodes ) {
bool ins = true;
foreach ( Node *n, lst ) {
if ( node->isChildOf( n ) ) {
//debugPlan<<node->name()<<" is child of"<<n->name();
ins = false;
break;
}
}
if ( ins ) {
//debugPlan<<" insert"<<node->name();
lst << node;
}
}
QList<Node*> nl = lst;
QList<Node*> nlst = lst;
foreach ( Node *node, nl ) {
foreach ( Node *n, nlst ) {
if ( n->isChildOf( node ) ) {
//debugPlan<<n->name()<<" is child of"<<node->name();
int i = nodes.indexOf( n );
lst.removeAt( i );
}
}
}
return lst;
}
bool MilestoneItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent )
{
//debugPlan<<action;
if (action == Qt::IgnoreAction) {
return true;
}
if ( !data->hasFormat( "application/x-vnd.kde.plan.nodeitemmodel.internal" ) ) {
return false;
}
if ( action == Qt::MoveAction ) {
//debugPlan<<"MoveAction";
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.nodeitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
Node *par = 0;
if ( parent.isValid() ) {
par = node( parent );
} else {
par = m_project;
}
QList<Node*> lst = nodeList( stream );
QList<Node*> nodes = removeChildNodes( lst ); // children goes with their parent
foreach ( Node *n, nodes ) {
if ( ! m_project->canMoveTask( n, par ) ) {
//debugPlan<<"Can't move task:"<<n->name();
return false;
}
}
int offset = 0;
MacroCommand *cmd = 0;
foreach ( Node *n, nodes ) {
if ( cmd == 0 ) cmd = new MacroCommand( kundo2_i18n( "Move tasks" ) );
// append nodes if dropped *on* another node, insert if dropped *after*
int pos = row == -1 ? -1 : row + offset;
cmd->addCommand( new NodeMoveCmd( m_project, n, par, pos ) );
offset++;
}
if ( cmd ) {
emit executeCommand( cmd );
}
//debugPlan<<row<<","<<column<<" parent="<<parent.row()<<","<<parent.column()<<":"<<par->name();
return true;
}
return false;
}
Node *MilestoneItemModel::node( const QModelIndex &index ) const
{
Node *n = 0;
if ( index.isValid() ) {
//debugPlan<<index;
n = static_cast<Node*>( index.internalPointer() );
}
return n;
}
void MilestoneItemModel::slotNodeChanged( Node *node )
{
//debugPlan<<node->name();
if ( node == 0 ) {
return;
}
beginResetModel();
resetData();
endResetModel();
}
void MilestoneItemModel::slotWbsDefinitionChanged()
{
//debugPlan;
if ( m_project == 0 ) {
return;
}
if ( ! m_nodemap.isEmpty() ) {
beginResetModel();
resetData();
endResetModel();
}
}
int MilestoneItemModel::sortRole( int column ) const
{
int v = Qt::DisplayRole;
switch ( column ) {
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime:
case NodeModel::NodeActualStart:
case NodeModel::NodeActualFinish:
case NodeModel::NodeEarlyStart:
case NodeModel::NodeEarlyFinish:
case NodeModel::NodeLateStart:
case NodeModel::NodeLateFinish:
case NodeModel::NodeConstraintStart:
case NodeModel::NodeConstraintEnd:
v = Qt::EditRole;
break;
case NodeModel::NodeWBSCode:
v = NodeModel::SortableRole;
break;
default:
break;
}
return v;
}
//--------------
NodeSortFilterProxyModel::NodeSortFilterProxyModel( ItemModelBase* model, QObject *parent, bool filterUnscheduled )
: QSortFilterProxyModel( parent ),
m_filterUnscheduled( filterUnscheduled )
{
setSourceModel( model );
setDynamicSortFilter( true );
}
ItemModelBase *NodeSortFilterProxyModel::itemModel() const
{
return static_cast<ItemModelBase *>( sourceModel() );
}
void NodeSortFilterProxyModel::setFilterUnscheduled( bool on ) {
m_filterUnscheduled = on;
invalidateFilter();
}
bool NodeSortFilterProxyModel::filterAcceptsRow ( int row, const QModelIndex & parent ) const
{
//debugPlan<<sourceModel()<<row<<parent;
if ( itemModel()->project() == 0 ) {
//debugPlan<<itemModel()->project();
return false;
}
if ( m_filterUnscheduled ) {
QString s = sourceModel()->data( sourceModel()->index( row, NodeModel::NodeNotScheduled, parent ), Qt::EditRole ).toString();
if ( s == "true" ) {
//debugPlan<<"Filtered unscheduled:"<<sourceModel()->index( row, 0, parent );
return false;
}
}
bool accepted = QSortFilterProxyModel::filterAcceptsRow( row, parent );
//debugPlan<<this<<sourceModel()->index( row, 0, parent )<<"accepted ="<<accepted<<filterRegExp()<<filterRegExp().isEmpty()<<filterRegExp().capturedTexts();
return accepted;
}
void NodeSortFilterProxyModel::sort(int column, Qt::SortOrder order)
{
setSortRole(itemModel()->sortRole(column));
QSortFilterProxyModel::sort(column, order);
}
//------------------
TaskModuleModel::TaskModuleModel( QObject *parent )
: QAbstractItemModel( parent )
{
}
void TaskModuleModel::addTaskModule( Project *project, const QUrl &url )
{
beginInsertRows( QModelIndex(), m_modules.count(), m_modules.count() );
m_modules << project;
m_urls << url;
endInsertRows();
}
Qt::ItemFlags TaskModuleModel::flags( const QModelIndex &idx ) const
{
Qt::ItemFlags f = QAbstractItemModel::flags( idx ) | Qt::ItemIsDropEnabled;
if ( idx.isValid() ) {
f |= Qt::ItemIsDragEnabled;
}
return f;
}
int TaskModuleModel::columnCount (const QModelIndex &/*idx*/ ) const
{
return 1;
}
int TaskModuleModel::rowCount( const QModelIndex &idx ) const
{
return idx.isValid() ? 0 : m_modules.count();
}
QVariant TaskModuleModel::data( const QModelIndex& idx, int role ) const
{
if (!idx.isValid() || idx.row() >= m_modules.count()) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole: return m_modules.value( idx.row() )->name();
case Qt::ToolTipRole: return m_modules.value( idx.row() )->description();
case Qt::WhatsThisRole: return m_modules.value( idx.row() )->description();
case Qt::UserRole: return m_urls.value(idx.row());
default: break;
}
return QVariant();
}
QVariant TaskModuleModel::headerData( int /*section*/, Qt::Orientation orientation , int role ) const
{
if ( orientation == Qt::Horizontal ) {
switch ( role ) {
case Qt::DisplayRole: return xi18nc( "@title:column", "Name" );
default: break;
}
}
return QVariant();
}
QModelIndex TaskModuleModel::parent( const QModelIndex& /*idx*/ ) const
{
return QModelIndex();
}
QModelIndex TaskModuleModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return QModelIndex();
}
return createIndex( row, column, m_modules.value( row ) );
}
QStringList TaskModuleModel::mimeTypes() const
{
return QStringList() << "application/x-vnd.kde.plan" << "text/uri-list";
}
bool TaskModuleModel::dropMimeData( const QMimeData *data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex &/*parent*/ )
{
if ( data->hasUrls() ) {
QList<QUrl> urls = data->urls();
debugPlan<<urls;
foreach ( const QUrl &url, urls ) {
const QMimeType mime = QMimeDatabase().mimeTypeForUrl( url );
debugPlan<<url<<mime.name();
if ( mime.inherits(QStringLiteral("application/x-vnd.kde.plan")) || mime.inherits(QStringLiteral("application/xml")) ) {
importProject( url );
}
}
return true;
}
return false;
}
bool TaskModuleModel::importProject( const QUrl &url, bool emitsignal )
{
if ( ! url.isLocalFile() ) {
debugPlan<<"TODO: download if url not local";
return false;
}
KoStore *store = KoStore::createStore( url.path(), KoStore::Read, "", KoStore::Auto );
if ( store->bad() ) {
// d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file );
debugPlan<<"bad store"<<url.toDisplayString();
delete store;
// QApplication::restoreOverrideCursor();
return false;
}
if ( ! store->open( "root" ) ) { // maindoc.xml
debugPlan<<"No root"<<url.toDisplayString();
delete store;
return false;
}
KoXmlDocument doc;
doc.setContent( store->device() );
KoXmlElement element = doc.documentElement().namedItem( "project" ).toElement();
Project *project = new Project();
XMLLoaderObject status;
status.setVersion( doc.documentElement().attribute( "version", PLAN_FILE_SYNTAX_VERSION ) );
status.setProject( project );
if ( project->load( element, status ) ) {
stripProject( project );
addTaskModule( project, url );
if ( emitsignal ) {
// FIXME: save destroys the project, so give it a copy (see kptview.cpp)
Project p;
status.setProject( &p );
p.load( element, status );
emit saveTaskModule( url, &p );
}
} else {
debugPlan<<"Failed to load project from:"<<url;
delete project;
return false;
}
return true;
}
QMimeData* TaskModuleModel::mimeData( const QModelIndexList &lst ) const
{
QMimeData *mime = new QMimeData();
if ( lst.count() == 1 ) {
QModelIndex idx = lst.at( 0 );
if ( idx.isValid() ) {
Project *project = m_modules.value( idx.row() );
QDomDocument document( "plan" );
document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "plan" );
doc.setAttribute( "editor", "Plan" );
doc.setAttribute( "mime", "application/x-vnd.kde.plan" );
doc.setAttribute( "version", PLAN_FILE_SYNTAX_VERSION );
document.appendChild( doc );
project->save( doc );
mime->setData( "application/x-vnd.kde.plan.project", document.toByteArray() );
}
}
return mime;
}
void TaskModuleModel::stripProject( Project *project ) const
{
foreach ( ScheduleManager *sm, project->scheduleManagers() ) {
DeleteScheduleManagerCmd c( *project, sm );
}
}
void TaskModuleModel::loadTaskModules( const QStringList &files )
{
debugPlan<<files;
beginResetModel();
m_modules.clear();
m_urls.clear();
foreach ( const QString &file, files ) {
importProject( QUrl::fromLocalFile(file), false );
}
endResetModel();
}
} //namespace KPlato
diff --git a/src/libs/models/kptpertcpmmodel.cpp b/src/libs/models/kptpertcpmmodel.cpp
index 3395933c..c8c82c19 100644
--- a/src/libs/models/kptpertcpmmodel.cpp
+++ b/src/libs/models/kptpertcpmmodel.cpp
@@ -1,880 +1,881 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net>
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 "kptpertcpmmodel.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptnode.h"
#include "kptschedule.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QStringList>
#include <QMimeData>
#include <QLocale>
namespace KPlato
{
class Project;
class Node;
class Task;
typedef QList<Node*> NodeList;
// TODO: find some better values
static const quintptr ListItemId = static_cast<quintptr>(-1);
static const quintptr ProjectItemId = static_cast<quintptr>(-2);
CriticalPathItemModel::CriticalPathItemModel( QObject *parent )
: ItemModelBase( parent ),
m_manager( 0 )
{
/* connect( this, SIGNAL(modelAboutToBeReset()), SLOT(slotAboutToBeReset()) );
connect( this, SIGNAL(modelReset()), SLOT(slotReset()) );*/
}
CriticalPathItemModel::~CriticalPathItemModel()
{
}
void CriticalPathItemModel::slotNodeToBeInserted( Node *, int )
{
//debugPlan<<node->name();
}
void CriticalPathItemModel::slotNodeInserted( Node * /*node*/ )
{
//debugPlan<<node->getParent->name()<<"-->"<<node->name();
}
void CriticalPathItemModel::slotNodeToBeRemoved( Node *node )
{
Q_UNUSED(node);
//debugPlan<<node->name();
/* if ( m_path.contains( node ) ) {
}*/
}
void CriticalPathItemModel::slotNodeRemoved( Node *node )
{
Q_UNUSED(node);
//debugPlan<<node->name();
}
void CriticalPathItemModel::setProject( Project *project )
{
beginResetModel();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &CriticalPathItemModel::projectDeleted);
disconnect( m_project, &Project::nodeChanged, this, &CriticalPathItemModel::slotNodeChanged );
disconnect( m_project, &Project::nodeToBeAdded, this, &CriticalPathItemModel::slotNodeToBeInserted );
disconnect( m_project, &Project::nodeToBeRemoved, this, &CriticalPathItemModel::slotNodeToBeRemoved );
disconnect( m_project, &Project::nodeToBeMoved, this, &CriticalPathItemModel::slotLayoutToBeChanged );
disconnect( m_project, &Project::nodeAdded, this, &CriticalPathItemModel::slotNodeInserted );
disconnect( m_project, &Project::nodeRemoved, this, &CriticalPathItemModel::slotNodeRemoved );
disconnect( m_project, &Project::nodeMoved, this, &CriticalPathItemModel::slotLayoutChanged );
}
m_project = project;
m_nodemodel.setProject( project );
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &CriticalPathItemModel::projectDeleted);
connect( m_project, &Project::nodeChanged, this, &CriticalPathItemModel::slotNodeChanged );
connect( m_project, &Project::nodeToBeAdded, this, &CriticalPathItemModel::slotNodeToBeInserted );
connect( m_project, &Project::nodeToBeRemoved, this, &CriticalPathItemModel::slotNodeToBeRemoved );
connect( m_project, &Project::nodeToBeMoved, this, &CriticalPathItemModel::slotLayoutToBeChanged );
connect( m_project, &Project::nodeAdded, this, &CriticalPathItemModel::slotNodeInserted );
connect( m_project, &Project::nodeRemoved, this, &CriticalPathItemModel::slotNodeRemoved );
connect( m_project, &Project::nodeMoved, this, &CriticalPathItemModel::slotLayoutChanged );
}
endResetModel();
}
void CriticalPathItemModel::setManager( ScheduleManager *sm )
{
beginResetModel();
debugPlan<<this;
m_manager = sm;
m_nodemodel.setManager( sm );
if ( m_project == 0 || m_manager == 0 ) {
m_path.clear();
} else {
m_path = m_project->criticalPath( m_manager->scheduleId(), 0 );
}
debugPlan<<m_path;
endResetModel();
}
QModelIndex CriticalPathItemModel::parent( const QModelIndex & ) const
{
return QModelIndex();
}
QModelIndex CriticalPathItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
if ( parent.isValid() ) {
return QModelIndex();
}
Node *n = m_path.value( row );
QModelIndex i = createIndex(row, column, n );
return i;
}
Duration::Unit CriticalPathItemModel::presentationUnit( const Duration &dur ) const
{
if ( dur.toDouble( Duration::Unit_d ) < 1.0 ) {
return Duration::Unit_h;
}
return Duration::Unit_d;
}
QVariant CriticalPathItemModel::name( int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return i18n( "Path" );
case Qt::ToolTipRole:
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CriticalPathItemModel::duration( int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
Duration v = m_project->duration( m_manager->scheduleId() );
return QVariant(QLocale().toString( v.toDouble( presentationUnit( v ) ), 'f', 1 ) + Duration::unitToString( presentationUnit( v ) ));
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CriticalPathItemModel::variance( int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
double v = 0.0;
foreach ( Node *n, m_path ) {
long id = m_manager->scheduleId();
v += n->variance( id, presentationUnit( m_project->duration( id ) ) );
}
return QLocale().toString( v, 'f', 1 );
break;
}
case Qt::EditRole: {
double v = 0.0;
foreach ( Node *n, m_path ) {
v += n->variance( m_manager->scheduleId() );
}
return v;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CriticalPathItemModel::notUsed( int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return "";
default:
return QVariant();
}
return QVariant();
}
QVariant CriticalPathItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
if ( ! index.isValid() ) {
return result;
}
if ( role == Qt::TextAlignmentRole ) {
return alignment( index.column() );
}
Node *n = node( index );
if ( n == 0 ) {
switch ( index.column() ) {
case NodeModel::NodeName: result = name( role ); break;
case NodeModel::NodeDuration: result = duration( role ); break;
case NodeModel::NodeVarianceDuration: result = variance( role ); break;
default:
result = notUsed( role ); break;
}
} else {
result = m_nodemodel.data( n, index.column(), role );
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return result;
}
QVariant CriticalPathItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
return m_nodemodel.headerData( section, role );
} else if ( role == Qt::TextAlignmentRole ) {
return alignment( section );
}
}
if ( role == Qt::ToolTipRole ) {
return m_nodemodel.headerData( section, role );
} else if ( role == Qt::WhatsThisRole ) {
return m_nodemodel.headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QVariant CriticalPathItemModel::alignment( int column ) const
{
return m_nodemodel.headerData( column, Qt::TextAlignmentRole );
}
int CriticalPathItemModel::columnCount( const QModelIndex & ) const
{
return m_nodemodel.propertyCount();
}
int CriticalPathItemModel::rowCount( const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return 0;
}
if ( m_manager && m_manager->expected() && m_manager->expected()->criticalPathList() ) {
return m_path.count() + 1;
}
return 0;
}
Node *CriticalPathItemModel::node( const QModelIndex &index ) const
{
if ( ! index.isValid() ) {
return 0;
}
return m_path.value( index.row() );
}
void CriticalPathItemModel::slotNodeChanged( Node *node )
{
debugPlan;
if ( node == 0 || node->type() == Node::Type_Project || ! m_path.contains( node ) ) {
return;
}
int row = m_path.indexOf( node );
emit dataChanged( createIndex( row, 0, node ), createIndex( row, columnCount() - 1, node ) );
}
//-----------------------------
PertResultItemModel::PertResultItemModel( QObject *parent )
: ItemModelBase( parent ),
m_manager( 0 )
{
/* connect( this, SIGNAL(modelAboutToBeReset()), SLOT(slotAboutToBeReset()) );
connect( this, SIGNAL(modelReset()), SLOT(slotReset()) );*/
}
PertResultItemModel::~PertResultItemModel()
{
}
void PertResultItemModel::slotAboutToBeReset()
{
debugPlan;
clear();
}
void PertResultItemModel::slotReset()
{
debugPlan;
refresh();
}
void PertResultItemModel::slotNodeToBeInserted( Node *, int )
{
//debugPlan<<node->name();
clear();
}
void PertResultItemModel::slotNodeInserted( Node * /*node*/ )
{
//debugPlan<<node->getParent->name()<<"-->"<<node->name();
refresh();
}
void PertResultItemModel::slotNodeToBeRemoved( Node * /*node*/ )
{
//debugPlan<<node->name();
clear();
}
void PertResultItemModel::slotNodeRemoved( Node * /*node*/ )
{
//debugPlan<<node->name();
refresh();
}
void PertResultItemModel::setProject( Project *project )
{
clear();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &PertResultItemModel::projectDeleted);
disconnect( m_project, &Project::nodeChanged, this, &PertResultItemModel::slotNodeChanged );
disconnect( m_project, &Project::nodeToBeAdded, this, &PertResultItemModel::slotNodeToBeInserted );
disconnect( m_project, &Project::nodeToBeRemoved, this, &PertResultItemModel::slotNodeToBeRemoved );
disconnect( m_project, &Project::nodeToBeMoved, this, &PertResultItemModel::slotLayoutToBeChanged );
disconnect( m_project, &Project::nodeAdded, this, &PertResultItemModel::slotNodeInserted );
disconnect( m_project, &Project::nodeRemoved, this, &PertResultItemModel::slotNodeRemoved );
disconnect( m_project, &Project::nodeMoved, this, &PertResultItemModel::slotLayoutChanged );
}
m_project = project;
m_nodemodel.setProject( project );
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &PertResultItemModel::projectDeleted);
connect( m_project, &Project::nodeChanged, this, &PertResultItemModel::slotNodeChanged );
connect( m_project, &Project::nodeToBeAdded, this, &PertResultItemModel::slotNodeToBeInserted );
connect( m_project, &Project::nodeToBeRemoved, this, &PertResultItemModel::slotNodeToBeRemoved );
connect( m_project, &Project::nodeToBeMoved, this, &PertResultItemModel::slotLayoutToBeChanged );
connect( m_project, &Project::nodeAdded, this, &PertResultItemModel::slotNodeInserted );
connect( m_project, &Project::nodeRemoved, this, &PertResultItemModel::slotNodeRemoved );
connect( m_project, &Project::nodeMoved, this, &PertResultItemModel::slotLayoutChanged );
}
refresh();
}
void PertResultItemModel::setManager( ScheduleManager *sm )
{
m_manager = sm;
m_nodemodel.setManager( sm );
refresh();
}
void PertResultItemModel::clear()
{
debugPlan<<this;
foreach ( NodeList *l, m_top ) {
int c = l->count();
if ( c > 0 ) {
// FIXME: gives error msg:
// Can't select indexes from different model or with different parents
QModelIndex i = index( l );
debugPlan<<i<<": "<<c;
// beginRemoveRows( i, 0, c-1 );
// endRemoveRows();
}
}
m_critical.clear();
m_noncritical.clear();
if ( ! m_top.isEmpty() ) {
beginRemoveRows( QModelIndex(), 0, m_top.count() - 1 );
m_top.clear();
m_topNames.clear();
endRemoveRows();
}
}
void PertResultItemModel::refresh()
{
clear();
if ( m_project == 0 ) {
return;
}
long id = m_manager == 0 ? -1 : m_manager->scheduleId();
debugPlan<<id;
if ( id == -1 ) {
return;
}
m_topNames << i18n( "Project" );
m_top << &m_dummyList; // dummy
const QList< NodeList > *lst = m_project->criticalPathList( id );
if ( lst ) {
for ( int i = 0; i < lst->count(); ++i ) {
m_topNames << i18n( "Critical Path" );
m_top.append( const_cast<NodeList*>( &( lst->at( i ) ) ) );
debugPlan<<m_topNames.last()<<lst->at( i );
}
if ( lst->isEmpty() ) debugPlan<<"No critical path";
}
foreach( Node* n, m_project->allNodes() ) {
if ( n->type() != Node::Type_Task && n->type() != Node::Type_Milestone ) {
continue;
}
Task *t = static_cast<Task*>( n );
if ( t->inCriticalPath( id ) ) {
continue;
} else if ( t->isCritical( id ) ) {
m_critical.append( t );
} else {
m_noncritical.append( t );
}
}
if ( ! m_critical.isEmpty() ) {
m_topNames << i18n( "Critical" );
m_top.append(&m_critical );
}
if ( ! m_noncritical.isEmpty() ) {
m_topNames << i18n( "Non-critical" );
m_top.append(&m_noncritical );
}
if ( ! m_top.isEmpty() ) {
debugPlan<<m_top;
beginInsertRows( QModelIndex(), 0, m_top.count() -1 );
endInsertRows();
foreach ( NodeList *l, m_top ) {
int c = l->count();
if ( c > 0 ) {
beginInsertRows( index( l ), 0, c-1 );
endInsertRows();
}
}
}
}
Qt::ItemFlags PertResultItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
flags &= ~( Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
return flags;
}
QModelIndex PertResultItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<": "<<index.row()<<", "<<index.column();
int row = index.internalId();
if ( row < 0 ) {
return QModelIndex(); // top level has no parent
}
if ( m_top.value( row ) == 0 ) {
return QModelIndex();
}
return createIndex( row, 0, ListItemId );
}
QModelIndex PertResultItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
if ( ! parent.isValid() ) {
if ( row == 0 ) {
QModelIndex idx = createIndex(row, column, ProjectItemId ); // project
return idx;
}
if ( row >= m_top.count() ) {
return QModelIndex(); // shouldn't happened
}
QModelIndex idx = createIndex(row, column, ListItemId );
//debugPlan<<parent<<", "<<idx;
return idx;
}
if ( parent.row() == 0 ) {
return QModelIndex();
}
NodeList *l = m_top.value( parent.row() );
if ( l == 0 ) {
return QModelIndex();
}
QModelIndex i = createIndex(row, column, parent.row() );
return i;
}
// QModelIndex PertResultItemModel::index( const Node *node ) const
// {
// if ( m_project == 0 || node == 0 ) {
// return QModelIndex();
// }
// foreach( NodeList *l, m_top ) {
// int row = l->indexOf( const_cast<Node*>( node ) );
// if ( row != -1 ) {
// return createIndex( row, 0, const_cast<Node*>( node ) );
// }
// }
// return QModelIndex();
// }
QModelIndex PertResultItemModel::index( const NodeList *lst ) const
{
if ( m_project == 0 || lst == 0 ) {
return QModelIndex();
}
NodeList *l = const_cast<NodeList*>( lst );
int row = m_top.indexOf( l );
if ( row <= 0 ) {
return QModelIndex();
}
return createIndex( row, 0, ListItemId );
}
QVariant PertResultItemModel::name( int row, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return m_topNames.value( row );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::name( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return node->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::earlyStart( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->earlyStart( m_manager->scheduleId() );
case Qt::ToolTipRole:
return QLocale().toString( node->earlyStart( m_manager->scheduleId() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::earlyFinish( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->earlyFinish( m_manager->scheduleId() );
case Qt::ToolTipRole:
return QLocale().toString( node->earlyFinish( m_manager->scheduleId() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::lateStart( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->lateStart( m_manager->scheduleId() );
case Qt::ToolTipRole:
return QLocale().toString( node->lateStart( m_manager->scheduleId() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::lateFinish( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->lateFinish( m_manager->scheduleId() );
case Qt::ToolTipRole:
return QLocale().toString( node->lateFinish( m_manager->scheduleId() ).date(), QLocale::ShortFormat );
case Qt::EditRole:
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::positiveFloat( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->positiveFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return node->positiveFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::freeFloat( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->freeFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return node->freeFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::negativeFloat( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->negativeFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return node->negativeFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::startFloat( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->startFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return node->startFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::finishFloat( const Task *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return node->finishFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nHourFraction );
case Qt::ToolTipRole:
return node->finishFloat( m_manager->scheduleId() ).toString( Duration::Format_i18nDayTime );
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant PertResultItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
if ( ! index.isValid() ) {
return result;
}
if ( role == Qt::TextAlignmentRole ) {
return alignment( index.column() );
}
Node *n = node( index );
if ( n == 0 ) {
switch ( index.column() ) {
case 0: return name( index.row(), role );
default: break;
}
return QVariant();
}
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
result = m_nodemodel.data( n, index.column(), role );
}
if ( n->type() == Node::Type_Project ) {
//Project *p = static_cast<Project*>( n );
switch ( index.column() ) {
case NodeModel::NodeName: result = name( NodeModel::NodeName, role ); break;
default:
//debugPlan<<"data: invalid display value column "<<index.column();
return QVariant();
}
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return QVariant();
}
QVariant PertResultItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
return m_nodemodel.headerData( section, role );
} else if ( role == Qt::TextAlignmentRole ) {
return alignment( section );
}
}
if ( role == Qt::ToolTipRole ) {
return m_nodemodel.headerData( section, role );
} else if ( role == Qt::WhatsThisRole ) {
return m_nodemodel.headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QVariant PertResultItemModel::alignment( int column ) const
{
return m_nodemodel.headerData( column, Qt::TextAlignmentRole );
}
QAbstractItemDelegate *PertResultItemModel::createDelegate( int column, QWidget * /*parent*/ ) const
{
switch ( column ) {
default: return 0;
}
return 0;
}
int PertResultItemModel::columnCount( const QModelIndex & ) const
{
return m_nodemodel.propertyCount();
}
int PertResultItemModel::rowCount( const QModelIndex &parent ) const
{
if ( ! parent.isValid() ) {
//debugPlan<<"top="<<m_top.count();
return m_top.count();
}
NodeList *l = list( parent );
if ( l ) {
//debugPlan<<"list "<<parent.row()<<": "<<l->count();
return l->count();
}
//debugPlan<<"node "<<parent.row();
return 0; // nodes don't have children
}
Qt::DropActions PertResultItemModel::supportedDropActions() const
{
return (Qt::DropActions)Qt::CopyAction | Qt::MoveAction;
}
QStringList PertResultItemModel::mimeTypes() const
{
return QStringList();
}
QMimeData *PertResultItemModel::mimeData( const QModelIndexList & ) const
{
QMimeData *m = new QMimeData();
return m;
}
bool PertResultItemModel::dropAllowed( Node *, const QMimeData * )
{
return false;
}
bool PertResultItemModel::dropMimeData( const QMimeData *, Qt::DropAction , int , int , const QModelIndex & )
{
return false;
}
NodeList *PertResultItemModel::list( const QModelIndex &index ) const
{
if ( index.isValid() && index.internalId() == ListItemId ) {
//debugPlan<<index<<"is list: "<<m_top.value( index.row() );
return m_top.value( index.row() );
}
//debugPlan<<index<<"is not list";
return 0;
}
Node *PertResultItemModel::node( const QModelIndex &index ) const
{
if ( ! index.isValid() ) {
return 0;
}
if ( index.internalId() == ProjectItemId ) {
return m_project;
}
if ( index.internalId() == 0 ) {
return 0;
}
NodeList *l = m_top.value( index.internalId() );
if ( l ) {
return l->value( index.row() );
}
return 0;
}
void PertResultItemModel::slotNodeChanged( Node *)
{
debugPlan;
refresh();
/* if ( node == 0 || node->type() == Node::Type_Project ) {
return;
}
int row = node->getParent()->findChildNode( node );
emit dataChanged( createIndex( row, 0, node ), createIndex( row, columnCount() - 1, node ) );*/
}
} // namespace KPlato
diff --git a/src/libs/models/kptrelationmodel.cpp b/src/libs/models/kptrelationmodel.cpp
index d880800c..3e1e98c1 100644
--- a/src/libs/models/kptrelationmodel.cpp
+++ b/src/libs/models/kptrelationmodel.cpp
@@ -1,454 +1,455 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptrelationmodel.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptduration.h"
#include "kptproject.h"
#include "kptnode.h"
#include "kptrelation.h"
#include "kptdebug.h"
#include <QModelIndex>
#include <QWidget>
#include <QLocale>
namespace KPlato
{
QVariant RelationModel::parentName( const Relation *r, int role ) const
{
//debugPlan<<r<<", "<<role<<endl;
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
case Qt::EditRole:
return r->parent()->name();
case Qt::TextAlignmentRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant RelationModel::childName( const Relation *r, int role ) const
{
//debugPlan<<r<<", "<<role<<endl;
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
case Qt::EditRole:
return r->child()->name();
case Qt::TextAlignmentRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant RelationModel::type( const Relation *r, int role ) const
{
//debugPlan<<r<<", "<<role<<endl;
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return r->typeToString( true );
case Role::EnumList:
return r->typeList( true );
case Qt::EditRole:
case Role::EnumListValue:
return (int)r->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant RelationModel::lag( const Relation *r, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
Duration::Unit unit = Duration::Unit_h;
return QVariant(QLocale().toString( r->lag().toDouble( unit ), 'f', 1 ) + Duration::unitToString( unit, true ));
}
case Qt::EditRole:
return r->lag().toDouble( Duration::Unit_h );
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant RelationModel::data( const Relation *r, int property, int role ) const
{
QVariant result;
switch ( property ) {
case 0: result = parentName( r, role ); break;
case 1: result = childName( r, role ); break;
case 2: result = type( r, role ); break;
case 3: result = lag( r, role ); break;
default:
//debugPlan<<"Invalid property number: "<<property<<endl;
return result;
}
return result;
}
int RelationModel::propertyCount()
{
return 4;
}
QVariant RelationModel::headerData( int section, int role )
{
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case 0: return i18n( "Parent" );
case 1: return i18n( "Child" );
case 2: return i18n( "Type" );
case 3: return i18n( "Lag" );
default: return QVariant();
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case 0: return ToolTip::relationParent();
case 1: return ToolTip::relationChild();
case 2: return ToolTip::relationType();
case 3: return ToolTip::relationLag();
default: return QVariant();
}
}
return QVariant();
}
//----------------------------
RelationItemModel::RelationItemModel( QObject *parent )
: ItemModelBase( parent ),
m_node( 0 ),
m_removedRelation( 0 )
{
}
RelationItemModel::~RelationItemModel()
{
}
void RelationItemModel::slotRelationToBeAdded( Relation *relation, int, int )
{
debugPlan;
if ( m_node == 0 || m_node != relation->child() ) {
return;
}
// relations always appended
int row = rowCount();
beginInsertRows( QModelIndex(), row, row );
}
void RelationItemModel::slotRelationAdded( Relation *relation )
{
debugPlan;
if ( m_node == 0 || m_node != relation->child() ) {
return;
}
endInsertRows();
}
void RelationItemModel::slotRelationToBeRemoved( Relation *relation )
{
if ( m_node == 0 || ! m_node->dependParentNodes().contains( relation ) ) {
return;
}
m_removedRelation = relation;
int row = m_node->dependParentNodes().indexOf( relation );
debugPlan<<row;
beginRemoveRows( QModelIndex(), row, row );
}
void RelationItemModel::slotRelationRemoved( Relation *relation )
{
debugPlan;
if ( m_removedRelation != relation ) {
return;
}
m_removedRelation = 0;
endRemoveRows();
}
void RelationItemModel::slotRelationModified( Relation *relation )
{
debugPlan;
if ( m_node == 0 || ! m_node->dependParentNodes().contains( relation ) ) {
return;
}
int row = m_node->dependParentNodes().indexOf( relation );
emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount()-1 ) );
}
void RelationItemModel::slotNodeToBeRemoved( Node *node )
{
if ( node != m_node ) {
return;
}
setNode( 0 );
}
void RelationItemModel::slotNodeRemoved( Node *node )
{
Q_UNUSED(node);
}
void RelationItemModel::slotLayoutChanged()
{
//debugPlan<<node->name()<<endl;
emit layoutAboutToBeChanged();
emit layoutChanged();
}
void RelationItemModel::setProject( Project *project )
{
beginResetModel();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &RelationItemModel::projectDeleted);
disconnect( m_project, &Project::nodeChanged, this, &RelationItemModel::slotNodeChanged );
disconnect( m_project, &Project::nodeToBeRemoved, this, &RelationItemModel::slotNodeToBeRemoved );
disconnect( m_project, &Project::relationToBeAdded, this, &RelationItemModel::slotRelationToBeAdded );
disconnect( m_project, &Project::relationAdded, this, &RelationItemModel::slotRelationAdded );
disconnect( m_project, &Project::relationToBeRemoved, this, &RelationItemModel::slotRelationToBeRemoved );
disconnect( m_project, &Project::relationRemoved, this, &RelationItemModel::slotRelationRemoved );
disconnect( m_project, &Project::relationModified, this, &RelationItemModel::slotRelationModified );
}
m_project = project;
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &RelationItemModel::projectDeleted);
connect( m_project, &Project::nodeChanged, this, &RelationItemModel::slotNodeChanged );
connect( m_project, &Project::nodeToBeRemoved, this, &RelationItemModel::slotNodeToBeRemoved );
connect( m_project, &Project::relationToBeAdded, this, &RelationItemModel::slotRelationToBeAdded );
connect( m_project, &Project::relationAdded, this, &RelationItemModel::slotRelationAdded );
connect( m_project, &Project::relationToBeRemoved, this, &RelationItemModel::slotRelationToBeRemoved );
connect( m_project, &Project::relationRemoved, this, &RelationItemModel::slotRelationRemoved );
connect( m_project, &Project::relationModified, this, &RelationItemModel::slotRelationModified );
}
endResetModel();
}
void RelationItemModel::setNode( Node *node )
{
beginResetModel();
m_node = node;
endResetModel();
}
Qt::ItemFlags RelationItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( !index.isValid() ) {
if ( m_readWrite ) {
flags |= Qt::ItemIsDropEnabled;
}
return flags;
}
if ( m_readWrite ) {
flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
switch ( index.column() ) {
case 2: // type
flags |= Qt::ItemIsEditable;
break;
case 3: // lag
flags |= Qt::ItemIsEditable;
break;
default:
flags &= ~Qt::ItemIsEditable;
break;
}
}
return flags;
}
QModelIndex RelationItemModel::parent( const QModelIndex &/*index*/ ) const
{
return QModelIndex(); // flat model
}
QModelIndex RelationItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 ) {
return QModelIndex();
}
if ( parent.isValid() ) {
return QModelIndex(); // flat model
}
return createIndex( row, column );
}
bool RelationItemModel::setType( Relation *r, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
Relation::Type v = Relation::Type( value.toInt() );
//debugPlan<<v<<r->type();
if ( v == r->type() ) {
return false;
}
emit executeCommand( new ModifyRelationTypeCmd( r, v, kundo2_i18n("Modify relation type") ) );
return true;
}
return false;
}
bool RelationItemModel::setLag( Relation *r, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration d( value.toList()[0].toDouble(), unit );
debugPlan<<value.toList()[0].toDouble()<<","<<unit<<" ->"<<d.toString();
if ( d == r->lag() ) {
return false;
}
emit executeCommand( new ModifyRelationLagCmd( r, d, kundo2_i18n( "Modify relation time lag" ) ) );
return true;
}
default:
break;
}
return false;
}
QVariant RelationItemModel::data( const QModelIndex &index, int role ) const
{
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
QVariant result;
Relation *r = relation( index );
if ( r != 0 ) {
result = m_relationmodel.data( r, index.column(), role );
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return result;
}
bool RelationItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags(index) & Qt::ItemIsEditable ) == 0 || role != Qt::EditRole ) {
return false;
}
Relation *r = relation( index );
switch (index.column()) {
case 0: return false;
case 1: return false;
case 2: return setType( r, value, role );
case 3: return setLag( r, value, role );
default:
qWarning("data: invalid display value column %d", index.column());
return false;
}
return false;
}
QVariant RelationItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
return m_relationmodel.headerData( section, role );
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case 2: return Qt::AlignCenter;
case 3: return Qt::AlignRight;
default: return QVariant();
}
}
}
if ( role == Qt::ToolTipRole ) {
return RelationModel::headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QAbstractItemDelegate *RelationItemModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
case 2: return new EnumDelegate( parent );
case 3: return new DurationSpinBoxDelegate( parent );
default: return 0;
}
return 0;
}
int RelationItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return m_relationmodel.propertyCount();
}
int RelationItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 || m_node == 0 || parent.isValid() ) {
return 0;
}
return m_node->numDependParentNodes();
}
Relation *RelationItemModel::relation( const QModelIndex &index ) const
{
if ( ! index.isValid() || m_node == 0 ) {
return 0;
}
return m_node->dependParentNodes().value( index.row() );
}
void RelationItemModel::slotNodeChanged( Node *node )
{
Q_UNUSED(node);
beginResetModel();
endResetModel();
}
} //namespace KPlato
diff --git a/src/libs/models/kptresourceallocationmodel.cpp b/src/libs/models/kptresourceallocationmodel.cpp
index 11dce9cb..0710ba2d 100644
--- a/src/libs/models/kptresourceallocationmodel.cpp
+++ b/src/libs/models/kptresourceallocationmodel.cpp
@@ -1,1076 +1,1077 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2012 Dag Andersen danders@get2net>
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 "kptresourceallocationmodel.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QStringList>
namespace KPlato
{
//--------------------------------------
ResourceAllocationModel::ResourceAllocationModel( QObject *parent )
: QObject( parent ),
m_project( 0 ),
m_task( 0 )
{
}
ResourceAllocationModel::~ResourceAllocationModel()
{
}
const QMetaEnum ResourceAllocationModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
void ResourceAllocationModel::setProject( Project *project )
{
m_project = project;
}
void ResourceAllocationModel::setTask( Task *task )
{
m_task = task;
}
int ResourceAllocationModel::propertyCount() const
{
return columnMap().keyCount();
}
QVariant ResourceAllocationModel::name( const Resource *res, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationModel::name( const ResourceGroup *res, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->name();
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationModel::type( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->typeToString( true );
case Role::EnumList:
return res->typeToStringList( true );
case Role::EnumListValue:
return (int)res->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationModel::type( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->typeToString( true );
case Role::EnumList:
return res->typeToStringList( true );
case Role::EnumListValue:
return (int)res->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationModel::allocation( const ResourceGroup *group, const Resource *res, int role ) const
{
if ( m_project == 0 || m_task == 0 ) {
return QVariant();
}
const ResourceGroupRequest *rg = m_task->requests().find( group );
const ResourceRequest *rr = 0;
if ( rg ) {
rr = rg->find( res );
}
switch ( role ) {
case Qt::DisplayRole: {
int units = rr ? rr->units() : 0;
// xgettext: no-c-format
return i18nc( "<value>%", "%1%", units );
}
case Qt::EditRole:
return rr ? rr->units() : 0;
case Qt::ToolTipRole: {
int units = rr ? rr->units() : 0;
if ( units == 0 ) {
return xi18nc( "@info:tooltip", "Not allocated" );
}
// xgettext: no-c-format
return xi18nc( "@info:tooltip", "Allocated units: %1%", units );
}
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Minimum:
return 0;
case Role::Maximum:
return 100;
case Qt::CheckStateRole:
return Qt::Unchecked;
}
return QVariant();
}
QVariant ResourceAllocationModel::allocation( const ResourceGroup *res, int role ) const
{
if ( m_project == 0 || m_task == 0 ) {
return QVariant();
}
const ResourceGroupRequest *req = m_task->requests().find( res );
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return req ? req->units() : 0;
case Qt::ToolTipRole:
return QVariant();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Minimum:
return 0;
case Role::Maximum:
return res->numResources();
}
return QVariant();
}
QVariant ResourceAllocationModel::maximum( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
// xgettext: no-c-format
return i18nc( "<value>%", "%1%", res->units() );
case Qt::EditRole:
return res->units();
case Qt::ToolTipRole:
// xgettext: no-c-format
return i18n( "Maximum units available: %1%", res->units() );
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationModel::required( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
QStringList lst;
foreach ( Resource *r, res->requiredResources() ) {
lst << r->name();
}
return lst.join( "," );
}
case Qt::EditRole:
return QVariant();//Not used
case Qt::ToolTipRole:
return QVariant();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
return QVariant();
case Qt::WhatsThisRole:
return xi18nc( "@info:whatsthis", "<title>Required Resources</title>"
"<para>A working resource can be assigned to one or more required resources."
" A required resource is a material resource that the working resource depends on"
" in order to do the work.</para>"
"<para>To be able to use a material resource as a required resource, the material resource"
" must be part of a group of type <emphasis>Material</emphasis>.</para>" );
}
return QVariant();
}
QVariant ResourceAllocationModel::maximum( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return res->numResources();
case Qt::ToolTipRole:
return i18np( "There is %1 resource available in this group", "There are %1 resources available in this group", res->numResources() );
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationModel::data( const ResourceGroup *group, const Resource *resource, int property, int role ) const
{
QVariant result;
if ( resource == 0 ) {
return result;
}
switch ( property ) {
case RequestName: result = name( resource, role ); break;
case RequestType: result = type( resource, role ); break;
case RequestAllocation: result = allocation( group, resource, role ); break;
case RequestMaximum: result = maximum( resource, role ); break;
case RequestRequired: result = required( resource, role ); break;
default:
debugPlan<<"data: invalid display value: property="<<property;
break;
}
return result;
}
QVariant ResourceAllocationModel::data( const ResourceGroup *group, int property, int role ) const
{
QVariant result;
if ( group == 0 ) {
return result;
}
switch ( property ) {
case RequestName: result = name( group, role ); break;
case RequestType: result = type( group, role ); break;
case RequestAllocation: result = allocation( group, role ); break;
case RequestMaximum: result = maximum( group, role ); break;
default:
if ( role == Qt::DisplayRole ) {
if ( property < propertyCount() ) {
result = QString();
} else {
debugPlan<<"data: invalid display value column"<<property;
return QVariant();
}
}
break;
}
return result;
}
QVariant ResourceAllocationModel::headerData( int section, int role )
{
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case RequestName: return i18n( "Name" );
case RequestType: return i18n( "Type" );
case RequestAllocation: return i18n( "Allocation" );
case RequestMaximum: return xi18nc( "@title:column", "Available" );
case RequestRequired: return xi18nc( "@title:column", "Required Resources" );
default: return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case 0: return QVariant();
default: return Qt::AlignCenter;
}
} else if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case RequestName: return ToolTip::resourceName();
case RequestType: return ToolTip::resourceType();
case RequestAllocation: return i18n( "Resource allocation" );
case RequestMaximum: return xi18nc( "@info:tootip", "Available resources or resource units" );
case RequestRequired: return xi18nc( "@info:tootip", "Required material resources" );
default: return QVariant();
}
} else if ( role == Qt::WhatsThisRole ) {
switch ( section ) {
case RequestRequired:
return xi18nc( "@info:whatsthis", "<title>Required Resources</title>"
"<para>A working resource can be assigned to one or more required resources."
" A required resource is a material resource that the working resource depends on"
" in order to do the work.</para>"
"<para>To be able to use a material resource as a required resource, the material resource"
" must be part of a group of type Material.</para>" );
default: return QVariant();
}
}
return QVariant();
}
//--------------------------------------
ResourceAllocationItemModel::ResourceAllocationItemModel( QObject *parent )
: ItemModelBase( parent )
{
}
ResourceAllocationItemModel::~ResourceAllocationItemModel()
{
}
void ResourceAllocationItemModel::slotResourceToBeInserted( const ResourceGroup *group, int row )
{
//debugPlan<<group->name()<<","<<row;
beginInsertRows( index( group ), row, row );
}
void ResourceAllocationItemModel::slotResourceInserted( const Resource */*resource */)
{
//debugPlan<<resource->name();
endInsertRows();
emit layoutChanged(); //HACK to make the right view react! Bug in qt?
}
void ResourceAllocationItemModel::slotResourceToBeRemoved( const Resource *resource )
{
//debugPlan<<resource->name();
int row = index( resource ).row();
beginRemoveRows( index( resource->parentGroup() ), row, row );
}
void ResourceAllocationItemModel::slotResourceRemoved( const Resource */*resource */)
{
//debugPlan<<resource->name();
endRemoveRows();
}
void ResourceAllocationItemModel::slotResourceGroupToBeInserted( const ResourceGroup */*group*/, int row )
{
//debugPlan<<group->name();
beginInsertRows( QModelIndex(), row, row );
}
void ResourceAllocationItemModel::slotResourceGroupInserted( const ResourceGroup */*group */)
{
//debugPlan<<group->name();
endInsertRows();
}
void ResourceAllocationItemModel::slotResourceGroupToBeRemoved( const ResourceGroup *group )
{
//debugPlan<<group->name();
int row = index( group ).row();
beginRemoveRows( QModelIndex(), row, row );
}
void ResourceAllocationItemModel::slotResourceGroupRemoved( const ResourceGroup */*group */)
{
//debugPlan<<group->name();
endRemoveRows();
}
void ResourceAllocationItemModel::setProject( Project *project )
{
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ResourceAllocationItemModel::projectDeleted);
disconnect( m_project, &Project::resourceChanged, this, &ResourceAllocationItemModel::slotResourceChanged );
disconnect( m_project, &Project::resourceGroupChanged, this, &ResourceAllocationItemModel::slotResourceGroupChanged );
disconnect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceAllocationItemModel::slotResourceGroupToBeInserted );
disconnect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceAllocationItemModel::slotResourceGroupToBeRemoved );
disconnect( m_project, &Project::resourceToBeAdded, this, &ResourceAllocationItemModel::slotResourceToBeInserted );
disconnect( m_project, &Project::resourceToBeRemoved, this, &ResourceAllocationItemModel::slotResourceToBeRemoved );
disconnect( m_project, &Project::resourceGroupAdded, this, &ResourceAllocationItemModel::slotResourceGroupInserted );
disconnect( m_project, &Project::resourceGroupRemoved, this, &ResourceAllocationItemModel::slotResourceGroupRemoved );
disconnect( m_project, &Project::resourceAdded, this, &ResourceAllocationItemModel::slotResourceInserted );
disconnect( m_project, &Project::resourceRemoved, this, &ResourceAllocationItemModel::slotResourceRemoved );
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ResourceAllocationItemModel::projectDeleted);
connect( m_project, &Project::resourceChanged, this, &ResourceAllocationItemModel::slotResourceChanged );
connect( m_project, &Project::resourceGroupChanged, this, &ResourceAllocationItemModel::slotResourceGroupChanged );
connect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceAllocationItemModel::slotResourceGroupToBeInserted );
connect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceAllocationItemModel::slotResourceGroupToBeRemoved );
connect( m_project, &Project::resourceToBeAdded, this, &ResourceAllocationItemModel::slotResourceToBeInserted );
connect( m_project, &Project::resourceToBeRemoved, this, &ResourceAllocationItemModel::slotResourceToBeRemoved );
connect( m_project, &Project::resourceGroupAdded, this, &ResourceAllocationItemModel::slotResourceGroupInserted );
connect( m_project, &Project::resourceGroupRemoved, this, &ResourceAllocationItemModel::slotResourceGroupRemoved );
connect( m_project, &Project::resourceAdded, this, &ResourceAllocationItemModel::slotResourceInserted );
connect( m_project, &Project::resourceRemoved, this, &ResourceAllocationItemModel::slotResourceRemoved );
}
m_model.setProject( m_project );
}
void ResourceAllocationItemModel::setTask( Task *task )
{
if ( task == m_model.task() ) {
return;
}
if ( m_model.task() == 0 ) {
beginResetModel();
filldata( task );
m_model.setTask( task );
endResetModel();
return;
}
if ( task ) {
emit layoutAboutToBeChanged();
filldata( task );
m_model.setTask( task );
emit layoutChanged();
}
}
void ResourceAllocationItemModel::filldata( Task *task )
{
qDeleteAll( m_resourceCache );
m_resourceCache.clear();
qDeleteAll( m_groupCache );
m_groupCache.clear();
m_requiredChecked.clear();
if ( m_project && task ) {
foreach ( const ResourceGroup *g, m_project->resourceGroups() ) {
const ResourceGroupRequest *gr = task->requests().find( g );
if ( gr ) {
m_groupCache[ g ] = new ResourceGroupRequest( *gr );
}
}
foreach ( const Resource *r, m_project->resourceList() ) {
const ResourceRequest *rr = task->requests().find( r );
if ( rr ) {
m_resourceCache[ r ] = new ResourceRequest( *rr );
if ( ! m_resourceCache[ r ]->requiredResources().isEmpty() ) {
m_requiredChecked[ r ] = Qt::Checked;
}
}
}
}
}
bool ResourceAllocationItemModel::hasMaterialResources() const
{
if ( ! m_project ) {
return false;
}
foreach ( const ResourceGroup *g, m_project->resourceGroups() ) {
if ( g->type() == ResourceGroup::Type_Material ) {
foreach ( const Resource *r, g->resources() ) {
if ( r->type() == Resource::Type_Material ) {
return true;
}
}
}
}
return false;
}
Qt::ItemFlags ResourceAllocationItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = ItemModelBase::flags( index );
if ( !m_readWrite ) {
//debugPlan<<"read only"<<flags;
return flags &= ~Qt::ItemIsEditable;
}
if ( !index.isValid() ) {
//debugPlan<<"invalid"<<flags;
return flags;
}
switch ( index.column() ) {
case ResourceAllocationModel::RequestAllocation:
flags |= ( Qt::ItemIsEditable | Qt::ItemIsUserCheckable );
break;
case ResourceAllocationModel::RequestRequired: {
Resource *r = resource( index );
if ( r && r->type() != Resource::Type_Work ) {
flags &= ~( Qt::ItemIsEditable | Qt::ItemIsUserCheckable );
} else if ( m_resourceCache.contains( r ) && m_resourceCache[ r ]->units() > 0 ) {
flags |= ( Qt::ItemIsEditable | Qt::ItemIsUserCheckable );
if ( ! hasMaterialResources() ) {
flags &= ~Qt::ItemIsEnabled;
}
}
break;
}
default:
flags &= ~Qt::ItemIsEditable;
break;
}
return flags;
}
QModelIndex ResourceAllocationItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() || m_project == 0 ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
Resource *r = qobject_cast<Resource*>( object( index ) );
if ( r && r->parentGroup() ) {
// only resources have parent
int row = m_project->indexOf( r->parentGroup() );
return createIndex( row, 0, r->parentGroup() );
}
return QModelIndex();
}
QModelIndex ResourceAllocationItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
if ( ! parent.isValid() ) {
if ( row < m_project->numResourceGroups() ) {
return createIndex( row, column, m_project->resourceGroupAt( row ) );
}
return QModelIndex();
}
QObject *p = object( parent );
ResourceGroup *g = qobject_cast<ResourceGroup*>( p );
if ( g ) {
if ( row < g->numResources() ) {
return createIndex( row, column, g->resourceAt( row ) );
}
return QModelIndex();
}
return QModelIndex();
}
QModelIndex ResourceAllocationItemModel::index( const Resource *resource ) const
{
if ( m_project == 0 || resource == 0 ) {
return QModelIndex();
}
Resource *r = const_cast<Resource*>(resource);
int row = -1;
ResourceGroup *par = r->parentGroup();
if ( par ) {
row = par->indexOf( r );
return createIndex( row, 0, r );
}
return QModelIndex();
}
QModelIndex ResourceAllocationItemModel::index( const ResourceGroup *group ) const
{
if ( m_project == 0 || group == 0 ) {
return QModelIndex();
}
ResourceGroup *g = const_cast<ResourceGroup*>(group);
int row = m_project->indexOf( g );
return createIndex( row, 0, g );
}
int ResourceAllocationItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return m_model.propertyCount();
}
int ResourceAllocationItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 || m_model.task() == 0 ) {
return 0;
}
if ( ! parent.isValid() ) {
return m_project->numResourceGroups();
}
QObject *p = object( parent );
ResourceGroup *g = qobject_cast<ResourceGroup*>( p );
if ( g ) {
return g->numResources();
}
return 0;
}
QVariant ResourceAllocationItemModel::allocation( const ResourceGroup *group, const Resource *res, int role ) const
{
if ( m_model.task() == 0 ) {
return QVariant();
}
if ( ! m_resourceCache.contains( res ) ) {
if ( role == Qt::EditRole ) {
ResourceRequest *req = m_model.task()->requests().find( res );
if ( req == 0 ) {
req = new ResourceRequest( const_cast<Resource*>( res ), 0 );
}
const_cast<ResourceAllocationItemModel*>( this )->m_resourceCache.insert( res, req );
return req->units();
}
return m_model.allocation( group, res, role );
}
switch ( role ) {
case Qt::DisplayRole: {
// xgettext: no-c-format
return i18nc( "<value>%", "%1%", m_resourceCache[ res ]->units() );
}
case Qt::EditRole:
return m_resourceCache[ res ]->units();
case Qt::ToolTipRole: {
if ( res->units() == 0 ) {
return xi18nc( "@info:tooltip", "Not allocated" );
}
return xi18nc( "@info:tooltip", "%1 allocated out of %2 available", allocation( group, res, Qt::DisplayRole ).toString(), m_model.maximum( res, Qt::DisplayRole ).toString() );
}
case Qt::CheckStateRole:
return m_resourceCache[ res ]->units() == 0 ? Qt::Unchecked : Qt::Checked;
default:
return m_model.allocation( group, res, role );
}
return QVariant();
}
int ResourceAllocationItemModel::requestedResources( const ResourceGroup *res ) const
{
int c = 0;
foreach ( const Resource *r, res->resources() ) {
if ( m_resourceCache.contains( r ) && m_resourceCache[ r ]->units() > 0 ) {
++c;
}
}
return c;
}
QVariant ResourceAllocationItemModel::allocation( const ResourceGroup *res, int role ) const
{
if ( m_model.task() == 0 ) {
return QVariant();
}
if ( ! m_groupCache.contains( res ) ) {
return m_model.allocation( res, role );
}
switch ( role ) {
case Qt::DisplayRole:
return QString(" %1 (%2)" )
.arg( qMax( m_groupCache[ res ]->units(), allocation( res, Role::Minimum ).toInt() ) )
.arg(requestedResources( res ) );
case Qt::EditRole:
return qMax( m_groupCache[ res ]->units(), allocation( res, Role::Minimum ).toInt() );
case Qt::ToolTipRole: {
QString s1 = i18ncp( "@info:tooltip",
"%1 resource requested for dynamic allocation",
"%1 resources requested for dynamic allocation",
allocation( res, Qt::EditRole ).toInt() );
QString s2 = i18ncp( "@info:tooltip",
"%1 resource allocated",
"%1 resources allocated",
requestedResources( res ) );
return xi18nc( "@info:tooltip", "%1<nl/>%2", s1, s2 );
}
case Qt::WhatsThisRole: {
return xi18nc( "@info:whatsthis",
"<title>Group allocations</title>"
"<para>You can allocate a number of resources from a group and let"
" the scheduler select from the available resources at the time of scheduling.</para>"
" These dynamically allocated resources will be in addition to any resource you have allocated specifically." );
}
case Role::Minimum: {
return 0;
}
case Role::Maximum: {
return res->numResources() - requestedResources( res );
}
default:
return m_model.allocation( res, role );
}
return QVariant();
}
bool ResourceAllocationItemModel::setAllocation( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
m_resourceCache[ res ]->setUnits( value.toInt() );
QModelIndex idx = index( res->parentGroup() );
emit dataChanged( index( idx.row(), 0, QModelIndex() ), index( idx.row(), columnCount() - 1, QModelIndex() ) );
return true;
}
case Qt::CheckStateRole: {
if ( ! m_resourceCache.contains( res ) ) {
m_resourceCache[ res ] = new ResourceRequest( res, 0 );
}
if ( m_resourceCache[ res ]->units() == 0 ) {
m_resourceCache[ res ]->setUnits( 100 );
ResourceGroup *g = res->parentGroup();
if ( m_groupCache.contains( g ) ) {
ResourceGroupRequest *gr = m_groupCache[ g ];
if ( gr->units() + requestedResources( g ) > g->numResources() ) {
gr->setUnits( gr->units() - 1 );
}
}
} else {
m_resourceCache[ res ]->setUnits( 0 );
}
QModelIndex idx = index( res->parentGroup() );
emit dataChanged( index( idx.row(), 0, QModelIndex() ), index( idx.row(), columnCount() - 1, QModelIndex() ) );
return true;
}
}
return false;
}
bool ResourceAllocationItemModel::setAllocation( ResourceGroup *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( ! m_groupCache.contains( res ) ) {
m_groupCache[ res ] = new ResourceGroupRequest( res, 0 );
}
m_groupCache[ res ]->setUnits( value.toInt() );
emit dataChanged( index( res ), index( res ) );
return true;
}
return false;
}
QVariant ResourceAllocationItemModel::maximum( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
int c = res->numResources() - requestedResources( res );
if ( m_groupCache.contains( res ) ) {
c -= m_groupCache[ res ]->units();
}
return i18nc( "1: free resources, 2: number of resources", "%1 of %2", c, res->numResources() );
}
case Qt::ToolTipRole:
return xi18ncp( "@info:tooltip", "There is %1 resource available in this group", "There are %1 resources available in this group", res->numResources() );
default:
return m_model.maximum( res, role );
}
return QVariant();
}
QVariant ResourceAllocationItemModel::required( const QModelIndex &idx, int role ) const
{
if ( m_model.task() == 0 ) {
return QVariant();
}
Resource *res = resource( idx );
if ( res == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole: {
if ( res->type() == Resource::Type_Work ) {
QStringList lst;
if ( m_requiredChecked[ res ] ) {
foreach ( const Resource *r, required( idx ) ) {
lst << r->name();
}
}
return lst.isEmpty() ? i18n( "None" ) : lst.join( "," );
}
break;
}
case Qt::EditRole: break;
case Qt::ToolTipRole:
switch ( res->type() ) {
case Resource::Type_Work: {
if ( ! hasMaterialResources() ) {
return xi18nc( "@info:tooltip", "No material resources available" );
}
QStringList lst;
if ( m_requiredChecked[ res ] ) {
foreach ( const Resource *r, required( idx ) ) {
lst << r->name();
}
}
return lst.isEmpty() ? xi18nc( "@info:tooltip", "No required resources" ) : lst.join( "\n" );
}
case Resource::Type_Material:
return xi18nc( "@info:tooltip", "Material resources cannot have required resources" );
case Resource::Type_Team:
return xi18nc( "@info:tooltip", "Team resources cannot have required resources" );
}
break;
case Qt::CheckStateRole:
if ( res->type() == Resource::Type_Work ) {
return m_requiredChecked[ res ];
}
break;
default:
return m_model.required( res, role );
}
return QVariant();
}
bool ResourceAllocationItemModel::setRequired( const QModelIndex &idx, const QVariant &value, int role )
{
Resource *res = resource( idx );
if ( res == 0 ) {
return false;
}
switch ( role ) {
case Qt::CheckStateRole:
m_requiredChecked[ res ] = value.toInt();
if ( value.toInt() == Qt::Unchecked ) {
m_resourceCache[ res ]->setRequiredResources( QList<Resource*>() );
}
emit dataChanged( idx, idx );
return true;
}
return false;
}
QVariant ResourceAllocationItemModel::notUsed( const ResourceGroup *, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QString(" ");
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAllocationItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
QObject *obj = object( index );
if ( obj == 0 ) {
return QVariant();
}
if ( role == Qt::TextAlignmentRole ) {
// use same alignment as in header (headers always horizontal)
return headerData( index.column(), Qt::Horizontal, role );
}
Resource *r = qobject_cast<Resource*>( obj );
if ( r ) {
if ( index.column() == ResourceAllocationModel::RequestAllocation ) {
return allocation( r->parentGroup(), r, role );
}
if ( index.column() == ResourceAllocationModel::RequestRequired ) {
return required( index, role );
}
result = m_model.data( r->parentGroup(), r, index.column(), role );
} else {
ResourceGroup *g = qobject_cast<ResourceGroup*>( obj );
if ( g ) {
switch ( index.column() ) {
case ResourceAllocationModel::RequestAllocation:
result = allocation( g, role );
break;
case ResourceAllocationModel::RequestMaximum:
result = maximum( g, role );
break;
default:
result = m_model.data( g, index.column(), role );
break;
}
}
}
if ( role == Qt::DisplayRole && ! result.isValid() ) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
bool ResourceAllocationItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags( index ) & Qt::ItemIsEditable ) == 0 ) {
return false;
}
QObject *obj = object( index );
Resource *r = qobject_cast<Resource*>( obj );
if ( r ) {
switch (index.column()) {
case ResourceAllocationModel::RequestAllocation:
if ( setAllocation( r, value, role ) ) {
emit dataChanged( index, index );
QModelIndex idx = this->index( index.row(), ResourceAllocationModel::RequestAllocation, parent( parent( index ) ) );
emit dataChanged( idx, idx );
return true;
}
return false;
case ResourceAllocationModel::RequestRequired:
return setRequired( index, value, role );
default:
//qWarning("data: invalid display value column %d", index.column());
return false;
}
}
ResourceGroup *g = qobject_cast<ResourceGroup*>( obj );
if ( g ) {
switch (index.column()) {
case ResourceAllocationModel::RequestAllocation:
if ( setAllocation( g, value, role ) ) {
emit dataChanged( index, index );
return true;
}
return false;
default:
//qWarning("data: invalid display value column %d", index.column());
return false;
}
}
return false;
}
QVariant ResourceAllocationItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
return m_model.headerData( section, role );
}
if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case 0: return QVariant();
default: return Qt::AlignCenter;
}
return Qt::AlignCenter;
}
}
return m_model.headerData( section, role );
}
QAbstractItemDelegate *ResourceAllocationItemModel::createDelegate( int col, QWidget *parent ) const
{
switch ( col ) {
case ResourceAllocationModel::RequestAllocation: return new SpinBoxDelegate( parent );
case ResourceAllocationModel::RequestRequired: return new RequieredResourceDelegate( parent );
default: break;
}
return 0;
}
QObject *ResourceAllocationItemModel::object( const QModelIndex &index ) const
{
QObject *o = 0;
if ( index.isValid() ) {
o = static_cast<QObject*>( index.internalPointer() );
Q_ASSERT( o );
}
return o;
}
void ResourceAllocationItemModel::slotResourceChanged( Resource *res )
{
ResourceGroup *g = res->parentGroup();
if ( g ) {
int row = g->indexOf( res );
emit dataChanged( createIndex( row, 0, res ), createIndex( row, columnCount() - 1, res ) );
return;
}
}
void ResourceAllocationItemModel::slotResourceGroupChanged( ResourceGroup *res )
{
Project *p = res->project();
if ( p ) {
int row = p->resourceGroups().indexOf( res );
emit dataChanged( createIndex( row, 0, res ), createIndex( row, columnCount() - 1, res ) );
}
}
Resource *ResourceAllocationItemModel::resource( const QModelIndex &idx ) const
{
return qobject_cast<Resource*>( object( idx ) );
}
void ResourceAllocationItemModel::setRequired( const QModelIndex &idx, const QList<Resource*> &lst )
{
Resource *r = resource( idx );
Q_ASSERT( r );
if ( m_resourceCache.contains( r ) ) {
m_resourceCache[ r ]->setRequiredResources( lst );
emit dataChanged( idx, idx );
}
}
QList<Resource*> ResourceAllocationItemModel::required( const QModelIndex &idx ) const
{
Resource *r = resource( idx );
Q_ASSERT( r );
if ( m_resourceCache.contains( r ) ) {
ResourceRequest* request = m_resourceCache[ r ];
return request->requiredResources();
}
return r->requiredResources();
}
} // namespace KPlato
diff --git a/src/libs/models/kptresourceappointmentsmodel.cpp b/src/libs/models/kptresourceappointmentsmodel.cpp
index 1eb890fd..5517afde 100644
--- a/src/libs/models/kptresourceappointmentsmodel.cpp
+++ b/src/libs/models/kptresourceappointmentsmodel.cpp
@@ -1,2031 +1,2032 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2011, 2012 Dag Andersen danders@get2net>
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 "kptresourceappointmentsmodel.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptappointment.h"
#include "kptcommand.h"
#include "kpteffortcostmap.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptdebug.h"
#include <KFormat>
#include <QDate>
#include <QList>
#include <QLocale>
#include <QHash>
#include <KGanttGlobal>
namespace KPlato
{
ResourceAppointmentsItemModel::ResourceAppointmentsItemModel( QObject *parent )
: ItemModelBase( parent ),
m_group( 0 ),
m_resource( 0 ),
m_showInternal( true ),
m_showExternal( true )
{
}
ResourceAppointmentsItemModel::~ResourceAppointmentsItemModel()
{
}
void ResourceAppointmentsItemModel::slotResourceToBeInserted( const ResourceGroup *group, int row )
{
debugPlan<<group->name()<<row;
Q_ASSERT( m_group == 0 );
m_group = const_cast<ResourceGroup*>(group);
QModelIndex i = index( group );
beginInsertRows( i, row, row );
}
void ResourceAppointmentsItemModel::slotResourceInserted( const Resource *r )
{
debugPlan<<r->name();
Q_ASSERT( r->parentGroup() == m_group );
endInsertRows();
m_group = 0;
refresh();
connect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsItemModel::slotAppointmentToBeInserted );
connect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsItemModel::slotAppointmentInserted );
connect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentToBeRemoved );
connect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentRemoved );
connect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsItemModel::slotAppointmentChanged );
}
void ResourceAppointmentsItemModel::slotResourceToBeRemoved( const Resource *r )
{
debugPlan<<r->name();
int row = r->parentGroup()->indexOf( r );
beginRemoveRows( index( r->parentGroup() ), row, row );
disconnect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsItemModel::slotAppointmentToBeInserted );
disconnect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsItemModel::slotAppointmentInserted );
disconnect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentToBeRemoved );
disconnect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentRemoved );
disconnect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsItemModel::slotAppointmentChanged );
}
void ResourceAppointmentsItemModel::slotResourceRemoved( const Resource *resource )
{
Q_UNUSED(resource);
//debugPlan<<resource->name();
endRemoveRows();
refresh();
}
void ResourceAppointmentsItemModel::slotResourceGroupToBeInserted( const ResourceGroup *group, int row )
{
//debugPlan<<group->name()<<endl;
Q_ASSERT( m_group == 0 );
m_group = const_cast<ResourceGroup*>(group);
beginInsertRows( QModelIndex(), row, row );
}
void ResourceAppointmentsItemModel::slotResourceGroupInserted( const ResourceGroup *group )
{
//debugPlan<<group->name()<<endl;
Q_ASSERT( group == m_group ); Q_UNUSED( group );
endInsertRows();
m_group = 0;
}
void ResourceAppointmentsItemModel::slotResourceGroupToBeRemoved( const ResourceGroup *group )
{
//debugPlan<<group->name()<<endl;
Q_ASSERT( m_group == 0 );
m_group = const_cast<ResourceGroup*>(group);
int row = index( group ).row();
beginRemoveRows( QModelIndex(), row, row );
}
void ResourceAppointmentsItemModel::slotResourceGroupRemoved( const ResourceGroup *group )
{
//debugPlan<<group->name()<<endl;
Q_ASSERT( group == m_group ); Q_UNUSED( group );
endRemoveRows();
m_group = 0;
}
void ResourceAppointmentsItemModel::slotAppointmentToBeInserted( Resource *r, int row )
{
Q_UNUSED(r);
Q_UNUSED(row);
}
void ResourceAppointmentsItemModel::slotAppointmentInserted( Resource *r, Appointment *a )
{
Q_UNUSED(r);
Q_UNUSED(a);
beginResetModel();
refreshData();
endResetModel();
}
void ResourceAppointmentsItemModel::slotAppointmentToBeRemoved( Resource *r, int row )
{
Q_UNUSED(r);
Q_UNUSED(row);
}
void ResourceAppointmentsItemModel::slotAppointmentRemoved()
{
beginResetModel();
refreshData();
endResetModel();
}
void ResourceAppointmentsItemModel::slotAppointmentChanged( Resource *r, Appointment *a )
{
int row = rowNumber( r, a );
Q_ASSERT( row >= 0 );
refreshData();
emit dataChanged( createExternalAppointmentIndex( row, 0, a ), createExternalAppointmentIndex( row, columnCount() - 1, a ) );
}
void ResourceAppointmentsItemModel::slotProjectCalculated( ScheduleManager *sm )
{
if ( sm == m_manager ) {
setScheduleManager( sm );
}
}
int ResourceAppointmentsItemModel::rowNumber( Resource *res, Appointment *a ) const
{
int r = 0;
if ( m_showInternal ) {
r = res->appointments( id() ).indexOf( a );
if ( r > -1 ) {
return r;
}
r = res->numAppointments();
}
if ( m_showExternal ) {
int rr = res->externalAppointmentList().indexOf( a );
if ( rr > -1 ) {
return r + rr;
}
}
return -1;
}
void ResourceAppointmentsItemModel::setShowInternalAppointments( bool show )
{
if ( m_showInternal == show ) {
return;
}
beginResetModel();
m_showInternal = show;
refreshData();
endResetModel();
}
void ResourceAppointmentsItemModel::setShowExternalAppointments( bool show )
{
if ( m_showExternal == show ) {
return;
}
beginResetModel();
m_showExternal = show;
refreshData();
endResetModel();
}
void ResourceAppointmentsItemModel::setProject( Project *project )
{
beginResetModel();
debugPlan;
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ResourceAppointmentsItemModel::projectDeleted);
disconnect( m_project, &Project::resourceChanged, this, &ResourceAppointmentsItemModel::slotResourceChanged );
disconnect( m_project, &Project::resourceGroupChanged, this, &ResourceAppointmentsItemModel::slotResourceGroupChanged );
disconnect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceAppointmentsItemModel::slotResourceGroupToBeInserted );
disconnect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceAppointmentsItemModel::slotResourceGroupToBeRemoved );
disconnect( m_project, &Project::resourceToBeAdded, this, &ResourceAppointmentsItemModel::slotResourceToBeInserted );
disconnect( m_project, &Project::resourceToBeRemoved, this, &ResourceAppointmentsItemModel::slotResourceToBeRemoved );
disconnect( m_project, &Project::resourceGroupAdded, this, &ResourceAppointmentsItemModel::slotResourceGroupInserted );
disconnect( m_project, &Project::resourceGroupRemoved, this, &ResourceAppointmentsItemModel::slotResourceGroupRemoved );
disconnect( m_project, &Project::resourceAdded, this, &ResourceAppointmentsItemModel::slotResourceInserted );
disconnect( m_project, &Project::resourceRemoved, this, &ResourceAppointmentsItemModel::slotResourceRemoved );
disconnect( m_project, &Project::defaultCalendarChanged, this, &ResourceAppointmentsItemModel::slotCalendarChanged );
disconnect( m_project, &Project::projectCalculated, this, &ResourceAppointmentsItemModel::slotProjectCalculated );
disconnect( m_project, &Project::scheduleManagerChanged, this, &ResourceAppointmentsItemModel::slotProjectCalculated );
foreach ( Resource *r, m_project->resourceList() ) {
disconnect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsItemModel::slotAppointmentToBeInserted );
disconnect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsItemModel::slotAppointmentInserted );
disconnect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentToBeRemoved );
disconnect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentRemoved );
disconnect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsItemModel::slotAppointmentChanged );
}
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ResourceAppointmentsItemModel::projectDeleted);
connect( m_project, &Project::resourceChanged, this, &ResourceAppointmentsItemModel::slotResourceChanged );
connect( m_project, &Project::resourceGroupChanged, this, &ResourceAppointmentsItemModel::slotResourceGroupChanged );
connect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceAppointmentsItemModel::slotResourceGroupToBeInserted );
connect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceAppointmentsItemModel::slotResourceGroupToBeRemoved );
connect( m_project, &Project::resourceToBeAdded, this, &ResourceAppointmentsItemModel::slotResourceToBeInserted );
connect( m_project, &Project::resourceToBeRemoved, this, &ResourceAppointmentsItemModel::slotResourceToBeRemoved );
connect( m_project, &Project::resourceGroupAdded, this, &ResourceAppointmentsItemModel::slotResourceGroupInserted );
connect( m_project, &Project::resourceGroupRemoved, this, &ResourceAppointmentsItemModel::slotResourceGroupRemoved );
connect( m_project, &Project::resourceAdded, this, &ResourceAppointmentsItemModel::slotResourceInserted );
connect( m_project, &Project::resourceRemoved, this, &ResourceAppointmentsItemModel::slotResourceRemoved );
connect( m_project, &Project::defaultCalendarChanged, this, &ResourceAppointmentsItemModel::slotCalendarChanged );
connect( m_project, &Project::projectCalculated, this, &ResourceAppointmentsItemModel::slotProjectCalculated );
connect( m_project, &Project::scheduleManagerChanged, this, &ResourceAppointmentsItemModel::slotProjectCalculated );
foreach ( Resource *r, m_project->resourceList() ) {
connect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsItemModel::slotAppointmentToBeInserted );
connect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsItemModel::slotAppointmentInserted );
connect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentToBeRemoved );
connect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsItemModel::slotAppointmentRemoved );
connect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsItemModel::slotAppointmentChanged );
}
}
refreshData();
endResetModel();
emit refreshed();
}
QDate ResourceAppointmentsItemModel::startDate() const
{
if ( m_project && m_manager ) {
return m_project->startTime( id() ).date();
}
return QDate::currentDate();
}
QDate ResourceAppointmentsItemModel::endDate() const
{
if ( m_project && m_manager ) {
return m_project->endTime( id() ).date();
}
return QDate::currentDate();
}
void ResourceAppointmentsItemModel::setScheduleManager( ScheduleManager *sm )
{
if (sm == m_manager) {
return;
}
beginResetModel();
debugPlan<<sm;
m_manager = sm;
refreshData();
endResetModel();
emit refreshed();
}
long ResourceAppointmentsItemModel::id() const
{
return m_manager == 0 ? -1 : m_manager->scheduleId();
}
Qt::ItemFlags ResourceAppointmentsItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = ItemModelBase::flags( index );
return flags &= ~Qt::ItemIsEditable;
}
QModelIndex ResourceAppointmentsItemModel::parent( const QModelIndex &idx ) const
{
if ( !idx.isValid() || m_project == 0 || m_manager == 0 ) {
warnPlan<<"No data "<<idx;
return QModelIndex();
}
QModelIndex p;
if ( ! p.isValid() ) {
Resource *r = resource( idx );
if ( r ) {
int row = m_project->indexOf( r->parentGroup() );
p = createGroupIndex( row, 0, r->parentGroup() );
//debugPlan<<"Parent:"<<p<<r->parentGroup()->name();
Q_ASSERT( p.isValid() );
}
}
if ( ! p.isValid() && m_showInternal ) {
Appointment *a = appointment( idx );
if ( a && a->resource() && a->resource()->resource() ) {
Resource *r = a->resource()->resource();
int row = r->parentGroup()->indexOf( r );
p = createResourceIndex( row, 0, r );
//debugPlan<<"Parent:"<<p<<r->name();
Q_ASSERT( p.isValid() );
}
}
if ( ! p.isValid() && m_showExternal ) {
Appointment *a = externalAppointment( idx );
Resource *r = parent( a );
if ( r ) {
int row = r->parentGroup()->indexOf( r );
p = createResourceIndex( row, 0, r );
}
}
if ( ! p.isValid() ) {
//debugPlan<<"Parent:"<<p;
}
//debugPlan<<"Child :"<<idx;
return p;
}
Resource *ResourceAppointmentsItemModel::parent( const Appointment *a ) const
{
if ( a == 0 || m_project == 0 ) {
return 0;
}
foreach ( Resource *r, m_project->resourceList() ) {
if ( r->appointments( id() ).contains( const_cast<Appointment*>( a ) ) ) {
return r;
}
if ( r->externalAppointmentList().contains( const_cast<Appointment*>( a ) ) ) {
return r;
}
}
return 0;
}
QModelIndex ResourceAppointmentsItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return QModelIndex();
}
if ( ! parent.isValid() ) {
if ( row < m_project->numResourceGroups() ) {
//debugPlan<<"Group: "<<m_project->resourceGroupAt( row )<<endl;
return createGroupIndex( row, column, m_project->resourceGroupAt( row ) );
}
return QModelIndex();
}
ResourceGroup *g = resourcegroup( parent );
if ( g ) {
if ( row < g->numResources() ) {
//debugPlan<<"Resource: "<<g->resourceAt( row )<<endl;
return createResourceIndex( row, column, g->resourceAt( row ) );
}
return QModelIndex();
}
Resource *r = resource( parent );
if ( r && ( m_showInternal || m_showExternal ) ) {
int num = m_showInternal ? r->numAppointments( id() ) : 0;
if ( row < num ) {
//debugPlan<<"Appointment: "<<r->appointmentAt( row, m_manager->scheduleId() );
return createAppointmentIndex( row, column, r->appointmentAt( row, id() ) );
}
int extRow = row - num;
//debugPlan<<"Appointment: "<<r->externalAppointmentList().value( extRow );
Q_ASSERT( extRow >= 0 && extRow < r->externalAppointmentList().count() );
return createExternalAppointmentIndex( row, column, r->externalAppointmentList().value( extRow ) );
}
return QModelIndex();
}
QModelIndex ResourceAppointmentsItemModel::index( const Resource *resource ) const
{
if ( m_project == 0 || resource == 0 ) {
return QModelIndex();
}
Resource *r = const_cast<Resource*>(resource);
int row = -1;
ResourceGroup *par = r->parentGroup();
if ( par ) {
row = par->indexOf( r );
return createResourceIndex( row, 0, r );
}
return QModelIndex();
}
QModelIndex ResourceAppointmentsItemModel::index( const ResourceGroup *group ) const
{
if ( m_project == 0 || group == 0 ) {
return QModelIndex();
}
ResourceGroup *g = const_cast<ResourceGroup*>(group);
int row = m_project->indexOf( g );
return createGroupIndex( row, 0, g );
}
void ResourceAppointmentsItemModel::refresh()
{
refreshData();
emit refreshed();
}
void ResourceAppointmentsItemModel::refreshData()
{
long id = m_manager == 0 ? -1 : m_manager->scheduleId();
//debugPlan<<"Schedule id: "<<id<<endl;
QDate start;
QDate end;
QHash<const Appointment*, EffortCostMap> ec;
QHash<const Appointment*, EffortCostMap> extEff;
foreach ( Resource *r, m_project->resourceList() ) {
foreach (Appointment* a, r->appointments( id )) {
QDate s = a->startTime().date();
QDate e = a->endTime().date();
ec[ a ] = a->plannedPrDay( s, e );
if ( ! start.isValid() || s < start ) {
start = s;
}
if ( ! end.isValid() || e > end ) {
end = e;
}
//debugPlan<<a->node()->node()->name()<<": "<<s<<e<<": "<<m_effortMap[ a ].totalEffort().toDouble(Duration::Unit_h);
}
// add external appointments
foreach (Appointment* a, r->externalAppointmentList() ) {
extEff[ a ] = a->plannedPrDay( startDate(), endDate() );
//debugPlan<<r->name()<<a->auxcilliaryInfo()<<": "<<extEff[ a ].totalEffort().toDouble(Duration::Unit_h);
//debugPlan<<r->name()<<a->auxcilliaryInfo()<<": "<<extEff[ a ].startDate()<<extEff[ a ].endDate();
}
}
m_effortMap.clear();
m_externalEffortMap.clear();
m_effortMap = ec;
m_externalEffortMap = extEff;
return;
}
int ResourceAppointmentsItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return 3 + startDate().daysTo( endDate() );
}
int ResourceAppointmentsItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return 0;
}
//debugPlan<<parent.row()<<", "<<parent.column()<<endl;
if ( ! parent.isValid() ) {
//debugPlan<<m_project->name()<<": "<<m_project->numResourceGroups()<<endl;
return m_project->numResourceGroups();
}
ResourceGroup *g = resourcegroup( parent );
if ( g ) {
//debugPlan<<g->name()<<": "<<g->numResources()<<endl;
return g->numResources();
}
Resource *r = resource( parent );
if ( r ) {
int rows = m_showInternal ? r->numAppointments( id() ) : 0;
rows += m_showExternal ? r->numExternalAppointments() : 0;
return rows;
}
return 0;
}
QVariant ResourceAppointmentsItemModel::name( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->name();
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::name( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->name();
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::name( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return node->name();
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::name( const Appointment *app, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return app->auxcilliaryInfo();
case Qt::ToolTipRole:
return i18n( "External project: %1", app->auxcilliaryInfo() );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::ForegroundRole:
if ( m_externalEffortMap.contains( app ) ) {
return QColor( Qt::blue );
}
break;
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::total( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
Duration d;
if ( m_showInternal ) {
QList<Appointment*> lst = res->appointments( m_manager->scheduleId() );
foreach ( Appointment *a, lst ) {
if ( m_effortMap.contains( a ) ) {
d += m_effortMap[ a ].totalEffort();
}
}
}
if ( m_showExternal ) {
QList<Appointment*> lst = res->externalAppointmentList();
foreach ( Appointment *a, lst ) {
if ( m_externalEffortMap.contains( a ) ) {
d += m_externalEffortMap[ a ].totalEffort();
}
}
}
return QLocale().toString( d.toDouble( Duration::Unit_h ), 'f', 1 );
}
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::TextAlignmentRole:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::total( const Resource *res, const QDate &date, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
Duration d;
if ( m_showInternal ) {
QList<Appointment*> lst = res->appointments( id() );
foreach ( Appointment *a, lst ) {
if ( m_effortMap.contains( a ) ) {
d += m_effortMap[ a ].effortOnDate( date );
}
}
}
if ( m_showExternal ) {
QList<Appointment*> lst = res->externalAppointmentList();
foreach ( Appointment *a, lst ) {
if ( m_externalEffortMap.contains( a ) ) {
d += m_externalEffortMap[ a ].effortOnDate( date );
}
}
}
QString ds = QLocale().toString( d.toDouble( Duration::Unit_h ), 'f', 1 );
Duration avail = res->effort( 0, DateTime( date, QTime(0,0,0) ), Duration( 1.0, Duration::Unit_d ) );
QString avails = QLocale().toString( avail.toDouble( Duration::Unit_h ), 'f', 1 );
return QString( "%1(%2)").arg( ds).arg( avails );
}
case Qt::EditRole:
case Qt::ToolTipRole:
return i18n( "The total booking on %1, along with the maximum hours for the resource", QLocale().toString( date, QLocale::ShortFormat ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::TextAlignmentRole:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
case Qt::BackgroundRole: {
if ( res->calendar() && res->calendar()->state( date ) != CalendarDay::Working ) {
QColor c( 0xf0f0f0 );
return QVariant::fromValue( c );
//return QVariant( Qt::cyan );
}
break;
}
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::total( const Appointment *a, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
Duration d;
if ( m_effortMap.contains( a ) ) {
d = m_effortMap[ a ].totalEffort();
} else if ( m_externalEffortMap.contains( a ) ) {
d = m_externalEffortMap[ a ].totalEffort();
}
return QLocale().toString( d.toDouble( Duration::Unit_h ), 'f', 1 );
}
case Qt::ToolTipRole: {
if ( m_effortMap.contains( a ) ) {
return i18n( "Total booking by this task" );
} else if ( m_externalEffortMap.contains( a ) ) {
return i18n( "Total booking by the external project" );
}
return QVariant();
}
case Qt::EditRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::TextAlignmentRole:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
case Qt::ForegroundRole:
if ( m_externalEffortMap.contains( a ) ) {
return QColor( Qt::blue );
}
break;
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::assignment( const Appointment *a, const QDate &date, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
Duration d;
if ( m_effortMap.contains( a ) ) {
if ( date < m_effortMap[ a ].startDate() || date > m_effortMap[ a ].endDate() ) {
return QVariant();
}
d = m_effortMap[ a ].effortOnDate( date );
return QLocale().toString( d.toDouble( Duration::Unit_h ), 'f', 1 );
} else if ( m_externalEffortMap.contains( a ) ) {
if ( date < m_externalEffortMap[ a ].startDate() || date > m_externalEffortMap[ a ].endDate() ) {
return QVariant();
}
d = m_externalEffortMap[ a ].effortOnDate( date );
return QLocale().toString( d.toDouble( Duration::Unit_h ), 'f', 1 );
}
return QVariant();
}
case Qt::EditRole:
case Qt::ToolTipRole: {
if ( m_effortMap.contains( a ) ) {
return i18n( "Booking by this task on %1", QLocale().toString( date, QLocale::ShortFormat ) );
} else if ( m_externalEffortMap.contains( a ) ) {
return i18n( "Booking by external project on %1",QLocale().toString( date, QLocale::ShortFormat ) );
}
return QVariant();
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::TextAlignmentRole:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
case Qt::ForegroundRole:
if ( m_externalEffortMap.contains( a ) ) {
return QColor( Qt::blue );
}
break;
case Qt::BackgroundRole: {
Resource *r = parent( a );
if ( r && r->calendar() && r->calendar()->state( date ) != CalendarDay::Working ) {
QColor c( 0xf0f0f0 );
return QVariant::fromValue( c );
//return QVariant( Qt::cyan );
}
break;
}
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::notUsed( const ResourceGroup *, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QString(" ");
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceAppointmentsItemModel::data( const QModelIndex &index, int role ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return QVariant();
}
QVariant result;
if ( index.column() >= columnCount() ) {
debugPlan<<"invalid display value column "<<index;
return result;
}
if ( ! index.isValid() ) {
debugPlan<<"Invalid index:"<<index;
return result;
}
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
Resource *r = resource( index );
if ( r ) {
switch ( index.column() ) {
case 0: result = name( r, role ); break;
case 1: result = total( r, role ); break;
default:
QDate d = startDate().addDays( index.column() - 2 );
result = total( r, d, role );
break;
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return QVariant();
}
ResourceGroup *g = resourcegroup( index );
if ( g ) {
switch ( index.column() ) {
case 0: result = name( g, role ); break;
default:
result = notUsed( g, role );
break;
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return QVariant();
}
Appointment *a = appointment( index );
if ( a ) {
switch ( index.column() ) {
case 0: result = name( a->node()->node(), role ); break;
case 1: result = total( a, role ); break;
default: {
QDate d = startDate().addDays( index.column()-2 );
result = assignment( a, d, role );
break;
}
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return QVariant();
}
a = externalAppointment( index );
if ( a ) {
//debugPlan<<"external"<<a->auxcilliaryInfo()<<index;
switch ( index.column() ) {
case 0: result = name( a, role ); break;
case 1: result = total( a, role ); break;
default: {
QDate d = startDate().addDays( index.column()-2 );
result = assignment( a, d, role );
break;
}
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return QVariant();
}
debugPlan<<"Could not find ptr:"<<index;
return QVariant();
}
bool ResourceAppointmentsItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( ( flags( index ) &Qt::ItemIsEditable ) == 0 || role != Qt::EditRole ) {
return false;
}
Resource *r = resource( index );
if ( r ) {
switch (index.column()) {
default:
qWarning("data: invalid display value column %d", index.column());
break;
}
return false;
}
ResourceGroup *g = resourcegroup( index );
if ( g ) {
switch (index.column()) {
default:
qWarning("data: invalid display value column %d", index.column());
break;
}
return false;
}
return false;
}
QVariant ResourceAppointmentsItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case 0: return i18n( "Name" );
case 1: return i18n( "Total" );
default: {
//debugPlan<<section<<", "<<startDate()<<endDate();
if ( section < columnCount() ) {
QDate d = startDate().addDays( section - 2 );
if ( d <= endDate() ) {
return d;
}
}
return QVariant();
}
}
} else if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case 0: return i18n( "Name" );
case 1: return i18n( "The total hours booked" );
default: {
//debugPlan<<section<<", "<<startDate()<<endDate();
QDate d = startDate().addDays( section - 2 );
return i18n( "Bookings on %1", QLocale().toString( d, QLocale::ShortFormat ) );
}
return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case 0: return QVariant();
default: return (int)(Qt::AlignRight|Qt::AlignVCenter);
}
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
default: return QVariant();
}
}
return ItemModelBase::headerData(section, orientation, role);
}
QObject *ResourceAppointmentsItemModel::object( const QModelIndex &index ) const
{
QObject *o = 0;
if ( index.isValid() ) {
o = dynamic_cast<QObject*>( resource( index ) );
if ( o ) {
return o;
}
o = dynamic_cast<QObject*>( resourcegroup( index ) );
}
return o;
}
Node *ResourceAppointmentsItemModel::node( const QModelIndex &index ) const
{
Appointment *a = appointment( index );
if ( a == 0 ) {
return 0;
}
return a->node()->node();
}
Appointment *ResourceAppointmentsItemModel::appointment( const QModelIndex &index ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return 0;
}
foreach ( Resource *r, m_project->resourceList() ) {
foreach ( Appointment *a, r->appointments( id() ) ) {
if ( a == index.internalPointer() ) {
return a;
}
}
}
return 0;
}
Appointment *ResourceAppointmentsItemModel::externalAppointment( const QModelIndex &index ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return 0;
}
foreach ( Resource *r, m_project->resourceList() ) {
foreach ( Appointment *a, r->externalAppointmentList() ) {
if ( a == index.internalPointer() ) {
return a;
}
}
}
return 0;
}
QModelIndex ResourceAppointmentsItemModel::createAppointmentIndex( int row, int col, void *ptr ) const
{
return createIndex( row, col, ptr );
}
QModelIndex ResourceAppointmentsItemModel::createExternalAppointmentIndex( int row, int col, void *ptr ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return QModelIndex();
}
QModelIndex i = createIndex( row, col, ptr );
//debugPlan<<i;
return i;
}
Resource *ResourceAppointmentsItemModel::resource( const QModelIndex &index ) const
{
if ( m_project == 0 ) {
return 0;
}
foreach ( Resource *r, m_project->resourceList() ) {
if ( r == index.internalPointer() ) {
return r;
}
}
return 0;
}
QModelIndex ResourceAppointmentsItemModel::createResourceIndex( int row, int col, void *ptr ) const
{
return createIndex( row, col, ptr );
}
ResourceGroup *ResourceAppointmentsItemModel::resourcegroup( const QModelIndex &index ) const
{
if ( m_project == 0 ) {
return 0;
}
foreach ( ResourceGroup *r, m_project->resourceGroups() ) {
if ( r == index.internalPointer() ) {
return r;
}
}
return 0;
}
QModelIndex ResourceAppointmentsItemModel::createGroupIndex( int row, int col, void *ptr ) const
{
return createIndex( row, col, ptr );
}
void ResourceAppointmentsItemModel::slotCalendarChanged( Calendar* )
{
foreach ( Resource *r, m_project->resourceList() ) {
if ( r->calendar( true ) == 0 ) {
slotResourceChanged( r );
}
}
}
void ResourceAppointmentsItemModel::slotResourceChanged( Resource *res )
{
ResourceGroup *g = res->parentGroup();
if ( g ) {
int row = g->indexOf( res );
emit dataChanged( createResourceIndex( row, 0, res ), createResourceIndex( row, columnCount() - 1, res ) );
return;
}
}
void ResourceAppointmentsItemModel::slotResourceGroupChanged( ResourceGroup *res )
{
Project *p = res->project();
if ( p ) {
int row = p->resourceGroups().indexOf( res );
emit dataChanged( createGroupIndex( row, 0, res ), createGroupIndex( row, columnCount() - 1, res ) );
}
}
//-------------------------------------------------------
class Q_DECL_HIDDEN ResourceAppointmentsRowModel::Private
{
public:
Private( Private *par=0, void *p=0, KPlato::ObjectType t=OT_None ) : parent( par ), ptr( p ), type( t ), internalCached( false ), externalCached( false ), intervalRow( -1 )
{}
~Private()
{
qDeleteAll( intervals );
}
QVariant data( int column, long id = -1, int role = Qt::DisplayRole ) const;
Private *parent;
void *ptr;
KPlato::ObjectType type;
bool internalCached;
bool externalCached;
Private *intervalAt( int row ) const;
// used by interval
AppointmentInterval interval;
protected:
QVariant groupData( int column, int role ) const;
QVariant resourceData( int column, long id, int role ) const;
QVariant appointmentData( int column, int role ) const;
QVariant externalData( int column, int role ) const;
QVariant intervalData( int column, int role ) const;
private:
// used by resource
Appointment internal;
Appointment external;
// used by appointment
int intervalRow;
mutable QMap<int, Private*> intervals;
};
QVariant ResourceAppointmentsRowModel::Private::data( int column, long id, int role ) const
{
if ( role == Role::ObjectType ) {
return (int)type;
}
switch ( type ) {
case OT_ResourceGroup: return groupData( column, role );
case OT_Resource: return resourceData( column, id, role );
case OT_Appointment: return appointmentData( column, role );
case OT_External: return externalData( column, role );
case OT_Interval: return intervalData( column, role );
default: break;
}
return QVariant();
}
QVariant ResourceAppointmentsRowModel::Private::groupData( int column, int role ) const
{
ResourceGroup *g = static_cast<ResourceGroup*>( ptr );
if ( role == Qt::DisplayRole ) {
switch ( column ) {
case ResourceAppointmentsRowModel::Name: return g->name();
case ResourceAppointmentsRowModel::Type: return g->typeToString( true );
case ResourceAppointmentsRowModel::StartTime: return " ";
case ResourceAppointmentsRowModel::EndTime: return " ";
case ResourceAppointmentsRowModel::Load: return " ";
}
} else if ( role == Role::Maximum ) {
return g->units(); //TODO: Maximum Load
}
return QVariant();
}
QVariant ResourceAppointmentsRowModel::Private::resourceData( int column, long id, int role ) const
{
KPlato::Resource *r = static_cast<KPlato::Resource*>( ptr );
if ( role == Qt::DisplayRole ) {
switch ( column ) {
case ResourceAppointmentsRowModel::Name: return r->name();
case ResourceAppointmentsRowModel::Type: return r->typeToString( true );
case ResourceAppointmentsRowModel::StartTime: return " ";
case ResourceAppointmentsRowModel::EndTime: return " ";
case ResourceAppointmentsRowModel::Load: return " ";
}
} else if ( role == Role::Maximum ) {
return r->units(); //TODO: Maximum Load
} else if ( role == Role::InternalAppointments ) {
if ( ! internalCached ) {
Resource *r = static_cast<Resource*>( ptr );
const_cast<Private*>( this )->internal.clear();
foreach ( Appointment *a, r->appointments( id ) ) {
const_cast<Private*>( this )->internal += *a;
}
const_cast<Private*>( this )->internalCached = true;
}
return QVariant::fromValue( (void*)(&internal) );
} else if ( role == Role::ExternalAppointments ) {
if ( ! externalCached ) {
Resource *r = static_cast<Resource*>( ptr );
const_cast<Private*>( this )->external.clear();
foreach ( Appointment *a, r->externalAppointmentList() ) {
Appointment e;
e.setIntervals( a->intervals( r->startTime( id ), r->endTime( id ) ) );
const_cast<Private*>( this )->external += e;
}
const_cast<Private*>( this )->externalCached = true;
}
return QVariant::fromValue( (void*)(&external) );
}
return QVariant();
}
QVariant ResourceAppointmentsRowModel::Private::appointmentData( int column, int role ) const
{
KPlato::Appointment *a = static_cast<KPlato::Appointment*>( ptr );
if ( role == Qt::DisplayRole ) {
switch ( column ) {
case ResourceAppointmentsRowModel::Name: return a->node()->node()->name();
case ResourceAppointmentsRowModel::Type: return a->node()->node()->typeToString( true );
case ResourceAppointmentsRowModel::StartTime: return QLocale().toString( a->startTime(), QLocale::ShortFormat );
case ResourceAppointmentsRowModel::EndTime: return QLocale().toString( a->endTime(), QLocale::ShortFormat );
case ResourceAppointmentsRowModel::Load: return " ";
}
} else if ( role == Qt::ToolTipRole ) {
Node *n = a->node()->node();
return xi18nc( "@info:tooltip", "%1: %2<nl/>%3: %4",
n->wbsCode(),
n->name(),
QLocale().toString( a->startTime(), QLocale::ShortFormat ),
KFormat().formatDuration( ( a->endTime() - a->startTime() ).milliseconds() )
);
} else if ( role == Role::Maximum ) {
return a->resource()->resource()->units(); //TODO: Maximum Load
}
return QVariant();
}
QVariant ResourceAppointmentsRowModel::Private::externalData( int column, int role ) const
{
KPlato::Appointment *a = static_cast<KPlato::Appointment*>( ptr );
if ( role == Qt::DisplayRole ) {
switch ( column ) {
case ResourceAppointmentsRowModel::Name: return a->auxcilliaryInfo();
case ResourceAppointmentsRowModel::Type: return i18n( "Project" );
case ResourceAppointmentsRowModel::StartTime: return QLocale().toString( a->startTime(), QLocale::ShortFormat );
case ResourceAppointmentsRowModel::EndTime: return QLocale().toString( a->endTime(), QLocale::ShortFormat );
case ResourceAppointmentsRowModel::Load: return " ";
}
} else if ( role == Qt::ForegroundRole ) {
return QColor( Qt::blue );
} else if ( role == Role::Maximum ) {
KPlato::Resource *r = static_cast<KPlato::Resource*>( parent->ptr );
return r->units(); //TODO: Maximum Load
}
return QVariant();
}
ResourceAppointmentsRowModel::Private *ResourceAppointmentsRowModel::Private::intervalAt( int row ) const
{
Q_ASSERT( type == OT_Appointment || type == OT_External );
Private *p = intervals.value( row );
if ( p ) {
return p;
}
Appointment *a = static_cast<Appointment*>( ptr );
p = new Private( const_cast<Private*>( this ), 0, OT_Interval );
p->intervalRow = row;
p->interval = a->intervalAt( row );
intervals.insert( row, p );
return p;
}
QVariant ResourceAppointmentsRowModel::Private::intervalData( int column, int role ) const
{
if ( role == Qt::DisplayRole ) {
switch ( column ) {
case ResourceAppointmentsRowModel::Name: return QVariant();
case ResourceAppointmentsRowModel::Type: return i18n( "Interval" );
case ResourceAppointmentsRowModel::StartTime: return QLocale().toString( interval.startTime(), QLocale::ShortFormat );
case ResourceAppointmentsRowModel::EndTime: return QLocale().toString( interval.endTime(), QLocale::ShortFormat );
case ResourceAppointmentsRowModel::Load: return interval.load();
}
} else if ( role == Qt::ToolTipRole ) {
Appointment *a = static_cast<Appointment*>( parent->ptr );
if (a && a->node() && a->node()->node()) {
Node *n = a->node()->node();
return xi18nc( "@info:tooltip", "%1: %2<nl/>%3: %4<nl/>Assigned: %5<nl/>Available: %6",
n->wbsCode(),
n->name(),
QLocale().toString( a->startTime(), QLocale::ShortFormat ),
KFormat().formatDuration( ( a->endTime() - a->startTime() ).milliseconds() ),
interval.load(),
a->resource()->resource()->units()
);
}
} else if ( role == Role::Maximum ) {
return parent->appointmentData( column, role );
}
return QVariant();
}
int ResourceAppointmentsRowModel::sortRole( int column ) const
{
switch ( column ) {
case ResourceAppointmentsRowModel::StartTime:
case ResourceAppointmentsRowModel::EndTime:
return Qt::EditRole;
default:
break;
}
return Qt::DisplayRole;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<( QDebug dbg, KPlato::ObjectType t)
{
switch(t){
case KPlato::OT_None: dbg << "None"; break;
case KPlato::OT_ResourceGroup: dbg << "Group"; break;
case KPlato::OT_Resource: dbg << "Resource"; break;
case KPlato::OT_Appointment: dbg << "Appointment"; break;
case KPlato::OT_External: dbg << "External"; break;
case KPlato::OT_Interval: dbg << "Interval"; break;
default: dbg << "Unknown";
}
return dbg;
}
QDebug operator<<( QDebug dbg, const ResourceAppointmentsRowModel::Private& s )
{
dbg <<&s;
return dbg;
}
QDebug operator<<( QDebug dbg, const ResourceAppointmentsRowModel::Private* s )
{
if ( s == 0 ) {
dbg<<"ResourceAppointmentsRowModel::Private[ ("<<(void*)s<<") ]";
} else {
dbg << "ResourceAppointmentsRowModel::Private[ ("<<(void*)s<<") Type="<<s->type<<" parent=";
switch( s->type ) {
case KPlato::OT_ResourceGroup:
dbg<<static_cast<ResourceGroup*>(s->ptr)->project()<<static_cast<ResourceGroup*>(s->ptr)->project()->name();
dbg<<" ptr="<<static_cast<ResourceGroup*>(s->ptr)<<static_cast<ResourceGroup*>(s->ptr)->name();
break;
case KPlato::OT_Resource:
dbg<<static_cast<ResourceGroup*>(s->parent->ptr)<<static_cast<ResourceGroup*>(s->parent->ptr)->name();
dbg<<" ptr="<<static_cast<Resource*>(s->ptr)<<static_cast<Resource*>(s->ptr)->name();
break;
case KPlato::OT_Appointment:
case KPlato::OT_External:
dbg<<static_cast<Resource*>(s->parent->ptr)<<static_cast<Resource*>(s->parent->ptr)->name();
dbg<<" ptr="<<static_cast<Appointment*>(s->ptr);
break;
case KPlato::OT_Interval:
dbg<<static_cast<Appointment*>(s->parent->ptr)<<" ptr="<<static_cast<AppointmentInterval*>(s->ptr);
break;
default:
dbg<<s->parent<<" ptr="<<s->ptr;
break;
}
dbg<<" ]";
}
return dbg;
}
#endif
ResourceAppointmentsRowModel::ResourceAppointmentsRowModel( QObject *parent )
: ItemModelBase( parent ),
m_schedule( 0 )
{
}
ResourceAppointmentsRowModel::~ResourceAppointmentsRowModel()
{
qDeleteAll( m_datamap );
}
void ResourceAppointmentsRowModel::setProject( Project *project )
{
beginResetModel();
//debugPlan<<project;
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ResourceAppointmentsRowModel::projectDeleted);
disconnect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceAppointmentsRowModel::slotResourceGroupToBeInserted );
disconnect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceAppointmentsRowModel::slotResourceGroupToBeRemoved );
disconnect( m_project, &Project::resourceToBeAdded, this, &ResourceAppointmentsRowModel::slotResourceToBeInserted );
disconnect( m_project, &Project::resourceToBeRemoved, this, &ResourceAppointmentsRowModel::slotResourceToBeRemoved );
disconnect( m_project, &Project::resourceGroupAdded, this, &ResourceAppointmentsRowModel::slotResourceGroupInserted );
disconnect( m_project, &Project::resourceGroupRemoved, this, &ResourceAppointmentsRowModel::slotResourceGroupRemoved );
disconnect( m_project, &Project::resourceAdded, this, &ResourceAppointmentsRowModel::slotResourceInserted );
disconnect( m_project, &Project::resourceRemoved, this, &ResourceAppointmentsRowModel::slotResourceRemoved );
disconnect( m_project, &Project::projectCalculated, this, &ResourceAppointmentsRowModel::slotProjectCalculated );
foreach ( Resource *r, m_project->resourceList() ) {
disconnect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsRowModel::slotAppointmentToBeInserted );
disconnect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsRowModel::slotAppointmentInserted );
disconnect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentToBeRemoved );
disconnect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentRemoved );
disconnect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsRowModel::slotAppointmentChanged );
}
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ResourceAppointmentsRowModel::projectDeleted);
connect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceAppointmentsRowModel::slotResourceGroupToBeInserted );
connect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceAppointmentsRowModel::slotResourceGroupToBeRemoved );
connect( m_project, &Project::resourceToBeAdded, this, &ResourceAppointmentsRowModel::slotResourceToBeInserted );
connect( m_project, &Project::resourceToBeRemoved, this, &ResourceAppointmentsRowModel::slotResourceToBeRemoved );
connect( m_project, &Project::resourceGroupAdded, this, &ResourceAppointmentsRowModel::slotResourceGroupInserted );
connect( m_project, &Project::resourceGroupRemoved, this, &ResourceAppointmentsRowModel::slotResourceGroupRemoved );
connect( m_project, &Project::resourceAdded, this, &ResourceAppointmentsRowModel::slotResourceInserted );
connect( m_project, &Project::resourceRemoved, this, &ResourceAppointmentsRowModel::slotResourceRemoved );
connect( m_project, &Project::projectCalculated, this, &ResourceAppointmentsRowModel::slotProjectCalculated );
foreach ( Resource *r, m_project->resourceList() ) {
connect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsRowModel::slotAppointmentToBeInserted );
connect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsRowModel::slotAppointmentInserted );
connect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentToBeRemoved );
connect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentRemoved );
connect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsRowModel::slotAppointmentChanged );
}
}
endResetModel();
}
void ResourceAppointmentsRowModel::setScheduleManager( ScheduleManager *sm )
{
debugPlan<<"ResourceAppointmentsRowModel::setScheduleManager:"<<sm;
if ( sm == 0 || sm != m_manager || sm->expected() != m_schedule ) {
beginResetModel();
m_manager = sm;
m_schedule = sm ? sm->expected() : 0;
qDeleteAll( m_datamap );
m_datamap.clear();
endResetModel();
}
}
long ResourceAppointmentsRowModel::id() const
{
return m_manager ? m_manager->scheduleId() : -1;
}
const QMetaEnum ResourceAppointmentsRowModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
int ResourceAppointmentsRowModel::columnCount( const QModelIndex & /*parent */) const
{
return columnMap().keyCount();
}
int ResourceAppointmentsRowModel::rowCount( const QModelIndex & parent ) const
{
if ( m_project == 0 ) {
return 0;
}
if ( ! parent.isValid() ) {
return m_project->numResourceGroups();
}
if ( ResourceGroup *g = resourcegroup( parent ) ) {
return g->numResources();
}
if ( m_manager == 0 ) {
return 0;
}
if ( Resource *r = resource( parent ) ) {
return r->numAppointments( id() ) + r->numExternalAppointments(); // number of tasks there are appointments with + external projects
}
if ( Appointment *a = appointment( parent ) ) {
return a->count(); // number of appointment intervals
}
return 0;
}
QVariant ResourceAppointmentsRowModel::data( const QModelIndex &index, int role ) const
{
//debugPlan<<index<<role;
if ( ! index.isValid() ) {
return QVariant();
}
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
return static_cast<Private*>(index.internalPointer() )->data( index.column(), id(), role );
}
QVariant ResourceAppointmentsRowModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Vertical ) {
return QVariant();
}
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case Name: return i18n( "Name" );
case Type: return i18n( "Type" );
case StartTime: return i18n( "Start Time" );
case EndTime: return i18n( "End Time" );
case Load: return xi18nc( "@title:column noun", "Load" );
}
}
if ( role == Qt::TextAlignmentRole ) {
switch ( section ) {
case Name:
case Type:
case StartTime:
case EndTime:
return (int)(Qt::AlignLeft|Qt::AlignVCenter);
case Load:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
}
}
return ItemModelBase::headerData( section, orientation, role );
}
QModelIndex ResourceAppointmentsRowModel::parent( const QModelIndex &idx ) const
{
if ( !idx.isValid() || m_project == 0 ) {
warnPlan<<"No data "<<idx;
return QModelIndex();
}
QModelIndex p;
if ( resourcegroup( idx ) ) {
// ResourceGroup, no parent
return QModelIndex();
}
if ( ResourceGroup *pg = parentGroup( idx ) ) {
// Resource, parent is ResourceGroup
int row = m_project->indexOf( pg );
p = const_cast<ResourceAppointmentsRowModel*>( this )->createGroupIndex( row, 0, m_project );
//debugPlan<<"Parent:"<<p<<r->parentGroup()->name();
Q_ASSERT( p.isValid() );
return p;
}
if ( Resource *pr = parentResource( idx ) ) {
// Appointment, parent is Resource
int row = pr->parentGroup()->indexOf( pr );
p = const_cast<ResourceAppointmentsRowModel*>( this )->createResourceIndex( row, 0, pr->parentGroup() );
//debugPlan<<"Parent:"<<p<<r->parentGroup()->name();
Q_ASSERT( p.isValid() );
return p;
}
if ( Appointment *a = parentAppointment( idx ) ) {
// AppointmentInterval, parent is Appointment
Private *pi = static_cast<Private*>( idx.internalPointer() );
if ( pi->parent->type == OT_Appointment ) {
Q_ASSERT( a->resource()->id() == id() );
if ( a->resource() && a->resource()->resource() ) {
Resource *r = a->resource()->resource();
int row = r->indexOf( a, id() );
Q_ASSERT( row >= 0 );
p = const_cast<ResourceAppointmentsRowModel*>( this )->createAppointmentIndex( row, 0, r );
//debugPlan<<"Parent:"<<p<<r->name();
Q_ASSERT( p.isValid() );
}
} else if ( pi->parent->type == OT_External ) {
Resource *r = static_cast<Resource*>( pi->parent->parent->ptr );
int row = r->externalAppointmentList().indexOf( a );
Q_ASSERT( row >= 0 );
row += r->numAppointments( id() );
p = const_cast<ResourceAppointmentsRowModel*>( this )->createAppointmentIndex( row, 0, r );
}
return p;
}
return QModelIndex();
}
QModelIndex ResourceAppointmentsRowModel::index( ResourceGroup *g ) const
{
if ( m_project == 0 || g == 0 ) {
return QModelIndex();
}
return const_cast<ResourceAppointmentsRowModel*>( this )->createGroupIndex( m_project->indexOf( g ), 0, m_project );
}
QModelIndex ResourceAppointmentsRowModel::index( Resource *r ) const
{
if ( m_project == 0 || r == 0 ) {
return QModelIndex();
}
return const_cast<ResourceAppointmentsRowModel*>( this )->createResourceIndex( r->parentGroup()->indexOf( r ), 0, r->parentGroup() );
}
QModelIndex ResourceAppointmentsRowModel::index( Appointment *a ) const
{
if ( m_project == 0 || m_manager == 0 || a == 0 || a->resource()->resource() ) {
return QModelIndex();
}
Resource *r = a->resource()->resource();
return const_cast<ResourceAppointmentsRowModel*>( this )->createAppointmentIndex( r->indexOf( a, id() ), 0, r );
}
QModelIndex ResourceAppointmentsRowModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || row < 0 || column < 0 ) {
return QModelIndex();
}
if ( ! parent.isValid() ) {
if ( row < m_project->numResourceGroups() ) {
//debugPlan<<"Group: "<<m_project->resourceGroupAt( row );
return const_cast<ResourceAppointmentsRowModel*>( this )->createGroupIndex( row, column, m_project );
}
return QModelIndex();
}
if ( ResourceGroup *g = resourcegroup( parent ) ) {
if ( row < g->numResources() ) {
//debugPlan<<"Resource: "<<g->resourceAt( row )<<static_cast<Private*>( parent.internalPointer() );
return const_cast<ResourceAppointmentsRowModel*>( this )->createResourceIndex( row, column, g );
}
return QModelIndex();
}
if ( m_manager == 0 ) {
return QModelIndex();
}
if ( Resource *r = resource( parent ) ) {
int num = r->numAppointments( id() ) + r->numExternalAppointments();
if ( row < num ) {
//debugPlan<<"Appointment: "<<r->appointmentAt( row, m_manager->scheduleId() )<<static_cast<Private*>( parent.internalPointer() );
return const_cast<ResourceAppointmentsRowModel*>( this )->createAppointmentIndex( row, column, r );
}
return QModelIndex();
}
if ( Appointment *a = appointment( parent ) ) {
int num = a->count();
if ( row < num ) {
//debugPlan<<"Appointment interval at: "<<row<<static_cast<Private*>( parent.internalPointer() );
return const_cast<ResourceAppointmentsRowModel*>( this )->createIntervalIndex( row, column, a );
}
return QModelIndex();
}
return QModelIndex();
}
QModelIndex ResourceAppointmentsRowModel::createGroupIndex( int row, int column, Project *project )
{
ResourceGroup *group = project->resourceGroupAt( row );
Private *p = m_datamap.value( (void*)group );
if ( p == 0 ) {
p = new Private( 0, group, OT_ResourceGroup );
m_datamap.insert( group, p );
}
QModelIndex idx = createIndex( row, column, p );
Q_ASSERT( idx.isValid() );
return idx;
}
QModelIndex ResourceAppointmentsRowModel::createResourceIndex( int row, int column, ResourceGroup *g )
{
Resource *res = g->resourceAt( row );
Private *p = m_datamap.value( (void*)res );
if ( p == 0 ) {
Private *pg = m_datamap.value( g );
Q_ASSERT( pg );
p = new Private( pg, res, OT_Resource );
m_datamap.insert( res, p );
}
QModelIndex idx = createIndex( row, column, p );
Q_ASSERT( idx.isValid() );
return idx;
}
QModelIndex ResourceAppointmentsRowModel::createAppointmentIndex( int row, int column, Resource *r )
{
Private *p = 0;
KPlato::ObjectType type;
Appointment *a = 0;
if ( row < r->numAppointments( id() ) ) {
a = r->appointmentAt( row, id() );
type = OT_Appointment;
} else {
a = r->externalAppointmentList().value( row - r->numAppointments( id() ) );
type = OT_External;
}
Q_ASSERT( a );
p = m_datamap.value( (void*)a );
if ( p == 0 ) {
Private *pr = m_datamap.value( r );
Q_ASSERT( pr );
p = new Private( pr, a, type );
m_datamap.insert( a, p );
}
QModelIndex idx = createIndex( row, column, p );
Q_ASSERT( idx.isValid() );
return idx;
}
QModelIndex ResourceAppointmentsRowModel::createIntervalIndex( int row, int column, Appointment *a )
{
AppointmentInterval i = a->intervalAt( row );
Private *pr = m_datamap.value( a );
Q_ASSERT( pr );
Private *p = pr->intervalAt( row );
Q_ASSERT( p );
QModelIndex idx = createIndex( row, column, p );
Q_ASSERT( idx.isValid() );
return idx;
}
void ResourceAppointmentsRowModel::slotResourceToBeInserted( const ResourceGroup *group, int row )
{
debugPlan<<group->name()<<row;
QModelIndex i = index( const_cast<ResourceGroup*>( group ) );
beginInsertRows( i, row, row );
}
void ResourceAppointmentsRowModel::slotResourceInserted( const Resource *r )
{
debugPlan<<r->name();
endInsertRows();
connect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsRowModel::slotAppointmentToBeInserted );
connect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsRowModel::slotAppointmentInserted );
connect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentToBeRemoved );
connect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentRemoved );
connect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsRowModel::slotAppointmentChanged );
}
void ResourceAppointmentsRowModel::slotResourceToBeRemoved( const Resource *r )
{
debugPlan<<r->name();
int row = r->parentGroup()->indexOf( r );
beginRemoveRows( index( r->parentGroup() ), row, row );
disconnect( r, &Resource::externalAppointmentToBeAdded, this, &ResourceAppointmentsRowModel::slotAppointmentToBeInserted );
disconnect( r, &Resource::externalAppointmentAdded, this, &ResourceAppointmentsRowModel::slotAppointmentInserted );
disconnect( r, &Resource::externalAppointmentToBeRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentToBeRemoved );
disconnect( r, &Resource::externalAppointmentRemoved, this, &ResourceAppointmentsRowModel::slotAppointmentRemoved );
disconnect( r, &Resource::externalAppointmentChanged, this, &ResourceAppointmentsRowModel::slotAppointmentChanged );
Private *p = 0;
foreach ( Appointment *a, r->appointments( id() ) ) {
// remove appointment
p = m_datamap.value( a );
if ( p ) {
m_datamap.remove( a );
delete p;
}
}
foreach ( Appointment *a, r->externalAppointmentList() ) {
// remove appointment
p = m_datamap.value( a );
if ( p ) {
m_datamap.remove( a );
delete p;
}
}
// remove resource
p = m_datamap.value( (void*)r );
if ( p ) {
m_datamap.remove( const_cast<Resource*>( r ) );
delete p;
}
}
void ResourceAppointmentsRowModel::slotResourceRemoved( const Resource *resource )
{
Q_UNUSED(resource);
//debugPlan<<resource->name();
endRemoveRows();
}
void ResourceAppointmentsRowModel::slotResourceGroupToBeInserted( const ResourceGroup *group, int row )
{
Q_UNUSED(group);
beginInsertRows( QModelIndex(), row, row );
}
void ResourceAppointmentsRowModel::slotResourceGroupInserted( const ResourceGroup*/*group*/ )
{
endInsertRows();
}
void ResourceAppointmentsRowModel::slotResourceGroupToBeRemoved( const ResourceGroup *group )
{
//debugPlan<<group->name()<<endl;
int row = m_project->indexOf( const_cast<ResourceGroup*>( group ) );
beginRemoveRows( QModelIndex(), row, row );
Private *p = m_datamap.value( const_cast<ResourceGroup*>( group ) );
if ( p ) {
m_datamap.remove( const_cast<ResourceGroup*>( group ) );
delete p;
}
}
void ResourceAppointmentsRowModel::slotResourceGroupRemoved( const ResourceGroup *group )
{
Q_UNUSED(group);
//debugPlan<<group->name();
endRemoveRows();
}
void ResourceAppointmentsRowModel::slotAppointmentToBeInserted( Resource *r, int row )
{
Q_UNUSED(r);
Q_UNUSED(row);
// external appointments only, (Internal handled in slotProjectCalculated)
}
void ResourceAppointmentsRowModel::slotAppointmentInserted( Resource *r, Appointment *a )
{
Q_UNUSED(a);
beginResetModel();
// external appointments only, (Internal handled in slotProjectCalculated)
Private *p = m_datamap.value( r );
if ( p ) {
p->externalCached = false;
}
endResetModel();
}
void ResourceAppointmentsRowModel::slotAppointmentToBeRemoved( Resource *r, int row )
{
Q_UNUSED(row);
// external appointments only, (Internal handled in slotProjectCalculated)
Private *p = m_datamap.value( r );
if ( p ) {
p->externalCached = false;
}
}
void ResourceAppointmentsRowModel::slotAppointmentRemoved()
{
// external appointments only, (Internal handled in slotProjectCalculated)
beginResetModel();
endResetModel();
}
void ResourceAppointmentsRowModel::slotAppointmentChanged( Resource *r, Appointment *a )
{
Q_UNUSED(r);
Q_UNUSED(a);
// external appointments only, (Internal handled in slotProjectCalculated)
// will not happen atm
}
void ResourceAppointmentsRowModel::slotProjectCalculated( ScheduleManager *sm )
{
if ( sm == m_manager ) {
setScheduleManager( sm );
}
}
ResourceGroup *ResourceAppointmentsRowModel::parentGroup( const QModelIndex &index ) const
{
if ( m_project == 0 ) {
return 0;
}
Private *ch = static_cast<Private*>( index.internalPointer() );
if ( ch && ch->type == OT_Resource ) {
return static_cast<ResourceGroup*>( ch->parent->ptr );
}
return 0;
}
ResourceGroup *ResourceAppointmentsRowModel::resourcegroup( const QModelIndex &index ) const
{
if ( m_project == 0 ) {
return 0;
}
Private *p = static_cast<Private*>( index.internalPointer() );
if ( p && p->type == OT_ResourceGroup ) {
return static_cast<ResourceGroup*>( p->ptr );
}
return 0;
}
Resource *ResourceAppointmentsRowModel::parentResource( const QModelIndex &index ) const
{
if ( m_project == 0 ) {
return 0;
}
Private *ch = static_cast<Private*>( index.internalPointer() );
if ( ch && ( ch->type == OT_Appointment || ch->type == OT_External ) ) {
return static_cast<Resource*>( ch->parent->ptr );
}
return 0;
}
Resource *ResourceAppointmentsRowModel::resource( const QModelIndex &index ) const
{
if ( m_project == 0 ) {
return 0;
}
Private *p = static_cast<Private*>( index.internalPointer() );
if ( p && p->type == OT_Resource ) {
return static_cast<Resource*>( p->ptr );
}
return 0;
}
Appointment *ResourceAppointmentsRowModel::parentAppointment( const QModelIndex &index ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return 0;
}
Private *ch = static_cast<Private*>( index.internalPointer() );
if ( ch && ch->type == OT_Interval ) {
return static_cast<Appointment*>( ch->parent->ptr );
}
return 0;
}
Appointment *ResourceAppointmentsRowModel::appointment( const QModelIndex &index ) const
{
if ( m_project == 0 || m_manager == 0 || ! index.isValid() ) {
return 0;
}
Private *p = static_cast<Private*>( index.internalPointer() );
if ( p && ( p->type == OT_Appointment || p->type == OT_External ) ) {
return static_cast<Appointment*>( p->ptr );
}
return 0;
}
AppointmentInterval *ResourceAppointmentsRowModel::interval( const QModelIndex &index ) const
{
if ( m_project == 0 || m_manager == 0 ) {
return 0;
}
Private *p = static_cast<Private*>( index.internalPointer() );
if ( p && p->type == OT_Interval ) {
return &( p->interval );
}
return 0;
}
Node *ResourceAppointmentsRowModel::node( const QModelIndex &idx ) const
{
Appointment *a = appointment( idx );
return ( a && a->node() ? a->node()->node() : 0 );
}
//---------------------------------------------
ResourceAppointmentsGanttModel::ResourceAppointmentsGanttModel( QObject *parent )
: ResourceAppointmentsRowModel( parent )
{
}
ResourceAppointmentsGanttModel::~ResourceAppointmentsGanttModel()
{
}
QVariant ResourceAppointmentsGanttModel::data( const ResourceGroup *g, int column, int role ) const
{
Q_UNUSED(column);
switch( role ) {
case KGantt::ItemTypeRole: return KGantt::TypeSummary;
case KGantt::StartTimeRole: return g->startTime( id() );
case KGantt::EndTimeRole: return g->endTime( id() );
}
return QVariant();
}
QVariant ResourceAppointmentsGanttModel::data( const Resource *r, int column, int role ) const
{
Q_UNUSED(column);
switch( role ) {
case KGantt::ItemTypeRole: return KGantt::TypeSummary;
case KGantt::StartTimeRole: return r->startTime( id() );
case KGantt::EndTimeRole: return r->endTime( id() );
}
return QVariant();
}
QVariant ResourceAppointmentsGanttModel::data( const Appointment *a, int column, int role ) const
{
Q_UNUSED(column);
switch( role ) {
case KGantt::ItemTypeRole: return KGantt::TypeMulti;
case KGantt::StartTimeRole: return a->startTime();
case KGantt::EndTimeRole: return a->endTime();
}
return QVariant();
}
QVariant ResourceAppointmentsGanttModel::data( const AppointmentInterval *a, int column, int role ) const
{
Q_UNUSED(column);
switch( role ) {
case KGantt::ItemTypeRole: return KGantt::TypeTask;
case KGantt::StartTimeRole: return a->startTime();
case KGantt::EndTimeRole: return a->endTime();
}
return QVariant();
}
QVariant ResourceAppointmentsGanttModel::data( const QModelIndex &index, int role ) const
{
//debugPlan<<index<<role;
if ( m_project == 0 || ! index.isValid() ) {
return QVariant();
}
if ( role == KGantt::ItemTypeRole ||
role == KGantt::StartTimeRole ||
role == KGantt::EndTimeRole ||
role == KGantt::TaskCompletionRole )
{
if ( ResourceGroup *g = resourcegroup( index ) ) {
return data( g, index.column(), role );
}
if ( Resource *r = resource( index ) ) {
return data( r, index.column(), role );
}
if ( m_manager == 0 ) {
return QVariant();
}
if ( Appointment *a = appointment( index ) ) {
return data( a, index.column(), role );
}
if ( AppointmentInterval *i = interval( index ) ) {
return data( i, index.column(), role );
}
return QVariant();
}
return ResourceAppointmentsRowModel::data( index, role );
}
} // namespace KPlato
diff --git a/src/libs/models/kptresourcemodel.cpp b/src/libs/models/kptresourcemodel.cpp
index 2de1f704..59f068c3 100644
--- a/src/libs/models/kptresourcemodel.cpp
+++ b/src/libs/models/kptresourcemodel.cpp
@@ -1,1832 +1,1833 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 2011, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptresourcemodel.h"
#include "kptlocale.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <QMimeData>
#include <QMimeDatabase>
#include <QStringList>
#include <QLocale>
#include <KIO/TransferJob>
#include <KIO/StatJob>
#ifdef PLAN_KCONTACTS_FOUND
#include <KContacts/Addressee>
#include <KContacts/VCardConverter>
#endif
namespace KPlato
{
//--------------------------------------
ResourceModel::ResourceModel( QObject *parent )
: QObject( parent ),
m_project( 0 )
{
}
ResourceModel::~ResourceModel()
{
}
const QMetaEnum ResourceModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
void ResourceModel::setProject( Project *project )
{
m_project = project;
}
int ResourceModel::propertyCount() const
{
return columnMap().keyCount();
}
QVariant ResourceModel::name( const Resource *res, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return res->name();
case Qt::ToolTipRole:
if (res->isShared()) {
return xi18nc("@info:tooltip", "%1 is a <emphasis>Shared</emphasis> resource and can thus be shared with other projects", res->name());
}
if ( res->autoAllocate() ) {
return xi18nc( "@info:tooltip", "%1:<nl/>This resource will be automatically allocated to new tasks", res->name() );
}
return res->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::DecorationRole:
if ( res->isBaselined() ) {
return koIcon("view-time-schedule-baselined");
}
break;
case Qt::CheckStateRole:
return res->autoAllocate() ? Qt::Checked : Qt::Unchecked;
default:
break;
}
return QVariant();
}
QVariant ResourceModel::name( const ResourceGroup *res, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return res->name();
case Qt::ToolTipRole:
if (!res->isShared()) {
return res->name();
}
return xi18nc("@info:tooltip", "%1 is a <emphasis>Shared</emphasis> resource group and can thus be shared with other projects", res->name());
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::scope( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return res->isShared() ? i18n("Shared") : i18n("Local");
case Qt::ToolTipRole:
if (!res->isShared()) {
return xi18nc("@info:tooltip", "%1 is a <emphasis>Local</emphasis> resource and can only be used in this project", res->name());
}
return xi18nc("@info:tooltip", "%1 is a <emphasis>Shared</emphasis> resource and can thus be shared with other projects", res->name());
case Qt::EditRole:
return res->isShared() ? "Shared" : "Local";
case Role::EnumList:
return QStringList() << i18n("Local") << i18n("Shared");
case Role::EnumListValue:
return res->isShared() ? 1 : 0;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::scope( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return res->isShared() ? i18n("Shared") : i18n("Local");
case Qt::ToolTipRole:
if (!res->isShared()) {
return xi18nc("@info:tooltip", "%1 is a <emphasis>Local</emphasis> resource group and can only be used in this project", res->name());
}
return xi18nc("@info:tooltip", "%1 is a <emphasis>Shared</emphasis> resource group and can thus be shared with other projects", res->name());
case Qt::EditRole:
return res->isShared() ? "Shared" : "Local";
case Role::EnumList:
return QStringList() << i18n("Local") << i18n("Shared");
case Role::EnumListValue:
return res->isShared() ? 1 : 0;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::type( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return res->typeToString( true );
case Qt::EditRole:
return res->typeToString( false );
case Role::EnumList:
return res->typeToStringList( true );
case Role::EnumListValue:
return (int)res->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::type( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return res->typeToString( true );
case Qt::EditRole:
return res->typeToString( false );
case Role::EnumList:
return res->typeToStringList( true );
case Role::EnumListValue:
return (int)res->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::initials( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->initials();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::email( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->email();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::calendar( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( res->type() == Resource::Type_Team ) {
return " ";
}
QString s = i18n( "None" );
Calendar *cal = res->calendar( true ); // don't check for default calendar
if ( cal ) {
s = cal->name();
} else if ( res->type() == Resource::Type_Work ) {
// Do we get a default calendar
cal = res->calendar();
if ( cal ) {
s = i18nc( "Default (calendar name)", "Default (%1)", cal->name() );
}
}
return s;
}
case Qt::ToolTipRole: {
if ( res->type() == Resource::Type_Team ) {
return xi18nc( "@info:tooltip", "A team resource does not have a calendar" );
}
QString s = xi18nc( "@info:tooltip", "No calendar" );
Calendar *cal = res->calendar( true ); // don't check for default calendar
if ( cal ) {
s = cal->name();
} else if ( res->type() == Resource::Type_Work ) {
// Do we get a default calendar
cal = res->calendar();
if ( cal ) {
s = xi18nc( "@info:tooltip 1=calendar name", "Using default calendar: %1", cal->name() );
}
}
return s;
}
case Role::EnumList: {
Calendar *cal = m_project->defaultCalendar();
QString s = i18n( "None" );
if ( cal && res->type() == Resource::Type_Work ) {
s = i18nc( "Default (calendar name)", "Default (%1)", cal->name() );
}
return QStringList() << s << m_project->calendarNames();
}
case Qt::EditRole:
case Role::EnumListValue: {
Calendar *cal = res->calendar( true ); // don't check for default calendar
return cal == 0 ? 0 : m_project->calendarNames().indexOf( cal->name() ) + 1;
}
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::units( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return res->units();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::availableFrom( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( res->availableFrom(), QLocale::ShortFormat );
case Qt::EditRole:
return res->availableFrom();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::ToolTipRole: {
if ( res->availableFrom().isValid() ) {
return xi18nc( "infor:tooltip", "Available from: %1", QLocale().toString( res->availableFrom(), QLocale::LongFormat ) );
}
return xi18nc( "infor:tooltip", "Available from project target start time: %1", QLocale().toString( m_project->constraintStartTime(), QLocale::LongFormat ) );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::availableUntil( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( res->availableUntil(), QLocale::ShortFormat );
case Qt::EditRole:
return res->availableUntil();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::ToolTipRole: {
if ( res->availableUntil().isValid() ) {
return xi18nc( "infor:tooltip", "Available until: %1", QLocale().toString( res->availableUntil(), QLocale::LongFormat ) );
}
return xi18nc( "infor:tooltip", "Available from project target finish time: %1", QLocale().toString( m_project->constraintEndTime(), QLocale::LongFormat ) );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::normalRate( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return m_project->locale()->formatMoney( res->normalRate() );
case Qt::EditRole:
return res->normalRate();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::ToolTipRole:
return i18n( "Cost per hour, normal time: %1", m_project->locale()->formatMoney( res->normalRate() ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::overtimeRate( const Resource *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return m_project->locale()->formatMoney( res->overtimeRate() );
case Qt::EditRole:
return res->overtimeRate();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::ToolTipRole:
return i18n( "Cost per hour, overtime: %1", m_project->locale()->formatMoney( res->overtimeRate() ) );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::account( const Resource *resource, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
Account *a = resource->account();
return a == 0 ? i18n( "None" ) : a->name();
}
case Qt::ToolTipRole: {
Account *a = resource->account();
return i18n( "Account: %1", (a == 0 ? i18n( "None" ) : a->name() ) );
}
case Role::EnumListValue:
case Qt::EditRole: {
Account *a = resource->account();
return a == 0 ? 0 : ( m_project->accounts().costElements().indexOf( a->name() ) + 1 );
}
case Role::EnumList: {
QStringList lst;
lst << i18n("None");
lst += m_project->accounts().costElements();
return lst;
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceModel::data( const Resource *resource, int property, int role ) const
{
if ( role == Role::ObjectType ) {
return OT_Resource;
}
QVariant result;
if ( resource == 0 ) {
return result;
}
switch ( property ) {
case ResourceName: result = name( resource, role ); break;
case ResourceScope: result = scope( resource, role ); break;
case ResourceType: result = type( resource, role ); break;
case ResourceInitials: result = initials( resource, role ); break;
case ResourceEmail: result = email( resource, role ); break;
case ResourceCalendar: result = calendar( resource, role ); break;
case ResourceLimit: result = units( resource, role ); break;
case ResourceAvailableFrom: result = availableFrom( resource, role ); break;
case ResourceAvailableUntil: result = availableUntil( resource, role ); break;
case ResourceNormalRate: result = normalRate( resource, role ); break;
case ResourceOvertimeRate: result = overtimeRate( resource, role ); break;
case ResourceAccount: result = account( resource, role ); break;
default:
debugPlan<<"data: invalid display value: property="<<property;
break;
}
return result;
}
QVariant ResourceModel::data( const ResourceGroup *group, int property, int role ) const
{
if ( role == Role::ObjectType ) {
return OT_ResourceGroup;
}
QVariant result;
if ( group == 0 ) {
return result;
}
switch ( property ) {
case ResourceModel::ResourceName: result = name( group, role ); break;
case ResourceModel::ResourceScope: result = scope( group, role ); break;
case ResourceModel::ResourceType: result = type( group, role ); break;
default:
if ( role == Qt::DisplayRole ) {
if ( property < propertyCount() ) {
result = QString();
} else {
debugPlan<<"data: invalid display value column"<<property;
return QVariant();
}
}
break;
}
return result;
}
QVariant ResourceModel::headerData( int section, int role )
{
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case ResourceName: return i18n( "Name" );
case ResourceScope: return i18n( "Scope" );
case ResourceType: return i18n( "Type" );
case ResourceInitials: return i18n( "Initials" );
case ResourceEmail: return i18n( "Email" );
case ResourceCalendar: return i18n( "Calendar" );
case ResourceLimit: return i18n( "Limit (%)" );
case ResourceAvailableFrom: return i18n( "Available From" );
case ResourceAvailableUntil: return i18n( "Available Until" );
case ResourceNormalRate: return i18n( "Normal Rate" );
case ResourceOvertimeRate: return i18n( "Overtime Rate" );
case ResourceAccount: return i18n( "Account" );
default: return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
switch (section) {
case ResourceName:
case ResourceScope:
case ResourceType:
case ResourceInitials:
case ResourceEmail:
case ResourceCalendar:
return QVariant();
case ResourceLimit:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
case ResourceAvailableFrom:
case ResourceAvailableUntil:
return QVariant();
case ResourceNormalRate:
case ResourceOvertimeRate:
return (int)(Qt::AlignRight|Qt::AlignVCenter);
case ResourceAccount:
return QVariant();
default:
return QVariant();
}
} else if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case ResourceName: return ToolTip::resourceName();
case ResourceScope: return ToolTip::resourceScope();
case ResourceType: return ToolTip::resourceType();
case ResourceInitials: return ToolTip::resourceInitials();
case ResourceEmail: return ToolTip::resourceEMail();
case ResourceCalendar: return ToolTip::resourceCalendar();
case ResourceLimit: return ToolTip::resourceUnits();
case ResourceAvailableFrom: return ToolTip::resourceAvailableFrom();
case ResourceAvailableUntil: return ToolTip::resourceAvailableUntil();
case ResourceNormalRate: return ToolTip::resourceNormalRate();
case ResourceOvertimeRate: return ToolTip::resourceOvertimeRate();
case ResourceAccount: return ToolTip::resourceAccount();
default: return QVariant();
}
}
return QVariant();
}
//--------------------------------------
ResourceItemModel::ResourceItemModel( QObject *parent )
: ItemModelBase( parent ),
m_group( 0 ),
m_resource( 0 )
{
}
ResourceItemModel::~ResourceItemModel()
{
}
void ResourceItemModel::slotResourceToBeInserted( const ResourceGroup *group, int row )
{
//debugPlan<<group->name()<<","<<row;
Q_ASSERT( m_group == 0 );
m_group = const_cast<ResourceGroup*>(group);
beginInsertRows( index( group ), row, row );
}
void ResourceItemModel::slotResourceInserted( const Resource *resource )
{
//debugPlan<<resource->name();
Q_ASSERT( resource->parentGroup() == m_group );
#ifdef NDEBUG
Q_UNUSED(resource)
#endif
endInsertRows();
m_group = 0;
emit layoutChanged(); //HACK to make the right view react! Bug in qt?
}
void ResourceItemModel::slotResourceToBeRemoved( const Resource *resource )
{
//debugPlan<<resource->name();
Q_ASSERT( m_resource == 0 );
#ifdef NDEBUG
Q_UNUSED(resource)
#endif
m_resource = const_cast<Resource*>(resource);
int row = index( resource ).row();
beginRemoveRows( index( resource->parentGroup() ), row, row );
}
void ResourceItemModel::slotResourceRemoved( const Resource *resource )
{
//debugPlan<<resource->name();
Q_ASSERT( resource == m_resource );
#ifdef NDEBUG
Q_UNUSED(resource)
#endif
endRemoveRows();
m_resource = 0;
}
void ResourceItemModel::slotResourceGroupToBeInserted( const ResourceGroup *group, int row )
{
//debugPlan<<group->name();
Q_ASSERT( m_group == 0 );
m_group = const_cast<ResourceGroup*>(group);
beginInsertRows( QModelIndex(), row, row );
}
void ResourceItemModel::slotResourceGroupInserted( const ResourceGroup *group )
{
//debugPlan<<group->name();
Q_ASSERT( group == m_group );
#ifdef NDEBUG
Q_UNUSED(group)
#endif
endInsertRows();
m_group = 0;
}
void ResourceItemModel::slotResourceGroupToBeRemoved( const ResourceGroup *group )
{
//debugPlan<<group->name();
Q_ASSERT( m_group == 0 );
m_group = const_cast<ResourceGroup*>(group);
int row = index( group ).row();
beginRemoveRows( QModelIndex(), row, row );
}
void ResourceItemModel::slotResourceGroupRemoved( const ResourceGroup *group )
{
//debugPlan<<group->name();
Q_ASSERT( group == m_group );
#ifdef NDEBUG
Q_UNUSED(group)
#endif
endRemoveRows();
m_group = 0;
}
void ResourceItemModel::setProject( Project *project )
{
beginResetModel();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ResourceItemModel::projectDeleted);
disconnect( m_project, &Project::localeChanged, this, &ResourceItemModel::slotLayoutChanged );
disconnect( m_project, &Project::resourceChanged, this, &ResourceItemModel::slotResourceChanged );
disconnect( m_project, &Project::resourceGroupChanged, this, &ResourceItemModel::slotResourceGroupChanged );
disconnect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceItemModel::slotResourceGroupToBeInserted );
disconnect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceItemModel::slotResourceGroupToBeRemoved );
disconnect( m_project, &Project::resourceToBeAdded, this, &ResourceItemModel::slotResourceToBeInserted );
disconnect( m_project, &Project::resourceToBeRemoved, this, &ResourceItemModel::slotResourceToBeRemoved );
disconnect( m_project, &Project::resourceGroupAdded, this, &ResourceItemModel::slotResourceGroupInserted );
disconnect( m_project, &Project::resourceGroupRemoved, this, &ResourceItemModel::slotResourceGroupRemoved );
disconnect( m_project, &Project::resourceAdded, this, &ResourceItemModel::slotResourceInserted );
disconnect( m_project, &Project::resourceRemoved, this, &ResourceItemModel::slotResourceRemoved );
disconnect( m_project, &Project::defaultCalendarChanged, this, &ResourceItemModel::slotCalendarChanged );
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ResourceItemModel::projectDeleted);
connect( m_project, &Project::localeChanged, this, &ResourceItemModel::slotLayoutChanged );
connect( m_project, &Project::resourceChanged, this, &ResourceItemModel::slotResourceChanged );
connect( m_project, &Project::resourceGroupChanged, this, &ResourceItemModel::slotResourceGroupChanged );
connect( m_project, &Project::resourceGroupToBeAdded, this, &ResourceItemModel::slotResourceGroupToBeInserted );
connect( m_project, &Project::resourceGroupToBeRemoved, this, &ResourceItemModel::slotResourceGroupToBeRemoved );
connect( m_project, &Project::resourceToBeAdded, this, &ResourceItemModel::slotResourceToBeInserted );
connect( m_project, &Project::resourceToBeRemoved, this, &ResourceItemModel::slotResourceToBeRemoved );
connect( m_project, &Project::resourceGroupAdded, this, &ResourceItemModel::slotResourceGroupInserted );
connect( m_project, &Project::resourceGroupRemoved, this, &ResourceItemModel::slotResourceGroupRemoved );
connect( m_project, &Project::resourceAdded, this, &ResourceItemModel::slotResourceInserted );
connect( m_project, &Project::resourceRemoved, this, &ResourceItemModel::slotResourceRemoved );
connect( m_project, &Project::defaultCalendarChanged, this, &ResourceItemModel::slotCalendarChanged );
}
m_model.setProject( m_project );
endResetModel();
}
Qt::ItemFlags ResourceItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = ItemModelBase::flags( index );
if ( !m_readWrite ) {
//debugPlan<<"read only"<<flags;
return flags &= ~Qt::ItemIsEditable;
}
if ( !index.isValid() ) {
//debugPlan<<"invalid"<<flags;
return flags;
}
Resource *r = qobject_cast<Resource*>( object ( index ) );
if ( r != 0 ) {
flags |= Qt::ItemIsDragEnabled;
if (r->isShared()) {
flags &= ~Qt::ItemIsEditable;
if (index.column() == ResourceModel::ResourceName) {
flags |= Qt::ItemIsUserCheckable;
}
return flags;
}
switch ( index.column() ) {
case ResourceModel::ResourceName:
flags |= Qt::ItemIsEditable | Qt::ItemIsUserCheckable;
break;
case ResourceModel::ResourceScope:
flags &= ~Qt::ItemIsEditable;
break;
case ResourceModel::ResourceType:
if ( ! r->isBaselined() ) {
flags |= Qt::ItemIsEditable;
}
break;
case ResourceModel::ResourceAccount:
if ( ! r->isBaselined() ) {
flags |= Qt::ItemIsEditable;
}
break;
case ResourceModel::ResourceNormalRate:
if ( ! r->isBaselined() ) {
flags |= Qt::ItemIsEditable;
}
break;
case ResourceModel::ResourceOvertimeRate:
if ( ! r->isBaselined() ) {
flags |= Qt::ItemIsEditable;
}
break;
default:
flags |= Qt::ItemIsEditable;
}
//debugPlan<<"resource"<<flags;
} else {
ResourceGroup *g = qobject_cast<ResourceGroup*>( object( index ) );
if ( g ) {
if (g->isShared()) {
flags &= ~Qt::ItemIsEditable;
return flags;
}
flags |= Qt::ItemIsDropEnabled;
switch ( index.column() ) {
case ResourceModel::ResourceName: flags |= Qt::ItemIsEditable; break;
case ResourceModel::ResourceType: flags |= Qt::ItemIsEditable; break;
default: flags &= ~Qt::ItemIsEditable;
}
//debugPlan<<"group"<<flags;
}
}
return flags;
}
QModelIndex ResourceItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() || m_project == 0 ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
Resource *r = resource( index );
if ( r && r->parentGroup() ) {
// only resources have parent
int row = m_project->indexOf( r->parentGroup() );
return createIndex( row, 0, r->parentGroup() );
}
return QModelIndex();
}
QModelIndex ResourceItemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
if ( ! parent.isValid() ) {
if ( row < m_project->numResourceGroups() ) {
return createIndex( row, column, m_project->resourceGroupAt( row ) );
}
return QModelIndex();
}
ResourceGroup *g = group( parent );
if ( g ) {
if ( row < g->numResources() ) {
return createIndex( row, column, g->resourceAt( row ) );
}
return QModelIndex();
}
return QModelIndex();
}
QModelIndex ResourceItemModel::index( const Resource *resource, int column ) const
{
if ( m_project == 0 || resource == 0 ) {
return QModelIndex();
}
Resource *r = const_cast<Resource*>(resource);
int row = -1;
ResourceGroup *par = r->parentGroup();
if ( par ) {
row = par->indexOf( r );
return createIndex( row, column, r );
}
return QModelIndex();
}
QModelIndex ResourceItemModel::index( const ResourceGroup *group, int column ) const
{
if ( m_project == 0 || group == 0 ) {
return QModelIndex();
}
ResourceGroup *g = const_cast<ResourceGroup*>(group);
int row = m_project->indexOf( g );
return createIndex( row, column, g );
}
int ResourceItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return m_model.propertyCount();
}
int ResourceItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 ) {
return 0;
}
if ( ! parent.isValid() ) {
return m_project->numResourceGroups();
}
ResourceGroup *g = group( parent );
if ( g ) {
return g->numResources();
}
return 0;
}
QVariant ResourceItemModel::name( const ResourceGroup *res, int role ) const
{
//debugPlan<<res->name()<<","<<role;
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return res->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool ResourceItemModel::setName( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() == res->name() ) {
return false;
}
emit executeCommand( new ModifyResourceNameCmd( res, value.toString(), kundo2_i18n( "Modify resource name" ) ) );
return true;
case Qt::CheckStateRole:
emit executeCommand( new ModifyResourceAutoAllocateCmd( res, value.toBool(), kundo2_i18n( "Modify resource auto allocate" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setName( ResourceGroup *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() == res->name() ) {
return false;
}
emit executeCommand( new ModifyResourceGroupNameCmd( res, value.toString(), kundo2_i18n( "Modify resourcegroup name" ) ) );
return true;
}
return false;
}
QVariant ResourceItemModel::type( const ResourceGroup *res, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return res->typeToString( true );
case Qt::EditRole:
return res->typeToString( false );
case Role::EnumList:
return res->typeToStringList( true );
case Role::EnumListValue:
return (int)res->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool ResourceItemModel::setType( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Resource::Type v;
QStringList lst = res->typeToStringList( false );
if ( lst.contains( value.toString() ) ) {
v = static_cast<Resource::Type>( lst.indexOf( value.toString() ) );
} else {
v = static_cast<Resource::Type>( value.toInt() );
}
if ( v == res->type() ) {
return false;
}
emit executeCommand( new ModifyResourceTypeCmd( res, v, kundo2_i18n( "Modify resource type" ) ) );
return true;
}
}
return false;
}
bool ResourceItemModel::setType( ResourceGroup *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
ResourceGroup::Type v;
QStringList lst = res->typeToStringList( false );
if ( lst.contains( value.toString() ) ) {
v = static_cast<ResourceGroup::Type>( lst.indexOf( value.toString() ) );
} else {
v = static_cast<ResourceGroup::Type>( value.toInt() );
}
if ( v == res->type() ) {
return false;
}
emit executeCommand( new ModifyResourceGroupTypeCmd( res, v, kundo2_i18n( "Modify resourcegroup type" ) ) );
return true;
}
}
return false;
}
bool ResourceItemModel::setInitials( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() == res->initials() ) {
return false;
}
emit executeCommand( new ModifyResourceInitialsCmd( res, value.toString(), kundo2_i18n( "Modify resource initials" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setEmail( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toString() == res->email() ) {
return false;
}
emit executeCommand( new ModifyResourceEmailCmd( res, value.toString(), kundo2_i18n( "Modify resource email" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setCalendar( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
{
Calendar *c = 0;
if ( value.toInt() > 0 ) {
QStringList lst = m_model.calendar( res, Role::EnumList ).toStringList();
if ( value.toInt() < lst.count() ) {
c = m_project->calendarByName( lst.at( value.toInt() ) );
}
}
if ( c == res->calendar( true ) ) {
return false;
}
emit executeCommand( new ModifyResourceCalendarCmd( res, c, kundo2_i18n( "Modify resource calendar" ) ) );
return true;
}
}
return false;
}
bool ResourceItemModel::setUnits( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toInt() == res->units() ) {
return false;
}
emit executeCommand( new ModifyResourceUnitsCmd( res, value.toInt(), kundo2_i18n( "Modify resource available units" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setAvailableFrom( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toDateTime() == res->availableFrom() ) {
return false;
}
emit executeCommand( new ModifyResourceAvailableFromCmd( res, value.toDateTime(), kundo2_i18n( "Modify resource available from" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setAvailableUntil( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toDateTime() == res->availableUntil() ) {
return false;
}
emit executeCommand( new ModifyResourceAvailableUntilCmd( res, value.toDateTime(), kundo2_i18n( "Modify resource available until" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setNormalRate( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toDouble() == res->normalRate() ) {
return false;
}
emit executeCommand( new ModifyResourceNormalRateCmd( res, value.toDouble(), kundo2_i18n( "Modify resource normal rate" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setOvertimeRate( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole:
if ( value.toDouble() == res->overtimeRate() ) {
return false;
}
emit executeCommand( new ModifyResourceOvertimeRateCmd( res, value.toDouble(), kundo2_i18n( "Modify resource overtime rate" ) ) );
return true;
}
return false;
}
bool ResourceItemModel::setAccount( Resource *res, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Account *a = 0;
if ( value.type() == QVariant::Int ) {
QStringList lst = m_model.account( res, Role::EnumList ).toStringList();
if ( value.toInt() >= lst.count() ) {
return false;
}
a = m_project->accounts().findAccount( lst.at( value.toInt() ) );
} else if ( value.type() == QVariant::String ) {
a = m_project->accounts().findAccount( value.toString() );
}
Account *old = res->account();
if ( old != a ) {
emit executeCommand( new ResourceModifyAccountCmd( *res, old, a, kundo2_i18n( "Modify resource account" ) ) );
return true;
}
}
default:
break;
}
return false;
}
QVariant ResourceItemModel::notUsed( const ResourceGroup *, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return QString(" ");
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ResourceItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
QObject *obj = object( index );
if ( obj == 0 ) {
return QVariant();
}
if ( role == Qt::TextAlignmentRole ) {
// use same alignment as in header (headers always horizontal)
return headerData( index.column(), Qt::Horizontal, role );
}
Resource *r = qobject_cast<Resource*>( obj );
if ( r ) {
result = m_model.data( r, index.column(), role );
} else {
ResourceGroup *g = qobject_cast<ResourceGroup*>( obj );
if ( g ) {
result = m_model.data( g, index.column(), role );
}
}
return result;
}
bool ResourceItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if (role != Qt::EditRole && role != Qt::CheckStateRole) {
return false;
}
if ((flags( index ) & (Qt::ItemIsEditable | Qt::ItemIsUserCheckable)) == 0) {
return false;
}
QObject *obj = object( index );
Resource *r = qobject_cast<Resource*>( obj );
if ( r ) {
switch (index.column()) {
case ResourceModel::ResourceName: return setName( r, value, role );
case ResourceModel::ResourceScope: return false; // Not editable
case ResourceModel::ResourceType: return setType( r, value, role );
case ResourceModel::ResourceInitials: return setInitials( r, value, role );
case ResourceModel::ResourceEmail: return setEmail( r, value, role );
case ResourceModel::ResourceCalendar: return setCalendar( r, value, role );
case ResourceModel::ResourceLimit: return setUnits( r, value, role );
case ResourceModel::ResourceAvailableFrom: return setAvailableFrom( r, value, role );
case ResourceModel::ResourceAvailableUntil: return setAvailableUntil( r, value, role );
case ResourceModel::ResourceNormalRate: return setNormalRate( r, value, role );
case ResourceModel::ResourceOvertimeRate: return setOvertimeRate( r, value, role );
case ResourceModel::ResourceAccount: return setAccount( r, value, role );
default:
qWarning("data: invalid display value column %d", index.column());
return false;
}
} else {
ResourceGroup *g = qobject_cast<ResourceGroup*>( obj );
if ( g ) {
switch (index.column()) {
case ResourceModel::ResourceName: return setName( g, value, role );
case ResourceModel::ResourceScope: return false; // Not editable
case ResourceModel::ResourceType: return setType( g, value, role );
default:
qWarning("data: invalid display value column %d", index.column());
return false;
}
}
}
return false;
}
QVariant ResourceItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole || role == Qt::TextAlignmentRole ) {
return m_model.headerData( section, role );
}
}
if ( role == Qt::ToolTipRole ) {
return m_model.headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QAbstractItemDelegate *ResourceItemModel::createDelegate( int col, QWidget *parent ) const
{
switch ( col ) {
case ResourceModel::ResourceType: return new EnumDelegate( parent );
case ResourceModel::ResourceCalendar: return new EnumDelegate( parent );
case ResourceModel::ResourceAvailableFrom: return new DateTimeCalendarDelegate( parent );
case ResourceModel::ResourceAvailableUntil: return new DateTimeCalendarDelegate( parent );
case ResourceModel::ResourceAccount: return new EnumDelegate( parent );
default: break;
}
return 0;
}
QObject *ResourceItemModel::object( const QModelIndex &index ) const
{
QObject *o = 0;
if ( index.isValid() ) {
Q_ASSERT( m_project );
//debugPlan<<(void*)index.internalPointer()<<m_project->resourceGroups()<<m_project->resourceList();
Q_ASSERT(m_project->resourceGroups().contains(static_cast<ResourceGroup*>(index.internalPointer())) || m_project->resourceList().contains(static_cast<Resource*>(index.internalPointer())));
o = static_cast<QObject*>( index.internalPointer() );
Q_ASSERT( o );
}
return o;
}
ResourceGroup *ResourceItemModel::group( const QModelIndex &index ) const
{
return qobject_cast<ResourceGroup*>( object( index ) );
}
Resource *ResourceItemModel::resource( const QModelIndex &index ) const
{
return qobject_cast<Resource*>( object( index ) );
}
void ResourceItemModel::slotCalendarChanged( Calendar* )
{
foreach ( Resource *r, m_project->resourceList() ) {
if ( r->calendar( true ) == 0 ) {
slotResourceChanged( r );
}
}
}
void ResourceItemModel::slotResourceChanged( Resource *res )
{
ResourceGroup *g = res->parentGroup();
if ( g ) {
int row = g->indexOf( res );
emit dataChanged( createIndex( row, 0, res ), createIndex( row, columnCount() - 1, res ) );
return;
}
}
void ResourceItemModel::slotResourceGroupChanged( ResourceGroup *res )
{
Project *p = res->project();
if ( p ) {
int row = p->resourceGroups().indexOf( res );
emit dataChanged( createIndex( row, 0, res ), createIndex( row, columnCount() - 1, res ) );
}
}
Qt::DropActions ResourceItemModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
bool ResourceItemModel::dropAllowed( const QModelIndex &index, int dropIndicatorPosition, const QMimeData *data )
{
if ( data->hasFormat( "application/x-vnd.kde.plan.resourceitemmodel.internal" ) ) {
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.resourceitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
int i = 0;
foreach ( Resource *r, resourceList( stream ) ) {
if (r->isShared()) {
return false;
}
}
}
//debugPlan<<index<<data;
// TODO: if internal, don't allow dropping on my own parent
switch ( dropIndicatorPosition ) {
case ItemModelBase::OnItem:
return qobject_cast<ResourceGroup*>( object( index ) ); // Allow only on group
default:
break;
}
return false;
}
QStringList ResourceItemModel::mimeTypes() const
{
return QStringList()
#ifdef PLAN_KDEPIMLIBS_FOUND
<< "text/x-vcard"
<< "text/directory"
<< "text/uri-list"
#endif
<< "application/x-vnd.kde.plan.resourceitemmodel.internal";
}
void ResourceItemModel::slotDataArrived( KIO::Job *job, const QByteArray &data )
{
if ( m_dropDataMap.contains( job ) ) {
m_dropDataMap[ job ].data += data;
}
}
void ResourceItemModel::slotJobFinished( KJob *job )
{
if ( job->error() || ! m_dropDataMap.contains( job ) ) {
debugPlan<<(job->error() ? "Job error":"Error: no such job");
} else if ( QMimeDatabase().mimeTypeForData( m_dropDataMap[ job ].data ).inherits(QStringLiteral("text/x-vcard") ) ) {
ResourceGroup *g = 0;
if ( m_dropDataMap[ job ].parent.isValid() ) {
g = qobject_cast<ResourceGroup*>( object( m_dropDataMap[ job ].parent ) );
} else {
g = qobject_cast<ResourceGroup*>( object( index( m_dropDataMap[ job ].row, m_dropDataMap[ job ].column, m_dropDataMap[ job ].parent ) ) );
}
if ( g == 0 ) {
debugPlan<<"No group"<<m_dropDataMap[ job ].row<<m_dropDataMap[ job ].column<<m_dropDataMap[ job ].parent;
} else {
createResources( g, m_dropDataMap[ job ].data );
}
}
if ( m_dropDataMap.contains( job ) ) {
m_dropDataMap.remove( job );
}
disconnect(job, &KJob::result, this, &ResourceItemModel::slotJobFinished);
}
bool ResourceItemModel::createResources( ResourceGroup *group, const QByteArray &data )
{
#ifdef PLAN_KCONTACTS_FOUND
KContacts::VCardConverter vc;
KContacts::Addressee::List lst = vc.parseVCards( data );
MacroCommand *m = new MacroCommand( kundo2_i18np( "Add resource from address book", "Add %1 resources from address book", lst.count() ) );
// Note: windows needs this type of iteration
for (int a = 0; a < lst.count(); ++a) {
Resource *r = new Resource();
QString uid = lst[a].uid();
if ( ! m_project->findResource( uid ) ) {
r->setId( uid );
}
r->setName( lst[a].formattedName() );
r->setEmail( lst[a].preferredEmail() );
m->addCommand( new AddResourceCmd( group, r ) );
}
if ( m->isEmpty() ) {
delete m;
return false;
}
emit executeCommand( m );
return true;
#else
Q_UNUSED(group);
Q_UNUSED(data);
return false;
#endif
}
bool ResourceItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
debugPlan<<row<<column<<parent;
if (action == Qt::IgnoreAction) {
return true;
}
if (column > 0) {
return false;
}
ResourceGroup *g = 0;
if ( parent.isValid() ) {
g = qobject_cast<ResourceGroup*>( object( parent ) );
} else {
g = qobject_cast<ResourceGroup*>( object( index( row, column, parent ) ) );
}
if ( g == 0 ) {
debugPlan<<"No group"<<row<<column<<parent;
return false;
}
debugPlan<<data->formats()<<g->name();
if ( data->hasFormat( "application/x-vnd.kde.plan.resourceitemmodel.internal" ) ) {
debugPlan<<action<<Qt::MoveAction;
if ( action == Qt::MoveAction ) {
MacroCommand *m = 0;
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.resourceitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
int i = 0;
foreach ( Resource *r, resourceList( stream ) ) {
if ( r->parentGroup() == g ) {
continue;
}
if ( m == 0 ) m = new MacroCommand( KUndo2MagicString() );
m->addCommand( new MoveResourceCmd( g, r ) );
++i;
}
if ( m ) {
KUndo2MagicString msg = kundo2_i18np( "Move resource", "Move %1 resources", i );
MacroCommand *c = new MacroCommand( msg );
c->addCommand( m );
emit executeCommand( c );
}
return true;
}
if ( action == Qt::CopyAction ) {
MacroCommand *m = 0;
QByteArray encodedData = data->data( "application/x-vnd.kde.plan.resourceitemmodel.internal" );
QDataStream stream(&encodedData, QIODevice::ReadOnly);
int i = 0;
foreach ( Resource *r, resourceList( stream ) ) {
Resource *nr = new Resource( r );
if ( m == 0 ) m = new MacroCommand( KUndo2MagicString() );
m->addCommand( new AddResourceCmd( g, nr ) );
++i;
}
if ( m ) {
KUndo2MagicString msg = kundo2_i18np( "Copy resource", "Copy %1 resources", i );
MacroCommand *c = new MacroCommand( msg );
c->addCommand( m );
emit executeCommand( c );
}
return true;
}
return true;
}
if ( data->hasFormat( "text/x-vcard" ) || data->hasFormat( "text/directory" ) ) {
if ( action != Qt::CopyAction ) {
return false;
}
QString f = data->hasFormat( "text/x-vcard" ) ? "text/x-vcard" : "text/directory";
return createResources( g, data->data( f ) );
}
if ( data->hasFormat( "text/uri-list" ) ) {
const QList<QUrl> urls = data->urls();
if ( urls.isEmpty() ) {
return false;
}
bool result = false;
foreach ( const QUrl &url, urls ) {
if ( url.scheme() != "akonadi" ) {
debugPlan<<url<<"is not 'akonadi'";
continue;
}
// TODO: KIO::get will find out about this as well, no?
KIO::StatJob* statJob = KIO::stat( url );
statJob->setSide( KIO::StatJob::SourceSide );
const bool isUrlReadable = statJob->exec();
if (! isUrlReadable ) {
debugPlan<<url<<"does not exist";
continue;
}
KIO::TransferJob *job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
bool res = connect(job, &KIO::TransferJob::data, this, &ResourceItemModel::slotDataArrived);
Q_ASSERT( res );
Q_UNUSED( res );
res = connect(job, &KJob::result, this, &ResourceItemModel::slotJobFinished);
Q_ASSERT( res );
m_dropDataMap[ job ].action = action;
m_dropDataMap[ job ].row = row;
m_dropDataMap[ job ].column = column;
m_dropDataMap[ job ].parent = parent;
job->start();
result = true;
}
return result;
}
return false;
}
QList<Resource*> ResourceItemModel::resourceList( QDataStream &stream )
{
QList<Resource*> lst;
while (!stream.atEnd()) {
QString id;
stream >> id;
Resource *r = m_project->findResource( id );
if ( r ) {
lst << r;
}
}
debugPlan<<lst;
return lst;
}
QMimeData *ResourceItemModel::mimeData( const QModelIndexList & indexes ) const
{
QMimeData *m = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QList<int> rows;
foreach (const QModelIndex &index, indexes) {
if ( index.isValid() && !rows.contains( index.row() ) ) {
//debugPlan<<index.row();
Resource *r = ::qobject_cast<Resource*>( object( index ) );
if ( r ) {
rows << index.row();
stream << r->id();
} else if ( ::qobject_cast<ResourceGroup*>( object( index ) ) ) {
rows.clear();
break;
}
}
}
if ( rows.isEmpty() ) {
delete m;
return 0;
}
m->setData("application/x-vnd.kde.plan.resourceitemmodel.internal", encodedData);
return m;
}
QModelIndex ResourceItemModel::insertGroup( ResourceGroup *g )
{
//debugPlan;
emit executeCommand( new AddResourceGroupCmd( m_project, g, kundo2_i18n( "Add resource group" ) ) );
int row = m_project->resourceGroups().indexOf( g );
if ( row != -1 ) {
return createIndex( row, 0, g );
}
return QModelIndex();
}
QModelIndex ResourceItemModel::insertResource( ResourceGroup *g, Resource *r, Resource * /*after*/ )
{
//debugPlan;
emit executeCommand( new AddResourceCmd( g, r, kundo2_i18n( "Add resource" ) ) );
int row = g->indexOf( r );
if ( row != -1 ) {
return createIndex( row, 0, r );
}
return QModelIndex();
}
int ResourceItemModel::sortRole( int column ) const
{
switch ( column ) {
case ResourceModel::ResourceAvailableFrom:
case ResourceModel::ResourceAvailableUntil:
return Qt::EditRole;
default:
break;
}
return Qt::DisplayRole;
}
//-------------------
ResourceItemSFModel::ResourceItemSFModel( QObject *parent )
: QSortFilterProxyModel( parent )
{
setDynamicSortFilter( true );
setSourceModel( new ResourceItemModel( this ) );
}
void ResourceItemSFModel::setProject( Project *project )
{
static_cast<ResourceItemModel*>( sourceModel() )->setProject( project );
}
Resource *ResourceItemSFModel::resource( const QModelIndex &idx ) const
{
return static_cast<ResourceItemModel*>( sourceModel() )->resource( mapToSource( idx ) );
}
QModelIndex ResourceItemSFModel::index( Resource *r ) const
{
return mapFromSource( static_cast<ResourceItemModel*>( sourceModel() )->index( r ) );
}
Qt::ItemFlags ResourceItemSFModel::flags( const QModelIndex & index ) const
{
Qt::ItemFlags f = QSortFilterProxyModel::flags( index );
if ( index.isValid() && ! parent( index ).isValid() ) {
// group, not selectable
f &= ~Qt::ItemIsSelectable;
}
return f;
}
void ResourceItemSFModel::addFilteredResource( const Resource *r )
{
if ( ! m_filteredResources.contains( r ) ) {
m_filteredResources << r;
}
}
bool ResourceItemSFModel::filterAcceptsRow( int source_row, const QModelIndex & source_parent ) const
{
//TODO make this general filter
ResourceItemModel *m = static_cast<ResourceItemModel*>( sourceModel() );
if ( m->index( source_row, ResourceModel::ResourceType, source_parent ).data( Role::EnumListValue ).toInt() == ResourceGroup::Type_Work ) {
return false;
}
QModelIndex idx = m->index( source_row, 0, source_parent );
return ! m_filteredResources.contains( m->resource( idx ) );
}
//-----------------------
AllocatedResourceItemModel::AllocatedResourceItemModel( QObject *parent )
: QSortFilterProxyModel( parent ),
m_task( 0 )
{
setDynamicSortFilter( true );
setSourceModel( new ResourceItemModel( this ) );
}
int AllocatedResourceItemModel::columnCount( const QModelIndex &idx ) const
{
Q_UNUSED(idx);
return 2;
}
Project *AllocatedResourceItemModel::project() const
{
return static_cast<ResourceItemModel*>( sourceModel() )->project();
}
void AllocatedResourceItemModel::setProject( Project *project )
{
debugPlan<<this->project()<<"="<<project;
Project *p =this->project();
if ( p ) {
disconnect(p, &Project::nodeChanged, this, &AllocatedResourceItemModel::slotNodeChanged);
}
static_cast<ResourceItemModel*>( sourceModel() )->setProject( project );
if ( project ) {
connect(project, &Project::nodeChanged, this, &AllocatedResourceItemModel::slotNodeChanged);
}
debugPlan<<rowCount()<<":"<<sourceModel()->rowCount();
}
void AllocatedResourceItemModel::reset()
{
beginResetModel();
endResetModel();
emit expandAll();
emit resizeColumnToContents( 0 );
}
void AllocatedResourceItemModel::slotNodeChanged( Node *n )
{
debugPlan<<(n==m_task)<<n<<n->name();
if ( n != m_task ) {
return;
}
reset();
}
Task *AllocatedResourceItemModel::task() const
{
return m_task;
}
void AllocatedResourceItemModel::setTask( Task *task )
{
debugPlan<<m_task<<"="<<task<<(task?task->name():"");
m_task = task;
reset();
debugPlan<<rowCount()<<":"<<sourceModel()->rowCount();
}
QObject* AllocatedResourceItemModel::object(const QModelIndex& idx) const
{
return static_cast<ResourceItemModel*>( sourceModel() )->object( mapToSource( idx ) );
}
Resource *AllocatedResourceItemModel::resource( const QModelIndex &idx ) const
{
return qobject_cast<Resource*>( object( idx ) );
}
QModelIndex AllocatedResourceItemModel::index( Resource *r ) const
{
return mapFromSource( static_cast<ResourceItemModel*>( sourceModel() )->index( r ) );
}
Qt::ItemFlags AllocatedResourceItemModel::flags( const QModelIndex & index ) const
{
Qt::ItemFlags f = QSortFilterProxyModel::flags( index );
f &= ~Qt::ItemIsUserCheckable;
return f;
}
QVariant AllocatedResourceItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if ( section == 1 ) {
if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) {
return xi18nc( "@title:column", "Allocation" );
}
return QVariant();
}
return QSortFilterProxyModel::headerData( section, orientation, role );
}
QVariant AllocatedResourceItemModel::allocation( const Resource *res, int role ) const
{
ResourceRequest *rr = m_task->requests().find( res );
ResourceGroupRequest *gr = m_task->requests().find( res->parentGroup() );
if ( rr == 0 || gr == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole: {
case Qt::EditRole:
// xgettext: no-c-format
return i18nc( "<value>%", "%1%",rr->units() );
}
case Qt::ToolTipRole: {
if ( rr->units() == 0 ) {
return xi18nc( "@info:tooltip", "Not allocated" );
}
return xi18nc( "@info:tooltip", "%1 allocated out of %2 available", gr->count(), res->parentGroup()->numResources() );
}
default:
break;
}
return QVariant();
}
QVariant AllocatedResourceItemModel::allocation( const ResourceGroup *res, int role ) const
{
ResourceGroupRequest *gr = m_task->requests().find( res );
if ( gr == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return QString( "%1 (%2)" ).arg( gr->units() ).arg( gr->count() );
case Qt::ToolTipRole: {
QString s1 = i18ncp( "@info:tooltip",
"%1 resource requested for dynamic allocation",
"%1 resources requested for dynamic allocation",
gr->units() );
QString s2 = i18ncp( "@info:tooltip",
"%1 resource allocated",
"%1 resources allocated",
gr->count() );
return xi18nc( "@info:tooltip", "%1<nl/>%2", s1, s2 );
}
case Qt::WhatsThisRole: {
return xi18nc( "@info:whatsthis",
"<title>Group allocations</title>"
"<para>You can allocate a number of resources from a group and let"
" the scheduler select from the available resources at the time of scheduling.</para>"
" These dynamically allocated resources will be in addition to any resource you have allocated specifically." );
}
case Role::Minimum: {
return 0;
}
case Role::Maximum: {
return res->numResources() - gr->units();
}
default:
break;
}
return QVariant();
}
QVariant AllocatedResourceItemModel::data(const QModelIndex& idx, int role) const
{
if ( m_task == 0 || role == Qt::CheckStateRole || role == Qt::DecorationRole ) {
return QVariant();
}
if ( idx.column() == 1 ) {
switch ( role ) {
case Qt::TextAlignmentRole:
return Qt::AlignLeft;
default: {
QObject *o = object( idx );
Resource *r = qobject_cast<Resource*>( o );
if ( r ) {
return allocation( r, role );
}
ResourceGroup *g = qobject_cast<ResourceGroup*>( o );
if ( g ) {
return allocation( g, role );
}
break;
}
return QVariant();
}
}
return QSortFilterProxyModel::data( idx, role );
}
bool AllocatedResourceItemModel::filterAcceptsRow( int source_row, const QModelIndex & source_parent ) const
{
if ( m_task == 0 ) {
return false;
}
QModelIndex idx = sourceModel()->index( source_row, 0, source_parent );
if ( ! idx.isValid() ) {
return false;
}
bool result = false;
const ResourceRequestCollection &req = m_task->requests();
if ( source_parent.isValid() ) {
const Resource *r = static_cast<ResourceItemModel*>( sourceModel() )->resource( idx );
result = (bool) req.find( r );
} else {
const ResourceGroup *g = static_cast<ResourceItemModel*>( sourceModel() )->group( idx );
ResourceGroupRequest *gr = req.find( g );
result = (bool) gr && ( gr->units() > 0 || gr->count() > 0 );
}
debugPlan<<result<<":"<<source_parent<<idx;
return result;
}
} // namespace KPlato
diff --git a/src/libs/models/kptschedulemodel.cpp b/src/libs/models/kptschedulemodel.cpp
index 1769a91a..8f4e04af 100644
--- a/src/libs/models/kptschedulemodel.cpp
+++ b/src/libs/models/kptschedulemodel.cpp
@@ -1,1264 +1,1265 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen danders@get2net>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptschedulemodel.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptschedule.h"
#include "kptdatetime.h"
#include "kptschedulerplugin.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <QObject>
#include <QStringList>
#include <QLocale>
#include <KFormat>
namespace KPlato
{
//--------------------------------------
ScheduleModel::ScheduleModel( QObject *parent )
: QObject( parent )
{
}
ScheduleModel::~ScheduleModel()
{
}
const QMetaEnum ScheduleModel::columnMap() const
{
return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
}
int ScheduleModel::propertyCount() const
{
return columnMap().keyCount();
}
//--------------------------------------
ScheduleItemModel::ScheduleItemModel( QObject *parent )
: ItemModelBase( parent ),
m_manager( 0 ),
m_flat( false )
{
}
ScheduleItemModel::~ScheduleItemModel()
{
}
void ScheduleItemModel::slotScheduleManagerToBeInserted( const ScheduleManager *parent, int row )
{
//debugPlan<<parent<<row;
if ( m_flat ) {
return; // handle in *Inserted();
}
Q_ASSERT( m_manager == 0 );
m_manager = const_cast<ScheduleManager*>(parent);
beginInsertRows( index( parent ), row, row );
}
void ScheduleItemModel::slotScheduleManagerInserted( const ScheduleManager *manager )
{
//debugPlan<<manager->name();
if ( m_flat ) {
int row = m_project->allScheduleManagers().indexOf( const_cast<ScheduleManager*>( manager ) );
Q_ASSERT( row >= 0 );
beginInsertRows( QModelIndex(), row, row );
m_managerlist.insert( row, const_cast<ScheduleManager*>( manager ) );
endInsertRows();
emit scheduleManagerAdded( const_cast<ScheduleManager*>( manager ) );
return;
}
Q_ASSERT( manager->parentManager() == m_manager );
endInsertRows();
m_manager = 0;
emit scheduleManagerAdded( const_cast<ScheduleManager*>( manager ) );
}
void ScheduleItemModel::slotScheduleManagerToBeRemoved( const ScheduleManager *manager )
{
//debugPlan<<manager->name();
if ( m_flat ) {
int row = m_managerlist.indexOf( const_cast<ScheduleManager*>( manager ) );
beginRemoveRows( QModelIndex(), row, row );
m_managerlist.removeAt( row );
return;
}
Q_ASSERT( m_manager == 0 );
m_manager = const_cast<ScheduleManager*>(manager);
QModelIndex i = index( manager );
int row = i.row();
beginRemoveRows( parent( i ), row, row );
}
void ScheduleItemModel::slotScheduleManagerRemoved( const ScheduleManager *manager )
{
//debugPlan<<manager->name();
if ( m_flat ) {
endRemoveRows();
return;
}
Q_ASSERT( manager == m_manager ); Q_UNUSED( manager );
endRemoveRows();
m_manager = 0;
}
void ScheduleItemModel::slotScheduleManagerToBeMoved( const ScheduleManager *manager )
{
//debugPlan<<this<<manager->name()<<"from"<<(manager->parentManager()?manager->parentManager()->name():"project");
slotScheduleManagerToBeRemoved( manager );
}
void ScheduleItemModel::slotScheduleManagerMoved( const ScheduleManager *manager, int index )
{
//debugPlan<<this<<manager->name()<<"to"<<manager->parentManager()<<index;
slotScheduleManagerRemoved( manager );
slotScheduleManagerToBeInserted( manager->parentManager(), index );
slotScheduleManagerInserted( manager );
}
void ScheduleItemModel::slotScheduleToBeInserted( const ScheduleManager *, int /*row*/ )
{
}
void ScheduleItemModel::slotScheduleInserted( const MainSchedule * )
{
}
void ScheduleItemModel::slotScheduleToBeRemoved( const MainSchedule * )
{
}
void ScheduleItemModel::slotScheduleRemoved( const MainSchedule * )
{
}
void ScheduleItemModel::setProject( Project *project )
{
beginResetModel();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ScheduleItemModel::projectDeleted);
disconnect(m_project, &Project::scheduleManagerChanged, this, &ScheduleItemModel::slotManagerChanged);
disconnect(m_project, &Project::scheduleManagerToBeAdded, this, &ScheduleItemModel::slotScheduleManagerToBeInserted);
disconnect(m_project, &Project::scheduleManagerToBeRemoved, this, &ScheduleItemModel::slotScheduleManagerToBeRemoved);
disconnect(m_project, &Project::scheduleManagerAdded, this, &ScheduleItemModel::slotScheduleManagerInserted);
disconnect(m_project, &Project::scheduleManagerRemoved, this, &ScheduleItemModel::slotScheduleManagerRemoved);
disconnect(m_project, &Project::scheduleManagerToBeMoved, this, &ScheduleItemModel::slotScheduleManagerToBeMoved);
disconnect(m_project, &Project::scheduleManagerMoved, this, &ScheduleItemModel::slotScheduleManagerMoved);
disconnect(m_project, &Project::scheduleChanged, this, &ScheduleItemModel::slotScheduleChanged);
disconnect(m_project, &Project::scheduleToBeAdded, this, &ScheduleItemModel::slotScheduleToBeInserted);
disconnect(m_project, &Project::scheduleToBeRemoved, this, &ScheduleItemModel::slotScheduleToBeRemoved);
disconnect(m_project, &Project::scheduleAdded, this, &ScheduleItemModel::slotScheduleInserted);
disconnect(m_project, &Project::scheduleRemoved, this, &ScheduleItemModel::slotScheduleRemoved);
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ScheduleItemModel::projectDeleted);
connect(m_project, &Project::scheduleManagerChanged, this, &ScheduleItemModel::slotManagerChanged);
connect(m_project, &Project::scheduleManagerToBeAdded, this, &ScheduleItemModel::slotScheduleManagerToBeInserted);
connect(m_project, &Project::scheduleManagerToBeRemoved, this, &ScheduleItemModel::slotScheduleManagerToBeRemoved);
connect(m_project, &Project::scheduleManagerAdded, this, &ScheduleItemModel::slotScheduleManagerInserted);
connect(m_project, &Project::scheduleManagerRemoved, this, &ScheduleItemModel::slotScheduleManagerRemoved);
connect(m_project, &Project::scheduleManagerToBeMoved, this, &ScheduleItemModel::slotScheduleManagerToBeMoved);
connect(m_project, &Project::scheduleManagerMoved, this, &ScheduleItemModel::slotScheduleManagerMoved);
connect(m_project, &Project::scheduleChanged, this, &ScheduleItemModel::slotScheduleChanged);
connect(m_project, &Project::scheduleToBeAdded, this, &ScheduleItemModel::slotScheduleToBeInserted);
connect(m_project, &Project::scheduleToBeRemoved, this, &ScheduleItemModel::slotScheduleToBeRemoved);
connect(m_project, &Project::scheduleAdded, this, &ScheduleItemModel::slotScheduleInserted);
connect(m_project, &Project::scheduleRemoved, this, &ScheduleItemModel::slotScheduleRemoved);
}
setFlat( m_flat ); // update m_managerlist
endResetModel();
}
void ScheduleItemModel::slotManagerChanged( ScheduleManager *sch )
{
if ( m_flat ) {
int row = m_managerlist.indexOf( sch );
emit dataChanged( createIndex( row, 0, sch ), createIndex( row, columnCount() - 1, sch ) );
return;
}
int r = sch->parentManager() ? sch->parentManager()->indexOf( sch ) : m_project->indexOf( sch );
//debugPlan<<sch<<":"<<r;
emit dataChanged( createIndex( r, 0, sch ), createIndex( r, columnCount() - 1, sch ) );
}
void ScheduleItemModel::slotScheduleChanged( MainSchedule * )
{
}
Qt::ItemFlags ScheduleItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( !index.isValid() )
return flags;
if ( !m_readWrite ) {
return flags &= ~Qt::ItemIsEditable;
}
ScheduleManager *sm = manager( index );
if ( sm == 0 ) {
return flags;
}
SchedulerPlugin *pl = sm->schedulerPlugin();
if ( pl == 0 ) {
return flags;
}
int capabilities = pl->capabilities();
flags &= ~Qt::ItemIsEditable;
if ( sm && ! sm->isBaselined() ) {
switch ( index.column() ) {
case ScheduleModel::ScheduleState: break;
case ScheduleModel::ScheduleOverbooking:
if ( capabilities & SchedulerPlugin::AllowOverbooking &&
capabilities & SchedulerPlugin::AvoidOverbooking )
{
flags |= Qt::ItemIsEditable;
}
break;
case ScheduleModel::ScheduleDirection:
if ( sm->parentManager() == 0 &&
capabilities & SchedulerPlugin::ScheduleForward &&
capabilities & SchedulerPlugin::ScheduleBackward)
{
flags |= Qt::ItemIsEditable;
}
break;
case ScheduleModel::SchedulePlannedStart: break;
case ScheduleModel::SchedulePlannedFinish: break;
case ScheduleModel::ScheduleGranularity:
if ( ! sm->supportedGranularities().isEmpty() ) {
flags |= Qt::ItemIsEditable;
}
break;
default: flags |= Qt::ItemIsEditable; break;
}
return flags;
}
return flags;
}
QModelIndex ScheduleItemModel::parent( const QModelIndex &inx ) const
{
if ( !inx.isValid() || m_project == 0 || m_flat ) {
return QModelIndex();
}
//debugPlan<<inx.internalPointer()<<":"<<inx.row()<<","<<inx.column();
ScheduleManager *sm = manager( inx );
if ( sm == 0 ) {
return QModelIndex();
}
return index( sm->parentManager() );
}
QModelIndex ScheduleItemModel::index( int row, int column, const QModelIndex &parent ) const
{
//debugPlan<<m_project<<":"<<row<<","<<column;
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 || row >= rowCount( parent ) ) {
//debugPlan<<row<<","<<column<<" out of bounce";
return QModelIndex();
}
if ( m_flat ) {
return createIndex( row, column, m_managerlist[ row ] );
}
if ( parent.isValid() ) {
return createIndex( row, column, manager( parent )->children().value( row ) );
}
return createIndex( row, column, m_project->scheduleManagers().value( row ) );
}
QModelIndex ScheduleItemModel::index( const ScheduleManager *manager ) const
{
if ( m_project == 0 || manager == 0 ) {
return QModelIndex();
}
if ( m_flat ) {
return createIndex( m_managerlist.indexOf( const_cast<ScheduleManager*>( manager ) ), 0, const_cast<ScheduleManager*>(manager) );
}
if ( manager->parentManager() == 0 ) {
return createIndex( m_project->indexOf( manager ), 0, const_cast<ScheduleManager*>(manager) );
}
return createIndex( manager->parentManager()->indexOf( manager ), 0, const_cast<ScheduleManager*>(manager) );
}
int ScheduleItemModel::columnCount( const QModelIndex &/*parent*/ ) const
{
return m_model.propertyCount();
}
int ScheduleItemModel::rowCount( const QModelIndex &parent ) const
{
if ( m_project == 0 ) {
return 0;
}
if ( m_flat ) {
return m_managerlist.count();
}
if ( !parent.isValid() ) {
return m_project->numScheduleManagers();
}
ScheduleManager *sm = manager( parent );
if ( sm ) {
//debugPlan<<sm->name()<<","<<sm->children().count();
return sm->children().count();
}
return 0;
}
QVariant ScheduleItemModel::name( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return sm->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Qt::DecorationRole:
if ( sm->isBaselined() ) {
return koIcon("view-time-schedule-baselined");
}
return QVariant();
default:
break;
}
return QVariant();
}
bool ScheduleItemModel::setName( const QModelIndex &index, const QVariant &value, int role )
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return false;
}
switch ( role ) {
case Qt::EditRole:
emit executeCommand( new ModifyScheduleManagerNameCmd( *sm, value.toString(), kundo2_i18n( "Modify schedule name" ) ) );
return true;
}
return false;
}
QVariant ScheduleItemModel::state( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
{
if ( sm->progress() > 0 ) {
return sm->progress();
}
QStringList l = sm->state();
if ( l.isEmpty() ) {
return "";
}
return l.first();
}
case Qt::EditRole:
{
QStringList l = sm->state();
if ( l.isEmpty() ) {
return "";
}
return l.first();
}
case Qt::ToolTipRole:
return sm->state().join(", ");
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::Maximum:
return sm->maxProgress();
case Role::Minimum:
return 0;
}
return QVariant();
}
bool ScheduleItemModel::setState( const QModelIndex &, const QVariant &, int role )
{
switch ( role ) {
case Qt::EditRole:
return false;
}
return false;
}
QVariant ScheduleItemModel::allowOverbooking( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
SchedulerPlugin *pl = sm->schedulerPlugin();
if ( pl == 0 ) {
return QVariant();
}
int capabilities = pl->capabilities();
switch ( role ) {
case Qt::EditRole:
return sm->allowOverbooking();
case Qt::DisplayRole:
if ( capabilities & SchedulerPlugin::AllowOverbooking &&
capabilities & SchedulerPlugin::AvoidOverbooking )
{
return sm->allowOverbooking() ? i18n( "Allow" ) : i18n( "Avoid" );
}
if ( capabilities & SchedulerPlugin::AllowOverbooking ) {
return sm->allowOverbooking() ? i18n( "Allow" ) : i18n( "(Avoid)" );
}
if ( capabilities & SchedulerPlugin::AvoidOverbooking ) {
return sm->allowOverbooking() ? i18n( "(Allow)" ) : i18n( "Avoid" );
}
break;
case Qt::ToolTipRole:
if ( capabilities & SchedulerPlugin::AllowOverbooking &&
capabilities & SchedulerPlugin::AvoidOverbooking )
{
return sm->allowOverbooking()
? xi18nc( "@info:tooltip", "Allow overbooking resources" )
: xi18nc( "@info:tooltip", "Avoid overbooking resources" );
}
if ( capabilities & SchedulerPlugin::AllowOverbooking ) {
return sm->allowOverbooking()
? xi18nc( "@info:tooltip", "Allow overbooking of resources" )
: xi18nc( "@info:tooltip 1=scheduler name", "%1 always allows overbooking of resources", pl->name() );
}
if ( capabilities & SchedulerPlugin::AvoidOverbooking ) {
return sm->allowOverbooking()
? xi18nc( "@info:tooltip 1=scheduler name", "%1 always avoids overbooking of resources", pl->name() )
: xi18nc( "@info:tooltip", "Avoid overbooking resources" );
}
break;
case Role::EnumList:
return QStringList() << xi18nc( "@label:listbox", "Avoid" ) << xi18nc( "@label:listbox", "Allow" );
case Role::EnumListValue:
return sm->allowOverbooking() ? 1 : 0;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool ScheduleItemModel::setAllowOverbooking( const QModelIndex &index, const QVariant &value, int role )
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return false;
}
switch ( role ) {
case Qt::EditRole:
emit executeCommand( new ModifyScheduleManagerAllowOverbookingCmd( *sm, value.toBool(), kundo2_i18n( "Modify allow overbooking" ) ) );
return true;
}
return false;
}
QVariant ScheduleItemModel::usePert( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::EditRole:
return sm->usePert();
case Qt::DisplayRole:
return sm->usePert() ? i18n( "PERT" ) : i18n( "None" );
case Qt::ToolTipRole:
return sm->usePert()
? xi18nc( "@info:tooltip", "Use PERT distribution to calculate expected estimate for the tasks" )
: xi18nc( "@info:tooltip", "Use the tasks expected estimate directly" );
case Role::EnumList:
return QStringList() << xi18nc( "@label:listbox", "None" ) << xi18nc( "@label:listbox", "PERT" );
case Role::EnumListValue:
return sm->usePert() ? 1 : 0;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool ScheduleItemModel::setUsePert( const QModelIndex &index, const QVariant &value, int role )
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return false;
}
switch ( role ) {
case Qt::EditRole:
emit executeCommand( new ModifyScheduleManagerDistributionCmd( *sm, value.toBool(), kundo2_i18n( "Modify scheduling distribution" ) ) );
slotManagerChanged( static_cast<ScheduleManager*>( sm ) );
return true;
}
return false;
}
QVariant ScheduleItemModel::projectStart( const QModelIndex &index, int role ) const
{
if ( m_project == 0 ) {
return QVariant();
}
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
if ( sm->isScheduled() ) {
return QLocale().toString( sm->expected()->start(), QLocale::ShortFormat );
}
break;
case Qt::EditRole:
if ( sm->isScheduled() ) {
return sm->expected()->start();
}
break;
case Qt::ToolTipRole:
if ( sm->isScheduled() ) {
return xi18nc( "@info:tooltip", "Planned start: %1<nl/>Target start: %2", QLocale().toString( sm->expected()->start(), QLocale::ShortFormat ), QLocale().toString( m_project->constraintStartTime(), QLocale::ShortFormat ) );
} else {
return xi18nc( "@info:tooltip", "Target start: %1", QLocale().toString( m_project->constraintStartTime(), QLocale::ShortFormat ) );
}
break;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ScheduleItemModel::projectEnd( const QModelIndex &index, int role ) const
{
if ( m_project == 0 ) {
return QVariant();
}
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
if ( sm->isScheduled() ) {
return QLocale().toString( sm->expected()->end(), QLocale::ShortFormat );
}
break;
case Qt::EditRole:
if ( sm->isScheduled() ) {
return sm->expected()->end();
}
break;
case Qt::ToolTipRole:
if ( sm->isScheduled() ) {
return xi18nc( "@info:tooltip", "Planned finish: %1<nl/>Target finish: %2", QLocale().toString( sm->expected()->end(), QLocale::ShortFormat ), QLocale().toString( m_project->constraintEndTime(), QLocale::ShortFormat ) );
} else {
return xi18nc( "@info:tooltip", "Target finish: %1", QLocale().toString( m_project->constraintEndTime(), QLocale::ShortFormat ) );
}
break;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ScheduleItemModel::schedulingDirection( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return QVariant();
}
SchedulerPlugin *pl = sm->schedulerPlugin();
if ( pl == 0 ) {
return QVariant();
}
int capabilities = pl->capabilities();
switch ( role ) {
case Qt::EditRole:
return sm->schedulingDirection();
case Qt::DisplayRole:
if ( capabilities & SchedulerPlugin::ScheduleForward &&
capabilities & SchedulerPlugin::ScheduleBackward )
{
return sm->schedulingDirection() ? i18n( "Backwards" ) : i18n( "Forward" );
}
if ( capabilities & SchedulerPlugin::ScheduleForward ) {
return sm->schedulingDirection() ? i18n( "(Backwards)" ) : i18n( "Forward" );
}
if ( capabilities & SchedulerPlugin::ScheduleBackward ) {
return sm->schedulingDirection() ? i18n( "Backwards" ) : i18n( "(Forward)" );
}
break;
case Qt::ToolTipRole:
if ( capabilities & SchedulerPlugin::ScheduleForward &&
capabilities & SchedulerPlugin::ScheduleBackward )
{
return sm->schedulingDirection()
? xi18nc( "@info:tooltip", "Schedule project from target end time" )
: xi18nc( "@info:tooltip", "Schedule project from target start time" );
}
if ( capabilities & SchedulerPlugin::ScheduleForward ) {
return sm->schedulingDirection()
? xi18nc( "@info:tooltip 1=scheduler name", "%1 always schedules from target start time", pl->name() )
: xi18nc( "@info:tooltip", "Schedule project from target start time" );
}
if ( capabilities & SchedulerPlugin::ScheduleBackward ) {
return sm->schedulingDirection()
? xi18nc( "@info:tooltip", "Schedule project from target end time" )
: xi18nc( "@info:tooltip 1=scheduler name", "%1 always schedules from target end time", pl->name() );
}
break;
case Role::EnumList:
return QStringList() << xi18nc( "@label:listbox", "Forward" ) << xi18nc( "@label:listbox", "Backwards" );
case Role::EnumListValue:
return sm->schedulingDirection() ? 1 : 0;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool ScheduleItemModel::setSchedulingDirection( const QModelIndex &index, const QVariant &value, int role )
{
ScheduleManager *sm = manager ( index );
if ( sm == 0 ) {
return false;
}
switch ( role ) {
case Qt::EditRole:
emit executeCommand(new ModifyScheduleManagerSchedulingDirectionCmd( *sm, value.toBool(), kundo2_i18n( "Modify scheduling direction" ) ) );
slotManagerChanged( static_cast<ScheduleManager*>( sm ) );
return true;
}
return false;
}
QVariant ScheduleItemModel::scheduler( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager( index );
if ( sm == 0 ) {
return QVariant();
}
SchedulerPlugin *pl = sm->schedulerPlugin();
if ( pl ) {
switch ( role ) {
case Qt::EditRole:
return sm->schedulerPluginId();
case Qt::DisplayRole:
return pl ? pl->name() : i18n( "Unknown" );
case Qt::ToolTipRole:
return pl ? pl->comment() : QString();
case Role::EnumList:
return sm->schedulerPluginNames();
case Role::EnumListValue:
return sm->schedulerPluginIndex();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
return QVariant();
case Qt::WhatsThisRole: {
QString s = pl->description();
return s.isEmpty() ? QVariant() : QVariant( s );
}
}
}
return QVariant();
}
bool ScheduleItemModel::setScheduler( const QModelIndex &index, const QVariant &value, int role )
{
ScheduleManager *sm = manager( index );
if ( sm != 0 ) {
switch ( role ) {
case Qt::EditRole: {
emit executeCommand( new ModifyScheduleManagerSchedulerCmd( *sm, value.toInt(), kundo2_i18n( "Modify scheduler" ) ) );
return true;
}
}
}
return false;
}
QVariant ScheduleItemModel::isScheduled( const QModelIndex &index, int role ) const
{
ScheduleManager *sm = manager( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::EditRole:
return sm->isScheduled();
case Qt::DisplayRole:
return sm->isScheduled() ? i18n( "Scheduled" ) : i18n( "Not scheduled" );
case Qt::ToolTipRole:
break;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant ScheduleItemModel::granularity(const QModelIndex &index, int role) const
{
ScheduleManager *sm = manager( index );
if ( sm == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::EditRole:
case Role::EnumListValue:
return qMin( sm->granularity(), sm->supportedGranularities().count() - 1 );
case Qt::DisplayRole: {
QList<long unsigned int> lst = sm->supportedGranularities();
if ( lst.isEmpty() ) {
return i18nc( "Scheduling granularity not supported", "None" );
}
int idx = sm->granularity();
qulonglong g = idx < lst.count() ? lst[ idx ] : lst.last();
return KFormat().formatDuration( g );
}
case Qt::ToolTipRole: {
QList<long unsigned int> lst = sm->supportedGranularities();
if ( lst.isEmpty() ) {
return xi18nc( "@info:tooltip", "Scheduling granularity not supported" );
}
int idx = sm->granularity();
qulonglong g = idx < lst.count() ? lst[ idx ] : lst.last();
return xi18nc( "@info:tooltip", "Selected scheduling granularity: %1", KFormat().formatDuration( g ) );
}
case Qt::TextAlignmentRole:
return Qt::AlignRight;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::EnumList: {
QStringList sl;
KFormat format;
foreach ( long unsigned int v, sm->supportedGranularities() ) {
sl << format.formatDuration( v );
}
return sl;
}
}
return QVariant();
}
bool ScheduleItemModel::setGranularity( const QModelIndex &index, const QVariant &value, int role )
{
ScheduleManager *sm = manager( index );
if ( sm != 0 ) {
switch ( role ) {
case Qt::EditRole: {
emit executeCommand( new ModifyScheduleManagerSchedulingGranularityCmd( *sm, value.toInt(), kundo2_i18n( "Modify scheduling granularity" ) ) );
return true;
}
}
}
return false;
}
QVariant ScheduleItemModel::data( const QModelIndex &index, int role ) const
{
//debugPlan<<index.row()<<","<<index.column();
QVariant result;
if ( role == Qt::TextAlignmentRole ) {
return headerData( index.column(), Qt::Horizontal, role );
}
switch ( index.column() ) {
case ScheduleModel::ScheduleName: result = name( index, role ); break;
case ScheduleModel::ScheduleState: result = state( index, role ); break;
case ScheduleModel::ScheduleDirection: result = schedulingDirection( index, role ); break;
case ScheduleModel::ScheduleOverbooking: result = allowOverbooking( index, role ); break;
case ScheduleModel::ScheduleDistribution: result = usePert( index, role ); break;
case ScheduleModel::SchedulePlannedStart: result = projectStart( index, role ); break;
case ScheduleModel::SchedulePlannedFinish: result = projectEnd( index, role ); break;
case ScheduleModel::ScheduleScheduler: result = scheduler( index, role ); break;
case ScheduleModel::ScheduleGranularity: result = granularity( index, role ); break;
case ScheduleModel::ScheduleScheduled: result = isScheduled( index, role ); break;
default:
debugPlan<<"data: invalid display value column"<<index.column();
return QVariant();
}
if ( result.isValid() ) {
if ( role == Qt::DisplayRole && result.type() == QVariant::String && result.toString().isEmpty()) {
// HACK to show focus in empty cells
result = ' ';
}
return result;
}
return QVariant();
}
bool ScheduleItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
if ( !index.isValid() || ( flags( index ) & Qt::ItemIsEditable ) == 0 || role != Qt::EditRole ) {
return false;
}
switch (index.column()) {
case ScheduleModel::ScheduleName: return setName( index, value, role );
case ScheduleModel::ScheduleState: return setState( index, value, role );
case ScheduleModel::ScheduleDirection: return setSchedulingDirection( index, value, role );
case ScheduleModel::ScheduleOverbooking: return setAllowOverbooking( index, value, role );
case ScheduleModel::ScheduleDistribution: return setUsePert( index, value, role );
// case ScheduleModel::ScheduleCalculate: return setCalculateAll( index, value, role );
case ScheduleModel::SchedulePlannedStart: return false;
case ScheduleModel::SchedulePlannedFinish: return false;
case ScheduleModel::ScheduleScheduler: return setScheduler( index, value, role ); break;
case ScheduleModel::ScheduleGranularity: return setGranularity( index, value, role );
case ScheduleModel::ScheduleScheduled: break;
default:
qWarning("data: invalid display value column %d", index.column());
break;
}
return false;
}
QVariant ScheduleItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case ScheduleModel::ScheduleName: return i18n( "Name" );
case ScheduleModel::ScheduleState: return i18n( "State" );
case ScheduleModel::ScheduleDirection: return i18n( "Direction" );
case ScheduleModel::ScheduleOverbooking: return i18n( "Overbooking" );
case ScheduleModel::ScheduleDistribution: return i18n( "Distribution" );
// case ScheduleModel::ScheduleCalculate: return i18n( "Calculate" );
case ScheduleModel::SchedulePlannedStart: return i18n( "Planned Start" );
case ScheduleModel::SchedulePlannedFinish: return i18n( "Planned Finish" );
case ScheduleModel::ScheduleScheduler: return i18n( "Scheduler" );
case ScheduleModel::ScheduleGranularity: return xi18nc( "title:column", "Granularity" );
case ScheduleModel::ScheduleScheduled: return i18n( "Scheduled" );
default: return QVariant();
}
} else if (role == Qt::EditRole) {
switch ( section ) {
case ScheduleModel::ScheduleName: return "Name";
case ScheduleModel::ScheduleState: return "State";
case ScheduleModel::ScheduleDirection: return "Direction";
case ScheduleModel::ScheduleOverbooking: return "Overbooking";
case ScheduleModel::ScheduleDistribution: return "Distribution";
// case ScheduleModel::ScheduleCalculate: return i18n( "Calculate" );
case ScheduleModel::SchedulePlannedStart: return "Planned Start";
case ScheduleModel::SchedulePlannedFinish: return "Planned Finish";
case ScheduleModel::ScheduleScheduler: return "Scheduler";
case ScheduleModel::ScheduleGranularity: return "Granularity";
case ScheduleModel::ScheduleScheduled: return "Scheduled";
default: return QVariant();
}
} else if ( role == Qt::TextAlignmentRole ) {
return QVariant();
}
}
if ( role == Qt::ToolTipRole ) {
switch ( section ) {
case ScheduleModel::ScheduleName: return ToolTip::scheduleName();
case ScheduleModel::ScheduleState: return ToolTip::scheduleState();
case ScheduleModel::ScheduleDirection: return ToolTip::schedulingDirection();
case ScheduleModel::ScheduleOverbooking: return ToolTip::scheduleOverbooking();
case ScheduleModel::ScheduleDistribution: return ToolTip::scheduleDistribution();
// case ScheduleModel::ScheduleCalculate: return ToolTip::scheduleCalculate();
case ScheduleModel::SchedulePlannedStart: return ToolTip::scheduleStart();
case ScheduleModel::SchedulePlannedFinish: return ToolTip::scheduleFinish();
case ScheduleModel::ScheduleScheduler: return ToolTip::scheduleScheduler();
case ScheduleModel::ScheduleGranularity: return ToolTip::scheduleGranularity();
case ScheduleModel::ScheduleScheduled: return QVariant();
default: return QVariant();
}
} else if ( role == Qt::WhatsThisRole ) {
switch ( section ) {
case ScheduleModel::ScheduleDirection: return WhatsThis::schedulingDirection();
case ScheduleModel::ScheduleOverbooking: return WhatsThis::scheduleOverbooking();
case ScheduleModel::ScheduleDistribution: return WhatsThis::scheduleDistribution();
case ScheduleModel::ScheduleScheduler: return WhatsThis::scheduleScheduler();
default: return QVariant();
}
}
return ItemModelBase::headerData(section, orientation, role);
}
QAbstractItemDelegate *ScheduleItemModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
case ScheduleModel::ScheduleState: return new ProgressBarDelegate( parent );
case ScheduleModel::ScheduleDirection: return new EnumDelegate( parent );
case ScheduleModel::ScheduleOverbooking: return new EnumDelegate( parent );
case ScheduleModel::ScheduleDistribution: return new EnumDelegate( parent );
case ScheduleModel::ScheduleGranularity: return new EnumDelegate( parent );
case ScheduleModel::ScheduleScheduler: return new EnumDelegate( parent );
}
return 0;
}
void ScheduleItemModel::sort( int column, Qt::SortOrder order )
{
Q_UNUSED(column);
Q_UNUSED(order);
}
QMimeData * ScheduleItemModel::mimeData( const QModelIndexList & ) const
{
return 0;
}
QStringList ScheduleItemModel::mimeTypes () const
{
return QStringList();
}
ScheduleManager *ScheduleItemModel::manager( const QModelIndex &index ) const
{
ScheduleManager *o = 0;
if ( index.isValid() && m_project != 0 && index.internalPointer() != 0 && m_project->isScheduleManager( index.internalPointer() ) ) {
o = static_cast<ScheduleManager*>( index.internalPointer() );
Q_ASSERT( o );
}
return o;
}
void ScheduleItemModel::setFlat( bool flat )
{
m_flat = flat;
m_managerlist.clear();
if ( ! flat || m_project == 0 ) {
return;
}
m_managerlist = m_project->allScheduleManagers();
}
//--------------------------------------
ScheduleSortFilterModel::ScheduleSortFilterModel( QObject *parent )
: QSortFilterProxyModel( parent )
{
}
ScheduleSortFilterModel::~ScheduleSortFilterModel()
{
}
ScheduleManager *ScheduleSortFilterModel::manager( const QModelIndex &index ) const
{
QModelIndex i = mapToSource( index );
const ScheduleItemModel *m = qobject_cast<const ScheduleItemModel*>( i.model() );
return m == 0 ? 0 : m->manager( i );
}
//--------------------------------------
ScheduleLogItemModel::ScheduleLogItemModel( QObject *parent )
: QStandardItemModel( parent ),
m_project( 0 ),
m_manager( 0 ),
m_schedule( 0 )
{
}
ScheduleLogItemModel::~ScheduleLogItemModel()
{
}
void ScheduleLogItemModel::slotScheduleManagerToBeRemoved( const ScheduleManager *manager )
{
if ( m_manager == manager ) {
setManager( 0 );
}
}
void ScheduleLogItemModel::slotScheduleManagerRemoved( const ScheduleManager *manager )
{
debugPlan<<manager->name();
}
void ScheduleLogItemModel::slotScheduleToBeInserted( const ScheduleManager *manager, int row )
{
Q_UNUSED(manager);
Q_UNUSED(row);
if ( m_manager && m_manager->expected() /*== ??*/ ) {
//TODO
}
}
//FIXME remove const on MainSchedule
void ScheduleLogItemModel::slotScheduleInserted( const MainSchedule *sch )
{
debugPlan<<m_schedule<<sch;
if ( m_manager && m_manager == sch->manager() && sch == m_manager->expected() ) {
m_schedule = const_cast<MainSchedule*>( sch );
refresh();
}
}
void ScheduleLogItemModel::slotScheduleToBeRemoved( const MainSchedule *sch )
{
debugPlan<<m_schedule<<sch;
if ( m_schedule == sch ) {
m_schedule = 0;
clear();
}
}
void ScheduleLogItemModel::slotScheduleRemoved( const MainSchedule *sch )
{
debugPlan<<m_schedule<<sch;
}
void ScheduleLogItemModel::projectDeleted()
{
setProject(0);
}
void ScheduleLogItemModel::setProject( Project *project )
{
debugPlan<<m_project<<"->"<<project;
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &ScheduleLogItemModel::projectDeleted);
disconnect(m_project, &Project::scheduleManagerChanged, this, &ScheduleLogItemModel::slotManagerChanged);
disconnect(m_project, &Project::scheduleManagerToBeRemoved, this, &ScheduleLogItemModel::slotScheduleManagerToBeRemoved);
disconnect(m_project, &Project::scheduleManagerRemoved, this, &ScheduleLogItemModel::slotScheduleManagerRemoved);
disconnect(m_project, &Project::scheduleChanged, this, &ScheduleLogItemModel::slotScheduleChanged);
disconnect(m_project, &Project::scheduleToBeAdded, this, &ScheduleLogItemModel::slotScheduleToBeInserted);
disconnect(m_project, &Project::scheduleToBeRemoved, this, &ScheduleLogItemModel::slotScheduleToBeRemoved);
disconnect(m_project, &Project::scheduleAdded, this, &ScheduleLogItemModel::slotScheduleInserted);
disconnect(m_project, &Project::scheduleRemoved, this, &ScheduleLogItemModel::slotScheduleRemoved);
}
m_project = project;
if ( m_project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &ScheduleLogItemModel::projectDeleted);
connect(m_project, &Project::scheduleManagerChanged, this, &ScheduleLogItemModel::slotManagerChanged);
connect(m_project, &Project::scheduleManagerToBeRemoved, this, &ScheduleLogItemModel::slotScheduleManagerToBeRemoved);
connect(m_project, &Project::scheduleManagerRemoved, this, &ScheduleLogItemModel::slotScheduleManagerRemoved);
connect(m_project, &Project::scheduleChanged, this, &ScheduleLogItemModel::slotScheduleChanged);
connect(m_project, &Project::scheduleToBeAdded, this, &ScheduleLogItemModel::slotScheduleToBeInserted);
connect(m_project, &Project::scheduleToBeRemoved, this, &ScheduleLogItemModel::slotScheduleToBeRemoved);
connect(m_project, &Project::scheduleAdded, this, &ScheduleLogItemModel::slotScheduleInserted);
connect(m_project, &Project::scheduleRemoved, this, &ScheduleLogItemModel::slotScheduleRemoved);
}
}
void ScheduleLogItemModel::setManager( ScheduleManager *manager )
{
debugPlan<<m_manager<<"->"<<manager;
if ( manager != m_manager ) {
if ( m_manager ) {
disconnect(m_manager, &ScheduleManager::logInserted, this, &ScheduleLogItemModel::slotLogInserted);
}
m_manager = manager;
m_schedule = 0;
clear();
if ( m_manager ) {
m_schedule = m_manager->expected();
refresh();
connect(m_manager, &ScheduleManager::logInserted, this, &ScheduleLogItemModel::slotLogInserted);
}
}
}
void ScheduleLogItemModel::slotLogInserted( MainSchedule *s, int firstrow, int lastrow )
{
for ( int i = firstrow; i <= lastrow; ++i ) {
addLogEntry( s->logs().value( i ), i + 1 );
}
}
//FIXME: This only add logs (insert is not used atm)
void ScheduleLogItemModel::addLogEntry( const Schedule::Log &log, int /*row*/ )
{
// debugPlan<<log;
QList<QStandardItem*> lst;
if ( log.resource ) {
lst.append( new QStandardItem( log.resource->name() ) );
} else if ( log.node ) {
lst.append( new QStandardItem( log.node->name() ) );
} else {
lst.append( new QStandardItem( "" ) );
}
lst.append( new QStandardItem( m_schedule->logPhase( log.phase ) ) );
QStandardItem *item = new QStandardItem( m_schedule->logSeverity( log.severity ) );
item->setData( log.severity, SeverityRole );
lst.append( item );
lst.append( new QStandardItem( log.message ) );
foreach ( QStandardItem *itm, lst ) {
if ( log.resource ) {
itm->setData( log.resource->id(), IdentityRole );
} else if ( log.node ) {
itm->setData( log.node->id(), IdentityRole );
}
switch ( log.severity ) {
case Schedule::Log::Type_Debug:
itm->setData( QColor(Qt::darkYellow), Qt::ForegroundRole );
break;
case Schedule::Log::Type_Info:
break;
case Schedule::Log::Type_Warning:
itm->setData( QColor(Qt::blue), Qt::ForegroundRole );
break;
case Schedule::Log::Type_Error:
itm->setData( QColor(Qt::red), Qt::ForegroundRole );
break;
default:
break;
}
}
appendRow( lst );
// debugPlan<<"added:"<<row<<rowCount()<<columnCount();
}
void ScheduleLogItemModel::refresh()
{
clear();
QStringList lst;
lst << i18n( "Name" ) << i18n( "Phase" ) << i18n( "Severity" ) << i18n( "Message" );
setHorizontalHeaderLabels( lst );
if ( m_schedule == 0 ) {
debugPlan<<"No main schedule";
return;
}
// debugPlan<<m_schedule<<m_schedule->logs().count();
int i = 1;
foreach ( const Schedule::Log &l, m_schedule->logs() ) {
addLogEntry( l, i++ );
}
}
QString ScheduleLogItemModel::identity( const QModelIndex &idx ) const
{
QStandardItem *itm = itemFromIndex( idx );
return itm ? itm->data( IdentityRole ).toString() : QString();
}
void ScheduleLogItemModel::slotManagerChanged( ScheduleManager *manager )
{
debugPlan<<m_manager<<manager;
if ( m_manager == manager ) {
//TODO
// refresh();
}
}
void ScheduleLogItemModel::slotScheduleChanged( MainSchedule *sch )
{
debugPlan<<m_schedule<<sch;
if ( m_schedule == sch ) {
refresh();
}
}
Qt::ItemFlags ScheduleLogItemModel::flags( const QModelIndex &index ) const
{
Q_UNUSED(index);
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
} // namespace KPlato
diff --git a/src/libs/models/kpttaskcompletedelegate.cpp b/src/libs/models/kpttaskcompletedelegate.cpp
index 2284fb7a..ed6b65d1 100644
--- a/src/libs/models/kpttaskcompletedelegate.cpp
+++ b/src/libs/models/kpttaskcompletedelegate.cpp
@@ -1,67 +1,68 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <calligra-devel@kde.org>
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 "kpttaskcompletedelegate.h"
#include "kptnodeitemmodel.h"
#include "kptnode.h"
#include <QModelIndex>
#include <QApplication>
#include <QStyleOptionViewItem>
#include <QStyle>
#include <QPainter>
namespace KPlato
{
//-----------------------------
TaskCompleteDelegate::TaskCompleteDelegate( QObject *parent )
: ProgressBarDelegate( parent )
{
}
TaskCompleteDelegate::~TaskCompleteDelegate()
{
}
void TaskCompleteDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
QModelIndex typeidx = index.model()->index( index.row(), NodeModel::NodeType, index.parent() );
if ( ! typeidx.isValid() ) {
errorPlan<<"Cannot find nodetype for index:"<<index;
return;
}
int type = typeidx.data( Qt::EditRole ).toInt();
if ( type == Node::Type_Task || type == Node::Type_Milestone ) {
ProgressBarDelegate::paint( painter, option, index );
} else {
QStyle *style;
QStyleOptionViewItem opt = option;
//initStyleOption( &opt, index );
style = opt.widget ? opt.widget->style() : QApplication::style();
style->drawPrimitive( QStyle::PE_PanelItemViewItem, &opt, painter );
//debugPlan<<"Draw something else, type="<<type<<index.model()->index( index.row(), NodeModel::NodeName, index.parent() ).data().toString();
ProgressBarDelegate::paint( painter, option, index );
}
}
} //namespace KPlato
diff --git a/src/libs/models/kpttaskstatusmodel.cpp b/src/libs/models/kpttaskstatusmodel.cpp
index e6d286eb..1cae685d 100644
--- a/src/libs/models/kpttaskstatusmodel.cpp
+++ b/src/libs/models/kpttaskstatusmodel.cpp
@@ -1,791 +1,792 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kpttaskstatusmodel.h"
#include "PlanMacros.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptitemmodelbase.h"
#include "kpttaskcompletedelegate.h"
#include "kptcommand.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptnodeitemmodel.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QMimeData>
#include <QModelIndex>
namespace KPlato
{
TaskStatusItemModel::TaskStatusItemModel( QObject *parent )
: ItemModelBase( parent ),
m_period( 7 ),
m_periodType( UseCurrentDate ),
m_weekday( Qt::Friday )
{
m_topNames << i18n( "Not Started" );
m_topTips << i18n( "Tasks that should have been started" );
m_top.append(&m_notstarted );
m_topNames << i18n( "Running" );
m_topTips << i18n( "Tasks that are running" );
m_top.append(&m_running );
m_topNames << i18n( "Finished" );
m_topTips << i18n( "Tasks that have finished during this period" );
m_top.append(&m_finished );
m_topNames << i18n( "Next Period" );
m_topTips << i18n( "Tasks that are scheduled to start next period" );
m_top.append(&m_upcoming );
/* connect( this, SIGNAL(modelAboutToBeReset()), SLOT(slotAboutToBeReset()) );
connect( this, SIGNAL(modelReset()), SLOT(slotReset()) );*/
}
TaskStatusItemModel::~TaskStatusItemModel()
{
}
void TaskStatusItemModel::slotAboutToBeReset()
{
debugPlan;
clear();
}
void TaskStatusItemModel::slotReset()
{
debugPlan;
refresh();
}
void TaskStatusItemModel::slotNodeToBeInserted( Node *, int )
{
//debugPlan<<node->name();
clear();
}
void TaskStatusItemModel::slotNodeInserted( Node * /*node*/ )
{
//debugPlan<<node->getParent->name()<<"-->"<<node->name();
refresh();
}
void TaskStatusItemModel::slotNodeToBeRemoved( Node * /*node*/ )
{
//debugPlan<<node->name();
clear();
}
void TaskStatusItemModel::slotNodeRemoved( Node * /*node*/ )
{
//debugPlan<<node->name();
refresh();
}
void TaskStatusItemModel::slotNodeToBeMoved(Node *node, int pos, Node *newParent, int newPos)
{
Q_UNUSED( node );
Q_UNUSED( pos );
Q_UNUSED( newParent );
Q_UNUSED( newPos );
clear();
}
void TaskStatusItemModel::slotNodeMoved( Node * /*node*/ )
{
//debugPlan<<node->name();
refresh();
}
void TaskStatusItemModel::setProject( Project *project )
{
beginResetModel();
clear();
if ( m_project ) {
disconnect(m_project, &Project::aboutToBeDeleted, this, &TaskStatusItemModel::projectDeleted);
disconnect(m_project, &Project::localeChanged, this, &TaskStatusItemModel::slotLayoutChanged);
disconnect( m_project, &Project::wbsDefinitionChanged, this, &TaskStatusItemModel::slotWbsDefinitionChanged );
disconnect( m_project, &Project::nodeChanged, this, &TaskStatusItemModel::slotNodeChanged );
disconnect( m_project, &Project::nodeToBeAdded, this, &TaskStatusItemModel::slotNodeToBeInserted );
disconnect( m_project, &Project::nodeToBeRemoved, this, &TaskStatusItemModel::slotNodeToBeRemoved );
disconnect(m_project, &Project::nodeToBeMoved, this, &TaskStatusItemModel::slotNodeToBeMoved);
disconnect( m_project, &Project::nodeAdded, this, &TaskStatusItemModel::slotNodeInserted );
disconnect( m_project, &Project::nodeRemoved, this, &TaskStatusItemModel::slotNodeRemoved );
disconnect(m_project, &Project::nodeMoved, this, &TaskStatusItemModel::slotNodeMoved);
}
m_project = project;
m_nodemodel.setProject( project );
if ( project ) {
connect(m_project, &Project::aboutToBeDeleted, this, &TaskStatusItemModel::projectDeleted);
connect(m_project, &Project::localeChanged, this, &TaskStatusItemModel::slotLayoutChanged);
connect( m_project, &Project::wbsDefinitionChanged, this, &TaskStatusItemModel::slotWbsDefinitionChanged );
connect( m_project, &Project::nodeChanged, this, &TaskStatusItemModel::slotNodeChanged );
connect( m_project, &Project::nodeToBeAdded, this, &TaskStatusItemModel::slotNodeToBeInserted );
connect( m_project, &Project::nodeToBeRemoved, this, &TaskStatusItemModel::slotNodeToBeRemoved );
connect(m_project, &Project::nodeToBeMoved, this, &TaskStatusItemModel::slotNodeToBeMoved);
connect( m_project, &Project::nodeAdded, this, &TaskStatusItemModel::slotNodeInserted );
connect( m_project, &Project::nodeRemoved, this, &TaskStatusItemModel::slotNodeRemoved );
connect(m_project, &Project::nodeMoved, this, &TaskStatusItemModel::slotNodeMoved);
}
endResetModel();
}
void TaskStatusItemModel::setScheduleManager( ScheduleManager *sm )
{
beginResetModel();
if (sm == m_nodemodel.manager()) {
return;
}
clear();
if ( m_nodemodel.manager() ) {
}
m_nodemodel.setManager( sm );
ItemModelBase::setScheduleManager( sm );
if ( sm ) {
}
endResetModel();
refresh();
}
void TaskStatusItemModel::clear()
{
foreach ( NodeMap *l, m_top ) {
int c = l->count();
if ( c > 0 ) {
//FIXME: gives error msg:
// Can't select indexes from different model or with different parents
QModelIndex i = index( l );
debugPlan<<i<<0<<c-1;
beginRemoveRows( i, 0, c-1 );
l->clear();
endRemoveRows();
}
}
}
void TaskStatusItemModel::setNow()
{
switch ( m_periodType ) {
case UseWeekday: {
QDate date = QDate::currentDate();
int wd = date.dayOfWeek();
date = date.addDays( m_weekday - wd );
if ( wd < m_weekday ) {
date = date.addDays( -7 );
}
m_nodemodel.setNow( date );
break; }
case UseCurrentDate: m_nodemodel.setNow( QDate::currentDate() ); break;
default:
m_nodemodel.setNow( QDate::currentDate() );
break;
}
}
void TaskStatusItemModel::refresh()
{
clear();
if ( m_project == 0 ) {
return;
}
m_id = m_nodemodel.id();
if ( m_id == -1 ) {
return;
}
setNow();
const QDate begin = m_nodemodel.now().addDays( -m_period );
const QDate end = m_nodemodel.now().addDays( m_period );
foreach( Node* n, m_project->allNodes() ) {
if ( n->type() != Node::Type_Task && n->type() != Node::Type_Milestone ) {
continue;
}
Task *task = static_cast<Task*>( n );
const TaskStatus status = taskStatus(task, begin, end);
if (status != TaskUnknownStatus) {
m_top.at(status)->insert(task->wbsCode(), task);
}
}
foreach ( NodeMap *l, m_top ) {
int c = l->count();
if ( c > 0 ) {
debugPlan<<index(l)<<0<<c-1;
beginInsertRows( index( l ), 0, c-1 );
endInsertRows();
}
}
emit layoutChanged(); //HACK to get views updated
}
Qt::ItemFlags TaskStatusItemModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
flags &= ~( Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
Node *n = node( index );
if ( ! m_readWrite || n == 0 || m_id == -1 || ! n->isScheduled( m_id ) ) {
return flags;
}
if ( n->type() != Node::Type_Task && n->type() != Node::Type_Milestone ) {
return flags;
}
Task *t = static_cast<Task*>( n );
if ( ! t->completion().isStarted() ) {
switch ( index.column() ) {
case NodeModel::NodeActualStart:
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeCompleted:
if ( t->state() & Node::State_ReadyToStart ) {
flags |= Qt::ItemIsEditable;
}
break;
default: break;
}
} else if ( ! t->completion().isFinished() ) {
// task is running
switch ( index.column() ) {
case NodeModel::NodeActualFinish:
case NodeModel::NodeCompleted:
case NodeModel::NodeRemainingEffort:
flags |= Qt::ItemIsEditable;
break;
case NodeModel::NodeActualEffort:
if ( t->completion().entrymode() == Completion::EnterEffortPerTask || t->completion().entrymode() == Completion::EnterEffortPerResource ) {
flags |= Qt::ItemIsEditable;
}
break;
default: break;
}
}
return flags;
}
QModelIndex TaskStatusItemModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() ) {
return QModelIndex();
}
//debugPlan<<index.internalPointer()<<":"<<index.row()<<","<<index.column();
int row = m_top.indexOf( static_cast<NodeMap*>( index.internalPointer() ) );
if ( row != -1 ) {
return QModelIndex(); // top level has no parent
}
Node *n = node( index );
if ( n == 0 ) {
return QModelIndex();
}
NodeMap *lst = 0;
foreach ( NodeMap *l, m_top ) {
if (CONTAINS((*l), n)) {
lst = l;
break;
}
}
if ( lst == 0 ) {
return QModelIndex();
}
return createIndex( m_top.indexOf( lst ), 0, lst );
}
QModelIndex TaskStatusItemModel::index( int row, int column, const QModelIndex &parent ) const
{
//debugPlan<<row<<column<<parent;
if ( m_project == 0 || column < 0 || column >= columnCount() || row < 0 ) {
return QModelIndex();
}
if ( ! parent.isValid() ) {
if ( row >= m_top.count() ) {
return QModelIndex();
}
return createIndex(row, column, m_top.value( row ) );
}
NodeMap *l = list( parent );
if ( l == 0 ) {
return QModelIndex();
}
if ( row >= rowCount( parent ) ) {
warnPlan<<"Row >= rowCount, Qt4.4 asks, so we need to handle it"<<parent<<row<<column;
return QModelIndex();
}
const QList<Node*> &nodes = l->values();
QModelIndex i = createIndex(row, column, nodes.value(row));
Q_ASSERT( i.internalPointer() != 0 );
return i;
}
QModelIndex TaskStatusItemModel::index( const Node *node ) const
{
if ( m_project == 0 || node == 0 ) {
return QModelIndex();
}
foreach( NodeMap *l, m_top ) {
const QList<Node*> &nodes = l->values();
int row = nodes.indexOf( const_cast<Node*>( node ) );
if ( row != -1 ) {
return createIndex( row, 0, const_cast<Node*>( node ) );
}
}
return QModelIndex();
}
QModelIndex TaskStatusItemModel::index( const NodeMap *lst ) const
{
if ( m_project == 0 || lst == 0 ) {
return QModelIndex();
}
NodeMap *l = const_cast<NodeMap*>( lst );
int row = m_top.indexOf( l );
if ( row == -1 ) {
return QModelIndex();
}
return createIndex( row, 0, l );
}
QVariant TaskStatusItemModel::name( int row, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return m_topNames.value( row );
case Qt::ToolTipRole:
return m_topTips.value( row );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
bool TaskStatusItemModel::setCompletion( Node *node, const QVariant &value, int role )
{
if ( role != Qt::EditRole ) {
return false;
}
if ( node->type() == Node::Type_Task ) {
Completion &c = static_cast<Task*>( node )->completion();
QDateTime dt = QDateTime::currentDateTime();
QDate date = dt.date();
// xgettext: no-c-format
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify completion" ) );
if ( ! c.isStarted() ) {
m->addCommand( new ModifyCompletionStartedCmd( c, true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) );
}
m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, value.toInt() ) );
if ( value.toInt() == 100 ) {
m->addCommand( new ModifyCompletionFinishedCmd( c, true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) );
}
emit executeCommand( m ); // also adds a new entry if necessary
if ( c.entrymode() == Completion::EnterCompleted ) {
Duration planned = static_cast<Task*>( node )->plannedEffort( m_nodemodel.id() );
Duration actual = ( planned * value.toInt() ) / 100;
debugPlan<<planned.toString()<<value.toInt()<<actual.toString();
NamedCommand *cmd = new ModifyCompletionActualEffortCmd( c, date, actual );
cmd->execute();
m->addCommand( cmd );
cmd = new ModifyCompletionRemainingEffortCmd( c, date, planned - actual );
cmd->execute();
m->addCommand( cmd );
}
return true;
}
if ( node->type() == Node::Type_Milestone ) {
Completion &c = static_cast<Task*>( node )->completion();
if ( value.toInt() > 0 ) {
QDateTime dt = QDateTime::currentDateTime();
QDate date = dt.date();
MacroCommand *m = new MacroCommand( kundo2_i18n( "Set finished" ) );
m->addCommand( new ModifyCompletionStartedCmd( c, true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionFinishedCmd( c, true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, 100 ) );
emit executeCommand( m ); // also adds a new entry if necessary
return true;
}
return false;
}
return false;
}
bool TaskStatusItemModel::setRemainingEffort( Node *node, const QVariant &value, int role )
{
if ( role == Qt::EditRole && node->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( node );
double d( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration dur( d, unit );
emit executeCommand( new ModifyCompletionRemainingEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify remaining effort" ) ) );
return true;
}
return false;
}
bool TaskStatusItemModel::setActualEffort( Node *node, const QVariant &value, int role )
{
if ( role == Qt::EditRole && node->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( node );
double d( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration dur( d, unit );
emit executeCommand( new ModifyCompletionActualEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify actual effort" ) ) );
return true;
}
return false;
}
bool TaskStatusItemModel::setStartedTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Task *t = qobject_cast<Task*>( node );
if ( t == 0 ) {
return false;
}
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify actual start time" ) );
if ( ! t->completion().isStarted() ) {
m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) );
}
m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->type() == Node::Type_Milestone ) {
m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->completion().percentFinished() < 100 ) {
Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration );
m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) );
}
}
emit executeCommand( m );
return true;
}
}
return false;
}
bool TaskStatusItemModel::setFinishedTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Task *t = qobject_cast<Task*>( node );
if ( t == 0 ) {
return false;
}
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify actual finish time" ) );
if ( ! t->completion().isFinished() ) {
m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) );
if ( t->completion().percentFinished() < 100 ) {
Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration );
m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) );
}
}
m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->type() == Node::Type_Milestone ) {
m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) );
}
emit executeCommand( m );
return true;
}
}
return false;
}
QVariant TaskStatusItemModel::data( const QModelIndex &index, int role ) const
{
QVariant result;
if ( ! index.isValid() ) {
return result;
}
if ( role == Qt::TextAlignmentRole ) {
return alignment( index.column() );
}
Node *n = node( index );
if ( n == 0 ) {
switch ( index.column() ) {
case NodeModel::NodeName: return name( index.row(), role );
default: break;
}
return QVariant();
}
result = m_nodemodel.data( n, index.column(), role );
if ( role == Qt::DisplayRole ) {
switch ( index.column() ) {
case NodeModel::NodeActualStart:
if ( ! result.isValid() ) {
return m_nodemodel.data( n, NodeModel::NodeStatus, role );
}
break;
}
} else if ( role == Qt::EditRole ) {
switch ( index.column() ) {
case NodeModel::NodeActualStart:
case NodeModel::NodeActualFinish:
if ( ! result.isValid() ) {
return QDateTime::currentDateTime();
}
break;
}
}
return result;
}
bool TaskStatusItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
switch ( index.column() ) {
case NodeModel::NodeCompleted:
return setCompletion( node( index ), value, role );
case NodeModel::NodeRemainingEffort:
return setRemainingEffort( node( index ), value, role );
case NodeModel::NodeActualEffort:
return setActualEffort( node( index ), value, role );
case NodeModel::NodeActualStart:
return setStartedTime( node( index ), value, role );
case NodeModel::NodeActualFinish:
return setFinishedTime( node( index ), value, role );
default:
break;
}
return false;
}
QVariant TaskStatusItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal ) {
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return m_nodemodel.headerData( section, role );
} else if ( role == Qt::TextAlignmentRole ) {
return alignment( section );
}
}
if ( role == Qt::ToolTipRole ) {
return m_nodemodel.headerData( section, role );
}
return ItemModelBase::headerData(section, orientation, role);
}
QVariant TaskStatusItemModel::alignment( int column ) const
{
return m_nodemodel.headerData( column, Qt::TextAlignmentRole );
}
QAbstractItemDelegate *TaskStatusItemModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
case NodeModel::NodeCompleted: return new TaskCompleteDelegate( parent );
case NodeModel::NodeRemainingEffort: return new DurationSpinBoxDelegate( parent );
case NodeModel::NodeActualEffort: return new DurationSpinBoxDelegate( parent );
default: return 0;
}
return 0;
}
int TaskStatusItemModel::columnCount( const QModelIndex & ) const
{
return m_nodemodel.propertyCount();
}
int TaskStatusItemModel::rowCount( const QModelIndex &parent ) const
{
if ( ! parent.isValid() ) {
//debugPlan<<"top="<<m_top.count()<<m_top;
return m_top.count();
}
NodeMap *l = list( parent );
if ( l ) {
//debugPlan<<"list"<<parent.row()<<":"<<l->count()<<l<<m_topNames.value( parent.row() );
return l->count();
}
//debugPlan<<"node"<<parent.row();
return 0; // nodes don't have children
}
Qt::DropActions TaskStatusItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList TaskStatusItemModel::mimeTypes() const
{
return QStringList();
}
QMimeData *TaskStatusItemModel::mimeData( const QModelIndexList & indexes ) const
{
QMimeData *m = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QList<int> rows;
foreach (const QModelIndex &index, indexes) {
if ( index.isValid() && !rows.contains( index.row() ) ) {
//debugPlan<<index.row();
Node *n = node( index );
if ( n ) {
rows << index.row();
stream << n->id();
}
}
}
m->setData("application/x-vnd.kde.plan.nodeitemmodel.internal", encodedData);
return m;
}
bool TaskStatusItemModel::dropAllowed( Node *, const QMimeData * )
{
return false;
}
bool TaskStatusItemModel::dropMimeData( const QMimeData *, Qt::DropAction , int , int , const QModelIndex & )
{
return false;
}
NodeMap *TaskStatusItemModel::list( const QModelIndex &index ) const
{
if ( index.isValid() ) {
Q_ASSERT( index.internalPointer() );
if ( m_top.contains( static_cast<NodeMap*>( index.internalPointer() ) ) ) {
return static_cast<NodeMap*>( index.internalPointer() );
}
}
return 0;
}
Node *TaskStatusItemModel::node( const QModelIndex &index ) const
{
if ( index.isValid() ) {
foreach ( NodeMap *l, m_top ) {
const QList<Node*> &nodes = l->values();
int row = nodes.indexOf( static_cast<Node*>( index.internalPointer() ) );
if ( row != -1 ) {
return static_cast<Node*>( index.internalPointer() );
}
}
}
return 0;
}
TaskStatusItemModel::TaskStatus TaskStatusItemModel::taskStatus(const Task *task,
const QDate &begin, const QDate &end)
{
TaskStatus result = TaskUnknownStatus;
const Completion &completion = task->completion();
if (completion.isFinished()) {
if (completion.finishTime().date() > begin) {
result = TaskFinished;
}
} else if (completion.isStarted()) {
result = TaskRunning;
} else if (task->startTime(m_id).date() < m_nodemodel.now()) {
// should have been started
result = TaskNotStarted;
} else if (task->startTime(m_id).date() <= end) {
// start next period
result = TaskUpcoming;
}
return result;
}
void TaskStatusItemModel::slotNodeChanged( Node *node )
{
debugPlan;
if (node == 0 || node->type() == Node::Type_Project ||
(node->type() != Node::Type_Task && node->type() != Node::Type_Milestone)) {
return;
}
Task *task = static_cast<Task*>(node);
const QDate begin = m_nodemodel.now().addDays( -m_period );
const QDate end = m_nodemodel.now().addDays( m_period );
const TaskStatus status = taskStatus(task, begin, end);
int row = -1;
if (status != TaskUnknownStatus) {
// find the row of the task
const QString wbs = node->wbsCode();
// TODO: not enough to just check the result of indexOf? wbs not unique?
if (m_top.at(status)->value(wbs) == node ) {
row = m_top.at(status)->keys().indexOf(wbs);
}
}
if (row >= 0) {
// task in old group, just changed values
emit dataChanged(createIndex(row, 0, node), createIndex(row, columnCount() - 1, node));
} else {
// task is new or changed groups
refresh();
}
}
void TaskStatusItemModel::slotWbsDefinitionChanged()
{
debugPlan;
foreach ( NodeMap *l, m_top ) {
for ( int row = 0; row < l->count(); ++row ) {
const QList<Node*> &nodes = l->values();
emit dataChanged( createIndex( row, NodeModel::NodeWBSCode, nodes.value( row ) ), createIndex( row, NodeModel::NodeWBSCode, nodes.value( row ) ) );
}
}
}
int TaskStatusItemModel::sortRole( int column ) const
{
switch ( column ) {
case NodeModel::NodeStartTime:
case NodeModel::NodeEndTime:
case NodeModel::NodeActualStart:
case NodeModel::NodeActualFinish:
case NodeModel::NodeEarlyStart:
case NodeModel::NodeEarlyFinish:
case NodeModel::NodeLateStart:
case NodeModel::NodeLateFinish:
case NodeModel::NodeConstraintStart:
case NodeModel::NodeConstraintEnd:
return Qt::EditRole;
default:
break;
}
return Qt::DisplayRole;
}
} // namespace KPlato
diff --git a/src/libs/models/kpttreecombobox.cpp b/src/libs/models/kpttreecombobox.cpp
index ade4d3d8..309b4f9e 100644
--- a/src/libs/models/kpttreecombobox.cpp
+++ b/src/libs/models/kpttreecombobox.cpp
@@ -1,155 +1,156 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "kpttreecombobox.h"
#include <KLocalizedString>
#include <QModelIndex>
#include <QTreeView>
#include <QHeaderView>
#include <QStylePainter>
namespace KPlato
{
//----------------------
TreeComboBox::TreeComboBox( QWidget *parent )
: KComboBox( parent ),
m_selectionmode( QAbstractItemView::ExtendedSelection )
{
m_showcolumns << 0;
m_showheader = false;
updateView();
connect( this, SIGNAL(activated(int)), SLOT(slotSelectionChanged()) );
}
void TreeComboBox::updateView()
{
QTreeView *v = new QTreeView();
setView( v );
v->setSelectionMode( m_selectionmode );
// don't want to have mouseover select an item
v->disconnect(SIGNAL(entered(QModelIndex)));
QHeaderView *h = v->header();
for ( int i = 0; i < h->count(); ++i ) {
h->setSectionHidden( i, ! m_showcolumns.contains( i ) );
}
h->setVisible( m_showheader );
v->setRootIsDecorated( false );
}
QTreeView *TreeComboBox::view() const
{
return static_cast<QTreeView*>( KComboBox::view() );
}
void TreeComboBox::setModel( QAbstractItemModel *model )
{
KComboBox::setModel( model );
updateView();
}
QAbstractItemModel *TreeComboBox::model() const
{
return KComboBox::model();
}
void TreeComboBox::setSelectionMode( QAbstractItemView::SelectionMode mode )
{
m_selectionmode = mode;
view()->setSelectionMode( mode );
}
void TreeComboBox::slotSelectionChanged()
{
updateCurrentIndexes( view()->selectionModel()->selectedRows() );
}
void TreeComboBox::showPopup()
{
QComboBox::showPopup();
// now clean up things we want different
QItemSelectionModel *sm = view()->selectionModel();
sm->clearSelection();
view()->setSelectionMode( m_selectionmode );
view()->setSelectionBehavior( QAbstractItemView::SelectRows );
foreach ( const QModelIndex &i, m_currentIndexes ) {
if ( i.isValid() ) {
sm->select( i, QItemSelectionModel::Select | QItemSelectionModel::Rows );
}
}
if ( ! sm->selectedRows().contains( sm->currentIndex() ) ) {
sm->setCurrentIndex( sm->selectedRows().value( 0 ), QItemSelectionModel::NoUpdate );
}
}
void TreeComboBox::paintEvent( QPaintEvent *event )
{
Q_UNUSED(event);
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStringList lst;
foreach ( const QPersistentModelIndex &idx, m_currentIndexes ) {
if ( idx.isValid() ) {
lst << idx.data().toString();
}
}
opt.currentText = lst.isEmpty() ? i18n( "None" ) : lst.join( "," );
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
// draw the icon and text
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
void TreeComboBox::setCurrentIndexes( const QModelIndexList &lst )
{
m_currentIndexes.clear();
foreach ( const QModelIndex &idx, lst ) {
m_currentIndexes << QPersistentModelIndex( idx );
}
}
void TreeComboBox::setCurrentIndexes( const QList<QPersistentModelIndex> &lst )
{
m_currentIndexes = lst;
}
void TreeComboBox::updateCurrentIndexes( const QModelIndexList &lst )
{
QList<QPersistentModelIndex> x;
foreach ( const QModelIndex &idx, lst ) {
x << QPersistentModelIndex( idx );
}
if ( x == m_currentIndexes ) {
return;
}
m_currentIndexes = x;
emit changed();
}
} //namespace KPlato
diff --git a/src/libs/models/kptworkpackagemodel.cpp b/src/libs/models/kptworkpackagemodel.cpp
index 9aed4af6..404b2954 100644
--- a/src/libs/models/kptworkpackagemodel.cpp
+++ b/src/libs/models/kptworkpackagemodel.cpp
@@ -1,558 +1,559 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptworkpackagemodel.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptflatproxymodel.h"
#include "kptnodeitemmodel.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QModelIndex>
#include <QVariant>
namespace KPlato
{
QVariant WorkPackageModel::nodeName( const WorkPackage *wp, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return wp->parentTask() ? wp->parentTask()->name() : "";
case Qt::EditRole:
return wp->parentTask() ? wp->parentTask()->name() : "";
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant WorkPackageModel::ownerName( const WorkPackage *wp, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
return wp->ownerName();
case Qt::EditRole:
return wp->ownerName();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant WorkPackageModel::transmitionStatus( const WorkPackage *wp, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
return wp->transmitionStatusToString( wp->transmitionStatus(), true );
case Qt::EditRole:
return wp->transmitionStatus();
case Qt::ToolTipRole: {
int sts = wp->transmitionStatus();
if ( sts == WorkPackage::TS_Send ) {
return i18n( "Sent to %1 at %2", wp->ownerName(), transmitionTime( wp, Qt::DisplayRole ).toString() );
}
if ( sts == WorkPackage::TS_Receive ) {
return i18n( "Received from %1 at %2", wp->ownerName(), transmitionTime( wp, Qt::DisplayRole ).toString() );
}
return i18n( "Not available" );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant WorkPackageModel::transmitionTime( const WorkPackage *wp, int role ) const
{
if ( ! wp ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
return QLocale().toString( wp->transmitionTime(), QLocale::ShortFormat );
case Qt::EditRole:
return wp->transmitionTime();
case Qt::ToolTipRole: {
int sts = wp->transmitionStatus();
QString t = QLocale().toString( wp->transmitionTime(), QLocale::LongFormat );
if ( sts == WorkPackage::TS_Send ) {
return i18n( "Work package sent at: %1", t );
}
if ( sts == WorkPackage::TS_Receive ) {
return i18n( "Work package transmission received at: %1", t );
}
return i18n( "Not available" );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant WorkPackageModel::completion( const WorkPackage *wp, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
if ( wp->transmitionStatus() == WorkPackage::TS_Receive ) {
return wp->completion().percentFinished();
}
break;
case Qt::EditRole:
if ( wp->transmitionStatus() == WorkPackage::TS_Receive ) {
return wp->completion().percentFinished();
}
break;
case Qt::ToolTipRole:
if ( wp->transmitionStatus() == WorkPackage::TS_Receive ) {
return i18n( "Task reported %1% completed", wp->completion().percentFinished() );
}
break;
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant WorkPackageModel::data( const WorkPackage *wp, int column, int role ) const
{
switch ( column ) {
case NodeModel::WPOwnerName:
case NodeModel::NodeName: return ownerName( wp, role );
case NodeModel::WPTransmitionStatus:
case NodeModel::NodeStatus: return transmitionStatus( wp, role );
case NodeModel::NodeCompleted: return completion( wp, role );
case NodeModel::WPTransmitionTime:
case NodeModel::NodeActualStart: return transmitionTime( wp, role );
default: break;
}
return QVariant();
}
//-----------------------------
bool WPSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if ( sourceModel()->index( source_row, NodeModel::NodeType, source_parent ).data( Qt::EditRole ).toInt() != Node::Type_Task ) {
return false;
}
if ( sourceModel()->index( source_row, NodeModel::NodeStatus, source_parent ).data( Qt::EditRole ).toInt() & Node::State_NotScheduled ) {
return false;
}
return true;
}
WorkPackageProxyModel::WorkPackageProxyModel( QObject *parent )
: QAbstractProxyModel( parent )
{
m_proxies << new WPSortFilterProxyModel( this );
m_proxies << new FlatProxyModel( this );
m_nodemodel = new NodeItemModel( this );
QAbstractProxyModel *p = this;
foreach ( QAbstractProxyModel *m, m_proxies ) {
p->setSourceModel( m );
p = m;
}
p->setSourceModel( m_nodemodel );
}
Qt::ItemFlags WorkPackageProxyModel::flags(const QModelIndex &index) const
{
if ( isWorkPackageIndex( index ) ) {
return Qt::ItemIsEnabled;
}
return QAbstractProxyModel::flags( index );
}
void WorkPackageProxyModel::setSourceModel( QAbstractItemModel *model )
{
if ( sourceModel() ) {
disconnect(sourceModel(), &QAbstractItemModel::dataChanged,
this, &WorkPackageProxyModel::sourceDataChanged);
/* disconnect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
this, SLOT(sourceHeaderDataChanged(Qt::Orientation,int,int)));*/
disconnect(sourceModel(), &QAbstractItemModel::layoutChanged,
this, &QAbstractItemModel::layoutChanged);
disconnect(sourceModel(), &QAbstractItemModel::layoutAboutToBeChanged,
this, &QAbstractItemModel::layoutAboutToBeChanged);
disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted,
this, &WorkPackageProxyModel::sourceRowsAboutToBeInserted);
disconnect(sourceModel(), &QAbstractItemModel::rowsInserted,
this, &WorkPackageProxyModel::sourceRowsInserted);
disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved,
this, &WorkPackageProxyModel::sourceRowsAboutToBeRemoved);
disconnect(sourceModel(), &QAbstractItemModel::rowsRemoved,
this, &WorkPackageProxyModel::sourceRowsAboutToBeRemoved);
/*
disconnect(sourceModel(), SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(sourceColumnsAboutToBeInserted(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(sourceColumnsInserted(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(sourceColumnsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(sourceColumnsRemoved(QModelIndex,int,int)));
*/
disconnect(sourceModel(), &QAbstractItemModel::modelAboutToBeReset, this, &WorkPackageProxyModel::sourceModelAboutToBeReset);
disconnect(sourceModel(), &QAbstractItemModel::modelReset, this, &WorkPackageProxyModel::sourceModelReset);
disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeMoved,
this, &WorkPackageProxyModel::sourceRowsAboutToBeMoved);
disconnect(sourceModel(), &QAbstractItemModel::rowsMoved,
this, &WorkPackageProxyModel::sourceRowsMoved);
/*
disconnect(sourceModel(), SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
this, SLOT(sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
disconnect(sourceModel(), SIGNAL(columnsMoved(QModelIndex&parent,int,int,QModelIndex,int)),
this, SLOT(sourceColumnsMoved(QModelIndex&parent,int,int,QModelIndex,int)));*/
}
QAbstractProxyModel::setSourceModel( model );
if ( model ) {
connect(model, &QAbstractItemModel::dataChanged,
this, &WorkPackageProxyModel::sourceDataChanged);
/* connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
this, SLOT(sourceHeaderDataChanged(Qt::Orientation,int,int)));*/
connect(model, &QAbstractItemModel::layoutChanged,
this, &QAbstractItemModel::layoutChanged);
connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
this, &QAbstractItemModel::layoutAboutToBeChanged);
connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
this, &WorkPackageProxyModel::sourceRowsAboutToBeInserted);
connect(model, &QAbstractItemModel::rowsInserted,
this, &WorkPackageProxyModel::sourceRowsInserted);
connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
this, &WorkPackageProxyModel::sourceRowsAboutToBeRemoved);
connect(model, &QAbstractItemModel::rowsRemoved,
this, &WorkPackageProxyModel::sourceRowsRemoved);
/*
connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(sourceColumnsAboutToBeInserted(QModelIndex,int,int)));
connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(sourceColumnsInserted(QModelIndex,int,int)));
connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(sourceColumnsAboutToBeRemoved(QModelIndex,int,int)));
connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(sourceColumnsRemoved(QModelIndex,int,int)));
*/
connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &WorkPackageProxyModel::sourceModelAboutToBeReset);
connect(model, &QAbstractItemModel::modelReset, this, &WorkPackageProxyModel::sourceModelReset);
connect(model, &QAbstractItemModel::rowsAboutToBeMoved,
this, &WorkPackageProxyModel::sourceRowsAboutToBeMoved);
connect(model, &QAbstractItemModel::rowsMoved,
this, &WorkPackageProxyModel::sourceRowsMoved);
/*
connect(model, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
this, SLOT(sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
connect(model, SIGNAL(columnsMoved(QModelIndex&parent,int,int,QModelIndex,int)),
this, SLOT(sourceColumnsMoved(QModelIndex&parent,int,int,QModelIndex,int)));*/
}
}
void WorkPackageProxyModel::sourceDataChanged(const QModelIndex &start, const QModelIndex &end)
{
emit dataChanged( mapFromSource( start ), mapFromSource( end ) );
}
void WorkPackageProxyModel::sourceModelAboutToBeReset()
{
// debugPlan;
beginResetModel();
detachTasks();
}
void WorkPackageProxyModel::sourceModelReset()
{
// debugPlan;
attachTasks();
for ( int r = 0; r < rowCount(); ++r ) {
debugPlan<<index( r, 0 ).data();
}
endResetModel();
}
void WorkPackageProxyModel::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end )
{
debugPlan<<parent<<start<<end;
Q_ASSERT( ! parent.isValid() );
beginInsertRows( QModelIndex(), start, end );
}
void WorkPackageProxyModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
{
debugPlan<<parent<<start<<end<<":"<<rowCount();
Q_ASSERT( ! parent.isValid() );
for ( int r = start; r <= end; ++r ) {
QModelIndex i = index( r, 0 );
Task *task = taskFromIndex( i );
if ( task ) {
attachTasks( task );
}
}
endInsertRows();
}
void WorkPackageProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end )
{
debugPlan<<parent<<start<<end;
Q_ASSERT( ! parent.isValid() );
beginInsertRows( QModelIndex(), start, end );
}
void WorkPackageProxyModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
{
debugPlan<<parent<<start<<end;
Q_ASSERT( ! parent.isValid() );
for ( int r = start; r <= end; ++r ) {
QModelIndex i = index( r, 0 );
Task *task = taskFromIndex( i );
if ( task ) {
detachTasks( task );
}
}
endInsertRows();
}
void WorkPackageProxyModel::sourceRowsAboutToBeMoved(const QModelIndex&, int sourceStart, int sourceEnd, const QModelIndex&, int destStart )
{
beginMoveRows( QModelIndex(), sourceStart, sourceEnd, QModelIndex(), destStart );
}
void WorkPackageProxyModel::sourceRowsMoved(const QModelIndex &, int , int , const QModelIndex &, int )
{
endMoveRows();
}
bool WorkPackageProxyModel::hasChildren(const QModelIndex &parent) const
{
return rowCount(parent) > 0;
}
int WorkPackageProxyModel::rowCount( const QModelIndex &parent ) const
{
int rows = 0;
if ( ! parent.isValid() ) {
rows = sourceModel()->rowCount();
} else if ( isTaskIndex( parent ) ) {
Task *task = taskFromIndex( parent );
rows = task ? task->workPackageLogCount() : 0;
}
// debugPlan<<rows;
for ( int r = 0; r < rows; ++r ) {
// debugPlan<<r<<index( r, 0 ).data();
}
return rows;
}
int WorkPackageProxyModel::columnCount(const QModelIndex &/*parent*/) const
{
return sourceModel()->columnCount();
}
QModelIndex WorkPackageProxyModel::mapToSource( const QModelIndex &proxyIndex ) const
{
if ( ! proxyIndex.isValid() ) {
return QModelIndex();
}
if ( isWorkPackageIndex( proxyIndex ) ) {
// workpackage, not mapped to source model
return QModelIndex();
}
return sourceModel()->index( proxyIndex.row(), proxyIndex.column() );
}
QModelIndex WorkPackageProxyModel::mapFromSource( const QModelIndex &sourceIndex ) const
{
// index from source model is always a node
return createIndex( sourceIndex.row(), sourceIndex.column() );
}
QModelIndex WorkPackageProxyModel::parent( const QModelIndex &child ) const
{
QModelIndex idx;
if ( isWorkPackageIndex( child ) ) {
// only work packages have parent
idx = m_nodemodel->index( static_cast<Node*>( child.internalPointer() ) );
idx = mapFromBaseModel( idx );
}
// debugPlan<<child<<idx;
return idx;
}
QModelIndex WorkPackageProxyModel::index(int row, int column, const QModelIndex &parent) const
{
QModelIndex idx;
if ( ! parent.isValid() ) {
// this should be a node
idx = createIndex( row, column );
} else if ( isTaskIndex( parent ) ) {
// Should be a work package, parent should be a task
Task *task = taskFromIndex( parent );
if ( task ) {
idx = createIndex( row, column, task );
}
}
/* if ( ! idx.isValid() ) {
debugPlan<<"not valid:"<<parent<<row<<column<<idx;
} else {
debugPlan<<parent<<row<<column<<idx;
}*/
return idx;
}
QVariant WorkPackageProxyModel::data( const QModelIndex &idx, int role ) const
{
QVariant value;
if ( isTaskIndex( idx ) ) {
value = mapToSource( idx ).data( role );
} else if ( isWorkPackageIndex( idx ) ) {
Task *task = taskFromIndex( idx );
if ( task ) {
value = m_model.data( task->workPackageAt( idx.row() ), idx.column(), role );
}
}
// debugPlan<<idx<<value;
return value;
}
Task *WorkPackageProxyModel::taskFromIndex( const QModelIndex &idx ) const
{
Task *task = 0;
if ( idx.internalPointer() ) {
task = static_cast<Task*>( idx.internalPointer() );
} else if ( idx.isValid() ) {
QVariant obj = data( idx, Role::Object );
task = qobject_cast<Task*>( obj.value<QObject*>() );
}
// debugPlan<<idx<<task;
return task;
}
QModelIndex WorkPackageProxyModel::indexFromTask(const Node *node) const
{
return mapFromBaseModel( m_nodemodel->index( node ) );
}
QModelIndex WorkPackageProxyModel::mapFromBaseModel( const QModelIndex &idx ) const
{
if ( ! idx.isValid() ) {
return QModelIndex();
}
QModelIndex in = idx;
for ( int i = m_proxies.count() -1; i >= 0; --i ) {
in = m_proxies.at( i )->mapFromSource( in );
}
return mapFromSource( in );
}
void WorkPackageProxyModel::setProject( Project *project )
{
debugPlan<<project;
m_nodemodel->setProject( project );
}
void WorkPackageProxyModel::setScheduleManager(ScheduleManager *sm)
{
debugPlan<<sm;
m_nodemodel->setScheduleManager( sm );
}
NodeItemModel *WorkPackageProxyModel::baseModel() const
{
return m_nodemodel;
}
void WorkPackageProxyModel::detachTasks( Task *task )
{
if ( task ) {
disconnect(task, &Task::workPackageToBeAdded, this, &WorkPackageProxyModel::workPackageToBeAdded);
disconnect(task, &Task::workPackageAdded, this, &WorkPackageProxyModel::workPackageAdded);
disconnect(task, &Task::workPackageToBeRemoved, this, &WorkPackageProxyModel::workPackageToBeRemoved);
disconnect(task, &Task::workPackageRemoved, this, &WorkPackageProxyModel::workPackageRemoved);
// debugPlan<<task;
} else {
for ( int r = 0; r < rowCount(); ++r ) {
Task *t = taskFromIndex( index( r, 0 ) );
if ( t ) {
detachTasks( t );
}
}
}
}
void WorkPackageProxyModel::attachTasks( Task *task )
{
if ( task ) {
connect(task, &Task::workPackageToBeAdded, this, &WorkPackageProxyModel::workPackageToBeAdded);
connect(task, &Task::workPackageAdded, this, &WorkPackageProxyModel::workPackageAdded);
connect(task, &Task::workPackageToBeRemoved, this, &WorkPackageProxyModel::workPackageToBeRemoved);
connect(task, &Task::workPackageRemoved, this, &WorkPackageProxyModel::workPackageRemoved);
// debugPlan<<task;
} else {
for ( int r = 0; r < rowCount(); ++r ) {
Task *t = taskFromIndex( index( r, 0 ) );
if ( t ) {
attachTasks( t );
}
}
}
}
void WorkPackageProxyModel::workPackageToBeAdded(Node *node, int row )
{
QModelIndex idx = indexFromTask( node );
debugPlan<<node<<row<<idx;
beginInsertRows( idx, row, row );
}
void WorkPackageProxyModel::workPackageAdded(Node *)
{
endInsertRows();
}
void WorkPackageProxyModel::workPackageToBeRemoved(Node *node, int row)
{
QModelIndex idx = indexFromTask( node );
beginRemoveRows( idx, row, row );
}
void WorkPackageProxyModel::workPackageRemoved(Node *)
{
endRemoveRows();
}
} //namespace KPlato
diff --git a/src/libs/models/reportgenerator/ReportGenerator.cpp b/src/libs/models/reportgenerator/ReportGenerator.cpp
index e721c30a..8e87a482 100644
--- a/src/libs/models/reportgenerator/ReportGenerator.cpp
+++ b/src/libs/models/reportgenerator/ReportGenerator.cpp
@@ -1,107 +1,108 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "planmodels_export.h"
#include "ReportGenerator.h"
#include "ReportGeneratorOdt.h"
#include <KLocalizedString>
namespace KPlato
{
ReportGenerator::ReportGenerator()
: m_project(0)
, m_manager(0)
, m_reportGenerator(0)
{
}
ReportGenerator::~ReportGenerator()
{
close();
}
void ReportGenerator::setReportType(const QString &type)
{
m_reportType = type;
}
void ReportGenerator::setTemplateFile(const QString &file)
{
m_templateFile = file;
}
void ReportGenerator::setReportFile(const QString &file)
{
m_reportFile = file;
}
void ReportGenerator::setProject(Project *project)
{
m_project = project;
}
void ReportGenerator::setScheduleManager(ScheduleManager *manager)
{
m_manager = manager;
}
bool ReportGenerator::open()
{
m_lastError.clear();
if (m_reportType == "odt") {
m_reportGenerator = new ReportGeneratorOdt();
m_reportGenerator->setTemplateFile(m_templateFile);
m_reportGenerator->setReportFile(m_reportFile);
m_reportGenerator->setProject(m_project);
m_reportGenerator->setScheduleManager(m_manager);
return m_reportGenerator->open();
}
m_lastError = i18n("Unknown report type: %1", m_reportType);
return false;
}
void ReportGenerator::close()
{
delete m_reportGenerator;
m_reportGenerator = 0;
}
bool ReportGenerator::createReport()
{
if (!m_reportGenerator) {
m_lastError = i18n("The report generator has not been opened successfully.");
return false;
}
return m_reportGenerator->createReport();
}
QString ReportGenerator::lastError() const
{
if (m_reportGenerator) {
return m_reportGenerator->lastError();
}
return m_lastError;
}
} //namespace KPlato
diff --git a/src/libs/models/reportgenerator/ReportGeneratorOdt.cpp b/src/libs/models/reportgenerator/ReportGeneratorOdt.cpp
index 127cade6..81f077a0 100644
--- a/src/libs/models/reportgenerator/ReportGeneratorOdt.cpp
+++ b/src/libs/models/reportgenerator/ReportGeneratorOdt.cpp
@@ -1,1338 +1,1339 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "planmodels_export.h"
#include "ReportGeneratorOdt.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptnodeitemmodel.h"
#include "kpttaskstatusmodel.h"
#include "kptitemmodelbase.h"
#include "kptnodechartmodel.h"
#include "kptschedulemodel.h"
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoOdf.h>
#include <KoOdfWriteStore.h>
#include <KoOdfReadStore.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoXmlNS.h>
#include <KoIcon.h>
#include <KLocalizedString>
#include <QIODevice>
#include <QMimeData>
#include <QMimeDatabase>
#include <QModelIndex>
#include <QByteArray>
#include <qdom.h>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QVariantList>
#include <QPair>
#define HeaderRole Qt::UserRole + 543
const QLoggingCategory &PLANRG_LOG()
{
static const QLoggingCategory category("calligra.plan.reportodt");
return category;
}
#define dbgRG qCDebug(PLANRG_LOG)<<Q_FUNC_INFO
const QLoggingCategory &PLANRG_TMP_LOG()
{
static const QLoggingCategory category("calligra.plan.reportodt.template");
return category;
}
#define dbgRGTmp qCDebug(PLANRG_TMP_LOG)<<Q_FUNC_INFO
const QLoggingCategory &PLANRG_TABLE_LOG()
{
static const QLoggingCategory category("calligra.plan.reportodt.table");
return category;
}
#define dbgRGTable qCDebug(PLANRG_TABLE_LOG)<<Q_FUNC_INFO
const QLoggingCategory &PLANRG_CHART_LOG()
{
static const QLoggingCategory category("calligra.plan.reportodt.chart");
return category;
}
#define dbgRGChart qCDebug(PLANRG_CHART_LOG)<<Q_FUNC_INFO
const QLoggingCategory &PLANRG_VARIABLE_LOG()
{
static const QLoggingCategory category("calligra.plan.reportodt.variable");
return category;
}
#define dbgRGVariable qCDebug(PLANRG_VARIABLE_LOG)<<Q_FUNC_INFO
const QLoggingCategory &PLANRG_TR_LOG()
{
static const QLoggingCategory category("calligra.plan.reportodt.tr");
return category;
}
#define dbgRGTr qCDebug(PLANRG_TR_LOG)<<Q_FUNC_INFO
namespace KPlato
{
ChartItemModel *findChartItemModel(QSortFilterProxyModel &model)
{
for (QAbstractProxyModel *p = &model; p; p = qobject_cast<QAbstractProxyModel*>(p->sourceModel())) {
ChartItemModel *c = qobject_cast<ChartItemModel*>(p->sourceModel());
if (c) {
return c;
}
}
return 0;
}
bool startsWith(const QStringList &keys, const QString &key)
{
for (const QString &k : keys) {
if (key.startsWith(k)) {
return true;
}
}
return false;
}
QStringList trimmed(const QStringList &lst)
{
QStringList rlst;
for (const QString &s : lst) {
QString r = s.trimmed();
if (!r.isEmpty()) {
rlst << r;
}
}
return rlst;
}
bool addDataToFile(QByteArray &buffer, const QString &destName, KoStore &to)
{
QBuffer file(&buffer);
if (!file.open(QIODevice::ReadOnly)) {
dbgRG<<"Failed to open buffer";
return false;
}
if (!to.open(destName)) {
dbgRG<<"Failed to open file for writing:"<<destName;
return false;
}
QByteArray data;
data.resize(8 * 1024);
uint total = 0;
for (int block = 0; (block = file.read(data.data(), data.size())) > 0; total += block) {
data.resize(block);
if (to.write(data) != block) {
dbgRG<<"Failed to write block of data";
return false;
}
data.resize(8*1024);
}
to.close();
file.close();
return true;
}
QAbstractItemModel *translationModel()
{
QList<QPair<QString, QString> > names;
names << QPair<QString, QString>("Project", i18n("Project"))
<< QPair<QString, QString>("Manager", i18n("Manager"))
<< QPair<QString, QString>("Schedule", i18n("Schedule"))
<< QPair<QString, QString>("BCWS", xi18nc("@title:column Budgeted Cost of Work Scheduled", "BCWS"))
<< QPair<QString, QString>("BCWP", xi18nc("@title:column Budgeted Cost of Work Performed", "BCWP"))
<< QPair<QString, QString>("ACWP", xi18nc("@title:column Actual Cost of Work Performed", "ACWP"))
<< QPair<QString, QString>("SPI", xi18nc("@title:column Schedule Performance Index", "SPI"))
<< QPair<QString, QString>("CPI", xi18nc("@title:column Cost Performance Index", "CPI"));
QStandardItemModel *model = new QStandardItemModel();
for (int column = 0; column < names.count(); ++column) {
model->setHeaderData(column, Qt::Horizontal, names.at(column).first, HeaderRole);
model->setHeaderData(column, Qt::Horizontal, names.at(column).second);
}
return model;
}
QAbstractItemModel *projectModel()
{
QList<QPair<QString, QString> > names;
names << QPair<QString, QString>("Name", i18n("Name"))
<< QPair<QString, QString>("Manager", i18n("Manager"))
<< QPair<QString, QString>("BCWS Cost", i18nc("Cost based Budgeted Cost of Work Scheduled", "BCWS Cost"))
<< QPair<QString, QString>("BCWP Cost", i18nc("Cost based Budgeted Cost of Work Performed", "BCWP Cost"))
<< QPair<QString, QString>("ACWP Cost", i18nc("Cost based Actual Cost of Work Performed", "ACWP Cost"))
<< QPair<QString, QString>("SPI Cost", i18nc("Cost based Schedule Performance Index", "SPI Cost"))
<< QPair<QString, QString>("CPI Cost", i18nc("Cost based Cost Performance Index", "CPI Cost"))
<< QPair<QString, QString>("BCWS Effort", i18nc("Effort based Budgeted Cost of Work Scheduled", "BCWS Effort"))
<< QPair<QString, QString>("BCWP Effort", i18nc("Effort based Budgeted Cost of Work Performed", "BCWP Effort"))
<< QPair<QString, QString>("ACWP Effort", i18nc("Effort based Actual Cost of Work Performed", "ACWP Effort"))
<< QPair<QString, QString>("SPI Effort", i18nc("Effort based Schedule Performance Index", "SPI Effort"))
<< QPair<QString, QString>("CPI Effort", i18nc("Effort based Cost Performance Index", "CPI Effort"));
QStandardItemModel *model = new QStandardItemModel(0, names.count());
for (int column = 0; column < names.count(); ++column) {
model->setHeaderData(column, Qt::Horizontal, names.at(column).first, HeaderRole);
model->setHeaderData(column, Qt::Horizontal, names.at(column).second);
}
return model;
}
void initProjectModel(QAbstractItemModel *model, Project *project, ScheduleManager *sm)
{
if (model->rowCount() == 0) {
model->insertRow(0);
}
QModelIndex idx = model->index(0, 0);
model->setData(idx, project->name());
idx = model->index(0, 1);
model->setData(idx, project->leader());
PerformanceDataCurrentDateModel m(0);
m.setProject(project);
m.setScheduleManager(sm);
m.setNodes(QList<Node*>() << project);
int col = 2; // column of BCWS Cost
for (int r = 0; r < 2; ++r) {
for (int c = 0; c < 5; ++c) {
idx = model->index(0, col++);
QModelIndex cidx = m.index(r, c);
model->setData(idx, cidx.data());
}
}
}
QAbstractItemModel *scheduleModel()
{
ScheduleItemModel m;
QStandardItemModel *model = new QStandardItemModel(0, m.columnCount());
for (int c = 0; c < m.columnCount(); ++c) {
model->setHeaderData(c, Qt::Horizontal, m.headerData(c, Qt::Horizontal));
model->setHeaderData(c, Qt::Horizontal, m.headerData(c, Qt::Horizontal, Qt::EditRole), HeaderRole);
}
return model;
}
void initScheduleModel(QAbstractItemModel *model, Project *project, ScheduleManager *sm)
{
ScheduleItemModel m;
m.setProject(project);
QModelIndex idx = m.index(sm);
if (idx.isValid()) {
if (model->rowCount() == 0) {
model->insertRow(0);
}
for (QModelIndex i = idx; i.isValid(); i = i.sibling(i.row(), i.column() + 1)) {
QModelIndex midx = model->index(0, i.column());
model->setData(midx, i.data());
dbgRGVariable<<model->headerData(midx.column(), Qt::Horizontal, HeaderRole).toString()<<'='<<i.data().toString();
}
} else dbgRGVariable<<"Could not find schedule"<<sm;
dbgRGVariable<<model->rowCount()<<model->columnCount();
}
//--------------------------------------
ReportGeneratorOdt::ReportGeneratorOdt()
: ReportGenerator()
, m_templateStore(0)
{
m_keys = QStringList() << "table" << "chart";
m_variables = QStringList() << "project" << "schedule";
m_basemodels
<< new NodeItemModel()
<< new TaskStatusItemModel()
<< new ChartItemModel()
<< new ScheduleItemModel();
m_datamodels["tasks"] = m_basemodels.at(0);
m_headerrole["tasks"] = Qt::EditRole;
m_datamodels["taskstatus"] = m_basemodels.at(1);
m_headerrole["taskstatus"] = Qt::EditRole;
m_datamodels["chart.project"] = m_basemodels.at(2);
m_headerrole["chart.project"] = Qt::EditRole;
m_datamodels["projects"] = projectModel();
m_headerrole["projects"] = HeaderRole;
m_datamodels["schedules"] = m_basemodels.at(3);
m_headerrole["schedules"] = Qt::EditRole;
m_datamodels["project"] = projectModel();
m_headerrole["project"] = HeaderRole;
m_datamodels["schedule"] = scheduleModel();
m_headerrole["schedule"] = HeaderRole;
m_datamodels["tr"] = translationModel();
m_headerrole["tr"] = HeaderRole;
}
ReportGeneratorOdt::~ReportGeneratorOdt()
{
for (QAbstractItemModel *m : m_datamodels) { // clazy:exclude=range-loop
if (!m_basemodels.contains(qobject_cast<ItemModelBase*>(m))) {
delete m;
}
}
qDeleteAll(m_basemodels);
close();
}
bool ReportGeneratorOdt::open()
{
m_lastError.clear();
if (m_templateFile.isEmpty()) {
m_lastError = i18n("Missing report template file");
return false;
}
if (m_reportFile.isEmpty()) {
m_lastError = i18n("Missing report result file");
return false;
}
if (m_templateStore) {
m_lastError = i18n("Report generator is already open");
return false;
}
m_templateStore = KoStore::createStore(m_templateFile, KoStore::Read);
if (!m_templateStore) {
dbgRG<<"Failed to open store:"<<m_templateFile;
m_lastError = i18n("Failed to open template file: %1", m_templateFile);
return false;
}
for (ItemModelBase *m : m_basemodels) { // clazy:exclude=range-loop
m->setProject(m_project);
m->setScheduleManager(m_manager);
if (qobject_cast<ChartItemModel*>(m)) {
qobject_cast<ChartItemModel*>(m)->setNodes(QList<Node*>() << m_project);
dbgRGChart<<"chart:"<<m_project<<m_manager<<"set nodes"<<m_project;
}
}
initProjectModel(m_datamodels["projects"], m_project, m_manager);
initProjectModel(m_datamodels["project"], m_project, m_manager);
initScheduleModel(m_datamodels["schedule"], m_project, m_manager);
return true;
}
void ReportGeneratorOdt::close()
{
delete m_templateStore;
m_templateStore = 0;
}
bool ReportGeneratorOdt::createReport()
{
if (!m_templateStore) {
m_lastError = i18n("Report generator has not been correctly opened");
return false;
}
// TODO get mimetype
return createReportOdt();
}
bool ReportGeneratorOdt::createReportOdt()
{
m_tags.clear();
dbgRG<<"url:"<<m_templateStore->urlOfStore();
KoOdfReadStore reader(m_templateStore);
if (!reader.loadAndParse(m_lastError)) {
dbgRG<<"Failed to loadAndParse:"<<m_lastError;
return false;
}
// copy manifest file and store a list of file references
KoStore *outStore = copyStore(reader, m_reportFile);
if (!outStore) {
dbgRG<<"Failed to copy template";
return false;
}
dbgRG << endl << "---- treat main content.xml ----" << endl;
QBuffer buffer;
KoXmlWriter *writer = createOasisXmlWriter(reader, &buffer, "content.xml", "office:document-content");
if (!writer) {
dbgRG<<"Failed to create content.xml writer";
return false;
}
KoXmlDocument kodoc = reader.contentDoc();
KoXmlElement parent = kodoc.documentElement();
writeChildElements(*writer, parent);
writer->endElement(); // office:document-content
writer->endDocument();
if (!outStore->addDataToFile(buffer.buffer(), "content.xml")) {
dbgRG<<"Failed to open 'content.xml' for writing";
m_lastError = i18n("Failed to write to store: %1", QString("content.xml"));
delete writer;
delete outStore;
return false;
}
buffer.close();
if (m_manifestfiles.contains("styles.xml")) {
dbgRG << endl << "---- treat styles.xml (for master-page headers/footers) ----" << endl;
QBuffer buffer2;
KoXmlWriter *styles = createOasisXmlWriter(reader, &buffer2, "styles.xml", "office:document-styles");
if (!styles) {
dbgRG<<"Failed to create styles.xml writer";
return false;
}
KoXmlDocument stylesDoc;
if (!reader.loadAndParse("styles.xml", stylesDoc, m_lastError)) {
debugPlan<<"Failed to read styles.xml"<<m_lastError;
delete writer;
delete outStore;
return false;
}
writeChildElements(*styles, stylesDoc.documentElement());
styles->endElement(); // office:document-styles
styles->endDocument();
if (!outStore->addDataToFile(buffer2.buffer(), "styles.xml")) {
dbgRG<<"Failed to open 'styles.xml' for writing";
m_lastError = i18n("Failed to write to store: %1", QString("styles.xml"));
delete writer;
delete outStore;
}
m_manifestfiles.removeAt(m_manifestfiles.indexOf("styles.xml"));
}
dbgRG << endl << "---- treat the embedded files ----" << endl;
treatEmbededObjects(reader, *outStore);
dbgRG << endl << "---- copy rest of files ----" << endl;
for (int i = 0; i < m_manifestfiles.count(); ++i) {
copyFile(*reader.store(), *outStore, m_manifestfiles.at(i));
}
if (!outStore->finalize()) {
dbgRG<<"Failed to write store:"<<outStore->urlOfStore();
m_lastError = i18n("Failed to write report file: %1", outStore->urlOfStore().path());
delete writer;
delete outStore;
return false;
}
delete writer;
delete outStore;
dbgRG<<"finished";
return true;
}
bool ReportGeneratorOdt::handleTextP(KoXmlWriter &writer, const KoXmlElement &textp)
{
Q_UNUSED(writer);
dbgRG<<"Check:"<<textp.text();
// search for user fields
KoXmlElement e;
forEachElement(e, textp) {
QString tag = e.prefix() + ':' + e.localName();
dbgRG<<"Tag:"<<tag;
if (tag == "text:user-field-get") {
QString field = e.attributeNS(KoXmlNS::text, "name");
dbgRG<<"Check:"<<field<<m_userfields;
if (m_userfields.contains(field) && startsWith(m_keys, field)) {
m_sortedfields << field;
dbgRG<<"Found:"<<tag<<field;
return false;
} else {
dbgRG<<"Not found:"<<tag<<field<<m_userfields.keys()<<startsWith(m_keys, field);
}
} else {
dbgRG<<" skipping:"<<tag;
}
}
return false;
}
void ReportGeneratorOdt::handleDrawFrame(KoXmlWriter &writer, const KoXmlElement &frame)
{
Q_UNUSED(writer);
dbgRGChart<<m_sortedfields;
if (m_sortedfields.isEmpty()) {
dbgRGChart<<"No fields";
return;
}
KoXmlElement e = frame.namedItemNS(KoXmlNS::draw, "object").toElement();
if (e.isNull()) {
dbgRGChart<<"No 'object'";
return;
}
QString dir = e.attributeNS(KoXmlNS::xlink, "href");
if (dir.isEmpty()) {
dbgRGChart<<"No dir";
return;
}
if (m_sortedfields.first().startsWith("chart")) {
QString name = m_sortedfields.takeFirst();
m_embededcharts[name] = dir;
UserField *field = m_userfields[name];
QString modelName = field->type + '.' + field->dataName;
field->setModel(dataModel(modelName), m_headerrole[modelName]);
dbgRGChart<<"Found chart:"<<field;
return;
}
if (m_sortedfields.first().startsWith("gantt")) {
m_embededgantts[m_sortedfields.takeFirst()] = dir;
dbgRGChart<<"Found gantt";
return;
}
dbgRGChart<<"No chart or gantt";
}
void ReportGeneratorOdt::treatText(KoXmlWriter &writer, const KoXmlText &text)
{
dbgRG<<" text node:"<<text.data();
writer.addTextNode(text.data());
}
void ReportGeneratorOdt::treatTable(KoXmlWriter &writer, const KoXmlElement &tableElement)
{
const QString name = m_sortedfields.value(0);
if (name.startsWith("table")) {
dbgRGTable<<" treat table expansion:"<<name;
m_sortedfields.takeFirst();
m_activefields << name;
UserField *field = m_userfields[name];
field->seqNr = -1;
dbgRGTable<<field;
writer.startElement("table:table");
writeElementAttributes(writer, tableElement);
writeChildElements(writer, tableElement);
writer.endElement();
m_activefields.removeLast();
} else {
dbgRGTable<<" just a table";
writer.startElement("table:table");
writeElementAttributes(writer, tableElement);
writeChildElements(writer, tableElement);
writer.endElement();
}
}
bool ReportGeneratorOdt::treatTableHeaderRows(KoXmlWriter &writer, const KoXmlElement &headerRowElement)
{
Q_UNUSED(writer);
Q_UNUSED(headerRowElement);
if (m_activefields.isEmpty() || m_userfields[m_activefields.last()]->type != "table") {
return false;
}
dbgRGTable;
UserField *field = m_userfields[m_activefields.last()];
field->seqNr = 0; // we are in header row
return false;
}
bool ReportGeneratorOdt::treatTableRow(KoXmlWriter &writer, const KoXmlElement &rowElement)
{
if (m_activefields.isEmpty() || m_userfields[m_activefields.last()]->type != "table") {
return false;
}
UserField *field = m_userfields[m_activefields.last()];
dbgRGTable<<field->seqNr;
if (field->seqNr == -1) {
// there is no header row, so start with data rows directly
field->seqNr = 1;
}
if (field->seqNr == 0) {
// header row
writer.startElement("table:table-row");
writeElementAttributes(writer, rowElement);
writeChildElements(writer, rowElement);
writer.endElement();
field->seqNr = 1; // next is first row
} else {
dbgRGTable<<" add rows:"<<field->rowCount();
for (field->begin(); field->next();) {
writer.startElement("table:table-row");
writeElementAttributes(writer, rowElement);
writeChildElements(writer, rowElement);
writer.endElement();
}
}
return true;
}
ReportGeneratorOdt::UserField *ReportGeneratorOdt::findUserField(const KoXmlElement &decl) const
{
UserField *field = 0;
QString name = decl.attributeNS(KoXmlNS::text, "name"); // eg: table1 or table1.Type or project.name or tr.bcws
field = m_userfields.value(name); // if Variable or Translation
if (!field) {
QStringList lst = name.split('.');
QMap<QString, UserField*>::const_iterator it;
for (it = m_userfields.constBegin(); it != m_userfields.constEnd(); ++it) {
if (lst.first().startsWith(it.key())) {
field = it.value();
break;
}
}
}
return field;
}
void ReportGeneratorOdt::treatUserFieldGet(KoXmlWriter &writer, const KoXmlElement &e)
{
dbgRG<<e.text();
UserField *field = findUserField(e);
if (field) {
writer.startElement("text:span");
if (field->variant() == UserField::Rows) {
QString name = e.attributeNS(KoXmlNS::text, "name"); // eg: table1.type
QString value = e.text(); // eg: type or name
QString data = field->data(value);
writer.addTextNode(data);
dbgRGTable<<"rows:"<<name<<value<<'='<<data;
} else if (field->variant() == UserField::Header) {
QString name = e.attributeNS(KoXmlNS::text, "name"); // eg: table1.type
QString value = e.text(); // eg: type or BCWS Cost
QString data = field->headerData(value);
writer.addTextNode(data);
dbgRGTable<<"header row:"<<name<<value<<'='<<data;
} else if (field->variant() == UserField::Variable) {
QString name = e.attributeNS(KoXmlNS::text, "name"); // eg: project.name
dbgRGVariable<<"variable:"<<name<<field->columns.value(0)<<field->data(field->columns.value(0));
writer.addTextNode(field->data(field->columns.value(0))); // a variable has only one column
} else if (field->variant() == UserField::Translation) {
QString name = e.attributeNS(KoXmlNS::text, "name"); // eg: tr.bcws
QString data = i18n(field->dataName.toLatin1());
if (field->headerNames.contains(field->dataName)) {
data = field->data(field->dataName);
}
dbgRGTr<<"translation:"<<name<<field->dataName<<'='<<data;
writer.addTextNode(data);
}
writer.endElement();
} else {
// this is not a report field, just copy verbatim
writer.startElement("text:user-field-get");
writeElementAttributes(writer, e);
dbgRG<<"no active user field";
writer.endElement();
}
}
void ReportGeneratorOdt::handleUserFieldDecls(KoXmlWriter &writer, const KoXmlElement &decls)
{
// load declarations
for (KoXmlNode node = decls.firstChild(); !node.isNull(); node = node.nextSibling()) {
if (!node.isElement()) {
continue;
}
KoXmlElement e = node.toElement();
QByteArray tag = QString(e.prefix() + ':' + e.localName()).toUtf8();
if (tag != "text:user-field-decl") {
continue;
}
QString name = e.attributeNS(KoXmlNS::text, "name").toLower();
if (name.isEmpty()) {
dbgRG<<" User field name is empty";
continue;
}
QString value = e.attributeNS(KoXmlNS::office, "string-value");
if (value.isEmpty()) {
dbgRG<<" User field value is empty:"<<name;
}
QStringList tags = name.split('.');
dbgRG<<" decl="<<name<<tags;
if (tags.first() == "tr") {
UserField *field = new UserField();
field->name = name;
m_userfields[field->name] = field;
field->dataName = value;
field->seqNr = -3;
field->columns << value;
field->setModel(dataModel("tr"), m_headerrole["tr"]);
dbgRGTr<<" added translation"<<field->name<<field->dataName;
} else if (m_variables.contains(tags.first())) {
Q_ASSERT(tags.count() >= 2);
Q_ASSERT(!m_userfields.contains(tags.first()));
UserField *field = new UserField();
field->name = name;
m_userfields[field->name] = field;
field->dataName = tags.first();
field->seqNr = -2;
QStringList vl = value.split(';');
if (!vl.isEmpty()) {
field->columns << vl.takeFirst();
field->properties = vl;
}
field->setModel(dataModel(field->dataName), m_headerrole[field->dataName]);
dbgRGVariable<<" added variable"<<field->name<<field->columns<<field->properties;
} else {
for (const QString &k : m_keys) { // clazy:exclude=range-loop
const QString vname = tags.first();
if (!vname.startsWith(k)) {
continue;
}
if (tags.count() == 1) {
// this is the main definition (eg: name: "table1", value: "tasks ...")
if (!m_userfields.contains(vname)) {
m_userfields[vname] = new UserField();
}
UserField *field = m_userfields[vname];
field->name = vname;
field->type = k;
QStringList vl = trimmed(value.toLower().split(';', QString::SkipEmptyParts));
field->dataName = vl.takeFirst();
field->properties += vl;
field->setModel(dataModel(field->dataName), m_headerrole[field->dataName]);
if (k == "chart") {
dbgRGChart<<" "<<"added tag:"<<field<<field->properties;
} else {
dbgRG<<" "<<"added tag:"<<field<<field->properties;
}
} else {
// this is the fields column definitions (eg: name: "table1.type" value: "<not used>")
if (!m_userfields.contains(vname)) {
m_userfields[vname] = new UserField();
}
UserField *field = m_userfields[vname];
field->name = vname;
field->columns << value.trimmed().toLower();
dbgRG<<" "<<"added column:"<<field->name<<field->columns;
}
}
}
}
writer.startElement("text:user-field-decls");
writeElementAttributes(writer, decls);
writeChartElements(writer, decls);
writer.endElement();
}
void ReportGeneratorOdt::writeElementAttributes(KoXmlWriter &writer, const KoXmlElement &element, const QStringList &exclude)
{
for (const QPair<QString, QString> &a : element.attributeFullNames()) { // clazy:exclude=range-loop
QString prefix = KoXmlNS::nsURI2NS(a.first);
if (prefix.isEmpty()) {
dbgRG<<" Skipping unknown namespace:"<<a.first<<a.second;
continue;
}
QString attr = QString(prefix + ':' + a.second);
if (exclude.contains(attr)) {
continue;
}
m_tags << attr.toUtf8(); // save
// dbgRGa.first<<a.second<<"->"<<attr;
//dbgRG<<" : "<<m_tags.last().constData()<<'='<<e.attributeNS(a.first, a.second);
writer.addAttribute(m_tags.last().constData(), element.attributeNS(a.first, a.second));
}
}
void ReportGeneratorOdt::writeChildElements(KoXmlWriter &writer, const KoXmlElement &parent)
{
//dbgRGparent.prefix()<<':'<<parent.localName();
for (KoXmlNode node = parent.firstChild(); !node.isNull(); node = node.nextSibling()) {
if (node.isText()) {
writer.addTextNode(node.toText().data());
continue;
}
// dbgRGnode.prefix()<<node.localName()<<node.nodeType();
KoXmlElement e = node.toElement();
if (e.isNull()) {
continue;
}
// dbgRG<<" "<<e.prefix()<<e.localName() << e.attributeFullNames();
QByteArray tag = QString(e.prefix() + ':' + e.localName()).toUtf8();
m_tags << tag; // make sure tags survives until we are finished
if (tag == "text:user-field-decls") {
handleUserFieldDecls(writer, e);
continue;
}
if (tag == "text:user-field-decl") {
dbgRG<<"Should we get here?"<<tag;
continue; // treated by handleUserFieldDecls()
}
if (tag == "table:table") {
treatTable(writer, e);
continue;
}
if (tag == "table:table-header-rows") {
if (treatTableHeaderRows(writer, e)) {
continue; // header rows treats its own children
}
}
if (tag == "table:table-row") {
if (treatTableRow(writer, e)) {
continue;
}
}
if (tag == "text:user-field-get") {
treatUserFieldGet(writer, e);
continue;
}
if (tag == "text:p") {
// check for/handle keywords
if (handleTextP(writer, e)) {
dbgRG<<"Skip:"<<tag;
continue;
}
}
if (tag == "draw:frame") {
handleDrawFrame(writer, e);
}
writer.startElement(tag.constData());
writeElementAttributes(writer, e);
writeChildElements(writer, e);
writer.endElement();
}
}
bool ReportGeneratorOdt::copyFile(KoStore &from, KoStore &to, const QString &file)
{
QByteArray data;
bool ok = from.extractFile(file, data);
if (!ok) {
dbgRG<<"Failed to extract:"<<file;
}
if (ok) {
ok = addDataToFile(data, file, to);
if (!ok) {
dbgRG<<"Failed to add file:"<<file;
}
}
if (ok) {
dbgRG<<"Added:"<<file;
}
return ok;
}
KoStore *ReportGeneratorOdt::copyStore(KoOdfReadStore &reader, const QString &outfile)
{
if (!reader.store()->hasFile("META-INF/manifest.xml")) {
dbgRG<<"No manifest file";
return 0;
}
KoXmlDocument manifest;
if (!reader.loadAndParse("META-INF/manifest.xml", manifest, m_lastError)) {
dbgRG<<"Failed to read manifest:"<<m_lastError;
return 0;
}
QUrl url(outfile);
if (!url.isLocalFile()) {
// FIXME: KoStore only handles local files
dbgRG<<"KoStore only handles local files";
m_lastError = i18n("Report generator can only generate local files");
return 0;
}
KoStore *out = KoStore::createStore(url.path(), KoStore::Write);
if (!out) {
dbgRG<<"Failed to create store";
m_lastError = i18n("Failed to open report file: %1", url.path());
return 0;
}
// This should go first, see OpenDocument v1.2 part 3: Packages
if (reader.store()->hasFile("mimetype")) {
if (!copyFile(*reader.store(), *out, "mimetype")) {
m_lastError = i18n("Failed to load manifest file");
delete out;
return 0;
}
}
if (!copyFile(*reader.store(), *out, "META-INF/manifest.xml")) {
m_lastError = i18n("Failed to write manifest file");
delete out;
return 0;
}
KoXmlElement e;
forEachElement(e, manifest.documentElement()) {
dbgRG<<e.tagName()<<e.attributeNames();
QString file;
if (e.hasAttribute("full-path")) {
file = e.attribute("full-path");
if (file.isEmpty() || file == "content.xml" || file.endsWith("/")) {
file.clear();
continue;
}
}
if (!file.isEmpty()) {
m_manifestfiles << file;
}
}
return out;
}
KoXmlWriter *ReportGeneratorOdt::createOasisXmlWriter(KoOdfReadStore &reader, QBuffer *buffer, const QString fileName, const char *rootElementName)
{
if (!reader.store()) {
m_lastError = i18n("No store backend");
dbgRG<<"No store";
return 0;
}
dbgRGTmp<<fileName<<rootElementName<<"has file:"<<reader.store()->hasFile(fileName);
if (reader.store()->isOpen()) {
reader.store()->close();
}
if (!reader.store()->open(fileName)) {
dbgRG << "Entry " << fileName << " not found!";
m_lastError = xi18nc("@info", "Failed to open file <filename>%1</filename> from store.", fileName);
return 0;
}
if (!reader.store()->device()->isOpen()) {
reader.store()->device()->open(QIODevice::ReadOnly);
}
KoXmlWriter *writer = 0;
QXmlStreamReader xml(reader.store()->device());
xml.setNamespaceProcessing(true);
while (!xml.atEnd()) {
xml.readNext();
if (xml.tokenType() == QXmlStreamReader::StartElement && !xml.namespaceDeclarations().isEmpty()) {
writer = new KoXmlWriter(buffer);
writer->startDocument(rootElementName);
writer->startElement(rootElementName);
writer->addAttribute("xmlns:calligra", KoXmlNS::calligra);
// Note: windows needs this type of iteration
QXmlStreamNamespaceDeclarations dcl = xml.namespaceDeclarations();
for (int ns = 0; ns < dcl.count(); ++ns) {
dbgRGTmp<<"add namespace:"<<("xmlns:" + dcl[ns].prefix())<<dcl[ns].namespaceUri();
writer->addAttribute(("xmlns:" + dcl[ns].prefix()).toLatin1(), dcl[ns].namespaceUri().toUtf8());
}
QXmlStreamAttributes attr = xml.attributes();
for (int a = 0; a < attr.count(); ++a) {
dbgRGTmp<<"add attribute:"<<attr[a].qualifiedName().toString()<<attr[a].value().toString();
writer->addAttribute(attr[a].qualifiedName().toLatin1(), attr[a].value().toUtf8());
}
}
}
reader.store()->close();
if (!writer) {
dbgRG<<"Failed to find a start elemet with namespace declarations in"<<fileName;
m_lastError = xi18nc("@info", "Missing namespace declarations:<nl/><filename>%1</filename>", fileName);
}
return writer;
}
void ReportGeneratorOdt::treatEmbededObjects(KoOdfReadStore &reader, KoStore &outStore)
{
dbgRGChart;
{QMap<QString, QString>::const_iterator it;
for (it = m_embededcharts.constBegin(); it != m_embededcharts.constEnd(); ++it) {
treatChart(reader, outStore, it.key(), it.value());
}}
{QMap<QString, QString>::const_iterator it;
for (it = m_embededgantts.constBegin(); it != m_embededgantts.constEnd(); ++it) {
treatGantt(reader, outStore, it.key(), it.value());
}}
}
void ReportGeneratorOdt::treatChart(KoOdfReadStore &reader, KoStore &outStore, const QString &name, const QString &dir)
{
dbgRGChart<<name<<dir;
if (!m_userfields.contains(name)) {
dbgRGChart<<"No user field with name:"<<name;
return;
}
QString file = dir + "/content.xml";
file = file.remove("./");
dbgRGChart<<file<<m_manifestfiles;
QString err;
KoXmlDocument doc;
if (!reader.loadAndParse(file, doc, err)) {
dbgRGChart<<err;
return;
}
m_activefields << name;
UserField *field = m_userfields[name];
if (field->dataName == "project") {
ChartItemModel *m = findChartItemModel(field->model);
if (m) {
m->setNodes(QList<Node*>() << m_project);
}
if (field->properties.isEmpty()) {
// default: take all
for (const QString &c : field->headerNames) { // clazy:exclude=range-loop
field->columns << c;
}
} else {
QStringList values;
for (const QString &p : field->properties) { // clazy:exclude=range-loop
if (p.startsWith("values")) {
QStringList vl = p.split("=");
Q_ASSERT(vl.count() > 1);
Q_ASSERT(vl.at(0) == "values");
for (const QString &v : vl.at(1).split(',')) { // clazy:exclude=range-loop
values << v.toLower().trimmed();
}
}
}
field->columns = values;
}
dbgRGChart<<field;
}
QBuffer buffer;
KoXmlWriter *writer = createOasisXmlWriter(reader, &buffer, file, "office:document-content");
writeChartElements(*writer, doc.documentElement());
writer->endElement(); // office:document-content
writer->endDocument();
dbgRGChart<<writer->toString();
if (!outStore.addDataToFile(buffer.buffer(), file)) {
dbgRGChart<<"Failed to open"<<file<<"for writing";
m_lastError = i18n("Failed to write to store: %1", file);
}
m_manifestfiles.removeAt(m_manifestfiles.indexOf(file));
dbgRGChart<<m_manifestfiles;
}
void ReportGeneratorOdt::treatGantt(KoOdfReadStore &reader, KoStore &outStore, const QString &name, const QString &file)
{
Q_UNUSED(reader);
Q_UNUSED(outStore);
Q_UNUSED(name);
Q_UNUSED(file);
dbgRGChart;
}
void ReportGeneratorOdt::writeChartElements(KoXmlWriter &writer, const KoXmlElement &parent)
{
dbgRGChart<<"writeChartElements: parent="<<parent.prefix()<<':'<<parent.localName();
for (KoXmlNode node = parent.firstChild(); !node.isNull(); node = node.nextSibling()) {
if (node.isText()) {
writer.addTextNode(node.toText().data());
continue;
}
KoXmlElement e = node.toElement();
if (e.isNull()) {
continue;
}
//dbgRGChart<<" "<<e.namespaceURI()<<e.prefix()<<e.localName() << e.attributeFullNames();
if (QString(KoXmlNS::nsURI2NS(e.namespaceURI())).isEmpty()) {
continue; // skip unknown namespace
}
QByteArray tag = QString(e.prefix() + ':' + e.localName()).toUtf8();
m_tags << tag; // make sure tags survives until we are finished
//dbgRGChart<<" handle element:"<<tag;
if (tag == "chart:plot-area") {
UserField *field = m_userfields[m_activefields.last()];
field->hasLabels = e.attributeNS(KoXmlNS::chart, "data-source-has-labels");
writer.startElement(tag.constData());
writeElementAttributes(writer, e);
writeChartElements(writer, e);
writer.endElement();
} else if (tag == "chart:categories") {
// assume this is x-axis
UserField *field = m_userfields[m_activefields.last()];
int columns = field->columnCount();
if (columns > 0) {
writer.startElement(tag.constData());
writeElementAttributes(writer, e, QStringList() << "table:cell-range-address");
int startRow = 1;
if (field->hasLabels == "both" || field->hasLabels == "primary-x") {
++startRow;
}
QString end = QString("local-table.$A$%1:$A$%2").arg(startRow).arg(startRow+field->rowCount()-1);
writer.addAttribute("table:cell-range-address", end);
writeChartElements(writer, e);
writer.endElement();
}
} else if (tag == "chart:series") {
UserField *field = m_userfields[m_activefields.last()];
int columns = field->columnCount();
if (columns > 0 && field->serieNr < columns) {
int startRow = 1;
if (field->hasLabels == "both" || field->hasLabels == "primary-x") {
++startRow;
}
char startColumn = 'A';
if (field->hasLabels == "both" || field->hasLabels == "primary-y") {
++startColumn;
}
writer.startElement(tag.constData());
writeElementAttributes(writer, e, QStringList() << "chart:values-cell-range-address"<<"chart:label-cell-address");
QChar c(startColumn + field->serieNr);
QString lab = QString("local-table.$%1$1").arg(c);
writer.addAttribute("chart:values-cell-address", lab);
QString end = QString("local-table.$%1$%2:$%1$%3").arg(c).arg(startRow).arg(startRow+field->rowCount()-1);
writer.addAttribute("chart:values-cell-range-address", end);
writer.startElement("chart:data-point");
writer.addAttribute("chart:repeated", field->rowCount());
writer.endElement();
writer.endElement();
++field->serieNr;
}
} else if (tag == "chart:data-point") {
// do nothing handled under chart:series
} else if (tag == "table:table-header-columns") {
writer.startElement(tag.constData());
writer.startElement("table:table-column"); // just an empty tag
writer.endElement();
writer.endElement();
} else if (tag == "table:table-columns") {
writer.startElement(tag.constData());
writeChartElements(writer, e);
writer.endElement();
} else if (tag == "table:table-column") {
int columns = m_userfields[m_activefields.last()]->columnCount();
writer.startElement(tag.constData());
writer.addAttribute("table:number-columns-repeated", columns);
writer.endElement();
} else if (tag == "table:table-header-rows") {
writer.startElement(tag.constData());
writer.startElement("table:table-row");
// first column not used, just an empty cell
writer.startElement("table:table-cell");
writer.startElement("text:p");
writer.endElement();
writer.endElement();
// write legends
UserField *field = m_userfields[m_activefields.last()];
for (const QString &name : field->columns) { // clazy:exclude=range-loop
QString value = field->headerData(name);
writer.startElement("table:table-cell");
writer.addAttribute("office:value-type", "string");
writer.startElement("text:p");
writer.addTextNode(value);
writer.endElement();
writer.endElement();
}
writer.endElement();
writer.endElement();
} else if (tag == "table:table-rows") {
writer.startElement(tag.constData());
UserField *field = m_userfields[m_activefields.last()];
int columns = field->columnCount();
if (columns > 0) {
int rows = field->model.rowCount();
for (int r = 0; r < rows; ++r) {
writer.startElement("table:table-row");
// first x-axis labels
QDate date = QDate(field->model.headerData(r, Qt::Vertical, Qt::EditRole).toDate());
// NOTE: 1899-12-30 is the reference date, but could it depend on style?
int day = QDate(1899, 12, 30).daysTo(date);
writer.startElement("table:table-cell");
writer.addAttribute("office:value", day);
writer.addAttribute("office:value-type", "float");
writer.startElement("text:p");
writer.addTextNode(QString::number(day));
writer.endElement();
writer.endElement();
// then the data
for (const QString &name : field->columns) { // clazy:exclude=range-loop
QVariant value = field->model.index(r, field->column(name)).data();
writer.startElement("table:table-cell");
writer.addAttribute("office:value-type", "float");
writer.addAttribute("office:value", value.toDouble());
writer.startElement("text:p");
writer.addTextNode(QString::number(value.toDouble()));
writer.endElement();
writer.endElement();
}
writer.endElement();
}
}
writer.endElement();
} else if (tag == "chart:legend") {
writer.startElement(tag.constData());
if (!e.hasAttributeNS(KoXmlNS::chart, "legend-align") && e.hasAttributeNS(KoXmlNS::chart, "legend-position")) {
// lowriter does not specify this attribute
// If legend-position is start, end, top or bottom
// we need to have legend-align == center so that words
// repositions the legend correctly
QStringList lst = QStringList() << "start" << "end" << "top" << "bottom";
if (lst.contains(e.attributeNS(KoXmlNS::chart, "legend-position"))) {
writer.addAttribute("chart:legend-align", "center");
dbgRGChart<<"adding legend-align";
}
}
writeElementAttributes(writer, e);
writeChartElements(writer, e);
writer.endElement();
} else {
writer.startElement(tag.constData());
writeElementAttributes(writer, e);
writeChartElements(writer, e);
writer.endElement();
}
}
}
void ReportGeneratorOdt::listChildNodes(const QDomNode &parent)
{
QDomNodeList lst = parent.childNodes();
for (int i = 0; i < lst.count(); ++i) {
if (lst.at(i).isElement()) {
QDomElement e = lst.at(i).toElement();
dbgRG<<"Element:"<<e.tagName()<<"value="<<e.text();
QDomNamedNodeMap map = e.attributes();
for (int j = 0; j < map.count(); ++j) {
QDomAttr attr = map.item(j).toAttr();
dbgRG<<" "<<attr.name()<<attr.value();
}
}
listChildNodes(lst.at(i));
}
}
QAbstractItemModel *ReportGeneratorOdt::dataModel(const QString &name) const
{
dbgRG<<name<<m_datamodels;
QAbstractItemModel *model = 0;
if (m_datamodels.contains(name)) {
model = m_datamodels[name];
}
return model;
}
void ReportGeneratorOdt::UserField::setModel(QAbstractItemModel *model, int role)
{
headerNames.clear();
this->model.setSourceModel(model);
for (int c = 0; c < this->model.columnCount(); ++c) {
headerNames << this->model.headerData(c, Qt::Horizontal, role).toString().toLower();
}
}
int ReportGeneratorOdt::UserField::column(const QString &columnName) const
{
QStringList l = columnName.split('.');
int c = l.isEmpty() ? -1 : headerNames.indexOf(l.last().toLower());
dbgRGTable<<" column:"<<columnName<<'='<<c;
return c;
}
void ReportGeneratorOdt::UserField::begin()
{
currentIndex = QModelIndex();
}
bool ReportGeneratorOdt::UserField::next()
{
if (model.hasChildren(currentIndex)) {
currentIndex = model.index(0, column(columns.value(0)), currentIndex);
} else {
do {
QModelIndex idx = currentIndex.sibling(currentIndex.row()+1, column(columns.value(0)));
if (idx.isValid()) {
currentIndex = idx;
break;
}
currentIndex = model.parent(currentIndex);
} while (currentIndex.isValid());
}
return currentIndex.isValid();
}
QString ReportGeneratorOdt::UserField::data(const QString &column) const
{
QModelIndex idx;
if (currentIndex.isValid()) {
idx = model.index(currentIndex.row(), this->column(column), currentIndex.parent());
} else {
idx = model.index(0, this->column(column));
}
QString s = "No data";
if (idx.isValid()) {
s = model.data(idx).toString();
}
dbgRG<<column<<'='<<s;
return s;
}
QString ReportGeneratorOdt::UserField::headerData(const QString &columnName) const
{
return model.headerData(column(columnName), Qt::Horizontal).toString();
}
int ReportGeneratorOdt::UserField::rowCount()
{
int rows = 0;
for (begin(); next(); ++rows) {}
return rows;
}
int ReportGeneratorOdt::UserField::variant() const
{
int t = Rows;
switch (seqNr) {
case -3: t = Translation; break;
case -2: t = Variable; break;
case -1: t = None; break;
case 0: t = Header; break;
default: break;
}
return t;
}
QDebug operator<<(QDebug dbg, ReportGeneratorOdt::UserField *f)
{
if (f) {
return operator<<(dbg, *f);
}
dbg << "UserField(0x0)";
return dbg;
}
QDebug operator<<(QDebug dbg, ReportGeneratorOdt::UserField &f)
{
dbg.nospace() << "UserField[";
switch (f.variant()) {
case ReportGeneratorOdt::UserField::Header: dbg << "H: " + f.name + '.' + f.dataName; break;
case ReportGeneratorOdt::UserField::Rows: dbg << "R: " + f.name + '.' + f.dataName; break;
case ReportGeneratorOdt::UserField::Variable: dbg << "V: " + f.name; break;
case ReportGeneratorOdt::UserField::Translation: dbg << "T: " + f.name; break;
default: dbg << "U: " + f.name; break;
}
if (!f.columns.isEmpty()) {
dbg << endl <<"Columns: " << f.columns;
}
if (!f.headerNames.isEmpty()) {
dbg << endl << "Headers: " << f.headerNames;
}
if (!f.columns.isEmpty() || !f.headerNames.isEmpty()) {
dbg << endl;
}
dbg << ']';
return dbg.space();
}
} //namespace KPlato
diff --git a/src/libs/models/tests/FlatProxyModelTester.cpp b/src/libs/models/tests/FlatProxyModelTester.cpp
index f3fd669d..a721bc84 100644
--- a/src/libs/models/tests/FlatProxyModelTester.cpp
+++ b/src/libs/models/tests/FlatProxyModelTester.cpp
@@ -1,621 +1,622 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
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 "FlatProxyModelTester.h"
#include "kptnodeitemmodel.h"
#include "kptproject.h"
#include "kpttask.h"
#include <QModelIndex>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QSortFilterProxyModel>
#include <QTest>
namespace KPlato
{
void FlatProxyModelTester::emptyModel()
{
QCOMPARE( m_flatmodel.columnCount(), 0 );
QCOMPARE( m_flatmodel.rowCount(), 0 );
}
void FlatProxyModelTester::test()
{
qDebug()<<"set 1 row, 1 column";
m_standardmodel.setColumnCount( 1 );
m_standardmodel.setRowCount( 1 );
m_standardmodel.setHorizontalHeaderLabels( QStringList() << "Column 1" );
m_flatmodel.setSourceModel( &m_standardmodel );
QCOMPARE( m_flatmodel.columnCount(), 2 ); // it adds an extra column
QCOMPARE( m_flatmodel.rowCount(), 1 );
QCOMPARE( m_flatmodel.headerData( 0, Qt::Horizontal ), QVariant( "Column 1" ) );
m_standardmodel.setData( m_standardmodel.index( 0, 0 ), "Index 0,0" );
QModelIndex idx = m_flatmodel.index( 0, 0 );
QVERIFY( idx.isValid() );
qDebug()<<"Index 0,0:"<<idx.data();
QCOMPARE( idx.data(), QVariant( "Index 0,0" ) );
qDebug()<<"1 row, set 2 columns";
m_standardmodel.setColumnCount( 2 );
QCOMPARE( m_flatmodel.columnCount(), 3 ); // it adds an extra column
m_flatmodel.setHeaderData( 1, Qt::Horizontal, "Column 2" );
QCOMPARE( m_flatmodel.headerData( 1, Qt::Horizontal ), QVariant( "Column 2" ) );
m_standardmodel.setData( m_standardmodel.index( 0, 1 ), "Index 0,1" );
idx = m_flatmodel.index( 0, 1 );
QVERIFY( idx.isValid() );
qDebug()<<"Index 0,1:"<<idx.data();
QCOMPARE( idx.data(), QVariant( "Index 0,1" ) );
qDebug()<<"Set 2 rows, 2 columns";
m_standardmodel.setRowCount( 2 );
QCOMPARE( m_flatmodel.rowCount(), 2 );
m_standardmodel.setData( m_standardmodel.index( 1, 0 ), "Index 1,0" );
idx = m_flatmodel.index( 1, 0 );
QVERIFY( idx.isValid() );
qDebug()<<"Index 1,0:"<<idx.data();
QCOMPARE( idx.data(), QVariant( "Index 1,0" ) );
m_standardmodel.setData( m_standardmodel.index( 1, 1 ), "Index 1,1" );
idx = m_flatmodel.index( 1, 1 );
QVERIFY( idx.isValid() );
qDebug()<<"Index 1,1:"<<idx.data();
QCOMPARE( idx.data(), QVariant( "Index 1,1" ) );
qDebug()<<"Add child on last index, adds a new row 1 in the flat model";
// NOTE: m_standardmodel.insertRow( 0, m_standardmodel.index( 1, 0 ) );
// does not work as there will be no columns and thus no valid indexes for this row
QStandardItem *item = m_standardmodel.itemFromIndex( m_standardmodel.index( 1, 0 ) );
QList<QStandardItem*> items;
items << new QStandardItem( "Child last column 1" ) << new QStandardItem( "Child last column 2" );
item->appendRow( items );
QModelIndex parent = m_standardmodel.index( 1, 0 );
QCOMPARE( m_standardmodel.rowCount(parent), 1);
QCOMPARE( m_flatmodel.rowCount(), 3 );
idx = m_flatmodel.index( 2, 0 );
QCOMPARE( idx.data(), QVariant( "Child last column 1" ) );
idx = m_flatmodel.index( 2, 1 );
QCOMPARE( idx.data(), QVariant( "Child last column 2" ) );
qDebug()<<"add child on first index";
item = m_standardmodel.itemFromIndex( m_standardmodel.index( 0, 0 ) );
items.clear();
items << new QStandardItem( "Child first column 1" ) << new QStandardItem( "Child first column 2" );
item->appendRow( items );
QCOMPARE( m_flatmodel.rowCount(), 4 );
idx = m_flatmodel.index( 1, 0 );
QCOMPARE( idx.data(), QVariant( "Child first column 1" ) );
idx = m_flatmodel.index( 1, 1 );
QCOMPARE( idx.data(), QVariant( "Child first column 2" ) );
qDebug()<<"add row (2) on top level between first and last";
item = m_standardmodel.invisibleRootItem();
items.clear();
items << new QStandardItem( "New index 1,0" ) << new QStandardItem( "New index 1,1" );
item->insertRow( 1, items );
QCOMPARE( m_flatmodel.rowCount(), 5 );
idx = m_flatmodel.index( 2, 0 );
QCOMPARE( idx.data(), QVariant( "New index 1,0" ) );
idx = m_flatmodel.index( 2, 1 );
QCOMPARE( idx.data(), QVariant( "New index 1,1" ) );
qDebug()<<"Add child on middle index, adds row 3 to flat model";
item = m_standardmodel.itemFromIndex( m_standardmodel.index( 1, 0 ) );
items.clear();
items << new QStandardItem( "Child middle column 1" ) << new QStandardItem( "Child middle column 2" );
item->appendRow( items );
QCOMPARE( m_flatmodel.rowCount(), 6 );
idx = m_flatmodel.index( 3, 0 );
QCOMPARE( idx.data().toString(), QVariant( "Child middle column 1" ).toString() );
idx = m_flatmodel.index( 3, 1 );
QCOMPARE( idx.data(), QVariant( "Child middle column 2" ) );
qDebug()<<"Add child on middle index's child, adds row 4 to flat model";
parent = m_standardmodel.index( 1, 0 );
QCOMPARE( parent.data().toString(), QString( "New index 1,0" ) );
idx = m_standardmodel.index( 0, 0, parent );
QCOMPARE( idx.data().toString(), QString( "Child middle column 1" ) );
item = m_standardmodel.itemFromIndex( idx );
items.clear();
items << new QStandardItem( "Child of middle child column 1" ) << new QStandardItem( "Child of middle child column 2" );
item->appendRow( items );
QCOMPARE( m_flatmodel.rowCount(), 7 );
idx = m_flatmodel.index( 4, 0 );
QCOMPARE( idx.data().toString(), QVariant( "Child of middle child column 1" ).toString() );
idx = m_flatmodel.index( 4, 1 );
QCOMPARE( idx.data(), QVariant( "Child of middle child column 2" ) );
qDebug()<<"Add child on middle index at row 0, adds row 3 to flat model";
idx = m_standardmodel.index( 1, 0 );
QCOMPARE( idx.data().toString(), QString( "New index 1,0" ) );
item = m_standardmodel.itemFromIndex( idx );
items.clear();
items << new QStandardItem( "Index 1,0 -> Index 0,0" ) << new QStandardItem( "Index 1,0 -> Index 0,1" );
item->insertRow( 0, items );
QCOMPARE( m_flatmodel.rowCount(), 8 );
idx = m_flatmodel.index( 3, 0 );
QCOMPARE( idx.data().toString(), QVariant( "Index 1,0 -> Index 0,0" ).toString() );
idx = m_flatmodel.index( 3, 1 );
QCOMPARE( idx.data(), QVariant( "Index 1,0 -> Index 0,1" ) );
qDebug()<<"Add child on middle index at row 1, adds row 4 to flat model";
idx = m_standardmodel.index( 1, 0 );
QCOMPARE( idx.data().toString(), QString( "New index 1,0" ) );
item = m_standardmodel.itemFromIndex( idx );
items.clear();
items << new QStandardItem( "Index 1,0 -> Index 1,0" ) << new QStandardItem( "Index 1,0 -> Index 1,1" );
item->insertRow( 1, items );
QCOMPARE( m_flatmodel.rowCount(), 9 );
idx = m_flatmodel.index( 4, 0 );
QCOMPARE( idx.data().toString(), QVariant( "Index 1,0 -> Index 1,0" ).toString() );
idx = m_flatmodel.index( 4, 1 );
QCOMPARE( idx.data(), QVariant( "Index 1,0 -> Index 1,1" ) );
qDebug()<<"Add child on middle index at last row (4), adds row 8 to flat model";
idx = m_standardmodel.index( 1, 0 );
QCOMPARE( idx.data().toString(), QString( "New index 1,0" ) );
item = m_standardmodel.itemFromIndex( idx );
items.clear();
items << new QStandardItem( "Index 1,0 -> Index 4,0" ) << new QStandardItem( "Index 1,0 -> Index 4,1" );
item->insertRow( 3, items );
QCOMPARE( m_standardmodel.rowCount( m_standardmodel.index( 1, 0 ) ), 4 );
QCOMPARE( m_flatmodel.rowCount(), 10 );
idx = m_flatmodel.index( 7, 0 );
QCOMPARE( idx.data().toString(), QVariant( "Index 1,0 -> Index 4,0" ).toString() );
idx = m_flatmodel.index( 7, 1 );
QCOMPARE( idx.data(), QVariant( "Index 1,0 -> Index 4,1" ) );
}
void FlatProxyModelTester::testInsertRemoveTop()
{
QSortFilterProxyModel sf;
FlatProxyModel fm;
QStandardItemModel sm;
sf.setSourceModel( &fm );
fm.setSourceModel( &sm );
sm.setHeaderData( 0, Qt::Horizontal, "Column 0" );
QCOMPARE( sm.rowCount(), 0 );
// insert
sm.insertRow( 0, new QStandardItem( "First" ) );
QCOMPARE( sm.rowCount(), 1 );
QCOMPARE( fm.rowCount(), 1 );
QCOMPARE( sf.rowCount(), 1 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "First" ) );
sm.insertRow( 1, new QStandardItem( "Second" ) );
QCOMPARE( sm.rowCount(), 2 );
QCOMPARE( fm.rowCount(), 2 );
QCOMPARE( sf.rowCount(), 2 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sm.index( 1, 0 ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "First" ) );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "Second" ) );
sm.insertRow( 1, new QStandardItem( "In between" ) );
QCOMPARE( sm.rowCount(), 3 );
QCOMPARE( fm.rowCount(), 3 );
QCOMPARE( sf.rowCount(), 3 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sm.index( 1, 0 ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 2, 0 ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "First" ) );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "Second" ) );
sm.insertRow( 0, new QStandardItem( "Before first" ) );
QCOMPARE( sm.rowCount(), 4 );
QCOMPARE( fm.rowCount(), 4 );
QCOMPARE( sf.rowCount(), 4 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sm.index( 1, 0 ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 2, 0 ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 3, 0 ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "Before first" ) );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "Second" ) );
// remove
sm.removeRow( 0 );
QCOMPARE( sm.rowCount(), 3 );
QCOMPARE( fm.rowCount(), 3 );
QCOMPARE( sf.rowCount(), 3 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sm.index( 1, 0 ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 2, 0 ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "First" ) );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "Second" ) );
sm.removeRow( 1 );
QCOMPARE( sm.rowCount(), 2 );
QCOMPARE( fm.rowCount(), 2 );
QCOMPARE( sf.rowCount(), 2 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sm.index( 1, 0 ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "First" ) );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "Second" ) );
sm.removeRow( 1 );
QCOMPARE( sm.rowCount(), 1 );
QCOMPARE( fm.rowCount(), 1 );
QCOMPARE( sf.rowCount(), 1 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "First" ) );
sm.removeRow( 0 );
QCOMPARE( sm.rowCount(), 0 );
QCOMPARE( fm.rowCount(), 0 );
QCOMPARE( sf.rowCount(), 0 );
}
void FlatProxyModelTester::testInsertRemoveChildren()
{
QSortFilterProxyModel sf;
FlatProxyModel fm;
QStandardItemModel sm;
sf.setSourceModel( &fm );
fm.setSourceModel( &sm );
sm.setHeaderData( 0, Qt::Horizontal, "Column 0" );
QCOMPARE( sm.rowCount(), 0 );
QStandardItem *pitem = new QStandardItem( "Parent" );
sm.insertRow( 0, pitem );
QCOMPARE( sm.rowCount(), 1 );
QCOMPARE( fm.rowCount(), 1 );
QCOMPARE( sf.rowCount(), 1 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "Parent" ) );
QModelIndex parent = sm.index( 0, 0 );
QVERIFY( parent.isValid() );
QStandardItem *item = new QStandardItem( "First child" );
pitem->insertRow( 0, item );
QCOMPARE( sm.rowCount( parent ), 1 );
QCOMPARE( fm.rowCount(), 2 );
QCOMPARE( sf.rowCount(), 2 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
item = new QStandardItem( "Second child" );
pitem->insertRow( 1, item );
QCOMPARE( sm.rowCount( parent ), 2 );
QCOMPARE( fm.rowCount(), 3 );
QCOMPARE( sf.rowCount(), 3 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "Second child" ) );
item = new QStandardItem( "In between" );
pitem->insertRow( 1, item );
QCOMPARE( sm.rowCount( parent ), 3 );
QCOMPARE( fm.rowCount(), 4 );
QCOMPARE( sf.rowCount(), 4 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 2, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "Second child" ) );
item = new QStandardItem( "Before first" );
pitem->insertRow( 0, item );
QCOMPARE( sm.rowCount( parent ), 4 );
QCOMPARE( fm.rowCount(), 5 );
QCOMPARE( sf.rowCount(), 5 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 2, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sm.index( 3, 0, parent ).data(), sf.index( 4, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "Before first" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First child" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 4, 0 ).data(), QVariant( "Second child" ) );
sm.removeRow( 0, parent );
QCOMPARE( sm.rowCount( parent ), 3 );
QCOMPARE( fm.rowCount(), 4 );
QCOMPARE( sf.rowCount(), 4 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 2, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "Second child" ) );
sm.removeRow( 1, parent );
QCOMPARE( sm.rowCount( parent ), 2 );
QCOMPARE( fm.rowCount(), 3 );
QCOMPARE( sf.rowCount(), 3 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "Second child" ) );
sm.removeRow( 1, parent );
QCOMPARE( sm.rowCount( parent ), 1 );
QCOMPARE( fm.rowCount(), 2 );
QCOMPARE( sf.rowCount(), 2 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
sm.removeRow( 0, parent );
QCOMPARE( sm.rowCount( parent ), 0 );
QCOMPARE( fm.rowCount(), 1 );
QCOMPARE( sf.rowCount(), 1 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
sm.removeRow( 0 );
QCOMPARE( sm.rowCount(), 0 );
QCOMPARE( fm.rowCount(), 0 );
QCOMPARE( sf.rowCount(), 0 );
}
void FlatProxyModelTester::testInsertRemoveGrandChildren()
{
QSortFilterProxyModel sf;
FlatProxyModel fm;
QStandardItemModel sm;
sf.setSourceModel( &fm );
fm.setSourceModel( &sm );
sm.setHeaderData( 0, Qt::Horizontal, "Column 0" );
QCOMPARE( sm.rowCount(), 0 );
QStandardItem *pitem = new QStandardItem( "Parent" );
sm.insertRow( 0, pitem );
QCOMPARE( sm.rowCount(), 1 );
QCOMPARE( fm.rowCount(), 1 );
QCOMPARE( sf.rowCount(), 1 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "Parent" ) );
QModelIndex grandparent = sm.index( 0, 0 );
QVERIFY( grandparent.isValid() );
QStandardItem *gitem = new QStandardItem( "First child" );
pitem->insertRow( 0, gitem );
QCOMPARE( sm.rowCount( grandparent ), 1 );
QCOMPARE( fm.rowCount(), 2 );
QCOMPARE( sf.rowCount(), 2 );
QCOMPARE( sm.index( 0, 0, grandparent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
QModelIndex parent = sm.index( 0, 0, grandparent );
QVERIFY( parent.isValid() );
QStandardItem *item = new QStandardItem( "First grandchild" );
gitem->insertRow( 0, item );
QCOMPARE( sm.rowCount( parent ), 1 );
QCOMPARE( fm.rowCount(), 3 );
QCOMPARE( sf.rowCount(), 3 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First grandchild" ) );
item = new QStandardItem( "Second grandchild" );
gitem->insertRow( 1, item );
QCOMPARE( sm.rowCount( parent ), 2 );
QCOMPARE( fm.rowCount(), 4 );
QCOMPARE( sf.rowCount(), 4 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First grandchild" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "Second grandchild" ) );
item = new QStandardItem( "In between" );
gitem->insertRow( 1, item );
QCOMPARE( sm.rowCount( parent ), 3 );
QCOMPARE( fm.rowCount(), 5 );
QCOMPARE( sf.rowCount(), 5 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sm.index( 2, 0, parent ).data(), sf.index( 4, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First grandchild" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 4, 0 ).data(), QVariant( "Second grandchild" ) );
item = new QStandardItem( "Before first" );
gitem->insertRow( 0, item );
QCOMPARE( sm.rowCount( parent ), 4 );
QCOMPARE( fm.rowCount(), 6 );
QCOMPARE( sf.rowCount(), 6 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sm.index( 2, 0, parent ).data(), sf.index( 4, 0 ).data() );
QCOMPARE( sm.index( 3, 0, parent ).data(), sf.index( 5, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "Before first" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "First grandchild" ) );
QCOMPARE( sf.index( 4, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 5, 0 ).data(), QVariant( "Second grandchild" ) );
sm.removeRow( 0, parent );
QCOMPARE( sm.rowCount( parent ), 3 );
QCOMPARE( fm.rowCount(), 5 );
QCOMPARE( sf.rowCount(), 5 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sm.index( 2, 0, parent ).data(), sf.index( 4, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First grandchild" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "In between" ) );
QCOMPARE( sf.index( 4, 0 ).data(), QVariant( "Second grandchild" ) );
sm.removeRow( 1, parent );
QCOMPARE( sm.rowCount( parent ), 2 );
QCOMPARE( fm.rowCount(), 4 );
QCOMPARE( sf.rowCount(), 4 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sm.index( 1, 0, parent ).data(), sf.index( 3, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First grandchild" ) );
QCOMPARE( sf.index( 3, 0 ).data(), QVariant( "Second grandchild" ) );
sm.removeRow( 1, parent );
QCOMPARE( sm.rowCount( parent ), 1 );
QCOMPARE( fm.rowCount(), 3 );
QCOMPARE( sf.rowCount(), 3 );
QCOMPARE( sm.index( 0, 0, parent ).data(), sf.index( 2, 0 ).data() );
QCOMPARE( sf.index( 2, 0 ).data(), QVariant( "First grandchild" ) );
sm.removeRow( 0, parent );
QCOMPARE( sm.rowCount( parent ), 0 );
QCOMPARE( fm.rowCount(), 2 );
QCOMPARE( sf.rowCount(), 2 );
QCOMPARE( sm.index( 0, 0, grandparent ).data(), sf.index( 1, 0 ).data() );
QCOMPARE( sf.index( 1, 0 ).data(), QVariant( "First child" ) );
QCOMPARE( sm.rowCount( grandparent ), 1 );
sm.removeRow( 0, grandparent );
QCOMPARE( sm.rowCount( grandparent ), 0 );
QCOMPARE( fm.rowCount(), 1 );
QCOMPARE( sf.rowCount(), 1 );
QCOMPARE( sm.index( 0, 0 ).data(), sf.index( 0, 0 ).data() );
QCOMPARE( sf.index( 0, 0 ).data(), QVariant( "Parent" ) );
QCOMPARE( sm.rowCount(), 1 );
sm.removeRow( 0 );
QCOMPARE( sm.rowCount(), 0 );
QCOMPARE( fm.rowCount(), 0 );
QCOMPARE( sf.rowCount(), 0 );
}
void FlatProxyModelTester::testWithNodeItemModel()
{
Project project;
Task *t = project.createTask();
t->setName("S1");
project.addSubTask(t, &project);
Task *t1 = project.createTask();
t1->setName("S1.1");
project.addSubTask(t1, t);
t = project.createTask();
t->setName("T1.1.1");
project.addSubTask(t, t1);
NodeItemModel model;
model.setProject(&project);
FlatProxyModel flatmodel;
flatmodel.setSourceModel(&model);
QCOMPARE(flatmodel.rowCount(), 3);
QModelIndex idx = flatmodel.index(0, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S1"));
idx = flatmodel.index(1, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S1.1"));
idx = flatmodel.index(2, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T1.1.1"));
// test non 0 column
int column = NodeModel::NodeType;
idx = flatmodel.index(0, column);
QCOMPARE(flatmodel.data(idx, Qt::EditRole), QVariant(Node::Type_Summarytask));
idx = flatmodel.index(1, column);
QCOMPARE(flatmodel.data(idx, Qt::EditRole), QVariant(Node::Type_Summarytask));
idx = flatmodel.index(2, column);
QCOMPARE(flatmodel.data(idx, Qt::EditRole), QVariant(Node::Type_Task));
// test parent
column = flatmodel.columnCount() - 1;
idx = flatmodel.index(0, column);
QCOMPARE(flatmodel.data(idx), QVariant());
idx = flatmodel.index(1, column);
QCOMPARE(flatmodel.data(idx), QVariant("S1"));
idx = flatmodel.index(2, column);
QCOMPARE(flatmodel.data(idx), QVariant("S1.1"));
t = project.createTask();
t->setName("S2");
project.addSubTask(t, &project);
t1 = project.createTask();
t1->setName("S2.1");
project.addSubTask(t1, t);
t = project.createTask();
t->setName("T2.1.1");
project.addSubTask(t, t1);
QCOMPARE(flatmodel.rowCount(), 6);
idx = flatmodel.index(0, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S1"));
idx = flatmodel.index(1, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S1.1"));
idx = flatmodel.index(2, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T1.1.1"));
idx = flatmodel.index(3, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S2"));
idx = flatmodel.index(4, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S2.1"));
idx = flatmodel.index(5, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T2.1.1"));
t = project.createTask();
t->setName("T3");
project.addSubTask(t, &project);
QCOMPARE(flatmodel.rowCount(), 7);
idx = flatmodel.index(6, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T3"));
project.takeTask(t);
QCOMPARE(flatmodel.rowCount(), 6);
project.addSubTask(t, &project);
QCOMPARE(flatmodel.rowCount(), 7);
idx = flatmodel.index(6, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T3"));
project.moveTaskUp(t);
idx = flatmodel.index(3, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T3"));
idx = flatmodel.index(4, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S2"));
idx = flatmodel.index(5, 0);
QCOMPARE(flatmodel.data(idx), QVariant("S2.1"));
idx = flatmodel.index(6, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T2.1.1"));
project.moveTask(t, &project, 0);
idx = flatmodel.index(0, 0);
QCOMPARE(flatmodel.data(idx), QVariant("T3"));
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::FlatProxyModelTester )
diff --git a/src/libs/models/tests/ResourceModelTester.cpp b/src/libs/models/tests/ResourceModelTester.cpp
index baa9903b..56fe6b16 100644
--- a/src/libs/models/tests/ResourceModelTester.cpp
+++ b/src/libs/models/tests/ResourceModelTester.cpp
@@ -1,363 +1,364 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "ResourceModelTester.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include "kptappointment.h"
#include <QModelIndex>
#include <QTest>
namespace QTest
{
template<>
char *toString(const KPlato::DateTime &dt)
{
return toString( dt.toString() );
}
}
namespace KPlato
{
void ResourceModelTester::printDebug( long id ) const {
Project *p = m_project;
Resource *r = m_resource;
qDebug()<<"Debug info -------------------------------------";
qDebug()<<"project start time:"<<p->startTime().toString();
qDebug()<<"project end time :"<<p->endTime().toString();
qDebug()<<"Resource start:"<<r->startTime( id ).toString();
qDebug()<<"Resource end :"<<r->endTime( id ).toString();
qDebug()<<"Appointments:"<<r->numAppointments( id )<<"(internal)";
foreach ( Appointment *a, r->appointments( id ) ) {
foreach ( const AppointmentInterval &i, a->intervals().map() ) {
qDebug()<<" "<<i.startTime().toString()<<"-"<<i.endTime().toString()<<";"<<i.load();
}
}
qDebug()<<"Appointments:"<<r->numExternalAppointments()<<"(external)";
foreach ( Appointment *a, r->externalAppointmentList() ) {
foreach ( const AppointmentInterval &i, a->intervals().map() ) {
qDebug()<<" "<<i.startTime().toString()<<"-"<<i.endTime().toString()<<";"<<i.load();
}
}
}
void ResourceModelTester::printSchedulingLog( const ScheduleManager &sm ) const
{
qDebug()<<"Scheduling log ---------------------------------";
foreach ( const QString &s, sm.expected()->logMessages() ) {
qDebug()<<s;
}
}
void ResourceModelTester::initTestCase()
{
m_project = new Project();
m_project->setName( "P1" );
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId( m_project );
DateTime targetstart = DateTime( QDate::currentDate(), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 3 ) );
m_project->setConstraintStartTime( targetstart );
m_project->setConstraintEndTime( targetend);
// standard worktime defines 8 hour day as default
QVERIFY( m_project->standardWorktime() );
QCOMPARE( m_project->standardWorktime()->day(), 8.0 );
m_calendar = new Calendar( "Test" );
m_calendar->setDefault( true );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = m_calendar->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
m_project->addCalendar( m_calendar );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
m_project->addResourceGroup( g );
m_resource = new Resource();
m_resource->setName( "R1" );
m_resource->setCalendar( m_calendar );
m_project->addResource( g, m_resource );
m_task = m_project->createTask();
m_task->setName( "T1" );
m_project->addTask( m_task, m_project );
m_task->estimate()->setUnit( Duration::Unit_h );
m_task->estimate()->setExpectedEstimate( 8.0 );
m_task->estimate()->setType( Estimate::Type_Effort );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
gr->addResourceRequest( new ResourceRequest( m_resource, 100 ) );
m_task->addRequest( gr );
m_model.setProject( m_project );
QModelIndex idx;
int rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
idx = m_model.index( 0, 0, idx );
QCOMPARE( g->name(), m_model.data( idx ).toString() );
rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
idx = m_model.index( 0, 0, idx );
QCOMPARE( m_resource->name(), m_model.data( idx ).toString() );
idx = m_model.parent( idx );
QCOMPARE( g->name(), m_model.data( idx ).toString() );
}
void ResourceModelTester::cleanupTestCase()
{
delete m_project;
}
void ResourceModelTester::internalAppointments()
{
ScheduleManager *sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
long id = sm->scheduleId();
m_model.setScheduleManager( sm );
//printDebug( sm->scheduleId() );
QModelIndex idx;
// resource group
int rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
QModelIndex gidx = m_model.index( 0, 0, idx );
QVERIFY( gidx.isValid() );
// resource
rows = m_model.rowCount( gidx );
QCOMPARE( rows, 1 );
QModelIndex ridx = m_model.index( 0, 0, gidx );
QCOMPARE( m_resource->name(), m_model.data( ridx ).toString() );
ridx = m_model.index( m_resource );
QVERIFY( ridx.isValid() );
// appointment
rows = m_model.rowCount( ridx );
QCOMPARE( rows, m_resource->numAppointments( id ) );
QCOMPARE( rows, 1 );
QModelIndex aidx = m_model.index( 0, 0, ridx ); // first appointment
QVERIFY( aidx.isValid() );
rows = m_model.rowCount( aidx ); // num intervals
QCOMPARE( rows, 1 );
// interval
QModelIndex iidx = m_model.index( 0, 0, aidx ); // first interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
// appointment
idx = m_model.parent( iidx );
QCOMPARE( idx, aidx );
// resource
idx = m_model.parent( aidx );
QCOMPARE( idx, ridx );
// resource group
idx = m_model.parent( ridx );
QCOMPARE( idx, gidx );
// top
idx = m_model.parent( gidx );
QVERIFY( ! idx.isValid() );
}
void ResourceModelTester::externalAppointments()
{
DateTime targetstart = m_project->constraintStartTime();
DateTime targetend = m_project->constraintEndTime();
Task *t = m_task;
Resource *r = m_resource;
r->addExternalAppointment( "Ext-1", "External project 1", targetstart, targetstart.addDays( 1 ), 100 );
r->addExternalAppointment( "Ext-1", "External project 1", targetend.addDays( -1 ), targetend, 100 );
ScheduleManager *sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->createSchedules();
m_project->calculate( *sm );
long id = sm->scheduleId();
m_model.setScheduleManager( sm );
//printSchedulingLog( *sm );
//printDebug( sm->scheduleId() );
// resource group
QModelIndex idx;
int rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
idx = m_model.index( 0, 0, idx );
QVERIFY( idx.isValid() );
QVERIFY( ! m_model.parent( idx ).isValid() );
// resource
rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
QModelIndex ridx = m_model.index( 0, 0, idx );
QCOMPARE( m_resource->name(), m_model.data( ridx ).toString() );
QCOMPARE( ridx.parent(), idx );
ridx = m_model.index( m_resource );
QVERIFY( ridx.isValid() );
// appointments
rows = m_model.rowCount( ridx );
QCOMPARE( rows, m_resource->numAppointments( id ) + m_resource->numExternalAppointments() );
QCOMPARE( rows, 2 ); // one internal, one external
// internal appointment
QModelIndex aidx = m_model.index( 0, 0, ridx ); // first appointment (internal)
QVERIFY( aidx.isValid() );
rows = m_model.rowCount( aidx ); // num intervals
QCOMPARE( rows, 1 );
QCOMPARE( aidx.parent(), ridx );
QModelIndex iidx = m_model.index( 0, 0, aidx ); // first interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
QCOMPARE( iidx.parent(), aidx );
// external appointment
aidx = m_model.index( 1, 0, ridx ); // second appointment (external)
QVERIFY( aidx.isValid() );
rows = m_model.rowCount( aidx ); // num intervals
QCOMPARE( rows, 2 );
QCOMPARE( aidx.parent(), ridx );
iidx = m_model.index( 0, 0, aidx ); // first interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
QCOMPARE( iidx.parent(), aidx );
iidx = m_model.index( 1, 0, aidx ); // second interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
QCOMPARE( iidx.parent(), aidx );
QCOMPARE( t->startTime(), m_calendar->firstAvailableAfter( targetstart + Duration( 1, 0, 0 ), t->endTime() ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
}
void ResourceModelTester::externalOverbook()
{
DateTime targetstart = m_project->constraintStartTime();
DateTime targetend = m_project->constraintEndTime();
Task *t = m_task;
Resource *r = m_resource;
r->addExternalAppointment( "Ext-1", "External project 1", targetstart, targetstart.addDays( 1 ), 100 );
r->addExternalAppointment( "Ext-1", "External project 1", targetend.addDays( -1 ), targetend, 100 );
ScheduleManager *sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
sm->setAllowOverbooking( true );
sm->createSchedules();
m_project->calculate( *sm );
long id = sm->scheduleId();
m_model.setScheduleManager( sm );
//printSchedulingLog( *sm );
//printDebug( id );
// resource group
QModelIndex idx;
int rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
idx = m_model.index( 0, 0, idx );
QVERIFY( idx.isValid() );
// resource
rows = m_model.rowCount( idx );
QCOMPARE( rows, 1 );
idx = m_model.index( 0, 0, idx );
QCOMPARE( m_resource->name(), m_model.data( idx ).toString() );
idx = m_model.index( m_resource );
QVERIFY( idx.isValid() );
// appointments
rows = m_model.rowCount( idx );
QCOMPARE( rows, m_resource->numAppointments( id ) + m_resource->numExternalAppointments() );
QCOMPARE( rows, 2 ); // one internal, one external
// internal appointment
QModelIndex aidx = m_model.index( 0, 0, idx ); // first appointment (internal)
QVERIFY( aidx.isValid() );
rows = m_model.rowCount( aidx ); // num intervals
QCOMPARE( rows, 1 );
QModelIndex iidx = m_model.index( 0, 0, aidx ); // first interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
// external appointment
aidx = m_model.index( 1, 0, idx ); // second appointment (external)
QVERIFY( aidx.isValid() );
rows = m_model.rowCount( aidx ); // num intervals
QCOMPARE( rows, 2 );
iidx = m_model.index( 0, 0, aidx ); // first interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
iidx = m_model.index( 1, 0, aidx ); // second interval
QVERIFY( iidx.isValid() );
rows = m_model.rowCount( iidx ); // intervals don't have children
QCOMPARE( rows, 0 );
QCOMPARE( t->startTime(), m_calendar->firstAvailableAfter( targetstart, t->endTime() ) );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::ResourceModelTester )
diff --git a/src/libs/models/tests/WorkPackageProxyModelTester.cpp b/src/libs/models/tests/WorkPackageProxyModelTester.cpp
index 0b83d668..b0c17ae3 100644
--- a/src/libs/models/tests/WorkPackageProxyModelTester.cpp
+++ b/src/libs/models/tests/WorkPackageProxyModelTester.cpp
@@ -1,225 +1,226 @@
/* This file is part of the KDE project
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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 "WorkPackageProxyModelTester.h"
#include "kptcommand.h"
#include "kpttask.h"
#include "kptnode.h"
#include "kptnodeitemmodel.h"
#include <QModelIndex>
#include <QTest>
#include "../../kernel/tests/debug.cpp"
namespace KPlato
{
Task *WorkPackageProxyModelTester::addTask( Node *parent, int after )
{
Task *task = project.createTask();
TaskAddCmd cmd( &project, task, parent->childNode( after ) );
cmd.redo();
ResourceGroupRequest *gr = new ResourceGroupRequest( project.resourceGroupAt(0) );
ResourceRequest *rr = new ResourceRequest( project.resourceGroupAt(0)->resourceAt(0), 100 );
gr->addResourceRequest( rr );
AddResourceGroupRequestCmd c( *task, gr );
c.redo();
return task;
}
void WorkPackageProxyModelTester::removeTask( Node *task )
{
NodeDeleteCmd cmd( task );
cmd.redo();
}
void WorkPackageProxyModelTester::moveTask( Node *task, Node *newParent, int newPos )
{
NodeMoveCmd cmd( &project, task, newParent, newPos );
cmd.redo();
}
void WorkPackageProxyModelTester::initTestCase()
{
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
Calendar *calendar = new Calendar();
calendar->setName( "C1" );
calendar->setDefault( true );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = calendar->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
project.addCalendar( calendar );
QCOMPARE( m_model.rowCount(), 0 );
m_model.setProject( &project );
QCOMPARE( m_model.rowCount(), 0 );
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
project.addResourceGroup( g );
Resource *r = new Resource();
r->setName( "R1" );
project.addResource( g, r );
sm = project.createScheduleManager();
m_model.setScheduleManager( sm );
}
void WorkPackageProxyModelTester::testInsert()
{
Task *task = addTask( &project, 0 );
task->setName( "T1" );
QCOMPARE( m_model.baseModel()->rowCount(), 1 );
QCOMPARE( m_model.rowCount(), 0 ); // filtered, (not scheduled)
sm->createSchedules();
project.calculate( *sm );
QVERIFY( task->isScheduled() );
QCOMPARE( m_model.rowCount(), 1 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
task = addTask( &project, -1 );
task->setName( "T2" );
QCOMPARE( m_model.baseModel()->rowCount(), 2 );
QCOMPARE( m_model.rowCount(), 1 ); // filtered, (not scheduled)
sm->createSchedules();
project.calculate( *sm );
QVERIFY( task->isScheduled() );
QCOMPARE( m_model.rowCount(), 2 );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
task = addTask( &project, -1 );
task->setName( "T3" );
QCOMPARE( m_model.baseModel()->rowCount(), 3 );
QCOMPARE( m_model.rowCount(), 2 ); // filtered, (not scheduled)
sm->createSchedules();
project.calculate( *sm );
QVERIFY( task->isScheduled() );
QCOMPARE( m_model.rowCount(), 3 );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
}
void WorkPackageProxyModelTester::testRemove()
{
removeTask( project.childNode( 0 ) );
QCOMPARE( m_model.rowCount(), 2 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
removeTask( project.childNode( 1 ) );
QCOMPARE( m_model.rowCount(), 1 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
removeTask( project.childNode( 0 ) );
QCOMPARE( m_model.rowCount(), 0 );
}
void WorkPackageProxyModelTester::testMove()
{
testInsert();
//Debug::print( &project, "T1, T2, T3 ---------", true );
moveTask( project.childNode( 0 ), &project, 1 );
//Debug::print( &project, "T2, T1, T3 ---------", true );
QCOMPARE( m_model.rowCount(), 3 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
moveTask( project.childNode( 1 ), &project, 0 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
moveTask( project.childNode( 1 ), &project, 0 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
moveTask( project.childNode( 0 ), &project, -1 ); // last
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
}
void WorkPackageProxyModelTester::testMoveChild()
{
while ( project.numChildren() > 0 ) {
Node *t = project.childNode( 0 );
project.takeTask( t );
delete t;
}
testInsert();
Task *task = addTask( &project, 1 ); // after 1 = 2 :(
task->setName( "T4" );
//Debug::print( &project, "T1, T2, T4, T3 ----", true );
QCOMPARE( project.numChildren(), 4 );
QCOMPARE( m_model.rowCount(), 3 ); // filtered, (not scheduled)
sm->createSchedules();
project.calculate( *sm );
//Debug::print( &project, "T1, T2, T4, T3 ----", true );
QVERIFY( task->isScheduled() );
QCOMPARE( m_model.rowCount(), 4 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T4" ) );
QCOMPARE( m_model.index( 3, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
project.indentTask( project.childNode( 2 ) );
//Debug::print( &project, "T1, T2 -> T4, T3 ----", true );
QCOMPARE( m_model.rowCount(), 3 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T4" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
moveTask( project.childNode( 1 )->childNode( 0 ), project.childNode( 0 ), 0 );
//Debug::print( &project, "T1 -> T4, T2, T3 ----", true );
QCOMPARE( m_model.rowCount(), 3 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T4" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
moveTask( project.childNode( 0 )->childNode( 0 ), project.childNode( 2 ), 0 );
//Debug::print( &project, "T1, T2, T3 -> T4 ----", true );
QCOMPARE( m_model.rowCount(), 3 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T4" ) );
moveTask( project.childNode( 2 )->childNode( 0 ), &project, 0 );
//Debug::print( &project, "T4, T1, T2, T3 ----", true );
QCOMPARE( m_model.rowCount(), 4 );
QCOMPARE( m_model.index( 0, NodeModel::NodeName ).data().toString(), QString( "T4" ) );
QCOMPARE( m_model.index( 1, NodeModel::NodeName ).data().toString(), QString( "T1" ) );
QCOMPARE( m_model.index( 2, NodeModel::NodeName ).data().toString(), QString( "T2" ) );
QCOMPARE( m_model.index( 3, NodeModel::NodeName ).data().toString(), QString( "T3" ) );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::WorkPackageProxyModelTester )
diff --git a/src/libs/odf/Ko3dScene.cpp b/src/libs/odf/Ko3dScene.cpp
index 96ba04e5..6a9c22bd 100644
--- a/src/libs/odf/Ko3dScene.cpp
+++ b/src/libs/odf/Ko3dScene.cpp
@@ -1,337 +1,338 @@
/* This file is part of the KDE project
*
* Copyright (C) 2012 Inge Wallin <inge@lysator.liu.se>
*
* 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.
*/
// Own
+// clazy:excludeall=qstring-arg
#include "Ko3dScene.h"
#include "OdfDebug.h"
// Calligra
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoXmlWriter.h>
static QVector3D odfToVector3D(const QString &string);
// ----------------------------------------------------------------
// Ko3dScene::Private
class Q_DECL_HIDDEN Ko3dScene::Private
{
public:
Private() {};
~Private() {};
// Camera attributes
QVector3D vrp; // Camera origin
QVector3D vpn; // Camera direction
QVector3D vup; // Up direction
Projection projection;
QString distance;
QString focalLength;
QString shadowSlant;
// Rendering attributes
Shademode shadeMode;
QColor ambientColor;
bool lightingMode; // True: enable lights, false: disable lights
QString transform;
// Lightsources (these are children of the element, not attributes)
QVector<Lightsource> lights;
};
// ----------------------------------------------------------------
// Lightsource
Ko3dScene::Lightsource::Lightsource()
{
}
Ko3dScene::Lightsource::~Lightsource()
{
}
// getters
QColor Ko3dScene::Lightsource::diffuseColor() const { return m_diffuseColor; }
QVector3D Ko3dScene::Lightsource::direction() const { return m_direction; }
bool Ko3dScene::Lightsource::enabled() const { return m_enabled; }
bool Ko3dScene::Lightsource::specular() const { return m_specular; }
// setters
void
Ko3dScene::Lightsource::setDiffuseColor(const QColor &color)
{
m_diffuseColor = color;
}
void
Ko3dScene::Lightsource::setDirection(const QVector3D &direction)
{
m_direction = direction;
}
void
Ko3dScene::Lightsource::setEnabled(const bool enabled)
{
m_enabled = enabled;
}
void
Ko3dScene::Lightsource::setSpecular(const bool specular)
{
m_specular = specular;
}
bool Ko3dScene::Lightsource::loadOdf(const KoXmlElement &lightElement)
{
m_diffuseColor = QColor(lightElement.attributeNS(KoXmlNS::dr3d, "diffuse-color", "#ffffff"));
QString direction = lightElement.attributeNS(KoXmlNS::dr3d, "direction");
m_direction = odfToVector3D(direction);
m_enabled = (lightElement.attributeNS(KoXmlNS::dr3d, "enabled") == "true");
m_specular = (lightElement.attributeNS(KoXmlNS::dr3d, "specular") == "true");
return true;
}
void Ko3dScene::Lightsource::saveOdf(KoXmlWriter &writer) const
{
writer.startElement("dr3d:light");
writer.addAttribute("dr3d:diffuse-color", m_diffuseColor.name());
writer.addAttribute("dr3d:direction", (QString("(%1 %2 %3)")
.arg(m_direction.x(), 0, 'f', 11)
.arg(m_direction.y(), 0, 'f', 11)
.arg(m_direction.z(), 0, 'f', 11)));
writer.addAttribute("dr3d:enabled", m_enabled);
writer.addAttribute("dr3d:specular", m_specular);
writer.endElement(); // dr3d:light
}
// ----------------------------------------------------------------
// Ko3dScene
Ko3dScene::Ko3dScene()
: d(new Private())
{
}
Ko3dScene::~Ko3dScene()
{
delete d;
}
// getters
QVector3D Ko3dScene::vrp() const { return d->vrp; }
QVector3D Ko3dScene::vpn() const { return d->vpn; }
QVector3D Ko3dScene::vup() const { return d->vup; }
Ko3dScene::Projection Ko3dScene::projection() const { return d->projection; }
QString Ko3dScene::distance() const { return d->distance; }
QString Ko3dScene::focalLength() const { return d->focalLength; }
QString Ko3dScene::shadowSlant() const { return d->shadowSlant; }
Ko3dScene::Shademode Ko3dScene::shadeMode() const { return d->shadeMode; }
QColor Ko3dScene::ambientColor() const { return d->ambientColor; }
bool Ko3dScene::lightingMode() const { return d->lightingMode; }
QString Ko3dScene::transform() const { return d->transform; }
// setters
void Ko3dScene::setVrp(const QVector3D &vrp) { d->vrp = vrp; }
void Ko3dScene::setVpn(const QVector3D &vpn) { d->vpn = vpn; }
void Ko3dScene::setVup(const QVector3D &vup) { d->vup = vup; }
void Ko3dScene::setProjection(Projection projection) { d->projection = projection; }
void Ko3dScene::setDistance(const QString &distance) { d->distance = distance; }
void Ko3dScene::setFocalLength(const QString &focalLength) { d->focalLength = focalLength; }
void Ko3dScene::setShadowSlant(const QString &shadowSlant) { d->shadowSlant = shadowSlant; }
void Ko3dScene::setShadeMode(Shademode shadeMode) { d->shadeMode = shadeMode; }
void Ko3dScene::setAmbientColor(const QColor &ambientColor) { d->ambientColor = ambientColor; }
void Ko3dScene::setLightingMode(bool lightingMode) { d->lightingMode = lightingMode; }
void Ko3dScene::setTransform(const QString &transform) { d->transform = transform; }
bool Ko3dScene::loadOdf(const KoXmlElement &sceneElement)
{
QString dummy;
// Check if there is a 3d scene at all in this element. We
// approximate that by checking if there are any camera parameters.
if (!sceneElement.hasAttributeNS(KoXmlNS::dr3d, "vrp")
&& !sceneElement.hasAttributeNS(KoXmlNS::dr3d, "vpn")
&& !sceneElement.hasAttributeNS(KoXmlNS::dr3d, "vup"))
{
return false;
}
// 1. Load the scene attributes.
// Camera attributes
dummy = sceneElement.attributeNS(KoXmlNS::dr3d, "vrp");
d->vrp = odfToVector3D(dummy);
dummy = sceneElement.attributeNS(KoXmlNS::dr3d, "vpn");
d->vpn = odfToVector3D(dummy);
dummy = sceneElement.attributeNS(KoXmlNS::dr3d, "vup", "(0.0 0.0 1.0)");
d->vup = odfToVector3D(dummy);
dummy = sceneElement.attributeNS(KoXmlNS::dr3d, "projection", "perspective");
if (dummy == "parallel") {
d->projection = Parallel;
}
else {
d->projection = Perspective;
}
d->distance = sceneElement.attributeNS(KoXmlNS::dr3d, "distance");
d->focalLength = sceneElement.attributeNS(KoXmlNS::dr3d, "focal-length");
d->shadowSlant = sceneElement.attributeNS(KoXmlNS::dr3d, "shadow-slant");
d->ambientColor = QColor(sceneElement.attributeNS(KoXmlNS::dr3d, "ambient-color", "#888888"));
// Rendering attributes
dummy = sceneElement.attributeNS(KoXmlNS::dr3d, "shade-mode", "gouraud");
if (dummy == "flat") {
d->shadeMode = Flat;
}
else if (dummy == "phong") {
d->shadeMode = Phong;
}
else if (dummy == "draft") {
d->shadeMode = Draft;
}
else {
d->shadeMode = Gouraud;
}
d->lightingMode = (sceneElement.attributeNS(KoXmlNS::dr3d, "lighting-mode") == "true");
d->transform = sceneElement.attributeNS(KoXmlNS::dr3d, "transform");
// 2. Load the light sources.
// From the ODF 1.1 spec section 9.4.1:
KoXmlElement elem;
forEachElement(elem, sceneElement) {
if (elem.localName() == "light" && elem.namespaceURI() == KoXmlNS::dr3d) {
Lightsource light;
light.loadOdf(elem);
d->lights.append(light);
}
}
//debugOdf << "Lights:" << d->lights.size();
return true;
}
void Ko3dScene::saveOdfAttributes(KoXmlWriter &writer) const
{
// 1. Write scene attributes
// Camera attributes
writer.addAttribute("dr3d:vrp", (QString("(%1 %2 %3)")
.arg(d->vrp.x(), 0, 'f', 11)
.arg(d->vrp.y(), 0, 'f', 11)
.arg(d->vrp.z(), 0, 'f', 11)));
writer.addAttribute("dr3d:vpn", (QString("(%1 %2 %3)")
.arg(d->vpn.x(), 0, 'f', 11)
.arg(d->vpn.y(), 0, 'f', 11)
.arg(d->vpn.z(), 0, 'f', 11)));
writer.addAttribute("dr3d:vup", (QString("(%1 %2 %3)")
.arg(d->vup.x(), 0, 'f', 11)
.arg(d->vup.y(), 0, 'f', 11)
.arg(d->vup.z(), 0, 'f', 11)));
writer.addAttribute("dr3d:projection", (d->projection == Parallel) ? "parallel" : "perspective");
writer.addAttribute("dr3d:distance", d->distance);
writer.addAttribute("dr3d:focal-length", d->focalLength);
writer.addAttribute("dr3d:shadow-slant", d->shadowSlant);
writer.addAttribute("dr3d:ambient-color", d->ambientColor.name());
// Rendering attributes
switch (d->shadeMode) {
case Flat:
writer.addAttribute("dr3d:shade-mode", "flat");
break;
case Phong:
writer.addAttribute("dr3d:shade-mode", "phong");
break;
case Draft:
writer.addAttribute("dr3d:shade-mode", "draft");
break;
case Gouraud:
default:
writer.addAttribute("dr3d:shade-mode", "gouraud");
break;
}
writer.addAttribute("dr3d:lighting-mode", d->lightingMode);
writer.addAttribute("dr3d:transform", d->transform);
}
void Ko3dScene::saveOdfChildren(KoXmlWriter &writer) const
{
// Write light sources.
foreach (const Lightsource &light, d->lights) {
light.saveOdf(writer);
}
}
// ----------------------------------------------------------------
// Public functions
KOODF_EXPORT Ko3dScene *load3dScene(const KoXmlElement &element)
{
Ko3dScene *scene = new Ko3dScene();
if (scene->loadOdf(element)) {
return scene;
}
delete scene;
return 0;
}
// ----------------------------------------------------------------
// Static functions
QVector3D odfToVector3D(const QString &string)
{
// The string comes into this function in the form "(0 3.5 0.3)".
QStringList elements = string.mid(1, string.size() - 2).split(' ', QString::SkipEmptyParts);
if (elements.size() == 3) {
return QVector3D(elements[0].toDouble(), elements[1].toDouble(), elements[2].toDouble());
}
else {
return QVector3D(0, 0, 1);
}
}
diff --git a/src/libs/odf/KoBorder.cpp b/src/libs/odf/KoBorder.cpp
index 06211e56..f87f45a4 100644
--- a/src/libs/odf/KoBorder.cpp
+++ b/src/libs/odf/KoBorder.cpp
@@ -1,1162 +1,1163 @@
/* This file is part of the KDE project
*
* Copyright (C) 2009 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2011 Pierre Ducroquet <pinaraf@pinaraf.info>
*
* 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 "KoBorder.h"
#include <QPainter>
#include <OdfDebug.h>
#include <KoUnit.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoStyleStack.h>
class KoBorderPrivate : public QSharedData
{
public:
KoBorderPrivate();
~KoBorderPrivate();
QMap<KoBorder::BorderSide, KoBorder::BorderData> data;
};
KoBorderPrivate::KoBorderPrivate()
{
}
KoBorderPrivate::~KoBorderPrivate()
{
}
KoBorder::BorderData::BorderData()
: style(KoBorder::BorderNone)
, outerPen(QPen())
, innerPen(QPen())
, spacing(0)
{
outerPen.setWidthF(0.0f);
innerPen.setWidthF(0.0f);
}
bool KoBorder::BorderData::operator==(const KoBorder::BorderData& other) const
{
// Left Borders
if (style == BorderNone && other.style == BorderNone) {
// If both styles are None, then the rest of the values don't
// need to be compared.
;
}
else if (style != other.style) {
// If any of them are non-None, and they are different, the
// borders are also different.
return false;
}
else {
// Here we know that the border styles are the same, now
// compare the rest of the values.
if (outerPen != other.outerPen)
return false;
// If the border style == BorderDouble, then compare a couple
// of other values too.
if (style == BorderDouble) {
if (innerPen != other.innerPen)
return false;
if (spacing != other.spacing)
return false;
}
}
return true;
}
// ----------------------------------------------------------------
KoBorder::KoBorder()
: d(new KoBorderPrivate)
{
}
KoBorder::KoBorder(const KoBorder &kb)
: d(kb.d)
{
}
KoBorder::~KoBorder()
{
// No delete because d is a QSharedDataPointer.
}
// ----------------------------------------------------------------
// operators
KoBorder &KoBorder::operator=(const KoBorder &other)
{
d = other.d;
return *this;
}
bool KoBorder::operator==(const KoBorder &other) const
{
if (d.data() == other.d.data())
return true;
if (d->data.size() != other.d->data.size())
return false;
for (auto i = d->data.constBegin(); i != d->data.constEnd(); ++i) {
if (!other.d->data.contains(i.key()))
return false;
if (!(other.d->data[i.key()] == i.value()))
return false;
}
return true;
}
// ----------------------------------------------------------------
// public, non-class functions
KoBorder::BorderStyle KoBorder::odfBorderStyle(const QString &borderstyle, bool *converted)
{
// Note: the styles marked "Not odf compatible" below are legacies
// from the old words format. There are also lots of border
// styles in the MS DOC that we may have to handle at some point.
if (converted)
*converted = true;
if (borderstyle == "none")
return BorderNone;
if (borderstyle == "solid")
return BorderSolid;
if (borderstyle == "dashed")
return BorderDashed;
if (borderstyle == "dotted")
return BorderDotted;
if (borderstyle == "dot-dash")
return BorderDashDot;
if (borderstyle == "dot-dot-dash")
return BorderDashDotDot;
if (borderstyle == "double")
return BorderDouble;
if (borderstyle == "groove") // Not odf compatible -- see above
return BorderGroove;
if (borderstyle == "ridge") // Not odf compatible -- see above
return BorderRidge;
if (borderstyle == "inset") // Not odf compatible -- see above
return BorderInset;
if (borderstyle == "outset") // Not odf compatible -- see above
return BorderOutset;
if (borderstyle == "dash-largegap")
return KoBorder::BorderDashedLong;
if (borderstyle == "slash") // not officially odf, but we support it anyway
return KoBorder::BorderSlash;
if (borderstyle == "wave") // not officially odf, but we support it anyway
return KoBorder::BorderWave;
if (borderstyle == "double-wave") // not officially odf, but we support it anyway
return KoBorder::BorderDoubleWave;
if (converted)
*converted = false;
return BorderSolid;
}
QString KoBorder::odfBorderStyleString(BorderStyle borderstyle)
{
switch (borderstyle) {
case BorderDashed:
return QString("dashed");
case BorderDotted:
return QString("dotted");
case BorderDashDot:
return QString("dot-dash");
case BorderDashDotDot:
return QString("dot-dot-dash");
case BorderDouble:
return QString("double");
case BorderGroove:
return QString("groove"); // not odf -- see above
case BorderRidge:
return QString("ridge"); // not odf -- see above
case BorderInset:
return QString("inset"); // not odf -- see above
case BorderOutset:
return QString("outset"); // not odf -- see above
case BorderSolid:
return QString("solid");
case BorderNone:
return QString("none");
default:
// Handle unknown types as solid.
return QString("solid");
}
}
QString KoBorder::msoBorderStyleString(BorderStyle borderstyle)
{
switch (borderstyle) {
case KoBorder::BorderDashedLong:
return QString("dash-largegap");
case KoBorder::BorderSlash:
return QString("slash"); // not officially odf, but we support it anyway
case KoBorder::BorderWave:
return QString("wave"); // not officially odf, but we support it anyway
case KoBorder::BorderDoubleWave:
return QString("double-wave"); // not officially odf, but we support it anyway
default:
// Handle remaining styles as odf type style.
return odfBorderStyleString(borderstyle);
}
}
// ----------------------------------------------------------------
// Getters and Setters
void KoBorder::setBorderStyle(BorderSide side, BorderStyle style)
{
if (d->data[side].style == style) {
return;
}
if (!d->data.contains(side)) {
BorderData data;
data.style = style;
d->data[side] = data;
} else {
d->data[side].style = style;
}
// Make a best effort to create the best possible dash pattern for the chosen style.
// FIXME: KoTableCellStyle::setEdge() should call this function.
BorderData &edge = d->data[side];
qreal width = edge.outerPen.widthF();
qreal innerWidth = 0;
qreal middleWidth = 0;
qreal space = 0;
QVector<qreal> dashes;
switch (style) {
case KoBorder::BorderNone:
width = 0.0;
break;
case KoBorder::BorderDouble:
innerWidth = space = edge.outerPen.width() / 3; //some nice default look
width -= (space + innerWidth);
edge.outerPen.setStyle(Qt::SolidLine);
break;
case KoBorder::BorderDotted:
dashes << 1 << 1;
edge.outerPen.setDashPattern(dashes);
break;
case KoBorder::BorderDashed:
dashes << 4 << 1;
edge.outerPen.setDashPattern(dashes);
break;
case KoBorder::BorderDashedLong: {
dashes << 4 << 4;
edge.outerPen.setDashPattern(dashes);
break;
}
case KoBorder::BorderTriple:
innerWidth = middleWidth = space = width/6;
width -= (space + innerWidth);
edge.outerPen.setStyle(Qt::SolidLine);
break;
case KoBorder::BorderDashDot:
dashes << 3 << 3<< 7 << 3;
edge.outerPen.setDashPattern(dashes);
break;
case KoBorder::BorderDashDotDot:
dashes << 2 << 2<< 6 << 2 << 2 << 2;
edge.outerPen.setDashPattern(dashes);
break;
case KoBorder::BorderWave:
edge.outerPen.setStyle(Qt::SolidLine);
break;
case KoBorder::BorderSlash:
edge.outerPen.setStyle(Qt::SolidLine);
break;
case KoBorder::BorderDoubleWave:
innerWidth = space = width/3; //some nice default look
width -= (space + innerWidth);
edge.outerPen.setStyle(Qt::SolidLine);
break;
default:
edge.outerPen.setStyle(Qt::SolidLine);
break;
}
edge.outerPen.setJoinStyle(Qt::MiterJoin);
edge.outerPen.setCapStyle(Qt::FlatCap);
edge.outerPen.setWidthF(width);
edge.spacing = space;
edge.innerPen = edge.outerPen;
edge.innerPen.setWidthF(innerWidth);
}
KoBorder::BorderStyle KoBorder::borderStyle(BorderSide side) const
{
if (!d->data.contains(side)) {
return BorderNone;
} else {
return d->data[side].style;
}
}
void KoBorder::setBorderColor(BorderSide side, const QColor &color)
{
if (!d->data.contains(side)) {
BorderData data;
data.outerPen.setColor(color);
d->data[side] = data;
} else {
d->data[side].outerPen.setColor(color);
}
}
QColor KoBorder::borderColor(BorderSide side) const
{
if (!d->data.contains(side)) {
return QColor();
} else {
return d->data[side].outerPen.color();
}
}
void KoBorder::setBorderWidth(BorderSide side, qreal width)
{
if (!d->data.contains(side)) {
BorderData data;
data.outerPen.setWidthF(width);
d->data[side] = data;
} else {
d->data[side].outerPen.setWidthF(width);
}
}
qreal KoBorder::borderWidth(BorderSide side) const
{
if (!d->data.contains(side)) {
return 0;
} else {
if (d->data[side].style == BorderDouble)
return (d->data[side].outerPen.widthF() + d->data[side].innerPen.widthF()
+ d->data[side].spacing);
else
return d->data[side].outerPen.widthF();
}
}
void KoBorder::setOuterBorderWidth(BorderSide side, qreal width)
{
if (!d->data.contains(side)) {
BorderData data;
data.outerPen.setWidthF(width);
d->data[side] = data;
} else {
d->data[side].outerPen.setWidthF(width);
}
}
qreal KoBorder::outerBorderWidth(BorderSide side) const
{
if (!d->data.contains(side)) {
return 0;
} else {
return d->data[side].outerPen.widthF();
}
}
void KoBorder::setInnerBorderWidth(BorderSide side, qreal width)
{
if (!d->data.contains(side)) {
BorderData data;
data.innerPen.setWidthF(width);
d->data[side] = data;
} else {
d->data[side].innerPen.setWidthF(width);
}
}
qreal KoBorder::innerBorderWidth(BorderSide side) const
{
if (!d->data.contains(side)) {
return 0;
} else {
return d->data[side].innerPen.widthF();
}
}
void KoBorder::setBorderSpacing(BorderSide side, qreal width)
{
if (!d->data.contains(side)) {
BorderData data;
data.spacing = width;
d->data[side] = data;
} else {
d->data[side].spacing = width;
}
}
qreal KoBorder::borderSpacing(BorderSide side) const
{
if (!d->data.contains(side)) {
return 0;
} else {
return d->data[side].spacing;
}
}
KoBorder::BorderData KoBorder::borderData(BorderSide side) const
{
return d->data.value(side, BorderData());
}
void KoBorder::setBorderData(BorderSide side, const BorderData &data)
{
d->data[side] = data;
}
// -------------------------------
bool KoBorder::hasBorder() const
{
if (borderStyle(LeftBorder) != BorderNone && borderWidth(LeftBorder) > 0.0)
return true;
if (borderStyle(RightBorder) != BorderNone && borderWidth(RightBorder) > 0.0)
return true;
if (borderStyle(TopBorder) != BorderNone && borderWidth(TopBorder) > 0.0)
return true;
if (borderStyle(BottomBorder) != BorderNone && borderWidth(BottomBorder) > 0.0)
return true;
if (borderStyle(TlbrBorder) != BorderNone && borderWidth(TlbrBorder) > 0.0)
return true;
if (borderStyle(BltrBorder) != BorderNone && borderWidth(BltrBorder) > 0.0)
return true;
return false;
}
bool KoBorder::hasBorder(KoBorder::BorderSide side) const
{
return borderStyle(side) != BorderNone && borderWidth(side) > 0.0;
}
// ----------------------------------------------------------------
// painting
void KoBorder::paint(QPainter &painter, const QRectF &borderRect,
BorderPaintArea whereToPaint) const
{
Q_UNUSED(whereToPaint);
// In tables it is apparently best practice to paint the
// horizontal lines over the vertical ones. So let's use the same
// strategy here.
QPointF start;
QPointF end;
// FIXME: Make KoBorder store pointers to BorderData instead. This is very inefficient.
BorderData leftEdge = borderData(KoBorder::LeftBorder);
BorderData rightEdge = borderData(KoBorder::RightBorder);
BorderData topEdge = borderData(KoBorder::TopBorder);
BorderData bottomEdge = borderData(KoBorder::BottomBorder);
// Left border
if (hasBorder(LeftBorder)) {
start = borderRect.topLeft();
end = borderRect.bottomLeft();
paintBorderSide(painter, start, end, &leftEdge, true,
hasBorder(TopBorder) ? &topEdge : 0,
hasBorder(BottomBorder) ? &bottomEdge : 0,
1);
}
// Right border
if (hasBorder(RightBorder)) {
start = borderRect.topRight();
end = borderRect.bottomRight();
paintBorderSide(painter, start, end, &rightEdge, true,
hasBorder(TopBorder) ? &topEdge : 0,
hasBorder(BottomBorder) ? &bottomEdge : 0,
-1);
}
// Top border
if (hasBorder(TopBorder)) {
start = borderRect.topLeft();
end = borderRect.topRight();
paintBorderSide(painter, start, end, &topEdge, false,
hasBorder(LeftBorder) ? &leftEdge : 0,
hasBorder(RightBorder) ? &rightEdge : 0,
1);
}
// Bottom border
if (hasBorder(BottomBorder)) {
start = borderRect.bottomLeft();
end = borderRect.bottomRight();
paintBorderSide(painter, start, end, &bottomEdge, false,
hasBorder(LeftBorder) ? &leftEdge : 0,
hasBorder(RightBorder) ? &rightEdge : 0,
-1);
}
// FIXME: Diagonal borders
}
void KoBorder::paintBorderSide(QPainter &painter, QPointF lineStart, QPointF lineEnd,
BorderData *borderData, bool isVertical,
BorderData *neighbour1, BorderData *neighbour2,
int inwardsAcross) const
{
// Adjust the outer line so that it is inside the boundary.
qreal displacement = borderData->outerPen.widthF() / qreal(2.0);
if (isVertical) {
lineStart.setX(lineStart.x() + inwardsAcross * displacement);
lineEnd.setX(lineEnd.x() + inwardsAcross * displacement);
}
else {
lineStart.setY(lineStart.y() + inwardsAcross * displacement);
lineEnd.setY(lineEnd.y() + inwardsAcross * displacement);
}
painter.setPen(borderData->outerPen);
painter.drawLine(lineStart, lineEnd);
if (borderData->style == BorderDouble) {
displacement = (borderData->outerPen.widthF() / qreal(2.0)
+ borderData->spacing
+ borderData->innerPen.widthF() / qreal(2.0));
if (isVertical) {
lineStart.setX(lineStart.x() + inwardsAcross * displacement);
lineEnd.setX(lineEnd.x() + inwardsAcross * displacement);
}
else {
lineStart.setY(lineStart.y() + inwardsAcross * displacement);
lineEnd.setY(lineEnd.y() + inwardsAcross * displacement);
}
// Adjust for neigboring inner lines.
if (neighbour1 && neighbour1->style == BorderDouble) {
displacement = neighbour1->outerPen.widthF() + neighbour1->spacing;
if (isVertical) {
lineStart.setY(lineStart.y() + displacement);
}
else {
lineStart.setX(lineStart.x() + displacement);
}
}
if (neighbour2 && neighbour2->style == BorderDouble) {
displacement = neighbour2->outerPen.widthF() + neighbour2->spacing;
if (isVertical) {
lineEnd.setY(lineEnd.y() - displacement);
}
else {
lineEnd.setX(lineEnd.x() - displacement);
}
}
// Draw the inner line.
painter.setPen(borderData->innerPen);
painter.drawLine(lineStart, lineEnd);
}
}
// ----------------------------------------------------------------
// static functions
void parseOdfBorder(const QString &border, QColor *color,
KoBorder::BorderStyle *borderStyle, bool *hasBorderStyle,
qreal *borderWidth, bool *hasBorderWidth)
{
*hasBorderStyle = false;
*hasBorderWidth = false;
if (!border.isEmpty() && border != "none" && border != "hidden") {
QStringList borderData = border.split(' ', QString::SkipEmptyParts);
if (borderData.length() > 0)
{
const QColor borderColor = QColor(borderData.last());
if (borderColor.isValid()) {
*color = borderColor;
borderData.removeLast();
}
bool converted = false;
const KoBorder::BorderStyle parsedBorderStyle = KoBorder::odfBorderStyle(borderData.last(), &converted);
if (converted) {
*hasBorderStyle = true;
borderData.removeLast();
*borderStyle = parsedBorderStyle;
}
if (!borderData.isEmpty()) {
const qreal parsedBorderWidth = KoUnit::parseValue(borderData[0], 1.0);
*borderWidth = parsedBorderWidth;
*hasBorderWidth = true;
}
}
}
}
// ----------------------------------------------------------------
// load and save
bool KoBorder::loadOdf(const KoXmlElement &style)
{
bool result = false;
QString borderString;
bool hasSpecialBorder;
QString specialBorderString;
if (style.hasAttributeNS(KoXmlNS::fo, "border")) {
borderString = style.attributeNS(KoXmlNS::fo, "border");
if (borderString == "none") {
// We use the "false" to indicate that there is no border
// rather than that the parsing has failed.
return false;
}
result = true;
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder");
}
parseAndSetBorder(borderString, hasSpecialBorder, specialBorderString);
}
else {
// No common border attributes, check for the individual ones.
if (style.hasAttributeNS(KoXmlNS::fo, "border-left")) {
result = true;
borderString = style.attributeNS(KoXmlNS::fo, "border-left");
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-left"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-left");
}
parseAndSetBorder(LeftBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (style.hasAttributeNS(KoXmlNS::fo, "border-top")) {
result = true;
borderString = style.attributeNS(KoXmlNS::fo, "border-top");
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-top"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-top");
}
parseAndSetBorder(TopBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (style.hasAttributeNS(KoXmlNS::fo, "border-right")) {
result = true;
borderString = style.attributeNS(KoXmlNS::fo, "border-right");
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-right"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-right");
}
parseAndSetBorder(RightBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (style.hasAttributeNS(KoXmlNS::fo, "border-bottom")) {
result = true;
borderString = style.attributeNS(KoXmlNS::fo, "border-bottom");
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-bottom"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-bottom");
}
parseAndSetBorder(BottomBorder, borderString, hasSpecialBorder, specialBorderString);
}
}
// Diagonals are treated individually and are NOT part of <style:border>.
if (style.hasAttributeNS(KoXmlNS::style, "diagonal-tl-br")) {
result = true;
borderString = style.attributeNS(KoXmlNS::fo, "border-tl-br");
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-tl-br"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-tl-br");
}
parseAndSetBorder(TlbrBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (style.hasAttributeNS(KoXmlNS::style, "diagonal-bl-tr")) {
result = true;
borderString = style.attributeNS(KoXmlNS::fo, "border-bl-tr");
if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-bl-tr"))) {
specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-bl-tr");
}
parseAndSetBorder(BltrBorder, borderString, hasSpecialBorder, specialBorderString);
}
// Handle double borders.
if (style.hasAttributeNS(KoXmlNS::style, "border-line-width")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1));
setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1));
setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1));
setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
else {
if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-left")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-left");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-top")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-top");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-right")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-right");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-bottom")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-bottom");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
}
if (style.hasAttributeNS(KoXmlNS::style, "diagonal-tl-br-widths")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "diagonal-tl-br-widths");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(TlbrBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(TlbrBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(TlbrBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (style.hasAttributeNS(KoXmlNS::style, "diagonal-bl-tr-widths")) {
result = true;
QString borderLineWidth = style.attributeNS(KoXmlNS::style, "diagonal-bl-tr-widths");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(BltrBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(BltrBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(BltrBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
return result;
}
bool KoBorder::loadOdf(const KoStyleStack &styleStack)
{
bool result = false;
QString borderString;
bool hasSpecialBorder;
QString specialBorderString;
if (styleStack.hasProperty(KoXmlNS::fo, "border")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder");
}
parseAndSetBorder(borderString, hasSpecialBorder, specialBorderString);
}
// Even if there are common border attributes, check for the
// individual ones since they have precedence.
if (styleStack.hasProperty(KoXmlNS::fo, "border-left")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border-left");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-left"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-left");
}
parseAndSetBorder(LeftBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (styleStack.hasProperty(KoXmlNS::fo, "border-top")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border-top");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-top"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-top");
}
parseAndSetBorder(TopBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (styleStack.hasProperty(KoXmlNS::fo, "border-right")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border-right");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-right"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-right");
}
parseAndSetBorder(RightBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (styleStack.hasProperty(KoXmlNS::fo, "border-bottom")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border-bottom");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-bottom"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-bottom");
}
parseAndSetBorder(BottomBorder, borderString, hasSpecialBorder, specialBorderString);
}
// Diagonals are treated individually and are NOT part of <style:border>.
if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border-tl-br");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-tl-br"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-tl-br");
}
parseAndSetBorder(TlbrBorder, borderString, hasSpecialBorder, specialBorderString);
}
if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr")) {
result = true;
borderString = styleStack.property(KoXmlNS::fo, "border-bl-tr");
if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-bl-tr"))) {
specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-bl-tr");
}
parseAndSetBorder(BltrBorder, borderString, hasSpecialBorder, specialBorderString);
}
// Handle double borders.
if (styleStack.hasProperty(KoXmlNS::style, "border-line-width")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1));
setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1));
setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1));
setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
// Even if there are common border attributes, check for the
// individual ones since they have precedence.
if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-left")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-left");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-top")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-top");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-right")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-right");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-bottom")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-bottom");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
// Diagonals are treated individually and are NOT part of <style:border>.
if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br-widths")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "diagonal-tl-br-widths");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(TlbrBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(TlbrBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(TlbrBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr-widths")) {
result = true;
QString borderLineWidth = styleStack.property(KoXmlNS::style, "diagonal-bl-tr-widths");
if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") {
QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts);
setInnerBorderWidth(BltrBorder, KoUnit::parseValue(blw[0], 0.1));
setBorderSpacing(BltrBorder, KoUnit::parseValue(blw[1], 1.0));
setOuterBorderWidth(BltrBorder, KoUnit::parseValue(blw[2], 0.1));
}
}
return result;
}
// Private
void KoBorder::parseAndSetBorder(const QString &borderString,
bool hasSpecialBorder, const QString &specialBorderString)
{
if (borderString == "none") {
return;
}
//debugOdf << "*** *** Found border: " << border;
QColor bordersColor;
BorderStyle bordersStyle;
qreal bordersWidth;
bool foundStyle;
bool foundWidth;
parseOdfBorder(borderString, &bordersColor, &bordersStyle, &foundStyle,
&bordersWidth, &foundWidth);
if (bordersColor.isValid()) {
setBorderColor(LeftBorder, bordersColor);
setBorderColor(TopBorder, bordersColor);
setBorderColor(RightBorder, bordersColor);
setBorderColor(BottomBorder, bordersColor);
}
if (hasSpecialBorder) {
bordersStyle = KoBorder::odfBorderStyle(specialBorderString, &foundStyle);
}
if (foundStyle) {
setBorderStyle(LeftBorder, bordersStyle);
setBorderStyle(TopBorder, bordersStyle);
setBorderStyle(RightBorder, bordersStyle);
setBorderStyle(BottomBorder, bordersStyle);
}
if (foundWidth) {
setBorderWidth(LeftBorder, bordersWidth);
setBorderWidth(TopBorder, bordersWidth);
setBorderWidth(RightBorder, bordersWidth);
setBorderWidth(BottomBorder, bordersWidth);
}
}
// Private
void KoBorder::parseAndSetBorder(const BorderSide borderSide, const QString &borderString,
bool hasSpecialBorder, const QString &specialBorderString)
{
QColor borderColor;
BorderStyle borderStyle;
qreal borderWidth;
bool foundStyle;
bool foundWidth;
parseOdfBorder(borderString, &borderColor, &borderStyle, &foundStyle,
&borderWidth, &foundWidth);
if (borderColor.isValid()) {
setBorderColor(borderSide, borderColor);
}
if (hasSpecialBorder) {
borderStyle = KoBorder::odfBorderStyle(specialBorderString, &foundStyle);
}
if (foundStyle) {
setBorderStyle( borderSide, borderStyle);
}
if (foundWidth) {
setBorderWidth( borderSide, borderWidth);
}
}
void KoBorder::saveOdf(KoGenStyle &style, KoGenStyle::PropertyType type) const
{
// Get the strings that describe respective borders.
QString leftBorderString = QString("%1pt %2 %3")
.arg(QString::number(borderWidth(LeftBorder)),
odfBorderStyleString(borderStyle(LeftBorder)),
borderColor(LeftBorder).name());
QString rightBorderString = QString("%1pt %2 %3")
.arg(QString::number(borderWidth(RightBorder)),
odfBorderStyleString(borderStyle(RightBorder)),
borderColor(RightBorder).name());
QString topBorderString = QString("%1pt %2 %3")
.arg(QString::number(borderWidth(TopBorder)),
odfBorderStyleString(borderStyle(TopBorder)),
borderColor(TopBorder).name());
QString bottomBorderString = QString("%1pt %2 %3")
.arg(QString::number(borderWidth(BottomBorder)),
odfBorderStyleString(borderStyle(BottomBorder)),
borderColor(BottomBorder).name());
QString tlbrBorderString = QString("%1pt %2 %3")
.arg(QString::number(borderWidth(TlbrBorder)),
odfBorderStyleString(borderStyle(TlbrBorder)),
borderColor(TlbrBorder).name());
QString trblBorderString = QString("%1pt %2 %3")
.arg(QString::number(borderWidth(BltrBorder)),
odfBorderStyleString(borderStyle(BltrBorder)),
borderColor(BltrBorder).name());
// Get the strings that describe respective special borders (for special mso support).
QString leftBorderSpecialString = msoBorderStyleString(borderStyle(LeftBorder));
QString rightBorderSpecialString = msoBorderStyleString(borderStyle(RightBorder));
QString topBorderSpecialString = msoBorderStyleString(borderStyle(TopBorder));
QString bottomBorderSpecialString = msoBorderStyleString(borderStyle(BottomBorder));
//QString tlbrBorderSpecialString = msoBorderStyleString(borderStyle(TlbrBorder));
//QString trblBorderSpecialString = msoBorderStyleString(borderStyle(BltrBorder));
// Check if we can save all borders in one fo:border attribute, or
// if we have to use several different ones like fo:border-left, etc.
if (leftBorderString == rightBorderString
&& leftBorderString == topBorderString
&& leftBorderString == bottomBorderString) {
// Yes, they were all the same, so use only fo:border
style.addProperty("fo:border", leftBorderString, type);
style.addProperty("calligra:specialborder-left", leftBorderSpecialString, type);
style.addProperty("calligra:specialborder-right", rightBorderSpecialString, type);
style.addProperty("calligra:specialborder-top", topBorderSpecialString, type);
style.addProperty("calligra:specialborder-bottom", bottomBorderSpecialString, type);
} else {
// No, they were different, so use the individual borders.
//if (leftBorderStyle() != BorderNone)
style.addProperty("fo:border-left", leftBorderString, type);
style.addProperty("calligra:specialborder-left", leftBorderSpecialString, type);
//if (rightBorderStyle() != BorderNone)
style.addProperty("fo:border-right", rightBorderString, type);
style.addProperty("calligra:specialborder-right", rightBorderSpecialString, type);
//if (topBorderStyle() != BorderNone)
style.addProperty("fo:border-top", topBorderString, type);
style.addProperty("calligra:specialborder-top", topBorderSpecialString, type);
//if (bottomBorderStyle() != BorderNone)
style.addProperty("fo:border-bottom", bottomBorderString, type);
style.addProperty("calligra:specialborder-bottom", bottomBorderSpecialString, type);
}
if (style.type() != KoGenStyle::PageLayoutStyle) {
//if (tlbrBorderStyle() != BorderNone) {
style.addProperty("style:diagonal-tl-br", tlbrBorderString, type);
//}
//if (trblBorderStyle() != BorderNone) {
style.addProperty("style:diagonal-bl-tr", trblBorderString, type);
//}
}
// Handle double borders
QString leftBorderLineWidth = QString("%1pt %2pt %3pt")
.arg(QString::number(innerBorderWidth(LeftBorder)),
QString::number(borderSpacing(LeftBorder)),
QString::number(outerBorderWidth(LeftBorder)));
QString rightBorderLineWidth = QString("%1pt %2pt %3pt")
.arg(QString::number(innerBorderWidth(RightBorder)),
QString::number(borderSpacing(RightBorder)),
QString::number(outerBorderWidth(RightBorder)));
QString topBorderLineWidth = QString("%1pt %2pt %3pt")
.arg(QString::number(innerBorderWidth(TopBorder)),
QString::number(borderSpacing(TopBorder)),
QString::number(outerBorderWidth(TopBorder)));
QString bottomBorderLineWidth = QString("%1pt %2pt %3pt")
.arg(QString::number(innerBorderWidth(BottomBorder)),
QString::number(borderSpacing(BottomBorder)),
QString::number(outerBorderWidth(BottomBorder)));
QString tlbrBorderLineWidth = QString("%1pt %2pt %3pt")
.arg(QString::number(innerBorderWidth(TlbrBorder)),
QString::number(borderSpacing(TlbrBorder)),
QString::number(outerBorderWidth(TlbrBorder)));
QString trblBorderLineWidth = QString("%1pt %2pt %3pt")
.arg(QString::number(innerBorderWidth(BltrBorder)),
QString::number(borderSpacing(BltrBorder)),
QString::number(outerBorderWidth(BltrBorder)));
if (leftBorderLineWidth == rightBorderLineWidth
&& leftBorderLineWidth == topBorderLineWidth
&& leftBorderLineWidth == bottomBorderLineWidth
&& borderStyle(LeftBorder) == borderStyle(RightBorder)
&& borderStyle(TopBorder) == borderStyle(BottomBorder)
&& borderStyle(TopBorder) == borderStyle(LeftBorder)
&& (borderStyle(LeftBorder) == BorderDouble || borderStyle(LeftBorder) == BorderDoubleWave)) {
style.addProperty("style:border-line-width", leftBorderLineWidth, type);
} else {
if (borderStyle(LeftBorder) == BorderDouble || borderStyle(LeftBorder) == BorderDoubleWave)
style.addProperty("style:border-line-width-left", leftBorderLineWidth, type);
if (borderStyle(RightBorder) == BorderDouble || borderStyle(RightBorder) == BorderDoubleWave)
style.addProperty("style:border-line-width-right", rightBorderLineWidth, type);
if (borderStyle(TopBorder) == BorderDouble || borderStyle(TopBorder) == BorderDoubleWave)
style.addProperty("style:border-line-width-top", topBorderLineWidth, type);
if (borderStyle(BottomBorder) == BorderDouble || borderStyle(BottomBorder) == BorderDoubleWave)
style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, type);
}
if (style.type() != KoGenStyle::PageLayoutStyle) {
if (borderStyle(TlbrBorder) == BorderDouble || borderStyle(TlbrBorder) == BorderDoubleWave) {
style.addProperty("style:diagonal-tl-br-widths", tlbrBorderLineWidth, type);
}
if (borderStyle(BltrBorder) == BorderDouble || borderStyle(BltrBorder) == BorderDoubleWave) {
style.addProperty("style:diagonal-bl-tr-widths", trblBorderLineWidth, type);
}
}
}
diff --git a/src/libs/odf/KoColumns.cpp b/src/libs/odf/KoColumns.cpp
index bffb965e..87a72a4f 100644
--- a/src/libs/odf/KoColumns.cpp
+++ b/src/libs/odf/KoColumns.cpp
@@ -1,280 +1,281 @@
/* This file is part of the KDE project
Copyright 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
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 "KoColumns.h"
#include "KoXmlWriter.h"
#include "KoGenStyle.h"
#include "KoXmlReader.h"
#include "KoXmlNS.h"
#include "KoUnit.h"
#include "OdfDebug.h"
static const int defaultColumnCount = 1;
static const KoColumns::SeparatorStyle defaultSeparatorStyle = KoColumns::None;
static const int defaultSeparatorHeight = 100;
static const Qt::GlobalColor defaultSeparatorColor = Qt::black;
static const KoColumns::SeparatorVerticalAlignment defaultSeparatorVerticalAlignment = KoColumns::AlignTop;
static const int defaultColumnGapWidth = 17; // in pt, ~ 6mm
const char * KoColumns::separatorStyleString(KoColumns::SeparatorStyle separatorStyle)
{
const char * result;
// skip KoColumns::None, is default
if (separatorStyle == Solid) {
result = "solid";
} else if (separatorStyle == Dotted) {
result = "dotted";
} else if (separatorStyle == Dashed) {
result = "dashed";
} else if (separatorStyle == DotDashed) {
result = "dot-dashed";
} else {
result = "none";
}
return result;
}
const char * KoColumns::separatorVerticalAlignmentString(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment)
{
const char * result;
// skip KoColumns::AlignTop, is default
if (separatorVerticalAlignment == AlignVCenter) {
result = "middle";
} else if (separatorVerticalAlignment == AlignBottom) {
result = "bottom";
} else {
result = "top";
}
return result;
}
KoColumns::SeparatorVerticalAlignment KoColumns::parseSeparatorVerticalAlignment(const QString &value)
{
// default to AlignTop
SeparatorVerticalAlignment result = defaultSeparatorVerticalAlignment;
if (! value.isEmpty()) {
// skip "top", is default
if (value == QLatin1String("middle")) {
result = AlignVCenter;
} else if (value == QLatin1String("bottom")) {
result = AlignBottom;
}
}
return result;
}
QColor KoColumns::parseSeparatorColor(const QString &value)
{
QColor result(value);
if (! result.isValid())
// default is black, cmp. ODF 1.2 §19.467
result = QColor(defaultSeparatorColor);
return result;
}
// TODO: see if there is another parse method somewhere which can be used here
int KoColumns::parseSeparatorHeight(const QString &value)
{
int result = defaultSeparatorHeight;
// only try to convert if it ends with a %, so is also not empty
if (value.endsWith(QLatin1Char('%'))) {
bool ok = false;
// try to convert
result = value.leftRef(value.length()-1).toInt(&ok);
// reset to 100% if conversion failed (which sets result to 0)
if (! ok) {
result = defaultSeparatorHeight;
}
}
return result;
}
KoColumns::SeparatorStyle KoColumns::parseSeparatorStyle(const QString &value)
{
SeparatorStyle result = None;
if (! value.isEmpty()) {
// skip "none", is default
if (value == QLatin1String("solid")) {
result = Solid;
} else if (value == QLatin1String("dotted")) {
result = Dotted;
} else if (value == QLatin1String("dashed")) {
result = Dashed;
} else if (value == QLatin1String("dot-dashed")) {
result = DotDashed;
}
}
return result;
}
int KoColumns::parseRelativeWidth(const QString &value)
{
int result = 0;
// only try to convert if it ends with a *, so is also not empty
if (value.endsWith(QLatin1Char('*'))) {
bool ok = false;
// try to convert
result = value.leftRef(value.length()-1).toInt(&ok);
if (! ok) {
result = 0;
}
}
return result;
}
KoColumns::KoColumns()
: count(defaultColumnCount)
, gapWidth(defaultColumnGapWidth)
, separatorStyle(defaultSeparatorStyle)
, separatorColor(defaultSeparatorColor)
, separatorVerticalAlignment(defaultSeparatorVerticalAlignment)
, separatorHeight(defaultSeparatorHeight)
{
}
void KoColumns::reset()
{
count = defaultColumnCount;
gapWidth = defaultColumnGapWidth;
separatorStyle = defaultSeparatorStyle;
separatorColor = QColor(defaultSeparatorColor);
separatorVerticalAlignment = defaultSeparatorVerticalAlignment;
separatorHeight = defaultSeparatorHeight;
}
bool KoColumns::operator==(const KoColumns& rhs) const {
return
count == rhs.count &&
(columnData.isEmpty() && rhs.columnData.isEmpty() ?
(qAbs(gapWidth - rhs.gapWidth) <= 1E-10) :
(columnData == rhs.columnData));
}
bool KoColumns::operator!=(const KoColumns& rhs) const {
return
count != rhs.count ||
(columnData.isEmpty() && rhs.columnData.isEmpty() ?
qAbs(gapWidth - rhs.gapWidth) > 1E-10 :
! (columnData == rhs.columnData));
}
void KoColumns::loadOdf(const KoXmlElement &style)
{
KoXmlElement columnsElement = KoXml::namedItemNS(style, KoXmlNS::style, "columns");
if (!columnsElement.isNull()) {
count = columnsElement.attributeNS(KoXmlNS::fo, "column-count").toInt();
if (count < 1)
count = 1;
gapWidth = KoUnit::parseValue(columnsElement.attributeNS(KoXmlNS::fo, "column-gap"));
KoXmlElement columnSep = KoXml::namedItemNS(columnsElement, KoXmlNS::style, "column-sep");
if (! columnSep.isNull()) {
separatorStyle = parseSeparatorStyle(columnSep.attributeNS(KoXmlNS::style, "style"));
separatorWidth = KoUnit::parseValue(columnSep.attributeNS(KoXmlNS::style, "width"));
separatorHeight = parseSeparatorHeight(columnSep.attributeNS(KoXmlNS::style, "height"));
separatorColor = parseSeparatorColor(columnSep.attributeNS(KoXmlNS::style, "color"));
separatorVerticalAlignment =
parseSeparatorVerticalAlignment(columnSep.attributeNS(KoXmlNS::style, "vertical-align"));
}
KoXmlElement columnElement;
forEachElement(columnElement, columnsElement) {
if(columnElement.localName() != QLatin1String("column") ||
columnElement.namespaceURI() != KoXmlNS::style)
continue;
ColumnDatum datum;
datum.leftMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "start-indent"), 0.0);
datum.rightMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "end-indent"), 0.0);
datum.topMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-before"), 0.0);
datum.bottomMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-after"), 0.0);
datum.relativeWidth = parseRelativeWidth(columnElement.attributeNS(KoXmlNS::style, "rel-width"));
// on a bad relativeWidth just drop all data
if (datum.relativeWidth <= 0) {
columnData.clear();
break;
}
columnData.append(datum);
}
if (! columnData.isEmpty() && count != columnData.count()) {
warnOdf << "Found not as many <style:column> elements as attribute fo:column-count has set:"<< count;
columnData.clear();
}
} else {
reset();
}
}
void KoColumns::saveOdf(KoGenStyle &style) const
{
if (count > 1) {
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter writer(&buffer);
writer.startElement("style:columns");
writer.addAttribute("fo:column-count", count);
if (columnData.isEmpty()) {
writer.addAttributePt("fo:column-gap", gapWidth);
}
if (separatorStyle != KoColumns::None) {
writer.startElement("style:column-sep");
writer.addAttribute("style:style", separatorStyleString(separatorStyle));
writer.addAttributePt("style:width", separatorWidth);
writer.addAttribute("style:height", QString::number(separatorHeight)+QLatin1Char('%'));
writer.addAttribute("style:color", separatorColor.name());
writer.addAttribute("style:vertical-align", separatorVerticalAlignmentString(separatorVerticalAlignment));
writer.endElement(); // style:column-sep
}
foreach(const ColumnDatum &cd, columnData) {
writer.startElement("style:column");
writer.addAttributePt("fo:start-indent", cd.leftMargin);
writer.addAttributePt("fo:end-indent", cd.rightMargin);
writer.addAttributePt("fo:space-before", cd.topMargin);
writer.addAttributePt("fo:space-after", cd.bottomMargin);
writer.addAttribute("style:rel-width", QString::number(cd.relativeWidth)+QLatin1Char('*'));
writer.endElement(); // style:column
}
writer.endElement(); // style:columns
QString contentElement = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
style.addChildElement("style:columns", contentElement);
}
}
diff --git a/src/libs/odf/KoDocumentBase.cpp b/src/libs/odf/KoDocumentBase.cpp
index cfd4e59c..1b7ee7d7 100644
--- a/src/libs/odf/KoDocumentBase.cpp
+++ b/src/libs/odf/KoDocumentBase.cpp
@@ -1,41 +1,42 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2005 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
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 "KoDocumentBase.h"
#include <QtGlobal>
class Q_DECL_HIDDEN KoDocumentBase::Private {
public:
};
KoDocumentBase::KoDocumentBase()
: d( new Private )
{
}
KoDocumentBase::~KoDocumentBase()
{
delete d;
}
diff --git a/src/libs/odf/KoDocumentInfo.cpp b/src/libs/odf/KoDocumentInfo.cpp
index 349d2763..34c4a6cb 100644
--- a/src/libs/odf/KoDocumentInfo.cpp
+++ b/src/libs/odf/KoDocumentInfo.cpp
@@ -1,466 +1,467 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2004 David Faure <faure@kde.org>
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 "KoDocumentInfo.h"
#include "config.h"
#include "KoDocumentBase.h"
#include "KoOdfWriteStore.h"
#include "KoXmlNS.h"
#include <QDateTime>
#include <KoStoreDevice.h>
#include <KoXmlWriter.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <OdfDebug.h>
#include <klocalizedstring.h>
#include <kuser.h>
#include <kemailsettings.h>
KoDocumentInfo::KoDocumentInfo(QObject *parent) : QObject(parent)
{
m_aboutTags << "title" << "description" << "subject" << "comments"
<< "keyword" << "initial-creator" << "editing-cycles"
<< "date" << "creation-date" << "language";
m_authorTags << "creator" << "initial" << "author-title"
<< "email" << "telephone" << "telephone-work"
<< "fax" << "country" << "postal-code" << "city"
<< "street" << "position" << "company";
setAboutInfo("editing-cycles", "0");
setAboutInfo("initial-creator", i18n("Unknown"));
setAboutInfo("creation-date", QDateTime::currentDateTime()
.toString(Qt::ISODate));
}
KoDocumentInfo::~KoDocumentInfo()
{
}
bool KoDocumentInfo::load(const KoXmlDocument &doc)
{
m_authorInfo.clear();
if (!loadAboutInfo(doc.documentElement()))
return false;
if (!loadAuthorInfo(doc.documentElement()))
return false;
return true;
}
bool KoDocumentInfo::loadOasis(const KoXmlDocument &metaDoc)
{
m_authorInfo.clear();
KoXmlNode t = KoXml::namedItemNS(metaDoc, KoXmlNS::office, "document-meta");
KoXmlNode office = KoXml::namedItemNS(t, KoXmlNS::office, "meta");
if (office.isNull())
return false;
if (!loadOasisAboutInfo(office))
return false;
if (!loadOasisAuthorInfo(office))
return false;
return true;
}
QDomDocument KoDocumentInfo::save(QDomDocument &doc)
{
updateParametersAndBumpNumCycles();
QDomElement s = saveAboutInfo(doc);
if (!s.isNull())
doc.documentElement().appendChild(s);
s = saveAuthorInfo(doc);
if (!s.isNull())
doc.documentElement().appendChild(s);
if (doc.documentElement().isNull())
return QDomDocument();
return doc;
}
bool KoDocumentInfo::saveOasis(KoStore *store)
{
updateParametersAndBumpNumCycles();
KoStoreDevice dev(store);
KoXmlWriter* xmlWriter = KoOdfWriteStore::createOasisXmlWriter(&dev,
"office:document-meta");
xmlWriter->startElement("office:meta");
xmlWriter->startElement("meta:generator");
xmlWriter->addTextNode(QString("Calligra Plan/%1")
.arg(PLAN_VERSION_STRING));
xmlWriter->endElement();
if (!saveOasisAboutInfo(*xmlWriter))
return false;
if (!saveOasisAuthorInfo(*xmlWriter))
return false;
xmlWriter->endElement();
xmlWriter->endElement(); // root element
xmlWriter->endDocument();
delete xmlWriter;
return true;
}
void KoDocumentInfo::setAuthorInfo(const QString &info, const QString &data)
{
if (!m_authorTags.contains(info)) {
return;
}
m_authorInfoOverride.insert(info, data);
}
void KoDocumentInfo::setActiveAuthorInfo(const QString &info, const QString &data)
{
if (!m_authorTags.contains(info)) {
return;
}
if (data.isEmpty()) {
m_authorInfo.remove(info);
} else {
m_authorInfo.insert(info, data);
}
emit infoUpdated(info, data);
}
QString KoDocumentInfo::authorInfo(const QString &info) const
{
if (!m_authorTags.contains(info))
return QString();
return m_authorInfo[ info ];
}
void KoDocumentInfo::setAboutInfo(const QString &info, const QString &data)
{
if (!m_aboutTags.contains(info))
return;
m_aboutInfo.insert(info, data);
emit infoUpdated(info, data);
}
QString KoDocumentInfo::aboutInfo(const QString &info) const
{
if (!m_aboutTags.contains(info)) {
return QString();
}
return m_aboutInfo[info];
}
bool KoDocumentInfo::saveOasisAuthorInfo(KoXmlWriter &xmlWriter)
{
foreach(const QString & tag, m_authorTags) {
if (!authorInfo(tag).isEmpty() && tag == "creator") {
xmlWriter.startElement("dc:creator");
xmlWriter.addTextNode(authorInfo("creator"));
xmlWriter.endElement();
} else if (!authorInfo(tag).isEmpty()) {
xmlWriter.startElement("meta:user-defined");
xmlWriter.addAttribute("meta:name", tag);
xmlWriter.addTextNode(authorInfo(tag));
xmlWriter.endElement();
}
}
return true;
}
bool KoDocumentInfo::loadOasisAuthorInfo(const KoXmlNode &metaDoc)
{
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, "creator");
if (!e.isNull() && !e.text().isEmpty())
setActiveAuthorInfo("creator", e.text());
KoXmlNode n = metaDoc.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (!n.isElement())
continue;
KoXmlElement e = n.toElement();
if (!(e.namespaceURI() == KoXmlNS::meta &&
e.localName() == "user-defined" && !e.text().isEmpty()))
continue;
QString name = e.attributeNS(KoXmlNS::meta, "name", QString());
setActiveAuthorInfo(name, e.text());
}
return true;
}
bool KoDocumentInfo::loadAuthorInfo(const KoXmlElement &e)
{
KoXmlNode n = e.namedItem("author").firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
KoXmlElement e = n.toElement();
if (e.isNull())
continue;
if (e.tagName() == "full-name")
setActiveAuthorInfo("creator", e.text().trimmed());
else
setActiveAuthorInfo(e.tagName(), e.text().trimmed());
}
return true;
}
QDomElement KoDocumentInfo::saveAuthorInfo(QDomDocument &doc)
{
QDomElement e = doc.createElement("author");
QDomElement t;
foreach(const QString &tag, m_authorTags) {
if (tag == "creator")
t = doc.createElement("full-name");
else
t = doc.createElement(tag);
e.appendChild(t);
t.appendChild(doc.createTextNode(authorInfo(tag)));
}
return e;
}
bool KoDocumentInfo::saveOasisAboutInfo(KoXmlWriter &xmlWriter)
{
foreach(const QString &tag, m_aboutTags) {
if (!aboutInfo(tag).isEmpty() || tag == "title") {
if (tag == "keyword") {
foreach(const QString & tmp, aboutInfo("keyword").split(';')) {
xmlWriter.startElement("meta:keyword");
xmlWriter.addTextNode(tmp);
xmlWriter.endElement();
}
} else if (tag == "title" || tag == "description" || tag == "subject" ||
tag == "date" || tag == "language") {
QByteArray elementName(QString("dc:" + tag).toLatin1());
xmlWriter.startElement(elementName.constData());
xmlWriter.addTextNode(aboutInfo(tag));
xmlWriter.endElement();
} else {
QByteArray elementName(QString("meta:" + tag).toLatin1());
xmlWriter.startElement(elementName.constData());
xmlWriter.addTextNode(aboutInfo(tag));
xmlWriter.endElement();
}
}
}
return true;
}
bool KoDocumentInfo::loadOasisAboutInfo(const KoXmlNode &metaDoc)
{
QStringList keywords;
KoXmlElement e;
forEachElement(e, metaDoc) {
QString tag(e.localName());
if (! m_aboutTags.contains(tag) && tag != "generator") {
continue;
}
//debugOdf<<"localName="<<e.localName();
if (tag == "keyword") {
if (!e.text().isEmpty())
keywords << e.text().trimmed();
} else if (tag == "description") {
//this is the odf way but add meta:comment if is already loaded
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo("description", aboutInfo("description") + e.text().trimmed());
} else if (tag == "comments") {
//this was the old way so add it to dc:description
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::meta, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo("description", aboutInfo("description") + e.text().trimmed());
} else if (tag == "title"|| tag == "subject"
|| tag == "date" || tag == "language") {
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo(tag, e.text().trimmed());
} else if (tag == "generator") {
setOriginalGenerator(e.text().trimmed());
} else {
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::meta, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo(tag, e.text().trimmed());
}
}
if (keywords.count() > 0) {
setAboutInfo("keyword", keywords.join(", "));
}
return true;
}
bool KoDocumentInfo::loadAboutInfo(const KoXmlElement &e)
{
KoXmlNode n = e.namedItem("about").firstChild();
KoXmlElement tmp;
for (; !n.isNull(); n = n.nextSibling()) {
tmp = n.toElement();
if (tmp.isNull())
continue;
if (tmp.tagName() == "abstract")
setAboutInfo("comments", tmp.text());
setAboutInfo(tmp.tagName(), tmp.text());
}
return true;
}
QDomElement KoDocumentInfo::saveAboutInfo(QDomDocument &doc)
{
QDomElement e = doc.createElement("about");
QDomElement t;
foreach(const QString &tag, m_aboutTags) {
if (tag == "comments") {
t = doc.createElement("abstract");
e.appendChild(t);
t.appendChild(doc.createCDATASection(aboutInfo(tag)));
} else {
t = doc.createElement(tag);
e.appendChild(t);
t.appendChild(doc.createTextNode(aboutInfo(tag)));
}
}
return e;
}
void KoDocumentInfo::updateParametersAndBumpNumCycles()
{
KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent());
if (doc && doc->isAutosaving()) {
return;
}
setAboutInfo("editing-cycles", QString::number(aboutInfo("editing-cycles").toInt() + 1));
setAboutInfo("date", QDateTime::currentDateTime().toString(Qt::ISODate));
updateParameters();
}
void KoDocumentInfo::updateParameters()
{
KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent());
if (doc && (!doc->isModified() && !doc->isEmpty())) {
return;
}
KConfig config("calligrarc");
config.reparseConfiguration();
KConfigGroup authorGroup(&config, "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
config.reparseConfiguration();
KConfigGroup appAuthorGroup(&config, "Author");
QString profile = appAuthorGroup.readEntry("active-profile", "");
if (profiles.contains(profile)) {
KConfigGroup cgs(&authorGroup, "Author-" + profile);
setActiveAuthorInfo("creator", cgs.readEntry("creator"));
setActiveAuthorInfo("initial", cgs.readEntry("initial"));
setActiveAuthorInfo("author-title", cgs.readEntry("author-title"));
setActiveAuthorInfo("email", cgs.readEntry("email"));
setActiveAuthorInfo("telephone", cgs.readEntry("telephone"));
setActiveAuthorInfo("telephone-work", cgs.readEntry("telephone-work"));
setActiveAuthorInfo("fax", cgs.readEntry("fax"));
setActiveAuthorInfo("country",cgs.readEntry("country"));
setActiveAuthorInfo("postal-code",cgs.readEntry("postal-code"));
setActiveAuthorInfo("city", cgs.readEntry("city"));
setActiveAuthorInfo("street", cgs.readEntry("street"));
setActiveAuthorInfo("position", cgs.readEntry("position"));
setActiveAuthorInfo("company", cgs.readEntry("company"));
} else {
if (profile == "anonymous") {
setActiveAuthorInfo("creator", QString());
setActiveAuthorInfo("telephone", QString());
setActiveAuthorInfo("telephone-work", QString());
setActiveAuthorInfo("email", QString());
} else {
KUser user(KUser::UseRealUserID);
setActiveAuthorInfo("creator", user.property(KUser::FullName).toString());
setActiveAuthorInfo("telephone-work", user.property(KUser::WorkPhone).toString());
setActiveAuthorInfo("telephone", user.property(KUser::HomePhone).toString());
KEMailSettings eMailSettings;
setActiveAuthorInfo("email", eMailSettings.getSetting(KEMailSettings::EmailAddress));
}
setActiveAuthorInfo("initial", "");
setActiveAuthorInfo("author-title", "");
setActiveAuthorInfo("fax", "");
setActiveAuthorInfo("country", "");
setActiveAuthorInfo("postal-code", "");
setActiveAuthorInfo("city", "");
setActiveAuthorInfo("street", "");
setActiveAuthorInfo("position", "");
setActiveAuthorInfo("company", "");
}
//alllow author info set programatically to override info from author profile
foreach(const QString &tag, m_authorTags) {
if (m_authorInfoOverride.contains(tag)) {
setActiveAuthorInfo(tag, m_authorInfoOverride.value(tag));
}
}
}
void KoDocumentInfo::resetMetaData()
{
setAboutInfo("editing-cycles", QString::number(0));
setAboutInfo("initial-creator", authorInfo("creator"));
setAboutInfo("creation-date", QDateTime::currentDateTime().toString(Qt::ISODate));
}
QString KoDocumentInfo::originalGenerator() const
{
return m_generator;
}
void KoDocumentInfo::setOriginalGenerator(const QString &generator)
{
m_generator = generator;
}
diff --git a/src/libs/odf/KoElementReference.cpp b/src/libs/odf/KoElementReference.cpp
index d8b39824..5e13649a 100644
--- a/src/libs/odf/KoElementReference.cpp
+++ b/src/libs/odf/KoElementReference.cpp
@@ -1,114 +1,115 @@
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "KoElementReference.h"
#include "KoXmlReader.h"
#include "KoXmlWriter.h"
#include <KoXmlNS.h>
KoElementReference::KoElementReference()
: d(new KoElementReferenceData())
{
d->xmlid = "id-" + d->xmlid;
}
KoElementReference::KoElementReference(const QString &prefix)
: d(new KoElementReferenceData)
{
d->xmlid = prefix + "-" + d->xmlid;
}
KoElementReference::KoElementReference(const QString &prefix, int counter)
: d(new KoElementReferenceData)
{
d->xmlid = QString("%1-%2").arg(prefix).arg(counter);
}
KoElementReference::KoElementReference(const KoElementReference &other)
: d(other.d)
{
}
KoElementReference &KoElementReference::operator=(const KoElementReference &rhs)
{
if (this == &rhs) return *this;
d = rhs.d;
return *this;
}
bool KoElementReference::operator==(const KoElementReference &other) const
{
return d->xmlid == other.d->xmlid;
}
bool KoElementReference::operator!=(const KoElementReference &other) const
{
return !(*this == other);
}
bool KoElementReference::isValid() const
{
return (!d->xmlid.isEmpty());
}
void KoElementReference::saveOdf(KoXmlWriter *writer, SaveOption saveOptions) const
{
if (d->xmlid.isEmpty()) return;
writer->addAttribute("xml:id", d->xmlid);
if (saveOptions & DrawId) {
writer->addAttribute("draw:id", d->xmlid);
}
if (saveOptions & TextId) {
writer->addAttribute("text:id", d->xmlid);
}
}
QString KoElementReference::toString() const
{
return d->xmlid;
}
KoElementReference KoElementReference::loadOdf(const KoXmlElement &element)
{
QString xmlid;
if (element.hasAttributeNS(KoXmlNS::xml, "id")) {
xmlid = element.attributeNS(KoXmlNS::xml, "id");
}
else if (element.hasAttributeNS(KoXmlNS::draw, "id")) {
xmlid = element.attributeNS(KoXmlNS::draw, "id");
}
else if (element.hasAttributeNS(KoXmlNS::text, "id")) {
xmlid = element.attributeNS(KoXmlNS::text, "id");
}
d->xmlid = xmlid;
return *this;
}
void KoElementReference::invalidate()
{
d->xmlid.clear();
}
diff --git a/src/libs/odf/KoEmbeddedDocumentSaver.cpp b/src/libs/odf/KoEmbeddedDocumentSaver.cpp
index 8983b2fb..70d851f4 100644
--- a/src/libs/odf/KoEmbeddedDocumentSaver.cpp
+++ b/src/libs/odf/KoEmbeddedDocumentSaver.cpp
@@ -1,275 +1,276 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2010 Thomas Zander <zander@kde.org>
Copyright (C) 2011 Inge Wallin <inge@lysator.liu.se>
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 "KoEmbeddedDocumentSaver.h"
#include <QList>
#include <OdfDebug.h>
#include <QUrl>
#include <KoStore.h>
#include <KoXmlWriter.h>
#include <KoOdfWriteStore.h>
#include "KoDocumentBase.h"
#include <KoOdfManifestEntry.h>
#define INTERNAL_PROTOCOL "intern"
struct FileEntry {
QString path;
QByteArray mimeType; // QBA because this is what addManifestEntry wants
QByteArray contents;
};
class Q_DECL_HIDDEN KoEmbeddedDocumentSaver::Private
{
public:
Private() {}
QHash<QString, int> prefixes; // Used in getFilename();
// These will be saved when saveEmbeddedDocuments() is called.
QList<KoDocumentBase*> documents; // Embedded documents
QList<FileEntry*> files; // Embedded files.
QList<KoOdfManifestEntry*> manifestEntries;
};
KoEmbeddedDocumentSaver::KoEmbeddedDocumentSaver()
: d(new Private())
{
}
KoEmbeddedDocumentSaver::~KoEmbeddedDocumentSaver()
{
qDeleteAll(d->files);
qDeleteAll(d->manifestEntries);
delete d;
}
QString KoEmbeddedDocumentSaver::getFilename(const QString &prefix)
{
int index = 1;
if (d->prefixes.contains(prefix)) {
index = d->prefixes.value(prefix);
}
// This inserts prefix into the map if it's not there.
d->prefixes[prefix] = index + 1;
//return prefix + QString("%1").arg(index, 4, 10, QChar('0'));
return prefix + QString("%1").arg(index);
}
void KoEmbeddedDocumentSaver::embedDocument(KoXmlWriter &writer, KoDocumentBase * doc)
{
Q_ASSERT(doc);
d->documents.append(doc);
QString ref;
if (!doc->isStoredExtern()) {
const QString name = getFilename("Object ");
// set URL in document so that saveEmbeddedDocuments will save
// the actual embedded object with the right name in the store.
QUrl u;
u.setScheme(INTERNAL_PROTOCOL);
u.setPath(name);
debugOdf << u;
doc->setUrl(u);
ref = "./" + name;
} else {
ref = doc->url().url();
}
debugOdf << "saving reference to embedded document as" << ref;
writer.addAttribute("xlink:href", /*"#" + */ref);
//<draw:object xlink:type="simple" xlink:show="embed"
// xlink:actuate="onLoad" xlink:href="#./Object 1"/>
writer.addAttribute("xlink:type", "simple");
writer.addAttribute("xlink:show", "embed");
writer.addAttribute("xlink:actuate", "onLoad");
}
// Examples:
// Videos/Video1.mov ← the number is autogenerated
// Videos/Video2.mov
// Object1/foo ← the number is autogenerated
// Object1/bar
// Note: The contents QByteArray is implicitly shared. It needs to be
// copied since otherwise the actual array may disappear before
// the real saving is done.
//
void KoEmbeddedDocumentSaver::embedFile(KoXmlWriter &writer, const char *element,
const QString &path, const QByteArray &mimeType,
const QByteArray &contents)
{
// Put the file in the list of files to be written to the store later.
FileEntry *entry = new FileEntry;
entry->mimeType = mimeType;
entry->path = path;
entry->contents = contents;
d->files.append(entry);
writer.startElement(element);
// Write the attributes that refer to the file.
//<draw:object xlink:href="#./Object 1" xlink:type="simple" xlink:show="embed"
// xlink:actuate="onLoad"/>
writer.addAttribute("xlink:type", "simple");
writer.addAttribute("xlink:show", "embed");
writer.addAttribute("xlink:actuate", "onLoad");
debugOdf << "saving reference to embedded file as" << path;
writer.addAttribute("xlink:href", path);
writer.endElement();
}
void KoEmbeddedDocumentSaver::saveFile(const QString &path, const QByteArray &mimeType,
const QByteArray &contents)
{
// Put the file in the list of files to be written to the store later.
FileEntry *entry = new FileEntry;
entry->mimeType = mimeType;
entry->path = path;
entry->contents = contents;
d->files.append(entry);
debugOdf << "saving reference to embedded file as" << path;
}
/**
*
*/
void KoEmbeddedDocumentSaver::saveManifestEntry(const QString &fullPath, const QString &mediaType,
const QString &version)
{
d->manifestEntries.append(new KoOdfManifestEntry(fullPath, mediaType, version));
}
bool KoEmbeddedDocumentSaver::saveEmbeddedDocuments(KoDocumentBase::SavingContext & documentContext)
{
KoStore *store = documentContext.odfStore.store();
// Write embedded documents.
foreach(KoDocumentBase *doc, d->documents) {
QString path;
if (doc->isStoredExtern()) {
debugOdf << " external (don't save) url:" << doc->url().url();
path = doc->url().url();
} else {
// The name comes from addEmbeddedDocument (which was set while saving the document).
Q_ASSERT(doc->url().scheme() == INTERNAL_PROTOCOL);
const QString name = doc->url().path();
debugOdf << "saving" << name;
if (doc->nativeOasisMimeType().isEmpty()) {
// Embedded object doesn't support OpenDocument, save in the old format.
debugOdf << "Embedded object doesn't support OpenDocument, save in the old format.";
if (!doc->saveToStore(store, name)) {
return false;
}
} else {
// To make the children happy cd to the correct directory
store->pushDirectory();
store->enterDirectory(name);
bool ok = doc->saveOdf(documentContext);
// Now that we're done leave the directory again
store->popDirectory();
if (!ok) {
warnOdf << "KoEmbeddedDocumentSaver::saveEmbeddedDocuments failed";
return false;
}
}
Q_ASSERT(doc->url().scheme() == INTERNAL_PROTOCOL);
path = store->currentPath();
if (!path.isEmpty()) {
path += '/';
}
path += doc->url().path();
if (path.startsWith(QLatin1Char('/'))) {
path.remove(0, 1); // remove leading '/', no wanted in manifest
}
}
// OOo uses a trailing slash for the path to embedded objects (== directories)
if (!path.endsWith('/')) {
path += '/';
}
QByteArray mimetype = doc->nativeOasisMimeType();
documentContext.odfStore.manifestWriter()->addManifestEntry(path, mimetype);
}
// Write the embedded files.
foreach(FileEntry *entry, d->files) {
QString path = entry->path;
debugOdf << "saving" << path;
// To make the children happy cd to the correct directory
store->pushDirectory();
int index = path.lastIndexOf('/');
const QString dirPath = path.left(index);
const QString fileName = path.right(path.size() - index - 1);
store->enterDirectory(dirPath);
if (!store->open(fileName)) {
return false;
}
store->write(entry->contents);
store->close();
// Now that we're done leave the directory again
store->popDirectory();
// Create the manifest entry.
if (path.startsWith(QLatin1String("./"))) {
path.remove(0, 2); // remove leading './', not wanted in manifest
}
documentContext.odfStore.manifestWriter()->addManifestEntry(path, entry->mimeType);
}
// Write the manifest entries.
KoXmlWriter *manifestWriter = documentContext.odfStore.manifestWriter();
foreach(KoOdfManifestEntry *entry, d->manifestEntries) {
manifestWriter->startElement("manifest:file-entry");
manifestWriter->addAttribute("manifest:version", entry->version());
manifestWriter->addAttribute("manifest:media-type", entry->mediaType());
manifestWriter->addAttribute("manifest:full-path", entry->fullPath());
manifestWriter->endElement(); // manifest:file-entry
}
return true;
}
diff --git a/src/libs/odf/KoFontFace.cpp b/src/libs/odf/KoFontFace.cpp
index c71bfe58..34034e6e 100644
--- a/src/libs/odf/KoFontFace.cpp
+++ b/src/libs/odf/KoFontFace.cpp
@@ -1,153 +1,154 @@
/* This file is part of the KDE project
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
Contact: Suresh Chande suresh.chande@nokia.com
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 "KoFontFace.h"
#include <KoXmlWriter.h>
#include <OdfDebug.h>
class KoFontFacePrivate : public QSharedData
{
public:
KoFontFacePrivate(const QString &_name)
: name(_name), pitch(KoFontFace::VariablePitch)
{
}
~KoFontFacePrivate()
{
}
void saveOdf(KoXmlWriter* xmlWriter) const
{
xmlWriter->startElement("style:font-face");
xmlWriter->addAttribute("style:name", name);
xmlWriter->addAttribute("svg:font-family", family.isEmpty() ? name : family);
if (!familyGeneric.isEmpty())
xmlWriter->addAttribute("style:font-family-generic", familyGeneric);
if (!style.isEmpty())
xmlWriter->addAttribute("svg:font-style", style);
xmlWriter->addAttribute("style:font-pitch", pitch == KoFontFace::FixedPitch ? "fixed" : "variable");
xmlWriter->endElement(); // style:font-face
}
QString name; //!< for style:name attribute
QString family; //!< for svg:font-family attribute
QString familyGeneric; //!< for style:font-family-generic attribute
QString style; //!< for svg:font-style attribute
KoFontFace::Pitch pitch; //!< for style:font-pitch attribute
};
KoFontFace::KoFontFace(const QString &_name)
: d(new KoFontFacePrivate(_name))
{
}
KoFontFace::KoFontFace(const KoFontFace &other)
: d(other.d)
{
}
KoFontFace::~KoFontFace()
{
}
KoFontFace &KoFontFace::operator=(const KoFontFace &other)
{
d = other.d;
return *this;
}
bool KoFontFace::operator==(const KoFontFace &other) const
{
if (isNull() && other.isNull())
return true;
return d.data() == other.d.data();
}
bool KoFontFace::isNull() const
{
return d->name.isEmpty();
}
QString KoFontFace::name() const
{
return d->name;
}
void KoFontFace::setName(const QString &name)
{
d->name = name;
}
QString KoFontFace::family() const
{
return d->family;
}
void KoFontFace::setFamily(const QString &family)
{
d->family = family;
}
QString KoFontFace::familyGeneric() const
{
return d->familyGeneric;
}
void KoFontFace::setFamilyGeneric(const QString &familyGeneric)
{
if (familyGeneric == "decorative" || familyGeneric == "modern"
|| familyGeneric == "roman" || familyGeneric == "script"
|| familyGeneric == "swiss" || familyGeneric == "system") {
d->familyGeneric = familyGeneric;
}
}
QString KoFontFace::style() const
{
return d->style;
}
void KoFontFace::setStyle(const QString &style)
{
d->style = style;
}
KoFontFace::Pitch KoFontFace::pitch() const
{
return d->pitch;
}
void KoFontFace::setPitch(KoFontFace::Pitch pitch)
{
d->pitch = pitch;
}
void KoFontFace::saveOdf(KoXmlWriter* xmlWriter) const
{
Q_ASSERT(!isNull());
if (isNull()) {
warnOdf << "This font face is null and will not be saved: set at least the name";
return;
}
d->saveOdf(xmlWriter);
}
diff --git a/src/libs/odf/KoGenChange.cpp b/src/libs/odf/KoGenChange.cpp
index 9926611e..8cccc80a 100644
--- a/src/libs/odf/KoGenChange.cpp
+++ b/src/libs/odf/KoGenChange.cpp
@@ -1,146 +1,147 @@
/* This file is part of the KDE project
Copyright (C) 2008 Pierre Stirnweiss <pierre.stirnweiss_calligra@gadz.org>
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 "KoGenChange.h"
#include <KoXmlWriter.h>
#include <QDateTime>
#include <OdfDebug.h>
// Returns -1, 0 (equal) or 1
static int compareMap(const QMap<QString, QString> &map1, const QMap<QString, QString> &map2)
{
QMap<QString, QString>::const_iterator it = map1.begin();
QMap<QString, QString>::const_iterator oit = map2.begin();
for (; it != map1.end(); ++it, ++oit) { // both maps have been checked for size already
if (it.key() != oit.key())
return it.key() < oit.key() ? -1 : + 1;
if (it.value() != oit.value())
return it.value() < oit.value() ? -1 : + 1;
}
return 0; // equal
}
KoGenChange::KoGenChange(KoGenChange::ChangeFormat changeFormat)
: m_changeFormat(changeFormat)
, m_type(UNKNOWN)
{
}
KoGenChange::~KoGenChange()
{
}
void KoGenChange::writeChangeMetaData(KoXmlWriter* writer) const
{
QMap<QString, QString>::const_iterator it = m_changeMetaData.begin();
const QMap<QString, QString>::const_iterator end = m_changeMetaData.end();
for (; it != end; ++it) {
//FIXME: if the propName is passed directly as it.key().toUtf8(), the opening tag is correct but the closing tag becomes undefined
//FIXME: example: <dc-creator>.......</`ok>
if (it.key() == "dc-creator") {
writer->startElement("dc:creator");
writer->addTextNode(it.value());
writer->endElement();
}
if (it.key() == "dc-date") {
writer->startElement("dc:date");
writer->addTextNode(it.value());
writer->endElement();
}
}
}
void KoGenChange::writeChange(KoXmlWriter *writer, const QString &name) const
{
if (m_changeFormat == KoGenChange::ODF_1_2) {
writeODF12Change(writer, name);
} else {
writeDeltaXmlChange(writer, name);
}
}
void KoGenChange::writeODF12Change(KoXmlWriter *writer, const QString &name) const
{
writer->startElement("text:changed-region");
writer->addAttribute("text:id", name);
writer->addAttribute("xml:id", name);
const char* elementName;
switch (m_type) {
case KoGenChange::DeleteChange:
elementName = "text:deletion";
break;
case KoGenChange::FormatChange:
elementName = "text:format-change";
break;
case KoGenChange::InsertChange:
elementName = "text:insertion";
break;
default:
elementName = "text:format-change"; //should not happen, format-change is probably the most harmless of the three.
}
writer->startElement(elementName);
if (!m_changeMetaData.isEmpty()) {
writer->startElement("office:change-info");
writeChangeMetaData(writer);
if (m_literalData.contains("changeMetaData"))
writer->addCompleteElement(m_literalData.value("changeMetaData").toUtf8());
writer->endElement(); // office:change-info
}
if ((m_type == KoGenChange::DeleteChange) && m_literalData.contains("deleteChangeXml"))
writer->addCompleteElement(m_literalData.value("deleteChangeXml").toUtf8());
writer->endElement(); // text:insertion/format/deletion
writer->endElement(); // text:change
}
void KoGenChange::writeDeltaXmlChange(KoXmlWriter *writer, const QString &name) const
{
writer->startElement("delta:change-transaction");
writer->addAttribute("delta:change-id", name);
if (!m_changeMetaData.isEmpty()) {
writer->startElement("delta:change-info");
writeChangeMetaData(writer);
writer->endElement(); // delta:change-info
}
writer->endElement(); // delta:change-transaction
}
bool KoGenChange::operator<(const KoGenChange &other) const
{
Q_UNUSED(other);
// if (m_changeMetaData.value("dc-date") != other.m_changeMetaData.value("dc-date")) return QDateTime::fromString(m_changeMetaData.value("dc-date"), Qt::ISODate) < QDateTime::fromString(other.m_changeMetaData.value("dc-date"), Qt::ISODate);
return true;
}
bool KoGenChange::operator==(const KoGenChange &other) const
{
if (m_type != other.m_type) return false;
if (m_changeMetaData.count() != other.m_changeMetaData.count()) return false;
if (m_literalData.count() != other.m_literalData.count()) return false;
int comp = compareMap(m_changeMetaData, other.m_changeMetaData);
if (comp != 0) return false;
return (compareMap(m_literalData, other.m_literalData) == 0);
}
diff --git a/src/libs/odf/KoGenChanges.cpp b/src/libs/odf/KoGenChanges.cpp
index 0b2f7599..af721a06 100644
--- a/src/libs/odf/KoGenChanges.cpp
+++ b/src/libs/odf/KoGenChanges.cpp
@@ -1,113 +1,114 @@
/* This file is part of the KDE project
Copyright (C) 2008 Pierre Stirnweiss <pierre.stirnweiss_calligra@gadz.org>
Copyright (C) 2010 Thomas Zander <zander@kde.org>
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 "KoGenChanges.h"
#include <KoXmlWriter.h>
#include <KoElementReference.h>
#include <QList>
#include <QMap>
#include <QString>
#include <OdfDebug.h>
class Q_DECL_HIDDEN KoGenChanges::Private
{
public:
Private(KoGenChanges *q)
: q(q)
{ }
struct NamedChange {
const KoGenChange* change; ///< @note owned by the collection
QString name;
};
/// style definition -> name
QMap<KoGenChange, QString> changeMap;
/// List of styles (used to preserve ordering)
QList<NamedChange> changeArray;
QMap<KoGenChange, QString> ::iterator insertChange(const KoGenChange &change);
KoGenChanges *q;
};
KoGenChanges::KoGenChanges()
: d(new Private(this))
{
}
KoGenChanges::~KoGenChanges()
{
delete d;
}
QString KoGenChanges::insert(const KoGenChange& change)
{
QMap<KoGenChange, QString> ::iterator it = d->changeMap.find(change);
if (it == d->changeMap.end()) {
it = d->insertChange(change);
}
return it.value();
}
QMap<KoGenChange, QString>::iterator KoGenChanges::Private::insertChange(const KoGenChange &change)
{
QString changeName;
switch (change.type()) {
case KoGenChange::InsertChange: changeName = 'I'; break;
case KoGenChange::FormatChange: changeName = 'F'; break;
case KoGenChange::DeleteChange: changeName = 'D'; break;
default:
changeName = 'C';
}
KoElementReference ref(changeName);
changeName = ref.toString();
QMap<KoGenChange, QString>::iterator it = changeMap.insert(change, changeName);
NamedChange s;
s.change = &it.key();
s.name = changeName;
changeArray.append(s);
return it;
}
void KoGenChanges::saveOdfChanges(KoXmlWriter* xmlWriter, bool trackChanges) const
{
QMap<KoGenChange, QString>::const_iterator it = d->changeMap.constBegin();
if ((it != d->changeMap.constEnd()) && (it.key().changeFormat() == KoGenChange::DELTAXML)) {
xmlWriter->startElement("delta:tracked-changes");
} else {
xmlWriter->startElement("text:tracked-changes");
xmlWriter->addAttribute("text:track-changes", trackChanges);
}
for (; it != d->changeMap.constEnd() ; ++it) {
KoGenChange change = it.key();
change.writeChange(xmlWriter, it.value());
}
xmlWriter->endElement(); // text:tracked-changes
}
diff --git a/src/libs/odf/KoGenStyle.cpp b/src/libs/odf/KoGenStyle.cpp
index f9b9fbff..4c84fde4 100644
--- a/src/libs/odf/KoGenStyle.cpp
+++ b/src/libs/odf/KoGenStyle.cpp
@@ -1,523 +1,524 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2010 Jarosław Staniek <staniek@kde.org>
Copyright (C) 2011 Pierre Ducroquet <pinaraf@pinaraf.info>
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 "KoGenStyle.h"
#include "KoGenStyles.h"
#include <QTextLength>
#include <KoXmlWriter.h>
#include <float.h>
#include <OdfDebug.h>
// Returns -1, 0 (equal) or 1
static int compareMap(const QMap<QString, QString>& map1, const QMap<QString, QString>& map2)
{
QMap<QString, QString>::const_iterator it = map1.constBegin();
QMap<QString, QString>::const_iterator oit = map2.constBegin();
for (; it != map1.constEnd(); ++it, ++oit) { // both maps have been checked for size already
if (it.key() != oit.key())
return it.key() < oit.key() ? -1 : + 1;
if (it.value() != oit.value())
return it.value() < oit.value() ? -1 : + 1;
}
return 0; // equal
}
KoGenStyle::KoGenStyle(Type type, const char* familyName,
const QString& parentName)
: m_type(type), m_familyName(familyName), m_parentName(parentName),
m_autoStyleInStylesDotXml(false), m_defaultStyle(false)
{
switch (type) {
case TextStyle:
case TextAutoStyle:
m_propertyType = TextType;
break;
case ParagraphStyle:
case ParagraphAutoStyle:
m_propertyType = ParagraphType;
break;
case GraphicStyle:
case GraphicAutoStyle:
m_propertyType = GraphicType;
break;
case SectionStyle:
case SectionAutoStyle:
m_propertyType = SectionType;
break;
case RubyStyle:
case RubyAutoStyle:
m_propertyType = RubyType;
break;
case TableStyle:
case TableAutoStyle:
m_propertyType = TableType;
break;
case TableColumnStyle:
case TableColumnAutoStyle:
m_propertyType = TableColumnType;
break;
case TableRowStyle:
case TableRowAutoStyle:
m_propertyType = TableRowType;
break;
case TableCellStyle:
case TableCellAutoStyle:
m_propertyType = TableCellType;
break;
case PresentationStyle:
case PresentationAutoStyle:
m_propertyType = PresentationType;
break;
case DrawingPageStyle:
case DrawingPageAutoStyle:
m_propertyType = DrawingPageType;
break;
case ChartStyle:
case ChartAutoStyle:
m_propertyType = ChartType;
break;
default:
m_propertyType = DefaultType;
break;
}
}
KoGenStyle::~KoGenStyle()
{
}
/*
* The order of this list is important; e.g. a graphic-properties must
* precede a text-properties always. See the Relax NG to check the order.
*/
static const KoGenStyle::PropertyType s_propertyTypes[] = {
KoGenStyle::DefaultType,
KoGenStyle::SectionType,
KoGenStyle::RubyType,
KoGenStyle::TableType,
KoGenStyle::TableColumnType,
KoGenStyle::TableRowType,
KoGenStyle::TableCellType,
KoGenStyle::DrawingPageType,
KoGenStyle::ChartType,
KoGenStyle::GraphicType,
KoGenStyle::ParagraphType,
KoGenStyle::TextType,
};
static const char* const s_propertyNames[] = {
0,
"style:section-properties",
"style:ruby-properties",
"style:table-properties",
"style:table-column-properties",
"style:table-row-properties",
"style:table-cell-properties",
"style:drawing-page-properties",
"style:chart-properties",
"style:graphic-properties",
"style:paragraph-properties",
"style:text-properties"
};
static const int s_propertyNamesCount = sizeof(s_propertyNames) / sizeof(*s_propertyNames);
static KoGenStyle::PropertyType propertyTypeByElementName(const char* propertiesElementName)
{
for (int i = 0; i < s_propertyNamesCount; ++i) {
if (qstrcmp(s_propertyNames[i], propertiesElementName) == 0) {
return s_propertyTypes[i];
}
}
return KoGenStyle::DefaultType;
}
void KoGenStyle::writeStyleProperties(KoXmlWriter* writer, PropertyType type,
const KoGenStyle* parentStyle) const
{
const char* elementName = 0;
for (int i=0; i<s_propertyNamesCount; ++i) {
if (s_propertyTypes[i] == type) {
elementName = s_propertyNames[i];
}
}
Q_ASSERT(elementName);
const StyleMap& map = m_properties[type];
const StyleMap& mapChild = m_childProperties[type];
if (!map.isEmpty() || !mapChild.isEmpty()) {
writer->startElement(elementName);
QMap<QString, QString>::const_iterator it = map.constBegin();
const QMap<QString, QString>::const_iterator end = map.constEnd();
for (; it != end; ++it) {
if (!parentStyle || parentStyle->property(it.key(), type) != it.value())
writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
}
QMap<QString, QString>::const_iterator itChild = mapChild.constBegin();
const QMap<QString, QString>::const_iterator endChild = mapChild.constEnd();
for (; itChild != endChild; ++itChild) {
if (!parentStyle || parentStyle->childProperty(itChild.key(), type) != itChild.value())
writer->addCompleteElement(itChild.value().toUtf8());
}
writer->endElement();
}
}
void KoGenStyle::writeStyle(KoXmlWriter* writer, const KoGenStyles& styles, const char* elementName, const QString& name, const char* propertiesElementName, bool closeElement, bool drawElement) const
{
//debugOdf <<"writing out style" << name <<" display-name=" << m_attributes["style:display-name"] <<" family=" << m_familyName;
writer->startElement(elementName);
const KoGenStyle* parentStyle = 0;
if (!m_defaultStyle) {
if (!drawElement)
writer->addAttribute("style:name", name);
else
writer->addAttribute("draw:name", name);
if (!m_parentName.isEmpty()) {
Q_ASSERT(!m_familyName.isEmpty());
parentStyle = styles.style(m_parentName, m_familyName);
if (parentStyle && m_familyName.isEmpty()) {
// get family from parent style, just in case
// Note: this is saving code, don't convert to attributeNS!
const_cast<KoGenStyle *>(this)->
m_familyName = parentStyle->attribute("style:family").toLatin1();
//debugOdf <<"Got familyname" << m_familyName <<" from parent";
}
if (parentStyle && !parentStyle->isDefaultStyle())
writer->addAttribute("style:parent-style-name", m_parentName);
}
} else { // default-style
Q_ASSERT(qstrcmp(elementName, "style:default-style") == 0);
Q_ASSERT(m_parentName.isEmpty());
}
if (!m_familyName.isEmpty())
const_cast<KoGenStyle *>(this)->
addAttribute("style:family", QString::fromLatin1(m_familyName));
else {
if (qstrcmp(elementName, "style:style") == 0)
warnOdf << "User style " << name << " is without family - invalid. m_type=" << m_type;
}
#if 0 // #ifndef NDEBUG
debugOdf << "style:" << name;
printDebug();
if (parentStyle) {
debugOdf << " parent:" << m_parentName;
parentStyle->printDebug();
}
#endif
// Write attributes [which differ from the parent style]
// We only look at the direct parent style because we assume
// that styles are fully specified, i.e. the inheritance is
// only in the final file, not in the caller's code.
QMap<QString, QString>::const_iterator it = m_attributes.constBegin();
for (; it != m_attributes.constEnd(); ++it) {
bool writeit = true;
if (parentStyle && it.key() != "style:family" // always write the family out
&& parentStyle->attribute(it.key()) == it.value())
writeit = false;
if (writeit)
writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
}
bool createPropertiesTag = propertiesElementName && propertiesElementName[0] != '\0';
KoGenStyle::PropertyType i = KoGenStyle::DefaultType;
KoGenStyle::PropertyType defaultPropertyType = KoGenStyle::DefaultType;
if (createPropertiesTag)
defaultPropertyType = propertyTypeByElementName(propertiesElementName);
if (!m_properties[i].isEmpty() ||
!m_childProperties[defaultPropertyType].isEmpty() ||
!m_properties[defaultPropertyType].isEmpty()) {
if (createPropertiesTag)
writer->startElement(propertiesElementName); // e.g. paragraph-properties
it = m_properties[i].constBegin();
for (; it != m_properties[i].constEnd(); ++it) {
if (!parentStyle || parentStyle->property(it.key(), i) != it.value())
writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
}
//write the explicitly-defined properties that are the same type as the default,
//but only if defaultPropertyType is Text, Paragraph, or GraphicType
if (defaultPropertyType != 0) {
it = m_properties[defaultPropertyType].constBegin();
for (; it != m_properties[defaultPropertyType].constEnd(); ++it) {
if (!parentStyle || parentStyle->property(it .key(), defaultPropertyType) != it.value())
writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
}
}
//write child elements of the properties elements
it = m_childProperties[defaultPropertyType].constBegin();
for (; it != m_childProperties[defaultPropertyType].constEnd(); ++it) {
if (!parentStyle || parentStyle->childProperty(it.key(), defaultPropertyType) != it.value()) {
writer->addCompleteElement(it.value().toUtf8());
}
}
if (createPropertiesTag)
writer->endElement();
}
// now write out any other properties elements
//start with i=1 to skip the defaultType that we already took care of
for (int i = 1; i < s_propertyNamesCount; ++i) {
//skip any properties that are the same as the defaultType
if (s_propertyTypes[i] != defaultPropertyType) {
writeStyleProperties(writer, s_propertyTypes[i], parentStyle);
}
}
//write child elements that aren't in any of the properties elements
i = KoGenStyle::StyleChildElement;
it = m_properties[i].constBegin();
for (; it != m_properties[i].constEnd(); ++it) {
if (!parentStyle || parentStyle->property(it.key(), i) != it.value()) {
writer->addCompleteElement(it.value().toUtf8());
}
}
// And now the style maps
for (int i = 0; i < m_maps.count(); ++i) {
bool writeit = true;
if (parentStyle && compareMap(m_maps[i], parentStyle->m_maps[i]) == 0)
writeit = false;
if (writeit) {
writer->startElement("style:map");
QMap<QString, QString>::const_iterator it = m_maps[i].constBegin();
for (; it != m_maps[i].constEnd(); ++it) {
writer->addAttribute(it.key().toUtf8(), it.value().toUtf8());
}
writer->endElement(); // style:map
}
}
if (closeElement)
writer->endElement();
}
void KoGenStyle::addPropertyPt(const QString& propName, qreal propValue, PropertyType type)
{
if (type == DefaultType) {
type = m_propertyType;
}
QString str;
str.setNum(propValue, 'f', DBL_DIG);
str += "pt";
m_properties[type].insert(propName, str);
}
void KoGenStyle::addPropertyLength(const QString& propName, const QTextLength &propValue, PropertyType type)
{
if (type == DefaultType) {
type = m_propertyType;
}
if (propValue.type() == QTextLength::FixedLength) {
return addPropertyPt(propName, propValue.rawValue(), type);
} else {
QString str;
str.setNum((int) propValue.rawValue());
str += '%';
m_properties[type].insert(propName, str);
}
}
void KoGenStyle::addAttributePt(const QString& attrName, qreal attrValue)
{
QString str;
str.setNum(attrValue, 'f', DBL_DIG);
str += "pt";
m_attributes.insert(attrName, str);
}
void KoGenStyle::addAttributePercent(const QString &attrName, qreal value)
{
QByteArray str;
str.setNum(value, 'f', FLT_DIG);
str += '%';
addAttribute(attrName, str.data());
}
void KoGenStyle::addAttributePercent(const QString &attrName, int value)
{
QByteArray str;
str.setNum(value);
str += '%';
addAttribute(attrName, str.data());
}
void KoGenStyle::addStyleMap(const QMap<QString, QString>& styleMap)
{
// check, if already present
for (int i = 0 ; i < m_maps.count() ; ++i) {
if (m_maps[i].count() == styleMap.count()) {
int comp = compareMap(m_maps[i], styleMap);
if (comp == 0)
return;
}
}
m_maps.append(styleMap);
}
#ifndef NDEBUG
void KoGenStyle::printDebug() const
{
int i = DefaultType;
debugOdf << m_properties[i].count() << " properties.";
for (QMap<QString, QString>::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
i = TextType;
debugOdf << m_properties[i].count() << " text properties.";
for (QMap<QString, QString>::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
i = ParagraphType;
debugOdf << m_properties[i].count() << " paragraph properties.";
for (QMap<QString, QString>::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
i = TextType;
debugOdf << m_childProperties[i].count() << " text child elements.";
for (QMap<QString, QString>::ConstIterator it = m_childProperties[i].constBegin(); it != m_childProperties[i].constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
i = ParagraphType;
debugOdf << m_childProperties[i].count() << " paragraph child elements.";
for (QMap<QString, QString>::ConstIterator it = m_childProperties[i].constBegin(); it != m_childProperties[i].constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
debugOdf << m_attributes.count() << " attributes.";
for (QMap<QString, QString>::ConstIterator it = m_attributes.constBegin(); it != m_attributes.constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
debugOdf << m_maps.count() << " maps.";
for (int i = 0; i < m_maps.count(); ++i) {
debugOdf << "map" << i << ":";
for (QMap<QString, QString>::ConstIterator it = m_maps[i].constBegin(); it != m_maps[i].constEnd(); ++it) {
debugOdf << "" << it.key() << " =" << it.value();
}
}
debugOdf;
}
#endif
bool KoGenStyle::operator<(const KoGenStyle &other) const
{
if (m_type != other.m_type) return m_type < other.m_type;
if (m_parentName != other.m_parentName) return m_parentName < other.m_parentName;
if (m_familyName != other.m_familyName) return m_familyName < other.m_familyName;
if (m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml) return m_autoStyleInStylesDotXml;
for (uint i = 0 ; i <= LastPropertyType; ++i) {
if (m_properties[i].count() != other.m_properties[i].count()) {
return m_properties[i].count() < other.m_properties[i].count();
}
if (m_childProperties[i].count() != other.m_childProperties[i].count()) {
return m_childProperties[i].count() < other.m_childProperties[i].count();
}
}
if (m_attributes.count() != other.m_attributes.count()) return m_attributes.count() < other.m_attributes.count();
if (m_maps.count() != other.m_maps.count()) return m_maps.count() < other.m_maps.count();
// Same number of properties and attributes, no other choice than iterating
for (uint i = 0 ; i <= LastPropertyType; ++i) {
int comp = compareMap(m_properties[i], other.m_properties[i]);
if (comp != 0)
return comp < 0;
}
for (uint i = 0 ; i <= LastPropertyType; ++i) {
int comp = compareMap(m_childProperties[i], other.m_childProperties[i]);
if (comp != 0)
return comp < 0;
}
int comp = compareMap(m_attributes, other.m_attributes);
if (comp != 0)
return comp < 0;
for (int i = 0 ; i < m_maps.count() ; ++i) {
int comp = compareMap(m_maps[i], other.m_maps[i]);
if (comp != 0)
return comp < 0;
}
return false;
}
bool KoGenStyle::operator==(const KoGenStyle &other) const
{
if (m_type != other.m_type) return false;
if (m_parentName != other.m_parentName) return false;
if (m_familyName != other.m_familyName) return false;
if (m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml) return false;
for (uint i = 0 ; i <= LastPropertyType; ++i) {
if (m_properties[i].count() != other.m_properties[i].count()) {
return false;
}
if (m_childProperties[i].count() != other.m_childProperties[i].count()) {
return false;
}
}
if (m_attributes.count() != other.m_attributes.count()) return false;
if (m_maps.count() != other.m_maps.count()) return false;
// Same number of properties and attributes, no other choice than iterating
for (uint i = 0 ; i <= LastPropertyType; ++i) {
int comp = compareMap(m_properties[i], other.m_properties[i]);
if (comp != 0)
return false;
}
for (uint i = 0 ; i <= LastPropertyType; ++i) {
int comp = compareMap(m_childProperties[i], other.m_childProperties[i]);
if (comp != 0)
return false;
}
int comp = compareMap(m_attributes, other.m_attributes);
if (comp != 0)
return false;
for (int i = 0 ; i < m_maps.count() ; ++i) {
int comp = compareMap(m_maps[i], other.m_maps[i]);
if (comp != 0)
return false;
}
return true;
}
bool KoGenStyle::isEmpty() const
{
if (!m_attributes.isEmpty() || ! m_maps.isEmpty())
return false;
for (uint i = 0 ; i <= LastPropertyType; ++i)
if (! m_properties[i].isEmpty())
return false;
return true;
}
void KoGenStyle::copyPropertiesFromStyle(const KoGenStyle &sourceStyle, KoGenStyle &targetStyle, PropertyType type)
{
if (type == DefaultType) {
type = sourceStyle.m_propertyType;
}
const StyleMap& map = sourceStyle.m_properties[type];
if (!map.isEmpty()) {
QMap<QString, QString>::const_iterator it = map.constBegin();
const QMap<QString, QString>::const_iterator end = map.constEnd();
for (; it != end; ++it) {
targetStyle.addProperty(it.key(), it.value(), type);
}
}
}
diff --git a/src/libs/odf/KoGenStyles.cpp b/src/libs/odf/KoGenStyles.cpp
index e8b544fd..00a02278 100644
--- a/src/libs/odf/KoGenStyles.cpp
+++ b/src/libs/odf/KoGenStyles.cpp
@@ -1,546 +1,547 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2009 Thomas Zander <zander@kde.org>
Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
Copyright (C) 2009 Inge Wallin <inge@lysator.liu.se>
Copyright (C) 2010 KO GmbH <jos.van.den.oever@kogmbh.com>
Copyright (C) 2010 Jarosław Staniek <staniek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoGenStyles.h"
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoXmlWriter.h>
#include "KoOdfWriteStore.h"
#include "KoFontFace.h"
#include <float.h>
#include <OdfDebug.h>
static const struct {
KoGenStyle::Type m_type;
const char * m_elementName;
const char * m_propertiesElementName;
bool m_drawElement;
} styleData[] = {
{ KoGenStyle::TextStyle, "style:style", "style:text-properties", false },
{ KoGenStyle::ParagraphStyle, "style:style", "style:paragraph-properties", false },
{ KoGenStyle::SectionStyle, "style:style", "style:section-properties", false },
{ KoGenStyle::RubyStyle, "style:style", "style:ruby-properties", false },
{ KoGenStyle::TableStyle, "style:style", "style:table-properties", false },
{ KoGenStyle::TableColumnStyle, "style:style", "style:table-column-properties", false },
{ KoGenStyle::TableRowStyle, "style:style", "style:table-row-properties", false },
{ KoGenStyle::TableCellStyle, "style:style", "style:table-cell-properties", false },
{ KoGenStyle::GraphicStyle, "style:style", "style:graphic-properties", false },
{ KoGenStyle::PresentationStyle, "style:style", "style:graphic-properties", false },
{ KoGenStyle::DrawingPageStyle, "style:style", "style:drawing-page-properties", false },
{ KoGenStyle::ChartStyle, "style:style", "style:chart-properties", false },
{ KoGenStyle::ListStyle, "text:list-style", 0, false },
{ KoGenStyle::LinearGradientStyle, "svg:linearGradient", 0, true },
{ KoGenStyle::RadialGradientStyle, "svg:radialGradient", 0, true },
{ KoGenStyle::ConicalGradientStyle, "calligra:conicalGradient", 0, true },
{ KoGenStyle::StrokeDashStyle, "draw:stroke-dash", 0, true },
{ KoGenStyle::FillImageStyle, "draw:fill-image", 0, true },
{ KoGenStyle::HatchStyle, "draw:hatch", "style:graphic-properties", true },
{ KoGenStyle::GradientStyle, "draw:gradient", "style:graphic-properties", true },
{ KoGenStyle::MarkerStyle, "draw:marker", "style:graphic-properties", true },
{ KoGenStyle::PresentationPageLayoutStyle, "style:presentation-page-layout", 0, false },
{ KoGenStyle::OutlineLevelStyle, "text:outline-style", 0, false }
};
static const unsigned int numStyleData = sizeof(styleData) / sizeof(*styleData);
static const struct {
KoGenStyle::Type m_type;
const char * m_elementName;
const char * m_propertiesElementName;
bool m_drawElement;
} autoStyleData[] = {
{ KoGenStyle::TextAutoStyle, "style:style", "style:text-properties", false },
{ KoGenStyle::ParagraphAutoStyle, "style:style", "style:paragraph-properties", false },
{ KoGenStyle::SectionAutoStyle, "style:style", "style:section-properties", false },
{ KoGenStyle::RubyAutoStyle, "style:style", "style:ruby-properties", false },
{ KoGenStyle::TableAutoStyle, "style:style", "style:table-properties", false },
{ KoGenStyle::TableColumnAutoStyle, "style:style", "style:table-column-properties", false },
{ KoGenStyle::TableRowAutoStyle, "style:style", "style:table-row-properties", false },
{ KoGenStyle::TableCellAutoStyle, "style:style", "style:table-cell-properties", false },
{ KoGenStyle::GraphicAutoStyle, "style:style", "style:graphic-properties", false },
{ KoGenStyle::PresentationAutoStyle, "style:style", "style:graphic-properties", false },
{ KoGenStyle::DrawingPageAutoStyle, "style:style", "style:drawing-page-properties", false },
{ KoGenStyle::ChartAutoStyle, "style:style", "style:chart-properties", false },
{ KoGenStyle::PageLayoutStyle, "style:page-layout", "style:page-layout-properties", false },
{ KoGenStyle::ListAutoStyle, "text:list-style", 0, false },
{ KoGenStyle::NumericNumberStyle, "number:number-style", 0, false },
{ KoGenStyle::NumericFractionStyle, "number:number-style", 0, false },
{ KoGenStyle::NumericScientificStyle, "number:number-style", 0, false },
{ KoGenStyle::NumericDateStyle, "number:date-style", 0, false },
{ KoGenStyle::NumericTimeStyle, "number:time-style", 0, false },
{ KoGenStyle::NumericPercentageStyle, "number:percentage-style", 0, false },
{ KoGenStyle::NumericCurrencyStyle, "number:currency-style", 0, false },
{ KoGenStyle::NumericBooleanStyle, "number:boolean-style", 0, false },
{ KoGenStyle::NumericTextStyle, "number:text-style", 0, false }
};
static const unsigned int numAutoStyleData = sizeof(autoStyleData) / sizeof(*autoStyleData);
static void insertRawOdfStyles(const QByteArray& xml, QByteArray& styles)
{
if (xml.isEmpty())
return;
if (!styles.isEmpty() && !styles.endsWith('\n') && !xml.startsWith('\n')) {
styles.append('\n');
}
styles.append(xml);
}
class Q_DECL_HIDDEN KoGenStyles::Private
{
public:
Private(KoGenStyles *q) : q(q)
{
}
~Private()
{
}
QVector<KoGenStyles::NamedStyle> styles(bool autoStylesInStylesDotXml, KoGenStyle::Type type) const;
void saveOdfAutomaticStyles(KoXmlWriter* xmlWriter, bool autoStylesInStylesDotXml,
const QByteArray& rawOdfAutomaticStyles) const;
void saveOdfDocumentStyles(KoXmlWriter* xmlWriter) const;
void saveOdfMasterStyles(KoXmlWriter* xmlWriter) const;
QString makeUniqueName(const QString& base, const QByteArray &family, InsertionFlags flags) const;
/**
* Save font face declarations
*
* This creates the office:font-face-decls tag containing all font face
* declarations
*/
void saveOdfFontFaceDecls(KoXmlWriter* xmlWriter) const;
/// style definition -> name
StyleMap styleMap;
/// Map with the style name as key.
/// This map is mainly used to check for name uniqueness
QMap<QByteArray, QSet<QString> > styleNames;
QMap<QByteArray, QSet<QString> > autoStylesInStylesDotXml;
/// List of styles (used to preserve ordering)
QVector<KoGenStyles::NamedStyle> styleList;
/// map for saving default styles
QMap<int, KoGenStyle> defaultStyles;
/// font faces
QMap<QString, KoFontFace> fontFaces;
StyleMap::iterator insertStyle(const KoGenStyle &style, const QString &name, InsertionFlags flags);
struct RelationTarget {
QString target; // the style we point to
QString attribute; // the attribute name used for the relation
};
QHash<QString, RelationTarget> relations; // key is the name of the source style
QByteArray rawOdfDocumentStyles;
QByteArray rawOdfAutomaticStyles_stylesDotXml;
QByteArray rawOdfAutomaticStyles_contentDotXml;
QByteArray rawOdfMasterStyles;
QByteArray rawOdfFontFaceDecls;
KoGenStyles *q;
};
QVector<KoGenStyles::NamedStyle> KoGenStyles::Private::styles(bool autoStylesInStylesDotXml, KoGenStyle::Type type) const
{
QVector<KoGenStyles::NamedStyle> lst;
QVector<KoGenStyles::NamedStyle>::const_iterator it = styleList.constBegin();
const QVector<KoGenStyles::NamedStyle>::const_iterator end = styleList.constEnd();
for (; it != end ; ++it) {
if ((*it).style->type() == type && (*it).style->autoStyleInStylesDotXml() == autoStylesInStylesDotXml) {
lst.append(*it);
}
}
return lst;
}
void KoGenStyles::Private::saveOdfAutomaticStyles(KoXmlWriter* xmlWriter, bool autoStylesInStylesDotXml,
const QByteArray& rawOdfAutomaticStyles) const
{
xmlWriter->startElement("office:automatic-styles");
for (uint i = 0; i < numAutoStyleData; ++i) {
QVector<KoGenStyles::NamedStyle> stylesList = styles(autoStylesInStylesDotXml, autoStyleData[i].m_type);
QVector<KoGenStyles::NamedStyle>::const_iterator it = stylesList.constBegin();
for (; it != stylesList.constEnd() ; ++it) {
(*it).style->writeStyle(xmlWriter, *q, autoStyleData[i].m_elementName, (*it).name,
autoStyleData[i].m_propertiesElementName, true, autoStyleData[i].m_drawElement);
}
}
if (!rawOdfAutomaticStyles.isEmpty()) {
xmlWriter->addCompleteElement(rawOdfAutomaticStyles.constData());
}
xmlWriter->endElement(); // office:automatic-styles
}
void KoGenStyles::Private::saveOdfDocumentStyles(KoXmlWriter* xmlWriter) const
{
xmlWriter->startElement("office:styles");
for (uint i = 0; i < numStyleData; ++i) {
const QMap<int, KoGenStyle>::const_iterator it(defaultStyles.constFind(styleData[i].m_type));
if (it != defaultStyles.constEnd()) {
it.value().writeStyle(xmlWriter, *q, "style:default-style", "",
styleData[i].m_propertiesElementName, true, styleData[i].m_drawElement);
}
}
for (uint i = 0; i < numStyleData; ++i) {
QVector<KoGenStyles::NamedStyle> stylesList(styles(false, styleData[i].m_type));
QVector<KoGenStyles::NamedStyle>::const_iterator it = stylesList.constBegin();
for (; it != stylesList.constEnd() ; ++it) {
if (relations.contains(it->name)) {
KoGenStyles::Private::RelationTarget relation = relations.value(it->name);
KoGenStyle styleCopy = *(*it).style;
styleCopy.addAttribute(relation.attribute, relation.target);
styleCopy.writeStyle(xmlWriter, *q, styleData[i].m_elementName, (*it).name,
styleData[i].m_propertiesElementName, true, styleData[i].m_drawElement);
} else {
(*it).style->writeStyle(xmlWriter, *q, styleData[i].m_elementName, (*it).name,
styleData[i].m_propertiesElementName, true, styleData[i].m_drawElement);
}
}
}
if (!rawOdfDocumentStyles.isEmpty()) {
xmlWriter->addCompleteElement(rawOdfDocumentStyles.constData());
}
xmlWriter->endElement(); // office:styles
}
void KoGenStyles::Private::saveOdfMasterStyles(KoXmlWriter* xmlWriter) const
{
xmlWriter->startElement("office:master-styles");
QVector<KoGenStyles::NamedStyle> stylesList = styles(false, KoGenStyle::MasterPageStyle);
QVector<KoGenStyles::NamedStyle>::const_iterator it = stylesList.constBegin();
for (; it != stylesList.constEnd() ; ++it) {
(*it).style->writeStyle(xmlWriter, *q, "style:master-page", (*it).name, 0);
}
if (!rawOdfMasterStyles.isEmpty()) {
xmlWriter->addCompleteElement(rawOdfMasterStyles.constData());
}
xmlWriter->endElement(); // office:master-styles
}
void KoGenStyles::Private::saveOdfFontFaceDecls(KoXmlWriter* xmlWriter) const
{
if (fontFaces.isEmpty())
return;
xmlWriter->startElement("office:font-face-decls");
for (QMap<QString, KoFontFace>::ConstIterator it(fontFaces.constBegin());
it != fontFaces.constEnd(); ++it)
{
it.value().saveOdf(xmlWriter);
}
if (!rawOdfFontFaceDecls.isEmpty()) {
xmlWriter->addCompleteElement(rawOdfFontFaceDecls.constData());
}
xmlWriter->endElement(); // office:font-face-decls
}
QString KoGenStyles::Private::makeUniqueName(const QString& base, const QByteArray &family, InsertionFlags flags) const
{
// If this name is not used yet, and numbering isn't forced, then the given name is ok.
if ((flags & DontAddNumberToName)
&& !autoStylesInStylesDotXml[family].contains(base)
&& !styleNames[family].contains(base))
return base;
int num = 1;
QString name;
do {
name = base + QString::number(num++);
} while (autoStylesInStylesDotXml[family].contains(name)
|| styleNames[family].contains(name));
return name;
}
//------------------------
KoGenStyles::KoGenStyles()
: d(new Private(this))
{
}
KoGenStyles::~KoGenStyles()
{
delete d;
}
QString KoGenStyles::insert(const KoGenStyle& style, const QString& baseName, InsertionFlags flags)
{
// if it is a default style it has to be saved differently
if (style.isDefaultStyle()) {
// we can have only one default style per type
Q_ASSERT(!d->defaultStyles.contains(style.type()));
// default style is only possible for style:style in office:style types
Q_ASSERT(style.type() == KoGenStyle::TextStyle ||
style.type() == KoGenStyle::ParagraphStyle ||
style.type() == KoGenStyle::SectionStyle ||
style.type() == KoGenStyle::RubyStyle ||
style.type() == KoGenStyle::TableStyle ||
style.type() == KoGenStyle::TableColumnStyle ||
style.type() == KoGenStyle::TableRowStyle ||
style.type() == KoGenStyle::TableCellStyle ||
style.type() == KoGenStyle::GraphicStyle ||
style.type() == KoGenStyle::PresentationStyle ||
style.type() == KoGenStyle::DrawingPageStyle ||
style.type() == KoGenStyle::ChartStyle);
d->defaultStyles.insert(style.type(), style);
// default styles don't have a name
return QString();
}
if (flags & AllowDuplicates) {
StyleMap::iterator it = d->insertStyle(style, baseName, flags);
return it.value();
}
StyleMap::iterator it = d->styleMap.find(style);
if (it == d->styleMap.end()) {
// Not found, try if this style is in fact equal to its parent (the find above
// wouldn't have found it, due to m_parentName being set).
if (!style.parentName().isEmpty()) {
KoGenStyle testStyle(style);
const KoGenStyle* parentStyle = this->style(style.parentName(), style.familyName()); // ## linear search
if (!parentStyle) {
debugOdf << "baseName=" << baseName << "parent style" << style.parentName()
<< "not found in collection";
} else {
// TODO remove
if (testStyle.m_familyName != parentStyle->m_familyName) {
warnOdf << "baseName=" << baseName << "family=" << testStyle.m_familyName
<< "parent style" << style.parentName() << "has a different family:"
<< parentStyle->m_familyName;
}
testStyle.m_parentName = parentStyle->m_parentName;
// Exclude the type from the comparison. It's ok for an auto style
// to have a user style as parent; they can still be identical
testStyle.m_type = parentStyle->m_type;
// Also it's ok to not have the display name of the parent style
// in the auto style
QMap<QString, QString>::const_iterator it = parentStyle->m_attributes.find("style:display-name");
if (it != parentStyle->m_attributes.end())
testStyle.addAttribute("style:display-name", *it);
if (*parentStyle == testStyle)
return style.parentName();
}
}
it = d->insertStyle(style, baseName, flags);
}
return it.value();
}
KoGenStyles::StyleMap::iterator KoGenStyles::Private::insertStyle(const KoGenStyle &style,
const QString& baseName, InsertionFlags flags)
{
QString styleName(baseName);
if (styleName.isEmpty()) {
switch (style.type()) {
case KoGenStyle::ParagraphAutoStyle: styleName = 'P'; break;
case KoGenStyle::ListAutoStyle: styleName = 'L'; break;
case KoGenStyle::TextAutoStyle: styleName = 'T'; break;
default:
styleName = 'A'; // for "auto".
}
flags &= ~DontAddNumberToName; // i.e. force numbering
}
styleName = makeUniqueName(styleName, style.m_familyName, flags);
if (style.autoStyleInStylesDotXml())
autoStylesInStylesDotXml[style.m_familyName].insert(styleName);
else
styleNames[style.m_familyName].insert(styleName);
KoGenStyles::StyleMap::iterator it = styleMap.insert(style, styleName);
NamedStyle s;
s.style = &it.key();
s.name = styleName;
styleList.append(s);
return it;
}
KoGenStyles::StyleMap KoGenStyles::styles() const
{
return d->styleMap;
}
QVector<KoGenStyles::NamedStyle> KoGenStyles::styles(KoGenStyle::Type type) const
{
return d->styles(false, type);
}
const KoGenStyle* KoGenStyles::style(const QString &name, const QByteArray &family) const
{
QVector<KoGenStyles::NamedStyle>::const_iterator it = d->styleList.constBegin();
const QVector<KoGenStyles::NamedStyle>::const_iterator end = d->styleList.constEnd();
for (; it != end ; ++it) {
if ((*it).name == name && (*it).style->familyName() == family) {
return (*it).style;
}
}
return 0;
}
KoGenStyle* KoGenStyles::styleForModification(const QString &name, const QByteArray &family)
{
return const_cast<KoGenStyle *>(style(name, family));
}
void KoGenStyles::markStyleForStylesXml(const QString &name, const QByteArray &family)
{
Q_ASSERT(d->styleNames[family].contains(name));
d->styleNames[family].remove(name);
d->autoStylesInStylesDotXml[family].insert(name);
styleForModification(name, family)->setAutoStyleInStylesDotXml(true);
}
void KoGenStyles::insertFontFace(const KoFontFace &face)
{
Q_ASSERT(!face.isNull());
if (face.isNull()) {
warnOdf << "This font face is null and will not be added to styles: set at least the name";
return;
}
d->fontFaces.insert(face.name(), face); // replaces prev item
}
KoFontFace KoGenStyles::fontFace(const QString& name) const
{
return d->fontFaces.value(name);
}
bool KoGenStyles::saveOdfStylesDotXml(KoStore* store, KoXmlWriter* manifestWriter) const
{
if (!store->open("styles.xml"))
return false;
manifestWriter->addManifestEntry("styles.xml", "text/xml");
KoStoreDevice stylesDev(store);
KoXmlWriter* stylesWriter = KoOdfWriteStore::createOasisXmlWriter(&stylesDev, "office:document-styles");
d->saveOdfFontFaceDecls(stylesWriter);
d->saveOdfDocumentStyles(stylesWriter);
d->saveOdfAutomaticStyles(stylesWriter, true, d->rawOdfAutomaticStyles_stylesDotXml);
d->saveOdfMasterStyles(stylesWriter);
stylesWriter->endElement(); // root element (office:document-styles)
stylesWriter->endDocument();
delete stylesWriter;
if (!store->close()) // done with styles.xml
return false;
return true;
}
void KoGenStyles::saveOdfStyles(StylesPlacement placement, KoXmlWriter* xmlWriter) const
{
switch (placement) {
case DocumentStyles:
d->saveOdfDocumentStyles(xmlWriter);
break;
case MasterStyles:
d->saveOdfMasterStyles(xmlWriter);
break;
case DocumentAutomaticStyles:
d->saveOdfAutomaticStyles(xmlWriter, false, d->rawOdfAutomaticStyles_contentDotXml);
break;
case StylesXmlAutomaticStyles:
d->saveOdfAutomaticStyles(xmlWriter, true, d->rawOdfAutomaticStyles_stylesDotXml);
break;
case FontFaceDecls:
d->saveOdfFontFaceDecls(xmlWriter);
break;
}
}
void KoGenStyles::insertRawOdfStyles(StylesPlacement placement, const QByteArray& xml)
{
switch (placement) {
case DocumentStyles:
::insertRawOdfStyles(xml, d->rawOdfDocumentStyles);
break;
case MasterStyles:
::insertRawOdfStyles(xml, d->rawOdfMasterStyles);
break;
case DocumentAutomaticStyles:
::insertRawOdfStyles(xml, d->rawOdfAutomaticStyles_contentDotXml);
break;
case StylesXmlAutomaticStyles:
::insertRawOdfStyles(xml, d->rawOdfAutomaticStyles_stylesDotXml);
break;
case FontFaceDecls:
::insertRawOdfStyles(xml, d->rawOdfFontFaceDecls);
break;
}
}
void KoGenStyles::insertStyleRelation(const QString &source, const QString &target, const char *tagName)
{
KoGenStyles::Private::RelationTarget relation;
relation.target = target;
relation.attribute = QString(tagName);
d->relations.insert(source, relation);
}
QDebug operator<<(QDebug dbg, const KoGenStyles& styles)
{
dbg.nospace() << "KoGenStyles:";
QVector<KoGenStyles::NamedStyle>::const_iterator it = styles.d->styleList.constBegin();
const QVector<KoGenStyles::NamedStyle>::const_iterator end = styles.d->styleList.constEnd();
for (; it != end ; ++it) {
dbg.nospace() << (*it).name;
}
for (QMap<QByteArray, QSet<QString> >::const_iterator familyIt(styles.d->styleNames.constBegin()); familyIt != styles.d->styleNames.constEnd(); ++familyIt) {
for (QSet<QString>::const_iterator it(familyIt.value().constBegin()); it != familyIt.value().constEnd(); ++it) {
dbg.space() << "style:" << *it;
}
}
#ifndef NDEBUG
for (QMap<QByteArray, QSet<QString> >::const_iterator familyIt(styles.d->autoStylesInStylesDotXml.constBegin()); familyIt != styles.d->autoStylesInStylesDotXml.constEnd(); ++familyIt) {
for (QSet<QString>::const_iterator it(familyIt.value().constBegin()); it != familyIt.value().constEnd(); ++it) {
dbg.space() << "auto style for style.xml:" << *it;
}
}
#endif
return dbg.space();
}
diff --git a/src/libs/odf/KoOasisSettings.cpp b/src/libs/odf/KoOasisSettings.cpp
index 31095afe..25ffaf5f 100644
--- a/src/libs/odf/KoOasisSettings.cpp
+++ b/src/libs/odf/KoOasisSettings.cpp
@@ -1,224 +1,225 @@
/* This file is part of the KDE project
Copyright (C) 2004 Laurent Montel <montel@kde.org>
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 "KoOasisSettings.h"
#include "KoXmlNS.h"
#include <OdfDebug.h>
class Q_DECL_HIDDEN KoOasisSettings::Private
{
};
KoOasisSettings::KoOasisSettings(const KoXmlDocument& doc)
: m_settingsElement(KoXml::namedItemNS(doc.documentElement(), KoXmlNS::office, "settings")),
m_configNsUri(KoXmlNS::config)
, d(0)
{
const KoXmlElement contents = doc.documentElement();
if (m_settingsElement.isNull())
debugOdf << " document doesn't have tag 'office:settings'";
}
KoOasisSettings::KoOasisSettings(const KoXmlDocument& doc, const char* officeNSURI, const char* configNSURI)
: m_settingsElement(KoXml::namedItemNS(doc.documentElement(), officeNSURI, "settings")),
m_configNsUri(configNSURI)
, d(0)
{
const KoXmlElement contents = doc.documentElement();
if (m_settingsElement.isNull())
debugOdf << " document doesn't have tag 'office:settings'";
}
KoOasisSettings::~KoOasisSettings()
{
delete d;
}
KoOasisSettings::Items KoOasisSettings::itemSet(const QString& itemSetName) const
{
KoXmlElement e;
forEachElement(e, m_settingsElement) {
if (e.localName() == "config-item-set" &&
e.namespaceURI() == m_configNsUri &&
e.attributeNS(m_configNsUri, "name", QString()) == itemSetName) {
return Items(e, this);
}
}
return Items(KoXmlElement(), this);
}
KoOasisSettings::IndexedMap KoOasisSettings::Items::indexedMap(const QString& itemMapName) const
{
KoXmlElement configItem;
forEachElement(configItem, m_element) {
if (configItem.localName() == "config-item-map-indexed" &&
configItem.namespaceURI() == m_settings->m_configNsUri &&
configItem.attributeNS(m_settings->m_configNsUri, "name", QString()) == itemMapName) {
return IndexedMap(configItem, m_settings);
}
}
return IndexedMap(KoXmlElement(), m_settings);
}
KoOasisSettings::NamedMap KoOasisSettings::Items::namedMap(const QString& itemMapName) const
{
KoXmlElement configItem;
forEachElement(configItem, m_element) {
if (configItem.localName() == "config-item-map-named" &&
configItem.namespaceURI() == m_settings->m_configNsUri &&
configItem.attributeNS(m_settings->m_configNsUri, "name", QString()) == itemMapName) {
return NamedMap(configItem, m_settings);
}
}
return NamedMap(KoXmlElement(), m_settings);
}
KoOasisSettings::Items KoOasisSettings::IndexedMap::entry(int entryIndex) const
{
int i = 0;
KoXmlElement entry;
forEachElement(entry, m_element) {
if (entry.localName() == "config-item-map-entry" &&
entry.namespaceURI() == m_settings->m_configNsUri) {
if (i == entryIndex)
return Items(entry, m_settings);
else
++i;
}
}
return Items(KoXmlElement(), m_settings);
}
KoOasisSettings::Items KoOasisSettings::NamedMap::entry(const QString& entryName) const
{
KoXmlElement entry;
forEachElement(entry, m_element) {
if (entry.localName() == "config-item-map-entry" &&
entry.namespaceURI() == m_settings->m_configNsUri &&
entry.attributeNS(m_settings->m_configNsUri, "name", QString()) == entryName) {
return Items(entry, m_settings);
}
}
return Items(KoXmlElement(), m_settings);
}
// helper method
QString KoOasisSettings::Items::findConfigItem(const KoXmlElement& element,
const QString& item, bool* ok) const
{
KoXmlElement it;
forEachElement(it, element) {
if (it.localName() == "config-item" &&
it.namespaceURI() == m_settings->m_configNsUri &&
it.attributeNS(m_settings->m_configNsUri, "name", QString()) == item) {
*ok = true;
return it.text();
}
}
*ok = false;
return QString();
}
QString KoOasisSettings::Items::findConfigItem(const QString& item, bool* ok) const
{
return findConfigItem(m_element, item, ok);
}
#if 0 // does anyone need this one? passing a default value does the job, too
bool KoOasisSettings::Items::hasConfigItem(const QString& configName) const
{
bool ok;
(void)findConfigItem(configName, &ok);
return ok;
}
#endif
QString KoOasisSettings::Items::parseConfigItemString(const QString& configName, const QString& defValue) const
{
bool ok;
const QString str = findConfigItem(configName, &ok);
return ok ? str : defValue;
}
int KoOasisSettings::Items::parseConfigItemInt(const QString& configName, int defValue) const
{
bool ok;
const QString str = findConfigItem(configName, &ok);
int value;
if (ok) {
value = str.toInt(&ok);
if (ok)
return value;
}
return defValue;
}
qreal KoOasisSettings::Items::parseConfigItemDouble(const QString& configName, qreal defValue) const
{
bool ok;
const QString str = findConfigItem(configName, &ok);
qreal value;
if (ok) {
value = str.toDouble(&ok);
if (ok)
return value;
}
return defValue;
}
bool KoOasisSettings::Items::parseConfigItemBool(const QString& configName, bool defValue) const
{
bool ok;
const QString str = findConfigItem(configName, &ok);
if (! ok)
return defValue;
if (str == "true")
return true;
else if (str == "false")
return false;
return defValue;
}
short KoOasisSettings::Items::parseConfigItemShort(const QString& configName, short defValue) const
{
bool ok;
const QString str = findConfigItem(configName, &ok);
short value;
if (ok) {
value = str.toShort(&ok);
if (ok)
return value;
}
return defValue;
}
long KoOasisSettings::Items::parseConfigItemLong(const QString& configName, long defValue) const
{
bool ok;
const QString str = findConfigItem(configName, &ok);
long value;
if (ok) {
value = str.toLong(&ok);
if (ok)
return value;
}
return defValue;
}
diff --git a/src/libs/odf/KoOdf.cpp b/src/libs/odf/KoOdf.cpp
index 409a8e18..07b58378 100644
--- a/src/libs/odf/KoOdf.cpp
+++ b/src/libs/odf/KoOdf.cpp
@@ -1,60 +1,61 @@
/* This file is part of the KDE project
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2010 Thomas Zander <zander@kde.org>
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 "KoOdf.h"
namespace KoOdf
{
struct DocumentData {
const char * mimeType;
const char * templateMimeType;
const char * bodyContentElement;
};
const DocumentData s_documentData[] = {
{ "application/vnd.oasis.opendocument.text", "application/vnd.oasis.opendocument.text-template", "office:text" },
{ "application/vnd.oasis.opendocument.graphics", "application/vnd.oasis.opendocument.graphics-template", "office:drawing" },
{ "application/vnd.oasis.opendocument.presentation", "application/vnd.oasis.opendocument.presentation-template", "office:presentation" },
{ "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.spreadsheet-template", "office:spreadsheet" },
{ "application/vnd.oasis.opendocument.chart", "application/vnd.oasis.opendocument.chart-template", "office:chart" },
{ "application/vnd.oasis.opendocument.image", "application/vnd.oasis.opendocument.image-template", "office:image" },
// TODO what is the element for a formula check if bodyContentElement is ok
{ "application/vnd.oasis.opendocument.formula", "application/vnd.oasis.opendocument.formula-template", "office:XXX" },
{ "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"", "", "office:text" }
};
//"application/vnd.oasis.opendocument.text-master"
//"application/vnd.oasis.opendocument.text-web"
const char * mimeType(DocumentType documentType)
{
return s_documentData[documentType].mimeType;
}
const char * templateMimeType(DocumentType documentType)
{
return s_documentData[documentType].templateMimeType;
}
const char * bodyContentElement(DocumentType documentType, bool withNamespace)
{
return withNamespace ? s_documentData[documentType].bodyContentElement : s_documentData[documentType].bodyContentElement + 7;
}
}
diff --git a/src/libs/odf/KoOdfBibliographyConfiguration.cpp b/src/libs/odf/KoOdfBibliographyConfiguration.cpp
index 433d59ae..d85cd401 100644
--- a/src/libs/odf/KoOdfBibliographyConfiguration.cpp
+++ b/src/libs/odf/KoOdfBibliographyConfiguration.cpp
@@ -1,202 +1,203 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Smit Patel <smitpatel24@gmail.com>
*
* 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 "KoOdfBibliographyConfiguration.h"
#include <OdfDebug.h>
#include "KoXmlNS.h"
#include "KoXmlWriter.h"
#include <QList>
const QList<QString> KoOdfBibliographyConfiguration::bibTypes = QList<QString>() << "article" << "book" << "booklet" << "conference"
<< "email" << "inbook" << "incollection"
<< "inproceedings" << "journal" << "manual"
<< "mastersthesis" << "misc" << "phdthesis"
<< "proceedings" << "techreport" << "unpublished"
<< "www" << "custom1" << "custom2"
<< "custom3" << "custom4" << "custom5";
const QList<QString> KoOdfBibliographyConfiguration::bibDataFields = QList<QString>() << "address" << "annote" << "author"
<< "bibliography-type" << "booktitle"
<< "chapter" << "custom1" << "custom2"
<< "custom3" << "custom4" << "custom5"
<< "edition" << "editor" << "howpublished"
<< "identifier" << "institution" << "isbn"
<< "issn" << "journal" << "month" << "note"
<< "number" << "organizations" << "pages"
<< "publisher" << "report-type" << "school"
<< "series" << "title" << "url" << "volume"
<< "year";
class Q_DECL_HIDDEN KoOdfBibliographyConfiguration::Private
{
public:
QString prefix;
QString suffix;
bool numberedEntries;
bool sortByPosition;
QString sortAlgorithm;
QVector<SortKeyPair> sortKeys;
};
KoOdfBibliographyConfiguration::KoOdfBibliographyConfiguration()
: d(new Private())
{
d->prefix = "[";
d->suffix = "]";
d->numberedEntries = false;
d->sortByPosition = true;
}
KoOdfBibliographyConfiguration::~KoOdfBibliographyConfiguration()
{
delete d;
}
KoOdfBibliographyConfiguration::KoOdfBibliographyConfiguration(const KoOdfBibliographyConfiguration &other)
: QObject(), d(new Private())
{
*this = other;
}
KoOdfBibliographyConfiguration &KoOdfBibliographyConfiguration::operator=(const KoOdfBibliographyConfiguration &other)
{
d->prefix = other.d->prefix;
d->suffix = other.d->suffix;
d->numberedEntries = other.d->numberedEntries;
d->sortAlgorithm = other.d->sortAlgorithm;
d->sortByPosition = other.d->sortByPosition;
d->sortKeys = other.d->sortKeys;
return *this;
}
void KoOdfBibliographyConfiguration::loadOdf(const KoXmlElement &element)
{
d->prefix = element.attributeNS(KoXmlNS::text, "prefix", QString());
d->suffix = element.attributeNS(KoXmlNS::text, "suffix", QString());
d->numberedEntries = (element.attributeNS(KoXmlNS::text, "numbered-entries", QString("false")) == "true")
? true : false;
d->sortByPosition = (element.attributeNS(KoXmlNS::text, "sort-by-position", QString("true")) == "true")
? true : false;
d->sortAlgorithm = element.attributeNS(KoXmlNS::text, "sort-algorithm", QString());
for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling())
{
KoXmlElement child = node.toElement();
if (child.namespaceURI() == KoXmlNS::text && child.localName() == "sort-key") {
QString key = child.attributeNS(KoXmlNS::text, "key", QString());
Qt::SortOrder order = (child.attributeNS(KoXmlNS::text, "sort-ascending", "true") == "true")
? (Qt::AscendingOrder): (Qt::DescendingOrder);
if(!key.isNull() && KoOdfBibliographyConfiguration::bibDataFields.contains(key)) {
d->sortKeys << QPair<QString, Qt::SortOrder>(key,order);
}
}
}
}
void KoOdfBibliographyConfiguration::saveOdf(KoXmlWriter *writer) const
{
writer->startElement("text:bibliography-configuration");
if (!d->prefix.isNull()) {
writer->addAttribute("text:prefix", d->prefix);
}
if (!d->suffix.isNull()) {
writer->addAttribute("text:suffix", d->suffix);
}
if (!d->sortAlgorithm.isNull()) {
writer->addAttribute("text:sort-algorithm", d->sortAlgorithm);
}
writer->addAttribute("text:numbered-entries", d->numberedEntries ? "true" : "false");
writer->addAttribute("text:sort-by-position", d->sortByPosition ? "true" : "false");
foreach (const SortKeyPair &key, d->sortKeys) {
writer->startElement("text:sort-key");
writer->addAttribute("text:key", key.first);
writer->addAttribute("text:sort-ascending",key.second);
writer->endElement();
}
writer->endElement();
}
QString KoOdfBibliographyConfiguration::prefix() const
{
return d->prefix;
}
QString KoOdfBibliographyConfiguration::suffix() const
{
return d->suffix;
}
QString KoOdfBibliographyConfiguration::sortAlgorithm() const
{
return d->sortAlgorithm;
}
bool KoOdfBibliographyConfiguration::sortByPosition() const
{
return d->sortByPosition;
}
QVector<SortKeyPair> KoOdfBibliographyConfiguration::sortKeys() const
{
return d->sortKeys;
}
bool KoOdfBibliographyConfiguration::numberedEntries() const
{
return d->numberedEntries;
}
void KoOdfBibliographyConfiguration::setNumberedEntries(bool enable)
{
d->numberedEntries = enable;
}
void KoOdfBibliographyConfiguration::setPrefix(const QString &prefixValue)
{
d->prefix = prefixValue;
}
void KoOdfBibliographyConfiguration::setSuffix(const QString &suffixValue)
{
d->suffix = suffixValue;
}
void KoOdfBibliographyConfiguration::setSortAlgorithm(const QString &algorithm)
{
d->sortAlgorithm = algorithm;
}
void KoOdfBibliographyConfiguration::setSortByPosition(bool enable)
{
d->sortByPosition = enable;
}
void KoOdfBibliographyConfiguration::setSortKeys(const QVector<SortKeyPair> &sortKeys)
{
d->sortKeys = sortKeys;
}
diff --git a/src/libs/odf/KoOdfGraphicStyles.cpp b/src/libs/odf/KoOdfGraphicStyles.cpp
index 3dd125bb..73fedeb5 100644
--- a/src/libs/odf/KoOdfGraphicStyles.cpp
+++ b/src/libs/odf/KoOdfGraphicStyles.cpp
@@ -1,782 +1,783 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2007-2008,2010-2011 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
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 "KoOdfGraphicStyles.h"
#include <QBrush>
#include <QBuffer>
#include <QPen>
#include <OdfDebug.h>
#include <KoGenStyles.h>
#include <KoStyleStack.h>
#include <KoUnit.h>
#include <KoXmlNS.h>
#include <KoXmlWriter.h>
#include "KoOdfStylesReader.h"
void KoOdfGraphicStyles::saveOdfFillStyle(KoGenStyle &styleFill, KoGenStyles& mainStyles, const QBrush & brush)
{
KoGenStyle::Type type = styleFill.type();
KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle ||
type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle )
? KoGenStyle::DefaultType : KoGenStyle::GraphicType;
switch (brush.style()) {
case Qt::Dense1Pattern:
styleFill.addProperty("draw:opacity", "6%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::Dense2Pattern:
styleFill.addProperty("draw:opacity", "12%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::Dense3Pattern:
styleFill.addProperty("draw:opacity", "37%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::Dense4Pattern:
styleFill.addProperty("draw:opacity", "50%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::Dense5Pattern:
styleFill.addProperty("draw:opacity", "63%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::Dense6Pattern:
styleFill.addProperty("draw:opacity", "88%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::Dense7Pattern:
styleFill.addProperty("draw:opacity", "94%", propertyType);
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
break;
case Qt::LinearGradientPattern:
case Qt::RadialGradientPattern:
case Qt::ConicalGradientPattern:
styleFill.addProperty("draw:fill", "gradient", propertyType);
styleFill.addProperty("draw:fill-gradient-name", saveOdfGradientStyle(mainStyles, brush), propertyType);
break;
case Qt::HorPattern:
case Qt::VerPattern:
case Qt::CrossPattern:
case Qt::BDiagPattern:
case Qt::FDiagPattern:
case Qt::DiagCrossPattern:
styleFill.addProperty("draw:fill", "hatch", propertyType);
styleFill.addProperty("draw:fill-hatch-name", saveOdfHatchStyle(mainStyles, brush), propertyType);
break;
case Qt::SolidPattern:
styleFill.addProperty("draw:fill", "solid", propertyType);
styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
if (! brush.isOpaque())
styleFill.addProperty("draw:opacity", QString("%1%").arg(brush.color().alphaF() * 100.0), propertyType);
break;
case Qt::NoBrush:
default:
styleFill.addProperty("draw:fill", "none", propertyType);
break;
}
}
void KoOdfGraphicStyles::saveOdfStrokeStyle(KoGenStyle &styleStroke, KoGenStyles &mainStyles, const QPen &pen)
{
// TODO implement all possibilities
switch (pen.style()) {
case Qt::NoPen:
styleStroke.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
return;
case Qt::SolidLine:
styleStroke.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
break;
default: { // must be a dashed line
styleStroke.addProperty("draw:stroke", "dash", KoGenStyle::GraphicType);
// save stroke dash (14.14.7) which is severely limited, but still
KoGenStyle dashStyle(KoGenStyle::StrokeDashStyle);
dashStyle.addAttribute("draw:style", "rect");
QVector<qreal> dashes = pen.dashPattern();
dashStyle.addAttribute("draw:dots1", static_cast<int>(1));
dashStyle.addAttributePt("draw:dots1-length", dashes[0]*pen.widthF());
dashStyle.addAttributePt("draw:distance", dashes[1]*pen.widthF());
if (dashes.size() > 2) {
dashStyle.addAttribute("draw:dots2", static_cast<int>(1));
dashStyle.addAttributePt("draw:dots2-length", dashes[2]*pen.widthF());
}
QString dashStyleName = mainStyles.insert(dashStyle, "dash");
styleStroke.addProperty("draw:stroke-dash", dashStyleName, KoGenStyle::GraphicType);
break;
}
}
if (pen.brush().gradient()) {
styleStroke.addProperty("calligra:stroke-gradient", saveOdfGradientStyle(mainStyles, pen.brush()), KoGenStyle::GraphicType);
}
else {
styleStroke.addProperty("svg:stroke-color", pen.color().name(), KoGenStyle::GraphicType);
styleStroke.addProperty("svg:stroke-opacity", QString("%1").arg(pen.color().alphaF()), KoGenStyle::GraphicType);
}
styleStroke.addPropertyPt("svg:stroke-width", pen.widthF(), KoGenStyle::GraphicType);
switch (pen.joinStyle()) {
case Qt::MiterJoin:
styleStroke.addProperty("draw:stroke-linejoin", "miter", KoGenStyle::GraphicType);
break;
case Qt::BevelJoin:
styleStroke.addProperty("draw:stroke-linejoin", "bevel", KoGenStyle::GraphicType);
break;
case Qt::RoundJoin:
styleStroke.addProperty("draw:stroke-linejoin", "round", KoGenStyle::GraphicType);
break;
default:
styleStroke.addProperty("draw:stroke-linejoin", "miter", KoGenStyle::GraphicType);
styleStroke.addProperty("calligra:stroke-miterlimit", QString("%1").arg(pen.miterLimit()), KoGenStyle::GraphicType);
break;
}
switch (pen.capStyle()) {
case Qt::RoundCap:
styleStroke.addProperty("svg:stroke-linecap", "round", KoGenStyle::GraphicType);
break;
case Qt::SquareCap:
styleStroke.addProperty("svg:stroke-linecap", "square", KoGenStyle::GraphicType);
break;
default:
styleStroke.addProperty("svg:stroke-linecap", "butt", KoGenStyle::GraphicType);
break;
}
}
QString KoOdfGraphicStyles::saveOdfHatchStyle(KoGenStyles& mainStyles, const QBrush &brush)
{
KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/);
hatchStyle.addAttribute("draw:color", brush.color().name());
//hatchStyle.addAttribute( "draw:distance", m_distance ); not implemented into Stage
switch (brush.style()) {
case Qt::HorPattern:
hatchStyle.addAttribute("draw:style", "single");
hatchStyle.addAttribute("draw:rotation", 0);
break;
case Qt::BDiagPattern:
hatchStyle.addAttribute("draw:style", "single");
hatchStyle.addAttribute("draw:rotation", 450);
break;
case Qt::VerPattern:
hatchStyle.addAttribute("draw:style", "single");
hatchStyle.addAttribute("draw:rotation", 900);
break;
case Qt::FDiagPattern:
hatchStyle.addAttribute("draw:style", "single");
hatchStyle.addAttribute("draw:rotation", 1350);
break;
case Qt::CrossPattern:
hatchStyle.addAttribute("draw:style", "double");
hatchStyle.addAttribute("draw:rotation", 0);
break;
case Qt::DiagCrossPattern:
hatchStyle.addAttribute("draw:style", "double");
hatchStyle.addAttribute("draw:rotation", 450);
break;
default:
break;
}
return mainStyles.insert(hatchStyle, "hatch");
}
QString KoOdfGraphicStyles::saveOdfGradientStyle(KoGenStyles &mainStyles, const QBrush &brush)
{
KoGenStyle gradientStyle;
if (brush.style() == Qt::RadialGradientPattern) {
const QRadialGradient *gradient = static_cast<const QRadialGradient*>(brush.gradient());
gradientStyle = KoGenStyle(KoGenStyle::RadialGradientStyle /*no family name*/);
gradientStyle.addAttributePercent("svg:cx", gradient->center().x() * 100);
gradientStyle.addAttributePercent("svg:cy", gradient->center().y() * 100);
gradientStyle.addAttributePercent("svg:r", gradient->radius() * 100);
gradientStyle.addAttributePercent("svg:fx", gradient->focalPoint().x() * 100);
gradientStyle.addAttributePercent("svg:fy", gradient->focalPoint().y() * 100);
} else if (brush.style() == Qt::LinearGradientPattern) {
const QLinearGradient *gradient = static_cast<const QLinearGradient*>(brush.gradient());
gradientStyle = KoGenStyle(KoGenStyle::LinearGradientStyle /*no family name*/);
gradientStyle.addAttributePercent("svg:x1", gradient->start().x() * 100);
gradientStyle.addAttributePercent("svg:y1", gradient->start().y() * 100);
gradientStyle.addAttributePercent("svg:x2", gradient->finalStop().x() * 100);
gradientStyle.addAttributePercent("svg:y2", gradient->finalStop().y() * 100);
} else if (brush.style() == Qt::ConicalGradientPattern) {
const QConicalGradient * gradient = static_cast<const QConicalGradient*>(brush.gradient());
gradientStyle = KoGenStyle(KoGenStyle::ConicalGradientStyle /*no family name*/);
gradientStyle.addAttributePercent("svg:cx", gradient->center().x() * 100);
gradientStyle.addAttributePercent("svg:cy", gradient->center().y() * 100);
gradientStyle.addAttribute("draw:angle", QString("%1").arg(gradient->angle()));
}
const QGradient * gradient = brush.gradient();
if (gradient->spread() == QGradient::RepeatSpread)
gradientStyle.addAttribute("svg:spreadMethod", "repeat");
else if (gradient->spread() == QGradient::ReflectSpread)
gradientStyle.addAttribute("svg:spreadMethod", "reflect");
else
gradientStyle.addAttribute("svg:spreadMethod", "pad");
if (! brush.transform().isIdentity()) {
gradientStyle.addAttribute("svg:gradientTransform", saveTransformation(brush.transform()));
}
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
// save stops
QGradientStops stops = gradient->stops();
foreach(const QGradientStop & stop, stops) {
elementWriter.startElement("svg:stop");
elementWriter.addAttribute("svg:offset", QString("%1").arg(stop.first));
elementWriter.addAttribute("svg:stop-color", stop.second.name());
if (stop.second.alphaF() < 1.0)
elementWriter.addAttribute("svg:stop-opacity", QString("%1").arg(stop.second.alphaF()));
elementWriter.endElement();
}
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
gradientStyle.addChildElement("svg:stop", elementContents);
return mainStyles.insert(gradientStyle, "gradient");
}
QBrush KoOdfGraphicStyles::loadOdfGradientStyle(const KoStyleStack &styleStack, const KoOdfStylesReader & stylesReader, const QSizeF &size)
{
QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name");
return loadOdfGradientStyleByName(stylesReader, styleName, size);
}
qreal percent(const KoXmlElement &element, const QString &ns, const QString &type, const QString &defaultValue, qreal absolute)
{
qreal tmp = 0.0;
QString value = element.attributeNS(ns, type, defaultValue);
if (value.indexOf('%') > -1) { // percent value
tmp = value.remove('%').toDouble() / 100.0;
}
else { // fixed value
tmp = KoUnit::parseValue(value) / absolute;
// The following is done so that we get the same data as when we save/load.
// This is needed that we get the same values due to rounding differences
// of absolute and relative values.
QString value = QString("%1").arg(tmp * 100.0);
tmp = value.toDouble() / 100;
}
return tmp;
}
QBrush KoOdfGraphicStyles::loadOdfGradientStyleByName(const KoOdfStylesReader &stylesReader, const QString &styleName, const QSizeF &size)
{
KoXmlElement* e = stylesReader.drawStyles("gradient").value(styleName);
if (! e)
return QBrush();
QGradient * gradient = 0;
QTransform transform;
if (e->namespaceURI() == KoXmlNS::draw && e->localName() == "gradient") {
// FIXME seems like oo renders the gradient start stop color at the center of the
// radial gradient, and the start color at the radius of the radial gradient
// whereas it is not mentioned in the spec how it should be rendered
// note that svg defines that exactly as the opposite as oo does
// so what should we do?
QString type = e->attributeNS(KoXmlNS::draw, "style", QString());
if (type == "radial") {
// Zagge: at the moment the only objectBoundingBox is supported:
// 18.539 svg:gradientUnits
// See §13.2.2 and §13.2.3 of [SVG].
// The default value for this attribute is objectBoundingBox.
// The only value of the svg:gradientUnits attribute is objectBoundingBox.
qreal cx = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "cx", QString()).remove('%'));
qreal cy = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "cy", QString()).remove('%'));
gradient = new QRadialGradient(QPointF(cx * 0.01, cy * 0.01), sqrt(0.5));
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
} else if (type == "linear" || type == "axial") {
QLinearGradient * lg = new QLinearGradient();
lg->setCoordinateMode(QGradient::ObjectBoundingMode);
// Dividing by 10 here because OOo saves as degree * 10
qreal angle = 90 + e->attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10;
qreal radius = sqrt(0.5);
qreal sx = cos(angle * M_PI / 180) * radius;
qreal sy = sin(angle * M_PI / 180) * radius;
lg->setStart(QPointF(0.5 + sx, 0.5 - sy));
lg->setFinalStop(QPointF(0.5 - sx, 0.5 + sy));
gradient = lg;
} else
return QBrush();
qreal border = 0.01 * e->attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble();
QGradientStops stops;
if (type != "axial") {
// In case of radial gradients the colors are reversed, because OOo saves them as the opposite of the SVG direction
// see bug 137639
QGradientStop start;
start.first = (type != "radial") ? border : 1.0 - border;
start.second = QColor(e->attributeNS(KoXmlNS::draw, "start-color", QString()));
start.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble());
QGradientStop end;
end.first = (type != "radial") ? 1.0 : 0.0;
end.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString()));
end.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
stops << start << end;
} else {
QGradientStop start;
start.first = 0.5 * border;
start.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString()));
start.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
QGradientStop middle;
middle.first = 0.5;
middle.second = QColor(e->attributeNS(KoXmlNS::draw, "start-color", QString()));
middle.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble());
QGradientStop end;
end.first = 1.0 - 0.5 * border;
end.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString()));
end.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
stops << start << middle << end;
}
gradient->setStops(stops);
} else if (e->namespaceURI() == KoXmlNS::svg) {
if (e->localName() == "linearGradient") {
QPointF start, stop;
start.setX(percent(*e, KoXmlNS::svg, "x1", "0%", size.width()));
start.setY(percent(*e, KoXmlNS::svg, "y1", "0%", size.height()));
stop.setX(percent(*e, KoXmlNS::svg, "x2", "100%", size.width()));
stop.setY(percent(*e, KoXmlNS::svg, "y2", "100%", size.height()));
gradient = new QLinearGradient(start, stop);
} else if (e->localName() == "radialGradient") {
QPointF center, focalPoint;
center.setX(percent(*e, KoXmlNS::svg, "cx", "50%", size.width()));
center.setY(percent(*e, KoXmlNS::svg, "cy", "50%", size.height()));
qreal r = percent(*e, KoXmlNS::svg, "r", "50%", sqrt(size.width() * size.width() + size.height() * size.height()));
focalPoint.setX(percent(*e, KoXmlNS::svg, "fx", QString(), size.width()));
focalPoint.setY(percent(*e, KoXmlNS::svg, "fy", QString(), size.height()));
gradient = new QRadialGradient(center, r, focalPoint );
}
if (! gradient)
return QBrush();
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
QString strSpread(e->attributeNS(KoXmlNS::svg, "spreadMethod", "pad"));
if (strSpread == "repeat")
gradient->setSpread(QGradient::RepeatSpread);
else if (strSpread == "reflect")
gradient->setSpread(QGradient::ReflectSpread);
else
gradient->setSpread(QGradient::PadSpread);
if (e->hasAttributeNS(KoXmlNS::svg, "gradientTransform"))
transform = loadTransformation(e->attributeNS(KoXmlNS::svg, "gradientTransform", QString()));
QGradientStops stops;
// load stops
KoXmlElement colorstop;
forEachElement(colorstop, (*e)) {
if (colorstop.namespaceURI() == KoXmlNS::svg && colorstop.localName() == "stop") {
QGradientStop stop;
stop.second = QColor(colorstop.attributeNS(KoXmlNS::svg, "stop-color", QString()));
stop.second.setAlphaF(colorstop.attributeNS(KoXmlNS::svg, "stop-opacity", "1.0").toDouble());
stop.first = colorstop.attributeNS(KoXmlNS::svg, "offset", "0.0").toDouble();
stops.append(stop);
}
}
gradient->setStops(stops);
} else if (e->namespaceURI() == KoXmlNS::calligra) {
if (e->localName() == "conicalGradient") {
QPointF center;
center.setX(percent(*e, KoXmlNS::svg, "cx", "50%", size.width()));
center.setY(percent(*e, KoXmlNS::svg, "cy", "50%", size.height()));
qreal angle = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "angle", QString()));
gradient = new QConicalGradient(center, angle);
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
QString strSpread(e->attributeNS(KoXmlNS::svg, "spreadMethod", "pad"));
if (strSpread == "repeat")
gradient->setSpread(QGradient::RepeatSpread);
else if (strSpread == "reflect")
gradient->setSpread(QGradient::ReflectSpread);
else
gradient->setSpread(QGradient::PadSpread);
if (e->hasAttributeNS(KoXmlNS::svg, "gradientTransform"))
transform = loadTransformation(e->attributeNS(KoXmlNS::svg, "gradientTransform", QString()));
QGradientStops stops;
// load stops
KoXmlElement colorstop;
forEachElement(colorstop, (*e)) {
if (colorstop.namespaceURI() == KoXmlNS::svg && colorstop.localName() == "stop") {
QGradientStop stop;
stop.second = QColor(colorstop.attributeNS(KoXmlNS::svg, "stop-color", QString()));
stop.second.setAlphaF(colorstop.attributeNS(KoXmlNS::svg, "stop-opacity", "1.0").toDouble());
stop.first = colorstop.attributeNS(KoXmlNS::svg, "offset", "0.0").toDouble();
stops.append(stop);
}
}
gradient->setStops(stops);
}
}
if (! gradient)
return QBrush();
QBrush resultBrush(*gradient);
resultBrush.setTransform(transform);
delete gradient;
return resultBrush;
}
QBrush KoOdfGraphicStyles::loadOdfFillStyle(const KoStyleStack &styleStack, const QString & fill, const KoOdfStylesReader & stylesReader)
{
QBrush tmpBrush; // default brush for "none" is a Qt::NoBrush
if (fill == "solid") {
tmpBrush.setStyle(Qt::SolidPattern);
if (styleStack.hasProperty(KoXmlNS::draw, "fill-color"))
tmpBrush.setColor(styleStack.property(KoXmlNS::draw, "fill-color"));
if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) {
QString opacity = styleStack.property(KoXmlNS::draw, "opacity");
if (! opacity.isEmpty() && opacity.right(1) == "%") {
float percent = opacity.leftRef(opacity.length() - 1).toFloat();
QColor color = tmpBrush.color();
color.setAlphaF(percent / 100.0);
tmpBrush.setColor(color);
}
}
//TODO
if (styleStack.hasProperty(KoXmlNS::draw, "transparency")) {
QString transparency = styleStack.property(KoXmlNS::draw, "transparency");
if (transparency == "94%") {
tmpBrush.setStyle(Qt::Dense1Pattern);
} else if (transparency == "88%") {
tmpBrush.setStyle(Qt::Dense2Pattern);
} else if (transparency == "63%") {
tmpBrush.setStyle(Qt::Dense3Pattern);
} else if (transparency == "50%") {
tmpBrush.setStyle(Qt::Dense4Pattern);
} else if (transparency == "37%") {
tmpBrush.setStyle(Qt::Dense5Pattern);
} else if (transparency == "12%") {
tmpBrush.setStyle(Qt::Dense6Pattern);
} else if (transparency == "6%") {
tmpBrush.setStyle(Qt::Dense7Pattern);
} else
debugOdf << " transparency is not defined into Stage :" << transparency;
}
} else if (fill == "hatch") {
QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name");
debugOdf << " hatch style is :" << style;
//type not defined by default
//try to use style.
KoXmlElement* draw = stylesReader.drawStyles("hatch").value(style);
if (draw) {
debugOdf << "We have a style";
int angle = 0;
if (draw->hasAttributeNS(KoXmlNS::draw, "rotation")) {
angle = (draw->attributeNS(KoXmlNS::draw, "rotation", QString()).toInt()) / 10;
debugOdf << "angle :" << angle;
}
if (draw->hasAttributeNS(KoXmlNS::draw, "color")) {
//debugOdf<<" draw:color :"<<draw->attributeNS( KoXmlNS::draw,"color", QString() );
tmpBrush.setColor(draw->attributeNS(KoXmlNS::draw, "color", QString()));
}
if (draw->hasAttributeNS(KoXmlNS::draw, "distance")) {
//todo implemente it into Stage
}
if (draw->hasAttributeNS(KoXmlNS::draw, "display-name")) {
//todo implement it into Stage
}
if (draw->hasAttributeNS(KoXmlNS::draw, "style")) {
//todo implemente it into Stage
QString styleHash = draw->attributeNS(KoXmlNS::draw, "style", QString());
if (styleHash == "single") {
switch (angle) {
case 0:
case 180:
tmpBrush.setStyle(Qt::HorPattern);
break;
case 45:
case 225:
tmpBrush.setStyle(Qt::BDiagPattern);
break;
case 90:
case 270:
tmpBrush.setStyle(Qt::VerPattern);
break;
case 135:
case 315:
tmpBrush.setStyle(Qt::FDiagPattern);
break;
default:
//todo fixme when we will have a kopaint
debugOdf << " draw:rotation 'angle' :" << angle;
break;
}
} else if (styleHash == "double") {
switch (angle) {
case 0:
case 180:
case 90:
case 270:
tmpBrush.setStyle(Qt::CrossPattern);
break;
case 45:
case 135:
case 225:
case 315:
tmpBrush.setStyle(Qt::DiagCrossPattern);
break;
default:
//todo fixme when we will have a kopaint
debugOdf << " draw:rotation 'angle' :" << angle;
break;
}
} else if (styleHash == "triple") {
debugOdf << " it is not implemented :(";
}
}
}
}
return tmpBrush;
}
static qreal parseDashEntrySize(QString& attr, qreal penWidth, qreal defaultValue = 0.0){
qreal result = defaultValue;
if (attr.endsWith('%')) {
bool ok;
const int percent = attr.remove('%').toInt(&ok);
if (ok && percent >= 0) {
result = percent / 100.0;
}
} else {
result = KoUnit::parseValue(attr) / penWidth;
}
return result;
}
QPen KoOdfGraphicStyles::loadOdfStrokeStyle(const KoStyleStack &styleStack, const QString & stroke, const KoOdfStylesReader & stylesReader)
{
QPen tmpPen(Qt::NoPen); // default pen for "none" is a Qt::NoPen
if (stroke == "solid" || stroke == "dash") {
// If solid or dash is set then we assume that the color is black and the penWidth
// is zero till defined otherwise with the following attributes.
tmpPen = QPen();
if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color"))
tmpPen.setColor(styleStack.property(KoXmlNS::svg, "stroke-color"));
if (styleStack.hasProperty(KoXmlNS::svg, "stroke-opacity")) {
QColor color = tmpPen.color();
QString opacity = styleStack.property(KoXmlNS::svg, "stroke-opacity");
if (opacity.endsWith('%'))
color.setAlphaF(0.01 * opacity.remove('%').toDouble());
else
color.setAlphaF(opacity.toDouble());
tmpPen.setColor(color);
}
if (styleStack.hasProperty(KoXmlNS::svg, "stroke-width"))
tmpPen.setWidthF(KoUnit::parseValue(styleStack.property(KoXmlNS::svg, "stroke-width")));
if (styleStack.hasProperty(KoXmlNS::draw, "stroke-linejoin")) {
QString join = styleStack.property(KoXmlNS::draw, "stroke-linejoin");
if (join == "bevel")
tmpPen.setJoinStyle(Qt::BevelJoin);
else if (join == "round")
tmpPen.setJoinStyle(Qt::RoundJoin);
else {
tmpPen.setJoinStyle(Qt::MiterJoin);
if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-miterlimit")) {
QString miterLimit = styleStack.property(KoXmlNS::calligra, "stroke-miterlimit");
tmpPen.setMiterLimit(miterLimit.toDouble());
}
}
}
if (styleStack.hasProperty(KoXmlNS::svg, "stroke-linecap")) {
const QString cap = styleStack.property(KoXmlNS::svg, "stroke-linecap");
if (cap == "round")
tmpPen.setCapStyle(Qt::RoundCap);
else if (cap == "square")
tmpPen.setCapStyle(Qt::SquareCap);
else
tmpPen.setCapStyle(Qt::FlatCap);
} else {
// default as per svg specification
tmpPen.setCapStyle(Qt::FlatCap);
}
if (stroke == "dash" && styleStack.hasProperty(KoXmlNS::draw, "stroke-dash")) {
QString dashStyleName = styleStack.property(KoXmlNS::draw, "stroke-dash");
// set width to 1 in case it is 0 as dividing by 0 gives infinity
qreal width = tmpPen.widthF();
if ( width == 0 ) {
width = 1;
}
KoXmlElement * dashElement = stylesReader.drawStyles("stroke-dash").value(dashStyleName);
if (dashElement) {
QVector<qreal> dashes;
if (dashElement->hasAttributeNS(KoXmlNS::draw, "dots1")) {
QString distance( dashElement->attributeNS(KoXmlNS::draw, "distance", QString()) );
qreal space = parseDashEntrySize(distance, width, 0.0);
QString dots1Length(dashElement->attributeNS(KoXmlNS::draw, "dots1-length", QString()));
qreal dot1Length = parseDashEntrySize(dots1Length,width,1.0);
bool ok;
int dots1 = dashElement->attributeNS(KoXmlNS::draw, "dots1").toInt(&ok);
if (!ok) {
dots1 = 1;
}
for (int i = 0; i < dots1; i++) {
dashes.append(dot1Length);
dashes.append(space);
}
if (dashElement->hasAttributeNS(KoXmlNS::draw, "dots2")) {
QString dots2Length(dashElement->attributeNS(KoXmlNS::draw, "dots2-length", QString()));
qreal dot2Length = parseDashEntrySize(dots2Length,width,1.0);
int dots2 = dashElement->attributeNS(KoXmlNS::draw, "dots2").toInt(&ok);
if (!ok) {
dots2 = 1;
}
for (int i = 0; i < dots2; i++) {
dashes.append(dot2Length);
dashes.append(space);
}
}
tmpPen.setDashPattern(dashes);
}
}
}
}
return tmpPen;
}
QTransform KoOdfGraphicStyles::loadTransformation(const QString &transformation)
{
QTransform transform;
// Split string for handling 1 transform statement at a time
QStringList subtransforms = transformation.split(')', QString::SkipEmptyParts);
QStringList::ConstIterator it = subtransforms.constBegin();
QStringList::ConstIterator end = subtransforms.constEnd();
for (; it != end; ++it) {
QStringList subtransform = (*it).split('(', QString::SkipEmptyParts);
subtransform[0] = subtransform[0].trimmed().toLower();
subtransform[1] = subtransform[1].simplified();
QRegExp reg("[,( ]");
QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts);
if (subtransform[0].startsWith(';') || subtransform[0].startsWith(','))
subtransform[0] = subtransform[0].right(subtransform[0].length() - 1);
if (subtransform[0] == "rotate") {
// TODO find out what oo2 really does when rotating, it seems severely broken
if (params.count() == 3) {
qreal x = KoUnit::parseValue(params[1]);
qreal y = KoUnit::parseValue(params[2]);
transform.translate(x, y);
// oo2 rotates by radians
transform.rotate(params[0].toDouble()*180.0 / M_PI);
transform.translate(-x, -y);
} else {
// oo2 rotates by radians
transform.rotate(params[0].toDouble()*180.0 / M_PI);
}
} else if (subtransform[0] == "translate") {
if (params.count() == 2) {
qreal x = KoUnit::parseValue(params[0]);
qreal y = KoUnit::parseValue(params[1]);
transform.translate(x, y);
} else // Spec : if only one param given, assume 2nd param to be 0
transform.translate(KoUnit::parseValue(params[0]) , 0);
} else if (subtransform[0] == "scale") {
if (params.count() == 2)
transform.scale(params[0].toDouble(), params[1].toDouble());
else // Spec : if only one param given, assume uniform scaling
transform.scale(params[0].toDouble(), params[0].toDouble());
} else if (subtransform[0] == "skewx")
transform.shear(tan(params[0].toDouble()), 0.0F);
else if (subtransform[0] == "skewy")
transform.shear(tan(params[0].toDouble()), 0.0F);
else if (subtransform[0] == "matrix") {
if (params.count() >= 6) {
transform.setMatrix(params[0].toDouble(), params[1].toDouble(), 0,
params[2].toDouble(), params[3].toDouble(), 0,
KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1);
}
}
}
return transform;
}
QString KoOdfGraphicStyles::saveTransformation(const QTransform &transformation, bool appendTranslateUnit)
{
QString transform;
if (appendTranslateUnit)
transform = QString("matrix(%1 %2 %3 %4 %5pt %6pt)")
.arg(transformation.m11()).arg(transformation.m12())
.arg(transformation.m21()).arg(transformation.m22())
.arg(transformation.dx()) .arg(transformation.dy());
else
transform = QString("matrix(%1 %2 %3 %4 %5 %6)")
.arg(transformation.m11()).arg(transformation.m12())
.arg(transformation.m21()).arg(transformation.m22())
.arg(transformation.dx()) .arg(transformation.dy());
return transform;
}
diff --git a/src/libs/odf/KoOdfLineNumberingConfiguration.cpp b/src/libs/odf/KoOdfLineNumberingConfiguration.cpp
index e5f364d0..2ac609ca 100644
--- a/src/libs/odf/KoOdfLineNumberingConfiguration.cpp
+++ b/src/libs/odf/KoOdfLineNumberingConfiguration.cpp
@@ -1,280 +1,281 @@
/* This file is part of the KDE project
Copyright (C) 2010 KO GmbH <boud@kogmbh.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoOdfLineNumberingConfiguration.h"
#include <OdfDebug.h>
#include "KoXmlNS.h"
#include "KoUnit.h"
#include "KoXmlWriter.h"
#include "KoOdfNumberDefinition.h"
class Q_DECL_HIDDEN KoOdfLineNumberingConfiguration::Private
{
public:
bool lineNumberingEnabled;
KoOdfNumberDefinition numberFormat;
QString textStyle;
int increment;
Position position;
int offset;
bool countEmptyLines;
bool countLinesInTextBoxes;
bool restartNumberingOnEveryPage;
QString separator;
int separatorIncrement;
};
KoOdfLineNumberingConfiguration::KoOdfLineNumberingConfiguration()
: d(new Private())
{
d->lineNumberingEnabled = false;
d->increment = 1;
d->position = Left;
d->offset = 10;
d->countEmptyLines = false;
d->countLinesInTextBoxes = false;
d->separatorIncrement = 5;
}
KoOdfLineNumberingConfiguration::~KoOdfLineNumberingConfiguration()
{
delete d;
}
KoOdfLineNumberingConfiguration::KoOdfLineNumberingConfiguration(const KoOdfLineNumberingConfiguration &other)
: QObject(), d(new Private())
{
d->lineNumberingEnabled = other.d->lineNumberingEnabled;
d->numberFormat = other.d->numberFormat;
d->textStyle = other.d->textStyle;
d->increment = other.d->increment;
d->position = other.d->position;
d->offset = other.d->offset;
d->countEmptyLines = other.d->countEmptyLines;
d->countLinesInTextBoxes = other.d->countLinesInTextBoxes;
d->restartNumberingOnEveryPage = other.d->restartNumberingOnEveryPage;
d->separator = other.d->separator;
d->separatorIncrement = other.d->separatorIncrement;
}
KoOdfLineNumberingConfiguration &KoOdfLineNumberingConfiguration::operator=(const KoOdfLineNumberingConfiguration &other)
{
d->lineNumberingEnabled = other.d->lineNumberingEnabled;
d->numberFormat = other.d->numberFormat;
d->textStyle = other.d->textStyle;
d->increment = other.d->increment;
d->position = other.d->position;
d->offset = other.d->offset;
d->countEmptyLines = other.d->countEmptyLines;
d->countLinesInTextBoxes = other.d->countLinesInTextBoxes;
d->restartNumberingOnEveryPage = other.d->restartNumberingOnEveryPage;
d->separator = other.d->separator;
d->separatorIncrement = other.d->separatorIncrement;
return *this;
}
void KoOdfLineNumberingConfiguration::loadOdf(const KoXmlElement &element)
{
d->lineNumberingEnabled = element.attributeNS(KoXmlNS::text, "number-lines", "true") == "true";
d->numberFormat.loadOdf(element);
d->textStyle = element.attributeNS(KoXmlNS::text, "style-name", QString());
d->increment = KoUnit::parseValue(element.attributeNS(KoXmlNS::text, "increment", "1"));
QString position = element.attributeNS(KoXmlNS::text, "position", "left");
if (position == "left") {
d->position = Left;
}
else if (position == "right") {
d->position = Right;
}
else if (position == "inner") {
d->position = Inner;
}
else if (position == "outer") {
d->position = Outer;
}
d->offset = KoUnit::parseValue(element.attributeNS(KoXmlNS::text, "offset", "10"));
d->countEmptyLines = element.attributeNS(KoXmlNS::text, "count-empty-lines", "false") == "true";
d->countLinesInTextBoxes = element.attributeNS(KoXmlNS::text, "count-in-text-boxes", "false") == "true";
d->restartNumberingOnEveryPage = element.attributeNS(KoXmlNS::text, "restart-on-page", "false") == "true";
if(element.hasChildNodes()) {
KoXmlNode node = element.firstChild();
while(!node.isNull()) {
if(node.isElement()) {
KoXmlElement nodeElement = node.toElement();
if(nodeElement.localName() == "linenumber-separator") {
d->separator = nodeElement.text();
d->separatorIncrement = KoUnit::parseValue(element.attributeNS(KoXmlNS::text, "increment", "10"));
break;
}
}
node = node.nextSibling();
}
}
}
void KoOdfLineNumberingConfiguration::saveOdf(KoXmlWriter *writer) const
{
writer->addAttribute("text:number-lines", "true");
d->numberFormat.saveOdf(writer);
if (!d->textStyle.isEmpty()) {
writer->addAttribute("text:style-name", d->textStyle);
}
writer->addAttribute("text:increment", d->increment);
switch(d->position) {
case Left:
break; // this is default, don't save
case Right:
writer->addAttribute("text:position", "right");
break;
case Inner:
writer->addAttribute("text:position", "inner");
break;
case Outer:
writer->addAttribute("text:position", "outer");
break;
}
if (d->offset != 10) { writer->addAttribute("text:offset", d->offset); }
if (d->countEmptyLines) { writer->addAttribute("text:count-empty-lines", d->countEmptyLines); }
if (d->countLinesInTextBoxes) { writer->addAttribute("text:count-in-text-boxes", d->countLinesInTextBoxes); }
if (d->restartNumberingOnEveryPage) { writer->addAttribute("text:restart-on-page", d->restartNumberingOnEveryPage); }
if (!d->separator.isNull()) {
writer->startElement("txt:linenumber-separator");
if (d->separatorIncrement != 10) { writer->addAttribute("text:increment", d->separatorIncrement); }
writer->addTextNode(d->separator);
writer->endElement();
}
}
bool KoOdfLineNumberingConfiguration::enabled() const
{
return d->lineNumberingEnabled;
}
void KoOdfLineNumberingConfiguration::setEnabled(bool enabled)
{
d->lineNumberingEnabled = enabled;
}
KoOdfNumberDefinition KoOdfLineNumberingConfiguration::numberFormat() const
{
return d->numberFormat;
}
void KoOdfLineNumberingConfiguration::setNumberFormat(const KoOdfNumberDefinition &numberFormat)
{
d->numberFormat = numberFormat;
}
QString KoOdfLineNumberingConfiguration::textStyle() const
{
return d->textStyle;
}
void KoOdfLineNumberingConfiguration::setTextStyle(const QString &textStyle)
{
d->textStyle = textStyle;
}
int KoOdfLineNumberingConfiguration::increment() const
{
return d->increment;
}
void KoOdfLineNumberingConfiguration::setIncrement(int increment)
{
d->increment = increment;
}
KoOdfLineNumberingConfiguration::Position KoOdfLineNumberingConfiguration::position() const
{
return d->position;
}
void KoOdfLineNumberingConfiguration::setPosition(KoOdfLineNumberingConfiguration::Position position)
{
d->position = position;
}
int KoOdfLineNumberingConfiguration::offset() const
{
return d->offset;
}
void KoOdfLineNumberingConfiguration::setOffset(int offset)
{
d->offset = offset;
}
bool KoOdfLineNumberingConfiguration::countEmptyLines() const
{
return d->countEmptyLines;
}
void KoOdfLineNumberingConfiguration::setCountEmptyLines(bool countEmptyLines)
{
d->countEmptyLines = countEmptyLines;
}
bool KoOdfLineNumberingConfiguration::countLinesInTextBoxes() const
{
return d->countLinesInTextBoxes;
}
void KoOdfLineNumberingConfiguration::setCountLinesInTextBoxes(bool countLinesInTextBoxes)
{
d->countLinesInTextBoxes = countLinesInTextBoxes;
}
bool KoOdfLineNumberingConfiguration::restartNumberingOnEveryPage() const
{
return d->restartNumberingOnEveryPage;
}
void KoOdfLineNumberingConfiguration::setRestartNumberingOnEveryPage(bool restartNumberingOnEveryPage)
{
d->restartNumberingOnEveryPage = restartNumberingOnEveryPage;
}
QString KoOdfLineNumberingConfiguration::separator() const
{
return d->separator;
}
void KoOdfLineNumberingConfiguration::setSeparator(const QString &separator)
{
d->separator = separator;
}
int KoOdfLineNumberingConfiguration::separatorIncrement() const
{
return d->separatorIncrement;
}
void KoOdfLineNumberingConfiguration::setSeparatorIncrement(int separatorIncrement)
{
d->separatorIncrement = separatorIncrement;
}
diff --git a/src/libs/odf/KoOdfLoadingContext.cpp b/src/libs/odf/KoOdfLoadingContext.cpp
index 8339243e..f936e15b 100644
--- a/src/libs/odf/KoOdfLoadingContext.cpp
+++ b/src/libs/odf/KoOdfLoadingContext.cpp
@@ -1,354 +1,355 @@
/* This file is part of the KDE project
Copyright (C) 2005 David Faure <faure@kde.org>
Copyright (C) 2010 Inge Wallin <inge@lysator.liu.se>
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.
*/
// Own
+// clazy:excludeall=qstring-arg
#include "KoOdfLoadingContext.h"
#include "OdfDebug.h"
// Calligra
#include <KoOdfReadStore.h>
#include <KoOdfStylesReader.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoXmlNS.h>
#include <KoOdfManifestEntry.h>
#include "KoStyleStack.h"
// Qt
#include <QStandardPaths>
#include <QMimeDatabase>
#include <QMimeType>
class Q_DECL_HIDDEN KoOdfLoadingContext::Private
{
public:
Private(KoOdfStylesReader &sr, KoStore *s)
: store(s),
stylesReader(sr),
generatorType(KoOdfLoadingContext::Unknown),
metaXmlParsed(false),
useStylesAutoStyles(false)
{
}
~Private() {
qDeleteAll(manifestEntries);
}
KoStore *store;
KoOdfStylesReader &stylesReader;
KoStyleStack styleStack;
mutable QString generator;
GeneratorType generatorType;
mutable bool metaXmlParsed;
bool useStylesAutoStyles;
KoXmlDocument manifestDoc;
QHash<QString, KoOdfManifestEntry *> manifestEntries;
KoOdfStylesReader defaultStylesReader;
KoXmlDocument doc; // the doc needs to be kept around so it is possible to access the styles
};
KoOdfLoadingContext::KoOdfLoadingContext(KoOdfStylesReader &stylesReader, KoStore* store, const QString &defaultStylesResourcePath)
: d(new Private(stylesReader, store))
{
// Ideally this should be done by KoDocument and passed as argument here...
KoOdfReadStore oasisStore(store);
QString dummy;
(void)oasisStore.loadAndParse("tar:/META-INF/manifest.xml", d->manifestDoc, dummy);
if (!defaultStylesResourcePath.isEmpty()) {
Q_ASSERT(defaultStylesResourcePath.endsWith(QLatin1Char('/')));
const QString fileName =
QStandardPaths::locate(QStandardPaths::GenericDataLocation,
defaultStylesResourcePath + "defaultstyles.xml");
if ( ! fileName.isEmpty() ) {
QFile file( fileName );
QString errorMessage;
if ( KoOdfReadStore::loadAndParse( &file, d->doc, errorMessage, fileName ) ) {
d->defaultStylesReader.createStyleMap( d->doc, true );
}
else {
warnOdf << "reading of defaultstyles.xml failed:" << errorMessage;
}
}
else {
warnOdf << "defaultstyles.xml not found";
}
}
if (!parseManifest(d->manifestDoc)) {
debugOdf << "could not parse manifest document";
}
}
KoOdfLoadingContext::~KoOdfLoadingContext()
{
delete d;
}
void KoOdfLoadingContext::setManifestFile(const QString& fileName) {
KoOdfReadStore oasisStore(d->store);
QString dummy;
(void)oasisStore.loadAndParse(fileName, d->manifestDoc, dummy);
if (!parseManifest(d->manifestDoc)) {
debugOdf << "could not parse manifest document";
}
}
void KoOdfLoadingContext::fillStyleStack(const KoXmlElement& object, const QString &nsURI, const QString &attrName, const QString &family)
{
// find all styles associated with an object and push them on the stack
if (object.hasAttributeNS(nsURI, attrName)) {
const QString styleName = object.attributeNS(nsURI, attrName, QString());
const KoXmlElement * style = d->stylesReader.findStyle(styleName, family, d->useStylesAutoStyles);
if (style)
addStyles(style, family, d->useStylesAutoStyles);
else
warnOdf << "style" << styleName << "not found in" << (d->useStylesAutoStyles ? "styles.xml" : "content.xml");
}
}
void KoOdfLoadingContext::addStyles(const KoXmlElement* style, const QString &family, bool usingStylesAutoStyles)
{
Q_ASSERT(style);
if (!style) return;
// this recursive function is necessary as parent styles can have parents themselves
if (style->hasAttributeNS(KoXmlNS::style, "parent-style-name")) {
const QString parentStyleName = style->attributeNS(KoXmlNS::style, "parent-style-name", QString());
const KoXmlElement* parentStyle = d->stylesReader.findStyle(parentStyleName, family, usingStylesAutoStyles);
if (parentStyle)
addStyles(parentStyle, family, usingStylesAutoStyles);
else {
warnOdf << "Parent style not found: " << family << parentStyleName << usingStylesAutoStyles;
//we are handling a non compliant odf file. let's at the very least load the application default, and the eventual odf default
if (!family.isEmpty()) {
const KoXmlElement* def = d->stylesReader.defaultStyle(family);
if (def) { // then, the default style for this family
d->styleStack.push(*def);
}
}
}
} else if (!family.isEmpty()) {
const KoXmlElement* def = d->stylesReader.defaultStyle(family);
if (def) { // then, the default style for this family
d->styleStack.push(*def);
}
}
//debugOdf <<"pushing style" << style->attributeNS( KoXmlNS::style,"name", QString() );
d->styleStack.push(*style);
}
void KoOdfLoadingContext::parseGenerator() const
{
// Regardless of whether we cd into the parent directory
// or not to find a meta.xml, restore the directory that
// we were in afterwards.
d->store->pushDirectory();
// Some embedded documents to not contain their own meta.xml
// Use the parent directory's instead.
if (!d->store->hasFile("meta.xml"))
// Only has an effect if there is a parent directory
d->store->leaveDirectory();
if (d->store->hasFile("meta.xml")) {
KoXmlDocument metaDoc;
KoOdfReadStore oasisStore(d->store);
QString errorMsg;
if (oasisStore.loadAndParse("meta.xml", metaDoc, errorMsg)) {
KoXmlNode meta = KoXml::namedItemNS(metaDoc, KoXmlNS::office, "document-meta");
KoXmlNode office = KoXml::namedItemNS(meta, KoXmlNS::office, "meta");
KoXmlElement generator = KoXml::namedItemNS(office, KoXmlNS::meta, "generator");
if (!generator.isNull()) {
d->generator = generator.text();
if (d->generator.startsWith(QLatin1String("Calligra"))) {
d->generatorType = Calligra;
}
// NeoOffice is a port of OpenOffice to Mac OS X
else if (d->generator.startsWith(QLatin1String("OpenOffice.org")) ||
d->generator.startsWith(QLatin1String("NeoOffice")) ||
d->generator.startsWith(QLatin1String("LibreOffice")) ||
d->generator.startsWith(QLatin1String("StarOffice")) ||
d->generator.startsWith(QLatin1String("Lotus Symphony"))) {
d->generatorType = OpenOffice;
}
else if (d->generator.startsWith(QLatin1String("MicrosoftOffice"))) {
d->generatorType = MicrosoftOffice;
}
}
}
}
d->metaXmlParsed = true;
d->store->popDirectory();
}
QString KoOdfLoadingContext::generator() const
{
if (!d->metaXmlParsed && d->store) {
parseGenerator();
}
return d->generator;
}
KoOdfLoadingContext::GeneratorType KoOdfLoadingContext::generatorType() const
{
if (!d->metaXmlParsed && d->store) {
parseGenerator();
}
return d->generatorType;
}
KoStore *KoOdfLoadingContext::store() const
{
return d->store;
}
KoOdfStylesReader &KoOdfLoadingContext::stylesReader()
{
return d->stylesReader;
}
/**
* Get the application default styles styleReader
*/
KoOdfStylesReader &KoOdfLoadingContext::defaultStylesReader()
{
return d->defaultStylesReader;
}
KoStyleStack &KoOdfLoadingContext::styleStack() const
{
return d->styleStack;
}
void KoOdfLoadingContext::setUseStylesAutoStyles(bool useStylesAutoStyles)
{
d->useStylesAutoStyles = useStylesAutoStyles;
}
bool KoOdfLoadingContext::useStylesAutoStyles() const
{
return d->useStylesAutoStyles;
}
QString KoOdfLoadingContext::mimeTypeForPath(const QString& path, bool guess) const
{
QHash<QString, KoOdfManifestEntry *>::ConstIterator it(d->manifestEntries.constFind(path));
if (it == d->manifestEntries.constEnd()) {
// try to find it with an added / at the end
QString dirPath = path + '/';
it = d->manifestEntries.constFind(dirPath);
}
if (it != d->manifestEntries.constEnd()) {
QString mimeType = it.value()->mediaType();
// figure out mimetype by content if it is not provided
if (mimeType.isEmpty() && guess) {
Q_ASSERT(!d->store->isOpen());
if (d->store->open(path)) {
KoStoreDevice device(d->store);
QByteArray data = device.read(16384);
d->store->close();
QMimeDatabase db;
QMimeType mtp = db.mimeTypeForData(data);
mimeType = mtp.name();
if (!mimeType.isEmpty()) {
it.value()->setMediaType(mimeType);
}
}
}
return mimeType;
}
else {
return QString();
}
}
QList<KoOdfManifestEntry*> KoOdfLoadingContext::manifestEntries() const
{
return d->manifestEntries.values();
}
bool KoOdfLoadingContext::parseManifest(const KoXmlDocument &manifestDocument)
{
// First find the manifest:manifest node.
KoXmlNode n = manifestDocument.firstChild();
debugOdf << "Searching for manifest:manifest " << n.toElement().nodeName();
for (; !n.isNull(); n = n.nextSibling()) {
if (!n.isElement()) {
debugOdf << "NOT element";
continue;
} else {
debugOdf << "element";
}
debugOdf << "name:" << n.toElement().localName()
<< "namespace:" << n.toElement().namespaceURI();
if (n.toElement().localName() == "manifest"
&& n.toElement().namespaceURI() == KoXmlNS::manifest)
{
debugOdf << "found manifest:manifest";
break;
}
}
if (n.isNull()) {
debugOdf << "Could not find manifest:manifest";
return false;
}
// Now loop through the children of the manifest:manifest and
// store all the manifest:file-entry elements.
const KoXmlElement manifestElement = n.toElement();
for (n = manifestElement.firstChild(); !n.isNull(); n = n.nextSibling()) {
if (!n.isElement())
continue;
KoXmlElement el = n.toElement();
if (!(el.localName() == "file-entry" && el.namespaceURI() == KoXmlNS::manifest))
continue;
QString fullPath = el.attributeNS(KoXmlNS::manifest, "full-path", QString());
QString mediaType = el.attributeNS(KoXmlNS::manifest, "media-type", QString(""));
QString version = el.attributeNS(KoXmlNS::manifest, "version", QString());
// Only if fullPath is valid, should we store this entry.
// If not, we don't bother to find out exactly what is wrong, we just skip it.
if (!fullPath.isNull()) {
d->manifestEntries.insert(fullPath,
new KoOdfManifestEntry(fullPath, mediaType, version));
}
}
return true;
}
diff --git a/src/libs/odf/KoOdfManifestEntry.cpp b/src/libs/odf/KoOdfManifestEntry.cpp
index 27ecf239..cfbe38c6 100644
--- a/src/libs/odf/KoOdfManifestEntry.cpp
+++ b/src/libs/odf/KoOdfManifestEntry.cpp
@@ -1,101 +1,102 @@
/* This file is part of the KDE project
Copyright (C) 2011 Inge Wallin <inge@lysator.liu.se>
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.
*/
// Own
+// clazy:excludeall=qstring-arg
#include "KoOdfManifestEntry.h"
#include <QString>
class Q_DECL_HIDDEN KoOdfManifestEntry::Private
{
public:
Private() {};
QString fullPath; // manifest:full-path
QString mediaType; // manifest:media-type
QString version; // manifest:version (isNull==true if not present)
};
// ----------------------------------------------------------------
KoOdfManifestEntry::KoOdfManifestEntry(const QString &fullPath, const QString &mediaType,
const QString &version)
: d(new Private())
{
d->fullPath = fullPath;
d->mediaType = mediaType;
d->version = version;
}
KoOdfManifestEntry::KoOdfManifestEntry(const KoOdfManifestEntry &other)
: d(new Private())
{
d->fullPath = other.d->fullPath;
d->mediaType = other.d->mediaType;
d->version = other.d->version;
}
KoOdfManifestEntry::~KoOdfManifestEntry()
{
delete d;
}
KoOdfManifestEntry &KoOdfManifestEntry::operator=(const KoOdfManifestEntry &other)
{
d->fullPath = other.d->fullPath;
d->mediaType = other.d->mediaType;
d->version = other.d->version;
return *this;
}
QString KoOdfManifestEntry::fullPath() const
{
return d->fullPath;
}
void KoOdfManifestEntry::setFullPath(const QString &fullPath)
{
d->fullPath = fullPath;
}
QString KoOdfManifestEntry::mediaType() const
{
return d->mediaType;
}
void KoOdfManifestEntry::setMediaType(const QString &mediaType)
{
d->mediaType = mediaType;
}
QString KoOdfManifestEntry::version() const
{
return d->version;
}
void KoOdfManifestEntry::setVersion(const QString &version)
{
d->version = version;
}
diff --git a/src/libs/odf/KoOdfNotesConfiguration.cpp b/src/libs/odf/KoOdfNotesConfiguration.cpp
index e0494897..3311f389 100644
--- a/src/libs/odf/KoOdfNotesConfiguration.cpp
+++ b/src/libs/odf/KoOdfNotesConfiguration.cpp
@@ -1,341 +1,342 @@
/* This file is part of the KDE project
Copyright (C) 2010 Boudewijn Rempt
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoOdfNotesConfiguration.h"
#include <OdfDebug.h>
#include "KoXmlNS.h"
#include "KoXmlWriter.h"
#include "KoOdfNumberDefinition.h"
class Q_DECL_HIDDEN KoOdfNotesConfiguration::Private
{
public:
KoOdfNotesConfiguration::NoteClass noteClass;
QString citationTextStyleName;
QString citationBodyTextStyleName;
QString defaultNoteParagraphStyleName;
void *citationTextStyle;
void *citationBodyTextStyle;
void *defaultNoteParagraphStyle;
QString masterPageName;
int startValue;
KoOdfNumberDefinition numberFormat;
KoOdfNotesConfiguration::NumberingScheme numberingScheme;
KoOdfNotesConfiguration::FootnotesPosition footnotesPosition;
QString footnotesContinuationForward;
QString footnotesContinuationBackward;
};
KoOdfNotesConfiguration::KoOdfNotesConfiguration(NoteClass noteClass)
: d(new Private())
{
d->noteClass = noteClass;
d->startValue = 1;
d->numberingScheme = BeginAtDocument;
d->footnotesPosition = Page;
d->defaultNoteParagraphStyle = 0;
d->citationTextStyle = 0;
d->citationBodyTextStyle = 0;
if (noteClass == KoOdfNotesConfiguration::Footnote) {
d->numberFormat.setFormatSpecification(KoOdfNumberDefinition::Numeric);
d->defaultNoteParagraphStyleName = "Footnote";
d->citationTextStyleName = "Footnote_20_Symbol";
d->citationBodyTextStyleName = "Footnote_20_anchor";
} else {
d->numberFormat.setFormatSpecification(KoOdfNumberDefinition::RomanLowerCase);
d->defaultNoteParagraphStyleName = "Endnote";
d->citationTextStyleName = "Endnote_20_Symbol";
d->citationBodyTextStyleName = "Endnote_20_anchor";
}
}
KoOdfNotesConfiguration::~KoOdfNotesConfiguration()
{
delete d;
}
KoOdfNotesConfiguration::KoOdfNotesConfiguration(const KoOdfNotesConfiguration &other)
: QObject(), d(new Private())
{
d->noteClass = other.d->noteClass;
d->citationTextStyleName = other.d->citationTextStyleName;
d->citationBodyTextStyleName = other.d->citationBodyTextStyleName;
d->defaultNoteParagraphStyleName = other.d->defaultNoteParagraphStyleName;
d->citationTextStyle = other.d->citationTextStyle;
d->citationBodyTextStyle = other.d->citationBodyTextStyle;
d->defaultNoteParagraphStyle = other.d->defaultNoteParagraphStyle;
d->masterPageName = other.d->masterPageName;
d->startValue = other.d->startValue;
d->numberFormat = other.d->numberFormat;
d->numberingScheme = other.d->numberingScheme;
d->footnotesPosition = other.d->footnotesPosition;
d->footnotesContinuationForward = other.d->footnotesContinuationForward;
d->footnotesContinuationBackward = other.d->footnotesContinuationBackward;
}
KoOdfNotesConfiguration &KoOdfNotesConfiguration::operator=(const KoOdfNotesConfiguration &other)
{
d->noteClass = other.d->noteClass;
d->citationTextStyleName = other.d->citationTextStyleName;
d->citationBodyTextStyleName = other.d->citationBodyTextStyleName;
d->defaultNoteParagraphStyleName = other.d->defaultNoteParagraphStyleName;
d->citationTextStyle = other.d->citationTextStyle;
d->citationBodyTextStyle = other.d->citationBodyTextStyle;
d->defaultNoteParagraphStyle = other.d->defaultNoteParagraphStyle;
d->masterPageName = other.d->masterPageName;
d->startValue = other.d->startValue;
d->numberFormat = other.d->numberFormat;
d->numberingScheme = other.d->numberingScheme;
d->footnotesPosition = other.d->footnotesPosition;
d->footnotesContinuationForward = other.d->footnotesContinuationForward;
d->footnotesContinuationBackward = other.d->footnotesContinuationBackward;
return *this;
}
void KoOdfNotesConfiguration::loadOdf(const KoXmlElement &element)
{
d->citationTextStyleName = element.attributeNS(KoXmlNS::text, "citation-style-name", d->citationTextStyleName);
d->citationBodyTextStyleName = element.attributeNS(KoXmlNS::text, "citation-body-style-name", d->citationBodyTextStyleName);
d->defaultNoteParagraphStyleName = element.attributeNS(KoXmlNS::text, "default-style-name", d->defaultNoteParagraphStyleName);
d->masterPageName = element.attributeNS(KoXmlNS::text, "master-page-name", d->masterPageName);
d->startValue = qMax(1, element.attributeNS(KoXmlNS::text, "start-value", QString::number(d->startValue)).toInt());
d->numberFormat.loadOdf(element);
QString numberingScheme = element.attributeNS(KoXmlNS::text, "start-numbering-at", "document");
if (numberingScheme == "document") {
d->numberingScheme = BeginAtDocument;
}
else if (numberingScheme == "chapter") {
d->numberingScheme = BeginAtChapter;
}
else if (numberingScheme == "page") {
d->numberingScheme = BeginAtPage;
}
QString footnotesPosition = element.attributeNS(KoXmlNS::text, "footnotes-position", "page");
if (footnotesPosition == "text") {
d->footnotesPosition = Text;
}
else if (footnotesPosition == "page") {
d->footnotesPosition = Page;
}
else if (footnotesPosition == "section") {
d->footnotesPosition = Section;
}
else if (footnotesPosition == "document") {
d->footnotesPosition = Document;
}
for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) {
KoXmlElement child = node.toElement();
if (child.namespaceURI() == KoXmlNS::text) {
if (child.localName() == "note-continuation-notice-forward") {
d->footnotesContinuationForward = child.text();
} else if (child.localName() == "note-continuation-notice-backward") {
d->footnotesContinuationBackward = child.text();
}
}
}
}
void KoOdfNotesConfiguration::saveOdf(KoXmlWriter *writer) const
{
writer->startElement("text:notes-configuration");
if (d->noteClass == Footnote) {
writer->addAttribute("text:note-class", "footnote");
}
else if (d->noteClass == Endnote) {
writer->addAttribute("text:note-class", "endnote");
}
if (!d->citationTextStyleName.isNull()) {writer->addAttribute("text:citation-style-name", d->citationTextStyleName); }
if (!d->citationBodyTextStyleName.isNull()) {writer->addAttribute("text:citation-body-style-name", d->citationBodyTextStyleName); }
if (!d->defaultNoteParagraphStyleName.isNull()) {writer->addAttribute("text:default-style-name", d->defaultNoteParagraphStyleName); }
if (!d->masterPageName.isNull()) {writer->addAttribute("text:master-page-name", d->masterPageName); }
if (d->startValue != 0) { writer->addAttribute("text:start-value", d->startValue); }
d->numberFormat.saveOdf(writer);
switch(d->numberingScheme) {
case BeginAtDocument:
writer->addAttribute("text:start-numbering-at", "document");
break;
case BeginAtChapter:
writer->addAttribute("text:start-numbering-at", "chapter");
break;
case BeginAtPage:
writer->addAttribute("text:start-numbering-at", "page");
break;
}
switch(d->footnotesPosition) {
case Text:
writer->addAttribute("text:footnotes-position", "text");
break;
case Page:
writer->addAttribute("text:footnotes-position", "page");
break;
case Section:
writer->addAttribute("text:footnotes-position", "section");
break;
case Document:
writer->addAttribute("text:footnotes-position", "document");
break;
}
if (!d->footnotesContinuationForward.isNull()) {
writer->startElement("text:note-continuation-notice-forward", false);
writer->addTextNode(d->footnotesContinuationForward);
writer->endElement();
}
if (!d->footnotesContinuationBackward.isNull()) {
writer->startElement("text:note-continuation-notice-backward", false);
writer->addTextNode(d->footnotesContinuationBackward);
writer->endElement();
}
writer->endElement(); //text:notes-configuration
}
KoOdfNotesConfiguration::NoteClass KoOdfNotesConfiguration::noteClass() const
{
return d->noteClass;
}
void *KoOdfNotesConfiguration::citationTextStyle() const
{
return d->citationTextStyle;
}
QString KoOdfNotesConfiguration::citationTextStyleName() const
{
return d->citationTextStyleName;
}
void KoOdfNotesConfiguration::setCitationTextStyle(void *citationTextStyle)
{
d->citationTextStyle = citationTextStyle;
}
void *KoOdfNotesConfiguration::citationBodyTextStyle() const
{
return d->citationBodyTextStyle;
}
QString KoOdfNotesConfiguration::citationBodyTextStyleName() const
{
return d->citationBodyTextStyleName;
}
void KoOdfNotesConfiguration::setCitationBodyTextStyle(void *citationBodyTextStyle)
{
d->citationBodyTextStyle = citationBodyTextStyle;
}
void *KoOdfNotesConfiguration::defaultNoteParagraphStyle() const
{
return d->defaultNoteParagraphStyle;
}
QString KoOdfNotesConfiguration::defaultNoteParagraphStyleName() const
{
return d->defaultNoteParagraphStyleName;
}
void KoOdfNotesConfiguration::setDefaultNoteParagraphStyle(void *defaultNoteParagraphStyle)
{
d->defaultNoteParagraphStyle = defaultNoteParagraphStyle;
}
QString KoOdfNotesConfiguration::masterPage() const
{
return d->masterPageName;
}
void KoOdfNotesConfiguration::setMasterPage(const QString &masterPage)
{
d->masterPageName = masterPage;
}
int KoOdfNotesConfiguration::startValue() const
{
return d->startValue;
}
void KoOdfNotesConfiguration::setStartValue(int startValue)
{
d->startValue = qMax(1, startValue);
}
KoOdfNumberDefinition KoOdfNotesConfiguration::numberFormat() const
{
return d->numberFormat;
}
void KoOdfNotesConfiguration::setNumberFormat(const KoOdfNumberDefinition &numberFormat)
{
d->numberFormat = numberFormat;
}
KoOdfNotesConfiguration::NumberingScheme KoOdfNotesConfiguration::numberingScheme() const
{
return d->numberingScheme;
}
void KoOdfNotesConfiguration::setNumberingScheme(NumberingScheme numberingScheme)
{
d->numberingScheme = numberingScheme;
}
KoOdfNotesConfiguration::FootnotesPosition KoOdfNotesConfiguration::footnotesPosition() const
{
return d->footnotesPosition;
}
void KoOdfNotesConfiguration::setFootnotesPosition(FootnotesPosition footnotesPosition)
{
d->footnotesPosition = footnotesPosition;
}
QString KoOdfNotesConfiguration::footnoteContinuationForward() const
{
return d->footnotesContinuationForward;
}
void KoOdfNotesConfiguration::setFootnoteContinuationForward(const QString &footnoteContinuationForward)
{
d->footnotesContinuationForward = footnoteContinuationForward;
}
QString KoOdfNotesConfiguration::footnoteContinuationBackward() const
{
return d->footnotesContinuationBackward;
}
void KoOdfNotesConfiguration::setFootnoteContinuationBackward(const QString &footnoteContinuationBackward)
{
d->footnotesContinuationBackward = footnoteContinuationBackward;
}
diff --git a/src/libs/odf/KoOdfNumberDefinition.cpp b/src/libs/odf/KoOdfNumberDefinition.cpp
index 2a3ab160..54f14f9e 100644
--- a/src/libs/odf/KoOdfNumberDefinition.cpp
+++ b/src/libs/odf/KoOdfNumberDefinition.cpp
@@ -1,413 +1,414 @@
/* This file is part of the KDE project
Copyright (C) 2010 Boudewijn Rempt
Copyright (C) 2011 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoOdfNumberDefinition.h"
#include "KoXmlNS.h"
#include "KoXmlWriter.h"
class Q_DECL_HIDDEN KoOdfNumberDefinition::Private
{
public:
QString prefix;
QString suffix;
KoOdfNumberDefinition::FormatSpecification formatSpecification;
bool letterSynchronization;
};
KoOdfNumberDefinition::KoOdfNumberDefinition()
: d(new Private())
{
d->formatSpecification = Numeric;
d->letterSynchronization = false;
}
KoOdfNumberDefinition::KoOdfNumberDefinition(const KoOdfNumberDefinition &other)
: d(new Private())
{
d->prefix = other.d->prefix;
d->suffix = other.d->suffix;
d->formatSpecification = other.d->formatSpecification;
d->letterSynchronization = other.d->letterSynchronization;
}
KoOdfNumberDefinition &KoOdfNumberDefinition::operator=(const KoOdfNumberDefinition &other)
{
d->prefix = other.d->prefix;
d->suffix = other.d->suffix;
d->formatSpecification = other.d->formatSpecification;
d->letterSynchronization = other.d->letterSynchronization;
return *this;
}
KoOdfNumberDefinition::~KoOdfNumberDefinition()
{
delete d;
}
QStringList KoOdfNumberDefinition::userFormatDescriptions()
{
QStringList list;
list << "1, 2, 3, ..."
<< "a, b, c, ..."
<< "A, B, C, ..."
<< "i, ii, iii, ..."
<< "I, II, III, ..." << "أ, ب, ت, ..."
<< "ก, ข, ค, ..."
<< "౧, ౨, ౩, ..."
<< "௧, ௨, ௪, ..."
<< "୧, ୨, ୩, ..."
<< "൧, ൨, ൩, ..."
<< "೧, ೨, ೩, ..."
<< "੧, ੨, ੩, ..."
<< "૧, ૨, ૩, ..."
<< "১, ২, ৩, ...";
return list;
}
void KoOdfNumberDefinition::loadOdf(const KoXmlElement &element)
{
const QString format = element.attributeNS(KoXmlNS::style, "num-format", QString());
if (format.isEmpty()) {
//do nothing fall back to what we had.
}
else if (format[0] == '1') {
d->formatSpecification = Numeric;
}
else if (format[0] == 'a') {
d->formatSpecification = AlphabeticLowerCase;
}
else if (format[0] == 'A') {
d->formatSpecification = AlphabeticUpperCase;
}
else if (format[0] == 'i') {
d->formatSpecification = RomanLowerCase;
}
else if (format[0] == 'I') {
d->formatSpecification = RomanUpperCase;
}
else if (format == QString::fromUtf8("أ, ب, ت, ...")){
d->formatSpecification = ArabicAlphabet;
}
else if (format == QString::fromUtf8("ก, ข, ค, ...")){
d->formatSpecification = Thai;
}
else if (format == QString::fromUtf8("أ, ب, ج, ...")) {
d->formatSpecification = Abjad;
}
else if (format == QString::fromUtf8("ﺃ,ﺏ, ﺝ, ... ")) {
d->formatSpecification = AbjadMinor;
}
else if (format == QString::fromUtf8("౧, ౨, ౩, ...")) {
d->formatSpecification = Telugu;
}
else if (format == QString::fromUtf8("௧, ௨, ௪, ...")) {
d->formatSpecification = Tamil;
}
else if (format == QString::fromUtf8("୧, ୨, ୩, ...")) {
d->formatSpecification = Oriya;
}
else if (format == QString::fromUtf8("൧, ൨, ൩, ...")) {
d->formatSpecification = Malayalam;
}
else if (format == QString::fromUtf8("೧, ೨, ೩, ...")) {
d->formatSpecification = Kannada;
}
else if (format == QString::fromUtf8("੧, ੨, ੩, ...")) {
d->formatSpecification = Gurumukhi;
}
else if (format == QString::fromUtf8("૧, ૨, ૩, ...")) {
d->formatSpecification = Gujarati;
}
else if (format == QString::fromUtf8("১, ২, ৩, ...")) {
d->formatSpecification = Bengali;
}
else {
d->formatSpecification = Numeric;
}
//The style:num-prefix and style:num-suffix attributes specify what to display before and after the number.
d->prefix = element.attributeNS(KoXmlNS::style, "num-prefix", d->prefix);
d->suffix = element.attributeNS(KoXmlNS::style, "num-suffix", d->suffix);
d->letterSynchronization = (element.attributeNS(KoXmlNS::style, "num-letter-sync", d->letterSynchronization ? "true" : "false") == "true");
}
void KoOdfNumberDefinition::saveOdf(KoXmlWriter *writer) const
{
if (!d->prefix.isNull()) {
writer->addAttribute("style:num-prefix", d->prefix);
}
if (!d->suffix.isNull()) {
writer->addAttribute("style:num-suffix", d->suffix);
}
QByteArray format;
switch(d->formatSpecification) {
case Numeric:
format = "1";
break;
case AlphabeticLowerCase:
format = "a";
break;
case AlphabeticUpperCase:
format = "A";
break;
case RomanLowerCase:
format = "i";
break;
case RomanUpperCase:
format = "I";
break;
case ArabicAlphabet:
format = "أ, ب, ت, ...";
break;
case Thai:
format = "ก, ข, ค, ...";
break;
case Telugu:
format = "౧, ౨, ౩, ...";
break;
case Tamil:
format = "௧, ௨, ௪, ...";
break;
case Oriya:
format = "୧, ୨, ୩, ...";
break;
case Malayalam:
format = "൧, ൨, ൩, ...";
break;
case Kannada:
format = "೧, ೨, ೩, ...";
break;
case Gurumukhi:
format = "੧, ੨, ੩, ...";
break;
case Gujarati:
format = "૧, ૨, ૩, ...";
break;
case Bengali:
format = "১, ২, ৩, ...";
break;
case Empty:
default:
;
};
if (!format.isNull()) {
writer->addAttribute("style:num-format", format);
}
if (d->letterSynchronization) {
writer->addAttribute("style:num-letter-sync", "true");
}
}
static QString intToRoman(int n)
{
static const QString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
static const QString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
static const QString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
static const QString RNThousands[] = {"", "m", "mm", "mmm", "mmmm", "mmmmm", "mmmmmm", "mmmmmmm", "mmmmmmmm", "mmmmmmmmm"};
if (n <= 0) {
return QString::number(n);
}
return RNThousands[(n / 1000)] +
RNHundreds[(n / 100) % 10 ] +
RNTens[(n / 10) % 10 ] +
RNUnits[(n) % 10 ];
}
static QString intToAlpha(int n, bool letterSynchronization)
{
QString answer;
if (letterSynchronization) {
int digits = 1;
for (; n > 26; n -= 26)
digits += 1;
for (int i = 0; i < digits; i++)
answer.prepend(QChar('a' + n - 1));
return answer;
} else {
char bottomDigit;
while (n > 26) {
bottomDigit = (n - 1) % 26;
n = (n - 1) / 26;
answer.prepend(QChar('a' + bottomDigit));
}
}
answer.prepend(QChar('a' + n - 1));
return answer;
}
static QString intToScript(int n, int offset)
{
// 10-base
QString answer;
while (n > 0) {
answer.prepend(QChar(offset + n % 10));
n = n / 10;
}
return answer;
}
static QString intToScriptList(int n, KoOdfNumberDefinition::FormatSpecification formatSpecification)
{
// 1 time Sequences
// note; the leading X is to make these 1 based.
static const char* const Abjad[] = { "أ", "ب", "ج", "د", "ﻫ", "و", "ز", "ح", "ط", "ي", "ك", "ل", "م",
"ن", "س", "ع", "ف", "ص", "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ", "غ"
};
static const char* const Abjad2[] = { "ﺃ", "ﺏ", "ﺝ", "ﺩ", "ﻫ", "ﻭ", "ﺯ", "ﺡ", "ﻁ", "ﻱ", "ﻙ", "ﻝ", "ﻡ",
"ﻥ", "ﺹ", "ﻉ", "ﻑ", "ﺽ", "ﻕ", "ﺭ", "ﺱ", "ﺕ", "ﺙ", "ﺥ", "ﺫ", "ﻅ", "ﻍ", "ﺵ"
};
static const char* const ArabicAlphabet[] = {"ا", "ب", "ت", "ث", "ج", "ح", "خ", "د", "ذ", "ر", "ز",
"س", "ش", "ص", "ض", "ط", "ظ", "ع", "غ", "ف", "ق", "ك", "ل", "م", "ن", "ه", "و", "ي"
};
/*
// see this page for the 10, 100, 1000 etc http://en.wikipedia.org/wiki/Chinese_numerals
static const char* const chinese1[] = { '零','壹','貳','叄','肆','伍','陸','柒','捌','玖' };
static const char* const chinese2[] = { '〇','一','二','三','四','五','六','七','八','九' };
TODO: http://en.wikipedia.org/wiki/Korean_numerals
http://en.wikipedia.org/wiki/Japanese_numerals
'http://en.wikipedia.org/wiki/Hebrew_numerals'
'http://en.wikipedia.org/wiki/Armenian_numerals'
'http://en.wikipedia.org/wiki/Greek_numerals'
'http://en.wikipedia.org/wiki/Cyrillic_numerals'
'http://en.wikipedia.org/wiki/Sanskrit_numerals'
'http://en.wikipedia.org/wiki/Ge%27ez_alphabet#Numerals'
'http://en.wikipedia.org/wiki/Abjad_numerals'
*/
switch (formatSpecification) {
case KoOdfNumberDefinition::Abjad:
if (n > 22) return "*";
return QString::fromUtf8(Abjad[n-1]);
case KoOdfNumberDefinition::AbjadMinor:
if (n > 22) return "*";
return QString::fromUtf8(Abjad2[n-1]);
case KoOdfNumberDefinition::ArabicAlphabet:
if (n > 28) return "*";
return QString::fromUtf8(ArabicAlphabet[n-1]);
default:
return QString::number(n);
}
}
QString KoOdfNumberDefinition::formattedNumber(int number, KoOdfNumberDefinition *defaultDefinition) const
{
switch(d->formatSpecification) {
case Numeric:
return QString::number(number);
break;
case AlphabeticLowerCase:
return intToAlpha(number, d->letterSynchronization);
case AlphabeticUpperCase:
return intToAlpha(number, d->letterSynchronization).toUpper();
case RomanLowerCase:
return intToRoman(number);
case RomanUpperCase:
return intToRoman(number).toUpper();
case Thai:
return intToScript(number, 0xe50);
case Tibetan:
return intToScript(number, 0xf20);
case Telugu:
return intToScript(number, 0xc66);
case Tamil:
return intToScript(number, 0x0be6);
case Oriya:
return intToScript(number, 0xb66);
case Malayalam:
return intToScript(number, 0xd66);
case Kannada:
return intToScript(number, 0xce6);
case Gurumukhi:
return intToScript(number, 0xa66);
case Gujarati:
return intToScript(number, 0xae6);
case Bengali:
return intToScript(number, 0x9e6);
case Abjad:
case AbjadMinor:
case ArabicAlphabet:
return intToScriptList(number, d->formatSpecification);
case Empty:
if (defaultDefinition) {
return defaultDefinition->formattedNumber(number);
}
break;
default:
;
};
return "";
}
QString KoOdfNumberDefinition::prefix() const
{
return d->prefix;
}
void KoOdfNumberDefinition::setPrefix(const QString &prefix)
{
d->prefix = prefix;
}
QString KoOdfNumberDefinition::suffix() const
{
return d->suffix;
}
void KoOdfNumberDefinition::setSuffix(const QString &suffix)
{
d->suffix = suffix;
}
KoOdfNumberDefinition::FormatSpecification KoOdfNumberDefinition::formatSpecification() const
{
return d->formatSpecification;
}
void KoOdfNumberDefinition::setFormatSpecification(FormatSpecification formatSpecification)
{
d->formatSpecification = formatSpecification;
}
bool KoOdfNumberDefinition::letterSynchronization() const
{
return d->letterSynchronization;
}
void KoOdfNumberDefinition::setLetterSynchronization(bool letterSynchronization)
{
d->letterSynchronization = letterSynchronization;
}
diff --git a/src/libs/odf/KoOdfNumberStyles.cpp b/src/libs/odf/KoOdfNumberStyles.cpp
index 30036b1d..6c4a9c7a 100644
--- a/src/libs/odf/KoOdfNumberStyles.cpp
+++ b/src/libs/odf/KoOdfNumberStyles.cpp
@@ -1,1374 +1,1375 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoOdfNumberStyles.h"
#include "KoGenStyles.h"
#include "KoXmlNS.h"
#include <QBuffer>
#include <QDateTime>
#include <QTime>
#include <klocalizedstring.h>
#include <OdfDebug.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <writeodf/writeodfnumber.h>
#include <math.h>
using namespace writeodf;
namespace KoOdfNumberStyles
{
static bool saveOdfTimeFormat(KoXmlWriter &elementWriter, QString &format, QString &text, bool &antislash);
static void parseOdfDatelocale(KoXmlWriter &elementWriter, QString &format, QString &text);
static bool saveOdflocaleTimeFormat(KoXmlWriter &elementWriter, QString &format, QString &text);
static void parseOdfTimelocale(KoXmlWriter &elementWriter, QString &format, QString &text);
static void addCalligraNumericStyleExtension(KoXmlWriter &elementWriter, const QString &_suffix, const QString &_prefix);
QString format(const QString &value, const NumericStyleFormat &format)
{
switch (format.type) {
case Number: {
bool ok;
qreal v = value.toDouble(&ok);
return ok ? formatNumber(v, format.formatStr, format.precision) : value;
} break;
case Boolean: {
return formatBoolean(value, format.formatStr);
} break;
case Date: {
bool ok;
int v = value.toInt(&ok);
return ok ? formatDate(v, format.formatStr) : value;
} break;
case Time: {
bool ok;
qreal v = value.toDouble(&ok);
return ok ? formatTime(v, format.formatStr) : value;
} break;
case Percentage: {
return formatPercent(value, format.formatStr, format.precision);
} break;
case Currency: {
bool ok;
qreal v = value.toDouble(&ok);
return ok ? formatCurrency(v, format.formatStr, format.currencySymbol, format.precision) : value;
} break;
case Scientific: {
bool ok;
qreal v = value.toDouble(&ok);
return ok ? formatScientific(v, format.formatStr, format.precision) : value;
} break;
case Fraction: {
bool ok;
qreal v = value.toDouble(&ok);
return ok ? formatFraction(v, format.formatStr) : value;
} break;
case Text: {
return value;
} break;
}
return value;
}
QString formatNumber(qreal value, const QString &format, int precision)
{
QString result;
int start = 0;
bool showNegative = format.startsWith('-');
if (showNegative)
start = 1;
for (int i = start; i < format.length(); ++i) {
QChar c = format[ i ];
switch (c.unicode()) {
case '.':
case ',':
case '#':
case '0':
case '?': {
// bool grouping = false;
bool gotDot = false;
bool gotE = false;
bool gotFraction = false;
int decimalPlaces = 0;
int integerDigits = 0;
int optionalDecimalPlaces = 0;
int optionalIntegerDigits = 0;
int exponentDigits = 0;
int numeratorDigits = 0;
int denominatorDigits = 0;
char ch = format[ i ].toLatin1();
do {
if (ch == '.') {
gotDot = true;
} else if (ch == ',') {
// grouping = true;
} else if (ch == 'E' || ch == 'e') {
//SET_TYPE_OR_RETURN(KoGenStyle::NumericScientificStyle);
if (i >= format.length() - 1) break;
const char chN = format[ i + 1 ].toLatin1();
if (chN == '-' || chN == '+') {
gotE = true;
++i;
}
} else if (ch == '0' && gotE) {
++exponentDigits;
} else if (ch == '0' && !gotDot && !gotFraction) {
++integerDigits;
} else if (ch == '#' && !gotDot && !gotFraction) {
++optionalIntegerDigits;
} else if (ch == '0' && gotDot && !gotFraction) {
++decimalPlaces;
} else if (ch == '#' && gotDot && !gotFraction) {
++optionalDecimalPlaces;
} else if (ch == '?' && !gotFraction) {
++numeratorDigits;
} else if (ch == '?' && gotFraction) {
++denominatorDigits;
} else if (ch == '/') {
//SET_TYPE_OR_RETURN(KoGenStyle::NumericFractionStyle);
if (gotDot) return QString(); // invalid
gotFraction = true;
}
if (i >= format.length() - 1) break;
ch = format[ ++i ].toLatin1();
if (ch == ' ') {
// spaces are not allowed - but there's an exception: if this is a fraction. Let's check for '?' or '/'
const char c = format[ i + 1 ].toLatin1();
if (c == '?' || c == '/')
ch = format[ ++i ].toLatin1();
}
} while (i < format.length() && (ch == '.' || ch == ',' || ch == '#' || ch == '0' || ch == 'E' || ch == 'e' || ch == '?' || ch == '/'));
if (!(ch == '.' || ch == ',' || ch == '#' || ch == '0' || ch == 'E' || ch == 'e' || ch == '?' || ch == '/')) {
--i;
}
QString v(QString::number(qAbs(value), 'f', precision >= 0 ? precision : (optionalDecimalPlaces + decimalPlaces)));
int p = v.indexOf('.');
QString integerValue = p >= 0 ? v.left(p) : v;
if (integerValue.length() < integerDigits)
integerValue.prepend(QString().fill('0', integerDigits - integerValue.length()));
QString decimalValue = p >= 0 ? v.mid(p + 1) : QString();
if (decimalValue.length() < decimalPlaces)
decimalValue.append(QString().fill('0', decimalPlaces - decimalValue.length()));
if (showNegative && value < 0)
result.append('-');
result.append(integerValue);
if (!decimalValue.isEmpty())
result.append('.' + decimalValue);
} break;
case '\\': { // backslash escapes the next char
if (i < format.length() - 1) {
result.append(format[ ++i ]);
}
} break;
default:
result.append(c);
break;
}
}
return result;
}
QString formatBoolean(const QString &value, const QString &format)
{
Q_UNUSED(format);
bool ok = false;
int v = value.toInt(&ok);
return ok && v != 0 ? "TRUE" : "FALSE";
}
QString formatDate(int value, const QString &format)
{
QDateTime dt(QDate(1899, 12, 30)); // reference date
dt = dt.addDays(value);
return dt.toString(format);
}
QString formatTime(qreal value, const QString &format)
{
QTime t(0,0,0);
t = t.addSecs(qRound(value * 86400.0)); // 24 hours
return t.toString(format);
}
QString formatCurrency(qreal value, const QString &format, const QString& currencySymbol, int precision)
{
if (currencySymbol == "CCC") // undocumented hack, see doc attached to comment 6 at bug 282972
return QLocale().toCurrencyString(value, "USD");
if (format.isEmpty()) // no format means use locale format
return QLocale().toCurrencyString(value, currencySymbol.isEmpty() ? QLocale().currencySymbol(QLocale::CurrencySymbol)
: currencySymbol);
return formatNumber(value, format, precision);
}
QString formatScientific(qreal value, const QString &format, int precision)
{
Q_UNUSED(format);
QString v(QString::number(value, 'E', precision));
int pos = v.indexOf('.');
if (pos != -1) {
v.replace(pos, 1, QLocale().decimalPoint());
}
return v;
}
QString formatFraction(qreal value, const QString &format)
{
QString prefix = value < 0 ? "-" : "";
value = fabs(value);
qreal result = value - floor(value);
if (result == 0) // return w/o fraction part if not necessary
return prefix + QString::number(value);
int index = 0;
int limit = 0;
if (format.endsWith(QLatin1String("/2"))) {
index = 2;
} else if (format.endsWith(QLatin1String("/4"))) {
index = 4;
} else if (format.endsWith(QLatin1String("/8"))) {
index = 8;
} else if (format.endsWith(QLatin1String("/16"))) {
index = 16;
} else if (format.endsWith(QLatin1String("/10"))) {
index = 10;
} else if (format.endsWith(QLatin1String("/100"))) {
index = 100;
} else if (format.endsWith(QLatin1String("/?"))) {
index = 3;
limit = 9;
} else if (format.endsWith(QLatin1String("/??"))) {
index = 4;
limit = 99;
} else if (format.endsWith(QLatin1String("/???"))) {
index = 5;
limit = 999;
} else { // fallback
return prefix + QString::number(value);
}
// handle halves, quarters, tenths, ...
if (!format.endsWith(QLatin1String("/?")) &&
!format.endsWith(QLatin1String("/??")) &&
!format.endsWith(QLatin1String("/???"))) {
qreal calc = 0;
int index1 = 0;
qreal diff = result;
for (int i = 1; i <= index; i++) {
calc = i * 1.0 / index;
if (fabs(result - calc) < diff) {
index1 = i;
diff = fabs(result - calc);
}
}
if (index1 == 0)
return prefix + QString("%1").arg(floor(value));
if (index1 == index)
return prefix + QString("%1").arg(floor(value) + 1);
if (floor(value) == 0)
return prefix + QString("%1/%2").arg(index1).arg(index);
return prefix + QString("%1 %2/%3").arg(floor(value)).arg(index1).arg(index);
}
// handle Format::fraction_one_digit, Format::fraction_two_digit and Format::fraction_three_digit style
qreal target = result;
qreal numerator = 1;
qreal denominator = 1;
qreal bestNumerator = 0;
qreal bestDenominator = 1;
qreal bestDist = target;
// as soon as either numerator or denominator gets above the limit, we're done
while (numerator <= limit && denominator <= limit) {
qreal dist = fabs((numerator / denominator) - target);
if (dist < bestDist) {
bestDist = dist;
bestNumerator = numerator;
bestDenominator = denominator;
}
if (numerator / denominator > target) {
++denominator;
} else {
++numerator;
}
}
if (bestNumerator == 0)
return prefix + QString().setNum(floor(value));
if (bestDenominator == bestNumerator)
return prefix + QString().setNum(floor(value + 1));
if (floor(value) == 0)
return prefix + QString("%1/%2").arg(bestNumerator).arg(bestDenominator);
return prefix + QString("%1 %2/%3").arg(floor(value)).arg(bestNumerator).arg(bestDenominator);
}
QString formatPercent(const QString &value, const QString &/*format*/, int precision)
{
if (value.contains('.')) {
bool ok;
qreal v = value.toDouble(&ok);
if (ok)
return QString::number(v * 100., 'f', precision) + QLatin1String("%");
}
return value;
}
// OO spec 2.5.4. p68. Conversion to Qt format: see qdate.html
// OpenCalcImport::loadFormat has similar code, but slower, intermixed with other stuff,
// lacking long-textual forms.
QPair<QString, NumericStyleFormat> loadOdfNumberStyle(const KoXmlElement &parent)
{
NumericStyleFormat dataStyle;
const QString localName = parent.localName();
if (localName == "number-style")
dataStyle.type = Number;
else if (localName == "currency-style")
dataStyle.type = Currency;
else if (localName == "percentage-style")
dataStyle.type = Percentage;
else if (localName == "boolean-style")
dataStyle.type = Boolean;
else if (localName == "text-style")
dataStyle.type = Text;
else if (localName == "date-style")
dataStyle.type = Date;
else if (localName == "time-style")
dataStyle.type = Time;
QString format;
int precision = -1;
int leadingZ = 1;
bool thousandsSep = false;
//todo negred
//bool negRed = false;
bool ok = false;
int i = 0;
KoXmlElement e;
QString prefix;
QString suffix;
forEachElement(e, parent) {
if (e.namespaceURI() != KoXmlNS::number)
continue;
QString localName = e.localName();
const QString numberStyle = e.attributeNS(KoXmlNS::number, "style", QString());
const bool shortForm = numberStyle == "short" || numberStyle.isEmpty();
if (localName == "day") {
format += shortForm ? "d" : "dd";
} else if (localName == "day-of-week") {
format += shortForm ? "ddd" : "dddd";
} else if (localName == "month") {
if (e.attributeNS(KoXmlNS::number, "possessive-form", QString()) == "true") {
format += shortForm ? "PPP" : "PPPP";
}
// TODO the spec has a strange mention of number:format-source
else if (e.attributeNS(KoXmlNS::number, "textual", QString()) == "true") {
bool isExtraShort = false; // find out if we have to use the extra-short month name (just 1st letter)
if (e.attributeNS(KoXmlNS::calligra, "number-length", QString()) == "extra-short") {
isExtraShort = true;
}
if (!isExtraShort) { // for normal month format (first 3 letters or complete name)
format += shortForm ? "MMM" : "MMMM";
} else { // for the extra-short month name use 'X' as a special mark
format += "X";
}
} else { // month number
format += shortForm ? "M" : "MM";
}
} else if (localName == "year") {
format += shortForm ? "yy" : "yyyy";
} else if (localName == "era") {
//TODO I don't know what is it... (define into oo spec)
} else if (localName == "week-of-year" || localName == "quarter") {
// ### not supported in Qt
} else if (localName == "hours") {
format += shortForm ? "h" : "hh";
} else if (localName == "minutes") {
format += shortForm ? "m" : "mm";
} else if (localName == "seconds") {
format += shortForm ? "s" : "ss";
} else if (localName == "am-pm") {
format += "ap";
} else if (localName == "text") { // literal
format += e.text();
} else if (localName == "suffix") {
suffix = e.text();
debugOdf << " suffix :" << suffix;
} else if (localName == "prefix") {
prefix = e.text();
debugOdf << " prefix :" << prefix;
} else if (localName == "currency-symbol") {
dataStyle.currencySymbol = e.text();
debugOdf << " currency-symbol:" << dataStyle.currencySymbol;
format += e.text();
//TODO
// number:language="de" number:country="DE">€</number:currency-symbol>
// Stefan: localization of the symbol?
} else if (localName == "number") {
if (e.hasAttributeNS(KoXmlNS::number, "grouping")) {
thousandsSep = e.attributeNS(KoXmlNS::number, "grouping", QString()).toLower() == "true";
}
if (e.hasAttributeNS(KoXmlNS::number, "decimal-places")) {
int d = e.attributeNS(KoXmlNS::number, "decimal-places", QString()).toInt(&ok);
if (ok)
precision = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "min-integer-digits")) {
int d = e.attributeNS(KoXmlNS::number, "min-integer-digits", QString()).toInt(&ok);
if (ok)
leadingZ = d;
}
if (thousandsSep && leadingZ <= 3) {
format += "#,";
for (i = leadingZ; i <= 3; ++i)
format += '#';
}
for (i = 1; i <= leadingZ; ++i) {
format += '0';
if ((i % 3 == 0) && thousandsSep)
format = + ',' ;
}
if (precision > -1) {
format += '.';
for (i = 0; i < precision; ++i)
format += '0';
}
} else if (localName == "scientific-number") {
if (dataStyle.type == Number)
dataStyle.type = Scientific;
int exp = 2;
if (e.hasAttributeNS(KoXmlNS::number, "decimal-places")) {
int d = e.attributeNS(KoXmlNS::number, "decimal-places", QString()).toInt(&ok);
if (ok)
precision = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "min-integer-digits")) {
int d = e.attributeNS(KoXmlNS::number, "min-integer-digits", QString()).toInt(&ok);
if (ok)
leadingZ = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "min-exponent-digits")) {
int d = e.attributeNS(KoXmlNS::number, "min-exponent-digits", QString()).toInt(&ok);
if (ok)
exp = d;
if (exp <= 0)
exp = 1;
}
if (e.hasAttributeNS(KoXmlNS::number, "grouping")) {
thousandsSep = e.attributeNS(KoXmlNS::number, "grouping", QString()).toLower() == "true";
}
if (thousandsSep && leadingZ <= 3) {
format += "#,";
for (i = leadingZ; i <= 3; ++i)
format += '#';
}
for (i = 1; i <= leadingZ; ++i) {
format += '0';
if ((i % 3 == 0) && thousandsSep)
format += ',';
}
if (precision > -1) {
format += '.';
for (i = 0; i < precision; ++i)
format += '0';
}
format += "E+";
for (i = 0; i < exp; ++i)
format += '0';
} else if (localName == "fraction") {
if (dataStyle.type == Number)
dataStyle.type = Fraction;
int integer = 0;
int numerator = 1;
int denominator = 1;
int denominatorValue = 0;
if (e.hasAttributeNS(KoXmlNS::number, "min-integer-digits")) {
int d = e.attributeNS(KoXmlNS::number, "min-integer-digits", QString()).toInt(&ok);
if (ok)
integer = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "min-numerator-digits")) {
int d = e.attributeNS(KoXmlNS::number, "min-numerator-digits", QString()).toInt(&ok);
if (ok)
numerator = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "min-denominator-digits")) {
int d = e.attributeNS(KoXmlNS::number, "min-denominator-digits", QString()).toInt(&ok);
if (ok)
denominator = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "denominator-value")) {
int d = e.attributeNS(KoXmlNS::number, "denominator-value", QString()).toInt(&ok);
if (ok)
denominatorValue = d;
}
if (e.hasAttributeNS(KoXmlNS::number, "grouping")) {
thousandsSep = e.attributeNS(KoXmlNS::number, "grouping", QString()).toLower() == "true";
}
for (i = 0; i < integer; ++i)
format += '#';
format += ' ';
for (i = 0; i < numerator; ++i)
format += '?';
format += '/';
if (denominatorValue != 0)
format += QString::number(denominatorValue);
else {
for (i = 0; i < denominator; ++i)
format += '?';
}
}
// stylesmap's are embedded into a style and are pointing to another style that
// should be used instead of this style if the defined condition is true. E.g.;
// <number:number-style style:name="N139P0" style:volatile="true"/>
// <number:number-style style:name="N139P1" style:volatile="true"/>
// <number:number-style style:name="N139P2" style:volatile="true"/>
// <number:text-style style:name="N139">
// <style:map style:condition="value()&gt;0" style:apply-style-name="N139P0"/>
// <style:map style:condition="value()&lt;0" style:apply-style-name="N139P1"/>
// <style:map style:condition="value()=0" style:apply-style-name="N139P2"/>
// </number:text-style>
for (KoXmlNode node(e); !node.isNull(); node = node.nextSibling()) {
KoXmlElement elem = node.toElement();
if (elem.namespaceURI() == KoXmlNS::style && elem.localName() == "map") {
QString condition, applyStyleName;
if (elem.hasAttributeNS(KoXmlNS::style, "condition"))
condition = elem.attributeNS(KoXmlNS::style, "condition");
if (elem.hasAttributeNS(KoXmlNS::style, "apply-style-name"))
applyStyleName = elem.attributeNS(KoXmlNS::style, "apply-style-name");
dataStyle.styleMaps.append( QPair<QString,QString>(condition,applyStyleName) );
}
}
}
const QString styleName = parent.attributeNS(KoXmlNS::style, "name", QString());
debugOdf<<"99 *****************************************************************************";
//Q_ASSERT(false);
debugOdf << "data style:" << styleName << " qt format=" << format;
if (!prefix.isEmpty()) {
debugOdf << " format.left( prefix.length() ) :" << format.left(prefix.length()) << " prefix :" << prefix;
if (format.left(prefix.length()) == prefix) {
format = format.right(format.length() - prefix.length());
} else
prefix.clear();
}
if (!suffix.isEmpty()) {
debugOdf << "format.right( suffix.length() ) :" << format.right(suffix.length()) << " suffix :" << suffix;
if (format.right(suffix.length()) == suffix) {
format = format.left(format.length() - suffix.length());
} else
suffix.clear();
}
dataStyle.formatStr = format;
dataStyle.prefix = prefix;
dataStyle.suffix = suffix;
dataStyle.precision = precision;
dataStyle.thousandsSep = thousandsSep;
debugOdf << " finish insert format :" << format << " prefix :" << prefix << " suffix :" << suffix;
return QPair<QString, NumericStyleFormat>(styleName, dataStyle);
}
QString saveOdfNumberStyle(KoGenStyles &mainStyles, const NumericStyleFormat &format)
{
QString styleName;
switch (format.type) {
case KoOdfNumberStyles::Number: {
styleName = KoOdfNumberStyles::saveOdfNumberStyle(mainStyles, format.formatStr, format.prefix, format.suffix, format.thousandsSep);
} break;
case KoOdfNumberStyles::Boolean: {
styleName = KoOdfNumberStyles::saveOdfBooleanStyle(mainStyles, format.formatStr, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Date: {
bool localeFormat = format.formatStr.isEmpty();
styleName = KoOdfNumberStyles::saveOdfDateStyle(mainStyles, format.formatStr, localeFormat, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Time: {
bool localeFormat = format.formatStr.isEmpty();
styleName = KoOdfNumberStyles::saveOdfTimeStyle(mainStyles, format.formatStr, localeFormat, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Percentage: {
styleName = KoOdfNumberStyles::saveOdfPercentageStyle(mainStyles, format.formatStr, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Currency: {
styleName = KoOdfNumberStyles::saveOdfCurrencyStyle(mainStyles, format.formatStr, format.currencySymbol, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Scientific: {
styleName = KoOdfNumberStyles::saveOdfScientificStyle(mainStyles, format.formatStr, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Fraction: {
styleName = KoOdfNumberStyles::saveOdfFractionStyle(mainStyles, format.formatStr, format.prefix, format.suffix);
} break;
case KoOdfNumberStyles::Text: {
styleName = KoOdfNumberStyles::saveOdfTextStyle(mainStyles, format.formatStr, format.prefix, format.suffix);
} break;
}
return styleName;
}
void addTextNumber(QString& text, KoXmlWriter &elementWriter)
{
if (!text.isEmpty()) {
number_text(&elementWriter).addTextNode(text);
text.clear();
}
}
void parseOdfTimelocale(KoXmlWriter &elementWriter, QString &format, QString &text)
{
debugOdf << "parseOdfTimelocale(KoXmlWriter &elementWriter, QString & format, QString & text ) :" << format;
do {
if (!saveOdflocaleTimeFormat(elementWriter, format, text)) {
text += format[0];
format.remove(0, 1);
}
} while (format.length() > 0);
addTextNumber(text, elementWriter);
}
bool saveOdflocaleTimeFormat(KoXmlWriter &elementWriter, QString &format, QString &text)
{
bool changed = false;
if (format.startsWith("%H")) { //hh
//hour in 24h
addTextNumber(text, elementWriter);
number_hours(&elementWriter).set_number_style("long");
format.remove(0, 2);
changed = true;
} else if (format.startsWith("%k")) { //h
addTextNumber(text, elementWriter);
number_hours(&elementWriter).set_number_style("short");
format.remove(0, 2);
changed = true;
} else if (format.startsWith("%I")) { // ?????
//TODO hour in 12h
changed = true;
} else if (format.startsWith("%l")) {
//TODO hour in 12h with 1 digit
changed = true;
} else if (format.startsWith("%M")) { // mm
addTextNumber(text, elementWriter);
number_minutes(&elementWriter).set_number_style("long");
format.remove(0, 2);
changed = true;
} else if (format.startsWith("%S")) { //ss
addTextNumber(text, elementWriter);
number_seconds(&elementWriter).set_number_style("long");
format.remove(0, 2);
changed = true;
} else if (format.startsWith("%p")) {
//TODO am or pm
addTextNumber(text, elementWriter);
number_am_pm(&elementWriter).end();
format.remove(0, 2);
changed = true;
}
return changed;
}
bool saveOdfTimeFormat(KoXmlWriter &elementWriter, QString &format, QString &text, bool &antislash)
{
bool changed = false;
//we can also add time to date.
if (antislash) {
text += format[0];
format.remove(0, 1);
antislash = false;
changed = true;
} else if (format.startsWith("hh")) {
addTextNumber(text, elementWriter);
number_hours(&elementWriter).set_number_style("long");
format.remove(0, 2);
changed = true;
} else if (format.startsWith('h')) {
addTextNumber(text, elementWriter);
number_hours(&elementWriter).set_number_style("short");
format.remove(0, 1);
changed = true;
} else if (format.startsWith("mm")) {
addTextNumber(text, elementWriter);
number_minutes(&elementWriter).set_number_style("long");
format.remove(0, 2);
changed = true;
} else if (format.startsWith('m')) {
addTextNumber(text, elementWriter);
number_minutes(&elementWriter).set_number_style("short");
format.remove(0, 1);
changed = true;
} else if (format.startsWith("ss")) {
addTextNumber(text, elementWriter);
number_seconds(&elementWriter).set_number_style("long");
format.remove(0, 2);
changed = true;
} else if (format.startsWith('s')) {
addTextNumber(text, elementWriter);
number_seconds(&elementWriter).set_number_style("short");
format.remove(0, 1);
changed = true;
} else if (format.startsWith("ap")) {
addTextNumber(text, elementWriter);
number_am_pm(&elementWriter).end();
format.remove(0, 2);
changed = true;
}
return changed;
}
QString saveOdfTimeStyle(KoGenStyles &mainStyles, const QString &_format, bool localeFormat,
const QString &_prefix, const QString &_suffix)
{
Q_UNUSED(_prefix);
Q_UNUSED(_suffix);
//debugOdf << "QString KoOdfNumberStyles::saveOdfTimeStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
QString format(_format);
KoGenStyle currentStyle(KoGenStyle::NumericTimeStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
if (localeFormat) {
parseOdfTimelocale(elementWriter, format, text);
} else {
bool antislash = false;
do {
if (!saveOdfTimeFormat(elementWriter, format, text, antislash)) {
QString elem(format[0]);
format.remove(0, 1);
if (elem == "\\") {
antislash = true;
} else {
text += elem;
antislash = false;
}
}
} while (format.length() > 0);
addTextNumber(text, elementWriter);
}
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
//convert locale string to good format
void parseOdfDatelocale(KoXmlWriter &elementWriter, QString &format, QString &text)
{
debugOdf << format;
do {
if (format.startsWith("%Y")) {
addTextNumber(text, elementWriter);
number_year(&elementWriter).set_number_style("long");
format.remove(0, 2);
} else if (format.startsWith("%y")) {
addTextNumber(text, elementWriter);
number_year(&elementWriter).set_number_style("short");
format.remove(0, 2);
} else if (format.startsWith("%n")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("short");
month.set_number_textual("false");
format.remove(0, 2);
} else if (format.startsWith("%m")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("long");
month.set_number_textual("false"); //not necessary remove it
format.remove(0, 2);
} else if (format.startsWith("%e")) {
addTextNumber(text, elementWriter);
number_day(&elementWriter).set_number_style("short");
format.remove(0, 2);
} else if (format.startsWith("%d")) {
addTextNumber(text, elementWriter);
number_day(&elementWriter).set_number_style("long");
format.remove(0, 2);
} else if (format.startsWith("%b")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("short");
month.set_number_textual("true");
format.remove(0, 2);
} else if (format.startsWith("%B")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("long");
month.set_number_textual("true");
elementWriter.endElement();
format.remove(0, 2);
} else if (format.startsWith("%a")) {
addTextNumber(text, elementWriter);
number_day_of_week(&elementWriter).set_number_style("short");
format.remove(0, 2);
} else if (format.startsWith("%A")) {
addTextNumber(text, elementWriter);
number_day_of_week(&elementWriter).set_number_style("long");
format.remove(0, 2);
} else {
if (!saveOdflocaleTimeFormat(elementWriter, format, text)) {
text += format[0];
format.remove(0, 1);
}
}
} while (format.length() > 0);
addTextNumber(text, elementWriter);
}
QString saveOdfDateStyle(KoGenStyles &mainStyles, const QString &_format, bool localeFormat,
const QString &_prefix, const QString &_suffix)
{
Q_UNUSED(_prefix);
Q_UNUSED(_suffix);
//debugOdf << _format;
QString format(_format);
// Not supported into Qt: "era" "week-of-year" "quarter"
KoGenStyle currentStyle(KoGenStyle::NumericDateStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
if (localeFormat) {
parseOdfDatelocale(elementWriter, format, text);
} else {
bool antislash = false;
do {
if (antislash) {
text += format[0];
format.remove(0, 1);
}
//TODO implement loading ! What is it ?
else if (format.startsWith("MMMMM")) { // MMMMM is extra-short month name (only 1st character)
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_textual("true");
month.set_calligra_number_length("extra-short");
format.remove(0, 5);
} else if (format.startsWith("MMMM")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("long");
month.set_number_textual("true");
format.remove(0, 4);
} else if (format.startsWith("MMM")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("short");
month.set_number_textual("true");
format.remove(0, 3);
} else if (format.startsWith("MM")) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("long");
month.set_number_textual("false"); //not necessary remove it
format.remove(0, 2);
} else if (format.startsWith('M')) {
addTextNumber(text, elementWriter);
number_month month(&elementWriter);
month.set_number_style("short");
month.set_number_textual("false");
format.remove(0, 1);
} else if (format.startsWith("PPPP")) {
addTextNumber(text, elementWriter);
//<number:month number:possessive-form="true" number:textual="true" number:style="long"/>
number_month month(&elementWriter);
month.set_number_style("short");
month.set_number_textual("false");
month.set_number_possessive_form("true");
format.remove(0, 4);
} else if (format.startsWith("PPP")) {
addTextNumber(text, elementWriter);
//<number:month number:possessive-form="true" number:textual="true" number:style="short"/>
number_month month(&elementWriter);
month.set_number_possessive_form("true");
month.set_number_style("short");
month.set_number_textual("false");
format.remove(0, 3);
} else if (format.startsWith("dddd")) {
addTextNumber(text, elementWriter);
number_day_of_week(&elementWriter).set_number_style("long");
format.remove(0, 4);
} else if (format.startsWith("ddd")) {
addTextNumber(text, elementWriter);
number_day_of_week(&elementWriter).set_number_style("short");
format.remove(0, 3);
} else if (format.startsWith("dd")) {
addTextNumber(text, elementWriter);
number_day(&elementWriter).set_number_style("long");
format.remove(0, 2);
} else if (format.startsWith('d')) {
addTextNumber(text, elementWriter);
number_day(&elementWriter).set_number_style("short");
format.remove(0, 1);
} else if (format.startsWith("yyyy")) {
addTextNumber(text, elementWriter);
number_year(&elementWriter).set_number_style("long");
format.remove(0, 4);
} else if (format.startsWith("yy")) {
addTextNumber(text, elementWriter);
number_year(&elementWriter).set_number_style("short");
format.remove(0, 2);
} else {
if (!saveOdfTimeFormat(elementWriter, format, text, antislash)) {
QString elem(format[0]);
format.remove(0, 1);
if (elem == "\\") {
antislash = true;
} else {
text += elem;
antislash = false;
}
}
}
} while (format.length() > 0);
addTextNumber(text, elementWriter);
}
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfFractionStyle(KoGenStyles &mainStyles, const QString &_format,
const QString &_prefix, const QString &_suffix)
{
//debugOdf << "QString saveOdfFractionStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
QString format(_format);
KoGenStyle currentStyle(KoGenStyle::NumericFractionStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
int integer = 0;
int numerator = 0;
int denominator = 0;
int denominatorValue = 0;
bool beforeSlash = true;
do {
if (format[0] == '#')
integer++;
else if (format[0] == '/')
beforeSlash = false;
else if (format[0] == '?') {
if (beforeSlash)
numerator++;
else
denominator++;
} else {
bool ok;
int value = format.toInt(&ok);
if (ok) {
denominatorValue = value;
break;
}
}
format.remove(0, 1);
} while (format.length() > 0);
text = _prefix;
addTextNumber(text, elementWriter);
number_fraction fraction(&elementWriter);
fraction.set_number_min_integer_digits(integer);
fraction.set_number_min_numerator_digits(numerator);
fraction.set_number_min_denominator_digits(denominator);
if (denominatorValue != 0) {
fraction.set_number_denominator_value(denominatorValue);
}
fraction.end();
addCalligraNumericStyleExtension(elementWriter, _suffix, _prefix);
text = _suffix;
addTextNumber(text, elementWriter);
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfNumberStyle(KoGenStyles &mainStyles, const QString &_format,
const QString &_prefix, const QString &_suffix, bool thousandsSep)
{
//debugOdf << "QString saveOdfNumberStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
QString format(_format);
KoGenStyle currentStyle(KoGenStyle::NumericNumberStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
int decimalplaces = 0;
int integerdigits = 0;
bool beforeSeparator = true;
do {
if (format[0] == '.' || format[0] == ',')
beforeSeparator = false;
else if (format[0] == '0' && beforeSeparator)
integerdigits++;
else if (format[0] == '0' && !beforeSeparator)
decimalplaces++;
else
debugOdf << " error format 0";
format.remove(0, 1);
} while (format.length() > 0);
text = _prefix ;
addTextNumber(text, elementWriter);
number_number number(&elementWriter);
//debugOdf << " decimalplaces :" << decimalplaces << " integerdigits :" << integerdigits;
if (!beforeSeparator) {
number.set_number_decimal_places(decimalplaces);
}
number.set_number_min_integer_digits(integerdigits);
if (thousandsSep) {
number.set_number_grouping(true);
}
number.end();
text = _suffix ;
addTextNumber(text, elementWriter);
addCalligraNumericStyleExtension(elementWriter, _suffix, _prefix);
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfBooleanStyle(KoGenStyles &mainStyles, const QString &format, const QString &prefix, const QString &suffix)
{
Q_UNUSED(format);
KoGenStyle currentStyle(KoGenStyle::NumericBooleanStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text = prefix;
addTextNumber(text, elementWriter);
number_boolean(&elementWriter).end();
text = suffix;
addTextNumber(text, elementWriter);
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfPercentageStyle(KoGenStyles &mainStyles, const QString &_format,
const QString &_prefix, const QString &_suffix)
{
//<number:percentage-style style:name="N11">
//<number:number number:decimal-places="2" number:min-integer-digits="1"/>
//<number:text>%</number:text>
//</number:percentage-style>
//debugOdf << "QString saveOdfPercentageStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
QString format(_format);
KoGenStyle currentStyle(KoGenStyle::NumericPercentageStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
int decimalplaces = 0;
int integerdigits = 0;
bool beforeSeparator = true;
do {
if (format[0] == '.' || format[0] == ',')
beforeSeparator = false;
else if (format[0] == '0' && beforeSeparator)
integerdigits++;
else if (format[0] == '0' && !beforeSeparator)
decimalplaces++;
else
debugOdf << " error format 0";
format.remove(0, 1);
} while (format.length() > 0);
text = _prefix ;
addTextNumber(text, elementWriter);
number_number number(&elementWriter);
if (!beforeSeparator) {
number.set_number_decimal_places(decimalplaces);
}
number.set_number_min_integer_digits(integerdigits);
number.end();
QString percent(QChar('%'));
addTextNumber(percent, elementWriter);
text = _suffix ;
addTextNumber(text, elementWriter);
addCalligraNumericStyleExtension(elementWriter, _suffix, _prefix);
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfScientificStyle(KoGenStyles &mainStyles, const QString &_format,
const QString &_prefix, const QString &_suffix, bool thousandsSep)
{
//<number:number-style style:name="N60">
//<number:scientific-number number:decimal-places="2" number:min-integer-digits="1" number:min-exponent-digits="3"/>
//</number:number-style>
//example 000,000e+0000
//debugOdf << "QString saveOdfScientificStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
QString format(_format);
KoGenStyle currentStyle(KoGenStyle::NumericScientificStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
int decimalplace = 0;
int integerdigits = 0;
int exponentdigits = 0;
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
bool beforeSeparator = true;
bool exponential = false;
bool positive = true;
do {
if (!exponential) {
if (format[0] == '0' && beforeSeparator)
integerdigits++;
else if (format[0] == ',' || format[0] == '.')
beforeSeparator = false;
else if (format[0] == '0' && !beforeSeparator)
decimalplace++;
else if (format[0].toLower() == 'e') {
format.remove(0, 1);
if (format[0] == '+')
positive = true;
else if (format[0] == '-')
positive = false;
else
debugOdf << "Error into scientific number";
exponential = true;
}
} else {
if (format[0] == '0' && positive)
exponentdigits++;
else if (format[0] == '0' && !positive)
exponentdigits--;
else
debugOdf << " error into scientific number exponential value";
}
format.remove(0, 1);
} while (format.length() > 0);
text = _prefix ;
addTextNumber(text, elementWriter);
number_scientific_number number(&elementWriter);
//debugOdf << " decimalplace :" << decimalplace << " integerdigits :" << integerdigits << " exponentdigits :" << exponentdigits;
if (!beforeSeparator) {
number.set_number_decimal_places(decimalplace);
}
number.set_number_min_integer_digits(integerdigits);
number.set_number_min_exponent_digits(exponentdigits);
if (thousandsSep) {
number.set_number_grouping(true);
}
number.end();
text = _suffix;
addTextNumber(text, elementWriter);
addCalligraNumericStyleExtension(elementWriter, _suffix, _prefix);
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfCurrencyStyle(KoGenStyles &mainStyles,
const QString &_format, const QString &symbol,
const QString &_prefix, const QString &_suffix)
{
//<number:currency-style style:name="N107P0" style:volatile="true">
//<number:number number:decimal-places="2" number:min-integer-digits="1" number:grouping="true"/>
//<number:text> </number:text>
//<number:currency-symbol>VEB</number:currency-symbol>
//</number:currency-style>
//debugOdf << "QString saveOdfCurrencyStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
QString format(_format);
KoGenStyle currentStyle(KoGenStyle::NumericCurrencyStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text;
int decimalplaces = 0;
int integerdigits = 0;
bool beforeSeparator = true;
do {
if (format[0] == '.' || format[0] == ',')
beforeSeparator = false;
else if (format[0] == '0' && beforeSeparator)
integerdigits++;
else if (format[0] == '0' && !beforeSeparator)
decimalplaces++;
else
debugOdf << " error format 0";
format.remove(0, 1);
} while (format.length() > 0);
text = _prefix ;
addTextNumber(text, elementWriter);
number_number number(&elementWriter);
//debugOdf << " decimalplaces :" << decimalplaces << " integerdigits :" << integerdigits;
if (!beforeSeparator) {
number.set_number_decimal_places(decimalplaces);
}
number.set_number_min_integer_digits(integerdigits);
number.end();
text = _suffix ;
addTextNumber(text, elementWriter);
addCalligraNumericStyleExtension(elementWriter, _suffix, _prefix);
number_currency_symbol sym(&elementWriter);
//debugOdf << " currency-symbol:" << symbol;
sym.addTextNode(symbol.toUtf8());
sym.end();
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
QString saveOdfTextStyle(KoGenStyles &mainStyles, const QString &_format, const QString &_prefix, const QString &_suffix)
{
Q_UNUSED(_format);
//<number:text-style style:name="N100">
//<number:text-content/>
///</number:text-style>
//debugOdf << "QString saveOdfTextStyle( KoGenStyles &mainStyles, const QString & _format ) :" << _format;
KoGenStyle currentStyle(KoGenStyle::NumericTextStyle);
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter elementWriter(&buffer); // TODO pass indentation level
QString text = _prefix ;
addTextNumber(text, elementWriter);
number_text_content(&elementWriter).end();
text = _suffix ;
addTextNumber(text, elementWriter);
addCalligraNumericStyleExtension(elementWriter, _suffix, _prefix);
QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
currentStyle.addChildElement("number", elementContents);
return mainStyles.insert(currentStyle, "N");
}
//This is an extension of numeric style. For the moment we used namespace of
//oasis format for specific calligra extension. Change it for the future.
void addCalligraNumericStyleExtension(KoXmlWriter &elementWriter, const QString &_suffix, const QString &_prefix)
{
if (!_suffix.isEmpty()) {
elementWriter.startElement("number:suffix");
elementWriter.addTextNode(_suffix);
elementWriter.endElement();
}
if (!_prefix.isEmpty()) {
elementWriter.startElement("number:prefix");
elementWriter.addTextNode(_prefix);
elementWriter.endElement();
}
}
}
diff --git a/src/libs/odf/KoOdfPaste.cpp b/src/libs/odf/KoOdfPaste.cpp
index d7d38852..d52e1515 100644
--- a/src/libs/odf/KoOdfPaste.cpp
+++ b/src/libs/odf/KoOdfPaste.cpp
@@ -1,88 +1,89 @@
/* This file is part of the KDE project
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
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 "KoOdfPaste.h"
#include <QBuffer>
#include <QByteArray>
#include <QMimeData>
#include <QString>
#include <OdfDebug.h>
#include <KoStore.h>
#include <KoOdfReadStore.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
KoOdfPaste::KoOdfPaste()
{
}
KoOdfPaste::~KoOdfPaste()
{
}
bool KoOdfPaste::paste(KoOdf::DocumentType documentType, const QMimeData *data)
{
QByteArray arr = data->data(KoOdf::mimeType(documentType));
return paste(documentType, arr);
}
bool KoOdfPaste::paste(KoOdf::DocumentType documentType, const QByteArray &bytes)
{
if (bytes.isEmpty())
return false;
QBuffer buffer;
buffer.setData(bytes);
KoStore *store = KoStore::createStore(&buffer, KoStore::Read);
//FIXME: Use shared_ptr or smth like these to auto delete store on return
// and delete all next "delete store;".
KoOdfReadStore odfStore(store); // KoOdfReadStore does not delete the store on destruction
QString errorMessage;
if (! odfStore.loadAndParse(errorMessage)) {
warnOdf << "loading and parsing failed:" << errorMessage;
delete store;
return false;
}
KoXmlElement content = odfStore.contentDoc().documentElement();
KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
if (realBody.isNull()) {
warnOdf << "No body tag found";
delete store;
return false;
}
KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, KoOdf::bodyContentElement(documentType, false));
if (body.isNull()) {
warnOdf << "No" << KoOdf::bodyContentElement(documentType, true) << "tag found";
delete store;
return false;
}
bool retval = process(body, odfStore);
delete store;
return retval;
}
diff --git a/src/libs/odf/KoOdfReadStore.cpp b/src/libs/odf/KoOdfReadStore.cpp
index c02e88d0..f9a9d305 100644
--- a/src/libs/odf/KoOdfReadStore.cpp
+++ b/src/libs/odf/KoOdfReadStore.cpp
@@ -1,145 +1,146 @@
/* This file is part of the KDE project
Copyright (C) 2005 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zach3n <zachmann@kde.org>
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 "KoOdfReadStore.h"
#include <OdfDebug.h>
#include <klocalizedstring.h>
#include <KoStore.h>
#include <KoXmlReader.h>
#include "KoOdfStylesReader.h"
#include <QXmlStreamReader>
class Q_DECL_HIDDEN KoOdfReadStore::Private
{
public:
Private(KoStore *s)
: store(s)
{
}
KoStore * store;
KoOdfStylesReader stylesReader;
// it is needed to keep the stylesDoc around so that you can access the styles
KoXmlDocument stylesDoc;
KoXmlDocument contentDoc;
KoXmlDocument settingsDoc;
};
KoOdfReadStore::KoOdfReadStore(KoStore *store)
: d(new Private(store))
{
}
KoOdfReadStore::~KoOdfReadStore()
{
delete d;
}
KoStore * KoOdfReadStore::store() const
{
return d->store;
}
KoOdfStylesReader &KoOdfReadStore::styles()
{
return d->stylesReader;
}
KoXmlDocument KoOdfReadStore::contentDoc() const
{
return d->contentDoc;
}
KoXmlDocument KoOdfReadStore::settingsDoc() const
{
return d->settingsDoc;
}
bool KoOdfReadStore::loadAndParse(QString &errorMessage)
{
if (!loadAndParse("content.xml", d->contentDoc, errorMessage)) {
return false;
}
if (d->store->hasFile("styles.xml")) {
if (!loadAndParse("styles.xml", d->stylesDoc, errorMessage)) {
return false;
}
}
// Load styles from style.xml
d->stylesReader.createStyleMap(d->stylesDoc, true);
// Also load styles from content.xml
d->stylesReader.createStyleMap(d->contentDoc, false);
if (d->store->hasFile("settings.xml")) {
loadAndParse("settings.xml", d->settingsDoc, errorMessage);
}
return true;
}
bool KoOdfReadStore::loadAndParse(const QString &fileName, KoXmlDocument &doc, QString &errorMessage)
{
if (!d->store) {
errorMessage = i18n("No store backend");
return false;
}
if (!d->store->isOpen()) {
if (!d->store->open(fileName)) {
debugOdf << "Entry " << fileName << " not found!"; // not a warning as embedded stores don't have to have all files
errorMessage = i18n("Could not find %1", fileName);
return false;
}
}
bool ok = loadAndParse(d->store->device(), doc, errorMessage, fileName);
d->store->close();
return ok;
}
bool KoOdfReadStore::loadAndParse(QIODevice *fileDevice, KoXmlDocument &doc, QString &errorMessage, const QString &fileName)
{
// Error variables for QDomDocument::setContent
QString errorMsg;
int errorLine, errorColumn;
if (!fileDevice->isOpen()) {
fileDevice->open(QIODevice::ReadOnly);
}
QXmlStreamReader reader(fileDevice);
reader.setNamespaceProcessing(true);
bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn);
if (!ok) {
errorOdf << "Parsing error in " << fileName << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errorMessage = i18n("Parsing error in the main document at line %1, column %2\nError message: %3"
, errorLine , errorColumn , errorMsg);
} else {
debugOdf << "File" << fileName << " loaded and parsed";
}
return ok;
}
diff --git a/src/libs/odf/KoOdfStylesReader.cpp b/src/libs/odf/KoOdfStylesReader.cpp
index a3bfeaac..7e7f8dec 100644
--- a/src/libs/odf/KoOdfStylesReader.cpp
+++ b/src/libs/odf/KoOdfStylesReader.cpp
@@ -1,413 +1,414 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoOdfStylesReader.h"
#include "KoXmlNS.h"
#include "KoOdfNotesConfiguration.h"
#include "KoOdfNumberDefinition.h"
#include "KoOdfLineNumberingConfiguration.h"
#include "KoOdfBibliographyConfiguration.h"
#include <OdfDebug.h>
class Q_DECL_HIDDEN KoOdfStylesReader::Private
{
public:
Private()
: globalFootnoteConfiguration(KoOdfNotesConfiguration::Footnote)
, globalEndnoteConfiguration(KoOdfNotesConfiguration::Endnote)
{
}
QHash < QString /*family*/, QHash < QString /*name*/, KoXmlElement* > > customStyles;
// auto-styles in content.xml
QHash < QString /*family*/, QHash < QString /*name*/, KoXmlElement* > > contentAutoStyles;
// auto-styles in styles.xml
QHash < QString /*family*/, QHash < QString /*name*/, KoXmlElement* > > stylesAutoStyles;
QHash < QString /*family*/, KoXmlElement* > defaultStyles;
QHash < QString /*name*/, KoXmlElement* > styles; // page-layout, font-face etc.
QHash < QString /*name*/, KoXmlElement* > masterPages;
QHash < QString /*name*/, KoXmlElement* > presentationPageLayouts;
QHash < QString /*drawType*/, QHash< QString /*name*/, KoXmlElement* > > drawStyles;
QList <KoXmlElement* > tableTemplates;
KoXmlElement officeStyle;
KoXmlElement layerSet;
DataFormatsMap dataFormats;
// XXX: there can also be notes configuration objects _per_ section.
KoOdfNotesConfiguration globalFootnoteConfiguration;
KoOdfNotesConfiguration globalEndnoteConfiguration;
KoOdfBibliographyConfiguration globalBibliographyConfiguration;
KoOdfLineNumberingConfiguration lineNumberingConfiguration;
};
KoOdfStylesReader::KoOdfStylesReader()
: d(new Private)
{
}
KoOdfStylesReader::~KoOdfStylesReader()
{
typedef QHash<QString, KoXmlElement*> AutoStylesMap;
foreach(const AutoStylesMap& map, d->customStyles)
qDeleteAll(map);
foreach(const AutoStylesMap& map, d->contentAutoStyles)
qDeleteAll(map);
foreach(const AutoStylesMap& map, d->stylesAutoStyles)
qDeleteAll(map);
foreach(const DataFormatsMap::mapped_type& dataFormat, d->dataFormats)
delete dataFormat.second;
qDeleteAll(d->defaultStyles);
qDeleteAll(d->styles);
qDeleteAll(d->masterPages);
qDeleteAll(d->presentationPageLayouts);
qDeleteAll(d->tableTemplates);
foreach(const AutoStylesMap& map, d->drawStyles) {
qDeleteAll(map);
}
delete d;
}
void KoOdfStylesReader::createStyleMap(const KoXmlDocument& doc, bool stylesDotXml)
{
const KoXmlElement docElement = doc.documentElement();
// We used to have the office:version check here, but better let the apps do that
KoXmlElement fontStyles = KoXml::namedItemNS(docElement, KoXmlNS::office, "font-face-decls");
if (!fontStyles.isNull()) {
//debugOdf <<"Starting reading in font-face-decls...";
insertStyles(fontStyles, stylesDotXml ? AutomaticInStyles : AutomaticInContent);
}// else
// debugOdf <<"No items found";
//debugOdf <<"Starting reading in office:automatic-styles. stylesDotXml=" << stylesDotXml;
KoXmlElement autoStyles = KoXml::namedItemNS(docElement, KoXmlNS::office, "automatic-styles");
if (!autoStyles.isNull()) {
insertStyles(autoStyles, stylesDotXml ? AutomaticInStyles : AutomaticInContent);
}// else
// debugOdf <<"No items found";
//debugOdf <<"Reading in master styles";
KoXmlNode masterStyles = KoXml::namedItemNS(docElement, KoXmlNS::office, "master-styles");
if (!masterStyles.isNull()) {
KoXmlElement master;
forEachElement(master, masterStyles) {
if (master.localName() == "master-page" &&
master.namespaceURI() == KoXmlNS::style) {
const QString name = master.attributeNS(KoXmlNS::style, "name", QString());
debugOdf << "Master style: '" << name << "' loaded";
d->masterPages.insert(name, new KoXmlElement(master));
} else if (master.localName() == "layer-set" && master.namespaceURI() == KoXmlNS::draw) {
debugOdf << "Master style: layer-set loaded";
d->layerSet = master;
} else
// OASIS docu mentions style:handout-master and draw:layer-set here
warnOdf << "Unknown tag " << master.tagName() << " in office:master-styles";
}
}
debugOdf << "Starting reading in office:styles";
const KoXmlElement officeStyle = KoXml::namedItemNS(docElement, KoXmlNS::office, "styles");
if (!officeStyle.isNull()) {
d->officeStyle = officeStyle;
insertOfficeStyles(officeStyle);
}
//debugOdf <<"Styles read in.";
}
QHash<QString, KoXmlElement*> KoOdfStylesReader::customStyles(const QString& family) const
{
if (family.isNull())
return QHash<QString, KoXmlElement*>();
return d->customStyles.value(family);
}
QHash<QString, KoXmlElement*> KoOdfStylesReader::autoStyles(const QString& family, bool stylesDotXml) const
{
if (family.isNull())
return QHash<QString, KoXmlElement*>();
return stylesDotXml ? d->stylesAutoStyles.value(family) : d->contentAutoStyles.value(family);
}
KoOdfStylesReader::DataFormatsMap KoOdfStylesReader::dataFormats() const
{
return d->dataFormats;
}
KoOdfNotesConfiguration KoOdfStylesReader::globalNotesConfiguration(KoOdfNotesConfiguration::NoteClass noteClass) const
{
switch (noteClass) {
case (KoOdfNotesConfiguration::Endnote):
return d->globalEndnoteConfiguration;
case (KoOdfNotesConfiguration::Footnote):
default:
return d->globalFootnoteConfiguration;
}
}
KoOdfBibliographyConfiguration KoOdfStylesReader::globalBibliographyConfiguration() const
{
return d->globalBibliographyConfiguration;
}
KoOdfLineNumberingConfiguration KoOdfStylesReader::lineNumberingConfiguration() const
{
return d->lineNumberingConfiguration;
}
void KoOdfStylesReader::insertOfficeStyles(const KoXmlElement& styles)
{
KoXmlElement e;
forEachElement(e, styles) {
const QString localName = e.localName();
const QString ns = e.namespaceURI();
if ((ns == KoXmlNS::svg && (
localName == "linearGradient"
|| localName == "radialGradient"))
|| (ns == KoXmlNS::draw && (
localName == "gradient"
|| localName == "hatch"
|| localName == "fill-image"
|| localName == "marker"
|| localName == "stroke-dash"
|| localName == "opacity"))
|| (ns == KoXmlNS::calligra && (
localName == "conicalGradient"))
) {
QString drawType = localName;
if (drawType.endsWith(QLatin1String("Gradient"))) {
drawType = "gradient";
}
const QString name = e.attributeNS(KoXmlNS::draw, "name", QString());
Q_ASSERT(!name.isEmpty());
KoXmlElement* ep = new KoXmlElement(e);
d->drawStyles[drawType].insert(name, ep);
}else if(ns == KoXmlNS::table && localName == "table-template") {
d->tableTemplates.append(new KoXmlElement(e));
} else {
insertStyle(e, CustomInStyles);
}
}
}
void KoOdfStylesReader::insertStyles(const KoXmlElement& styles, TypeAndLocation typeAndLocation)
{
//debugOdf <<"Inserting styles from" << styles.tagName();
KoXmlElement e;
forEachElement(e, styles)
insertStyle(e, typeAndLocation);
}
void KoOdfStylesReader::insertStyle(const KoXmlElement& e, TypeAndLocation typeAndLocation)
{
const QString localName = e.localName();
const QString ns = e.namespaceURI();
const QString name = e.attributeNS(KoXmlNS::style, "name", QString());
if ((ns == KoXmlNS::style && localName == "style")
|| (ns == KoXmlNS::text && localName == "list-style")) {
const QString family = localName == "list-style" ? "list" : e.attributeNS(KoXmlNS::style, "family", QString());
if (typeAndLocation == AutomaticInContent) {
QHash<QString, KoXmlElement*>& dict = d->contentAutoStyles[ family ];
if (dict.contains(name)) {
debugOdf << "Auto-style: '" << name << "' already exists";
delete dict.take(name);
}
dict.insert(name, new KoXmlElement(e));
//debugOdf <<"Style: '" << name <<"' loaded as a style auto style";
} else if (typeAndLocation == AutomaticInStyles) {
QHash<QString, KoXmlElement*>& dict = d->stylesAutoStyles[ family ];
if (dict.contains(name)) {
debugOdf << "Auto-style: '" << name << "' already exists";
delete dict.take(name);
}
dict.insert(name, new KoXmlElement(e));
//debugOdf <<"Style: '" << name <<"' loaded as a style auto style";
} else {
QHash<QString, KoXmlElement*>& dict = d->customStyles[ family ];
if (dict.contains(name)) {
debugOdf << "Style: '" << name << "' already exists";
delete dict.take(name);
}
dict.insert(name, new KoXmlElement(e));
//debugOdf <<"Style: '" << name <<"' loaded";
}
} else if (ns == KoXmlNS::style && (
localName == "page-layout"
|| localName == "font-face")) {
if (d->styles.contains(name)) {
debugOdf << "Style: '" << name << "' already exists";
delete d->styles.take(name);
}
d->styles.insert(name, new KoXmlElement(e));
} else if (localName == "presentation-page-layout" && ns == KoXmlNS::style) {
if (d->presentationPageLayouts.contains(name)) {
debugOdf << "Presentation page layout: '" << name << "' already exists";
delete d->presentationPageLayouts.take(name);
}
d->presentationPageLayouts.insert(name, new KoXmlElement(e));
} else if (localName == "default-style" && ns == KoXmlNS::style) {
const QString family = e.attributeNS(KoXmlNS::style, "family", QString());
if (!family.isEmpty())
d->defaultStyles.insert(family, new KoXmlElement(e));
} else if (ns == KoXmlNS::number && (
localName == "number-style"
|| localName == "currency-style"
|| localName == "percentage-style"
|| localName == "boolean-style"
|| localName == "text-style"
|| localName == "date-style"
|| localName == "time-style")) {
QPair<QString, KoOdfNumberStyles::NumericStyleFormat> numberStyle = KoOdfNumberStyles::loadOdfNumberStyle(e);
d->dataFormats.insert(numberStyle.first, qMakePair(numberStyle.second, new KoXmlElement(e)));
} else if (ns == KoXmlNS::text && localName == "notes-configuration") {
if (e.attributeNS(KoXmlNS::text, "note-class", "footnote") == "footnote") {
d->globalFootnoteConfiguration.loadOdf(e);
} else {
d->globalEndnoteConfiguration.loadOdf(e);
}
} else if (ns == KoXmlNS::text && localName == "linenumbering-configuration") {
d->lineNumberingConfiguration.loadOdf(e);
} else if (ns == KoXmlNS::text && localName == "bibliography-configuration") {
KoOdfBibliographyConfiguration bibConfiguration;
bibConfiguration.loadOdf(e);
d->globalBibliographyConfiguration = bibConfiguration;
}
}
KoXmlElement *KoOdfStylesReader::defaultStyle(const QString &family) const
{
return d->defaultStyles[family];
}
KoXmlElement KoOdfStylesReader::officeStyle() const
{
return d->officeStyle;
}
KoXmlElement KoOdfStylesReader::layerSet() const
{
return d->layerSet;
}
QHash<QString, KoXmlElement*> KoOdfStylesReader::masterPages() const
{
return d->masterPages;
}
QHash<QString, KoXmlElement*> KoOdfStylesReader::presentationPageLayouts() const
{
return d->presentationPageLayouts;
}
QHash<QString, KoXmlElement*> KoOdfStylesReader::drawStyles(const QString &drawType) const
{
return d->drawStyles.value(drawType);
}
const KoXmlElement* KoOdfStylesReader::findStyle(const QString& name) const
{
return d->styles[ name ];
}
const KoXmlElement* KoOdfStylesReader::findStyle(const QString& styleName, const QString& family) const
{
const KoXmlElement* style = findStyleCustomStyle(styleName, family);
if (!style)
style = findAutoStyleStyle(styleName, family);
if (!style)
style = findContentAutoStyle(styleName, family);
return style;
}
const KoXmlElement* KoOdfStylesReader::findStyle(const QString& styleName, const QString& family, bool stylesDotXml) const
{
const KoXmlElement* style = findStyleCustomStyle(styleName, family);
if (!style && !stylesDotXml) {
style = findContentAutoStyle(styleName, family);
}
if (!style && stylesDotXml) {
style = findAutoStyleStyle(styleName, family);
}
return style;
}
const KoXmlElement* KoOdfStylesReader::findStyleCustomStyle(const QString& styleName, const QString& family) const
{
const KoXmlElement* style = d->customStyles.value(family).value(styleName);
if (style && !family.isEmpty()) {
const QString styleFamily = style->attributeNS(KoXmlNS::style, "family", QString());
if (styleFamily != family) {
warnOdf << "KoOdfStylesReader: was looking for style " << styleName
<< " in family " << family << " but got " << styleFamily << endl;
}
}
return style;
}
const KoXmlElement* KoOdfStylesReader::findAutoStyleStyle(const QString& styleName, const QString& family) const
{
const KoXmlElement* style = d->stylesAutoStyles.value(family).value(styleName);
if (style) {
const QString styleFamily = style->attributeNS(KoXmlNS::style, "family", QString());
if (styleFamily != family) {
warnOdf << "KoOdfStylesReader: was looking for style " << styleName
<< " in family " << family << " but got " << styleFamily << endl;
}
}
return style;
}
const KoXmlElement* KoOdfStylesReader::findContentAutoStyle(const QString& styleName, const QString& family) const
{
const KoXmlElement* style = d->contentAutoStyles.value(family).value(styleName);
if (style) {
const QString styleFamily = style->attributeNS(KoXmlNS::style, "family", QString());
if (styleFamily != family) {
warnOdf << "KoOdfStylesReader: was looking for style " << styleName
<< " in family " << family << " but got " << styleFamily << endl;
}
}
return style;
}
QList<KoXmlElement*> KoOdfStylesReader::tableTemplates() const
{
return d->tableTemplates;
}
diff --git a/src/libs/odf/KoOdfWriteStore.cpp b/src/libs/odf/KoOdfWriteStore.cpp
index 09d0cf4b..d103b560 100644
--- a/src/libs/odf/KoOdfWriteStore.cpp
+++ b/src/libs/odf/KoOdfWriteStore.cpp
@@ -1,229 +1,230 @@
/* This file is part of the KDE project
Copyright (C) 2005 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
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 "KoOdfWriteStore.h"
#include <QBuffer>
#include <QTemporaryFile>
#include <OdfDebug.h>
#include <klocalizedstring.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoXmlWriter.h>
#include "KoXmlNS.h"
struct Q_DECL_HIDDEN KoOdfWriteStore::Private {
Private(KoStore * store)
: store(store)
, storeDevice(0)
, contentWriter(0)
, bodyWriter(0)
, manifestWriter(0)
, contentTmpFile(0) {}
~Private() {
// If all the right close methods were called, nothing should remain,
// so those deletes are really just in case.
Q_ASSERT(!contentWriter);
delete contentWriter;
Q_ASSERT(!bodyWriter);
delete bodyWriter;
Q_ASSERT(!storeDevice);
delete storeDevice;
Q_ASSERT(!manifestWriter);
delete manifestWriter;
}
KoStore * store;
KoStoreDevice * storeDevice;
KoXmlWriter * contentWriter;
KoXmlWriter * bodyWriter;
KoXmlWriter * manifestWriter;
QTemporaryFile * contentTmpFile;
};
KoOdfWriteStore::KoOdfWriteStore(KoStore* store)
: d(new Private(store))
{
}
KoOdfWriteStore::~KoOdfWriteStore()
{
delete d;
}
KoXmlWriter* KoOdfWriteStore::createOasisXmlWriter(QIODevice* dev, const char* rootElementName)
{
KoXmlWriter* writer = new KoXmlWriter(dev);
writer->startDocument(rootElementName);
writer->startElement(rootElementName);
if (qstrcmp(rootElementName, "VL:version-list") == 0) {
writer->addAttribute("xmlns:VL", KoXmlNS::VL);
writer->addAttribute("xmlns:dc", KoXmlNS::dc);
return writer;
}
writer->addAttribute("xmlns:office", KoXmlNS::office);
writer->addAttribute("xmlns:meta", KoXmlNS::meta);
if (qstrcmp(rootElementName, "office:document-meta") != 0) {
writer->addAttribute("xmlns:config", KoXmlNS::config);
writer->addAttribute("xmlns:text", KoXmlNS::text);
writer->addAttribute("xmlns:table", KoXmlNS::table);
writer->addAttribute("xmlns:draw", KoXmlNS::draw);
writer->addAttribute("xmlns:presentation", KoXmlNS::presentation);
writer->addAttribute("xmlns:dr3d", KoXmlNS::dr3d);
writer->addAttribute("xmlns:chart", KoXmlNS::chart);
writer->addAttribute("xmlns:form", KoXmlNS::form);
writer->addAttribute("xmlns:script", KoXmlNS::script);
writer->addAttribute("xmlns:style", KoXmlNS::style);
writer->addAttribute("xmlns:number", KoXmlNS::number);
writer->addAttribute("xmlns:math", KoXmlNS::math);
writer->addAttribute("xmlns:svg", KoXmlNS::svg);
writer->addAttribute("xmlns:fo", KoXmlNS::fo);
writer->addAttribute("xmlns:anim", KoXmlNS::anim);
writer->addAttribute("xmlns:smil", KoXmlNS::smil);
writer->addAttribute("xmlns:calligra", KoXmlNS::calligra);
writer->addAttribute("xmlns:officeooo", KoXmlNS::officeooo);
writer->addAttribute("xmlns:delta", KoXmlNS::delta);
writer->addAttribute("xmlns:split", KoXmlNS::split);
writer->addAttribute("xmlns:ac", KoXmlNS::ac);
}
if (qstrcmp(rootElementName, "office:document-settings") == 0) {
writer->addAttribute("xmlns:ooo", KoXmlNS::ooo);
}
writer->addAttribute("office:version", "1.2");
writer->addAttribute("xmlns:dc", KoXmlNS::dc);
writer->addAttribute("xmlns:xlink", KoXmlNS::xlink);
return writer;
}
KoStore* KoOdfWriteStore::store() const
{
return d->store;
}
KoXmlWriter* KoOdfWriteStore::contentWriter()
{
if (!d->contentWriter) {
if (!d->store->open("content.xml")) {
return 0;
}
d->storeDevice = new KoStoreDevice(d->store);
d->contentWriter = createOasisXmlWriter(d->storeDevice, "office:document-content");
}
return d->contentWriter;
}
KoXmlWriter* KoOdfWriteStore::bodyWriter()
{
if (!d->bodyWriter) {
Q_ASSERT(!d->contentTmpFile);
d->contentTmpFile = new QTemporaryFile;
if (!d->contentTmpFile->open()) {
warnOdf << "Failed to open the temporary content file";
delete d->contentTmpFile;
d->contentTmpFile = 0;
return 0;
}
d->bodyWriter = new KoXmlWriter(d->contentTmpFile, 1);
}
return d->bodyWriter;
}
bool KoOdfWriteStore::closeContentWriter()
{
Q_ASSERT(d->bodyWriter);
Q_ASSERT(d->contentTmpFile);
delete d->bodyWriter; d->bodyWriter = 0;
// copy over the contents from the tempfile to the real one
d->contentTmpFile->close(); // does not really close but seeks to the beginning of the file
if (d->contentWriter) {
d->contentWriter->addCompleteElement(d->contentTmpFile);
}
d->contentTmpFile->close(); // seek again to the beginning
delete d->contentTmpFile; d->contentTmpFile = 0; // and finally close and remove the QTemporaryFile
if (d->contentWriter) {
d->contentWriter->endElement(); // document-content
d->contentWriter->endDocument();
delete d->contentWriter;
d->contentWriter = 0;
}
delete d->storeDevice; d->storeDevice = 0;
if (!d->store->close()) { // done with content.xml
return false;
}
return true;
}
KoXmlWriter* KoOdfWriteStore::manifestWriter(const char* mimeType)
{
if (!d->manifestWriter) {
// the pointer to the buffer is already stored in the KoXmlWriter, no need to store it here as well
QBuffer *manifestBuffer = new QBuffer;
manifestBuffer->open(QIODevice::WriteOnly);
d->manifestWriter = new KoXmlWriter(manifestBuffer);
d->manifestWriter->startDocument("manifest:manifest");
d->manifestWriter->startElement("manifest:manifest");
d->manifestWriter->addAttribute("xmlns:manifest", KoXmlNS::manifest);
d->manifestWriter->addAttribute("manifest:version", "1.2");
d->manifestWriter->addManifestEntry("/", mimeType);
}
return d->manifestWriter;
}
KoXmlWriter* KoOdfWriteStore::manifestWriter()
{
Q_ASSERT(d->manifestWriter);
return d->manifestWriter;
}
bool KoOdfWriteStore::closeManifestWriter(bool writeMainfest)
{
Q_ASSERT(d->manifestWriter);
bool ok = true;
if (writeMainfest) {
d->manifestWriter->endElement();
d->manifestWriter->endDocument();
QBuffer* buffer = static_cast<QBuffer *>(d->manifestWriter->device());
if (d->store->open("META-INF/manifest.xml")) {
qint64 written = d->store->write(buffer->buffer());
ok = (written == (qint64) buffer->buffer().size() && d->store->close());
} else {
ok = false;
}
delete buffer;
}
delete d->manifestWriter; d->manifestWriter = 0;
return ok;
}
diff --git a/src/libs/odf/KoPageFormat.cpp b/src/libs/odf/KoPageFormat.cpp
index 9ffe799b..45aef1ff 100644
--- a/src/libs/odf/KoPageFormat.cpp
+++ b/src/libs/odf/KoPageFormat.cpp
@@ -1,188 +1,189 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright 2002, 2003 David Faure <faure@kde.org>
Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
Copyright 2007 Thomas Zander <zander@kde.org>
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 "KoPageFormat.h"
#include <klocalizedstring.h>
#include <OdfDebug.h>
// paper formats ( mm )
#define PG_A3_WIDTH 297.0
#define PG_A3_HEIGHT 420.0
#define PG_A4_WIDTH 210.0
#define PG_A4_HEIGHT 297.0
#define PG_A5_WIDTH 148.0
#define PG_A5_HEIGHT 210.0
#define PG_B5_WIDTH 182.0
#define PG_B5_HEIGHT 257.0
#define PG_US_LETTER_WIDTH 216.0
#define PG_US_LETTER_HEIGHT 279.0
#define PG_US_LEGAL_WIDTH 216.0
#define PG_US_LEGAL_HEIGHT 356.0
#define PG_US_EXECUTIVE_WIDTH 191.0
#define PG_US_EXECUTIVE_HEIGHT 254.0
// To ignore the clang warning we get because we have a
// for (int i = 0; pageFormatInfo[i].format != -1 ;i++)
// construct and pageFormatInfo has (KoPageFormat::Format) - 1
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
#endif
struct PageFormatInfo {
KoPageFormat::Format format;
QPrinter::PageSize qprinter;
const char* shortName; // Short name
const char* descriptiveName; // Full name, which will be translated
qreal width; // in mm
qreal height; // in mm
};
// NOTES:
// - the width and height of non-ISO formats are rounded
// http://en.wikipedia.org/wiki/Paper_size can help
// - the comments "should be..." indicates the exact values if the inch sizes would be multiplied by 25.4 mm/inch
const PageFormatInfo pageFormatInfo[] = {
{ KoPageFormat::IsoA3Size, QPrinter::A3, "A3", I18N_NOOP2("Page size", "ISO A3"), 297.0, 420.0 },
{ KoPageFormat::IsoA4Size, QPrinter::A4, "A4", I18N_NOOP2("Page size", "ISO A4"), 210.0, 297.0 },
{ KoPageFormat::IsoA5Size, QPrinter::A5, "A5", I18N_NOOP2("Page size", "ISO A5"), 148.0, 210.0 },
{ KoPageFormat::UsLetterSize, QPrinter::Letter, "Letter", I18N_NOOP2("Page size", "US Letter"), 215.9, 279.4 },
{ KoPageFormat::UsLegalSize, QPrinter::Legal, "Legal", I18N_NOOP2("Page size", "US Legal"), 215.9, 355.6 },
{ KoPageFormat::ScreenSize, QPrinter::A4, "Screen", I18N_NOOP2("Page size", "Screen"), PG_A4_HEIGHT, PG_A4_WIDTH }, // Custom, so fall back to A4
{ KoPageFormat::CustomSize, QPrinter::A4, "Custom", I18N_NOOP2("Page size", "Custom"), PG_A4_WIDTH, PG_A4_HEIGHT }, // Custom, so fall back to A4
{ KoPageFormat::IsoB5Size, QPrinter::B5, "B5", I18N_NOOP2("Page size", "ISO B5"), 182.0, 257.0 },
{ KoPageFormat::UsExecutiveSize, QPrinter::Executive, "Executive", I18N_NOOP2("Page size", "US Executive"), 191.0, 254.0 }, // should be 190.5 mm x 254.0 mm
{ KoPageFormat::IsoA0Size, QPrinter::A0, "A0", I18N_NOOP2("Page size", "ISO A0"), 841.0, 1189.0 },
{ KoPageFormat::IsoA1Size, QPrinter::A1, "A1", I18N_NOOP2("Page size", "ISO A1"), 594.0, 841.0 },
{ KoPageFormat::IsoA2Size, QPrinter::A2, "A2", I18N_NOOP2("Page size", "ISO A2"), 420.0, 594.0 },
{ KoPageFormat::IsoA6Size, QPrinter::A6, "A6", I18N_NOOP2("Page size", "ISO A6"), 105.0, 148.0 },
{ KoPageFormat::IsoA7Size, QPrinter::A7, "A7", I18N_NOOP2("Page size", "ISO A7"), 74.0, 105.0 },
{ KoPageFormat::IsoA8Size, QPrinter::A8, "A8", I18N_NOOP2("Page size", "ISO A8"), 52.0, 74.0 },
{ KoPageFormat::IsoA9Size, QPrinter::A9, "A9", I18N_NOOP2("Page size", "ISO A9"), 37.0, 52.0 },
{ KoPageFormat::IsoB0Size, QPrinter::B0, "B0", I18N_NOOP2("Page size", "ISO B0"), 1030.0, 1456.0 },
{ KoPageFormat::IsoB1Size, QPrinter::B1, "B1", I18N_NOOP2("Page size", "ISO B1"), 728.0, 1030.0 },
{ KoPageFormat::IsoB10Size, QPrinter::B10, "B10", I18N_NOOP2("Page size", "ISO B10"), 32.0, 45.0 },
{ KoPageFormat::IsoB2Size, QPrinter::B2, "B2", I18N_NOOP2("Page size", "ISO B2"), 515.0, 728.0 },
{ KoPageFormat::IsoB3Size, QPrinter::B3, "B3", I18N_NOOP2("Page size", "ISO B3"), 364.0, 515.0 },
{ KoPageFormat::IsoB4Size, QPrinter::B4, "B4", I18N_NOOP2("Page size", "ISO B4"), 257.0, 364.0 },
{ KoPageFormat::IsoB6Size, QPrinter::B6, "B6", I18N_NOOP2("Page size", "ISO B6"), 128.0, 182.0 },
{ KoPageFormat::IsoC5Size, QPrinter::C5E, "C5", I18N_NOOP2("Page size", "ISO C5"), 163.0, 229.0 }, // Some sources tells: 162 mm x 228 mm
{ KoPageFormat::UsComm10Size, QPrinter::Comm10E, "Comm10", I18N_NOOP2("Page size", "US Common 10"), 105.0, 241.0 }, // should be 104.775 mm x 241.3 mm
{ KoPageFormat::IsoDLSize, QPrinter::DLE, "DL", I18N_NOOP2("Page size", "ISO DL"), 110.0, 220.0 },
{ KoPageFormat::UsFolioSize, QPrinter::Folio, "Folio", I18N_NOOP2("Page size", "US Folio"), 210.0, 330.0 }, // should be 209.54 mm x 330.2 mm
{ KoPageFormat::UsLedgerSize, QPrinter::Ledger, "Ledger", I18N_NOOP2("Page size", "US Ledger"), 432.0, 279.0 }, // should be 431.8 mm x 297.4 mm
{ KoPageFormat::UsTabloidSize, QPrinter::Tabloid, "Tabloid", I18N_NOOP2("Page size", "US Tabloid"), 279.0, 432.0 }, // should be 297.4 mm x 431.8 mm
{(KoPageFormat::Format) - 1, (QPrinter::PageSize) - 1, "", "", -1, -1 }
};
QPrinter::PageSize KoPageFormat::printerPageSize(KoPageFormat::Format format)
{
if (format == ScreenSize) {
warnOdf << "You use the page layout SCREEN. Printing in ISO A4 Landscape.";
return QPrinter::A4;
}
if (format == CustomSize) {
warnOdf << "The used page layout (Custom) is not supported by KQPrinter. Printing in A4.";
return QPrinter::A4;
}
return pageFormatInfo[ format ].qprinter;
}
qreal KoPageFormat::width(Format format, Orientation orientation)
{
if (orientation == Landscape)
return height(format, Portrait);
return pageFormatInfo[ format ].width;
}
qreal KoPageFormat::height(Format format, Orientation orientation)
{
if (orientation == Landscape)
return width(format, Portrait);
return pageFormatInfo[ format ].height;
}
KoPageFormat::Format KoPageFormat::guessFormat(qreal width, qreal height)
{
for (int i = 0; pageFormatInfo[i].format != -1 ;i++) {
// We have some tolerance. 1pt is a third of a mm, this is
// barely noticeable for a page size.
if (qAbs(width - pageFormatInfo[i].width) < 1.0 && qAbs(height - pageFormatInfo[i].height) < 1.0)
return pageFormatInfo[i].format;
}
return CustomSize;
}
QString KoPageFormat::formatString(Format format)
{
return QString::fromLatin1(pageFormatInfo[ format ].shortName);
}
KoPageFormat::Format KoPageFormat::formatFromString(const QString & string)
{
for (int i = 0; pageFormatInfo[i].format != -1 ;i++) {
if (string == QString::fromLatin1(pageFormatInfo[ i ].shortName))
return pageFormatInfo[ i ].format;
}
// We do not know the format name, so we have a custom format
return CustomSize;
}
KoPageFormat::Format KoPageFormat::defaultFormat()
{
int qprinter;
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
qprinter = (int)QPageSize::Letter;
}
else {
qprinter = (int)QPageSize::A4;
}
for (int i = 0; pageFormatInfo[i].format != -1 ;i++) {
if (pageFormatInfo[ i ].qprinter == qprinter)
return static_cast<Format>(i);
}
return IsoA4Size;
}
QString KoPageFormat::name(Format format)
{
return i18nc("Page size", pageFormatInfo[ format ].descriptiveName);
}
QStringList KoPageFormat::localizedPageFormatNames()
{
QStringList lst;
for (int i = 0; pageFormatInfo[i].format != -1 ;i++) {
lst << i18nc("Page size", pageFormatInfo[ i ].descriptiveName);
}
return lst;
}
QStringList KoPageFormat::pageFormatNames()
{
QStringList lst;
for (int i = 0; pageFormatInfo[i].format != -1 ;i++) {
lst << pageFormatInfo[ i ].shortName;
}
return lst;
}
diff --git a/src/libs/odf/KoPageLayout.cpp b/src/libs/odf/KoPageLayout.cpp
index 0fa313d7..620596c5 100644
--- a/src/libs/odf/KoPageLayout.cpp
+++ b/src/libs/odf/KoPageLayout.cpp
@@ -1,176 +1,177 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright 2002, 2003 David Faure <faure@kde.org>
Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
Copyright 2007, 2010 Thomas Zander <zander@kde.org>
Copyright 2009 Inge Wallin <inge@lysator.liu.se>
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 "KoPageLayout.h"
#include <OdfDebug.h>
#include "KoXmlNS.h"
#include "KoUnit.h"
#include "KoXmlReader.h"
KoGenStyle KoPageLayout::saveOdf() const
{
KoGenStyle style(KoGenStyle::PageLayoutStyle);
// Save page dimension.
style.addPropertyPt("fo:page-width", width);
style.addPropertyPt("fo:page-height", height);
// Save margins. If all margins are the same, only one value needs to be saved.
if (leftMargin == topMargin && leftMargin == rightMargin && leftMargin == bottomMargin) {
style.addPropertyPt("fo:margin", leftMargin);
}
else {
style.addPropertyPt("fo:margin-left", leftMargin);
style.addPropertyPt("fo:margin-right", rightMargin);
style.addPropertyPt("fo:margin-top", topMargin);
style.addPropertyPt("fo:margin-bottom", bottomMargin);
}
// Save padding. If all paddings are the same, only one value needs to be saved.
if (leftPadding == topPadding && leftPadding == rightPadding && leftPadding == bottomPadding) {
style.addPropertyPt("fo:padding", leftPadding);
}
else {
style.addPropertyPt("fo:padding-left", leftPadding);
style.addPropertyPt("fo:padding-right", rightPadding);
style.addPropertyPt("fo:padding-top", topPadding);
style.addPropertyPt("fo:padding-bottom", bottomPadding);
}
// If there are any page borders, add them to the style.
border.saveOdf(style);
style.addProperty("style:print-orientation",
(orientation == KoPageFormat::Landscape
? "landscape" : "portrait"));
return style;
}
void KoPageLayout::loadOdf(const KoXmlElement &style)
{
KoXmlElement properties(KoXml::namedItemNS(style, KoXmlNS::style,
"page-layout-properties"));
if (!properties.isNull()) {
KoPageLayout standard;
// Page dimension -- width / height
width = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "page-width"),
standard.width);
height = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "page-height"),
standard.height);
// Page orientation
if (properties.attributeNS(KoXmlNS::style, "print-orientation", QString()) == "portrait")
orientation = KoPageFormat::Portrait;
else
orientation = KoPageFormat::Landscape;
// Margins. Check if there is one "margin" attribute and use it for all
// margins if there is. Otherwise load the individual margins.
if (properties.hasAttributeNS(KoXmlNS::fo, "margin")) {
leftMargin = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "margin"));
topMargin = leftMargin;
rightMargin = leftMargin;
bottomMargin = leftMargin;
} else {
/*
If one of the individual margins is specified then the default for the others is zero.
Otherwise all of them are set to 20mm.
*/
qreal defaultValue = 0;
if (!(properties.hasAttributeNS(KoXmlNS::fo, "margin-left")
|| properties.hasAttributeNS(KoXmlNS::fo, "margin-top")
|| properties.hasAttributeNS(KoXmlNS::fo, "margin-right")
|| properties.hasAttributeNS(KoXmlNS::fo, "margin-bottom")))
defaultValue = MM_TO_POINT(20.0); // no margin specified at all, lets make it 20mm
leftMargin = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "margin-left"), defaultValue);
topMargin = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "margin-top"), defaultValue);
rightMargin = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "margin-right"), defaultValue);
bottomMargin = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "margin-bottom"), defaultValue);
}
// Padding. Same reasoning as for margins
if (properties.hasAttributeNS(KoXmlNS::fo, "padding")) {
leftPadding = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "padding"));
topPadding = leftPadding;
rightPadding = leftPadding;
bottomPadding = leftPadding;
}
else {
leftPadding = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "padding-left"));
topPadding = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "padding-top"));
rightPadding = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "padding-right"));
bottomPadding = KoUnit::parseValue(properties.attributeNS(KoXmlNS::fo, "padding-bottom"));
}
// Parse border properties if there are any.
border.loadOdf(properties);
// guessFormat takes millimeters
if (orientation == KoPageFormat::Landscape)
format = KoPageFormat::guessFormat(POINT_TO_MM(height), POINT_TO_MM(width));
else
format = KoPageFormat::guessFormat(POINT_TO_MM(width), POINT_TO_MM(height));
}
}
bool KoPageLayout::operator==(const KoPageLayout &l) const
{
return qFuzzyCompare(width,l.width)
&& qFuzzyCompare(height,l.height)
&& qFuzzyCompare(leftMargin,l.leftMargin)
&& qFuzzyCompare(rightMargin,l.rightMargin)
&& qFuzzyCompare(topMargin,l.topMargin)
&& qFuzzyCompare(bottomMargin,l.bottomMargin)
&& qFuzzyCompare(pageEdge,l.pageEdge)
&& qFuzzyCompare(bindingSide,l.bindingSide)
&& border == l.border;
}
bool KoPageLayout::operator!=(const KoPageLayout& l) const
{
return !((*this) == l);
}
KoPageLayout::KoPageLayout()
: format(KoPageFormat::defaultFormat())
, orientation(KoPageFormat::Portrait)
, width(MM_TO_POINT(KoPageFormat::width(format, orientation)))
, height(MM_TO_POINT(KoPageFormat::height(format, orientation)))
, leftMargin(MM_TO_POINT(20.0))
, rightMargin(MM_TO_POINT(20.0))
, topMargin(MM_TO_POINT(20.0))
, bottomMargin(MM_TO_POINT(20.0))
, pageEdge(-1)
, bindingSide(-1)
, leftPadding(0)
, rightPadding(0)
, topPadding(0)
, bottomPadding(0)
, border()
{
}
diff --git a/src/libs/odf/KoShadowStyle.cpp b/src/libs/odf/KoShadowStyle.cpp
index 1ee7b81e..95d61b7e 100644
--- a/src/libs/odf/KoShadowStyle.cpp
+++ b/src/libs/odf/KoShadowStyle.cpp
@@ -1,164 +1,165 @@
/* This file is part of the KDE project
*
* Copyright (C) 2011 Pierre Ducroquet <pinaraf@pinaraf.info>
*
* 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 "KoShadowStyle.h"
#include <KoUnit.h>
// KoShadowStyle private class
class KoShadowStylePrivate: public QSharedData
{
public:
KoShadowStylePrivate();
~KoShadowStylePrivate();
QVector<KoShadowStyle::ShadowData> shadows;
};
KoShadowStylePrivate::KoShadowStylePrivate()
{
}
KoShadowStylePrivate::~KoShadowStylePrivate()
{
}
// KoShadowStyle::ShadowData structure
KoShadowStyle::ShadowData::ShadowData()
: color(), offset(0, 0), radius(0.0)
{
}
bool KoShadowStyle::ShadowData::operator==(const KoShadowStyle::ShadowData &other) const
{
return (color == other.color) && (offset == other.offset) && (radius == other.radius);
}
// KoShadowStyle class
KoShadowStyle::KoShadowStyle()
: d(new KoShadowStylePrivate)
{
}
KoShadowStyle::KoShadowStyle(const KoShadowStyle &other)
: d(other.d)
{
}
KoShadowStyle::~KoShadowStyle()
{
}
bool KoShadowStyle::operator==(const KoShadowStyle &other) const
{
if (d.data() == other.d.data())
return true;
if (shadowCount() != other.shadowCount())
return false;
foreach (const ShadowData &data, d->shadows)
{
if (!other.d->shadows.contains(data))
return false;
}
return true;
}
bool KoShadowStyle::operator!=(const KoShadowStyle &other) const
{
return !operator==(other);
}
// load value string as specified by CSS2 §7.16.5 "text-shadow"
bool KoShadowStyle::loadOdf (const QString &data)
{
if (data == QLatin1String("none"))
return true;
const QStringList sub_shadows = data.split(QLatin1Char(','));
foreach (const QString &shadow, sub_shadows) {
QStringList words = shadow.split(QLatin1Char(' '), QString::SkipEmptyParts);
if (words.isEmpty())
return false;
KoShadowStyle::ShadowData currentData;
// look for color at begin
QColor shadowColor(words.first());
if (shadowColor.isValid()) {
currentData.color = shadowColor;
words.removeFirst();
} else if (words.length() > 2) {
// look for color at end, if there could be one
shadowColor = QColor(words.last());
if (shadowColor.isValid()) {
currentData.color = shadowColor;
words.removeLast();
}
}
// We keep an invalid color.if none was found
// "Each shadow effect must specify a shadow offset and may optionally
// specify a blur radius and a shadow color.", from CSS2 §7.16.5 "text-shadow"
// But for some reason also no offset has been accepted before. TODO: which?
if (! words.isEmpty()) {
if ((words.length() < 2) || (words.length() > 3))
return false;
// Parse offset
currentData.offset.setX(KoUnit::parseValue(words.at(0), 0.0));
currentData.offset.setY(KoUnit::parseValue(words.at(1), 0.0));
// Parse blur radius if present
if (words.length() == 3)
currentData.radius = KoUnit::parseValue(words.at(2), 0.0);
}
d->shadows << currentData;
}
return true;
}
int KoShadowStyle::shadowCount() const
{
return d->shadows.size();
}
QString KoShadowStyle::saveOdf() const
{
if (d->shadows.isEmpty())
return QLatin1String("none");
QStringList parts;
const QString pt = QLatin1String("%1pt");
foreach (const ShadowData &data, d->shadows) {
QStringList elements;
if (data.color.isValid()) {
elements << data.color.name();
}
elements << pt.arg(data.offset.x()) << pt.arg(data.offset.y());
if (data.radius != 0)
elements << pt.arg(data.radius);
parts << elements.join(QLatin1String(" "));
}
return parts.join(QLatin1String(","));
}
diff --git a/src/libs/odf/KoStyleStack.cpp b/src/libs/odf/KoStyleStack.cpp
index 44d9dad6..85ea050f 100644
--- a/src/libs/odf/KoStyleStack.cpp
+++ b/src/libs/odf/KoStyleStack.cpp
@@ -1,278 +1,279 @@
/* This file is part of the KDE project
Copyright (c) 2003 Lukas Tinkl <lukas@kde.org>
Copyright (c) 2003 David Faure <faure@kde.org>
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 "KoStyleStack.h"
#include "KoUnit.h"
#include "KoXmlNS.h"
#include <OdfDebug.h>
//#define DEBUG_STYLESTACK
class KoStyleStack::KoStyleStackPrivate
{
};
KoStyleStack::KoStyleStack()
: m_styleNSURI(KoXmlNS::style), m_foNSURI(KoXmlNS::fo), d(0)
{
clear();
}
KoStyleStack::KoStyleStack(const char* styleNSURI, const char* foNSURI)
: m_styleNSURI(styleNSURI), m_foNSURI(foNSURI), d(0)
{
m_propertiesTagNames.append("properties");
clear();
}
KoStyleStack::~KoStyleStack()
{
delete d;
}
void KoStyleStack::clear()
{
m_stack.clear();
#ifdef DEBUG_STYLESTACK
debugOdf << "clear!";
#endif
}
void KoStyleStack::save()
{
m_marks.push(m_stack.count());
#ifdef DEBUG_STYLESTACK
debugOdf << "save (level" << m_marks.count() << ") -> index" << m_stack.count();
#endif
}
void KoStyleStack::restore()
{
Q_ASSERT(!m_marks.isEmpty());
int toIndex = m_marks.pop();
#ifdef DEBUG_STYLESTACK
debugOdf << "restore (level" << m_marks.count() + 1 << ") -> to index" << toIndex;
#endif
Q_ASSERT(toIndex > -1);
Q_ASSERT(toIndex <= (int)m_stack.count()); // If equal, nothing to remove. If greater, bug.
for (int index = (int)m_stack.count() - 1; index >= toIndex; --index)
m_stack.pop_back();
}
void KoStyleStack::pop()
{
Q_ASSERT(!m_stack.isEmpty());
m_stack.pop_back();
#ifdef DEBUG_STYLESTACK
debugOdf << "pop -> count=" << m_stack.count();
#endif
}
void KoStyleStack::push(const KoXmlElement& style)
{
m_stack.append(style);
#ifdef DEBUG_STYLESTACK
debugOdf << "pushed" << style.attributeNS(m_styleNSURI, "name", QString()) << " -> count=" << m_stack.count();
#endif
}
QString KoStyleStack::property(const QString &nsURI, const QString &name) const
{
return property(nsURI, name, 0);
}
QString KoStyleStack::property(const QString &nsURI, const QString &name, const QString &detail) const
{
return property(nsURI, name, &detail);
}
inline QString KoStyleStack::property(const QString &nsURI, const QString &name, const QString *detail) const
{
QString fullName(name);
if (detail) {
fullName += '-' + *detail;
}
QList<KoXmlElement>::ConstIterator it = m_stack.end();
while (it != m_stack.begin()) {
--it;
foreach (const QString &propertyTagName, m_propertiesTagNames) {
KoXmlElement properties = KoXml::namedItemNS(*it, m_styleNSURI, propertyTagName);
if (detail) {
QString attribute(properties.attributeNS(nsURI, fullName));
if (!attribute.isEmpty()) {
return attribute;
}
}
QString attribute(properties.attributeNS(nsURI, name));
if (!attribute.isEmpty()) {
return attribute;
}
}
}
return QString();
}
bool KoStyleStack::hasProperty(const QString &nsURI, const QString &name) const
{
return hasProperty(nsURI, name, 0);
}
bool KoStyleStack::hasProperty(const QString &nsURI, const QString &name, const QString &detail) const
{
return hasProperty(nsURI, name, &detail);
}
inline bool KoStyleStack::hasProperty(const QString &nsURI, const QString &name, const QString *detail) const
{
QString fullName(name);
if (detail) {
fullName += '-' + *detail;
}
QList<KoXmlElement>::ConstIterator it = m_stack.end();
while (it != m_stack.begin()) {
--it;
foreach (const QString &propertiesTagName, m_propertiesTagNames) {
const KoXmlElement properties = KoXml::namedItemNS(*it, m_styleNSURI, propertiesTagName);
if (properties.hasAttributeNS(nsURI, name) ||
(detail && properties.hasAttributeNS(nsURI, fullName)))
return true;
}
}
return false;
}
// Font size is a bit special. "115%" applies to "the fontsize of the parent style".
// This can be generalized though (hasPropertyThatCanBePercentOfParent() ? :)
QPair<qreal,qreal> KoStyleStack::fontSize(const qreal defaultFontPointSize) const
{
const QString name = "font-size";
qreal percent = 100;
QList<KoXmlElement>::ConstIterator it = m_stack.end(); // reverse iterator
while (it != m_stack.begin()) {
--it;
foreach (const QString &propertiesTagName, m_propertiesTagNames) {
KoXmlElement properties = KoXml::namedItemNS(*it, m_styleNSURI, propertiesTagName).toElement();
if (properties.hasAttributeNS(m_foNSURI, name)) {
const QString value = properties.attributeNS(m_foNSURI, name, QString());
if (value.endsWith('%')) {
//sebsauer, 20070609, the specs don't say that we have to calc them together but
//just that we are looking for a valid parent fontsize. So, let's only take the
//first percent definition into account and keep on to seek for a valid parent,
//percent *= value.left( value.length() - 1 ).toDouble() / 100.0;
if (percent == 100)
percent = value.leftRef(value.length() - 1).toDouble();
} else {
// e.g. 12pt and indicate that there was not percentage there
return QPair<qreal,qreal> ((percent * KoUnit::parseValue(value))/100.0, 0.0);
}
break;
}
}
}
//if there was no valid parent, we return the default fontsize together with an optional calculated percent-value.
return QPair<qreal,qreal> ((percent * defaultFontPointSize)/100.0, percent);
}
bool KoStyleStack::hasChildNode(const QString &nsURI, const QString &localName) const
{
QList<KoXmlElement>::ConstIterator it = m_stack.end();
while (it != m_stack.begin()) {
--it;
foreach (const QString &propertiesTagName, m_propertiesTagNames) {
KoXmlElement properties = KoXml::namedItemNS(*it, m_styleNSURI, propertiesTagName);
if (!KoXml::namedItemNS(properties, nsURI, localName).isNull())
return true;
}
}
return false;
}
KoXmlElement KoStyleStack::childNode(const QString &nsURI, const QString &localName) const
{
QList<KoXmlElement>::ConstIterator it = m_stack.end();
while (it != m_stack.begin()) {
--it;
foreach (const QString &propertiesTagName, m_propertiesTagNames) {
KoXmlElement properties = KoXml::namedItemNS(*it, m_styleNSURI, propertiesTagName);
KoXmlElement e = KoXml::namedItemNS(properties, nsURI, localName);
if (!e.isNull())
return e;
}
}
return KoXmlElement(); // a null element
}
bool KoStyleStack::isUserStyle(const KoXmlElement& e, const QString& family) const
{
if (e.attributeNS(m_styleNSURI, "family", QString()) != family)
return false;
const KoXmlElement parent = e.parentNode().toElement();
//debugOdf <<"tagName=" << e.tagName() <<" parent-tagName=" << parent.tagName();
return parent.localName() == "styles" /*&& parent.namespaceURI() == KoXmlNS::office*/;
}
QString KoStyleStack::userStyleName(const QString& family) const
{
QList<KoXmlElement>::ConstIterator it = m_stack.end();
while (it != m_stack.begin()) {
--it;
//debugOdf << (*it).attributeNS( m_styleNSURI,"name", QString());
if (isUserStyle(*it, family))
return (*it).attributeNS(m_styleNSURI, "name", QString());
}
// Can this ever happen?
return "Standard";
}
QString KoStyleStack::userStyleDisplayName(const QString& family) const
{
QList<KoXmlElement>::ConstIterator it = m_stack.end();
while (it != m_stack.begin()) {
--it;
//debugOdf << (*it).attributeNS( m_styleNSURI,"display-name");
if (isUserStyle(*it, family))
return (*it).attributeNS(m_styleNSURI, "display-name", QString());
}
return QString(); // no display name, this can happen since it's optional
}
void KoStyleStack::setTypeProperties(const char* typeProperties)
{
m_propertiesTagNames.clear();
m_propertiesTagNames.append(typeProperties == 0 || qstrlen(typeProperties) == 0 ? QString("properties") : (QString(typeProperties) + "-properties"));
}
void KoStyleStack::setTypeProperties(const QList<QString> &typeProperties)
{
m_propertiesTagNames.clear();
foreach (const QString &typeProperty, typeProperties) {
if (!typeProperty.isEmpty()) {
m_propertiesTagNames.append(typeProperty + "-properties");
}
}
if (m_propertiesTagNames.empty()) {
m_propertiesTagNames.append("properties");
}
}
diff --git a/src/libs/odf/KoUnit.cpp b/src/libs/odf/KoUnit.cpp
index b6278bb6..56c768a4 100644
--- a/src/libs/odf/KoUnit.cpp
+++ b/src/libs/odf/KoUnit.cpp
@@ -1,397 +1,398 @@
/* This file is part of the KDE project
Copyright (C) 2001 David Faure <faure@kde.org>
Copyright (C) 2004, Nicolas GOUTTE <goutte@kde.org>
Copyright 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
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 "KoUnit.h"
#include <cmath>
#include <QTransform>
#include <klocalizedstring.h>
#include <OdfDebug.h>
#include <QtGlobal>
// ensure the same order as in KoUnit::Unit
static const char* const unitNameList[KoUnit::TypeCount] =
{
"mm",
"pt",
"in",
"cm",
"dm",
"pi",
"cc",
"px"
};
QString KoUnit::unitDescription(KoUnit::Type type)
{
switch (type) {
case KoUnit::Millimeter:
return i18n("Millimeters (mm)");
case KoUnit::Centimeter:
return i18n("Centimeters (cm)");
case KoUnit::Decimeter:
return i18n("Decimeters (dm)");
case KoUnit::Inch:
return i18n("Inches (in)");
case KoUnit::Pica:
return i18n("Pica (pi)");
case KoUnit::Cicero:
return i18n("Cicero (cc)");
case KoUnit::Point:
return i18n("Points (pt)");
case KoUnit::Pixel:
return i18n("Pixels (px)");
default:
return i18n("Unsupported unit");
}
}
// grouped by units which are similar
static const KoUnit::Type typesInUi[KoUnit::TypeCount] =
{
KoUnit::Millimeter,
KoUnit::Centimeter,
KoUnit::Decimeter,
KoUnit::Inch,
KoUnit::Pica,
KoUnit::Cicero,
KoUnit::Point,
KoUnit::Pixel,
};
QStringList KoUnit::listOfUnitNameForUi(ListOptions listOptions)
{
QStringList lst;
for (int i = 0; i < KoUnit::TypeCount; ++i) {
const Type type = typesInUi[i];
if ((type != Pixel) || ((listOptions & HideMask) == ListAll))
lst.append(unitDescription(type));
}
return lst;
}
KoUnit KoUnit::fromListForUi(int index, ListOptions listOptions, qreal factor)
{
KoUnit::Type type = KoUnit::Point;
if ((0 <= index) && (index < KoUnit::TypeCount)) {
// iterate through all enums and skip the Pixel enum if needed
for (int i = 0; i < KoUnit::TypeCount; ++i) {
if ((listOptions&HidePixel) && (typesInUi[i] == Pixel)) {
++index;
continue;
}
if (i == index) {
type = typesInUi[i];
break;
}
}
}
return KoUnit(type, factor);
}
int KoUnit::indexInListForUi(ListOptions listOptions) const
{
if ((listOptions&HidePixel) && (m_type == Pixel)) {
return -1;
}
int result = -1;
int skipped = 0;
for (int i = 0; i < KoUnit::TypeCount; ++i) {
if ((listOptions&HidePixel) && (typesInUi[i] == Pixel)) {
++skipped;
continue;
}
if (typesInUi[i] == m_type) {
result = i - skipped;
break;
}
}
return result;
}
qreal KoUnit::toUserValue(qreal ptValue) const
{
switch (m_type) {
case Millimeter:
return toMillimeter(ptValue);
case Centimeter:
return toCentimeter(ptValue);
case Decimeter:
return toDecimeter(ptValue);
case Inch:
return toInch(ptValue);
case Pica:
return toPica(ptValue);
case Cicero:
return toCicero(ptValue);
case Pixel:
return ptValue * m_pixelConversion;
case Point:
default:
return toPoint(ptValue);
}
}
qreal KoUnit::ptToUnit(const qreal ptValue, const KoUnit &unit)
{
switch (unit.m_type) {
case Millimeter:
return POINT_TO_MM(ptValue);
case Centimeter:
return POINT_TO_CM(ptValue);
case Decimeter:
return POINT_TO_DM(ptValue);
case Inch:
return POINT_TO_INCH(ptValue);
case Pica:
return POINT_TO_PI(ptValue);
case Cicero:
return POINT_TO_CC(ptValue);
case Pixel:
return ptValue * unit.m_pixelConversion;
case Point:
default:
return ptValue;
}
}
QString KoUnit::toUserStringValue(qreal ptValue) const
{
return QLocale().toString(toUserValue(ptValue));
}
qreal KoUnit::fromUserValue(qreal value) const
{
switch (m_type) {
case Millimeter:
return MM_TO_POINT(value);
case Centimeter:
return CM_TO_POINT(value);
case Decimeter:
return DM_TO_POINT(value);
case Inch:
return INCH_TO_POINT(value);
case Pica:
return PI_TO_POINT(value);
case Cicero:
return CC_TO_POINT(value);
case Pixel:
return value / m_pixelConversion;
case Point:
default:
return value;
}
}
qreal KoUnit::fromUserValue(const QString &value, bool *ok) const
{
return fromUserValue(QLocale().toDouble(value, ok));
}
qreal KoUnit::parseValue(const QString& _value, qreal defaultVal)
{
if (_value.isEmpty())
return defaultVal;
QString value(_value.simplified());
value.remove(QLatin1Char(' '));
int firstLetter = -1;
for (int i = 0; i < value.length(); ++i) {
if (value.at(i).isLetter()) {
if (value.at(i) == QLatin1Char('e'))
continue;
firstLetter = i;
break;
}
}
if (firstLetter == -1)
return value.toDouble();
const QString symbol = value.mid(firstLetter);
value.truncate(firstLetter);
const qreal val = value.toDouble();
if (symbol == QLatin1String("pt"))
return val;
bool ok;
KoUnit u = KoUnit::fromSymbol(symbol, &ok);
if (ok)
return u.fromUserValue(val);
if (symbol == QLatin1String("m"))
return DM_TO_POINT(val * 10.0);
else if (symbol == QLatin1String("km"))
return DM_TO_POINT(val * 10000.0);
warnOdf << "KoUnit::parseValue: Unit " << symbol << " is not supported, please report.";
// TODO : add support for mi/ft ?
return defaultVal;
}
KoUnit KoUnit::fromSymbol(const QString &symbol, bool *ok)
{
Type result = Point;
if (symbol == QLatin1String("inch") /*compat*/) {
result = Inch;
if (ok)
*ok = true;
} else {
if (ok)
*ok = false;
for (int i = 0; i < TypeCount; ++i) {
if (symbol == QLatin1String(unitNameList[i])) {
result = static_cast<Type>(i);
if (ok)
*ok = true;
}
}
}
return KoUnit(result);
}
qreal KoUnit::convertFromUnitToUnit(const qreal value, const KoUnit &fromUnit, const KoUnit &toUnit, qreal factor)
{
qreal pt;
switch (fromUnit.type()) {
case Millimeter:
pt = MM_TO_POINT(value);
break;
case Centimeter:
pt = CM_TO_POINT(value);
break;
case Decimeter:
pt = DM_TO_POINT(value);
break;
case Inch:
pt = INCH_TO_POINT(value);
break;
case Pica:
pt = PI_TO_POINT(value);
break;
case Cicero:
pt = CC_TO_POINT(value);
break;
case Pixel:
pt = value / factor;
break;
case Point:
default:
pt = value;
}
switch (toUnit.type()) {
case Millimeter:
return POINT_TO_MM(pt);
case Centimeter:
return POINT_TO_CM(pt);
case Decimeter:
return POINT_TO_DM(pt);
case Inch:
return POINT_TO_INCH(pt);
case Pica:
return POINT_TO_PI(pt);
case Cicero:
return POINT_TO_CC(pt);
case Pixel:
return pt * factor;
case Point:
default:
return pt;
}
}
QString KoUnit::symbol() const
{
return QLatin1String(unitNameList[m_type]);
}
qreal KoUnit::parseAngle(const QString& _value, qreal defaultVal)
{
if (_value.isEmpty())
return defaultVal;
QString value(_value.simplified());
value.remove(QLatin1Char(' '));
int firstLetter = -1;
for (int i = 0; i < value.length(); ++i) {
if (value.at(i).isLetter()) {
if (value.at(i) == QLatin1Char('e'))
continue;
firstLetter = i;
break;
}
}
if (firstLetter == -1)
return value.toDouble();
const QString type = value.mid(firstLetter);
value.truncate(firstLetter);
const qreal val = value.toDouble();
if (type == QLatin1String("deg"))
return val;
else if (type == QLatin1String("rad"))
return val * 180 / M_PI;
else if (type == QLatin1String("grad"))
return val * 0.9;
return defaultVal;
}
qreal KoUnit::approxTransformScale(const QTransform &t)
{
return std::sqrt(qAbs(t.determinant()));
}
void KoUnit::adjustByPixelTransform(const QTransform &t)
{
m_pixelConversion *= approxTransformScale(t);
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const KoUnit &unit)
{
#ifndef NDEBUG
debug.nospace() << unit.symbol();
#else
Q_UNUSED(unit);
#endif
return debug.space();
}
#endif
diff --git a/src/libs/odf/OdfDebug.cpp b/src/libs/odf/OdfDebug.cpp
index d8761a85..f5ed53ab 100644
--- a/src/libs/odf/OdfDebug.cpp
+++ b/src/libs/odf/OdfDebug.cpp
@@ -1,27 +1,28 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "OdfDebug.h"
const QLoggingCategory &ODF_LOG() \
{
static const QLoggingCategory category("calligra.plan.lib.odf");
return category;
}
diff --git a/src/libs/odf/tests/TestKoElementReference.cpp b/src/libs/odf/tests/TestKoElementReference.cpp
index 211ecf3d..48e73b86 100644
--- a/src/libs/odf/tests/TestKoElementReference.cpp
+++ b/src/libs/odf/tests/TestKoElementReference.cpp
@@ -1,45 +1,46 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "TestKoElementReference.h"
#include <KoElementReference.h>
#include <QTest>
void TestKoElementReference::testElementReference()
{
KoElementReference ref1;
KoElementReference ref2;
QVERIFY(ref1 != ref2);
KoElementReference ref3(ref1);
QVERIFY(ref1 == ref3);
{
KoElementReference ref4;
ref3 = ref4;
QVERIFY(ref3 == ref4);
}
QVERIFY(ref3 != ref1);
}
QTEST_MAIN(TestKoElementReference)
diff --git a/src/libs/odf/tests/TestKoGenStyles.cpp b/src/libs/odf/tests/TestKoGenStyles.cpp
index 4f991703..392941a7 100644
--- a/src/libs/odf/tests/TestKoGenStyles.cpp
+++ b/src/libs/odf/tests/TestKoGenStyles.cpp
@@ -1,378 +1,379 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
Copyright (C) 2009 Thomas Zander <zander@kde.org>
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 "TestKoGenStyles.h"
#include <KoGenStyles.h>
#include <KoXmlWriter.h>
#include <OdfDebug.h>
#include <QBuffer>
#include <QRegExp>
#include <QTest>
#define TEST_BEGIN(publicId,systemId) \
{ \
QByteArray cstr; \
QBuffer buffer( &cstr ); \
buffer.open( QIODevice::WriteOnly ); \
{ \
KoXmlWriter writer( &buffer ); \
writer.startDocument( "r", publicId, systemId ); \
writer.startElement( "r" )
#define TEST_END_QTTEST(expected) \
writer.endElement(); \
writer.endDocument(); \
} \
buffer.putChar( '\0' ); /*null-terminate*/ \
QString expectedFull = QString::fromLatin1( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); \
expectedFull += expected; \
QString s1 = QString::fromLatin1( cstr ); \
QCOMPARE( expectedFull, s1 ); \
}
void TestKoGenStyles::testLookup()
{
debugOdf ;
KoGenStyles coll;
QMap<QString, QString> map1;
map1.insert("map1key", "map1value");
QMap<QString, QString> map2;
map2.insert("map2key1", "map2value1");
map2.insert("map2key2", "map2value2");
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter childWriter(&buffer);
childWriter.startElement("child");
childWriter.addAttribute("test:foo", "bar");
childWriter.endElement();
QString childContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
QBuffer buffer2;
buffer2.open(QIODevice::WriteOnly);
KoXmlWriter childWriter2(&buffer2);
childWriter2.startElement("child2");
childWriter2.addAttribute("test:foo", "bar");
childWriter2.endElement();
QString childContents2 = QString::fromUtf8(buffer2.buffer(), buffer2.buffer().size());
KoGenStyle first(KoGenStyle::ParagraphAutoStyle, "paragraph");
first.addAttribute("style:master-page-name", "Standard");
first.addProperty("style:page-number", "0");
first.addProperty("style:foobar", "2", KoGenStyle::TextType);
first.addStyleMap(map1);
first.addStyleMap(map2);
first.addChildElement("test", childContents);
first.addChildElement("test", childContents2, KoGenStyle::TextType);
QString firstName = coll.insert(first);
debugOdf << "The first style got assigned the name" << firstName;
QVERIFY(!firstName.isEmpty());
QCOMPARE(first.type(), KoGenStyle::ParagraphAutoStyle);
KoGenStyle second(KoGenStyle::ParagraphAutoStyle, "paragraph");
second.addAttribute("style:master-page-name", "Standard");
second.addProperty("style:page-number", "0");
second.addProperty("style:foobar", "2", KoGenStyle::TextType);
second.addStyleMap(map1);
second.addStyleMap(map2);
second.addChildElement("test", childContents);
second.addChildElement("test", childContents2, KoGenStyle::TextType);
QString secondName = coll.insert(second);
debugOdf << "The second style got assigned the name" << secondName;
QCOMPARE(firstName, secondName); // check that sharing works
QCOMPARE(first, second); // check that operator== works :)
const KoGenStyle* s = coll.style(firstName, "paragraph"); // check insert of existing style
QVERIFY(s != 0);
QCOMPARE(*s, first);
s = coll.style("foobarblah", "paragraph"); // check insert of non-existing style
QVERIFY(s == 0);
KoGenStyle third(KoGenStyle::ParagraphAutoStyle, "paragraph", secondName); // inherited style
third.addProperty("style:margin-left", "1.249cm");
third.addProperty("style:page-number", "0"); // same as parent
third.addProperty("style:foobar", "3", KoGenStyle::TextType); // different from parent
QCOMPARE(third.parentName(), secondName);
QString thirdName = coll.insert(third, "P");
debugOdf << "The third style got assigned the name" << thirdName;
QVERIFY(thirdName != firstName);
QVERIFY(!thirdName.isEmpty());
KoGenStyle user(KoGenStyle::ParagraphStyle, "paragraph"); // differs from third since it doesn't inherit second, and has a different type
user.addProperty("style:margin-left", "1.249cm");
QString userStyleName = coll.insert(user, "User", KoGenStyles::DontAddNumberToName);
debugOdf << "The user style got assigned the name" << userStyleName;
QCOMPARE(userStyleName, QString("User"));
KoGenStyle sameAsParent(KoGenStyle::ParagraphAutoStyle, "paragraph", secondName); // inherited style
sameAsParent.addAttribute("style:master-page-name", "Standard");
sameAsParent.addProperty("style:page-number", "0");
sameAsParent.addProperty("style:foobar", "2", KoGenStyle::TextType);
sameAsParent.addStyleMap(map1);
sameAsParent.addStyleMap(map2);
sameAsParent.addChildElement("test", childContents);
sameAsParent.addChildElement("test", childContents2, KoGenStyle::TextType);
QString sapName = coll.insert(sameAsParent, "foobar");
debugOdf << "The 'same as parent' style got assigned the name" << sapName;
QCOMPARE(sapName, secondName);
QCOMPARE(coll.styles().count(), 3);
// OK, now add a style marked as for styles.xml; it looks like the above style, but
// since it's marked for styles.xml it shouldn't be shared with it.
KoGenStyle headerStyle(KoGenStyle::ParagraphAutoStyle, "paragraph");
headerStyle.addAttribute("style:master-page-name", "Standard");
headerStyle.addProperty("style:page-number", "0");
headerStyle.addProperty("style:foobar", "2", KoGenStyle::TextType);
headerStyle.addStyleMap(map1);
headerStyle.addStyleMap(map2);
headerStyle.setAutoStyleInStylesDotXml(true);
coll.insert(headerStyle, "foobar");
QCOMPARE(coll.styles().count(), 4);
//QCOMPARE(coll.styles(KoGenStyle::ParagraphAutoStyle).count(), 2);
//QCOMPARE(coll.styles(KoGenStyle::ParagraphStyle).count(), 1);
// XML for first/second style
TEST_BEGIN(0, 0);
first.writeStyle(&writer, coll, "style:style", firstName, "style:paragraph-properties");
TEST_END_QTTEST("<r>\n <style:style style:name=\"" + firstName + "\" style:family=\"paragraph\" "
"style:master-page-name=\"Standard\">\n <style:paragraph-properties style:page-number=\"0\">\n"
" <child test:foo=\"bar\"/>\n </style:paragraph-properties>\n <style:text-properties style:foobar=\"2\">\n"
" <child2 test:foo=\"bar\"/>\n </style:text-properties>\n"
" <style:map map1key=\"map1value\"/>\n <style:map map2key1=\"map2value1\" map2key2=\"map2value2\"/>\n"
" </style:style>\n</r>\n");
// XML for third style
TEST_BEGIN(0, 0);
third.writeStyle(&writer, coll, "style:style", thirdName, "style:paragraph-properties");
TEST_END_QTTEST("<r>\n <style:style style:name=\"" + thirdName + "\""
" style:parent-style-name=\"" + firstName + "\" style:family=\"paragraph\">\n"
" <style:paragraph-properties style:margin-left=\"1.249cm\"/>\n"
" <style:text-properties style:foobar=\"3\"/>\n </style:style>\n</r>\n");
}
void TestKoGenStyles::testLookupFlags()
{
KoGenStyles coll;
KoGenStyle first(KoGenStyle::ParagraphAutoStyle, "paragraph");
first.addAttribute("style:master-page-name", "Standard");
first.addProperty("style:page-number", "0");
QString styleName = coll.insert(first, "P", KoGenStyles::DontAddNumberToName);
QCOMPARE(styleName, QString("P"));
styleName = coll.insert(first, "P");
QCOMPARE(styleName, QString("P"));
KoGenStyle second(KoGenStyle::ParagraphAutoStyle, "paragraph");
second.addProperty("fo:text-align", "left");
styleName = coll.insert(second, "P");
QCOMPARE(styleName, QString("P1"));
styleName = coll.insert(second, "P", KoGenStyles::AllowDuplicates);
QCOMPARE(styleName, QString("P2"));
styleName = coll.insert(second, "P", KoGenStyles::AllowDuplicates);
QCOMPARE(styleName, QString("P3"));
styleName = coll.insert(second, "P", KoGenStyles::AllowDuplicates | KoGenStyles::DontAddNumberToName);
QCOMPARE(styleName, QString("P4"));
}
void TestKoGenStyles::testWriteStyle()
{
debugOdf;
KoGenStyles coll;
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
KoXmlWriter styleChildWriter(&buffer);
styleChildWriter.startElement("styleChild");
styleChildWriter.addAttribute("foo", "bar");
styleChildWriter.endElement();
QString styleChildContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
KoGenStyle style(KoGenStyle::ParagraphStyle, "paragraph");
style.addProperty("style:foo", "bar");
style.addProperty("style:paragraph", "property", KoGenStyle::ParagraphType);
style.addProperty("style:graphic", "property", KoGenStyle::GraphicType);
style.addProperty("styleChild", styleChildContents, KoGenStyle::StyleChildElement);
QString styleName = coll.insert(style, "P");
// XML for style
TEST_BEGIN(0, 0);
style.writeStyle(&writer, coll, "style:style", styleName, "style:paragraph-properties");
TEST_END_QTTEST("<r>\n <style:style style:name=\"P1\" style:family=\"paragraph\">\n <style:paragraph-properties style:foo=\"bar\" style:paragraph=\"property\"/>\n <style:graphic-properties style:graphic=\"property\"/>\n <styleChild foo=\"bar\"/>\n </style:style>\n</r>\n");
KoGenStyle pageLayoutStyle(KoGenStyle::PageLayoutStyle);
pageLayoutStyle.addProperty("style:print-orientation", "portrait");
QString pageLayoutStyleName = coll.insert(pageLayoutStyle, "pm");
// XML for page layout style
TEST_BEGIN(0, 0);
pageLayoutStyle.writeStyle(&writer, coll, "style:page-layout", pageLayoutStyleName, "style:page-layout-properties");
TEST_END_QTTEST("<r>\n <style:page-layout style:name=\"pm1\">\n <style:page-layout-properties style:print-orientation=\"portrait\"/>\n </style:page-layout>\n</r>\n");
KoGenStyle listStyle(KoGenStyle::ListStyle);
QString listStyleName = coll.insert(listStyle, "L");
// XML for list layout style
TEST_BEGIN(0, 0);
listStyle.writeStyle(&writer, coll, "text:list-style", listStyleName, 0);
TEST_END_QTTEST("<r>\n <text:list-style style:name=\"L1\"/>\n</r>\n");
}
void TestKoGenStyles::testDefaultStyle()
{
debugOdf ;
/* Create a default style,
* and then an auto style with exactly the same attributes
* -> the insert gives the default style.
*
* Also checks how the default style gets written out to XML.
*/
KoGenStyles coll;
KoGenStyle defaultStyle(KoGenStyle::ParagraphStyle, "paragraph");
defaultStyle.addAttribute("style:master-page-name", "Standard");
defaultStyle.addProperty("myfont", "isBold");
defaultStyle.setDefaultStyle(true);
QString defaultStyleName = coll.insert(defaultStyle);
// default styles don't get a name
QVERIFY(defaultStyleName.isEmpty());
QCOMPARE(defaultStyle.type(), KoGenStyle::ParagraphStyle);
QVERIFY(defaultStyle.isDefaultStyle());
KoGenStyle anotherStyle(KoGenStyle::ParagraphStyle, "paragraph");
anotherStyle.addAttribute("style:master-page-name", "Standard");
anotherStyle.addProperty("myfont", "isBold");
QString anotherStyleName = coll.insert(anotherStyle);
QVERIFY(anotherStyleName != defaultStyleName);
QCOMPARE(coll.styles().count(), 1);
// XML for default style
TEST_BEGIN(0, 0);
defaultStyle.writeStyle(&writer, coll, "style:default-style", defaultStyleName, "style:paragraph-properties");
TEST_END_QTTEST("<r>\n <style:default-style style:family=\"paragraph\" style:master-page-name=\"Standard\">\n <style:paragraph-properties myfont=\"isBold\"/>\n </style:default-style>\n</r>\n");
// The Calligra Sheets case: not writing out all properties, only if they differ
// from the default style.
// KoGenStyles doesn't fetch info from the parent style when testing
// for equality, so Calligra Sheets uses isEmpty() to check for equality-to-parent.
KoGenStyle dataStyle(KoGenStyle::ParagraphStyle, "paragraph", defaultStyleName);
QVERIFY(dataStyle.isEmpty());
// and then it doesn't look up the auto style, but rather uses the parent style directly.
}
void TestKoGenStyles:: testUserStyles()
{
debugOdf ;
/* Two user styles with exactly the same attributes+properties will not get merged, since
* they don't have exactly the same attributes after all: the display-name obviously differs :)
*/
KoGenStyles coll;
KoGenStyle user1(KoGenStyle::ParagraphStyle, "paragraph");
user1.addAttribute("style:display-name", "User 1");
user1.addProperty("myfont", "isBold");
QString user1StyleName = coll.insert(user1, "User1", KoGenStyles::DontAddNumberToName);
debugOdf << "The user style got assigned the name" << user1StyleName;
QCOMPARE(user1StyleName, QString("User1"));
KoGenStyle user2(KoGenStyle::ParagraphStyle, "paragraph");
user2.addAttribute("style:display-name", "User 2");
user2.addProperty("myfont", "isBold");
QString user2StyleName = coll.insert(user2, "User2", KoGenStyles::DontAddNumberToName);
debugOdf << "The user style got assigned the name" << user2StyleName;
QCOMPARE(user2StyleName, QString("User2"));
// And now, what if the data uses that style?
// This is like sameAsParent in the other test, but this time the
// parent is a STYLE_USER...
KoGenStyle dataStyle(KoGenStyle::ParagraphAutoStyle, "paragraph", user2StyleName);
dataStyle.addProperty("myfont", "isBold");
QString dataStyleName = coll.insert(dataStyle, "DataStyle");
debugOdf << "The auto style got assigned the name" << dataStyleName;
QCOMPARE(dataStyleName, QString("User2")); // it found the parent as equal
// Let's do the opposite test, just to make sure
KoGenStyle dataStyle2(KoGenStyle::ParagraphAutoStyle, "paragraph", user2StyleName);
dataStyle2.addProperty("myfont", "isNotBold");
QString dataStyle2Name = coll.insert(dataStyle2, "DataStyle");
debugOdf << "The different auto style got assigned the name" << dataStyle2Name;
QCOMPARE(dataStyle2Name, QString("DataStyle1"));
QCOMPARE(coll.styles().count(), 3);
// XML for user style 1
TEST_BEGIN(0, 0);
user1.writeStyle(&writer, coll, "style:style", user1StyleName, "style:paragraph-properties");
TEST_END_QTTEST("<r>\n <style:style style:name=\"User1\" style:display-name=\"User 1\" style:family=\"paragraph\">\n <style:paragraph-properties myfont=\"isBold\"/>\n </style:style>\n</r>\n");
// XML for user style 2
TEST_BEGIN(0, 0);
user2.writeStyle(&writer, coll, "style:style", user2StyleName, "style:paragraph-properties");
TEST_END_QTTEST("<r>\n <style:style style:name=\"User2\" style:display-name=\"User 2\" style:family=\"paragraph\">\n <style:paragraph-properties myfont=\"isBold\"/>\n </style:style>\n</r>\n");
}
void TestKoGenStyles::testStylesDotXml()
{
debugOdf ;
KoGenStyles coll;
// Check that an autostyle-in-style.xml and an autostyle-in-content.xml
// don't get the same name. It confuses KoGenStyle's named-based maps.
KoGenStyle headerStyle(KoGenStyle::ParagraphAutoStyle, "paragraph");
headerStyle.addAttribute("style:master-page-name", "Standard");
headerStyle.addProperty("style:page-number", "0");
headerStyle.setAutoStyleInStylesDotXml(true);
QString headerStyleName = coll.insert(headerStyle, "P");
QCOMPARE(headerStyleName, QString("P1"));
//debugOdf << coll;
KoGenStyle first(KoGenStyle::ParagraphAutoStyle, "paragraph");
first.addAttribute("style:master-page-name", "Standard");
QString firstName = coll.insert(first, "P");
debugOdf << "The auto style got assigned the name" << firstName;
QCOMPARE(firstName, QString("P2")); // anything but not P1.
}
QTEST_MAIN(TestKoGenStyles)
diff --git a/src/libs/odf/tests/TestKoOdfLoadingContext.cpp b/src/libs/odf/tests/TestKoOdfLoadingContext.cpp
index 40a4c6a6..086c09d1 100644
--- a/src/libs/odf/tests/TestKoOdfLoadingContext.cpp
+++ b/src/libs/odf/tests/TestKoOdfLoadingContext.cpp
@@ -1,146 +1,147 @@
/* This file is part of the KDE project
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "TestKoOdfLoadingContext.h"
#include <QByteArray>
#include <QBuffer>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <KoOdfReadStore.h>
#include <KoOdfWriteStore.h>
#include <KoXmlWriter.h>
#include <QTest>
void TestKoOdfLoadingContext::testFillStyleStack()
{
#if 0
QByteArray byteArray;
QBuffer buffer(&byteArray);
#endif
const char * mimeType = "application/vnd.oasis.opendocument.text";
KoStore * store(KoStore::createStore("test.odt", KoStore::Write, mimeType));
KoOdfWriteStore odfStore(store);
KoXmlWriter* manifestWriter = odfStore.manifestWriter(mimeType);
KoXmlWriter* contentWriter = odfStore.contentWriter();
QVERIFY(contentWriter != 0);
KoXmlWriter * bodyWriter = odfStore.bodyWriter();
bodyWriter->startElement("office:body");
bodyWriter->startElement("office:text");
bodyWriter->startElement("draw:rect");
bodyWriter->addAttribute("draw:style-name", "gr1");
bodyWriter->endElement();
bodyWriter->endElement();
bodyWriter->endElement();
contentWriter->startElement("office:automatic-styles");
contentWriter->startElement("style:style");
contentWriter->addAttribute("style:name", "gr1");
contentWriter->addAttribute("style:family", "graphic");
contentWriter->addAttribute("style:parent-style-name", "standard");
contentWriter->startElement("style:graphic-properties");
contentWriter->addAttribute("draw:fill", "solid");
contentWriter->endElement();
contentWriter->endElement();
contentWriter->endElement(); // office:automatic-styles
odfStore.closeContentWriter();
//add manifest line for content.xml
manifestWriter->addManifestEntry("content.xml", "text/xml");
QVERIFY(store->open("styles.xml") == true);
KoStoreDevice stylesDev(store);
KoXmlWriter* stylesWriter = KoOdfWriteStore::createOasisXmlWriter(&stylesDev, "office:document-styles");
stylesWriter->startElement("office:styles");
stylesWriter->startElement("style:style");
stylesWriter->addAttribute("style:name", "standard");
stylesWriter->addAttribute("style:family", "graphic");
stylesWriter->startElement("style:graphic-properties");
stylesWriter->addAttribute("draw:fill", "hatch");
stylesWriter->addAttribute("draw:stroke", "solid");
stylesWriter->endElement();
stylesWriter->endElement();
stylesWriter->endElement();
stylesWriter->startElement("office:automatic-styles");
stylesWriter->startElement("style:style");
stylesWriter->addAttribute("style:name", "gr1");
stylesWriter->addAttribute("style:family", "graphic");
stylesWriter->addAttribute("style:parent-style-name", "standard");
stylesWriter->startElement("style:graphic-properties");
stylesWriter->addAttribute("draw:fill", "none");
stylesWriter->addAttribute("draw:stroke", "none");
stylesWriter->endElement();
stylesWriter->endElement();
stylesWriter->endElement(); // office:automatic-styles
stylesWriter->endElement(); // root element (office:document-styles)
stylesWriter->endDocument();
delete stylesWriter;
manifestWriter->addManifestEntry("styles.xml", "text/xml");
QVERIFY(store->close() == true); // done with styles.xml
QVERIFY(odfStore.closeManifestWriter() == true);
delete store;
store = KoStore::createStore("test.odt", KoStore::Read, mimeType);
KoOdfReadStore readStore(store);
QString errorMessage;
QVERIFY(readStore.loadAndParse(errorMessage) == true);
KoOdfLoadingContext context(readStore.styles(), readStore.store());
KoXmlElement content = readStore.contentDoc().documentElement();
KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
QVERIFY(realBody.isNull() == false);
KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, "text");
QVERIFY(body.isNull() == false);
KoXmlElement tag;
forEachElement(tag, body) {
//tz: So now that I have a test the fails I can go on implementing the solution
QCOMPARE(tag.localName(), QString("rect"));
KoStyleStack & styleStack = context.styleStack();
styleStack.save();
context.fillStyleStack(tag, KoXmlNS::draw, "style-name", "graphic");
styleStack.setTypeProperties("graphic");
QVERIFY(styleStack.hasProperty(KoXmlNS::draw, "fill"));
QCOMPARE(styleStack.property(KoXmlNS::draw, "fill"), QString("solid"));
QVERIFY(styleStack.hasProperty(KoXmlNS::draw, "stroke"));
QCOMPARE(styleStack.property(KoXmlNS::draw, "stroke"), QString("solid"));
styleStack.restore();
}
delete store;
}
QTEST_GUILESS_MAIN(TestKoOdfLoadingContext)
diff --git a/src/libs/odf/tests/TestKoUnit.cpp b/src/libs/odf/tests/TestKoUnit.cpp
index 41a6afac..85ca153f 100644
--- a/src/libs/odf/tests/TestKoUnit.cpp
+++ b/src/libs/odf/tests/TestKoUnit.cpp
@@ -1,162 +1,163 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* 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 "TestKoUnit.h"
#include <KoUnit.h>
#include <QTest>
Q_DECLARE_METATYPE(KoUnit::Type)
Q_DECLARE_METATYPE(KoUnit::ListOptions)
void TestKoUnit::testSimpleConstructor()
{
KoUnit unit;
QCOMPARE(unit.type(), KoUnit::Point);
KoUnit otherUnit;
QCOMPARE(unit, otherUnit);
}
void TestKoUnit::testConstructor_data()
{
QTest::addColumn<KoUnit::Type>("type");
QTest::newRow("point") << KoUnit::Point;
QTest::newRow("pica") << KoUnit::Pica;
QTest::newRow("millimeter") << KoUnit::Millimeter;
}
void TestKoUnit::testConstructor()
{
QFETCH(KoUnit::Type, type);
KoUnit unit(type);
QCOMPARE(unit.type(), type);
}
void TestKoUnit::testPixelConstructor()
{
KoUnit unit(KoUnit::Pixel, 0.5);
QCOMPARE(unit.type(), KoUnit::Pixel);
QCOMPARE(KoUnit::ptToUnit(100, unit), (qreal)50);
}
void TestKoUnit::testAssignOperator_data()
{
QTest::addColumn<KoUnit::Type>("type");
QTest::addColumn<KoUnit::Type>("otherType");
QTest::newRow("point-pica") << KoUnit::Point << KoUnit::Pica;
QTest::newRow("pica-point") << KoUnit::Pica << KoUnit::Point;
QTest::newRow("millimeter-inch") << KoUnit::Millimeter << KoUnit::Inch;
}
void TestKoUnit::testAssignOperator()
{
QFETCH(KoUnit::Type, type);
QFETCH(KoUnit::Type, otherType);
KoUnit unit(type);
KoUnit otherUnit(otherType);
unit = otherUnit;
QCOMPARE(unit, otherUnit);
}
void TestKoUnit::testVariant()
{
KoUnit unit(KoUnit::Pixel, 0.5);
QVariant variant;
variant.setValue(unit);
QCOMPARE(variant.value<KoUnit>(), unit);
}
void TestKoUnit::testFromSymbol_data()
{
QTest::addColumn<KoUnit::Type>("type");
QTest::addColumn<QString>("symbol");
QTest::addColumn<bool>("isOkay");
QTest::newRow("point") << KoUnit::Point << QString::fromLatin1("pt") << true;
QTest::newRow("pica") << KoUnit::Pica << QString::fromLatin1("pi") << true;
QTest::newRow("pixel") << KoUnit::Pixel << QString::fromLatin1("px") << true;
QTest::newRow("inch") << KoUnit::Inch << QString::fromLatin1("in") << true;
QTest::newRow("inch2") << KoUnit::Inch << QString::fromLatin1("inch") << true;
QTest::newRow("decimeter") << KoUnit::Decimeter << QString::fromLatin1("dm") << true;
QTest::newRow("badSymbol") << KoUnit::Point << QString::fromLatin1("badSymbol") << false;
}
void TestKoUnit::testFromSymbol()
{
QFETCH(KoUnit::Type, type);
QFETCH(QString, symbol);
QFETCH(bool, isOkay);
bool ok;
KoUnit unit = KoUnit::fromSymbol(symbol, &ok);
QCOMPARE(ok, isOkay);
if (isOkay) {
QCOMPARE(unit.type(), type);
}
}
void TestKoUnit::testListForUi_data()
{
QTest::addColumn<KoUnit::ListOptions>("listOptions");
QTest::addColumn<int>("index");
const QVector<KoUnit::ListOptions> optionsList =
QVector<KoUnit::ListOptions>() << KoUnit::HidePixel << KoUnit::ListAll;
static const char* const optionsName[2] = {"HidePixel", "ListDefault"};
static const char* const indexName[3] = {"-start", "-middle", "-end"};
for (int o = 0; o < optionsList.count(); ++o) {
const KoUnit::ListOptions options = optionsList.at(o);
const int unitCount = KoUnit::listOfUnitNameForUi(options).count();
for (int i = 0; i < 3; ++i) {
const int index =
(i == 0) ? 0 :
(i == 1) ? unitCount/2 :
/*i == 2*/ unitCount-1;
const QString rowName = QLatin1String(optionsName[o]) + QLatin1String(indexName[i]);
QTest::newRow(rowName.toLatin1().constData()) << options << index;
}
}
}
void TestKoUnit::testListForUi()
{
QFETCH(KoUnit::ListOptions, listOptions);
QFETCH(int, index);
KoUnit unit = KoUnit::fromListForUi(index, listOptions);
QCOMPARE(unit.indexInListForUi(listOptions), index);
}
QTEST_GUILESS_MAIN(TestKoUnit)
diff --git a/src/libs/odf/tests/TestNumberStyle.cpp b/src/libs/odf/tests/TestNumberStyle.cpp
index 8b72c2a4..163e6c3e 100644
--- a/src/libs/odf/tests/TestNumberStyle.cpp
+++ b/src/libs/odf/tests/TestNumberStyle.cpp
@@ -1,170 +1,171 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Sebastian Sauer <sebsauer@kdab.com>
*
* 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 "TestNumberStyle.h"
#include <KoOdfNumberStyles.h>
#include <QTest>
QString escapeLocals(const QString &text)
{
QString t(text);
t.replace(',','.');
return t;
}
void TestNumberStyle::testEmpty()
{
KoOdfNumberStyles::NumericStyleFormat f;
QCOMPARE(f.formatStr, QString());
QCOMPARE(f.prefix, QString());
QCOMPARE(f.suffix, QString());
QCOMPARE(f.type, KoOdfNumberStyles::Text);
QCOMPARE(f.precision, -1);
QCOMPARE(f.currencySymbol, QString());
QCOMPARE(f.thousandsSep, false);
QCOMPARE(f.styleMaps.count(), 0);
}
void TestNumberStyle::testText()
{
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Text;
QCOMPARE(KoOdfNumberStyles::format("Some text", f), QString("Some text"));
}
void TestNumberStyle::testNumber()
{
QCOMPARE(KoOdfNumberStyles::formatNumber(23, "0."), QString("23"));
QCOMPARE(KoOdfNumberStyles::formatNumber(0, "0."), QString("0"));
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Number;
f.precision = 3;
f.thousandsSep = true;
f.formatStr = "00.00 test";
QCOMPARE(KoOdfNumberStyles::format("12345.6789", f), QString("12345.679 test"));
f.precision = 1;
f.formatStr = "test1 00.00 test2";
QCOMPARE(KoOdfNumberStyles::format("12345.6789", f), QString("test1 12345.70 test2"));
}
void TestNumberStyle::testDate()
{
QCOMPARE(KoOdfNumberStyles::formatDate(4567, "MM/dd/yyyy"), QString("07/02/1912"));
QCOMPARE(KoOdfNumberStyles::formatDate(0, "MM/dd/yy"), QString("12/30/99"));
}
void TestNumberStyle::testTime()
{
QCOMPARE(KoOdfNumberStyles::formatTime(0.524259259259, "hh:mm:ss"), QString("12:34:56"));
QCOMPARE(KoOdfNumberStyles::formatTime(0.524259259259, "hh:mm"), QString("12:34"));
QCOMPARE(KoOdfNumberStyles::formatTime(0, "hh:mm:ss"), QString("00:00:00"));
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Time;
f.formatStr = "hh:mm:ss";
QCOMPARE(KoOdfNumberStyles::format("0.524259259259", f), QString("12:34:56"));
QCOMPARE(KoOdfNumberStyles::format("test", f), QString("test"));
QCOMPARE(KoOdfNumberStyles::format("123", f), QString("00:00:00"));
QCOMPARE(KoOdfNumberStyles::format("1.23", f), QString("05:31:12"));
}
void TestNumberStyle::testBoolean()
{
QCOMPARE(KoOdfNumberStyles::formatBoolean("0", ""), QString("FALSE"));
QCOMPARE(KoOdfNumberStyles::formatBoolean("234", ""), QString("TRUE"));
QCOMPARE(KoOdfNumberStyles::formatBoolean("0", ""), QString("FALSE"));
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Boolean;
QCOMPARE(KoOdfNumberStyles::format("0", f), QString("FALSE"));
QCOMPARE(KoOdfNumberStyles::format("1", f), QString("TRUE"));
QCOMPARE(KoOdfNumberStyles::format("123", f), QString("TRUE"));
QCOMPARE(KoOdfNumberStyles::format("test", f), QString("FALSE"));
}
void TestNumberStyle::testPercent()
{
QCOMPARE(KoOdfNumberStyles::formatPercent("23", ""), QString("23"));
QCOMPARE(KoOdfNumberStyles::formatPercent("23.4567", "0.00%", 2), QString("2345.67%"));
QCOMPARE(KoOdfNumberStyles::formatPercent("23.456789", "0.0000%", 4), QString("2345.6789%"));
QCOMPARE(KoOdfNumberStyles::formatPercent("0", ""), QString("0"));
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Percentage;
f.precision = 2;
f.formatStr = "0%";
QCOMPARE(KoOdfNumberStyles::format("0.2", f), QString("20.00%"));
QCOMPARE(KoOdfNumberStyles::format("0.02", f), QString("2.00%"));
QCOMPARE(KoOdfNumberStyles::format("0.02228", f), QString("2.23%"));
QCOMPARE(KoOdfNumberStyles::format("test", f), QString("test"));
QCOMPARE(KoOdfNumberStyles::format("123", f), QString("123"));
QCOMPARE(KoOdfNumberStyles::format("1.23", f), QString("123.00%"));
}
void TestNumberStyle::testScientific()
{
QEXPECT_FAIL("", "min-exponent-digits not handled", Continue);
QCOMPARE(escapeLocals(KoOdfNumberStyles::formatScientific(345678, "0.00E+000")), QString("3.456780E+05"));
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Scientific;
f.precision = 3;
QEXPECT_FAIL("", "min-exponent-digits not handled", Continue);
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("0.2", f)), QString("2.000E-01"));
QEXPECT_FAIL("", "min-exponent-digits not handled", Continue);
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("1.23", f)), QString("1.230E+00"));
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("test", f)), QString("test"));
}
void TestNumberStyle::testFraction()
{
QCOMPARE(KoOdfNumberStyles::formatFraction(34.5678, " ?/?"), QString("34 4/7"));
}
void TestNumberStyle::testCurrency()
{
QCOMPARE(KoOdfNumberStyles::formatCurrency(34.56, "-$#,###0.00", QString(), 2), QString("$34.56"));
QCOMPARE(KoOdfNumberStyles::formatCurrency(34.56, "-#,###0.00 EUR", QString(), 2), QString("34.56 EUR"));
QCOMPARE(KoOdfNumberStyles::formatCurrency(34.56, "-$#,###0.", QString(), 0), QString("$35"));
QString localDependentDollar = escapeLocals(KoOdfNumberStyles::formatCurrency(34.5, "#,###0 CCC", "CCC"));
QVERIFY(localDependentDollar.startsWith("34.50") || localDependentDollar.endsWith("34.50"));
QVERIFY(localDependentDollar.startsWith("USD") || localDependentDollar.endsWith("USD"));
QCOMPARE(KoOdfNumberStyles::formatCurrency(34.56789, "-#,###0.00 EUR", QString(), 2), QString("34.57 EUR"));
QCOMPARE(KoOdfNumberStyles::formatCurrency(34.5, "-#,###0.00 EUR", QString(), 2), QString("34.50 EUR"));
KoOdfNumberStyles::NumericStyleFormat f;
f.type = KoOdfNumberStyles::Currency;
f.currencySymbol = "";
f.precision = 2;
f.formatStr = "-#,###0.00 EUR";
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("0.2", f)), QString("0.20 EUR"));
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("$ 1.23", f)), QString("$ 1.23"));
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("test", f)), QString("test"));
f.currencySymbol = "$";
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("0.2", f)), QString("0.20 EUR"));
f.formatStr = "-#,###0.00";
QCOMPARE(escapeLocals(KoOdfNumberStyles::format("0.2", f)), QString("0.20"));
f.formatStr = "";
localDependentDollar = escapeLocals(KoOdfNumberStyles::format("0.2", f));
QVERIFY(localDependentDollar.startsWith("0.20") || localDependentDollar.endsWith("0.20"));
QVERIFY(localDependentDollar.startsWith("$") || localDependentDollar.endsWith("$"));
}
QTEST_MAIN(TestNumberStyle)
diff --git a/src/libs/odf/tests/TestOdfSettings.cpp b/src/libs/odf/tests/TestOdfSettings.cpp
index 6b92593d..089eb3b2 100644
--- a/src/libs/odf/tests/TestOdfSettings.cpp
+++ b/src/libs/odf/tests/TestOdfSettings.cpp
@@ -1,99 +1,100 @@
+// clazy:excludeall=qstring-arg
#include <QTest>
#include <QObject>
#include <KoXmlReader.h>
#include <KoOasisSettings.h>
class TestOdfSettings : public QObject
{
Q_OBJECT
public:
TestOdfSettings() { }
private Q_SLOTS:
void initTestCase();
void testParseConfigItemString();
void testSelectItemSet();
void testIndexedMap();
void testNamedMap();
private:
KoXmlDocument doc;
KoOasisSettings *settings;
};
void TestOdfSettings::initTestCase()
{
const QString xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<office:document-settings xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\""
" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\">"
"<office:settings>"
"<config:config-item-set config:name=\"view-settings\">"
"<config:config-item config:name=\"unit\" config:type=\"string\">mm</config:config-item>"
"<config:config-item-map-indexed config:name=\"Views\">"
"<config:config-item-map-entry>"
"<config:config-item config:name=\"ZoomFactor\" config:type=\"short\">100</config:config-item>"
"</config:config-item-map-entry>"
"</config:config-item-map-indexed>"
"<config:config-item-map-named config:name=\"NamedMap\">"
"<config:config-item-map-entry config:name=\"foo\">"
"<config:config-item config:name=\"ZoomFactor\" config:type=\"int\">100</config:config-item>"
"</config:config-item-map-entry>"
"</config:config-item-map-named>"
"</config:config-item-set>"
"</office:settings>"
"</office:document-settings>";
bool ok = doc.setContent( xml, true /* namespace processing */ );
QVERIFY(ok);
settings = new KoOasisSettings(doc);
}
void TestOdfSettings::testSelectItemSet()
{
KoOasisSettings::Items items = settings->itemSet("notexist");
QVERIFY(items.isNull());
items = settings->itemSet("view-settings");
QVERIFY(!items.isNull());
}
void TestOdfSettings::testParseConfigItemString()
{
KoOasisSettings::Items viewSettings = settings->itemSet("view-settings");
const QString unit = viewSettings.parseConfigItemString("unit");
QCOMPARE(unit, QString("mm"));
}
void TestOdfSettings::testIndexedMap()
{
KoOasisSettings::Items viewSettings = settings->itemSet("view-settings");
QVERIFY(!viewSettings.isNull());
KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap("Views");
QVERIFY(!viewMap.isNull());
KoOasisSettings::Items firstView = viewMap.entry(0);
QVERIFY(!firstView.isNull());
const short zoomFactor = firstView.parseConfigItemShort("ZoomFactor");
QCOMPARE(zoomFactor, (short) 100);
KoOasisSettings::Items secondView = viewMap.entry(1);
QVERIFY(secondView.isNull());
}
void TestOdfSettings::testNamedMap()
{
KoOasisSettings::Items viewSettings = settings->itemSet("view-settings");
QVERIFY(!viewSettings.isNull());
KoOasisSettings::NamedMap viewMap = viewSettings.namedMap("NamedMap");
QVERIFY(!viewMap.isNull());
KoOasisSettings::Items foo = viewMap.entry("foo");
QVERIFY(!foo.isNull());
const int zoomFactor = foo.parseConfigItemShort("ZoomFactor");
QCOMPARE(zoomFactor, 100);
KoOasisSettings::Items secondView = viewMap.entry("foobar");
QVERIFY(secondView.isNull());
}
QTEST_GUILESS_MAIN(TestOdfSettings)
#include <TestOdfSettings.moc>
diff --git a/src/libs/odf/tests/TestStorage.cpp b/src/libs/odf/tests/TestStorage.cpp
index 0d33183a..581e1b41 100644
--- a/src/libs/odf/tests/TestStorage.cpp
+++ b/src/libs/odf/tests/TestStorage.cpp
@@ -1,264 +1,265 @@
/* This file is part of the KDE project
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
Copyright (C) 2008 Thomas Zander <zander@kde.org>
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 <QFile>
#include <QDir>
#include <KoStore.h>
#include <KoEncryptionChecker.h>
#include <OdfDebug.h>
#include <stdlib.h>
#include <string.h>
#include <QTest>
class TestStorage : public QObject
{
Q_OBJECT
private Q_SLOTS:
void storage_data();
void storage();
void storage2_data();
void storage2();
private:
char getch(QIODevice * dev);
};
char TestStorage::getch(QIODevice * dev)
{
char c = 0;
dev->getChar(&c);
return c;
}
void TestStorage::storage_data()
{
QTest::addColumn<int>("type");
QTest::addColumn<QString>("testFile");
QTest::newRow("tar") << (int) KoStore::Tar << "test.tgz";
QTest::newRow("directory") << (int) KoStore::Directory << "testdir";
QTest::newRow("zip") << (int) KoStore::Zip <<"test.zip";
#ifdef QCA2
if (KoEncryptionChecker::isEncryptionSupported()) {
QTest::newRow("Encrypted") << (int) KoStore::Encrypted << "testEncrypted.zip";
}
#endif
}
void TestStorage::storage()
{
const char test1[] = "This test checks whether we're able to write to some arbitrary directory.\n";
const char testDir[] = "0";
const char testDirResult[] = "0/";
const char test2[] = "This time we try to append the given relative path to the current dir.\n";
const char test3[] = "<xml>Hello World</xml>";
const char testDir2[] = "test2/with/a";
const char testDir2Result[] = "0/test2/with/a/";
const char test4[] = "<xml>Heureka, it works</xml>";
QFETCH(int, type);
QFETCH(QString, testFile);
KoStore::Backend backend = static_cast<KoStore::Backend>(type);
if (QFile::exists(testFile))
QFile::remove(testFile);
QDir dirTest(testFile);
if (dirTest.exists()) {
#ifdef Q_OS_UNIX
QByteArray ba = QByteArray("rm -rf ") + QFile::encodeName(testFile);
system(ba.constData()); // QDir::rmdir isn't recursive!
#else
QFAIL("build dir not empty");
#endif
}
KoStore* store = KoStore::createStore(testFile, KoStore::Write, "", backend);
QVERIFY(store);
QVERIFY(store->bad() == false);
if (store->isEncrypted())
store->setPassword("password");
QVERIFY(store->open("test1/with/a/relative/dir.txt"));
for (int i = 0; i < 100; ++i)
store->write(test1, strlen(test1));
store->close();
store->enterDirectory(testDir);
QCOMPARE(store->currentPath(), QString(testDirResult));
QVERIFY(store->open("test2/with/a/relative/dir.txt"));
for (int i = 0; i < 100; ++i)
store->write(test2, strlen(test2));
store->close();
QVERIFY(store->open("root"));
store->write(test3, strlen(test3));
store->close();
store->enterDirectory(testDir2);
QCOMPARE(store->currentPath(), QString(testDir2Result));
QVERIFY(store->open("root"));
store->write(test4, strlen(test4));
store->close();
if (store->isOpen())
store->close();
delete store;
store = KoStore::createStore(testFile, KoStore::Read, "", backend);
QVERIFY(store->bad() == false);
if (store->isEncrypted())
store->setPassword("password");
QVERIFY (store->open("test1/with/a/relative/dir.txt"));
QIODevice* dev = store->device();
int i = 0, lim = strlen(test1), count = 0;
while (static_cast<char>(getch(dev)) == test1[i++]) {
if (i == lim) {
i = 0;
++count;
}
}
store->close();
QCOMPARE(count, 100);
store->enterDirectory(testDir);
QCOMPARE (store->currentPath(), QString(testDirResult));
QVERIFY (store->open("test2/with/a/relative/dir.txt"));
dev = store->device();
i = 0;
lim = strlen(test2);
count = 0;
while (static_cast<char>(getch(dev)) == test2[i++]) {
if (i == lim) {
i = 0;
++count;
}
}
store->close();
QCOMPARE(count, 100);
store->enterDirectory(testDir2);
store->pushDirectory();
while (store->leaveDirectory()) {
;
}
store->enterDirectory(testDir);
QCOMPARE (store->currentPath(), QString(testDirResult));
QVERIFY (store->open("root"));
QCOMPARE (store->size(), (qint64) 22);
dev = store->device();
QByteArray dataReadBack = dev->read(strlen(test3));
store->close();
QCOMPARE (dataReadBack, QByteArray(test3));
store->popDirectory();
QCOMPARE(store->currentPath(), QString(testDir2Result));
QVERIFY (store->hasFile("relative/dir.txt"));
QVERIFY (store->open("root"));
char buf[29];
store->read(buf, 28);
buf[28] = '\0';
store->close();
QVERIFY(strncmp(buf, test4, 28) == 0);
if (store->isOpen())
store->close();
delete store;
QFile::remove(testFile);
}
#define DATALEN 64
void TestStorage::storage2_data()
{
QTest::addColumn<int>("type");
QTest::addColumn<QString>("testFile");
QTest::newRow("tar") << (int) KoStore::Tar << "test.tgz";
QTest::newRow("directory") << (int) KoStore::Directory << "testdir";
QTest::newRow("zip") << (int) KoStore::Zip <<"test.zip";
}
void TestStorage::storage2()
{
QFETCH(int, type);
QFETCH(QString, testFile);
KoStore::Backend backend = static_cast<KoStore::Backend>(type);
if (QFile::exists(testFile))
QFile::remove(testFile);
QDir dirTest(testFile);
if (dirTest.exists()) {
#ifdef Q_OS_UNIX
QByteArray ba = QByteArray("rm -rf ") + QFile::encodeName(testFile);
system(ba.constData()); // QDir::rmdir isn't recursive!
#else
QFAIL("build dir not empty");
#endif
}
KoStore* store = KoStore::createStore(testFile, KoStore::Write, "", backend);
QVERIFY(store->bad() == false);
// Write
QVERIFY (store->open("layer"));
char str[DATALEN];
sprintf(str, "1,2,3,4\n");
store->write(str, strlen(str));
memset(str, '\0', DATALEN);
store->write(str, DATALEN);
store->close();
delete store;
store = KoStore::createStore(testFile, KoStore::Read, "", backend);
QVERIFY(store->bad() == false);
// Read back
QVERIFY (store->open("layer"));
char str2[DATALEN];
QIODevice *stream = store->device(); // << Possible suspect!
stream->readLine(str2, DATALEN); // << as is this
qint64 len = store->read(str2, DATALEN);
QCOMPARE(len, (qint64) DATALEN);
store->close();
delete store;
QFile::remove(testFile);
}
QTEST_GUILESS_MAIN(TestStorage)
#include <TestStorage.moc>
diff --git a/src/libs/odf/tests/TestWriteStyleXml.cpp b/src/libs/odf/tests/TestWriteStyleXml.cpp
index 2bea11d3..33430c4c 100644
--- a/src/libs/odf/tests/TestWriteStyleXml.cpp
+++ b/src/libs/odf/tests/TestWriteStyleXml.cpp
@@ -1,75 +1,76 @@
/* This file is part of the KDE project
* Copyright (C) 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <KoXmlWriter.h>
#include <writeodf/writeodf.h>
#include <writeodf/writeodfoffice.h>
#include <writeodf/writeodfstyle.h>
#include <writeodf/writeodftext.h>
#include <writeodf/writeodfofficestyle.h>
#include <QString>
#include <QBuffer>
#include <QTest>
using namespace writeodf;
class TestWriteStyleXml : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testWriteRegionLeft();
};
void TestWriteStyleXml::testWriteRegionLeft()
{
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
{
KoXmlWriter writer(&buffer);
writer.startDocument(0);
office_document_styles styles(&writer);
styles.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
styles.addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
styles.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
office_master_styles master_styles(styles.add_office_master_styles());
style_master_page master_page(master_styles.add_style_master_page("Standard", "Layout"));
style_header header(master_page.add_style_header());
style_region_left left(header.add_style_region_left());
text_p p(left.add_text_p());
p.addTextNode("left");
}
const QString r = buffer.buffer();
const QString e = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<office:document-styles office:version=\"1.2\" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\">\n"
" <office:master-styles>\n"
" <style:master-page style:name=\"Standard\" style:page-layout-name=\"Layout\">\n"
" <style:header>\n"
" <style:region-left>\n"
" <text:p>left</text:p>\n"
" </style:region-left>\n"
" </style:header>\n"
" </style:master-page>\n"
" </office:master-styles>\n"
"</office:document-styles>";
QCOMPARE(r, e);
}
QTEST_GUILESS_MAIN(TestWriteStyleXml)
#include <TestWriteStyleXml.moc>
diff --git a/src/libs/odf/tests/TestXmlReader.cpp b/src/libs/odf/tests/TestXmlReader.cpp
index 121ddf83..92ca0364 100644
--- a/src/libs/odf/tests/TestXmlReader.cpp
+++ b/src/libs/odf/tests/TestXmlReader.cpp
@@ -1,2767 +1,2768 @@
+// clazy:excludeall=qstring-arg
#include <QTest>
#include <QBuffer>
#include <QFile>
#include <QDateTime>
#include <QProcess>
#include <QString>
#include <QTextStream>
#include <KoXmlReader.h>
class TestXmlReader : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testNode();
void testElement();
void testAttributes();
void testText();
void testCDATA();
void testDocument();
void testDocumentType();
void testNamespace();
void testParseQString();
void testUnload();
void testSimpleXML();
void testRootError();
void testMismatchedTag();
void testConvertQDomDocument();
void testConvertQDomElement();
void testSimpleOpenDocumentText();
void testWhitespace();
void testSimpleOpenDocumentSpreadsheet();
void testSimpleOpenDocumentPresentation();
void testSimpleOpenDocumentFormula();
void testLargeOpenDocumentSpreadsheet();
void testExternalOpenDocumentSpreadsheet(const QString& filename);
};
void TestXmlReader::testNode()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth>";
xmlstream << " <continents>";
xmlstream << " <asia/>";
xmlstream << " <africa/>";
xmlstream << " <europe/>";
xmlstream << " <america/>";
xmlstream << " <australia/>";
xmlstream << " <antartic/>";
xmlstream << " </continents>";
xmlstream << " <oceans>";
xmlstream << " <pacific/>";
xmlstream << " <atlantic/>";
xmlstream << " </oceans>";
xmlstream << "</earth>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// null node
KoXmlNode node1;
QCOMPARE(node1.nodeName(), QString());
QCOMPARE(node1.isNull(), true);
QCOMPARE(node1.isElement(), false);
QCOMPARE(node1.isDocument(), false);
QCOMPARE(node1.ownerDocument().isNull(), true);
QCOMPARE(node1.parentNode().isNull(), true);
QCOMPARE(node1.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(node1), 0);
QCOMPARE(node1.firstChild().isNull(), true);
QCOMPARE(node1.lastChild().isNull(), true);
QCOMPARE(node1.previousSibling().isNull(), true);
QCOMPARE(node1.nextSibling().isNull(), true);
// compare with another null node
KoXmlNode node2;
QCOMPARE(node2.isNull(), true);
QCOMPARE(node1 == node2, true);
QCOMPARE(node1 != node2, false);
// a node which is a document
KoXmlNode node3 = doc;
QCOMPARE(node3.nodeName(), QString("#document"));
QCOMPARE(node3.isNull(), false);
QCOMPARE(node3.isElement(), false);
QCOMPARE(node3.isText(), false);
QCOMPARE(node3.isDocument(), true);
QCOMPARE(node3.ownerDocument().isNull(), false);
QCOMPARE(node3.ownerDocument() == doc, true);
QCOMPARE(node3.toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(node3), 1);
// convert to document and the compare
KoXmlDocument doc2 = node3.toDocument();
QCOMPARE(doc2.nodeName(), QString("#document"));
QCOMPARE(doc2.isNull(), false);
QCOMPARE(doc2.isDocument(), true);
QCOMPARE(node3 == doc2, true);
QCOMPARE(KoXml::childNodesCount(doc2), 1);
// a document is of course can't be converted to element
KoXmlElement invalidElement = node3.toElement();
QCOMPARE(invalidElement.nodeName(), QString());
QCOMPARE(invalidElement.isNull(), true);
QCOMPARE(invalidElement.isElement(), false);
QCOMPARE(invalidElement.isText(), false);
QCOMPARE(invalidElement.isDocument(), false);
// clear() makes it a null node again
node3.clear();
QCOMPARE(node3.isNull(), true);
QCOMPARE(node3.nodeName(), QString());
QCOMPARE(node3.isElement(), false);
QCOMPARE(node3.isText(), false);
QCOMPARE(node3.isDocument(), false);
QCOMPARE(node3.ownerDocument().isNull(), true);
QCOMPARE(node1 == node3, true);
QCOMPARE(node1 != node3, false);
// a node which is an element for <earth>
KoXmlNode node4 = doc.firstChild();
QCOMPARE(node4.isNull(), false);
QCOMPARE(node4.isElement(), true);
QCOMPARE(node4.isText(), false);
QCOMPARE(node4.isDocument(), false);
QCOMPARE(node4.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(node4), 4);
QCOMPARE(node4.ownerDocument() == doc, true);
QCOMPARE(node4.toElement() == doc.firstChild().toElement(), true);
// clear() makes it a null node again
node4.clear();
QCOMPARE(node4.isNull(), true);
QCOMPARE(node4.isElement(), false);
QCOMPARE(node4.isText(), false);
QCOMPARE(node4.isDocument(), false);
QCOMPARE(node4 == node1, true);
QCOMPARE(node4 != node1, false);
QCOMPARE(KoXml::childNodesCount(node4), 0);
// a node which is an element for <continents>
KoXmlNode node5 = doc.firstChild().firstChild().nextSibling();
QCOMPARE(node5.nodeName(), QString("continents"));
QCOMPARE(node5.isNull(), false);
QCOMPARE(node5.isElement(), true);
QCOMPARE(node5.isText(), false);
QCOMPARE(node5.isDocument(), false);
QCOMPARE(node5.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(node5), 13);
QCOMPARE(node5.ownerDocument() == doc, true);
// convert to element and the compare
KoXmlElement continentsElement = node5.toElement();
QCOMPARE(node5 == continentsElement, true);
QCOMPARE(continentsElement.isNull(), false);
QCOMPARE(continentsElement.isElement(), true);
QCOMPARE(continentsElement.isText(), false);
QCOMPARE(continentsElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(continentsElement), 13);
QCOMPARE(continentsElement.ownerDocument() == doc, true);
// and it doesn't make sense to convert that node to document
KoXmlDocument invalidDoc = node5.toDocument();
QCOMPARE(invalidDoc.isNull(), true);
QCOMPARE(invalidDoc.isElement(), false);
QCOMPARE(invalidDoc.isText(), false);
QCOMPARE(invalidDoc.isDocument(), false);
// node for <europe> using namedItem() function
KoXmlNode europeNode = continentsElement.namedItem(QString("europe"));
QCOMPARE(europeNode.nodeName(), QString("europe"));
QCOMPARE(europeNode.isNull(), false);
QCOMPARE(europeNode.isElement(), true);
QCOMPARE(europeNode.isText(), false);
QCOMPARE(europeNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(europeNode), 0);
QCOMPARE(europeNode.ownerDocument() == doc, true);
// search non-existing node
KoXmlNode fooNode = continentsElement.namedItem(QString("foobar"));
QCOMPARE(fooNode.isNull(), true);
QCOMPARE(fooNode.isElement(), false);
QCOMPARE(fooNode.isText(), false);
QCOMPARE(fooNode.isCDATASection(), false);
QCOMPARE(KoXml::childNodesCount(fooNode), 0);
}
void TestXmlReader::testElement()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<html>";
xmlstream << "<body bgcolor=\"#000\">";
xmlstream << "<p>";
xmlstream << "Hello, world!";
xmlstream << "</p>";
xmlstream << "</body>";
xmlstream << "</html>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// element for <html>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.nodeName(), QString("html"));
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.isDocument(), false);
QCOMPARE(rootElement.ownerDocument().isNull(), false);
QCOMPARE(rootElement.ownerDocument() == doc, true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 1);
QCOMPARE(rootElement.tagName(), QString("html"));
QCOMPARE(rootElement.prefix().isNull(), true);
// element for <body>
KoXmlElement bodyElement;
bodyElement = rootElement.firstChild().toElement();
QCOMPARE(bodyElement.nodeName(), QString("body"));
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.isDocument(), false);
QCOMPARE(bodyElement.ownerDocument().isNull(), false);
QCOMPARE(bodyElement.ownerDocument() == doc, true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == rootElement, true);
QCOMPARE(bodyElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 1);
QCOMPARE(bodyElement.tagName(), QString("body"));
QCOMPARE(bodyElement.prefix().isNull(), true);
QCOMPARE(bodyElement.hasAttribute("bgcolor"), true);
QCOMPARE(bodyElement.attribute("bgcolor"), QString("#000"));
// a shared copy of <body>, will still have access to attribute bgcolor
KoXmlElement body2Element;
body2Element = bodyElement;
QCOMPARE(body2Element.nodeName(), QString("body"));
QCOMPARE(body2Element.isNull(), false);
QCOMPARE(body2Element.isElement(), true);
QCOMPARE(body2Element.isDocument(), false);
QCOMPARE(body2Element.ownerDocument().isNull(), false);
QCOMPARE(body2Element.ownerDocument() == doc, true);
QCOMPARE(body2Element == bodyElement, true);
QCOMPARE(body2Element != bodyElement, false);
QCOMPARE(body2Element.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(body2Element), 1);
QCOMPARE(body2Element.tagName(), QString("body"));
QCOMPARE(body2Element.prefix().isNull(), true);
QCOMPARE(body2Element.hasAttribute("bgcolor"), true);
QCOMPARE(body2Element.attribute("bgcolor"), QString("#000"));
// empty element, by default constructor
KoXmlElement testElement;
QCOMPARE(testElement.nodeName(), QString());
QCOMPARE(testElement.isNull(), true);
QCOMPARE(testElement.isElement(), false);
QCOMPARE(testElement.isDocument(), false);
QCOMPARE(testElement.ownerDocument().isNull(), true);
QCOMPARE(testElement.ownerDocument() != doc, true);
QCOMPARE(testElement == rootElement, false);
QCOMPARE(testElement != rootElement, true);
QCOMPARE(testElement.parentNode().isNull(), true);
QCOMPARE(testElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(testElement), 0);
// check assignment operator
testElement = rootElement;
QCOMPARE(testElement.nodeName(), QString("html"));
QCOMPARE(testElement.isNull(), false);
QCOMPARE(testElement.isElement(), true);
QCOMPARE(testElement.isDocument(), false);
QCOMPARE(testElement == rootElement, true);
QCOMPARE(testElement != rootElement, false);
QCOMPARE(testElement.parentNode().isNull(), false);
QCOMPARE(testElement.parentNode().toDocument() == doc, true);
QCOMPARE(testElement.tagName(), QString("html"));
QCOMPARE(testElement.prefix().isNull(), true);
QCOMPARE(KoXml::childNodesCount(testElement), 1);
// assigned from another empty element
testElement = KoXmlElement();
QCOMPARE(testElement.isNull(), true);
QCOMPARE(testElement != rootElement, true);
// assigned from <body>
testElement = bodyElement;
QCOMPARE(testElement.isNull(), false);
QCOMPARE(testElement.isElement(), true);
QCOMPARE(testElement.isDocument(), false);
QCOMPARE(testElement.ownerDocument().isNull(), false);
QCOMPARE(testElement.ownerDocument() == doc, true);
QCOMPARE(testElement == bodyElement, true);
QCOMPARE(testElement.parentNode().isNull(), false);
QCOMPARE(testElement.tagName(), QString("body"));
QCOMPARE(testElement.prefix().isNull(), true);
QCOMPARE(testElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(testElement), 1);
// copy constructor
KoXmlElement dummyElement(rootElement);
QCOMPARE(dummyElement.isNull(), false);
QCOMPARE(dummyElement.isElement(), true);
QCOMPARE(dummyElement.isDocument(), false);
QCOMPARE(dummyElement.ownerDocument().isNull(), false);
QCOMPARE(dummyElement.ownerDocument() == doc, true);
QCOMPARE(dummyElement == rootElement, true);
QCOMPARE(dummyElement.parentNode().isNull(), false);
QCOMPARE(dummyElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(dummyElement), 1);
QCOMPARE(dummyElement.tagName(), QString("html"));
QCOMPARE(dummyElement.prefix().isNull(), true);
// clear() turns element to null node
dummyElement.clear();
QCOMPARE(dummyElement.isNull(), true);
QCOMPARE(dummyElement.isElement(), false);
QCOMPARE(dummyElement.isDocument(), false);
QCOMPARE(dummyElement.ownerDocument().isNull(), true);
QCOMPARE(dummyElement.ownerDocument() == doc, false);
QCOMPARE(dummyElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(dummyElement), 0);
QCOMPARE(dummyElement == rootElement, false);
QCOMPARE(dummyElement != rootElement, true);
// check for plain null node converted to element
KoXmlNode dummyNode;
dummyElement = dummyNode.toElement();
QCOMPARE(dummyElement.isNull(), true);
QCOMPARE(dummyElement.isElement(), false);
QCOMPARE(dummyElement.isDocument(), false);
QCOMPARE(dummyElement.ownerDocument().isNull(), true);
QCOMPARE(dummyElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(dummyElement), 0);
QCOMPARE(dummyElement.ownerDocument() == doc, false);
}
void TestXmlReader::testAttributes()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<p>";
xmlstream << "<img src=\"foo.png\" width=\"300\" height=\"150\"/>";
xmlstream << "</p>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
QCOMPARE(rootElement.tagName(), QString("p"));
QCOMPARE(rootElement.prefix().isNull(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 1);
KoXmlElement imgElement;
imgElement = rootElement.firstChild().toElement();
QCOMPARE(imgElement.isNull(), false);
QCOMPARE(imgElement.isElement(), true);
QCOMPARE(imgElement.tagName(), QString("img"));
QCOMPARE(imgElement.prefix().isNull(), true);
QCOMPARE(KoXml::childNodesCount(imgElement), 0);
QCOMPARE(imgElement.hasAttribute("src"), true);
QCOMPARE(imgElement.hasAttribute("width"), true);
QCOMPARE(imgElement.hasAttribute("height"), true);
QCOMPARE(imgElement.hasAttribute("non-exist"), false);
QCOMPARE(imgElement.hasAttribute("SRC"), false);
QCOMPARE(imgElement.attribute("src"), QString("foo.png"));
QCOMPARE(imgElement.attribute("width"), QString("300"));
QCOMPARE(imgElement.attribute("width").toInt(), 300);
QCOMPARE(imgElement.attribute("height"), QString("150"));
QCOMPARE(imgElement.attribute("height").toInt(), 150);
QCOMPARE(imgElement.attribute("border").isEmpty(), true);
QCOMPARE(imgElement.attribute("border", "0").toInt(), 0);
QCOMPARE(imgElement.attribute("border", "-1").toInt(), -1);
QStringList list = KoXml::attributeNames(imgElement);
QCOMPARE(list.count(), 3);
QVERIFY(list.contains("src"));
QVERIFY(list.contains("width"));
QVERIFY(list.contains("height"));
QVERIFY(! list.contains("border"));
foreach(QString a, list) {
QCOMPARE(imgElement.hasAttribute(a), true);
QCOMPARE(imgElement.attribute(a).isEmpty(), false);
}
}
void TestXmlReader::testText()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<p>";
xmlstream << "Hello ";
xmlstream << "<b>world</b>";
xmlstream << "</p>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// element for <p>
KoXmlElement parElement;
parElement = doc.documentElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.isText(), false);
QCOMPARE(parElement.isDocument(), false);
QCOMPARE(parElement.ownerDocument().isNull(), false);
QCOMPARE(parElement.ownerDocument() == doc, true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode().toDocument() == doc, true);
QCOMPARE(parElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(parElement), 2); // <b> and text node "Hello "
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.prefix().isNull(), true);
QCOMPARE(parElement.text(), QString("Hello world"));
// node for "Hello"
KoXmlNode helloNode;
helloNode = parElement.firstChild();
QCOMPARE(helloNode.nodeName(), QString("#text"));
QCOMPARE(helloNode.isNull(), false);
QCOMPARE(helloNode.isElement(), false);
QCOMPARE(helloNode.isText(), true);
QCOMPARE(helloNode.isDocument(), false);
QCOMPARE(helloNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(helloNode), 0);
// "Hello" text
KoXmlText helloText;
helloText = helloNode.toText();
QCOMPARE(helloText.nodeName(), QString("#text"));
QCOMPARE(helloText.isNull(), false);
QCOMPARE(helloText.isElement(), false);
QCOMPARE(helloText.isText(), true);
QCOMPARE(helloText.isDocument(), false);
QCOMPARE(helloText.data(), QString("Hello "));
QCOMPARE(KoXml::childNodesCount(helloText), 0);
// shared copy of the text
KoXmlText hello2Text;
hello2Text = helloText;
QCOMPARE(hello2Text.isNull(), false);
QCOMPARE(hello2Text.isElement(), false);
QCOMPARE(hello2Text.isText(), true);
QCOMPARE(hello2Text.isDocument(), false);
QCOMPARE(hello2Text.data(), QString("Hello "));
QCOMPARE(KoXml::childNodesCount(hello2Text), 0);
// element for <b>
KoXmlElement boldElement;
boldElement = helloNode.nextSibling().toElement();
QCOMPARE(boldElement.isNull(), false);
QCOMPARE(boldElement.isElement(), true);
QCOMPARE(boldElement.isText(), false);
QCOMPARE(boldElement.isDocument(), false);
QCOMPARE(boldElement.ownerDocument().isNull(), false);
QCOMPARE(boldElement.ownerDocument() == doc, true);
QCOMPARE(boldElement.parentNode().isNull(), false);
QCOMPARE(boldElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(boldElement), 1); // text node "world"
QCOMPARE(boldElement.tagName(), QString("b"));
QCOMPARE(boldElement.prefix().isNull(), true);
// "world" text
KoXmlText worldText;
worldText = boldElement.firstChild().toText();
QCOMPARE(worldText.isNull(), false);
QCOMPARE(worldText.isElement(), false);
QCOMPARE(worldText.isText(), true);
QCOMPARE(worldText.isDocument(), false);
QCOMPARE(worldText.data(), QString("world"));
QCOMPARE(KoXml::childNodesCount(worldText), 0);
}
void TestXmlReader::testCDATA()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<p>";
xmlstream << "Hello ";
xmlstream << "<![CDATA[world]]>";
xmlstream << "</p>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// element for <p>
KoXmlElement parElement;
parElement = doc.documentElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.isText(), false);
QCOMPARE(parElement.isDocument(), false);
QCOMPARE(parElement.ownerDocument().isNull(), false);
QCOMPARE(parElement.ownerDocument() == doc, true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode().toDocument() == doc, true);
QCOMPARE(parElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(parElement), 2);
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.prefix().isNull(), true);
QCOMPARE(parElement.text(), QString("Hello world"));
// node for "Hello"
KoXmlNode helloNode;
helloNode = parElement.firstChild();
QCOMPARE(helloNode.isNull(), false);
QCOMPARE(helloNode.isElement(), false);
QCOMPARE(helloNode.isText(), true);
QCOMPARE(helloNode.isDocument(), false);
// "Hello" text
KoXmlText helloText;
helloText = helloNode.toText();
QCOMPARE(helloText.isNull(), false);
QCOMPARE(helloText.isElement(), false);
QCOMPARE(helloText.isText(), true);
QCOMPARE(helloText.isDocument(), false);
QCOMPARE(helloText.data(), QString("Hello "));
// node for CDATA "world!"
// Note: isText() is also true for CDATA
KoXmlNode worldNode;
worldNode = helloNode.nextSibling();
QCOMPARE(worldNode.nodeName(), QString("#cdata-section"));
QCOMPARE(worldNode.isNull(), false);
QCOMPARE(worldNode.isElement(), false);
QCOMPARE(worldNode.isText(), true);
QCOMPARE(worldNode.isCDATASection(), true);
QCOMPARE(worldNode.isDocument(), false);
// CDATA section for "world!"
// Note: isText() is also true for CDATA
KoXmlCDATASection worldCDATA;
worldCDATA = worldNode.toCDATASection();
QCOMPARE(worldCDATA.nodeName(), QString("#cdata-section"));
QCOMPARE(worldCDATA.isNull(), false);
QCOMPARE(worldCDATA.isElement(), false);
QCOMPARE(worldCDATA.isText(), true);
QCOMPARE(worldCDATA.isCDATASection(), true);
QCOMPARE(worldCDATA.isDocument(), false);
QCOMPARE(worldCDATA.data(), QString("world"));
}
void TestXmlReader::testDocument()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<calligra>";
xmlstream << " <words/>\n";
xmlstream << " <kpresenter/>\n";
xmlstream << "</calligra>";
xmldevice.close();
KoXmlDocument doc;
// empty document
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), true);
QCOMPARE(doc.lastChild().isNull(), true);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// now give something as the content
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// this document has something already
QCOMPARE(doc.nodeName(), QString("#document"));
QCOMPARE(doc.isNull(), false);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), true);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), false);
QCOMPARE(doc.lastChild().isNull(), false);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// make sure its children are fine
KoXmlElement rootElement;
rootElement = doc.firstChild().toElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.isDocument(), false);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
rootElement = doc.lastChild().toElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.isDocument(), false);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
// clear() converts it into null node
doc.clear();
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), true);
QCOMPARE(doc.lastChild().isNull(), true);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// assigned from another empty document
doc = KoXmlDocument();
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.nodeName().isEmpty(), true);
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
}
void TestXmlReader::testDocumentType()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">";
xmlstream << "<body>";
xmlstream << " <img/>\n";
xmlstream << " <p/>\n";
xmlstream << " <blockquote/>\n";
xmlstream << "</body>";
xmldevice.close();
// empty document
KoXmlDocument doc;
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), true);
QCOMPARE(doc.lastChild().isNull(), true);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// has empty doctype
KoXmlDocumentType doctype = doc.doctype();
QCOMPARE(doctype.nodeName(), QString());
QCOMPARE(doctype.isNull(), true);
QCOMPARE(doctype.isElement(), false);
QCOMPARE(doctype.isDocument(), false);
QCOMPARE(doctype.isDocumentType(), false);
QCOMPARE(doctype.parentNode().isNull(), true);
QCOMPARE(doctype.firstChild().isNull(), true);
QCOMPARE(doctype.lastChild().isNull(), true);
QCOMPARE(doctype.previousSibling().isNull(), true);
QCOMPARE(doctype.nextSibling().isNull(), true);
// now give something as the content
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// this document has something already
QCOMPARE(doc.nodeName(), QString("#document"));
QCOMPARE(doc.isNull(), false);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), true);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), false);
QCOMPARE(doc.lastChild().isNull(), false);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// the doctype becomes a valid one
doctype = doc.doctype();
QCOMPARE(doctype.nodeName(), QString("html"));
QCOMPARE(doctype.name(), QString("html"));
QCOMPARE(doctype.isNull(), false);
QCOMPARE(doctype.isElement(), false);
QCOMPARE(doctype.isDocument(), false);
QCOMPARE(doctype.isDocumentType(), true);
QCOMPARE(doctype.parentNode().isNull(), false);
QCOMPARE(doctype.parentNode() == doc, true);
QCOMPARE(doctype.firstChild().isNull(), true);
QCOMPARE(doctype.lastChild().isNull(), true);
QCOMPARE(doctype.previousSibling().isNull(), true);
QCOMPARE(doctype.nextSibling().isNull(), true);
}
void TestXmlReader::testNamespace()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// taken from example in Qt documentation (xml.html)
xmlstream << "<document xmlns:book = \"http://trolltech.com/fnord/book/\"";
xmlstream << " xmlns = \"http://trolltech.com/fnord/\" >";
xmlstream << "<book>";
xmlstream << " <book:title>Practical XML</book:title>";
xmlstream << " <book:author xmlns:fnord = \"http://trolltech.com/fnord/\"";
xmlstream << " title=\"Ms\"";
xmlstream << " fnord:title=\"Goddess\"";
xmlstream << " name=\"Eris Kallisti\"/>";
xmlstream << " <chapter>";
xmlstream << " <title>A Namespace Called fnord</title>";
xmlstream << " </chapter>";
xmlstream << "</book>";
xmlstream << "</document>";
xmldevice.close();
KoXmlDocument doc;
KoXmlElement rootElement;
KoXmlElement bookElement;
KoXmlElement bookTitleElement;
KoXmlElement bookAuthorElement;
// ------------- first without any namespace processing -----------
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.tagName(), QString("document"));
QCOMPARE(rootElement.prefix().isNull(), true);
bookElement = rootElement.firstChildElement();
QCOMPARE(bookElement.isNull(), false);
QCOMPARE(bookElement.isElement(), true);
QCOMPARE(bookElement.tagName(), QString("book"));
QCOMPARE(bookElement.prefix().isNull(), true);
QCOMPARE(bookElement.localName(), QString());
bookTitleElement = bookElement.firstChildElement();
QCOMPARE(bookTitleElement.isNull(), false);
QCOMPARE(bookTitleElement.isElement(), true);
QCOMPARE(bookTitleElement.tagName(), QString("book:title"));
QCOMPARE(bookTitleElement.prefix().isNull(), true);
QCOMPARE(bookTitleElement.localName(), QString());
KoXmlNode whiteSpace = bookTitleElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
bookAuthorElement = whiteSpace.nextSibling().toElement();
QCOMPARE(bookAuthorElement.isNull(), false);
QCOMPARE(bookAuthorElement.isElement(), true);
QCOMPARE(bookAuthorElement.tagName(), QString("book:author"));
QCOMPARE(bookAuthorElement.prefix().isNull(), true);
QCOMPARE(bookAuthorElement.attribute("title"), QString("Ms"));
QCOMPARE(bookAuthorElement.attribute("fnord:title"), QString("Goddess"));
QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti"));
// ------------- now with namespace processing -----------
xmldevice.seek(0); // just to rewind
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* defaultNS = "http://trolltech.com/fnord/";
const char* bookNS = "http://trolltech.com/fnord/book/";
const char* fnordNS = "http://trolltech.com/fnord/";
// <document>
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.tagName(), QString("document"));
QCOMPARE(rootElement.prefix().isEmpty(), true);
QCOMPARE(rootElement.namespaceURI(), QString(defaultNS));
QCOMPARE(rootElement.localName(), QString("document"));
// <book>
bookElement = rootElement.firstChild().toElement();
QCOMPARE(bookElement.isNull(), false);
QCOMPARE(bookElement.isElement(), true);
QCOMPARE(bookElement.tagName(), QString("book"));
QCOMPARE(bookElement.prefix().isEmpty(), true);
QCOMPARE(bookElement.namespaceURI(), QString(defaultNS));
QCOMPARE(bookElement.localName(), QString("book"));
// <book:title>
bookTitleElement = bookElement.firstChildElement();
QCOMPARE(bookTitleElement.isNull(), false);
QCOMPARE(bookTitleElement.isElement(), true);
QCOMPARE(bookTitleElement.tagName(), QString("title"));
QCOMPARE(bookTitleElement.prefix(), QString("book"));
QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookTitleElement.localName(), QString("title"));
// another way, find it using namedItemNS()
KoXmlElement book2TitleElement;
book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title");
//book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement();
QCOMPARE(book2TitleElement == bookTitleElement, true);
QCOMPARE(book2TitleElement.isNull(), false);
QCOMPARE(book2TitleElement.isElement(), true);
QCOMPARE(book2TitleElement.tagName(), QString("title"));
whiteSpace = bookTitleElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// <book:author>
bookAuthorElement = whiteSpace.nextSibling().toElement();
QCOMPARE(bookAuthorElement.isNull(), false);
QCOMPARE(bookAuthorElement.isElement(), true);
QCOMPARE(bookAuthorElement.tagName(), QString("author"));
QCOMPARE(bookAuthorElement.prefix(), QString("book"));
QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookAuthorElement.localName(), QString("author"));
// another way, find it using namedItemNS()
KoXmlElement book2AuthorElement;
book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author");
//book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement();
QCOMPARE(book2AuthorElement == bookAuthorElement, true);
QCOMPARE(book2AuthorElement.isNull(), false);
QCOMPARE(book2AuthorElement.isElement(), true);
QCOMPARE(book2AuthorElement.tagName(), QString("author"));
// attributes in <book:author>
// Note: with namespace processing, attribute's prefix is taken out and
// hence "fnord:title" will simply override "title"
// and searching attribute with prefix will give no result
QCOMPARE(bookAuthorElement.hasAttribute("title"), true);
QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttribute("name"), true);
QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess"));
QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true);
QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti"));
// attributes in <book:author>, with NS family of functions
// those without prefix are not accessible at all, because they do not belong
// to any namespace at all.
// Note: default namespace does not apply to attribute names!
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString(""));
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true);
}
// mostly similar to testNamespace above, but parse from a QString
void TestXmlReader::testParseQString()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QString xmlText;
xmlText += "<document xmlns:book = \"http://trolltech.com/fnord/book/\"";
xmlText += " xmlns = \"http://trolltech.com/fnord/\" >";
xmlText += "<book>";
xmlText += " <book:title>Practical XML</book:title>";
xmlText += " <book:author xmlns:fnord = \"http://trolltech.com/fnord/\"";
xmlText += " title=\"Ms\"";
xmlText += " fnord:title=\"Goddess\"";
xmlText += " name=\"Eris Kallisti\"/>";
xmlText += " <chapter>";
xmlText += " <title>A Namespace Called fnord</title>";
xmlText += " </chapter>";
xmlText += "</book>";
xmlText += "</document>";
KoXmlDocument doc;
KoXmlElement rootElement;
KoXmlElement bookElement;
KoXmlElement bookTitleElement;
KoXmlElement bookAuthorElement;
QCOMPARE(doc.setContent(xmlText, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* defaultNS = "http://trolltech.com/fnord/";
const char* bookNS = "http://trolltech.com/fnord/book/";
const char* fnordNS = "http://trolltech.com/fnord/";
// <document>
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.tagName(), QString("document"));
QCOMPARE(rootElement.prefix().isEmpty(), true);
QCOMPARE(rootElement.namespaceURI(), QString(defaultNS));
QCOMPARE(rootElement.localName(), QString("document"));
// <book>
bookElement = rootElement.firstChild().toElement();
QCOMPARE(bookElement.isNull(), false);
QCOMPARE(bookElement.isElement(), true);
QCOMPARE(bookElement.tagName(), QString("book"));
QCOMPARE(bookElement.prefix().isEmpty(), true);
QCOMPARE(bookElement.namespaceURI(), QString(defaultNS));
QCOMPARE(bookElement.localName(), QString("book"));
// <book:title>
bookTitleElement = bookElement.firstChildElement();
QCOMPARE(bookTitleElement.isNull(), false);
QCOMPARE(bookTitleElement.isElement(), true);
QCOMPARE(bookTitleElement.tagName(), QString("title"));
QCOMPARE(bookTitleElement.prefix(), QString("book"));
QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookTitleElement.localName(), QString("title"));
// another way, find it using namedItemNS()
KoXmlElement book2TitleElement;
book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title");
//book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement();
QCOMPARE(book2TitleElement == bookTitleElement, true);
QCOMPARE(book2TitleElement.isNull(), false);
QCOMPARE(book2TitleElement.isElement(), true);
QCOMPARE(book2TitleElement.tagName(), QString("title"));
KoXmlNode whiteSpace = bookTitleElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// <book:author>
bookAuthorElement = whiteSpace.nextSibling().toElement();
QCOMPARE(bookAuthorElement.isNull(), false);
QCOMPARE(bookAuthorElement.isElement(), true);
QCOMPARE(bookAuthorElement.tagName(), QString("author"));
QCOMPARE(bookAuthorElement.prefix(), QString("book"));
QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookAuthorElement.localName(), QString("author"));
// another way, find it using namedItemNS()
KoXmlElement book2AuthorElement;
book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author");
//book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement();
QCOMPARE(book2AuthorElement == bookAuthorElement, true);
QCOMPARE(book2AuthorElement.isNull(), false);
QCOMPARE(book2AuthorElement.isElement(), true);
QCOMPARE(book2AuthorElement.tagName(), QString("author"));
// attributes in <book:author>
// Note: with namespace processing, attribute's prefix is taken out and
// hence "fnord:title" will simply override "title"
// and searching attribute with prefix will give no result
QCOMPARE(bookAuthorElement.hasAttribute("title"), true);
QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttribute("name"), true);
QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess"));
QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true);
QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti"));
// attributes in <book:author>, with NS family of functions
// those without prefix are not accessible at all, because they do not belong
// to any namespace at all.
// Note: default namespace does not apply to attribute names!
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString(""));
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true);
}
void TestXmlReader::testUnload()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth>";
xmlstream << "<continents>";
xmlstream << "<asia/>";
xmlstream << "<africa/>";
xmlstream << "<europe/>";
xmlstream << "<america/>";
xmlstream << "<australia/>";
xmlstream << "<antartic/>";
xmlstream << "</continents>";
xmlstream << "<oceans>";
xmlstream << "<pacific/>";
xmlstream << "<atlantic/>";
xmlstream << "</oceans>";
xmlstream << "</earth>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement earthElement;
earthElement = doc.documentElement().toElement();
QCOMPARE(earthElement.isNull(), false);
QCOMPARE(earthElement.isElement(), true);
QCOMPARE(earthElement.parentNode().isNull(), false);
QCOMPARE(earthElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(earthElement), 2);
QCOMPARE(earthElement.tagName(), QString("earth"));
QCOMPARE(earthElement.prefix().isNull(), true);
// this ensures that all child nodes of <earth> are loaded
earthElement.firstChild();
// explicitly unload all child nodes of <earth>
KoXml::unload(earthElement);
// we should get the correct first child
KoXmlElement continentsElement = earthElement.firstChild().toElement();
QCOMPARE(continentsElement.nodeName(), QString("continents"));
QCOMPARE(continentsElement.isNull(), false);
QCOMPARE(continentsElement.isElement(), true);
QCOMPARE(continentsElement.isText(), false);
QCOMPARE(continentsElement.isDocument(), false);
QCOMPARE(continentsElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(continentsElement), 6);
// let us unload everything again
KoXml::unload(earthElement);
// we should get the correct last child
KoXmlElement oceansElement = earthElement.lastChild().toElement();
QCOMPARE(oceansElement.nodeName(), QString("oceans"));
QCOMPARE(oceansElement.isNull(), false);
QCOMPARE(oceansElement.isElement(), true);
QCOMPARE(oceansElement.isText(), false);
QCOMPARE(oceansElement.isDocument(), false);
QCOMPARE(oceansElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(continentsElement), 6);
}
void TestXmlReader::testSimpleXML()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<solarsystem>";
xmlstream << " <mercurius/>\n";
xmlstream << " <venus/>\n";
xmlstream << " <earth>\n";
xmlstream << " <moon/>\n";
xmlstream << " </earth>\n";
xmlstream << " <mars/>\n";
xmlstream << " <jupiter/>\n";
xmlstream << "</solarsystem>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// <solarsystem>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 11);
QCOMPARE(rootElement.tagName(), QString("solarsystem"));
QCOMPARE(rootElement.prefix().isNull(), true);
// node <mercurius>
KoXmlNode firstPlanetNode;
firstPlanetNode = rootElement.firstChildElement();
QCOMPARE(firstPlanetNode.isNull(), false);
QCOMPARE(firstPlanetNode.isElement(), true);
QCOMPARE(firstPlanetNode.nextSibling().isNull(), false);
QCOMPARE(firstPlanetNode.previousSibling().isNull(), false);
QCOMPARE(firstPlanetNode.parentNode().isNull(), false);
QCOMPARE(firstPlanetNode.parentNode() == rootElement, true);
QCOMPARE(firstPlanetNode.parentNode() != rootElement, false);
QCOMPARE(firstPlanetNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0);
QCOMPARE(firstPlanetNode.firstChild().isNull(), true);
QCOMPARE(firstPlanetNode.lastChild().isNull(), true);
// element <mercurius>
KoXmlElement firstPlanetElement;
firstPlanetElement = firstPlanetNode.toElement();
QCOMPARE(firstPlanetElement.isNull(), false);
QCOMPARE(firstPlanetElement.isElement(), true);
QCOMPARE(firstPlanetElement.parentNode().isNull(), false);
QCOMPARE(firstPlanetElement.parentNode() == rootElement, true);
QCOMPARE(firstPlanetElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0);
QCOMPARE(firstPlanetElement.firstChild().isNull(), true);
QCOMPARE(firstPlanetElement.lastChild().isNull(), true);
QCOMPARE(firstPlanetElement.tagName(), QString("mercurius"));
QCOMPARE(firstPlanetElement.prefix().isNull(), true);
KoXmlNode whiteSpace = firstPlanetElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// node <venus>
KoXmlNode secondPlanetNode;
secondPlanetNode = whiteSpace.nextSibling();
QCOMPARE(secondPlanetNode.isNull(), false);
QCOMPARE(secondPlanetNode.isElement(), true);
QCOMPARE(secondPlanetNode.nextSibling().isNull(), false);
QCOMPARE(secondPlanetNode.previousSibling().isNull(), false);
QCOMPARE(secondPlanetNode.previousSibling() == whiteSpace, true);
QCOMPARE(secondPlanetNode.parentNode().isNull(), false);
QCOMPARE(secondPlanetNode.parentNode() == rootElement, true);
QCOMPARE(secondPlanetNode.parentNode() != rootElement, false);
QCOMPARE(secondPlanetNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0);
QCOMPARE(secondPlanetNode.firstChild().isNull(), true);
QCOMPARE(secondPlanetNode.lastChild().isNull(), true);
// element <venus>
KoXmlElement secondPlanetElement;
secondPlanetElement = secondPlanetNode.toElement();
QCOMPARE(secondPlanetElement.isNull(), false);
QCOMPARE(secondPlanetElement.isElement(), true);
QCOMPARE(secondPlanetElement.nextSibling().isNull(), false);
QCOMPARE(secondPlanetElement.previousSibling().isNull(), false);
QCOMPARE(secondPlanetElement.previousSibling() == whiteSpace, true);
QCOMPARE(secondPlanetElement.parentNode().isNull(), false);
QCOMPARE(secondPlanetElement.parentNode() == rootElement, true);
QCOMPARE(secondPlanetElement.parentNode() != rootElement, false);
QCOMPARE(secondPlanetElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0);
QCOMPARE(secondPlanetElement.firstChild().isNull(), true);
QCOMPARE(secondPlanetElement.lastChild().isNull(), true);
QCOMPARE(secondPlanetElement.tagName(), QString("venus"));
QCOMPARE(secondPlanetElement.prefix().isNull(), true);
}
void TestXmlReader::testRootError()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
// multiple root nodes are not valid !
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth></earth><moon></moon>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false);
QCOMPARE(errorMsg.isEmpty(), false);
QCOMPARE(errorMsg, QCoreApplication::translate("QXmlStream", "Extra content at end of document."));
QCOMPARE(errorLine, 1);
QCOMPARE(errorColumn, 21);
}
void TestXmlReader::testMismatchedTag()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth></e>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false);
QCOMPARE(errorMsg.isEmpty(), false);
QCOMPARE(errorMsg, QCoreApplication::translate("QXmlStream", "Opening and ending tag mismatch."));
QCOMPARE(errorLine, 1);
QCOMPARE(errorColumn, 11);
}
void TestXmlReader::testConvertQDomDocument()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<solarsystem star=\"sun\">";
xmlstream << " <mercurius/>\n";
xmlstream << " <venus/>\n";
xmlstream << " <earth habitable=\"true\"><p>The best place</p>";
xmlstream << " <moon habitable=\"possible\"/>\n";
xmlstream << " </earth>\n";
xmlstream << " <mars/>\n";
xmlstream << " <jupiter/>\n";
xmlstream << "</solarsystem>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// <solarsystem>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 11);
QCOMPARE(rootElement.tagName(), QString("solarsystem"));
QCOMPARE(rootElement.prefix().isNull(), true);
// now test converting KoXmlDocument to QDomDocument
QDomDocument universeDoc = KoXml::asQDomDocument(doc);
// <solarsystem>
QDomElement solarSystemElement = universeDoc.documentElement();
QCOMPARE(solarSystemElement.isNull(), false);
QCOMPARE(solarSystemElement.isElement(), true);
QCOMPARE(solarSystemElement.parentNode().isNull(), false);
QCOMPARE(solarSystemElement.hasChildNodes(), true);
QCOMPARE(solarSystemElement.tagName(), QString("solarsystem"));
QCOMPARE(solarSystemElement.prefix().isNull(), true);
// <earth>
QDomElement earthElement = solarSystemElement.namedItem("earth").toElement();
QCOMPARE(earthElement.isNull(), false);
QCOMPARE(earthElement.isElement(), true);
QCOMPARE(earthElement.parentNode().isNull(), false);
QCOMPARE(earthElement.hasAttribute("habitable"), true);
QCOMPARE(earthElement.hasChildNodes(), true);
QCOMPARE(earthElement.tagName(), QString("earth"));
QCOMPARE(earthElement.prefix().isNull(), true);
// <p> in <earth>
QDomNode placeNode = earthElement.firstChild();
QCOMPARE(placeNode.isNull(), false);
QCOMPARE(placeNode.isElement(), true);
QCOMPARE(placeNode.toElement().text(), QString("The best place"));
QCOMPARE(placeNode.nextSibling().isNull(), false);
QCOMPARE(placeNode.previousSibling().isNull(), true);
QCOMPARE(placeNode.parentNode().isNull(), false);
QCOMPARE(placeNode.parentNode() == earthElement, true);
QCOMPARE(placeNode.hasChildNodes(), true);
QCOMPARE(placeNode.childNodes().count(), 1);
//printf("Result:\n%s\n\n", qPrintable(universeDoc.toString()));
}
void TestXmlReader::testConvertQDomElement()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<universe>";
xmlstream << " <solarsystem star=\"sun\">";
xmlstream << " <mercurius/>\n";
xmlstream << " <venus/>\n";
xmlstream << " <earth habitable=\"true\"><p>The best place</p>";
xmlstream << " <moon habitable=\"possible\"/>\n";
xmlstream << " </earth>\n";
xmlstream << " <mars/>\n";
xmlstream << " <jupiter/>\n";
xmlstream << " </solarsystem>";
xmlstream << "</universe>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// <universe>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 2);
QCOMPARE(rootElement.tagName(), QString("universe"));
QCOMPARE(rootElement.prefix().isNull(), true);
// now test converting KoXmlElement to QDomElement
QDomDocument solarDoc;
KoXml::asQDomElement(solarDoc, rootElement.firstChildElement());
// <solarsystem>
QDomElement solarSystemElement = solarDoc.documentElement();
QCOMPARE(solarSystemElement.isNull(), false);
QCOMPARE(solarSystemElement.isElement(), true);
QCOMPARE(solarSystemElement.parentNode().isNull(), false);
QCOMPARE(solarSystemElement.hasChildNodes(), true);
QCOMPARE(solarSystemElement.tagName(), QString("solarsystem"));
QCOMPARE(solarSystemElement.prefix().isNull(), true);
// <earth>
QDomElement earthElement = solarSystemElement.namedItem("earth").toElement();
QCOMPARE(earthElement.isNull(), false);
QCOMPARE(earthElement.isElement(), true);
QCOMPARE(earthElement.parentNode().isNull(), false);
QCOMPARE(earthElement.hasAttribute("habitable"), true);
QCOMPARE(earthElement.hasChildNodes(), true);
QCOMPARE(earthElement.tagName(), QString("earth"));
QCOMPARE(earthElement.prefix().isNull(), true);
// <p> in <earth>
QDomNode placeNode = earthElement.firstChild();
QCOMPARE(placeNode.isNull(), false);
QCOMPARE(placeNode.isElement(), true);
QCOMPARE(placeNode.toElement().text(), QString("The best place"));
QCOMPARE(placeNode.nextSibling().isNull(), false);
QCOMPARE(placeNode.previousSibling().isNull(), true);
QCOMPARE(placeNode.parentNode().isNull(), false);
QCOMPARE(placeNode.parentNode() == earthElement, true);
QCOMPARE(placeNode.hasChildNodes(), true);
QCOMPARE(placeNode.childNodes().count(), 1);
//printf("Result:\n%s\n\n", qPrintable(universeDoc.toString()));
}
void TestXmlReader::testSimpleOpenDocumentText()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument text
// it has only paragraph "Hello, world!"
// automatic styles, declarations and unnecessary namespaces are omitted.
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"";
xmlstream << " xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"";
xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << " office:version=\"1.0\">";
xmlstream << " <office:automatic-styles/>";
xmlstream << " <office:body>";
xmlstream << " <office:text>";
xmlstream << " <text:p text:style-name=\"Standard\">Hello, world!</text:p>";
xmlstream << " </office:text>";
xmlstream << " </office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.parentNode().isNull(), false);
QCOMPARE(contentElement.parentNode().toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(contentElement), 4);
QCOMPARE(contentElement.firstChild().isNull(), false);
QCOMPARE(contentElement.lastChild().isNull(), false);
QCOMPARE(contentElement.previousSibling().isNull(), false);
QCOMPARE(contentElement.nextSibling().isNull(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true);
QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0"));
// <office:automatic-styles>
KoXmlElement stylesElement;
stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles");
QCOMPARE(stylesElement.isNull(), false);
QCOMPARE(stylesElement.isElement(), true);
QCOMPARE(stylesElement.parentNode().isNull(), false);
QCOMPARE(stylesElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(stylesElement), 0);
QCOMPARE(stylesElement.firstChild().isNull(), true);
QCOMPARE(stylesElement.lastChild().isNull(), true);
QCOMPARE(stylesElement.previousSibling().isNull(), false);
QCOMPARE(stylesElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(stylesElement.nextSibling().isNull(), false);
QCOMPARE(stylesElement.localName(), QString("automatic-styles"));
// also same <office:automatic-styles>, but without namedItemNS
KoXmlNode styles2Element;
styles2Element = contentElement.firstChildElement();
QCOMPARE(styles2Element.isNull(), false);
QCOMPARE(styles2Element.isElement(), true);
QCOMPARE(styles2Element.parentNode().isNull(), false);
QCOMPARE(styles2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(styles2Element), 0);
QCOMPARE(styles2Element.firstChild().isNull(), true);
QCOMPARE(styles2Element.lastChild().isNull(), true);
QCOMPARE(styles2Element.previousSibling().isNull(), false);
QCOMPARE(styles2Element.previousSibling().previousSibling().isNull(), true);
QCOMPARE(styles2Element.nextSibling().isNull(), false);
QCOMPARE(styles2Element.localName(), QString("automatic-styles"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body");
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 3);
QCOMPARE(bodyElement.firstChild().isNull(), false);
QCOMPARE(bodyElement.lastChild().isNull(), false);
QCOMPARE(bodyElement.previousSibling().isNull(), false);
QCOMPARE(bodyElement.nextSibling().isNull(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
KoXmlNode whiteSpace = stylesElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// also same <office:body>, but without namedItemNS
KoXmlElement body2Element = whiteSpace.nextSibling().toElement();
QCOMPARE(body2Element.isNull(), false);
QCOMPARE(body2Element.isElement(), true);
QCOMPARE(body2Element.parentNode().isNull(), false);
QCOMPARE(body2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(body2Element), 3);
QCOMPARE(body2Element.firstChild().isNull(), false);
QCOMPARE(body2Element.lastChild().isNull(), false);
QCOMPARE(body2Element.previousSibling().isNull(), false);
QCOMPARE(body2Element.nextSibling().isNull(), true);
QCOMPARE(body2Element.localName(), QString("body"));
// <office:text>
KoXmlElement textElement;
textElement = KoXml::namedItemNS(bodyElement, officeNS, "text");
QCOMPARE(textElement.isNull(), false);
QCOMPARE(textElement.isElement(), true);
QCOMPARE(textElement.parentNode().isNull(), false);
QCOMPARE(textElement.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(textElement), 3);
QCOMPARE(textElement.firstChild().isNull(), false);
QCOMPARE(textElement.lastChild().isNull(), false);
QCOMPARE(textElement.previousSibling().isNull(), false);
QCOMPARE(textElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(textElement.nextSibling().isNull(), false);
QCOMPARE(textElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(textElement.localName(), QString("text"));
// the same <office:text>, but without namedItemNS
KoXmlElement text2Element;
text2Element = bodyElement.firstChildElement();
QCOMPARE(text2Element.isNull(), false);
QCOMPARE(text2Element.isElement(), true);
QCOMPARE(text2Element.parentNode().isNull(), false);
QCOMPARE(text2Element.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(text2Element), 3);
QCOMPARE(text2Element.firstChild().isNull(), false);
QCOMPARE(text2Element.lastChild().isNull(), false);
QCOMPARE(text2Element.previousSibling().isNull(), false);
QCOMPARE(text2Element.previousSibling().previousSibling().isNull(), true);
QCOMPARE(text2Element.nextSibling().isNull(), false);
QCOMPARE(text2Element.nextSibling().nextSibling().isNull(), true);
QCOMPARE(text2Element.localName(), QString("text"));
// <text:p>
KoXmlElement parElement;
parElement = textElement.firstChildElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode() == textElement, true);
QCOMPARE(KoXml::childNodesCount(parElement), 1);
QCOMPARE(parElement.firstChild().isNull(), false);
QCOMPARE(parElement.lastChild().isNull(), false);
QCOMPARE(parElement.previousSibling().isNull(), false);
QCOMPARE(parElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(parElement.nextSibling().isNull(), false);
QCOMPARE(parElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.text(), QString("Hello, world!"));
QCOMPARE(parElement.attributeNS(QString(textNS), "style-name", ""), QString("Standard"));
}
void TestXmlReader::testWhitespace()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml for testing paragraphs with whitespace
/* The list of elements for which whitespace should be preserved can be
obtained from the Relax NG schema with these commands:
cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \
-N s="http://relaxng.org/ns/structure/1.0" -t -m '//s:text' \
-v '../@name' -n |grep :
cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \
-N s="http://relaxng.org/ns/structure/1.0" \
-t -m "//s:ref[@name='paragraph-content']" -v '../../@name' -n |grep :
*/
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"";
xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\">";
xmlstream << " <text:p> </text:p>";
xmlstream << " <text:p> <text:span/> </text:p>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement p1;
p1 = doc.documentElement().firstChildElement();
QCOMPARE(p1.isNull(), false);
QCOMPARE(p1.isElement(), true);
QCOMPARE(KoXml::childNodesCount(p1), 1);
KoXmlNode whiteSpace = p1.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
KoXmlElement p2;
p2 = whiteSpace.nextSibling().toElement();
QCOMPARE(p2.isNull(), false);
QCOMPARE(p2.isElement(), true);
QCOMPARE(KoXml::childNodesCount(p2), 3);
}
void TestXmlReader::testSimpleOpenDocumentSpreadsheet()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument spreadsheet
// the document has three worksheets, the last two are empty.
// on the first sheet, cell A1 contains the text "Hello, world".
// automatic styles, font declarations and unnecessary namespaces are omitted.
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << "xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" ";
xmlstream << "xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << "xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\">";
xmlstream << "<office:body>";
xmlstream << "<office:spreadsheet>";
xmlstream << "<table:table table:name=\"Sheet1\" table:style-name=\"ta1\" table:print=\"false\">";
xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>";
xmlstream << "<table:table-row table:style-name=\"ro1\">";
xmlstream << "<table:table-cell office:value-type=\"string\">";
xmlstream << "<text:p>Hello, world</text:p>";
xmlstream << "</table:table-cell>";
xmlstream << "</table:table-row>";
xmlstream << "</table:table>";
xmlstream << "<table:table table:name=\"Sheet2\" table:style-name=\"ta1\" table:print=\"false\">";
xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>";
xmlstream << "<table:table-row table:style-name=\"ro1\">";
xmlstream << "<table:table-cell/>";
xmlstream << "</table:table-row>";
xmlstream << "</table:table>";
xmlstream << "<table:table table:name=\"Sheet3\" table:style-name=\"ta1\" table:print=\"false\">";
xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>";
xmlstream << "<table:table-row table:style-name=\"ro1\">";
xmlstream << "<table:table-cell/>";
xmlstream << "</table:table-row>";
xmlstream << "</table:table>";
xmlstream << "</office:spreadsheet>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.parentNode().isNull(), false);
QCOMPARE(contentElement.parentNode().toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(contentElement), 1);
QCOMPARE(contentElement.firstChild().isNull(), false);
QCOMPARE(contentElement.lastChild().isNull(), false);
QCOMPARE(contentElement.previousSibling().isNull(), false);
QCOMPARE(contentElement.nextSibling().isNull(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = contentElement.firstChild().toElement();
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 1);
QCOMPARE(bodyElement.firstChild().isNull(), false);
QCOMPARE(bodyElement.lastChild().isNull(), false);
QCOMPARE(bodyElement.previousSibling().isNull(), true);
QCOMPARE(bodyElement.nextSibling().isNull(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
// <office:spreadsheet>
KoXmlElement spreadsheetElement;
spreadsheetElement = bodyElement.firstChild().toElement();
QCOMPARE(spreadsheetElement.isNull(), false);
QCOMPARE(spreadsheetElement.isElement(), true);
QCOMPARE(spreadsheetElement.parentNode().isNull(), false);
QCOMPARE(spreadsheetElement.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(spreadsheetElement), 3);
QCOMPARE(spreadsheetElement.firstChild().isNull(), false);
QCOMPARE(spreadsheetElement.lastChild().isNull(), false);
QCOMPARE(spreadsheetElement.previousSibling().isNull(), true);
QCOMPARE(spreadsheetElement.nextSibling().isNull(), true);
QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet"));
// <table:table> for Sheet1
KoXmlElement sheet1Element;
sheet1Element = spreadsheetElement.firstChild().toElement();
QCOMPARE(sheet1Element.isNull(), false);
QCOMPARE(sheet1Element.isElement(), true);
QCOMPARE(sheet1Element.parentNode().isNull(), false);
QCOMPARE(sheet1Element.parentNode() == spreadsheetElement, true);
QCOMPARE(KoXml::childNodesCount(sheet1Element), 2);
QCOMPARE(sheet1Element.firstChild().isNull(), false);
QCOMPARE(sheet1Element.lastChild().isNull(), false);
QCOMPARE(sheet1Element.previousSibling().isNull(), true);
QCOMPARE(sheet1Element.nextSibling().isNull(), false);
QCOMPARE(sheet1Element.tagName(), QString("table"));
QCOMPARE(sheet1Element.hasAttributeNS(tableNS, "name"), true);
QCOMPARE(sheet1Element.attributeNS(tableNS, "name", ""), QString("Sheet1"));
QCOMPARE(sheet1Element.attributeNS(tableNS, "style-name", ""), QString("ta1"));
QCOMPARE(sheet1Element.attributeNS(tableNS, "print", ""), QString("false"));
// KoXml::load( sheet1Element, 100 );
// <table:table-column>
KoXmlElement columnElement;
columnElement = sheet1Element.firstChild().toElement();
QCOMPARE(columnElement.isNull(), false);
QCOMPARE(columnElement.isElement(), true);
QCOMPARE(columnElement.parentNode().isNull(), false);
QCOMPARE(columnElement.parentNode() == sheet1Element, true);
QCOMPARE(KoXml::childNodesCount(columnElement), 0);
QCOMPARE(columnElement.firstChild().isNull(), true);
QCOMPARE(columnElement.lastChild().isNull(), true);
QCOMPARE(columnElement.previousSibling().isNull(), true);
QCOMPARE(columnElement.nextSibling().isNull(), false);
QCOMPARE(columnElement.tagName(), QString("table-column"));
QCOMPARE(columnElement.attributeNS(tableNS, "style-name", ""), QString("co1"));
QCOMPARE(columnElement.attributeNS(tableNS, "default-cell-style-name", ""), QString("Default"));
// <table:table-row>
KoXmlElement rowElement;
rowElement = columnElement.nextSibling().toElement();
QCOMPARE(rowElement.isNull(), false);
QCOMPARE(rowElement.isElement(), true);
QCOMPARE(rowElement.parentNode().isNull(), false);
QCOMPARE(rowElement.parentNode() == sheet1Element, true);
QCOMPARE(KoXml::childNodesCount(rowElement), 1);
QCOMPARE(rowElement.firstChild().isNull(), false);
QCOMPARE(rowElement.lastChild().isNull(), false);
QCOMPARE(rowElement.previousSibling().isNull(), false);
QCOMPARE(rowElement.nextSibling().isNull(), true);
QCOMPARE(rowElement.tagName(), QString("table-row"));
QCOMPARE(rowElement.attributeNS(tableNS, "style-name", ""), QString("ro1"));
// <table:table-cell>
KoXmlElement cellElement;
cellElement = rowElement.firstChild().toElement();
QCOMPARE(cellElement.isNull(), false);
QCOMPARE(cellElement.isElement(), true);
QCOMPARE(cellElement.parentNode().isNull(), false);
QCOMPARE(cellElement.parentNode() == rowElement, true);
QCOMPARE(KoXml::childNodesCount(cellElement), 1);
QCOMPARE(cellElement.firstChild().isNull(), false);
QCOMPARE(cellElement.lastChild().isNull(), false);
QCOMPARE(cellElement.previousSibling().isNull(), true);
QCOMPARE(cellElement.nextSibling().isNull(), true);
QCOMPARE(cellElement.tagName(), QString("table-cell"));
QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string"));
// <text:p>
KoXmlElement parElement;
parElement = cellElement.firstChild().toElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode() == cellElement, true);
QCOMPARE(KoXml::childNodesCount(parElement), 1);
QCOMPARE(parElement.firstChild().isNull(), false);
QCOMPARE(parElement.lastChild().isNull(), false);
QCOMPARE(parElement.previousSibling().isNull(), true);
QCOMPARE(parElement.nextSibling().isNull(), true);
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.text(), QString("Hello, world"));
// <table:table> for Sheet2
KoXmlElement sheet2Element;
sheet2Element = sheet1Element.nextSibling().toElement();
QCOMPARE(sheet2Element.isNull(), false);
QCOMPARE(sheet2Element.isElement(), true);
QCOMPARE(sheet2Element.parentNode().isNull(), false);
QCOMPARE(sheet2Element.parentNode() == spreadsheetElement, true);
QCOMPARE(KoXml::childNodesCount(sheet2Element), 2);
QCOMPARE(sheet2Element.firstChild().isNull(), false);
QCOMPARE(sheet2Element.lastChild().isNull(), false);
QCOMPARE(sheet2Element.previousSibling().isNull(), false);
QCOMPARE(sheet2Element.nextSibling().isNull(), false);
QCOMPARE(sheet2Element.tagName(), QString("table"));
// </table:table> for Sheet3
KoXmlElement sheet3Element;
sheet3Element = sheet2Element.nextSibling().toElement();
QCOMPARE(sheet3Element.isNull(), false);
QCOMPARE(sheet3Element.isElement(), true);
QCOMPARE(sheet3Element.parentNode().isNull(), false);
QCOMPARE(sheet3Element.parentNode() == spreadsheetElement, true);
QCOMPARE(KoXml::childNodesCount(sheet3Element), 2);
QCOMPARE(sheet3Element.firstChild().isNull(), false);
QCOMPARE(sheet3Element.lastChild().isNull(), false);
QCOMPARE(sheet3Element.previousSibling().isNull(), false);
QCOMPARE(sheet3Element.nextSibling().isNull(), true);
QCOMPARE(sheet3Element.tagName(), QString("table"));
}
void TestXmlReader::testSimpleOpenDocumentPresentation()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument presentation
// styles, declarations and unnecessary namespaces are omitted
// the first page is "Title" and has two text boxes
// the second page is
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" ";
xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << " xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" ";
xmlstream << " xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" ";
xmlstream << " xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" ";
xmlstream << " office:version=\"1.0\">";
xmlstream << " <office:scripts/>";
xmlstream << " <office:automatic-styles/>";
xmlstream << " <office:body>";
xmlstream << " <office:presentation>";
xmlstream << " <draw:page draw:name=\"Title\" draw:style-name=\"dp1\" ";
xmlstream << " draw:master-page-name=\"lyt-cool\" ";
xmlstream << " presentation:presentation-page-layout-name=\"AL1T0\">";
xmlstream << " <draw:frame presentation:style-name=\"pr1\" ";
xmlstream << " draw:text-style-name=\"P2\" draw:layer=\"layout\" ";
xmlstream << " svg:width=\"23.912cm\" svg:height=\"3.508cm\" ";
xmlstream << " svg:x=\"2.058cm\" svg:y=\"1.543cm\" ";
xmlstream << " presentation:class=\"title\" ";
xmlstream << " presentation:user-transformed=\"true\">";
xmlstream << " <draw:text-box>";
xmlstream << " <text:p text:style-name=\"P1\">Foobar</text:p>";
xmlstream << " </draw:text-box>";
xmlstream << " </draw:frame>";
xmlstream << " <draw:frame presentation:style-name=\"pr2\" ";
xmlstream << " draw:text-style-name=\"P3\" draw:layer=\"layout\"";
xmlstream << " svg:width=\"23.912cm\" svg:height=\"13.231cm\"";
xmlstream << " svg:x=\"2.058cm\" svg:y=\"5.838cm\" ";
xmlstream << " presentation:class=\"subtitle\">";
xmlstream << " <draw:text-box>";
xmlstream << " <text:p text:style-name=\"P3\">Foo</text:p>";
xmlstream << " </draw:text-box>";
xmlstream << " </draw:frame>";
xmlstream << " <presentation:notes draw:style-name=\"dp2\">";
xmlstream << " <draw:page-thumbnail draw:style-name=\"gr1\" draw:layer=\"layout\" svg:width=\"13.706cm\" svg:height=\"10.28cm\" svg:x=\"3.647cm\" svg:y=\"2.853cm\" draw:page-number=\"1\" presentation:class=\"page\"/>";
xmlstream << " <draw:frame presentation:style-name=\"pr3\" draw:text-style-name=\"P1\" draw:layer=\"layout\" svg:width=\"14.518cm\" svg:height=\"11.411cm\" svg:x=\"3.249cm\" svg:y=\"14.13cm\" presentation:class=\"notes\" presentation:placeholder=\"true\">";
xmlstream << " <draw:text-box/>";
xmlstream << " </draw:frame>";
xmlstream << " </presentation:notes>";
xmlstream << " </draw:page>";
xmlstream << " <presentation:settings presentation:stay-on-top=\"true\"/>";
xmlstream << " </office:presentation>";
xmlstream << " </office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
const char* drawNS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0";
const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
const char* presentationNS = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0";
const char* svgNS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.parentNode().isNull(), false);
QCOMPARE(contentElement.parentNode().toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(contentElement), 6);
QCOMPARE(contentElement.firstChild().isNull(), false);
QCOMPARE(contentElement.lastChild().isNull(), false);
QCOMPARE(contentElement.previousSibling().isNull(), false);
QCOMPARE(contentElement.nextSibling().isNull(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true);
QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0"));
// <office:scripts>
KoXmlElement scriptsElement;
scriptsElement = KoXml::namedItemNS(contentElement, officeNS, "scripts");
QCOMPARE(scriptsElement.isNull(), false);
QCOMPARE(scriptsElement.isElement(), true);
QCOMPARE(scriptsElement.parentNode().isNull(), false);
QCOMPARE(scriptsElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(scriptsElement), 0);
QCOMPARE(scriptsElement.firstChild().isNull(), true);
QCOMPARE(scriptsElement.lastChild().isNull(), true);
QCOMPARE(scriptsElement.previousSibling().isNull(), false);
QCOMPARE(scriptsElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(scriptsElement.nextSibling().isNull(), false);
QCOMPARE(scriptsElement.localName(), QString("scripts"));
// <office:automatic-styles>
KoXmlElement stylesElement;
stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles");
QCOMPARE(stylesElement.isNull(), false);
QCOMPARE(stylesElement.isElement(), true);
QCOMPARE(stylesElement.parentNode().isNull(), false);
QCOMPARE(stylesElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(stylesElement), 0);
QCOMPARE(stylesElement.firstChild().isNull(), true);
QCOMPARE(stylesElement.lastChild().isNull(), true);
QCOMPARE(stylesElement.previousSibling().isNull(), false);
QCOMPARE(stylesElement.nextSibling().isNull(), false);
QCOMPARE(stylesElement.localName(), QString("automatic-styles"));
KoXmlNode whiteSpace = scriptsElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// also same <office:automatic-styles>, but without namedItemNS
KoXmlNode styles2Element;
styles2Element = whiteSpace.nextSibling().toElement();
QCOMPARE(styles2Element.isNull(), false);
QCOMPARE(styles2Element.isElement(), true);
QCOMPARE(styles2Element.parentNode().isNull(), false);
QCOMPARE(styles2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(styles2Element), 0);
QCOMPARE(styles2Element.firstChild().isNull(), true);
QCOMPARE(styles2Element.lastChild().isNull(), true);
QCOMPARE(styles2Element.previousSibling().isNull(), false);
QCOMPARE(styles2Element.nextSibling().isNull(), false);
QCOMPARE(styles2Element.localName(), QString("automatic-styles"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body");
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 3);
QCOMPARE(bodyElement.firstChild().isNull(), false);
QCOMPARE(bodyElement.lastChild().isNull(), false);
QCOMPARE(bodyElement.previousSibling().isNull(), false);
QCOMPARE(bodyElement.nextSibling().isNull(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
whiteSpace = stylesElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// also same <office:body>, but without namedItemNS
KoXmlElement body2Element;
body2Element = whiteSpace.nextSibling().toElement();
QCOMPARE(body2Element.isNull(), false);
QCOMPARE(body2Element.isElement(), true);
QCOMPARE(body2Element.parentNode().isNull(), false);
QCOMPARE(body2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(body2Element), 3);
QCOMPARE(body2Element.firstChild().isNull(), false);
QCOMPARE(body2Element.lastChild().isNull(), false);
QCOMPARE(body2Element.previousSibling().isNull(), false);
QCOMPARE(body2Element.nextSibling().isNull(), true);
QCOMPARE(body2Element.localName(), QString("body"));
// <office:presentation>
KoXmlElement presentationElement;
presentationElement = KoXml::namedItemNS(bodyElement, officeNS, "presentation");
QCOMPARE(presentationElement.isNull(), false);
QCOMPARE(presentationElement.isElement(), true);
QCOMPARE(presentationElement.parentNode().isNull(), false);
QCOMPARE(presentationElement.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(presentationElement), 5);
QCOMPARE(presentationElement.firstChild().isNull(), false);
QCOMPARE(presentationElement.lastChild().isNull(), false);
QCOMPARE(presentationElement.previousSibling().isNull(), false);
QCOMPARE(presentationElement.previousSibling().isText(), true);
QCOMPARE(presentationElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(presentationElement.nextSibling().isNull(), false);
QCOMPARE(presentationElement.nextSibling().isText(), true);
QCOMPARE(presentationElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(presentationElement.localName(), QString("presentation"));
// the same <office:presentation>, but without namedItemNS
KoXmlElement presentation2Element;
presentation2Element = bodyElement.firstChildElement();
QCOMPARE(presentation2Element.isNull(), false);
QCOMPARE(presentation2Element.isElement(), true);
QCOMPARE(presentation2Element.parentNode().isNull(), false);
QCOMPARE(presentation2Element.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(presentation2Element), 5);
QCOMPARE(presentation2Element.firstChild().isNull(), false);
QCOMPARE(presentation2Element.lastChild().isNull(), false);
QCOMPARE(presentation2Element.previousSibling().isNull(), false);
QCOMPARE(presentation2Element.previousSibling().isText(), true);
QCOMPARE(presentation2Element.previousSibling().previousSibling().isNull(), true);
QCOMPARE(presentation2Element.nextSibling().isNull(), false);
QCOMPARE(presentation2Element.nextSibling().isText(), true);
QCOMPARE(presentation2Element.nextSibling().nextSibling().isNull(), true);
QCOMPARE(presentation2Element.localName(), QString("presentation"));
// <draw:page> for "Title"
KoXmlElement titlePageElement;
titlePageElement = presentationElement.firstChildElement();
QCOMPARE(titlePageElement.isNull(), false);
QCOMPARE(titlePageElement.isElement(), true);
QCOMPARE(titlePageElement.parentNode().isNull(), false);
QCOMPARE(titlePageElement.parentNode() == presentationElement, true);
QCOMPARE(KoXml::childNodesCount(titlePageElement), 7);
QCOMPARE(titlePageElement.firstChild().isNull(), false);
QCOMPARE(titlePageElement.lastChild().isNull(), false);
QCOMPARE(titlePageElement.previousSibling().isNull(), false);
QCOMPARE(titlePageElement.previousSibling().isText(), true);
QCOMPARE(titlePageElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(titlePageElement.nextSibling().isNull(), false);
QCOMPARE(titlePageElement.localName(), QString("page"));
QCOMPARE(titlePageElement.attributeNS(drawNS, "name", ""), QString("Title"));
QCOMPARE(titlePageElement.attributeNS(drawNS, "style-name", ""), QString("dp1"));
QCOMPARE(titlePageElement.attributeNS(drawNS, "master-page-name", ""), QString("lyt-cool"));
QCOMPARE(titlePageElement.attributeNS(presentationNS,
"presentation-page-layout-name", ""), QString("AL1T0"));
// <draw:frame> for the title frame
KoXmlElement titleFrameElement;
titleFrameElement = titlePageElement.firstChildElement();
QCOMPARE(titleFrameElement.isNull(), false);
QCOMPARE(titleFrameElement.isElement(), true);
QCOMPARE(titleFrameElement.parentNode().isNull(), false);
QCOMPARE(titleFrameElement.parentNode() == titlePageElement, true);
QCOMPARE(KoXml::childNodesCount(titleFrameElement), 3);
QCOMPARE(titleFrameElement.firstChild().isNull(), false);
QCOMPARE(titleFrameElement.lastChild().isNull(), false);
QCOMPARE(titleFrameElement.previousSibling().isNull(), false);
QCOMPARE(titleFrameElement.previousSibling().isText(), true);
QCOMPARE(titleFrameElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(titleFrameElement.nextSibling().isNull(), false);
QCOMPARE(titleFrameElement.localName(), QString("frame"));
QCOMPARE(titleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr1"));
QCOMPARE(titleFrameElement.attributeNS(presentationNS, "class", ""), QString("title"));
QCOMPARE(titleFrameElement.attributeNS(presentationNS, "user-transformed", ""), QString("true"));
QCOMPARE(titleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P2"));
QCOMPARE(titleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "height", ""), QString("3.508cm"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "y", ""), QString("1.543cm"));
// <draw:text-box> of the title frame
KoXmlElement titleBoxElement;
titleBoxElement = titleFrameElement.firstChildElement();
QCOMPARE(titleBoxElement.isNull(), false);
QCOMPARE(titleBoxElement.isElement(), true);
QCOMPARE(titleBoxElement.parentNode().isNull(), false);
QCOMPARE(titleBoxElement.parentNode() == titleFrameElement, true);
QCOMPARE(KoXml::childNodesCount(titleBoxElement), 3);
QCOMPARE(titleBoxElement.firstChild().isNull(), false);
QCOMPARE(titleBoxElement.lastChild().isNull(), false);
QCOMPARE(titleBoxElement.previousSibling().isNull(), false);
QCOMPARE(titleBoxElement.previousSibling().isText(), true);
QCOMPARE(titleBoxElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(titleBoxElement.nextSibling().isNull(), false);
QCOMPARE(titleBoxElement.nextSibling().isText(), true);
QCOMPARE(titleBoxElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(titleBoxElement.localName(), QString("text-box"));
// <text:p> for the title text-box
KoXmlElement titleParElement;
titleParElement = titleBoxElement.firstChildElement();
QCOMPARE(titleParElement.isNull(), false);
QCOMPARE(titleParElement.isElement(), true);
QCOMPARE(titleParElement.parentNode().isNull(), false);
QCOMPARE(titleParElement.parentNode() == titleBoxElement, true);
QCOMPARE(KoXml::childNodesCount(titleParElement), 1);
QCOMPARE(titleParElement.firstChild().isNull(), false);
QCOMPARE(titleParElement.lastChild().isNull(), false);
QCOMPARE(titleParElement.previousSibling().isNull(), false);
QCOMPARE(titleParElement.previousSibling().isText(), true);
QCOMPARE(titleParElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(titleParElement.nextSibling().isNull(), false);
QCOMPARE(titleParElement.nextSibling().isText(), true);
QCOMPARE(titleParElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(titleParElement.localName(), QString("p"));
QCOMPARE(titleParElement.attributeNS(textNS, "style-name", ""), QString("P1"));
QCOMPARE(titleParElement.text(), QString("Foobar"));
whiteSpace = titleFrameElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// <draw:frame> for the subtitle frame
KoXmlElement subtitleFrameElement;
subtitleFrameElement = whiteSpace.nextSibling().toElement();
QCOMPARE(subtitleFrameElement.isNull(), false);
QCOMPARE(subtitleFrameElement.isElement(), true);
QCOMPARE(subtitleFrameElement.parentNode().isNull(), false);
QCOMPARE(subtitleFrameElement.parentNode() == titlePageElement, true);
QCOMPARE(KoXml::childNodesCount(subtitleFrameElement), 3);
QCOMPARE(subtitleFrameElement.firstChild().isNull(), false);
QCOMPARE(subtitleFrameElement.lastChild().isNull(), false);
QCOMPARE(subtitleFrameElement.previousSibling().isNull(), false);
QCOMPARE(subtitleFrameElement.nextSibling().isNull(), false);
QCOMPARE(subtitleFrameElement.localName(), QString("frame"));
QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr2"));
QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "class", ""), QString("subtitle"));
QCOMPARE(subtitleFrameElement.hasAttributeNS(presentationNS, "user-transformed"), false);
QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P3"));
QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "height", ""), QString("13.231cm"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "y", ""), QString("5.838cm"));
// <draw:text-box> of the subtitle frame
KoXmlElement subtitleBoxElement;
subtitleBoxElement = subtitleFrameElement.firstChildElement();
QCOMPARE(subtitleBoxElement.isNull(), false);
QCOMPARE(subtitleBoxElement.isElement(), true);
QCOMPARE(subtitleBoxElement.parentNode().isNull(), false);
QCOMPARE(subtitleBoxElement.parentNode() == subtitleFrameElement, true);
QCOMPARE(KoXml::childNodesCount(subtitleBoxElement), 3);
QCOMPARE(subtitleBoxElement.firstChild().isNull(), false);
QCOMPARE(subtitleBoxElement.lastChild().isNull(), false);
QCOMPARE(subtitleBoxElement.previousSibling().isNull(), false);
QCOMPARE(subtitleBoxElement.previousSibling().isText(), true);
QCOMPARE(subtitleBoxElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(subtitleBoxElement.nextSibling().isNull(), false);
QCOMPARE(subtitleBoxElement.nextSibling().isText(), true);
QCOMPARE(subtitleBoxElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(subtitleBoxElement.localName(), QString("text-box"));
// <text:p> for the subtitle text-box
KoXmlElement subtitleParElement;
subtitleParElement = subtitleBoxElement.firstChildElement();
QCOMPARE(subtitleParElement.isNull(), false);
QCOMPARE(subtitleParElement.isElement(), true);
QCOMPARE(subtitleParElement.parentNode().isNull(), false);
QCOMPARE(subtitleParElement.parentNode() == subtitleBoxElement, true);
QCOMPARE(KoXml::childNodesCount(subtitleParElement), 1);
QCOMPARE(subtitleParElement.firstChild().isNull(), false);
QCOMPARE(subtitleParElement.lastChild().isNull(), false);
QCOMPARE(subtitleParElement.previousSibling().isNull(), false);
QCOMPARE(subtitleParElement.previousSibling().isText(), true);
QCOMPARE(subtitleParElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(subtitleParElement.nextSibling().isNull(), false);
QCOMPARE(subtitleParElement.nextSibling().isText(), true);
QCOMPARE(subtitleParElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(subtitleParElement.localName(), QString("p"));
QCOMPARE(subtitleParElement.attributeNS(textNS, "style-name", ""), QString("P3"));
QCOMPARE(subtitleParElement.text(), QString("Foo"));
}
void TestXmlReader::testSimpleOpenDocumentFormula()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument formula
// this is essentially MathML
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<!DOCTYPE math:math PUBLIC \"-//OpenOffice.org//DTD Modified W3C MathML 1.01//EN\" \"math.dtd\">";
xmlstream << "<math:math xmlns:math=\"http://www.w3.org/1998/Math/MathML\">";
xmlstream << " <math:semantics>";
xmlstream << " <math:mrow>";
xmlstream << " <math:mi>E</math:mi>";
xmlstream << " <math:mo math:stretchy=\"false\">=</math:mo>";
xmlstream << " <math:msup>";
xmlstream << " <math:mi math:fontstyle=\"italic\">mc</math:mi>";
xmlstream << " <math:mn>2</math:mn>";
xmlstream << " </math:msup>";
xmlstream << " </math:mrow>";
xmlstream << " <math:annotation math:encoding=\"StarMath 5.0\">E = mc^2 </math:annotation>";
xmlstream << " </math:semantics>";
xmlstream << "</math:math>";
xmldevice.close();
KoXmlDocument doc;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* mathNS = "http://www.w3.org/1998/Math/MathML";
// <math:math>
KoXmlElement mathElement;
mathElement = doc.documentElement();
QCOMPARE(mathElement.isNull(), false);
QCOMPARE(mathElement.isElement(), true);
QCOMPARE(mathElement.parentNode().isNull(), false);
QCOMPARE(mathElement.parentNode().toDocument() == doc, true);
QCOMPARE(mathElement.firstChild().isNull(), false);
QCOMPARE(mathElement.lastChild().isNull(), false);
QCOMPARE(mathElement.previousSibling().isNull(), false);
QCOMPARE(mathElement.nextSibling().isNull(), true);
QCOMPARE(mathElement.localName(), QString("math"));
// <math:semantics>
KoXmlElement semanticsElement;
semanticsElement = KoXml::namedItemNS(mathElement, mathNS, "semantics");
QCOMPARE(semanticsElement.isNull(), false);
QCOMPARE(semanticsElement.isElement(), true);
QCOMPARE(semanticsElement.parentNode().isNull(), false);
QCOMPARE(semanticsElement.parentNode().toElement() == mathElement, true);
QCOMPARE(semanticsElement.firstChild().isNull(), false);
QCOMPARE(semanticsElement.lastChild().isNull(), false);
QCOMPARE(semanticsElement.previousSibling().isNull(), false);
QCOMPARE(semanticsElement.previousSibling().isText(), true);
QCOMPARE(semanticsElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(semanticsElement.nextSibling().isNull(), true);
QCOMPARE(semanticsElement.localName(), QString("semantics"));
// the same <math:semantics> but without namedItemNS
KoXmlElement semantics2Element;
semantics2Element = mathElement.firstChildElement();
QCOMPARE(semantics2Element.isNull(), false);
QCOMPARE(semantics2Element.isElement(), true);
QCOMPARE(semantics2Element.parentNode().isNull(), false);
QCOMPARE(semantics2Element.parentNode().toElement() == mathElement, true);
QCOMPARE(semantics2Element.firstChild().isNull(), false);
QCOMPARE(semantics2Element.lastChild().isNull(), false);
QCOMPARE(semantics2Element.previousSibling().isNull(), false);
QCOMPARE(semantics2Element.previousSibling().isText(), true);
QCOMPARE(semantics2Element.previousSibling().previousSibling().isNull(), true);
QCOMPARE(semantics2Element.nextSibling().isNull(), true);
QCOMPARE(semantics2Element.localName(), QString("semantics"));
// <math:mrow>
KoXmlElement mrowElement;
mrowElement = semanticsElement.firstChildElement();
QCOMPARE(mrowElement.isNull(), false);
QCOMPARE(mrowElement.isElement(), true);
QCOMPARE(mrowElement.parentNode().isNull(), false);
QCOMPARE(mrowElement.parentNode().toElement() == semanticsElement, true);
QCOMPARE(mrowElement.firstChild().isNull(), false);
QCOMPARE(mrowElement.lastChild().isNull(), false);
QCOMPARE(mrowElement.previousSibling().isNull(), false);
QCOMPARE(mrowElement.previousSibling().isText(), true);
QCOMPARE(mrowElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(mrowElement.nextSibling().isNull(), false);
QCOMPARE(mrowElement.localName(), QString("mrow"));
// <math:mi> for "E"
KoXmlElement miElement;
miElement = mrowElement.firstChildElement();
QCOMPARE(miElement.isNull(), false);
QCOMPARE(miElement.isElement(), true);
QCOMPARE(miElement.parentNode().isNull(), false);
QCOMPARE(miElement.parentNode().toElement() == mrowElement, true);
QCOMPARE(miElement.firstChild().isNull(), false);
QCOMPARE(miElement.lastChild().isNull(), false);
QCOMPARE(miElement.previousSibling().isNull(), false);
QCOMPARE(miElement.previousSibling().isText(), true);
QCOMPARE(miElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(miElement.nextSibling().isNull(), false);
QCOMPARE(miElement.localName(), QString("mi"));
KoXmlNode whiteSpace = miElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// <math:mo> for "="
KoXmlElement moElement;
moElement = whiteSpace.nextSibling().toElement();
QCOMPARE(moElement.isNull(), false);
QCOMPARE(moElement.isElement(), true);
QCOMPARE(moElement.parentNode().isNull(), false);
QCOMPARE(moElement.parentNode().toElement() == mrowElement, true);
QCOMPARE(moElement.firstChild().isNull(), false);
QCOMPARE(moElement.lastChild().isNull(), false);
QCOMPARE(moElement.previousSibling().isNull(), false);
QCOMPARE(moElement.nextSibling().isNull(), false);
QCOMPARE(moElement.localName(), QString("mo"));
QCOMPARE(moElement.attributeNS(mathNS, "stretchy", ""), QString("false"));
whiteSpace = moElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// <math:msup> for "mc" and superscripted "2"
KoXmlElement msupElement;
msupElement = whiteSpace.nextSibling().toElement();
QCOMPARE(msupElement.isNull(), false);
QCOMPARE(msupElement.isElement(), true);
QCOMPARE(msupElement.parentNode().isNull(), false);
QCOMPARE(msupElement.parentNode().toElement() == mrowElement, true);
QCOMPARE(msupElement.firstChild().isNull(), false);
QCOMPARE(msupElement.lastChild().isNull(), false);
QCOMPARE(msupElement.previousSibling().isNull(), false);
QCOMPARE(msupElement.previousSibling().isText(), true);
QCOMPARE(msupElement.previousSibling().previousSibling().isNull(), false);
QCOMPARE(msupElement.nextSibling().isNull(), false);
QCOMPARE(msupElement.nextSibling().isText(), true);
QCOMPARE(msupElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(msupElement.localName(), QString("msup"));
// <math:mi> inside the <math:msup> for "mc"
KoXmlElement mcElement;
mcElement = msupElement.firstChildElement();
QCOMPARE(mcElement.isNull(), false);
QCOMPARE(mcElement.isElement(), true);
QCOMPARE(mcElement.parentNode().isNull(), false);
QCOMPARE(mcElement.parentNode().toElement() == msupElement, true);
QCOMPARE(mcElement.firstChild().isNull(), false);
QCOMPARE(mcElement.lastChild().isNull(), false);
QCOMPARE(mcElement.previousSibling().isNull(), false);
QCOMPARE(mcElement.previousSibling().isText(), true);
QCOMPARE(mcElement.previousSibling().previousSibling().isNull(), true);
QCOMPARE(mcElement.nextSibling().isNull(), false);
QCOMPARE(mcElement.localName(), QString("mi"));
QCOMPARE(mcElement.text(), QString("mc"));
QCOMPARE(mcElement.attributeNS(mathNS, "fontstyle", ""), QString("italic"));
// <math:mn> inside the <math:msup> for "2" (superscript)
whiteSpace = mcElement.nextSibling();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
KoXmlElement mnElement;
mnElement = whiteSpace.nextSibling().toElement();
QCOMPARE(mnElement.isNull(), false);
QCOMPARE(mnElement.isElement(), true);
QCOMPARE(mnElement.parentNode().isNull(), false);
QCOMPARE(mnElement.parentNode().toElement() == msupElement, true);
QCOMPARE(mnElement.firstChild().isNull(), false);
QCOMPARE(mnElement.lastChild().isNull(), false);
QCOMPARE(mnElement.previousSibling().isNull(), false);
QCOMPARE(mnElement.nextSibling().isNull(), false);
QCOMPARE(mnElement.nextSibling().isText(), true);
QCOMPARE(mnElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(mnElement.localName(), QString("mn"));
QCOMPARE(mnElement.text(), QString("2"));
whiteSpace = semanticsElement.lastChild();
QCOMPARE(whiteSpace.isNull(), false);
QCOMPARE(whiteSpace.isText(), true);
// <math:annotation>
KoXmlElement annotationElement;
annotationElement = whiteSpace.previousSibling().toElement();
QCOMPARE(annotationElement.isNull(), false);
QCOMPARE(annotationElement.isElement(), true);
QCOMPARE(annotationElement.parentNode().isNull(), false);
QCOMPARE(annotationElement.parentNode().toElement() == semanticsElement, true);
QCOMPARE(annotationElement.firstChild().isNull(), false);
QCOMPARE(annotationElement.lastChild().isNull(), false);
QCOMPARE(annotationElement.previousSibling().isNull(), false);
QCOMPARE(annotationElement.nextSibling().isNull(), false);
QCOMPARE(annotationElement.nextSibling().isText(), true);
QCOMPARE(annotationElement.nextSibling().nextSibling().isNull(), true);
QCOMPARE(annotationElement.localName(), QString("annotation"));
QCOMPARE(annotationElement.text(), QString("E = mc^2 "));
QCOMPARE(annotationElement.attributeNS(mathNS, "encoding", ""), QString("StarMath 5.0"));
}
void TestXmlReader::testLargeOpenDocumentSpreadsheet()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
int sheetCount = 4;
int rowCount = 200;
int colCount = 200 / 16;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
xmlstream << "<office:document-content ";
xmlstream << "xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" ";
xmlstream << "xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << "xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" >\n";
xmlstream << "<office:body>\n";
xmlstream << "<office:spreadsheet>\n";
for (int i = 0; i < sheetCount; i++) {
QString sheetName = QString("Sheet%1").arg(i + 1);
xmlstream << "<table:table table:name=\"" << sheetName;
xmlstream << "\" table:print=\"false\">\n";
for (int j = 0; j < rowCount; j++) {
xmlstream << "<table:table-row>\n";
for (int k = 0; k < colCount; k++) {
xmlstream << "<table:table-cell office:value-type=\"string\">";
xmlstream << "<text:p>Hello, world</text:p>";
xmlstream << "</table:table-cell>\n";
}
xmlstream << "</table:table-row>\n";
}
xmlstream << "</table:table>\n";
}
xmlstream << "</office:spreadsheet>\n";
xmlstream << "</office:body>\n";
xmlstream << "</office:document-content>\n";
xmldevice.close();
printf("Raw XML size: %lld KB\n", xmldevice.size() / 1024);
QTime timer;
#if 0
// just to test parsing speed with plain dumb handler
QXmlStreamReader *reader = new QXmlStreamReader(xmldevice);
reader->setNamespaceProcessing(true);
timer.start();
ParseError error = parseDocument(*reader, doc);
printf("Large spreadsheet: QXmlStreamReader parsing time is %d ms\n", timer.elapsed());
delete reader;
xmldevice.seek(0);
#endif
KoXmlDocument doc;
timer.start();
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
if (!errorMsg.isEmpty()) {
qDebug("Error: %s", qPrintable(errorMsg));
return;
}
printf("Large spreadsheet: KoXmlDocument parsing time is %d ms\n", timer.elapsed());
// release memory taken by the XML document content
//xmlstream.setDevice( 0 );
// namespaces that will be used
QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = contentElement.firstChildElement();
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
// <office:spreadsheet>
KoXmlElement spreadsheetElement;
spreadsheetElement = bodyElement.firstChildElement();
QCOMPARE(spreadsheetElement.isNull(), false);
QCOMPARE(spreadsheetElement.isElement(), true);
QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet"));
// now we visit every sheet, every row, every cell
timer.start();
KoXmlElement tableElement;
tableElement = spreadsheetElement.firstChildElement();
for (int table = 0; table < sheetCount; table++) {
QString tableName = QString("Sheet%1").arg(table + 1);
QCOMPARE(tableElement.isNull(), false);
QCOMPARE(tableElement.isElement(), true);
QCOMPARE(tableElement.localName(), QString("table"));
QCOMPARE(tableElement.hasAttributeNS(tableNS, "name"), true);
QCOMPARE(tableElement.attributeNS(tableNS, "name", ""), tableName);
QCOMPARE(tableElement.attributeNS(tableNS, "print", ""), QString("false"));
// load everything for this table
//KoXml::load( tableElement, 99 );
QCOMPARE(tableElement.parentNode().isNull(), false);
QCOMPARE(tableElement.parentNode() == spreadsheetElement, true);
QCOMPARE(tableElement.firstChild().isNull(), false);
QCOMPARE(tableElement.lastChild().isNull(), false);
KoXmlElement rowElement;
rowElement = tableElement.firstChildElement();
for (int row = 0; row < rowCount; row++) {
QCOMPARE(rowElement.isNull(), false);
QCOMPARE(rowElement.isElement(), true);
QCOMPARE(rowElement.localName(), QString("table-row"));
QCOMPARE(rowElement.parentNode().isNull(), false);
QCOMPARE(rowElement.parentNode() == tableElement, true);
QCOMPARE(rowElement.firstChild().isNull(), false);
QCOMPARE(rowElement.lastChild().isNull(), false);
KoXmlElement cellElement;
cellElement = rowElement.firstChildElement();
for (int col = 0; col < colCount; col++) {
QCOMPARE(cellElement.isNull(), false);
QCOMPARE(cellElement.isElement(), true);
QCOMPARE(cellElement.localName(), QString("table-cell"));
QCOMPARE(cellElement.text(), QString("Hello, world"));
QCOMPARE(cellElement.hasAttributeNS(officeNS, "value-type"), true);
QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string"));
QCOMPARE(cellElement.parentNode().isNull(), false);
QCOMPARE(cellElement.parentNode() == rowElement, true);
QCOMPARE(cellElement.firstChild().isNull(), false);
QCOMPARE(cellElement.lastChild().isNull(), false);
cellElement = cellElement.nextSibling().nextSibling().toElement();
}
//KoXml::unload( rowElement );
rowElement = rowElement.nextSibling().nextSibling().toElement();
}
KoXml::unload(tableElement);
tableElement = tableElement.nextSibling().nextSibling().toElement();
}
printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed());
}
void TestXmlReader::testExternalOpenDocumentSpreadsheet(const QString& filename)
{
QProcess unzip;
QStringList arguments;
arguments << "-o" << filename << "content.xml";
printf("Unzipping content.xml from %s...\n", qPrintable(filename));
unzip.start("unzip", arguments);
if (!unzip.waitForStarted()) {
printf("Error: can't invoke unzip. Check your PATH and installation!\n\n");
return;
}
if (!unzip.waitForFinished()) {
printf("Error: unzip failed, can't continue!\n\n");
return;
}
printf("Procesing content.xml....\n");
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QFile xmlfile("content.xml");
if (!xmlfile.open(QFile::ReadOnly)) {
printf("Can not open file '%s'\n", qPrintable(filename));
return;
}
printf("Test external file: %s %lld KB\n", qPrintable(filename), xmlfile.size() / 1024);
QTime timer;
timer.start();
KoXmlDocument doc;
QCOMPARE(KoXml::setDocument(doc, &xmlfile, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
printf("External spreadsheet: parsing time is %d ms\n", timer.elapsed());
// namespaces that will be used
QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
//QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
long totalCellCount = 0;
KoXmlElement bodyElement;
forEachElement(bodyElement, contentElement) {
// <office:body>
if (bodyElement.localName() != QString("body"))
continue;
// now we iterate inside the body
timer.start();
// <office:spreadsheet>
KoXmlElement spreadsheetElement;
spreadsheetElement = bodyElement.firstChild().toElement();
QCOMPARE(spreadsheetElement.isNull(), false);
QCOMPARE(spreadsheetElement.isElement(), true);
QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet"));
// now we visit every sheet
long tableCount = -1;
KoXmlElement tableElement;
tableElement = spreadsheetElement.firstChild().toElement();
for (;;) {
if (tableElement.isNull())
break;
if (tableElement.localName() != QString("table")) {
tableElement = tableElement.nextSibling().toElement();
continue;
}
QString tableName = tableElement.attributeNS(tableNS, "name", "");
tableCount++;
printf(" sheet #%ld (%s): ", tableCount + 1, qPrintable(tableName));
// use to preload everything in this sheet, will slow it down!
// KoXml::load( tableElement, 50 );
long rowCount = -1;
long cellCount = -1;
KoXmlElement rowElement;
rowElement = tableElement.firstChild().toElement();
for (;;) {
if (rowElement.isNull())
break;
if (rowElement.localName() != QString("table-row")) {
rowElement = rowElement.nextSibling().toElement();
continue;
}
rowCount++;
KoXml::load(rowElement, 4);
QCOMPARE(rowElement.isElement(), true);
QCOMPARE(rowElement.localName(), QString("table-row"));
QCOMPARE(rowElement.parentNode().isNull(), false);
QCOMPARE(rowElement.parentNode() == tableElement, true);
KoXmlElement cellElement;
cellElement = rowElement.firstChild().toElement();
for (; ;) {
if (cellElement.isNull())
break;
if (cellElement.localName() != QString("table-cell")) {
cellElement = cellElement.nextSibling().toElement();
continue;
}
cellCount++;
QCOMPARE(cellElement.isNull(), false);
QCOMPARE(cellElement.isElement(), true);
QCOMPARE(cellElement.localName(), QString("table-cell"));
QString text1 = cellElement.text();
QString text2 = cellElement.text();
QCOMPARE(text1, text2);
QString type1 = cellElement.attributeNS(officeNS, "value-type", QString());
QString type2 = cellElement.attributeNS(officeNS, "value-type", QString());
QCOMPARE(type1, type2);
QString style1 = cellElement.attributeNS(tableNS, "style-name", QString());
QString style2 = cellElement.attributeNS(tableNS, "style-name", QString());
QCOMPARE(style1, style2);
QCOMPARE(cellElement.parentNode().isNull(), false);
QCOMPARE(cellElement.parentNode() == rowElement, true);
cellElement = cellElement.nextSibling().toElement();
}
// better not to unload, freeing memory takes time
KoXml::unload(rowElement);
rowElement = rowElement.nextSibling().toElement();
}
printf(" %ld rows, %ld cells\n", rowCount + 1, cellCount + 1);
totalCellCount += (cellCount + 1);
// IMPORTANT: helps minimizing memory usage !!
// we do not need that element anymore, so just throw it away
KoXml::unload(tableElement);
tableElement = tableElement.nextSibling().toElement();
}
KoXml::unload(spreadsheetElement);
}
printf("Total number of cells: %ld\n", totalCellCount);
int elapsed = timer.elapsed();
printf("External spreadsheet: iterating time is %d ms\n", elapsed);
if (elapsed > 0)
printf(" approx. %ld cells/second\n", totalCellCount*1000 / elapsed);
// uncomment to check the XML
xmlfile.remove();
}
QTEST_GUILESS_MAIN(TestXmlReader)
#include <TestXmlReader.moc>
diff --git a/src/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp b/src/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp
index 84692e96..c9aaf78c 100644
--- a/src/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp
+++ b/src/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp
@@ -1,2690 +1,2691 @@
+// clazy:excludeall=qstring-arg
#include <QTest>
#include <QBuffer>
#include <QFile>
#include <QDateTime>
#include <QProcess>
#include <QString>
#include <QTextStream>
#include <KoXmlReader.h>
class TestXmlReaderWithoutSpaces : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testNode();
void testElement();
void testAttributes();
void testText();
void testCDATA();
void testDocument();
void testDocumentType();
void testNamespace();
void testParseQString();
void testUnload();
void testSimpleXML();
void testRootError();
void testMismatchedTag();
void testConvertQDomDocument();
void testConvertQDomElement();
void testSimpleOpenDocumentText();
void testWhitespace();
void testSimpleOpenDocumentSpreadsheet();
void testSimpleOpenDocumentPresentation();
void testSimpleOpenDocumentFormula();
void testLargeOpenDocumentSpreadsheet();
void testExternalOpenDocumentSpreadsheet(const QString& filename);
};
void TestXmlReaderWithoutSpaces::testNode()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth>";
xmlstream << "<continents>";
xmlstream << "<asia/>";
xmlstream << "<africa/>";
xmlstream << "<europe/>";
xmlstream << "<america/>";
xmlstream << "<australia/>";
xmlstream << "<antartic/>";
xmlstream << "</continents>";
xmlstream << "<oceans>";
xmlstream << "<pacific/>";
xmlstream << "<atlantic/>";
xmlstream << "</oceans>";
xmlstream << "</earth>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// null node
KoXmlNode node1;
QCOMPARE(node1.nodeName(), QString());
QCOMPARE(node1.isNull(), true);
QCOMPARE(node1.isElement(), false);
QCOMPARE(node1.isDocument(), false);
QCOMPARE(node1.ownerDocument().isNull(), true);
QCOMPARE(node1.parentNode().isNull(), true);
QCOMPARE(node1.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(node1), 0);
QCOMPARE(node1.firstChild().isNull(), true);
QCOMPARE(node1.lastChild().isNull(), true);
QCOMPARE(node1.previousSibling().isNull(), true);
QCOMPARE(node1.nextSibling().isNull(), true);
// compare with another null node
KoXmlNode node2;
QCOMPARE(node2.isNull(), true);
QCOMPARE(node1 == node2, true);
QCOMPARE(node1 != node2, false);
// a node which is a document
KoXmlNode node3 = doc;
QCOMPARE(node3.nodeName(), QString("#document"));
QCOMPARE(node3.isNull(), false);
QCOMPARE(node3.isElement(), false);
QCOMPARE(node3.isText(), false);
QCOMPARE(node3.isDocument(), true);
QCOMPARE(node3.ownerDocument().isNull(), false);
QCOMPARE(node3.ownerDocument() == doc, true);
QCOMPARE(node3.toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(node3), 1);
// convert to document and the compare
KoXmlDocument doc2 = node3.toDocument();
QCOMPARE(doc2.nodeName(), QString("#document"));
QCOMPARE(doc2.isNull(), false);
QCOMPARE(doc2.isDocument(), true);
QCOMPARE(node3 == doc2, true);
QCOMPARE(KoXml::childNodesCount(doc2), 1);
// a document is of course can't be converted to element
KoXmlElement invalidElement = node3.toElement();
QCOMPARE(invalidElement.nodeName(), QString());
QCOMPARE(invalidElement.isNull(), true);
QCOMPARE(invalidElement.isElement(), false);
QCOMPARE(invalidElement.isText(), false);
QCOMPARE(invalidElement.isDocument(), false);
// clear() makes it a null node again
node3.clear();
QCOMPARE(node3.isNull(), true);
QCOMPARE(node3.nodeName(), QString());
QCOMPARE(node3.isElement(), false);
QCOMPARE(node3.isText(), false);
QCOMPARE(node3.isDocument(), false);
QCOMPARE(node3.ownerDocument().isNull(), true);
QCOMPARE(node1 == node3, true);
QCOMPARE(node1 != node3, false);
// a node which is an element for <earth>
KoXmlNode node4 = doc.firstChild();
QCOMPARE(node4.isNull(), false);
QCOMPARE(node4.isElement(), true);
QCOMPARE(node4.isText(), false);
QCOMPARE(node4.isDocument(), false);
QCOMPARE(node4.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(node4), 2);
QCOMPARE(node4.ownerDocument() == doc, true);
QCOMPARE(node4.toElement() == doc.firstChild().toElement(), true);
// clear() makes it a null node again
node4.clear();
QCOMPARE(node4.isNull(), true);
QCOMPARE(node4.isElement(), false);
QCOMPARE(node4.isText(), false);
QCOMPARE(node4.isDocument(), false);
QCOMPARE(node4 == node1, true);
QCOMPARE(node4 != node1, false);
QCOMPARE(KoXml::childNodesCount(node4), 0);
// a node which is an element for <continents>
KoXmlNode node5 = doc.firstChild().firstChild();
QCOMPARE(node5.nodeName(), QString("continents"));
QCOMPARE(node5.isNull(), false);
QCOMPARE(node5.isElement(), true);
QCOMPARE(node5.isText(), false);
QCOMPARE(node5.isDocument(), false);
QCOMPARE(node5.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(node5), 6);
QCOMPARE(node5.ownerDocument() == doc, true);
// convert to element and the compare
KoXmlElement continentsElement = node5.toElement();
QCOMPARE(node5 == continentsElement, true);
QCOMPARE(continentsElement.isNull(), false);
QCOMPARE(continentsElement.isElement(), true);
QCOMPARE(continentsElement.isText(), false);
QCOMPARE(continentsElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(continentsElement), 6);
QCOMPARE(continentsElement.ownerDocument() == doc, true);
// and it doesn't make sense to convert that node to document
KoXmlDocument invalidDoc = node5.toDocument();
QCOMPARE(invalidDoc.isNull(), true);
QCOMPARE(invalidDoc.isElement(), false);
QCOMPARE(invalidDoc.isText(), false);
QCOMPARE(invalidDoc.isDocument(), false);
// node for <europe> using namedItem() function
KoXmlNode europeNode = continentsElement.namedItem(QString("europe"));
QCOMPARE(europeNode.nodeName(), QString("europe"));
QCOMPARE(europeNode.isNull(), false);
QCOMPARE(europeNode.isElement(), true);
QCOMPARE(europeNode.isText(), false);
QCOMPARE(europeNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(europeNode), 0);
QCOMPARE(europeNode.ownerDocument() == doc, true);
// search non-existing node
KoXmlNode fooNode = continentsElement.namedItem(QString("foobar"));
QCOMPARE(fooNode.isNull(), true);
QCOMPARE(fooNode.isElement(), false);
QCOMPARE(fooNode.isText(), false);
QCOMPARE(fooNode.isCDATASection(), false);
QCOMPARE(KoXml::childNodesCount(fooNode), 0);
}
void TestXmlReaderWithoutSpaces::testElement()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<html>";
xmlstream << "<body bgcolor=\"#000\">";
xmlstream << "<p>";
xmlstream << "Hello, world!";
xmlstream << "</p>";
xmlstream << "</body>";
xmlstream << "</html>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// element for <html>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.nodeName(), QString("html"));
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.isDocument(), false);
QCOMPARE(rootElement.ownerDocument().isNull(), false);
QCOMPARE(rootElement.ownerDocument() == doc, true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 1);
QCOMPARE(rootElement.tagName(), QString("html"));
QCOMPARE(rootElement.prefix().isNull(), true);
// element for <body>
KoXmlElement bodyElement;
bodyElement = rootElement.firstChild().toElement();
QCOMPARE(bodyElement.nodeName(), QString("body"));
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.isDocument(), false);
QCOMPARE(bodyElement.ownerDocument().isNull(), false);
QCOMPARE(bodyElement.ownerDocument() == doc, true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == rootElement, true);
QCOMPARE(bodyElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 1);
QCOMPARE(bodyElement.tagName(), QString("body"));
QCOMPARE(bodyElement.prefix().isNull(), true);
QCOMPARE(bodyElement.hasAttribute("bgcolor"), true);
QCOMPARE(bodyElement.attribute("bgcolor"), QString("#000"));
// a shared copy of <body>, will still have access to attribute bgcolor
KoXmlElement body2Element;
body2Element = bodyElement;
QCOMPARE(body2Element.nodeName(), QString("body"));
QCOMPARE(body2Element.isNull(), false);
QCOMPARE(body2Element.isElement(), true);
QCOMPARE(body2Element.isDocument(), false);
QCOMPARE(body2Element.ownerDocument().isNull(), false);
QCOMPARE(body2Element.ownerDocument() == doc, true);
QCOMPARE(body2Element == bodyElement, true);
QCOMPARE(body2Element != bodyElement, false);
QCOMPARE(body2Element.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(body2Element), 1);
QCOMPARE(body2Element.tagName(), QString("body"));
QCOMPARE(body2Element.prefix().isNull(), true);
QCOMPARE(body2Element.hasAttribute("bgcolor"), true);
QCOMPARE(body2Element.attribute("bgcolor"), QString("#000"));
// empty element, by default constructor
KoXmlElement testElement;
QCOMPARE(testElement.nodeName(), QString());
QCOMPARE(testElement.isNull(), true);
QCOMPARE(testElement.isElement(), false);
QCOMPARE(testElement.isDocument(), false);
QCOMPARE(testElement.ownerDocument().isNull(), true);
QCOMPARE(testElement.ownerDocument() != doc, true);
QCOMPARE(testElement == rootElement, false);
QCOMPARE(testElement != rootElement, true);
QCOMPARE(testElement.parentNode().isNull(), true);
QCOMPARE(testElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(testElement), 0);
// check assignment operator
testElement = rootElement;
QCOMPARE(testElement.nodeName(), QString("html"));
QCOMPARE(testElement.isNull(), false);
QCOMPARE(testElement.isElement(), true);
QCOMPARE(testElement.isDocument(), false);
QCOMPARE(testElement == rootElement, true);
QCOMPARE(testElement != rootElement, false);
QCOMPARE(testElement.parentNode().isNull(), false);
QCOMPARE(testElement.parentNode().toDocument() == doc, true);
QCOMPARE(testElement.tagName(), QString("html"));
QCOMPARE(testElement.prefix().isNull(), true);
QCOMPARE(KoXml::childNodesCount(testElement), 1);
// assigned from another empty element
testElement = KoXmlElement();
QCOMPARE(testElement.isNull(), true);
QCOMPARE(testElement != rootElement, true);
// assigned from <body>
testElement = bodyElement;
QCOMPARE(testElement.isNull(), false);
QCOMPARE(testElement.isElement(), true);
QCOMPARE(testElement.isDocument(), false);
QCOMPARE(testElement.ownerDocument().isNull(), false);
QCOMPARE(testElement.ownerDocument() == doc, true);
QCOMPARE(testElement == bodyElement, true);
QCOMPARE(testElement.parentNode().isNull(), false);
QCOMPARE(testElement.tagName(), QString("body"));
QCOMPARE(testElement.prefix().isNull(), true);
QCOMPARE(testElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(testElement), 1);
// copy constructor
KoXmlElement dummyElement(rootElement);
QCOMPARE(dummyElement.isNull(), false);
QCOMPARE(dummyElement.isElement(), true);
QCOMPARE(dummyElement.isDocument(), false);
QCOMPARE(dummyElement.ownerDocument().isNull(), false);
QCOMPARE(dummyElement.ownerDocument() == doc, true);
QCOMPARE(dummyElement == rootElement, true);
QCOMPARE(dummyElement.parentNode().isNull(), false);
QCOMPARE(dummyElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(dummyElement), 1);
QCOMPARE(dummyElement.tagName(), QString("html"));
QCOMPARE(dummyElement.prefix().isNull(), true);
// clear() turns element to null node
dummyElement.clear();
QCOMPARE(dummyElement.isNull(), true);
QCOMPARE(dummyElement.isElement(), false);
QCOMPARE(dummyElement.isDocument(), false);
QCOMPARE(dummyElement.ownerDocument().isNull(), true);
QCOMPARE(dummyElement.ownerDocument() == doc, false);
QCOMPARE(dummyElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(dummyElement), 0);
QCOMPARE(dummyElement == rootElement, false);
QCOMPARE(dummyElement != rootElement, true);
// check for plain null node converted to element
KoXmlNode dummyNode;
dummyElement = dummyNode.toElement();
QCOMPARE(dummyElement.isNull(), true);
QCOMPARE(dummyElement.isElement(), false);
QCOMPARE(dummyElement.isDocument(), false);
QCOMPARE(dummyElement.ownerDocument().isNull(), true);
QCOMPARE(dummyElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(dummyElement), 0);
QCOMPARE(dummyElement.ownerDocument() == doc, false);
}
void TestXmlReaderWithoutSpaces::testAttributes()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<p>";
xmlstream << "<img src=\"foo.png\" width=\"300\" height=\"150\"/>";
xmlstream << "</p>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
QCOMPARE(rootElement.tagName(), QString("p"));
QCOMPARE(rootElement.prefix().isNull(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 1);
KoXmlElement imgElement;
imgElement = rootElement.firstChild().toElement();
QCOMPARE(imgElement.isNull(), false);
QCOMPARE(imgElement.isElement(), true);
QCOMPARE(imgElement.tagName(), QString("img"));
QCOMPARE(imgElement.prefix().isNull(), true);
QCOMPARE(KoXml::childNodesCount(imgElement), 0);
QCOMPARE(imgElement.hasAttribute("src"), true);
QCOMPARE(imgElement.hasAttribute("width"), true);
QCOMPARE(imgElement.hasAttribute("height"), true);
QCOMPARE(imgElement.hasAttribute("non-exist"), false);
QCOMPARE(imgElement.hasAttribute("SRC"), false);
QCOMPARE(imgElement.attribute("src"), QString("foo.png"));
QCOMPARE(imgElement.attribute("width"), QString("300"));
QCOMPARE(imgElement.attribute("width").toInt(), 300);
QCOMPARE(imgElement.attribute("height"), QString("150"));
QCOMPARE(imgElement.attribute("height").toInt(), 150);
QCOMPARE(imgElement.attribute("border").isEmpty(), true);
QCOMPARE(imgElement.attribute("border", "0").toInt(), 0);
QCOMPARE(imgElement.attribute("border", "-1").toInt(), -1);
QStringList list = KoXml::attributeNames(imgElement);
QCOMPARE(list.count(), 3);
QVERIFY(list.contains("src"));
QVERIFY(list.contains("width"));
QVERIFY(list.contains("height"));
QVERIFY(! list.contains("border"));
foreach(QString a, list) {
QCOMPARE(imgElement.hasAttribute(a), true);
QCOMPARE(imgElement.attribute(a).isEmpty(), false);
}
}
void TestXmlReaderWithoutSpaces::testText()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<p>";
xmlstream << "Hello ";
xmlstream << "<b>world</b>";
xmlstream << "</p>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// element for <p>
KoXmlElement parElement;
parElement = doc.documentElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.isText(), false);
QCOMPARE(parElement.isDocument(), false);
QCOMPARE(parElement.ownerDocument().isNull(), false);
QCOMPARE(parElement.ownerDocument() == doc, true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode().toDocument() == doc, true);
QCOMPARE(parElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(parElement), 2); // <b> and text node "Hello "
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.prefix().isNull(), true);
QCOMPARE(parElement.text(), QString("Hello world"));
// node for "Hello"
KoXmlNode helloNode;
helloNode = parElement.firstChild();
QCOMPARE(helloNode.nodeName(), QString("#text"));
QCOMPARE(helloNode.isNull(), false);
QCOMPARE(helloNode.isElement(), false);
QCOMPARE(helloNode.isText(), true);
QCOMPARE(helloNode.isDocument(), false);
QCOMPARE(helloNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(helloNode), 0);
// "Hello" text
KoXmlText helloText;
helloText = helloNode.toText();
QCOMPARE(helloText.nodeName(), QString("#text"));
QCOMPARE(helloText.isNull(), false);
QCOMPARE(helloText.isElement(), false);
QCOMPARE(helloText.isText(), true);
QCOMPARE(helloText.isDocument(), false);
QCOMPARE(helloText.data(), QString("Hello "));
QCOMPARE(KoXml::childNodesCount(helloText), 0);
// shared copy of the text
KoXmlText hello2Text;
hello2Text = helloText;
QCOMPARE(hello2Text.isNull(), false);
QCOMPARE(hello2Text.isElement(), false);
QCOMPARE(hello2Text.isText(), true);
QCOMPARE(hello2Text.isDocument(), false);
QCOMPARE(hello2Text.data(), QString("Hello "));
QCOMPARE(KoXml::childNodesCount(hello2Text), 0);
// element for <b>
KoXmlElement boldElement;
boldElement = helloNode.nextSibling().toElement();
QCOMPARE(boldElement.isNull(), false);
QCOMPARE(boldElement.isElement(), true);
QCOMPARE(boldElement.isText(), false);
QCOMPARE(boldElement.isDocument(), false);
QCOMPARE(boldElement.ownerDocument().isNull(), false);
QCOMPARE(boldElement.ownerDocument() == doc, true);
QCOMPARE(boldElement.parentNode().isNull(), false);
QCOMPARE(boldElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(boldElement), 1); // text node "world"
QCOMPARE(boldElement.tagName(), QString("b"));
QCOMPARE(boldElement.prefix().isNull(), true);
// "world" text
KoXmlText worldText;
worldText = boldElement.firstChild().toText();
QCOMPARE(worldText.isNull(), false);
QCOMPARE(worldText.isElement(), false);
QCOMPARE(worldText.isText(), true);
QCOMPARE(worldText.isDocument(), false);
QCOMPARE(worldText.data(), QString("world"));
QCOMPARE(KoXml::childNodesCount(worldText), 0);
}
void TestXmlReaderWithoutSpaces::testCDATA()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<p>";
xmlstream << "Hello ";
xmlstream << "<![CDATA[world]]>";
xmlstream << "</p>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// element for <p>
KoXmlElement parElement;
parElement = doc.documentElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.isText(), false);
QCOMPARE(parElement.isDocument(), false);
QCOMPARE(parElement.ownerDocument().isNull(), false);
QCOMPARE(parElement.ownerDocument() == doc, true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode().toDocument() == doc, true);
QCOMPARE(parElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(parElement), 2);
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.prefix().isNull(), true);
QCOMPARE(parElement.text(), QString("Hello world"));
// node for "Hello"
KoXmlNode helloNode;
helloNode = parElement.firstChild();
QCOMPARE(helloNode.isNull(), false);
QCOMPARE(helloNode.isElement(), false);
QCOMPARE(helloNode.isText(), true);
QCOMPARE(helloNode.isDocument(), false);
// "Hello" text
KoXmlText helloText;
helloText = helloNode.toText();
QCOMPARE(helloText.isNull(), false);
QCOMPARE(helloText.isElement(), false);
QCOMPARE(helloText.isText(), true);
QCOMPARE(helloText.isDocument(), false);
QCOMPARE(helloText.data(), QString("Hello "));
// node for CDATA "world!"
// Note: isText() is also true for CDATA
KoXmlNode worldNode;
worldNode = helloNode.nextSibling();
QCOMPARE(worldNode.nodeName(), QString("#cdata-section"));
QCOMPARE(worldNode.isNull(), false);
QCOMPARE(worldNode.isElement(), false);
QCOMPARE(worldNode.isText(), true);
QCOMPARE(worldNode.isCDATASection(), true);
QCOMPARE(worldNode.isDocument(), false);
// CDATA section for "world!"
// Note: isText() is also true for CDATA
KoXmlCDATASection worldCDATA;
worldCDATA = worldNode.toCDATASection();
QCOMPARE(worldCDATA.nodeName(), QString("#cdata-section"));
QCOMPARE(worldCDATA.isNull(), false);
QCOMPARE(worldCDATA.isElement(), false);
QCOMPARE(worldCDATA.isText(), true);
QCOMPARE(worldCDATA.isCDATASection(), true);
QCOMPARE(worldCDATA.isDocument(), false);
QCOMPARE(worldCDATA.data(), QString("world"));
}
void TestXmlReaderWithoutSpaces::testDocument()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<calligra>";
xmlstream << "<words/>\n";
xmlstream << "<stage/>\n";
xmlstream << "</calligra>";
xmldevice.close();
KoXmlDocument doc(false);
// empty document
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), true);
QCOMPARE(doc.lastChild().isNull(), true);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// now give something as the content
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// this document has something already
QCOMPARE(doc.nodeName(), QString("#document"));
QCOMPARE(doc.isNull(), false);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), true);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), false);
QCOMPARE(doc.lastChild().isNull(), false);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// make sure its children are fine
KoXmlElement rootElement;
rootElement = doc.firstChild().toElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.isDocument(), false);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
rootElement = doc.lastChild().toElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.isDocument(), false);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.parentNode().toDocument() == doc, true);
// clear() converts it into null node
doc.clear();
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), true);
QCOMPARE(doc.lastChild().isNull(), true);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// assigned from another empty document
doc = KoXmlDocument();
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.nodeName().isEmpty(), true);
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
}
void TestXmlReaderWithoutSpaces::testDocumentType()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">";
xmlstream << "<body>";
xmlstream << "<img/>";
xmlstream << "<p/>";
xmlstream << "<blockquote/>";
xmlstream << "</body>";
xmldevice.close();
// empty document
KoXmlDocument doc(false);
QCOMPARE(doc.nodeName(), QString());
QCOMPARE(doc.isNull(), true);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), false);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), true);
QCOMPARE(doc.lastChild().isNull(), true);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// has empty doctype
KoXmlDocumentType doctype = doc.doctype();
QCOMPARE(doctype.nodeName(), QString());
QCOMPARE(doctype.isNull(), true);
QCOMPARE(doctype.isElement(), false);
QCOMPARE(doctype.isDocument(), false);
QCOMPARE(doctype.isDocumentType(), false);
QCOMPARE(doctype.parentNode().isNull(), true);
QCOMPARE(doctype.firstChild().isNull(), true);
QCOMPARE(doctype.lastChild().isNull(), true);
QCOMPARE(doctype.previousSibling().isNull(), true);
QCOMPARE(doctype.nextSibling().isNull(), true);
// now give something as the content
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// this document has something already
QCOMPARE(doc.nodeName(), QString("#document"));
QCOMPARE(doc.isNull(), false);
QCOMPARE(doc.isElement(), false);
QCOMPARE(doc.isDocument(), true);
QCOMPARE(doc.parentNode().isNull(), true);
QCOMPARE(doc.firstChild().isNull(), false);
QCOMPARE(doc.lastChild().isNull(), false);
QCOMPARE(doc.previousSibling().isNull(), true);
QCOMPARE(doc.nextSibling().isNull(), true);
// the doctype becomes a valid one
doctype = doc.doctype();
QCOMPARE(doctype.nodeName(), QString("html"));
QCOMPARE(doctype.name(), QString("html"));
QCOMPARE(doctype.isNull(), false);
QCOMPARE(doctype.isElement(), false);
QCOMPARE(doctype.isDocument(), false);
QCOMPARE(doctype.isDocumentType(), true);
QCOMPARE(doctype.parentNode().isNull(), false);
QCOMPARE(doctype.parentNode() == doc, true);
QCOMPARE(doctype.firstChild().isNull(), true);
QCOMPARE(doctype.lastChild().isNull(), true);
QCOMPARE(doctype.previousSibling().isNull(), true);
QCOMPARE(doctype.nextSibling().isNull(), true);
}
void TestXmlReaderWithoutSpaces::testNamespace()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// taken from example in Qt documentation (xml.html)
xmlstream << "<document xmlns:book = \"http://trolltech.com/fnord/book/\"";
xmlstream << " xmlns = \"http://trolltech.com/fnord/\" >";
xmlstream << "<book>";
xmlstream << "<book:title>Practical XML</book:title>";
xmlstream << "<book:author xmlns:fnord = \"http://trolltech.com/fnord/\"";
xmlstream << " title=\"Ms\"";
xmlstream << " fnord:title=\"Goddess\"";
xmlstream << " name=\"Eris Kallisti\"/>";
xmlstream << "<chapter>";
xmlstream << "<title>A Namespace Called fnord</title>";
xmlstream << "</chapter>";
xmlstream << "</book>";
xmlstream << "</document>";
xmldevice.close();
KoXmlDocument doc(false);
KoXmlElement rootElement;
KoXmlElement bookElement;
KoXmlElement bookTitleElement;
KoXmlElement bookAuthorElement;
// ------------- first without any namespace processing -----------
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.tagName(), QString("document"));
QCOMPARE(rootElement.prefix().isNull(), true);
bookElement = rootElement.firstChild().toElement();
QCOMPARE(bookElement.isNull(), false);
QCOMPARE(bookElement.isElement(), true);
QCOMPARE(bookElement.tagName(), QString("book"));
QCOMPARE(bookElement.prefix().isNull(), true);
QCOMPARE(bookElement.localName(), QString());
bookTitleElement = bookElement.firstChild().toElement();
QCOMPARE(bookTitleElement.isNull(), false);
QCOMPARE(bookTitleElement.isElement(), true);
QCOMPARE(bookTitleElement.tagName(), QString("book:title"));
QCOMPARE(bookTitleElement.prefix().isNull(), true);
QCOMPARE(bookTitleElement.localName(), QString());
bookAuthorElement = bookTitleElement.nextSibling().toElement();
QCOMPARE(bookAuthorElement.isNull(), false);
QCOMPARE(bookAuthorElement.isElement(), true);
QCOMPARE(bookAuthorElement.tagName(), QString("book:author"));
QCOMPARE(bookAuthorElement.prefix().isNull(), true);
QCOMPARE(bookAuthorElement.attribute("title"), QString("Ms"));
QCOMPARE(bookAuthorElement.attribute("fnord:title"), QString("Goddess"));
QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti"));
// ------------- now with namespace processing -----------
xmldevice.seek(0); // just to rewind
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* defaultNS = "http://trolltech.com/fnord/";
const char* bookNS = "http://trolltech.com/fnord/book/";
const char* fnordNS = "http://trolltech.com/fnord/";
// <document>
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.tagName(), QString("document"));
QCOMPARE(rootElement.prefix().isEmpty(), true);
QCOMPARE(rootElement.namespaceURI(), QString(defaultNS));
QCOMPARE(rootElement.localName(), QString("document"));
// <book>
bookElement = rootElement.firstChild().toElement();
QCOMPARE(bookElement.isNull(), false);
QCOMPARE(bookElement.isElement(), true);
QCOMPARE(bookElement.tagName(), QString("book"));
QCOMPARE(bookElement.prefix().isEmpty(), true);
QCOMPARE(bookElement.namespaceURI(), QString(defaultNS));
QCOMPARE(bookElement.localName(), QString("book"));
// <book:title>
bookTitleElement = bookElement.firstChild().toElement();
QCOMPARE(bookTitleElement.isNull(), false);
QCOMPARE(bookTitleElement.isElement(), true);
QCOMPARE(bookTitleElement.tagName(), QString("title"));
QCOMPARE(bookTitleElement.prefix(), QString("book"));
QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookTitleElement.localName(), QString("title"));
// another way, find it using namedItemNS()
KoXmlElement book2TitleElement;
book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title");
//book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement();
QCOMPARE(book2TitleElement == bookTitleElement, true);
QCOMPARE(book2TitleElement.isNull(), false);
QCOMPARE(book2TitleElement.isElement(), true);
QCOMPARE(book2TitleElement.tagName(), QString("title"));
// <book:author>
bookAuthorElement = bookTitleElement.nextSibling().toElement();
QCOMPARE(bookAuthorElement.isNull(), false);
QCOMPARE(bookAuthorElement.isElement(), true);
QCOMPARE(bookAuthorElement.tagName(), QString("author"));
QCOMPARE(bookAuthorElement.prefix(), QString("book"));
QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookAuthorElement.localName(), QString("author"));
// another way, find it using namedItemNS()
KoXmlElement book2AuthorElement;
book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author");
//book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement();
QCOMPARE(book2AuthorElement == bookAuthorElement, true);
QCOMPARE(book2AuthorElement.isNull(), false);
QCOMPARE(book2AuthorElement.isElement(), true);
QCOMPARE(book2AuthorElement.tagName(), QString("author"));
// attributes in <book:author>
// Note: with namespace processing, attribute's prefix is taken out and
// hence "fnord:title" will simply override "title"
// and searching attribute with prefix will give no result
QCOMPARE(bookAuthorElement.hasAttribute("title"), true);
QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttribute("name"), true);
QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess"));
QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true);
QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti"));
// attributes in <book:author>, with NS family of functions
// those without prefix are not accessible at all, because they do not belong
// to any namespace at all.
// Note: default namespace does not apply to attribute names!
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString(""));
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true);
}
// mostly similar to testNamespace above, but parse from a QString
void TestXmlReaderWithoutSpaces::testParseQString()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QString xmlText;
xmlText += "<document xmlns:book = \"http://trolltech.com/fnord/book/\"";
xmlText += " xmlns = \"http://trolltech.com/fnord/\" >";
xmlText += "<book>";
xmlText += "<book:title>Practical XML</book:title>";
xmlText += "<book:author xmlns:fnord = \"http://trolltech.com/fnord/\"";
xmlText += " title=\"Ms\"";
xmlText += " fnord:title=\"Goddess\"";
xmlText += " name=\"Eris Kallisti\"/>";
xmlText += "<chapter>";
xmlText += "<title>A Namespace Called fnord</title>";
xmlText += "</chapter>";
xmlText += "</book>";
xmlText += "</document>";
KoXmlDocument doc(false);
KoXmlElement rootElement;
KoXmlElement bookElement;
KoXmlElement bookTitleElement;
KoXmlElement bookAuthorElement;
QCOMPARE(doc.setContent(xmlText, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* defaultNS = "http://trolltech.com/fnord/";
const char* bookNS = "http://trolltech.com/fnord/book/";
const char* fnordNS = "http://trolltech.com/fnord/";
// <document>
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.tagName(), QString("document"));
QCOMPARE(rootElement.prefix().isEmpty(), true);
QCOMPARE(rootElement.namespaceURI(), QString(defaultNS));
QCOMPARE(rootElement.localName(), QString("document"));
// <book>
bookElement = rootElement.firstChild().toElement();
QCOMPARE(bookElement.isNull(), false);
QCOMPARE(bookElement.isElement(), true);
QCOMPARE(bookElement.tagName(), QString("book"));
QCOMPARE(bookElement.prefix().isEmpty(), true);
QCOMPARE(bookElement.namespaceURI(), QString(defaultNS));
QCOMPARE(bookElement.localName(), QString("book"));
// <book:title>
bookTitleElement = bookElement.firstChild().toElement();
QCOMPARE(bookTitleElement.isNull(), false);
QCOMPARE(bookTitleElement.isElement(), true);
QCOMPARE(bookTitleElement.tagName(), QString("title"));
QCOMPARE(bookTitleElement.prefix(), QString("book"));
QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookTitleElement.localName(), QString("title"));
// another way, find it using namedItemNS()
KoXmlElement book2TitleElement;
book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title");
//book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement();
QCOMPARE(book2TitleElement == bookTitleElement, true);
QCOMPARE(book2TitleElement.isNull(), false);
QCOMPARE(book2TitleElement.isElement(), true);
QCOMPARE(book2TitleElement.tagName(), QString("title"));
// <book:author>
bookAuthorElement = bookTitleElement.nextSibling().toElement();
QCOMPARE(bookAuthorElement.isNull(), false);
QCOMPARE(bookAuthorElement.isElement(), true);
QCOMPARE(bookAuthorElement.tagName(), QString("author"));
QCOMPARE(bookAuthorElement.prefix(), QString("book"));
QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS));
QCOMPARE(bookAuthorElement.localName(), QString("author"));
// another way, find it using namedItemNS()
KoXmlElement book2AuthorElement;
book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author");
//book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement();
QCOMPARE(book2AuthorElement == bookAuthorElement, true);
QCOMPARE(book2AuthorElement.isNull(), false);
QCOMPARE(book2AuthorElement.isElement(), true);
QCOMPARE(book2AuthorElement.tagName(), QString("author"));
// attributes in <book:author>
// Note: with namespace processing, attribute's prefix is taken out and
// hence "fnord:title" will simply override "title"
// and searching attribute with prefix will give no result
QCOMPARE(bookAuthorElement.hasAttribute("title"), true);
QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttribute("name"), true);
QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess"));
QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true);
QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti"));
// attributes in <book:author>, with NS family of functions
// those without prefix are not accessible at all, because they do not belong
// to any namespace at all.
// Note: default namespace does not apply to attribute names!
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString(""));
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess"));
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false);
QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false);
QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true);
QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true);
}
void TestXmlReaderWithoutSpaces::testUnload()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth>";
xmlstream << "<continents>";
xmlstream << "<asia/>";
xmlstream << "<africa/>";
xmlstream << "<europe/>";
xmlstream << "<america/>";
xmlstream << "<australia/>";
xmlstream << "<antartic/>";
xmlstream << "</continents>";
xmlstream << "<oceans>";
xmlstream << "<pacific/>";
xmlstream << "<atlantic/>";
xmlstream << "</oceans>";
xmlstream << "</earth>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement earthElement;
earthElement = doc.documentElement().toElement();
QCOMPARE(earthElement.isNull(), false);
QCOMPARE(earthElement.isElement(), true);
QCOMPARE(earthElement.parentNode().isNull(), false);
QCOMPARE(earthElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(earthElement), 2);
QCOMPARE(earthElement.tagName(), QString("earth"));
QCOMPARE(earthElement.prefix().isNull(), true);
// this ensures that all child nodes of <earth> are loaded
earthElement.firstChild();
// explicitly unload all child nodes of <earth>
KoXml::unload(earthElement);
// we should get the correct first child
KoXmlElement continentsElement = earthElement.firstChild().toElement();
QCOMPARE(continentsElement.nodeName(), QString("continents"));
QCOMPARE(continentsElement.isNull(), false);
QCOMPARE(continentsElement.isElement(), true);
QCOMPARE(continentsElement.isText(), false);
QCOMPARE(continentsElement.isDocument(), false);
QCOMPARE(continentsElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(continentsElement), 6);
// let us unload everything again
KoXml::unload(earthElement);
// we should get the correct last child
KoXmlElement oceansElement = earthElement.lastChild().toElement();
QCOMPARE(oceansElement.nodeName(), QString("oceans"));
QCOMPARE(oceansElement.isNull(), false);
QCOMPARE(oceansElement.isElement(), true);
QCOMPARE(oceansElement.isText(), false);
QCOMPARE(oceansElement.isDocument(), false);
QCOMPARE(oceansElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(continentsElement), 6);
}
void TestXmlReaderWithoutSpaces::testSimpleXML()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<solarsystem>";
xmlstream << "<mercurius/>";
xmlstream << "<venus/>";
xmlstream << "<earth>";
xmlstream << "<moon/>";
xmlstream << "</earth>";
xmlstream << "<mars/>";
xmlstream << "<jupiter/>";
xmlstream << "</solarsystem>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// <solarsystem>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 5);
QCOMPARE(rootElement.tagName(), QString("solarsystem"));
QCOMPARE(rootElement.prefix().isNull(), true);
// node <mercurius>
KoXmlNode firstPlanetNode;
firstPlanetNode = rootElement.firstChild();
QCOMPARE(firstPlanetNode.isNull(), false);
QCOMPARE(firstPlanetNode.isElement(), true);
QCOMPARE(firstPlanetNode.nextSibling().isNull(), false);
QCOMPARE(firstPlanetNode.previousSibling().isNull(), true);
QCOMPARE(firstPlanetNode.parentNode().isNull(), false);
QCOMPARE(firstPlanetNode.parentNode() == rootElement, true);
QCOMPARE(firstPlanetNode.parentNode() != rootElement, false);
QCOMPARE(firstPlanetNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0);
QCOMPARE(firstPlanetNode.firstChild().isNull(), true);
QCOMPARE(firstPlanetNode.lastChild().isNull(), true);
// element <mercurius>
KoXmlElement firstPlanetElement;
firstPlanetElement = firstPlanetNode.toElement();
QCOMPARE(firstPlanetElement.isNull(), false);
QCOMPARE(firstPlanetElement.isElement(), true);
QCOMPARE(firstPlanetElement.parentNode().isNull(), false);
QCOMPARE(firstPlanetElement.parentNode() == rootElement, true);
QCOMPARE(firstPlanetElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0);
QCOMPARE(firstPlanetElement.firstChild().isNull(), true);
QCOMPARE(firstPlanetElement.lastChild().isNull(), true);
QCOMPARE(firstPlanetElement.tagName(), QString("mercurius"));
QCOMPARE(firstPlanetElement.prefix().isNull(), true);
// node <venus>
KoXmlNode secondPlanetNode;
secondPlanetNode = firstPlanetNode.nextSibling();
QCOMPARE(secondPlanetNode.isNull(), false);
QCOMPARE(secondPlanetNode.isElement(), true);
QCOMPARE(secondPlanetNode.nextSibling().isNull(), false);
QCOMPARE(secondPlanetNode.previousSibling().isNull(), false);
QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetNode, true);
QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetElement, true);
QCOMPARE(secondPlanetNode.parentNode().isNull(), false);
QCOMPARE(secondPlanetNode.parentNode() == rootElement, true);
QCOMPARE(secondPlanetNode.parentNode() != rootElement, false);
QCOMPARE(secondPlanetNode.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0);
QCOMPARE(secondPlanetNode.firstChild().isNull(), true);
QCOMPARE(secondPlanetNode.lastChild().isNull(), true);
// element <venus>
KoXmlElement secondPlanetElement;
secondPlanetElement = secondPlanetNode.toElement();
QCOMPARE(secondPlanetElement.isNull(), false);
QCOMPARE(secondPlanetElement.isElement(), true);
QCOMPARE(secondPlanetElement.nextSibling().isNull(), false);
QCOMPARE(secondPlanetElement.previousSibling().isNull(), false);
QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetNode, true);
QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetElement, true);
QCOMPARE(secondPlanetElement.parentNode().isNull(), false);
QCOMPARE(secondPlanetElement.parentNode() == rootElement, true);
QCOMPARE(secondPlanetElement.parentNode() != rootElement, false);
QCOMPARE(secondPlanetElement.hasChildNodes(), false);
QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0);
QCOMPARE(secondPlanetElement.firstChild().isNull(), true);
QCOMPARE(secondPlanetElement.lastChild().isNull(), true);
QCOMPARE(secondPlanetElement.tagName(), QString("venus"));
QCOMPARE(secondPlanetElement.prefix().isNull(), true);
}
void TestXmlReaderWithoutSpaces::testRootError()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
// multiple root nodes are not valid !
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth></earth><moon></moon>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false);
QCOMPARE(errorMsg.isEmpty(), false);
QCOMPARE(errorMsg, QCoreApplication::translate("QXmlStream", "Extra content at end of document."));
QCOMPARE(errorLine, 1);
QCOMPARE(errorColumn, 21);
}
void TestXmlReaderWithoutSpaces::testMismatchedTag()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<earth></e>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false);
QCOMPARE(errorMsg.isEmpty(), false);
QCOMPARE(errorMsg, QCoreApplication::translate("QXmlStream", "Opening and ending tag mismatch."));
QCOMPARE(errorLine, 1);
QCOMPARE(errorColumn, 11);
}
static void dumpNodes(const KoXmlNode &node, int level=0)
{
QString indent = QString("%1").arg("", level*3);
if (node.isNull()) {
qDebug()<<indent<<"null";
}
qDebug()<<indent<<node.nodeName();
for(KoXmlNode n = node.firstChild(); ! n.isNull(); n = n.nextSibling() ) {
dumpNodes(n, level+1);
}
}
static void dumpNodes(const QDomNode &node, int level=0)
{
QString indent = QString("%1").arg("", level*3);
if (node.isNull()) {
qDebug()<<indent<<"null";
}
qDebug()<<indent<<node.nodeName();
for(QDomNode n = node.firstChild(); ! n.isNull(); n = n.nextSibling() ) {
dumpNodes(n, level+1);
}
}
void TestXmlReaderWithoutSpaces::testConvertQDomDocument()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<solarsystem star=\"sun\">";
xmlstream << "<mercurius/>";
xmlstream << "<venus/>";
xmlstream << "<earth habitable=\"true\"><p>The best place</p>";
xmlstream << "<moon habitable=\"possible\"/>";
xmlstream << "</earth>";
xmlstream << "<mars/>";
xmlstream << "<jupiter/>";
xmlstream << "</solarsystem>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
dumpNodes(doc);
// <solarsystem>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 5);
QCOMPARE(rootElement.tagName(), QString("solarsystem"));
QCOMPARE(rootElement.prefix().isNull(), true);
// now test converting KoXmlDocument to QDomDocument
QDomDocument universeDoc = KoXml::asQDomDocument(doc);
// <solarsystem>
QDomElement solarSystemElement = universeDoc.documentElement();
QCOMPARE(solarSystemElement.isNull(), false);
QCOMPARE(solarSystemElement.isElement(), true);
QCOMPARE(solarSystemElement.parentNode().isNull(), false);
QCOMPARE(solarSystemElement.hasChildNodes(), true);
QCOMPARE(solarSystemElement.tagName(), QString("solarsystem"));
QCOMPARE(solarSystemElement.prefix().isNull(), true);
// <earth>
QDomElement earthElement = solarSystemElement.namedItem("earth").toElement();
QCOMPARE(earthElement.isNull(), false);
QCOMPARE(earthElement.isElement(), true);
QCOMPARE(earthElement.parentNode().isNull(), false);
QCOMPARE(earthElement.hasAttribute("habitable"), true);
QCOMPARE(earthElement.hasChildNodes(), true);
QCOMPARE(earthElement.tagName(), QString("earth"));
QCOMPARE(earthElement.prefix().isNull(), true);
// <p> in <earth>
QDomNode placeNode = earthElement.firstChild();
qDebug()<<"placeNode"<<placeNode.nodeName();
dumpNodes(placeNode);
QCOMPARE(placeNode.isNull(), false);
QCOMPARE(placeNode.isElement(), true);
QCOMPARE(placeNode.toElement().text(), QString("The best place"));
QCOMPARE(placeNode.nextSibling().isNull(), false);
QCOMPARE(placeNode.previousSibling().isNull(), true);
QCOMPARE(placeNode.parentNode().isNull(), false);
QCOMPARE(placeNode.parentNode() == earthElement, true);
QCOMPARE(placeNode.hasChildNodes(), true);
QCOMPARE(placeNode.childNodes().count(), 1);
//printf("Result:\n%s\n\n", qPrintable(universeDoc.toString()));
}
void TestXmlReaderWithoutSpaces::testConvertQDomElement()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream << "<universe>";
xmlstream << "<solarsystem star=\"sun\">";
xmlstream << "<mercurius/>";
xmlstream << "<venus/>";
xmlstream << "<earth habitable=\"true\"><p>The best place</p>";
xmlstream << "<moon habitable=\"possible\"/>";
xmlstream << "</earth>";
xmlstream << "<mars/>";
xmlstream << "<jupiter/>";
xmlstream << "</solarsystem>";
xmlstream << "</universe>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
// <solarsystem>
KoXmlElement rootElement;
rootElement = doc.documentElement();
QCOMPARE(rootElement.isNull(), false);
QCOMPARE(rootElement.isElement(), true);
QCOMPARE(rootElement.parentNode().isNull(), false);
QCOMPARE(rootElement.hasChildNodes(), true);
QCOMPARE(KoXml::childNodesCount(rootElement), 1);
QCOMPARE(rootElement.tagName(), QString("universe"));
QCOMPARE(rootElement.prefix().isNull(), true);
// now test converting KoXmlElement to QDomElement
QDomDocument solarDoc;
KoXml::asQDomElement(solarDoc, rootElement.firstChild().toElement());
// <solarsystem>
QDomElement solarSystemElement = solarDoc.documentElement();
QCOMPARE(solarSystemElement.isNull(), false);
QCOMPARE(solarSystemElement.isElement(), true);
QCOMPARE(solarSystemElement.parentNode().isNull(), false);
QCOMPARE(solarSystemElement.hasChildNodes(), true);
QCOMPARE(solarSystemElement.tagName(), QString("solarsystem"));
QCOMPARE(solarSystemElement.prefix().isNull(), true);
// <earth>
QDomElement earthElement = solarSystemElement.namedItem("earth").toElement();
QCOMPARE(earthElement.isNull(), false);
QCOMPARE(earthElement.isElement(), true);
QCOMPARE(earthElement.parentNode().isNull(), false);
QCOMPARE(earthElement.hasAttribute("habitable"), true);
QCOMPARE(earthElement.hasChildNodes(), true);
QCOMPARE(earthElement.tagName(), QString("earth"));
QCOMPARE(earthElement.prefix().isNull(), true);
// <p> in <earth>
QDomNode placeNode = earthElement.firstChild();
QCOMPARE(placeNode.isNull(), false);
QCOMPARE(placeNode.isElement(), true);
QCOMPARE(placeNode.toElement().text(), QString("The best place"));
QCOMPARE(placeNode.nextSibling().isNull(), false);
QCOMPARE(placeNode.previousSibling().isNull(), true);
QCOMPARE(placeNode.parentNode().isNull(), false);
QCOMPARE(placeNode.parentNode() == earthElement, true);
QCOMPARE(placeNode.hasChildNodes(), true);
QCOMPARE(placeNode.childNodes().count(), 1);
//printf("Result:\n%s\n\n", qPrintable(universeDoc.toString()));
}
void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentText()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument text
// it has only paragraph "Hello, world!"
// automatic styles, declarations and unnecessary namespaces are omitted.
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"";
xmlstream << " xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"";
xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << " office:version=\"1.0\">";
xmlstream << "<office:automatic-styles/>";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<text:p text:style-name=\"Standard\">Hello, world!</text:p>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.parentNode().isNull(), false);
QCOMPARE(contentElement.parentNode().toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(contentElement), 2);
QCOMPARE(contentElement.firstChild().isNull(), false);
QCOMPARE(contentElement.lastChild().isNull(), false);
QCOMPARE(contentElement.previousSibling().isNull(), false);
QCOMPARE(contentElement.nextSibling().isNull(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true);
QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0"));
// <office:automatic-styles>
KoXmlElement stylesElement;
stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles");
QCOMPARE(stylesElement.isNull(), false);
QCOMPARE(stylesElement.isElement(), true);
QCOMPARE(stylesElement.parentNode().isNull(), false);
QCOMPARE(stylesElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(stylesElement), 0);
QCOMPARE(stylesElement.firstChild().isNull(), true);
QCOMPARE(stylesElement.lastChild().isNull(), true);
QCOMPARE(stylesElement.previousSibling().isNull(), true);
QCOMPARE(stylesElement.nextSibling().isNull(), false);
QCOMPARE(stylesElement.localName(), QString("automatic-styles"));
// also same <office:automatic-styles>, but without namedItemNS
KoXmlNode styles2Element;
styles2Element = contentElement.firstChild().toElement();
QCOMPARE(styles2Element.isNull(), false);
QCOMPARE(styles2Element.isElement(), true);
QCOMPARE(styles2Element.parentNode().isNull(), false);
QCOMPARE(styles2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(styles2Element), 0);
QCOMPARE(styles2Element.firstChild().isNull(), true);
QCOMPARE(styles2Element.lastChild().isNull(), true);
QCOMPARE(styles2Element.previousSibling().isNull(), true);
QCOMPARE(styles2Element.nextSibling().isNull(), false);
QCOMPARE(styles2Element.localName(), QString("automatic-styles"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body");
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 1);
QCOMPARE(bodyElement.firstChild().isNull(), false);
QCOMPARE(bodyElement.lastChild().isNull(), false);
QCOMPARE(bodyElement.previousSibling().isNull(), false);
QCOMPARE(bodyElement.nextSibling().isNull(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
// also same <office:body>, but without namedItemNS
KoXmlElement body2Element;
body2Element = stylesElement.nextSibling().toElement();
QCOMPARE(body2Element.isNull(), false);
QCOMPARE(body2Element.isElement(), true);
QCOMPARE(body2Element.parentNode().isNull(), false);
QCOMPARE(body2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(body2Element), 1);
QCOMPARE(body2Element.firstChild().isNull(), false);
QCOMPARE(body2Element.lastChild().isNull(), false);
QCOMPARE(body2Element.previousSibling().isNull(), false);
QCOMPARE(body2Element.nextSibling().isNull(), true);
QCOMPARE(body2Element.localName(), QString("body"));
// <office:text>
KoXmlElement textElement;
textElement = KoXml::namedItemNS(bodyElement, officeNS, "text");
QCOMPARE(textElement.isNull(), false);
QCOMPARE(textElement.isElement(), true);
QCOMPARE(textElement.parentNode().isNull(), false);
QCOMPARE(textElement.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(textElement), 1);
QCOMPARE(textElement.firstChild().isNull(), false);
QCOMPARE(textElement.lastChild().isNull(), false);
QCOMPARE(textElement.previousSibling().isNull(), true);
QCOMPARE(textElement.nextSibling().isNull(), true);
QCOMPARE(textElement.localName(), QString("text"));
// the same <office:text>, but without namedItemNS
KoXmlElement text2Element;
text2Element = bodyElement.firstChild().toElement();
QCOMPARE(text2Element.isNull(), false);
QCOMPARE(text2Element.isElement(), true);
QCOMPARE(text2Element.parentNode().isNull(), false);
QCOMPARE(text2Element.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(text2Element), 1);
QCOMPARE(text2Element.firstChild().isNull(), false);
QCOMPARE(text2Element.lastChild().isNull(), false);
QCOMPARE(text2Element.previousSibling().isNull(), true);
QCOMPARE(text2Element.nextSibling().isNull(), true);
QCOMPARE(text2Element.localName(), QString("text"));
// <text:p>
KoXmlElement parElement;
parElement = textElement.firstChild().toElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode() == textElement, true);
QCOMPARE(KoXml::childNodesCount(parElement), 1);
QCOMPARE(parElement.firstChild().isNull(), false);
QCOMPARE(parElement.lastChild().isNull(), false);
QCOMPARE(parElement.previousSibling().isNull(), true);
QCOMPARE(parElement.nextSibling().isNull(), true);
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.text(), QString("Hello, world!"));
QCOMPARE(parElement.attributeNS(QString(textNS), "style-name", ""), QString("Standard"));
}
void TestXmlReaderWithoutSpaces::testWhitespace()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml for testing paragraphs with whitespace
/* The list of elements for which whitespace should be preserved can be
obtained from the Relax NG schema with these commands:
cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \
-N s="http://relaxng.org/ns/structure/1.0" -t -m '//s:text' \
-v '../@name' -n |grep :
cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \
-N s="http://relaxng.org/ns/structure/1.0" \
-t -m "//s:ref[@name='paragraph-content']" -v '../../@name' -n |grep :
*/
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"";
xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\">";
xmlstream << "<text:p> </text:p>";
xmlstream << "<text:p> <text:span/> </text:p>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement p1;
p1 = doc.documentElement().firstChild().toElement();
QCOMPARE(p1.isNull(), false);
QCOMPARE(p1.isElement(), true);
QCOMPARE(KoXml::childNodesCount(p1), 1);
KoXmlElement p2;
p2 = p1.nextSibling().toElement();
QCOMPARE(p2.isNull(), false);
QCOMPARE(p2.isElement(), true);
QCOMPARE(KoXml::childNodesCount(p2), 3);
}
void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentSpreadsheet()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument spreadsheet
// the document has three worksheets, the last two are empty.
// on the first sheet, cell A1 contains the text "Hello, world".
// automatic styles, font declarations and unnecessary namespaces are omitted.
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << "xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" ";
xmlstream << "xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << "xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\">";
xmlstream << "<office:body>";
xmlstream << "<office:spreadsheet>";
xmlstream << "<table:table table:name=\"Sheet1\" table:style-name=\"ta1\" table:print=\"false\">";
xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>";
xmlstream << "<table:table-row table:style-name=\"ro1\">";
xmlstream << "<table:table-cell office:value-type=\"string\">";
xmlstream << "<text:p>Hello, world</text:p>";
xmlstream << "</table:table-cell>";
xmlstream << "</table:table-row>";
xmlstream << "</table:table>";
xmlstream << "<table:table table:name=\"Sheet2\" table:style-name=\"ta1\" table:print=\"false\">";
xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>";
xmlstream << "<table:table-row table:style-name=\"ro1\">";
xmlstream << "<table:table-cell/>";
xmlstream << "</table:table-row>";
xmlstream << "</table:table>";
xmlstream << "<table:table table:name=\"Sheet3\" table:style-name=\"ta1\" table:print=\"false\">";
xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>";
xmlstream << "<table:table-row table:style-name=\"ro1\">";
xmlstream << "<table:table-cell/>";
xmlstream << "</table:table-row>";
xmlstream << "</table:table>";
xmlstream << "</office:spreadsheet>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.parentNode().isNull(), false);
QCOMPARE(contentElement.parentNode().toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(contentElement), 1);
QCOMPARE(contentElement.firstChild().isNull(), false);
QCOMPARE(contentElement.lastChild().isNull(), false);
QCOMPARE(contentElement.previousSibling().isNull(), false);
QCOMPARE(contentElement.nextSibling().isNull(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = contentElement.firstChild().toElement();
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 1);
QCOMPARE(bodyElement.firstChild().isNull(), false);
QCOMPARE(bodyElement.lastChild().isNull(), false);
QCOMPARE(bodyElement.previousSibling().isNull(), true);
QCOMPARE(bodyElement.nextSibling().isNull(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
// <office:spreadsheet>
KoXmlElement spreadsheetElement;
spreadsheetElement = bodyElement.firstChild().toElement();
QCOMPARE(spreadsheetElement.isNull(), false);
QCOMPARE(spreadsheetElement.isElement(), true);
QCOMPARE(spreadsheetElement.parentNode().isNull(), false);
QCOMPARE(spreadsheetElement.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(spreadsheetElement), 3);
QCOMPARE(spreadsheetElement.firstChild().isNull(), false);
QCOMPARE(spreadsheetElement.lastChild().isNull(), false);
QCOMPARE(spreadsheetElement.previousSibling().isNull(), true);
QCOMPARE(spreadsheetElement.nextSibling().isNull(), true);
QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet"));
// <table:table> for Sheet1
KoXmlElement sheet1Element;
sheet1Element = spreadsheetElement.firstChild().toElement();
QCOMPARE(sheet1Element.isNull(), false);
QCOMPARE(sheet1Element.isElement(), true);
QCOMPARE(sheet1Element.parentNode().isNull(), false);
QCOMPARE(sheet1Element.parentNode() == spreadsheetElement, true);
QCOMPARE(KoXml::childNodesCount(sheet1Element), 2);
QCOMPARE(sheet1Element.firstChild().isNull(), false);
QCOMPARE(sheet1Element.lastChild().isNull(), false);
QCOMPARE(sheet1Element.previousSibling().isNull(), true);
QCOMPARE(sheet1Element.nextSibling().isNull(), false);
QCOMPARE(sheet1Element.tagName(), QString("table"));
QCOMPARE(sheet1Element.hasAttributeNS(tableNS, "name"), true);
QCOMPARE(sheet1Element.attributeNS(tableNS, "name", ""), QString("Sheet1"));
QCOMPARE(sheet1Element.attributeNS(tableNS, "style-name", ""), QString("ta1"));
QCOMPARE(sheet1Element.attributeNS(tableNS, "print", ""), QString("false"));
// KoXml::load( sheet1Element, 100 );
// <table:table-column>
KoXmlElement columnElement;
columnElement = sheet1Element.firstChild().toElement();
QCOMPARE(columnElement.isNull(), false);
QCOMPARE(columnElement.isElement(), true);
QCOMPARE(columnElement.parentNode().isNull(), false);
QCOMPARE(columnElement.parentNode() == sheet1Element, true);
QCOMPARE(KoXml::childNodesCount(columnElement), 0);
QCOMPARE(columnElement.firstChild().isNull(), true);
QCOMPARE(columnElement.lastChild().isNull(), true);
QCOMPARE(columnElement.previousSibling().isNull(), true);
QCOMPARE(columnElement.nextSibling().isNull(), false);
QCOMPARE(columnElement.tagName(), QString("table-column"));
QCOMPARE(columnElement.attributeNS(tableNS, "style-name", ""), QString("co1"));
QCOMPARE(columnElement.attributeNS(tableNS, "default-cell-style-name", ""), QString("Default"));
// <table:table-row>
KoXmlElement rowElement;
rowElement = columnElement.nextSibling().toElement();
QCOMPARE(rowElement.isNull(), false);
QCOMPARE(rowElement.isElement(), true);
QCOMPARE(rowElement.parentNode().isNull(), false);
QCOMPARE(rowElement.parentNode() == sheet1Element, true);
QCOMPARE(KoXml::childNodesCount(rowElement), 1);
QCOMPARE(rowElement.firstChild().isNull(), false);
QCOMPARE(rowElement.lastChild().isNull(), false);
QCOMPARE(rowElement.previousSibling().isNull(), false);
QCOMPARE(rowElement.nextSibling().isNull(), true);
QCOMPARE(rowElement.tagName(), QString("table-row"));
QCOMPARE(rowElement.attributeNS(tableNS, "style-name", ""), QString("ro1"));
// <table:table-cell>
KoXmlElement cellElement;
cellElement = rowElement.firstChild().toElement();
QCOMPARE(cellElement.isNull(), false);
QCOMPARE(cellElement.isElement(), true);
QCOMPARE(cellElement.parentNode().isNull(), false);
QCOMPARE(cellElement.parentNode() == rowElement, true);
QCOMPARE(KoXml::childNodesCount(cellElement), 1);
QCOMPARE(cellElement.firstChild().isNull(), false);
QCOMPARE(cellElement.lastChild().isNull(), false);
QCOMPARE(cellElement.previousSibling().isNull(), true);
QCOMPARE(cellElement.nextSibling().isNull(), true);
QCOMPARE(cellElement.tagName(), QString("table-cell"));
QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string"));
// <text:p>
KoXmlElement parElement;
parElement = cellElement.firstChild().toElement();
QCOMPARE(parElement.isNull(), false);
QCOMPARE(parElement.isElement(), true);
QCOMPARE(parElement.parentNode().isNull(), false);
QCOMPARE(parElement.parentNode() == cellElement, true);
QCOMPARE(KoXml::childNodesCount(parElement), 1);
QCOMPARE(parElement.firstChild().isNull(), false);
QCOMPARE(parElement.lastChild().isNull(), false);
QCOMPARE(parElement.previousSibling().isNull(), true);
QCOMPARE(parElement.nextSibling().isNull(), true);
QCOMPARE(parElement.tagName(), QString("p"));
QCOMPARE(parElement.text(), QString("Hello, world"));
// <table:table> for Sheet2
KoXmlElement sheet2Element;
sheet2Element = sheet1Element.nextSibling().toElement();
QCOMPARE(sheet2Element.isNull(), false);
QCOMPARE(sheet2Element.isElement(), true);
QCOMPARE(sheet2Element.parentNode().isNull(), false);
QCOMPARE(sheet2Element.parentNode() == spreadsheetElement, true);
QCOMPARE(KoXml::childNodesCount(sheet2Element), 2);
QCOMPARE(sheet2Element.firstChild().isNull(), false);
QCOMPARE(sheet2Element.lastChild().isNull(), false);
QCOMPARE(sheet2Element.previousSibling().isNull(), false);
QCOMPARE(sheet2Element.nextSibling().isNull(), false);
QCOMPARE(sheet2Element.tagName(), QString("table"));
// </table:table> for Sheet3
KoXmlElement sheet3Element;
sheet3Element = sheet2Element.nextSibling().toElement();
QCOMPARE(sheet3Element.isNull(), false);
QCOMPARE(sheet3Element.isElement(), true);
QCOMPARE(sheet3Element.parentNode().isNull(), false);
QCOMPARE(sheet3Element.parentNode() == spreadsheetElement, true);
QCOMPARE(KoXml::childNodesCount(sheet3Element), 2);
QCOMPARE(sheet3Element.firstChild().isNull(), false);
QCOMPARE(sheet3Element.lastChild().isNull(), false);
QCOMPARE(sheet3Element.previousSibling().isNull(), false);
QCOMPARE(sheet3Element.nextSibling().isNull(), true);
QCOMPARE(sheet3Element.tagName(), QString("table"));
}
void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentPresentation()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument presentation
// styles, declarations and unnecessary namespaces are omitted
// the first page is "Title" and has two text boxes
// the second page is
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content ";
xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" ";
xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << " xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" ";
xmlstream << " xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" ";
xmlstream << " xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" ";
xmlstream << " office:version=\"1.0\">";
xmlstream << "<office:scripts/>";
xmlstream << "<office:automatic-styles/>";
xmlstream << "<office:body>";
xmlstream << "<office:presentation>";
xmlstream << "<draw:page draw:name=\"Title\" draw:style-name=\"dp1\" ";
xmlstream << " draw:master-page-name=\"lyt-cool\" ";
xmlstream << " presentation:presentation-page-layout-name=\"AL1T0\">";
xmlstream << "<draw:frame presentation:style-name=\"pr1\" ";
xmlstream << " draw:text-style-name=\"P2\" draw:layer=\"layout\" ";
xmlstream << " svg:width=\"23.912cm\" svg:height=\"3.508cm\" ";
xmlstream << " svg:x=\"2.058cm\" svg:y=\"1.543cm\" ";
xmlstream << " presentation:class=\"title\" ";
xmlstream << " presentation:user-transformed=\"true\">";
xmlstream << "<draw:text-box>";
xmlstream << "<text:p text:style-name=\"P1\">Foobar</text:p>";
xmlstream << "</draw:text-box>";
xmlstream << "</draw:frame>";
xmlstream << "<draw:frame presentation:style-name=\"pr2\" ";
xmlstream << " draw:text-style-name=\"P3\" draw:layer=\"layout\"";
xmlstream << " svg:width=\"23.912cm\" svg:height=\"13.231cm\"";
xmlstream << " svg:x=\"2.058cm\" svg:y=\"5.838cm\" ";
xmlstream << " presentation:class=\"subtitle\">";
xmlstream << "<draw:text-box>";
xmlstream << "<text:p text:style-name=\"P3\">Foo</text:p>";
xmlstream << "</draw:text-box>";
xmlstream << "</draw:frame>";
xmlstream << "<presentation:notes draw:style-name=\"dp2\">";
xmlstream << "<draw:page-thumbnail draw:style-name=\"gr1\" draw:layer=\"layout\" svg:width=\"13.706cm\" svg:height=\"10.28cm\" svg:x=\"3.647cm\" svg:y=\"2.853cm\" draw:page-number=\"1\" presentation:class=\"page\"/>";
xmlstream << "<draw:frame presentation:style-name=\"pr3\" draw:text-style-name=\"P1\" draw:layer=\"layout\" svg:width=\"14.518cm\" svg:height=\"11.411cm\" svg:x=\"3.249cm\" svg:y=\"14.13cm\" presentation:class=\"notes\" presentation:placeholder=\"true\">";
xmlstream << "<draw:text-box/>";
xmlstream << "</draw:frame>";
xmlstream << "</presentation:notes>";
xmlstream << "</draw:page>";
xmlstream << "<presentation:settings presentation:stay-on-top=\"true\"/>";
xmlstream << "</office:presentation>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
const char* drawNS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0";
const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
const char* presentationNS = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0";
const char* svgNS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.parentNode().isNull(), false);
QCOMPARE(contentElement.parentNode().toDocument() == doc, true);
QCOMPARE(KoXml::childNodesCount(contentElement), 3);
QCOMPARE(contentElement.firstChild().isNull(), false);
QCOMPARE(contentElement.lastChild().isNull(), false);
QCOMPARE(contentElement.previousSibling().isNull(), false);
QCOMPARE(contentElement.nextSibling().isNull(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true);
QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0"));
// <office:scripts>
KoXmlElement scriptsElement;
scriptsElement = KoXml::namedItemNS(contentElement, officeNS, "scripts");
QCOMPARE(scriptsElement.isNull(), false);
QCOMPARE(scriptsElement.isElement(), true);
QCOMPARE(scriptsElement.parentNode().isNull(), false);
QCOMPARE(scriptsElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(scriptsElement), 0);
QCOMPARE(scriptsElement.firstChild().isNull(), true);
QCOMPARE(scriptsElement.lastChild().isNull(), true);
QCOMPARE(scriptsElement.previousSibling().isNull(), true);
QCOMPARE(scriptsElement.nextSibling().isNull(), false);
QCOMPARE(scriptsElement.localName(), QString("scripts"));
// <office:automatic-styles>
KoXmlElement stylesElement;
stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles");
QCOMPARE(stylesElement.isNull(), false);
QCOMPARE(stylesElement.isElement(), true);
QCOMPARE(stylesElement.parentNode().isNull(), false);
QCOMPARE(stylesElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(stylesElement), 0);
QCOMPARE(stylesElement.firstChild().isNull(), true);
QCOMPARE(stylesElement.lastChild().isNull(), true);
QCOMPARE(stylesElement.previousSibling().isNull(), false);
QCOMPARE(stylesElement.nextSibling().isNull(), false);
QCOMPARE(stylesElement.localName(), QString("automatic-styles"));
// also same <office:automatic-styles>, but without namedItemNS
KoXmlNode styles2Element;
styles2Element = scriptsElement.nextSibling().toElement();
QCOMPARE(styles2Element.isNull(), false);
QCOMPARE(styles2Element.isElement(), true);
QCOMPARE(styles2Element.parentNode().isNull(), false);
QCOMPARE(styles2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(styles2Element), 0);
QCOMPARE(styles2Element.firstChild().isNull(), true);
QCOMPARE(styles2Element.lastChild().isNull(), true);
QCOMPARE(styles2Element.previousSibling().isNull(), false);
QCOMPARE(styles2Element.nextSibling().isNull(), false);
QCOMPARE(styles2Element.localName(), QString("automatic-styles"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body");
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.parentNode().isNull(), false);
QCOMPARE(bodyElement.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(bodyElement), 1);
QCOMPARE(bodyElement.firstChild().isNull(), false);
QCOMPARE(bodyElement.lastChild().isNull(), false);
QCOMPARE(bodyElement.previousSibling().isNull(), false);
QCOMPARE(bodyElement.nextSibling().isNull(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
// also same <office:body>, but without namedItemNS
KoXmlElement body2Element;
body2Element = stylesElement.nextSibling().toElement();
QCOMPARE(body2Element.isNull(), false);
QCOMPARE(body2Element.isElement(), true);
QCOMPARE(body2Element.parentNode().isNull(), false);
QCOMPARE(body2Element.parentNode() == contentElement, true);
QCOMPARE(KoXml::childNodesCount(body2Element), 1);
QCOMPARE(body2Element.firstChild().isNull(), false);
QCOMPARE(body2Element.lastChild().isNull(), false);
QCOMPARE(body2Element.previousSibling().isNull(), false);
QCOMPARE(body2Element.nextSibling().isNull(), true);
QCOMPARE(body2Element.localName(), QString("body"));
// <office:presentation>
KoXmlElement presentationElement;
presentationElement = KoXml::namedItemNS(bodyElement, officeNS, "presentation");
QCOMPARE(presentationElement.isNull(), false);
QCOMPARE(presentationElement.isElement(), true);
QCOMPARE(presentationElement.parentNode().isNull(), false);
QCOMPARE(presentationElement.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(presentationElement), 2);
QCOMPARE(presentationElement.firstChild().isNull(), false);
QCOMPARE(presentationElement.lastChild().isNull(), false);
QCOMPARE(presentationElement.previousSibling().isNull(), true);
QCOMPARE(presentationElement.nextSibling().isNull(), true);
QCOMPARE(presentationElement.localName(), QString("presentation"));
// the same <office:presentation>, but without namedItemNS
KoXmlElement presentation2Element;
presentation2Element = bodyElement.firstChild().toElement();
QCOMPARE(presentation2Element.isNull(), false);
QCOMPARE(presentation2Element.isElement(), true);
QCOMPARE(presentation2Element.parentNode().isNull(), false);
QCOMPARE(presentation2Element.parentNode() == bodyElement, true);
QCOMPARE(KoXml::childNodesCount(presentation2Element), 2);
QCOMPARE(presentation2Element.firstChild().isNull(), false);
QCOMPARE(presentation2Element.lastChild().isNull(), false);
QCOMPARE(presentation2Element.previousSibling().isNull(), true);
QCOMPARE(presentation2Element.nextSibling().isNull(), true);
QCOMPARE(presentation2Element.localName(), QString("presentation"));
// <draw:page> for "Title"
KoXmlElement titlePageElement;
titlePageElement = presentationElement.firstChild().toElement();
QCOMPARE(titlePageElement.isNull(), false);
QCOMPARE(titlePageElement.isElement(), true);
QCOMPARE(titlePageElement.parentNode().isNull(), false);
QCOMPARE(titlePageElement.parentNode() == presentationElement, true);
QCOMPARE(KoXml::childNodesCount(titlePageElement), 3);
QCOMPARE(titlePageElement.firstChild().isNull(), false);
QCOMPARE(titlePageElement.lastChild().isNull(), false);
QCOMPARE(titlePageElement.previousSibling().isNull(), true);
QCOMPARE(titlePageElement.nextSibling().isNull(), false);
QCOMPARE(titlePageElement.localName(), QString("page"));
QCOMPARE(titlePageElement.attributeNS(drawNS, "name", ""), QString("Title"));
QCOMPARE(titlePageElement.attributeNS(drawNS, "style-name", ""), QString("dp1"));
QCOMPARE(titlePageElement.attributeNS(drawNS, "master-page-name", ""), QString("lyt-cool"));
QCOMPARE(titlePageElement.attributeNS(presentationNS,
"presentation-page-layout-name", ""), QString("AL1T0"));
// <draw:frame> for the title frame
KoXmlElement titleFrameElement;
titleFrameElement = titlePageElement.firstChild().toElement();
QCOMPARE(titleFrameElement.isNull(), false);
QCOMPARE(titleFrameElement.isElement(), true);
QCOMPARE(titleFrameElement.parentNode().isNull(), false);
QCOMPARE(titleFrameElement.parentNode() == titlePageElement, true);
QCOMPARE(KoXml::childNodesCount(titleFrameElement), 1);
QCOMPARE(titleFrameElement.firstChild().isNull(), false);
QCOMPARE(titleFrameElement.lastChild().isNull(), false);
QCOMPARE(titleFrameElement.previousSibling().isNull(), true);
QCOMPARE(titleFrameElement.nextSibling().isNull(), false);
QCOMPARE(titleFrameElement.localName(), QString("frame"));
QCOMPARE(titleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr1"));
QCOMPARE(titleFrameElement.attributeNS(presentationNS, "class", ""), QString("title"));
QCOMPARE(titleFrameElement.attributeNS(presentationNS, "user-transformed", ""), QString("true"));
QCOMPARE(titleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P2"));
QCOMPARE(titleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "height", ""), QString("3.508cm"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm"));
QCOMPARE(titleFrameElement.attributeNS(svgNS, "y", ""), QString("1.543cm"));
// <draw:text-box> of the title frame
KoXmlElement titleBoxElement;
titleBoxElement = titleFrameElement.firstChild().toElement();
QCOMPARE(titleBoxElement.isNull(), false);
QCOMPARE(titleBoxElement.isElement(), true);
QCOMPARE(titleBoxElement.parentNode().isNull(), false);
QCOMPARE(titleBoxElement.parentNode() == titleFrameElement, true);
QCOMPARE(KoXml::childNodesCount(titleBoxElement), 1);
QCOMPARE(titleBoxElement.firstChild().isNull(), false);
QCOMPARE(titleBoxElement.lastChild().isNull(), false);
QCOMPARE(titleBoxElement.previousSibling().isNull(), true);
QCOMPARE(titleBoxElement.nextSibling().isNull(), true);
QCOMPARE(titleBoxElement.localName(), QString("text-box"));
// <text:p> for the title text-box
KoXmlElement titleParElement;
titleParElement = titleBoxElement.firstChild().toElement();
QCOMPARE(titleParElement.isNull(), false);
QCOMPARE(titleParElement.isElement(), true);
QCOMPARE(titleParElement.parentNode().isNull(), false);
QCOMPARE(titleParElement.parentNode() == titleBoxElement, true);
QCOMPARE(KoXml::childNodesCount(titleParElement), 1);
QCOMPARE(titleParElement.firstChild().isNull(), false);
QCOMPARE(titleParElement.lastChild().isNull(), false);
QCOMPARE(titleParElement.previousSibling().isNull(), true);
QCOMPARE(titleParElement.nextSibling().isNull(), true);
QCOMPARE(titleParElement.localName(), QString("p"));
QCOMPARE(titleParElement.attributeNS(textNS, "style-name", ""), QString("P1"));
QCOMPARE(titleParElement.text(), QString("Foobar"));
// <draw:frame> for the subtitle frame
KoXmlElement subtitleFrameElement;
subtitleFrameElement = titleFrameElement.nextSibling().toElement();
QCOMPARE(subtitleFrameElement.isNull(), false);
QCOMPARE(subtitleFrameElement.isElement(), true);
QCOMPARE(subtitleFrameElement.parentNode().isNull(), false);
QCOMPARE(subtitleFrameElement.parentNode() == titlePageElement, true);
QCOMPARE(KoXml::childNodesCount(subtitleFrameElement), 1);
QCOMPARE(subtitleFrameElement.firstChild().isNull(), false);
QCOMPARE(subtitleFrameElement.lastChild().isNull(), false);
QCOMPARE(subtitleFrameElement.previousSibling().isNull(), false);
QCOMPARE(subtitleFrameElement.nextSibling().isNull(), false);
QCOMPARE(subtitleFrameElement.localName(), QString("frame"));
QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr2"));
QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "class", ""), QString("subtitle"));
QCOMPARE(subtitleFrameElement.hasAttributeNS(presentationNS, "user-transformed"), false);
QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P3"));
QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "height", ""), QString("13.231cm"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm"));
QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "y", ""), QString("5.838cm"));
// <draw:text-box> of the subtitle frame
KoXmlElement subtitleBoxElement;
subtitleBoxElement = subtitleFrameElement.firstChild().toElement();
QCOMPARE(subtitleBoxElement.isNull(), false);
QCOMPARE(subtitleBoxElement.isElement(), true);
QCOMPARE(subtitleBoxElement.parentNode().isNull(), false);
QCOMPARE(subtitleBoxElement.parentNode() == subtitleFrameElement, true);
QCOMPARE(KoXml::childNodesCount(subtitleBoxElement), 1);
QCOMPARE(subtitleBoxElement.firstChild().isNull(), false);
QCOMPARE(subtitleBoxElement.lastChild().isNull(), false);
QCOMPARE(subtitleBoxElement.previousSibling().isNull(), true);
QCOMPARE(subtitleBoxElement.nextSibling().isNull(), true);
QCOMPARE(subtitleBoxElement.localName(), QString("text-box"));
// <text:p> for the subtitle text-box
KoXmlElement subtitleParElement;
subtitleParElement = subtitleBoxElement.firstChild().toElement();
QCOMPARE(subtitleParElement.isNull(), false);
QCOMPARE(subtitleParElement.isElement(), true);
QCOMPARE(subtitleParElement.parentNode().isNull(), false);
QCOMPARE(subtitleParElement.parentNode() == subtitleBoxElement, true);
QCOMPARE(KoXml::childNodesCount(subtitleParElement), 1);
QCOMPARE(subtitleParElement.firstChild().isNull(), false);
QCOMPARE(subtitleParElement.lastChild().isNull(), false);
QCOMPARE(subtitleParElement.previousSibling().isNull(), true);
QCOMPARE(subtitleParElement.nextSibling().isNull(), true);
QCOMPARE(subtitleParElement.localName(), QString("p"));
QCOMPARE(subtitleParElement.attributeNS(textNS, "style-name", ""), QString("P3"));
QCOMPARE(subtitleParElement.text(), QString("Foo"));
}
void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentFormula()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml from a simple OpenDocument formula
// this is essentially MathML
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<!DOCTYPE math:math PUBLIC \"-//OpenOffice.org//DTD Modified W3C MathML 1.01//EN\" \"math.dtd\">";
xmlstream << "<math:math xmlns:math=\"http://www.w3.org/1998/Math/MathML\">";
xmlstream << "<math:semantics>";
xmlstream << "<math:mrow>";
xmlstream << "<math:mi>E</math:mi>";
xmlstream << "<math:mo math:stretchy=\"false\">=</math:mo>";
xmlstream << "<math:msup>";
xmlstream << "<math:mi math:fontstyle=\"italic\">mc</math:mi>";
xmlstream << "<math:mn>2</math:mn>";
xmlstream << "</math:msup>";
xmlstream << "</math:mrow>";
xmlstream << "<math:annotation math:encoding=\"StarMath 5.0\">E = mc^2 </math:annotation>";
xmlstream << "</math:semantics>";
xmlstream << "</math:math>";
xmldevice.close();
KoXmlDocument doc(false);
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
const char* mathNS = "http://www.w3.org/1998/Math/MathML";
// <math:math>
KoXmlElement mathElement;
mathElement = doc.documentElement();
QCOMPARE(mathElement.isNull(), false);
QCOMPARE(mathElement.isElement(), true);
QCOMPARE(mathElement.parentNode().isNull(), false);
QCOMPARE(mathElement.parentNode().toDocument() == doc, true);
QCOMPARE(mathElement.firstChild().isNull(), false);
QCOMPARE(mathElement.lastChild().isNull(), false);
QCOMPARE(mathElement.previousSibling().isNull(), false);
QCOMPARE(mathElement.nextSibling().isNull(), true);
QCOMPARE(mathElement.localName(), QString("math"));
// <math:semantics>
KoXmlElement semanticsElement;
semanticsElement = KoXml::namedItemNS(mathElement, mathNS, "semantics");
QCOMPARE(semanticsElement.isNull(), false);
QCOMPARE(semanticsElement.isElement(), true);
QCOMPARE(semanticsElement.parentNode().isNull(), false);
QCOMPARE(semanticsElement.parentNode().toElement() == mathElement, true);
QCOMPARE(semanticsElement.firstChild().isNull(), false);
QCOMPARE(semanticsElement.lastChild().isNull(), false);
QCOMPARE(semanticsElement.previousSibling().isNull(), true);
QCOMPARE(semanticsElement.nextSibling().isNull(), true);
QCOMPARE(semanticsElement.localName(), QString("semantics"));
// the same <math:semantics> but without namedItemNS
KoXmlElement semantics2Element;
semantics2Element = mathElement.firstChild().toElement();
QCOMPARE(semantics2Element.isNull(), false);
QCOMPARE(semantics2Element.isElement(), true);
QCOMPARE(semantics2Element.parentNode().isNull(), false);
QCOMPARE(semantics2Element.parentNode().toElement() == mathElement, true);
QCOMPARE(semantics2Element.firstChild().isNull(), false);
QCOMPARE(semantics2Element.lastChild().isNull(), false);
QCOMPARE(semantics2Element.previousSibling().isNull(), true);
QCOMPARE(semantics2Element.nextSibling().isNull(), true);
QCOMPARE(semantics2Element.localName(), QString("semantics"));
// <math:mrow>
KoXmlElement mrowElement;
mrowElement = semanticsElement.firstChild().toElement();
QCOMPARE(mrowElement.isNull(), false);
QCOMPARE(mrowElement.isElement(), true);
QCOMPARE(mrowElement.parentNode().isNull(), false);
QCOMPARE(mrowElement.parentNode().toElement() == semanticsElement, true);
QCOMPARE(mrowElement.firstChild().isNull(), false);
QCOMPARE(mrowElement.lastChild().isNull(), false);
QCOMPARE(mrowElement.previousSibling().isNull(), true);
QCOMPARE(mrowElement.nextSibling().isNull(), false);
QCOMPARE(mrowElement.localName(), QString("mrow"));
// <math:mi> for "E"
KoXmlElement miElement;
miElement = mrowElement.firstChild().toElement();
QCOMPARE(miElement.isNull(), false);
QCOMPARE(miElement.isElement(), true);
QCOMPARE(miElement.parentNode().isNull(), false);
QCOMPARE(miElement.parentNode().toElement() == mrowElement, true);
QCOMPARE(miElement.firstChild().isNull(), false);
QCOMPARE(miElement.lastChild().isNull(), false);
QCOMPARE(miElement.previousSibling().isNull(), true);
QCOMPARE(miElement.nextSibling().isNull(), false);
QCOMPARE(miElement.localName(), QString("mi"));
// <math:mo> for "="
KoXmlElement moElement;
moElement = miElement.nextSibling().toElement();
QCOMPARE(moElement.isNull(), false);
QCOMPARE(moElement.isElement(), true);
QCOMPARE(moElement.parentNode().isNull(), false);
QCOMPARE(moElement.parentNode().toElement() == mrowElement, true);
QCOMPARE(moElement.firstChild().isNull(), false);
QCOMPARE(moElement.lastChild().isNull(), false);
QCOMPARE(moElement.previousSibling().isNull(), false);
QCOMPARE(moElement.nextSibling().isNull(), false);
QCOMPARE(moElement.localName(), QString("mo"));
QCOMPARE(moElement.attributeNS(mathNS, "stretchy", ""), QString("false"));
// <math:msup> for "mc" and superscripted "2"
KoXmlElement msupElement;
msupElement = moElement.nextSibling().toElement();
QCOMPARE(msupElement.isNull(), false);
QCOMPARE(msupElement.isElement(), true);
QCOMPARE(msupElement.parentNode().isNull(), false);
QCOMPARE(msupElement.parentNode().toElement() == mrowElement, true);
QCOMPARE(msupElement.firstChild().isNull(), false);
QCOMPARE(msupElement.lastChild().isNull(), false);
QCOMPARE(msupElement.previousSibling().isNull(), false);
QCOMPARE(msupElement.nextSibling().isNull(), true);
QCOMPARE(msupElement.localName(), QString("msup"));
// <math:mi> inside the <math:msup> for "mc"
KoXmlElement mcElement;
mcElement = msupElement.firstChild().toElement();
QCOMPARE(mcElement.isNull(), false);
QCOMPARE(mcElement.isElement(), true);
QCOMPARE(mcElement.parentNode().isNull(), false);
QCOMPARE(mcElement.parentNode().toElement() == msupElement, true);
QCOMPARE(mcElement.firstChild().isNull(), false);
QCOMPARE(mcElement.lastChild().isNull(), false);
QCOMPARE(mcElement.previousSibling().isNull(), true);
QCOMPARE(mcElement.nextSibling().isNull(), false);
QCOMPARE(mcElement.localName(), QString("mi"));
QCOMPARE(mcElement.text(), QString("mc"));
QCOMPARE(mcElement.attributeNS(mathNS, "fontstyle", ""), QString("italic"));
// <math:mn> inside the <math:msup> for "2" (superscript)
KoXmlElement mnElement;
mnElement = mcElement.nextSibling().toElement();
QCOMPARE(mnElement.isNull(), false);
QCOMPARE(mnElement.isElement(), true);
QCOMPARE(mnElement.parentNode().isNull(), false);
QCOMPARE(mnElement.parentNode().toElement() == msupElement, true);
QCOMPARE(mnElement.firstChild().isNull(), false);
QCOMPARE(mnElement.lastChild().isNull(), false);
QCOMPARE(mnElement.previousSibling().isNull(), false);
QCOMPARE(mnElement.nextSibling().isNull(), true);
QCOMPARE(mnElement.localName(), QString("mn"));
QCOMPARE(mnElement.text(), QString("2"));
// <math:annotation>
KoXmlElement annotationElement;
annotationElement = semanticsElement.lastChild().toElement();
QCOMPARE(annotationElement.isNull(), false);
QCOMPARE(annotationElement.isElement(), true);
QCOMPARE(annotationElement.parentNode().isNull(), false);
QCOMPARE(annotationElement.parentNode().toElement() == semanticsElement, true);
QCOMPARE(annotationElement.firstChild().isNull(), false);
QCOMPARE(annotationElement.lastChild().isNull(), false);
QCOMPARE(annotationElement.previousSibling().isNull(), false);
QCOMPARE(annotationElement.nextSibling().isNull(), true);
QCOMPARE(annotationElement.localName(), QString("annotation"));
QCOMPARE(annotationElement.text(), QString("E = mc^2 "));
QCOMPARE(annotationElement.attributeNS(mathNS, "encoding", ""), QString("StarMath 5.0"));
}
void TestXmlReaderWithoutSpaces::testLargeOpenDocumentSpreadsheet()
{
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
int sheetCount = 4;
int rowCount = 200;
int colCount = 200 / 16;
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
// content.xml
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
xmlstream << "<office:document-content ";
xmlstream << "xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" ";
xmlstream << "xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" ";
xmlstream << "xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" >";
xmlstream << "<office:body>";
xmlstream << "<office:spreadsheet>";
for (int i = 0; i < sheetCount; i++) {
QString sheetName = QString("Sheet%1").arg(i + 1);
xmlstream << "<table:table table:name=\"" << sheetName;
xmlstream << "\" table:print=\"false\">";
for (int j = 0; j < rowCount; j++) {
xmlstream << "<table:table-row>";
for (int k = 0; k < colCount; k++) {
xmlstream << "<table:table-cell office:value-type=\"string\">";
xmlstream << "<text:p>Hello, world</text:p>";
xmlstream << "</table:table-cell>";
}
xmlstream << "</table:table-row>";
}
xmlstream << "</table:table>";
}
xmlstream << "</office:spreadsheet>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
printf("Raw XML size: %lld KB\n", xmldevice.size() / 1024);
QTime timer;
#if 0
// just to test parsing speed with plain dumb handler
QXmlStreamReader *reader = new QXmlStreamReader(xmldevice);
reader->setNamespaceProcessing(true);
timer.start();
ParseError error = parseDocument(*reader, doc);
printf("Large spreadsheet: QXmlStreamReader parsing time is %d ms\n", timer.elapsed());
delete reader;
xmldevice.seek(0);
#endif
KoXmlDocument doc(false);
timer.start();
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
if (!errorMsg.isEmpty()) {
qDebug("Error: %s", qPrintable(errorMsg));
return;
}
printf("Large spreadsheet: KoXmlDocument parsing time is %d ms\n", timer.elapsed());
// release memory taken by the XML document content
//xmlstream.setDevice( 0 );
// namespaces that will be used
QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
// <office:body>
KoXmlElement bodyElement;
bodyElement = contentElement.firstChild().toElement();
QCOMPARE(bodyElement.isNull(), false);
QCOMPARE(bodyElement.isElement(), true);
QCOMPARE(bodyElement.localName(), QString("body"));
// <office:spreadsheet>
KoXmlElement spreadsheetElement;
spreadsheetElement = bodyElement.firstChild().toElement();
QCOMPARE(spreadsheetElement.isNull(), false);
QCOMPARE(spreadsheetElement.isElement(), true);
QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet"));
// now we visit every sheet, every row, every cell
timer.start();
KoXmlElement tableElement;
tableElement = spreadsheetElement.firstChild().toElement();
for (int table = 0; table < sheetCount; table++) {
QString tableName = QString("Sheet%1").arg(table + 1);
QCOMPARE(tableElement.isNull(), false);
QCOMPARE(tableElement.isElement(), true);
QCOMPARE(tableElement.localName(), QString("table"));
QCOMPARE(tableElement.hasAttributeNS(tableNS, "name"), true);
QCOMPARE(tableElement.attributeNS(tableNS, "name", ""), tableName);
QCOMPARE(tableElement.attributeNS(tableNS, "print", ""), QString("false"));
// load everything for this table
//KoXml::load( tableElement, 99 );
QCOMPARE(tableElement.parentNode().isNull(), false);
QCOMPARE(tableElement.parentNode() == spreadsheetElement, true);
QCOMPARE(tableElement.firstChild().isNull(), false);
QCOMPARE(tableElement.lastChild().isNull(), false);
KoXmlElement rowElement;
rowElement = tableElement.firstChild().toElement();
for (int row = 0; row < rowCount; row++) {
QCOMPARE(rowElement.isNull(), false);
QCOMPARE(rowElement.isElement(), true);
QCOMPARE(rowElement.localName(), QString("table-row"));
QCOMPARE(rowElement.parentNode().isNull(), false);
QCOMPARE(rowElement.parentNode() == tableElement, true);
QCOMPARE(rowElement.firstChild().isNull(), false);
QCOMPARE(rowElement.lastChild().isNull(), false);
KoXmlElement cellElement;
cellElement = rowElement.firstChild().toElement();
for (int col = 0; col < colCount; col++) {
QCOMPARE(cellElement.isNull(), false);
QCOMPARE(cellElement.isElement(), true);
QCOMPARE(cellElement.localName(), QString("table-cell"));
QCOMPARE(cellElement.text(), QString("Hello, world"));
QCOMPARE(cellElement.hasAttributeNS(officeNS, "value-type"), true);
QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string"));
QCOMPARE(cellElement.parentNode().isNull(), false);
QCOMPARE(cellElement.parentNode() == rowElement, true);
QCOMPARE(cellElement.firstChild().isNull(), false);
QCOMPARE(cellElement.lastChild().isNull(), false);
cellElement = cellElement.nextSibling().toElement();
}
//KoXml::unload( rowElement );
rowElement = rowElement.nextSibling().toElement();
}
KoXml::unload(tableElement);
tableElement = tableElement.nextSibling().toElement();
}
printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed());
}
void TestXmlReaderWithoutSpaces::testExternalOpenDocumentSpreadsheet(const QString& filename)
{
QProcess unzip;
QStringList arguments;
arguments << "-o" << filename << "content.xml";
printf("Unzipping content.xml from %s...\n", qPrintable(filename));
unzip.start("unzip", arguments);
if (!unzip.waitForStarted()) {
printf("Error: can't invoke unzip. Check your PATH and installation!\n\n");
return;
}
if (!unzip.waitForFinished()) {
printf("Error: unzip failed, can't continue!\n\n");
return;
}
printf("Procesing content.xml....\n");
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QFile xmlfile("content.xml");
if (!xmlfile.open(QFile::ReadOnly)) {
printf("Can not open file '%s'\n", qPrintable(filename));
return;
}
printf("Test external file: %s %lld KB\n", qPrintable(filename), xmlfile.size() / 1024);
QTime timer;
timer.start();
KoXmlDocument doc(false);
QCOMPARE(KoXml::setDocument(doc, &xmlfile, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
printf("External spreadsheet: parsing time is %d ms\n", timer.elapsed());
// namespaces that will be used
QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
// <office:document-content>
KoXmlElement contentElement;
contentElement = doc.documentElement();
QCOMPARE(contentElement.isNull(), false);
QCOMPARE(contentElement.isElement(), true);
QCOMPARE(contentElement.localName(), QString("document-content"));
long totalCellCount = 0;
KoXmlElement bodyElement;
forEachElement(bodyElement, contentElement) {
// <office:body>
if (bodyElement.localName() != QString("body"))
continue;
// now we iterate inside the body
timer.start();
// <office:spreadsheet>
KoXmlElement spreadsheetElement;
spreadsheetElement = bodyElement.firstChild().toElement();
QCOMPARE(spreadsheetElement.isNull(), false);
QCOMPARE(spreadsheetElement.isElement(), true);
QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet"));
// now we visit every sheet
long tableCount = -1;
KoXmlElement tableElement;
tableElement = spreadsheetElement.firstChild().toElement();
for (;;) {
if (tableElement.isNull())
break;
if (tableElement.localName() != QString("table")) {
tableElement = tableElement.nextSibling().toElement();
continue;
}
QString tableName = tableElement.attributeNS(tableNS, "name", "");
tableCount++;
printf(" sheet #%ld (%s): ", tableCount + 1, qPrintable(tableName));
// use to preload everything in this sheet, will slow it down!
// KoXml::load( tableElement, 50 );
long rowCount = -1;
long cellCount = -1;
KoXmlElement rowElement;
rowElement = tableElement.firstChild().toElement();
for (;;) {
if (rowElement.isNull())
break;
if (rowElement.localName() != QString("table-row")) {
rowElement = rowElement.nextSibling().toElement();
continue;
}
rowCount++;
KoXml::load(rowElement, 4);
QCOMPARE(rowElement.isElement(), true);
QCOMPARE(rowElement.localName(), QString("table-row"));
QCOMPARE(rowElement.parentNode().isNull(), false);
QCOMPARE(rowElement.parentNode() == tableElement, true);
KoXmlElement cellElement;
cellElement = rowElement.firstChild().toElement();
for (; ;) {
if (cellElement.isNull())
break;
if (cellElement.localName() != QString("table-cell")) {
cellElement = cellElement.nextSibling().toElement();
continue;
}
cellCount++;
QCOMPARE(cellElement.isNull(), false);
QCOMPARE(cellElement.isElement(), true);
QCOMPARE(cellElement.localName(), QString("table-cell"));
QString text1 = cellElement.text();
QString text2 = cellElement.text();
QCOMPARE(text1, text2);
QString type1 = cellElement.attributeNS(officeNS, "value-type", QString());
QString type2 = cellElement.attributeNS(officeNS, "value-type", QString());
QCOMPARE(type1, type2);
QString style1 = cellElement.attributeNS(tableNS, "style-name", QString());
QString style2 = cellElement.attributeNS(tableNS, "style-name", QString());
QCOMPARE(style1, style2);
QCOMPARE(cellElement.parentNode().isNull(), false);
QCOMPARE(cellElement.parentNode() == rowElement, true);
cellElement = cellElement.nextSibling().toElement();
}
// better not to unload, freeing memory takes time
KoXml::unload(rowElement);
rowElement = rowElement.nextSibling().toElement();
}
printf(" %ld rows, %ld cells\n", rowCount + 1, cellCount + 1);
totalCellCount += (cellCount + 1);
// IMPORTANT: helps minimizing memory usage !!
// we do not need that element anymore, so just throw it away
KoXml::unload(tableElement);
tableElement = tableElement.nextSibling().toElement();
}
KoXml::unload(spreadsheetElement);
}
printf("Total number of cells: %ld\n", totalCellCount);
int elapsed = timer.elapsed();
printf("External spreadsheet: iterating time is %d ms\n", elapsed);
if (elapsed > 0)
printf(" approx. %ld cells/second\n", totalCellCount*1000 / elapsed);
// uncomment to check the XML
xmlfile.remove();
}
QTEST_GUILESS_MAIN(TestXmlReaderWithoutSpaces)
#include <TestXmlReaderWithoutSpaces.moc>
diff --git a/src/libs/odf/tests/TestXmlWriter.cpp b/src/libs/odf/tests/TestXmlWriter.cpp
index 80f1d6a3..4344a31b 100644
--- a/src/libs/odf/tests/TestXmlWriter.cpp
+++ b/src/libs/odf/tests/TestXmlWriter.cpp
@@ -1,254 +1,255 @@
/* This file is part of the KDE project
* Copyright (C) 2004 David Faure <faure@kde.org>
* Copyright 2008 Thomas Zander <zander@kde.org>
*
* 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 <KoXmlWriter.h>
#include <QString>
#include <QBuffer>
#include <QTest>
class TestXmlWriter : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testDocytype();
void testEmtpyElement();
void testAttributes();
void testIndent();
void testTextNode();
void testTextSpan();
void testTextSpanWithTabCache();
void testProcessingInstruction();
void testAddManifestEntry();
void testEscapingLongString();
void testEscalingLongString2();
void testConfig();
void speedTest();
private:
void setup(const char *publicId = 0, const char *systemId = 0);
QString content();
KoXmlWriter *writer;
QBuffer *buffer;
};
void TestXmlWriter::setup(const char *publicId, const char *systemId)
{
buffer = new QBuffer();
buffer->open( QIODevice::WriteOnly );
writer = new KoXmlWriter( buffer );
writer->startDocument( "dummy", publicId, systemId );
writer->startElement( "dummy" );
}
QString TestXmlWriter::content()
{
writer->endElement();
writer->endDocument();
buffer->putChar( '\0' ); /*null-terminate*/
buffer->close();
QString stringContent = QString::fromUtf8(buffer->data());
int index = stringContent.indexOf("<dummy");
Q_ASSERT(index);
index = stringContent.indexOf('>', index);
stringContent = stringContent.mid(index+1, stringContent.length() - index - 11).trimmed();
return stringContent;
}
void TestXmlWriter::testDocytype()
{
setup("foo", "bar");
QCOMPARE(content(), QString());
QString stringContent = QString::fromUtf8(buffer->data());
QCOMPARE(stringContent, QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE dummy PUBLIC \"foo\" \"bar\">\n<dummy/>\n"));
}
void TestXmlWriter::testAttributes()
{
setup();
writer->startElement("test");
writer->addAttribute("a", "val");
writer->addAttribute("b", "<\">");
writer->addAttribute("c", -42);
writer->addAttribute("d", 1234.56789012345);
writer->addAttributePt("e", 1234.56789012345);
writer->addAttribute("f", false);
writer->addAttribute("g", true);
writer->endElement();
QCOMPARE(content(), QString("<test a=\"val\" b=\"&lt;&quot;&gt;\" c=\"-42\" d=\"1234.56789012345\" e=\"1234.56789012345pt\" f=\"false\" g=\"true\"/>"));
}
void TestXmlWriter::testEmtpyElement()
{
setup();
writer->startElement("m");
writer->endElement();
QCOMPARE(content(), QString("<m/>"));
}
void TestXmlWriter::testIndent()
{
setup();
writer->startElement("a");
writer->startElement("b");
writer->startElement("c");
writer->endElement();
writer->endElement();
writer->endElement();
QCOMPARE(content(), QString("<a>\n <b>\n <c/>\n </b>\n </a>"));
}
void TestXmlWriter::testTextNode()
{
setup();
writer->startElement("a");
writer->startElement("b", false /*no indent*/);
writer->startElement("c");
writer->endElement();
writer->addTextNode("te");
writer->addTextNode("xt");
writer->endElement();
writer->endElement();
QCOMPARE(content(), QString("<a>\n <b><c/>text</b>\n </a>"));
}
void TestXmlWriter::testTextSpan()
{
setup();
writer->startElement("p", false /*no indent*/);
writer->addTextSpan(QString::fromLatin1(" \t\n foo "));
writer->endElement();
QCOMPARE(content(), QString("<p><text:s text:c=\"3\"/><text:tab/><text:line-break/> foo<text:s text:c=\"2\"/></p>"));
}
void TestXmlWriter::testTextSpanWithTabCache()
{
setup();
writer->startElement("p", false /*no indent*/);
QMap<int, int> tabCache;
tabCache.insert(3, 0);
writer->addTextSpan(QString::fromUtf8(" \t\n foö "), tabCache);
writer->endElement();
QCOMPARE(content(), QString::fromUtf8("<p><text:s text:c=\"3\"/><text:tab text:tab-ref=\"1\"/>"
"<text:line-break/> foö<text:s text:c=\"2\"/></p>"));
}
void TestXmlWriter::testProcessingInstruction()
{
setup();
writer->startElement("p", false /*no indent*/);
writer->addProcessingInstruction("opendocument foobar");
writer->addTextSpan(QString::fromLatin1("foo"));
writer->endElement();
QCOMPARE(content(), QString("<p><?opendocument foobar?>foo</p>"));
}
void TestXmlWriter::testAddManifestEntry()
{
setup();
writer->addManifestEntry(QString::fromLatin1("foo/bar/blah"), QString::fromLatin1("mime/type"));
QCOMPARE(content(), QString("<manifest:file-entry manifest:media-type=\"mime/type\" "
"manifest:full-path=\"foo/bar/blah\"/>"));
}
void TestXmlWriter::testEscapingLongString()
{
int sz = 15000; // must be more than KoXmlWriter::s_escapeBufferLen
QString x(sz);
x.fill('x', sz);
x += '&';
setup();
writer->startElement("test");
writer->addAttribute("a", x);
writer->endElement();
QString expected = "<test a=\"";
expected += x + "amp;\"/>";
QCOMPARE(content(), QString(expected));
}
void TestXmlWriter::testEscalingLongString2()
{
QString longPath;
for (uint i = 0 ; i < 1000 ; ++i)
longPath += QString::fromLatin1("M10 10L20 20 ");
setup();
writer->startElement("test");
writer->addAttribute("a", longPath);
writer->endElement();
QString expected = "<test a=\"";
expected += longPath.toUtf8() + "\"/>";
QCOMPARE(content(), expected);
}
void TestXmlWriter::testConfig()
{
setup();
const bool val = true;
const int num = 1;
const qreal numdouble = 5.0;
writer->addConfigItem(QString::fromLatin1("TestConfigBool"), val);
writer->addConfigItem(QString::fromLatin1("TestConfigInt"), num);
writer->addConfigItem(QString::fromLatin1("TestConfigDouble"), numdouble);
QCOMPARE(content(), QString("<config:config-item config:name=\"TestConfigBool\""
" config:type=\"boolean\">true</config:config-item>\n"
" <config:config-item config:name=\"TestConfigInt\" config:type=\"int\">1</config:config-item>\n"
" <config:config-item config:name=\"TestConfigDouble\" config:type=\"double\">5</config:config-item>"));
}
static const int NumParagraphs = 30000;
void TestXmlWriter::speedTest()
{
QTime time;
time.start();
QString paragText = QString::fromUtf8("This is the text of the paragraph. I'm including a euro sign to test encoding issues: €");
QString styleName = "Heading 1";
QFile out(QString::fromLatin1("out5.xml"));
if (out.open(QIODevice::WriteOnly)) {
KoXmlWriter writer(&out);
writer.startDocument("rootelem");
writer.startElement("rootelem");
for (int i = 0 ; i < NumParagraphs ; ++i) {
writer.startElement("paragraph");
writer.addAttribute("text:style-name", styleName);
writer.addTextNode(paragText);
writer.endElement();
}
writer.endElement();
writer.endDocument();
}
out.close();
out.remove();
qDebug("writing %i XML elements using KoXmlWriter: %i ms", NumParagraphs, time.elapsed());
// TODO we might want to convert this into a QBenchmark test
}
QTEST_GUILESS_MAIN(TestXmlWriter)
#include <TestXmlWriter.moc>
diff --git a/src/libs/odf/tests/kodomtest.cpp b/src/libs/odf/tests/kodomtest.cpp
index 81600062..ded6ba34 100644
--- a/src/libs/odf/tests/kodomtest.cpp
+++ b/src/libs/odf/tests/kodomtest.cpp
@@ -1,127 +1,128 @@
/* This file is part of the KDE project
Copyright (C) 2004 David Faure <faure@kde.org>
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 "kodomtest.h"
#include "KoXmlReader.h"
#include <QTest>
static QString const KoXmlNS_office() {
return QStringLiteral("urn:oasis:names:tc:opendocument:xmlns:office:1.0");
}
static QString const KoXmlNS_text() {
return QStringLiteral("urn:oasis:names:tc:opendocument:xmlns:text:1.0");
}
//static void debugElemNS( const QDomElement& elem )
//{
// qDebug( "nodeName=%s tagName=%s localName=%s prefix=%s namespaceURI=%s", elem.nodeName().latin1(), elem.tagName().latin1(), elem.localName().latin1(), elem.prefix().latin1(), elem.namespaceURI().latin1() );
//}
void KoDomTest::initTestCase()
{
const QByteArray xml = QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<o:document-content xmlns:o=\"")
+ KoXmlNS_office().toUtf8()
+ "\" xmlns=\"" + KoXmlNS_text().toUtf8()
+ "\" xmlns:text=\"" + KoXmlNS_text().toUtf8()
+ "\">\n"
"<o:body><p text:style-name=\"L1\">foobar</p><p>2nd</p></o:body><o:styles></o:styles>\n"
"</o:document-content>\n";
QVERIFY(m_doc.setContent(xml, true /* namespace processing */));
}
void KoDomTest::testQDom()
{
KoXmlElement docElem = m_doc.documentElement();
//debugElemNS( docElem );
QCOMPARE(docElem.nodeName(), QString("o:document-content"));
QCOMPARE(docElem.tagName(), QString("document-content"));
QCOMPARE(docElem.localName(), QString("document-content"));
QCOMPARE(docElem.prefix(), QString("o"));
QCOMPARE(docElem.namespaceURI(), KoXmlNS_office());
KoXmlElement elem = KoXml::namedItemNS(docElem, KoXmlNS_office().toUtf8(), "body");
//debugElemNS( elem );
QCOMPARE(elem.tagName(), QString("body"));
QCOMPARE(elem.localName(), QString("body"));
QCOMPARE(elem.prefix(), QString("o"));
QCOMPARE(elem.namespaceURI(), KoXmlNS_office());
KoXmlNode n = elem.firstChild();
for (; !n.isNull() ; n = n.nextSibling()) {
if (!n.isElement())
continue;
KoXmlElement e = n.toElement();
//debugElemNS( e );
QCOMPARE(e.tagName(), QString("p"));
QCOMPARE(e.localName(), QString("p"));
QVERIFY(e.prefix().isEmpty());
QCOMPARE(e.namespaceURI(), KoXmlNS_text());
}
}
void KoDomTest::testKoDom()
{
KoXmlElement docElem = KoXml::namedItemNS(m_doc, KoXmlNS_office().toUtf8(), "document-content");
QCOMPARE(docElem.isNull(), false);
QCOMPARE(docElem.localName(), QString("document-content"));
QCOMPARE(docElem.namespaceURI(), KoXmlNS_office());
KoXmlElement body = KoXml::namedItemNS(docElem, KoXmlNS_office().toUtf8(), "body");
QCOMPARE(body.isNull(), false);
QCOMPARE(body.localName(), QString("body"));
QCOMPARE(body.namespaceURI(), KoXmlNS_office());
KoXmlElement p = KoXml::namedItemNS(body, KoXmlNS_text().toUtf8(), "p");
QCOMPARE(p.isNull(), false);
QCOMPARE(p.localName(), QString("p"));
QCOMPARE(p.namespaceURI(), KoXmlNS_text());
const KoXmlElement officeStyle = KoXml::namedItemNS(docElem, KoXmlNS_office().toUtf8(), "styles");
QCOMPARE(officeStyle.isNull(), false);
// Look for a non-existing element
KoXmlElement notexist = KoXml::namedItemNS(body, KoXmlNS_text().toUtf8(), "notexist");
QVERIFY(notexist.isNull());
int count = 0;
KoXmlElement elem;
forEachElement(elem, body) {
QCOMPARE(elem.localName(), QString("p"));
QCOMPARE(elem.namespaceURI(), KoXmlNS_text());
++count;
}
QCOMPARE(count, 2);
// Attributes
// ### Qt bug: it doesn't work if using style-name instead of text:style-name in the XML
const QString styleName = p.attributeNS(KoXmlNS_text(), "style-name", QString());
// qDebug( "%s", qPrintable( styleName ) );
QCOMPARE(styleName, QString("L1"));
}
QTEST_MAIN(KoDomTest)
diff --git a/src/libs/odf/writeodf/helpers.cpp b/src/libs/odf/writeodf/helpers.cpp
index bd9e97d2..4a744ee7 100644
--- a/src/libs/odf/writeodf/helpers.cpp
+++ b/src/libs/odf/writeodf/helpers.cpp
@@ -1,118 +1,119 @@
/* This file is part of the KDE project
Copyright (C) 2013 Jos van den Oever <jos@vandenoever.info>
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 "helpers.h"
using namespace writeodf;
namespace {
template <typename T>
void
addTab(T& e, int ref) {
text_tab tab = e.add_text_tab();
if (ref >= 0) {
tab.set_text_tab_ref(ref);
}
}
}
void writeodf::addTextSpan(group_paragraph_content& content, const QString& text, const QMap<int, int>& tabCache)
{
int len = text.length();
int nrSpaces = 0; // number of consecutive spaces
bool leadingSpace = false;
QString str;
str.reserve(len);
// Accumulate chars either in str or in nrSpaces (for spaces).
// Flush str when writing a subelement (for spaces or for another reason)
// Flush nrSpaces when encountering two or more consecutive spaces
for (int i = 0; i < len ; ++i) {
const QChar ch = text[i];
ushort unicode = ch.unicode();
if (unicode == ' ') {
if (i == 0) {
leadingSpace = true;
}
++nrSpaces;
} else {
if (nrSpaces > 0) {
// For the first space we use ' '.
// "it is good practice to use (text:s) for the second and all following SPACE
// characters in a sequence." (per the ODF spec)
// however, per the HTML spec, "authors should not rely on user agents to render
// white space immediately after a start tag or immediately before an end tag"
// (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...)
if (!leadingSpace) {
str += ' ';
--nrSpaces;
}
if (nrSpaces > 0) { // there are more spaces
if (!str.isEmpty()) {
content.addTextNode(str);
}
str.clear();
text_s s = content.add_text_s();
if (nrSpaces > 1) { // it's 1 by default
s.set_text_c(nrSpaces);
}
}
}
nrSpaces = 0;
leadingSpace = false;
switch (unicode) {
case '\t':
if (!str.isEmpty()) {
content.addTextNode(str);
}
str.clear();
addTab(content, tabCache.contains(i) ?tabCache[i] + 1 :-1);
break;
// gracefully handle \f form feed in text input.
// otherwise the xml will not be valid.
// \f can be added e.g. in ascii import filter.
case '\f':
case '\n':
case QChar::LineSeparator:
if (!str.isEmpty()) {
content.addTextNode(str);
}
str.clear();
content.add_text_line_break();
break;
default:
// don't add stuff that is not allowed in xml. The stuff we need we have already handled above
if (ch.unicode() >= 0x20) {
str += ch;
}
break;
}
}
}
// either we still have text in str or we have spaces in nrSpaces
if (!str.isEmpty()) {
content.addTextNode(str);
}
if (nrSpaces > 0) { // there are more spaces
text_s s = content.add_text_s();
if (nrSpaces > 1) { // it's 1 by default
s.set_text_c(nrSpaces);
}
}
}
diff --git a/src/libs/plugin/KoPluginLoader.cpp b/src/libs/plugin/KoPluginLoader.cpp
index a0eabe09..ad78c973 100644
--- a/src/libs/plugin/KoPluginLoader.cpp
+++ b/src/libs/plugin/KoPluginLoader.cpp
@@ -1,210 +1,211 @@
/* This file is part of the KDE project
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Thomas Zander <zander@kde.org>
* Copyright (c) 2016 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* 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 "KoPluginLoader.h"
#include <config.h> // CALLIGRA_OLD_PLUGIN_METADATA
#include <KConfig>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KPluginFactory>
#include <KPluginLoader>
#include <QCoreApplication>
#include <QJsonObject>
#include <QPluginLoader>
#include <QLoggingCategory>
#include <QDebug>
const QLoggingCategory &PLUGIN_LOG()
{
static const QLoggingCategory category("calligra.lib.plugin");
return category;
}
#define debugPlugin qCDebug(PLUGIN_LOG)
#define warnPlugin qCWarning(PLUGIN_LOG)
class KoPluginLoaderImpl : public QObject
{
Q_OBJECT
public:
QStringList loadedDirectories;
};
Q_GLOBAL_STATIC(KoPluginLoaderImpl, pluginLoaderInstance)
void KoPluginLoader::load(const QString & directory, const PluginsConfig &config, QObject* owner)
{
// Don't load the same plugins again
if (pluginLoaderInstance->loadedDirectories.contains(directory)) {
return;
}
pluginLoaderInstance->loadedDirectories << directory;
QList<QPluginLoader *> offers = KoPluginLoader::pluginLoaders(directory);
QList<QPluginLoader *> plugins;
bool configChanged = false;
QList<QString> blacklist; // what we will save out afterwards
if (config.whiteList && config.blacklist && config.group) {
debugPlugin << "Loading" << directory << "with checking the config";
KConfigGroup configGroup(KSharedConfig::openConfig(), config.group);
QList<QString> whiteList = configGroup.readEntry(config.whiteList, config.defaults);
QList<QString> knownList;
// if there was no list of defaults; all plugins are loaded.
const bool firstStart = !config.defaults.isEmpty() && !configGroup.hasKey(config.whiteList);
knownList = configGroup.readEntry(config.blacklist, knownList);
if (firstStart) {
configChanged = true;
}
foreach(QPluginLoader *loader, offers) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
json = json.value("KPlugin").toObject();
const QString pluginName = json.value("Id").toString();
if (pluginName.isEmpty()) {
warnPlugin << "Loading plugin" << loader->fileName() << "failed, has no X-KDE-PluginInfo-Name.";
continue;
}
if (whiteList.contains(pluginName)) {
plugins.append(loader);
} else if (!firstStart && !knownList.contains(pluginName)) { // also load newly installed plugins.
plugins.append(loader);
configChanged = true;
} else {
blacklist << pluginName;
}
}
} else {
plugins = offers;
}
QMap<QString, QPluginLoader *> serviceNames;
foreach(QPluginLoader *loader, plugins) {
if (serviceNames.contains(loader->fileName())) { // duplicate
QJsonObject json2 = loader->metaData().value("MetaData").toObject();
QVariant pluginVersion2 = json2.value("X-Flake-PluginVersion").toVariant();
if (pluginVersion2.isNull()) { // just take the first one found...
continue;
}
QPluginLoader *currentLoader = serviceNames.value(loader->fileName());
QJsonObject json = currentLoader->metaData().value("MetaData").toObject();
QVariant pluginVersion = json.value("X-Flake-PluginVersion").toVariant();
if (!(pluginVersion.isNull() || pluginVersion.toInt() < pluginVersion2.toInt())) {
continue; // replace the old one with this one, since its newer.
}
}
serviceNames.insert(loader->fileName(), loader);
}
QList<QString> whiteList;
foreach(QPluginLoader *loader, serviceNames) {
KPluginFactory *factory = qobject_cast<KPluginFactory *>(loader->instance());
QObject *plugin = factory->create<QObject>(owner ? owner : pluginLoaderInstance, QVariantList());
if (plugin) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
json = json.value("KPlugin").toObject();
const QString pluginName = json.value("Id").toString();
whiteList << pluginName;
debugPlugin << "Loaded plugin" << loader->fileName() << owner;
if (!owner) {
delete plugin;
}
} else {
warnPlugin << "Loading plugin" << loader->fileName() << "failed, " << loader->errorString();
}
}
if (configChanged && config.whiteList && config.blacklist && config.group) {
KConfigGroup configGroup(KSharedConfig::openConfig(), config.group);
configGroup.writeEntry(config.whiteList, whiteList);
configGroup.writeEntry(config.blacklist, blacklist);
}
qDeleteAll(offers);
}
QList<KPluginFactory *> KoPluginLoader::instantiatePluginFactories(const QString & directory)
{
QList<KPluginFactory *> pluginFactories;
const QList<QPluginLoader *> offers = KoPluginLoader::pluginLoaders(directory);
foreach(QPluginLoader *pluginLoader, offers) {
QObject* pluginInstance = pluginLoader->instance();
if (!pluginInstance) {
warnPlugin << "Loading plugin" << pluginLoader->fileName() << "failed, " << pluginLoader->errorString();
continue;
}
KPluginFactory *factory = qobject_cast<KPluginFactory *>(pluginInstance);
if (factory == 0) {
warnPlugin << "Expected a KPluginFactory, got a" << pluginInstance->metaObject()->className();
delete pluginInstance;
continue;
}
pluginFactories.append(factory);
}
qDeleteAll(offers);
return pluginFactories;
}
QList<QPluginLoader *> KoPluginLoader::pluginLoaders(const QString &directory, const QString &mimeType)
{
QList<QPluginLoader *>list;
KPluginLoader::forEachPlugin(directory, [&](const QString &pluginPath) {
debugPlugin << "Trying to load" << pluginPath;
QPluginLoader *loader = new QPluginLoader(pluginPath);
QJsonObject metaData = loader->metaData().value("MetaData").toObject();
if (metaData.isEmpty()) {
debugPlugin << pluginPath << "has no MetaData!";
return;
}
if (!mimeType.isEmpty()) {
#ifdef CALLIGRA_OLD_PLUGIN_METADATA
QStringList mimeTypes = metaData.value("MimeType").toString().split(';');
mimeTypes += metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(QLatin1Char(','));
#else
QJsonObject pluginData = metaData.value("KPlugin").toObject();
QStringList mimeTypes = pluginData.value("MimeTypes").toVariant().toStringList();
mimeTypes += metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList();
#endif
mimeTypes += metaData.value("X-KDE-NativeMimeType").toString();
if (! mimeTypes.contains(mimeType)) {
return;
}
}
list.append(loader);
});
return list;
}
#include "KoPluginLoader.moc"
diff --git a/src/libs/store/KoDirectoryStore.cpp b/src/libs/store/KoDirectoryStore.cpp
index a4629048..1652bc09 100644
--- a/src/libs/store/KoDirectoryStore.cpp
+++ b/src/libs/store/KoDirectoryStore.cpp
@@ -1,121 +1,122 @@
/* This file is part of the KDE project
Copyright (C) 2002, 2006 David Faure <faure@kde.org>
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 "KoDirectoryStore.h"
#include "KoStore_p.h"
#include <QFile>
#include <QDir>
#include <StoreDebug.h>
// HMMM... I used QFile and QDir.... but maybe this should be made network transparent?
KoDirectoryStore::KoDirectoryStore(const QString& path, Mode mode, bool writeMimetype)
: KoStore(mode, writeMimetype)
, m_basePath(path)
{
//debugStore << "path:" << path
//debugStore << "base path:" << m_basePath;
init();
}
KoDirectoryStore::~KoDirectoryStore()
{
}
void KoDirectoryStore::init()
{
Q_D(KoStore);
if (!m_basePath.endsWith('/'))
m_basePath += '/';
m_currentPath = m_basePath;
QDir dir(m_basePath);
if (dir.exists()) {
d->good = true;
return;
}
// Dir doesn't exist. If reading -> error. If writing -> create.
if (d->mode == Write && dir.mkpath(m_basePath)) {
debugStore << "KoDirectoryStore::init Directory created:" << m_basePath;
d->good = true;
}
}
bool KoDirectoryStore::openReadOrWrite(const QString& name, QIODevice::OpenModeFlag iomode)
{
Q_D(KoStore);
//debugStore <<"KoDirectoryStore::openReadOrWrite m_currentPath=" << m_currentPath <<" name=" << name;
int pos = name.lastIndexOf('/');
if (pos != -1) { // there are subdirs in the name -> maybe need to create them, when writing
pushDirectory(); // remember where we were
enterAbsoluteDirectory(QString());
//debugStore <<"KoDirectoryStore::openReadOrWrite entering" << name.left(pos);
bool ret = enterDirectory(name.left(pos));
popDirectory();
if (!ret)
return false;
}
d->stream = new QFile(m_basePath + name);
if (!d->stream->open(iomode)) {
delete d->stream;
d->stream = 0;
return false;
}
if (iomode == QIODevice::ReadOnly)
d->size = d->stream->size();
return true;
}
bool KoDirectoryStore::enterRelativeDirectory(const QString& dirName)
{
QDir origDir(m_currentPath);
m_currentPath += dirName;
if (!m_currentPath.endsWith('/'))
m_currentPath += '/';
//debugStore <<"KoDirectoryStore::enterRelativeDirectory m_currentPath now" << m_currentPath;
QDir newDir(m_currentPath);
if (newDir.exists())
return true;
// Dir doesn't exist. If reading -> error. If writing -> create.
if (mode() == Write && origDir.mkdir(dirName)) {
debugStore << "Created" << dirName << " under" << origDir.absolutePath();
return true;
}
return false;
}
bool KoDirectoryStore::enterAbsoluteDirectory(const QString& path)
{
m_currentPath = m_basePath + path;
//debugStore <<"KoDirectoryStore::enterAbsoluteDirectory" << m_currentPath;
QDir newDir(m_currentPath);
Q_ASSERT(newDir.exists()); // We've been there before, therefore it must exist.
return newDir.exists();
}
bool KoDirectoryStore::fileExists(const QString& absPath) const
{
debugStore << "KoDirectoryStore::fileExists" << m_basePath + absPath;
return QFile::exists(m_basePath + absPath);
}
diff --git a/src/libs/store/KoEncryptedStore.cpp b/src/libs/store/KoEncryptedStore.cpp
index c74fcf35..db7d5641 100644
--- a/src/libs/store/KoEncryptedStore.cpp
+++ b/src/libs/store/KoEncryptedStore.cpp
@@ -1,850 +1,851 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thomas Schaap <thomas.schaap@kdemail.net>
Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
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.
*/
#ifdef QCA2
+// clazy:excludeall=qstring-arg
#include "KoEncryptedStore.h"
#include "KoEncryptionChecker.h"
#include "KoStore_p.h"
#include "KoXmlReader.h"
#include <KoXmlNS.h>
#include <QString>
#include <QByteArray>
#include <QIODevice>
#include <QWidget>
#include <QBuffer>
#include <kpassworddialog.h>
#include <knewpassworddialog.h>
#include <kwallet.h>
#include <klocalizedstring.h>
#include <kfilterdev.h>
#include <kmessage.h>
#include <kmessagebox.h>
#include <kzip.h>
#include <KoNetAccess.h>
#include <QTemporaryFile>
#include <StoreDebug.h>
struct KoEncryptedStore_EncryptionData {
// Needed for Key Derivation
QCA::SecureArray salt;
unsigned int iterationCount;
// Needed for enc/decryption
QCA::SecureArray initVector;
// Needed for (optional) password-checking
QCA::SecureArray checksum;
// checksumShort is set to true if the checksum-algorithm is SHA1/1K, which basically means we only use the first 1024 bytes of the unencrypted file to check against (see also http://www.openoffice.org/servlets/ReadMsg?list=dev&msgNo=17498)
bool checksumShort;
// The size of the uncompressed file
qint64 filesize;
};
// TODO: Discuss naming of this filer in saving-dialogues
// TODO: Discuss possibility of allowing programs to remember the password after opening to enable them to supply it when saving
// TODO: Discuss autosaving and password/leakage-problem (currently: hardcoded no autosave)
namespace
{
const char MANIFEST_FILE[] = "META-INF/manifest.xml";
const char META_FILE[] = "meta.xml";
const char THUMBNAIL_FILE[] = "Thumbnails/thumbnail.png";
}
KoEncryptedStore::KoEncryptedStore(const QString & filename, Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
, m_filename(filename)
, m_tempFile(0)
, m_bPasswordUsed(false)
, m_bPasswordDeclined(false)
, m_currentDir(0)
{
Q_D(KoStore);
m_pZip = new KZip(filename);
d->good = true;
d->localFileName = filename;
init(appIdentification);
}
KoEncryptedStore::KoEncryptedStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
, m_tempFile(0)
, m_bPasswordUsed(false)
, m_bPasswordDeclined(false)
, m_currentDir(0)
{
Q_D(KoStore);
m_pZip = new KZip(dev);
d->good = true;
init(appIdentification);
}
KoEncryptedStore::KoEncryptedStore(QWidget* window, const QUrl &url, const QString & filename,
Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
, m_filename(url.url())
, m_tempFile(0)
, m_bPasswordUsed(false)
, m_bPasswordDeclined(false)
, m_currentDir(0)
{
Q_D(KoStore);
d->window = window;
d->good = true;
if (mode == Read) {
d->fileMode = KoStorePrivate::RemoteRead;
d->localFileName = filename;
m_pZip = new KZip(d->localFileName);
} else {
d->fileMode = KoStorePrivate::RemoteWrite;
m_tempFile = new QTemporaryFile();
if (!m_tempFile->open()) {
d->good = false;
} else {
d->localFileName = m_tempFile->fileName();
m_pZip = new KZip(m_tempFile);
}
}
d->url = url;
init(appIdentification);
}
void KoEncryptedStore::init(const QByteArray & appIdentification)
{
Q_D(KoStore);
bool checksumErrorShown = false;
bool unreadableErrorShown = false;
if (d->mode == Write) {
d->good = KoEncryptionChecker::isEncryptionSupported();
if (d->good) {
if (!m_pZip->open(QIODevice::WriteOnly)) {
d->good = false;
return;
}
m_pZip->setExtraField(KZip::NoExtraField);
// Write identification
if (d->writeMimetype) {
m_pZip->setCompression(KZip::NoCompression);
(void)m_pZip->writeFile(QLatin1String("mimetype"), appIdentification);
}
// FIXME: Hmm, seems to be a bug here since this is
// inconsistent with the code in openWrite():
m_pZip->setCompression(KZip::DeflateCompression);
// We don't need the extra field in Calligra - so we leave it as "no extra field".
}
} else {
d->good = m_pZip->open(QIODevice::ReadOnly);
d->good &= m_pZip->directory() != 0;
if (!d->good) {
return;
}
// Read the manifest-file, so we can get the data we'll need to decrypt the other files in the store
const KArchiveEntry* manifestArchiveEntry = m_pZip->directory()->entry(MANIFEST_FILE);
if (!manifestArchiveEntry || !manifestArchiveEntry->isFile()) {
// No manifest file? OK, *I* won't complain
return;
}
QIODevice *dev = (static_cast< const KArchiveFile* >(manifestArchiveEntry))->createDevice();
KoXmlDocument xmldoc;
bool namespaceProcessing = true; // for the manifest ignore the namespace (bug #260515)
if (!xmldoc.setContent(dev, namespaceProcessing) || xmldoc.documentElement().localName() != "manifest" || xmldoc.documentElement().namespaceURI() != KoXmlNS::manifest) {
//KMessage::message(KMessage::Warning, i18n("The manifest file seems to be corrupted. The document could not be opened."));
/// FIXME this message is not something we actually want to not mention, but it makes thumbnails noisy at times, so... let's not
dev->close();
delete dev;
m_pZip->close();
d->good = false;
return;
}
KoXmlElement xmlroot = xmldoc.documentElement();
if (xmlroot.hasChildNodes()) {
QCA::Base64 base64decoder(QCA::Decode);
KoXmlNode xmlnode = xmlroot.firstChild();
while (!xmlnode.isNull()) {
// Search for files
if (!xmlnode.isElement() || xmlroot.namespaceURI() != KoXmlNS::manifest || xmlnode.toElement().localName() != "file-entry" || !xmlnode.toElement().hasAttribute("full-path") || !xmlnode.hasChildNodes()) {
xmlnode = xmlnode.nextSibling();
continue;
}
// Build a structure to hold the data and fill it with defaults
KoEncryptedStore_EncryptionData encData;
encData.filesize = 0;
encData.checksum = QCA::SecureArray();
encData.checksumShort = false;
encData.salt = QCA::SecureArray();
encData.iterationCount = 0;
encData.initVector = QCA::SecureArray();
// Get some info about the file
QString fullpath = xmlnode.toElement().attribute("full-path");
if (xmlnode.toElement().hasAttribute("size")) {
encData.filesize = xmlnode.toElement().attribute("size").toUInt();
}
// Find the embedded encryption-data block
KoXmlNode xmlencnode = xmlnode.firstChild();
while (!xmlencnode.isNull() && (!xmlencnode.isElement() || xmlencnode.toElement().localName() != "encryption-data" || !xmlencnode.hasChildNodes())) {
xmlencnode = xmlencnode.nextSibling();
}
if (xmlencnode.isNull()) {
xmlnode = xmlnode.nextSibling();
continue;
}
// Find some things about the checksum
if (xmlencnode.toElement().hasAttribute("checksum")) {
base64decoder.clear();
encData.checksum = base64decoder.decode(QCA::SecureArray(xmlencnode.toElement().attribute("checksum").toLatin1()));
if (xmlencnode.toElement().hasAttribute("checksum-type")) {
QString checksumType = xmlencnode.toElement().attribute("checksum-type");
if (checksumType == "SHA1") {
encData.checksumShort = false;
}
// For this particular hash-type: check KoEncryptedStore_encryptionData.checksumShort
else if (checksumType == "SHA1/1K") {
encData.checksumShort = true;
} else {
// Checksum type unknown
if (!checksumErrorShown) {
KMessage::message(KMessage::Warning, i18n("This document contains an unknown checksum. When you give a password it might not be verified."));
checksumErrorShown = true;
}
encData.checksum = QCA::SecureArray();
}
} else {
encData.checksumShort = false;
}
}
KoXmlNode xmlencattr = xmlencnode.firstChild();
bool algorithmFound = false;
bool keyDerivationFound = false;
// Search all data about encryption
while (!xmlencattr.isNull()) {
if (!xmlencattr.isElement()) {
xmlencattr = xmlencattr.nextSibling();
continue;
}
// Find some things about the encryption algorithm
if (xmlencattr.toElement().localName() == "algorithm" && xmlencattr.toElement().hasAttribute("initialisation-vector")) {
algorithmFound = true;
encData.initVector = base64decoder.decode(QCA::SecureArray(xmlencattr.toElement().attribute("initialisation-vector").toLatin1()));
if (xmlencattr.toElement().hasAttribute("algorithm-name") && xmlencattr.toElement().attribute("algorithm-name") != "Blowfish CFB") {
if (!unreadableErrorShown) {
KMessage::message(KMessage::Warning, i18n("This document contains an unknown encryption method. Some parts may be unreadable."));
unreadableErrorShown = true;
}
encData.initVector = QCA::SecureArray();
}
}
// Find some things about the key derivation
if (xmlencattr.toElement().localName() == "key-derivation" && xmlencattr.toElement().hasAttribute("salt")) {
keyDerivationFound = true;
encData.salt = base64decoder.decode(QCA::SecureArray(xmlencattr.toElement().attribute("salt").toLatin1()));
encData.iterationCount = 1024;
if (xmlencattr.toElement().hasAttribute("iteration-count")) {
encData.iterationCount = xmlencattr.toElement().attribute("iteration-count").toUInt();
}
if (xmlencattr.toElement().hasAttribute("key-derivation-name") && xmlencattr.toElement().attribute("key-derivation-name") != "PBKDF2") {
if (!unreadableErrorShown) {
KMessage::message(KMessage::Warning, i18n("This document contains an unknown encryption method. Some parts may be unreadable."));
unreadableErrorShown = true;
}
encData.salt = QCA::SecureArray();
}
}
xmlencattr = xmlencattr.nextSibling();
}
// Only use this encryption data if it makes sense to use it
if (!(encData.salt.isEmpty() || encData.initVector.isEmpty())) {
m_encryptionData.insert(fullpath, encData);
if (!(algorithmFound && keyDerivationFound)) {
if (!unreadableErrorShown) {
KMessage::message(KMessage::Warning, i18n("This document contains incomplete encryption data. Some parts may be unreadable."));
unreadableErrorShown = true;
}
}
}
xmlnode = xmlnode.nextSibling();
}
}
dev->close();
delete dev;
if (isEncrypted() && !(QCA::isSupported("sha1") && QCA::isSupported("pbkdf2(sha1)") && QCA::isSupported("blowfish-cfb"))) {
d->good = false;
KMessage::message(KMessage::Error, i18n("QCA has currently no support for SHA1 or PBKDF2 using SHA1. The document can not be opened."));
}
}
}
bool KoEncryptedStore::doFinalize()
{
Q_D(KoStore);
if (d->good) {
if (isOpen()) {
close();
}
if (d->mode == Write) {
// First change the manifest file and write it
// We'll use the QDom classes here, since KoXmlReader and KoXmlWriter have no way of copying a complete xml-file
// other than parsing it completely and rebuilding it.
// Errorhandling here is done to prevent data from being lost whatever happens
// TODO: Convert this to KoXML when KoXML is extended enough
// Note: right now this is impossible due to lack of possibilities to copy an element as-is
QDomDocument document;
if (m_manifestBuffer.isEmpty()) {
// No manifest? Better create one
document = QDomDocument();
QDomElement rootElement = document.createElement("manifest:manifest");
rootElement.setAttribute("xmlns:manifest", "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
document.appendChild(rootElement);
}
if (!m_manifestBuffer.isEmpty() && !document.setContent(m_manifestBuffer)) {
// Oi! That's fresh XML we should have here!
// This is the only case we can't fix
KMessage::message(KMessage::Error, i18n("The manifest file seems to be corrupted. It cannot be modified and the document will remain unreadable. Please try and save the document again to prevent losing your work."));
m_pZip->close();
return false;
}
QDomElement documentElement = document.documentElement();
QDomNodeList fileElements = documentElement.elementsByTagName("manifest:file-entry");
// Search all files in the manifest
QStringList foundFiles;
for (int i = 0; i < fileElements.size(); i++) {
QDomElement fileElement = fileElements.item(i).toElement();
QString fullpath = fileElement.toElement().attribute("manifest:full-path");
// See if it's encrypted
if (fullpath.isEmpty() || !m_encryptionData.contains(fullpath)) {
continue;
}
foundFiles += fullpath;
KoEncryptedStore_EncryptionData encData = m_encryptionData.value(fullpath);
// Set the unencrypted size of the file
fileElement.setAttribute("manifest:size", encData.filesize);
// See if the user of this store has already provided (old) encryption data
QDomNodeList childElements = fileElement.elementsByTagName("manifest:encryption-data");
QDomElement encryptionElement;
QDomElement algorithmElement;
QDomElement keyDerivationElement;
if (childElements.isEmpty()) {
encryptionElement = document.createElement("manifest:encryption-data");
fileElement.appendChild(encryptionElement);
} else {
encryptionElement = childElements.item(0).toElement();
}
childElements = encryptionElement.elementsByTagName("manifest:algorithm");
if (childElements.isEmpty()) {
algorithmElement = document.createElement("manifest:algorithm");
encryptionElement.appendChild(algorithmElement);
} else {
algorithmElement = childElements.item(0).toElement();
}
childElements = encryptionElement.elementsByTagName("manifest:key-derivation");
if (childElements.isEmpty()) {
keyDerivationElement = document.createElement("manifest:key-derivation");
encryptionElement.appendChild(keyDerivationElement);
} else {
keyDerivationElement = childElements.item(0).toElement();
}
// Set the right encryption data
QCA::Base64 encoder;
QCA::SecureArray checksum = encoder.encode(encData.checksum);
if (encData.checksumShort) {
encryptionElement.setAttribute("manifest:checksum-type", "SHA1/1K");
} else {
encryptionElement.setAttribute("manifest:checksum-type", "SHA1");
}
encryptionElement.setAttribute("manifest:checksum", QString(checksum.toByteArray()));
QCA::SecureArray initVector = encoder.encode(encData.initVector);
algorithmElement.setAttribute("manifest:algorithm-name", "Blowfish CFB");
algorithmElement.setAttribute("manifest:initialisation-vector", QString(initVector.toByteArray()));
QCA::SecureArray salt = encoder.encode(encData.salt);
keyDerivationElement.setAttribute("manifest:key-derivation-name", "PBKDF2");
keyDerivationElement.setAttribute("manifest:iteration-count", QString::number(encData.iterationCount));
keyDerivationElement.setAttribute("manifest:salt", QString(salt.toByteArray()));
}
if (foundFiles.size() < m_encryptionData.size()) {
QList<QString> keys = m_encryptionData.keys();
for (int i = 0; i < keys.size(); i++) {
if (!foundFiles.contains(keys.value(i))) {
KoEncryptedStore_EncryptionData encData = m_encryptionData.value(keys.value(i));
QDomElement fileElement = document.createElement("manifest:file-entry");
fileElement.setAttribute("manifest:full-path", keys.value(i));
fileElement.setAttribute("manifest:size", encData.filesize);
fileElement.setAttribute("manifest:media-type", "");
documentElement.appendChild(fileElement);
QDomElement encryptionElement = document.createElement("manifest:encryption-data");
QCA::Base64 encoder;
QCA::SecureArray checksum = encoder.encode(encData.checksum);
QCA::SecureArray initVector = encoder.encode(encData.initVector);
QCA::SecureArray salt = encoder.encode(encData.salt);
if (encData.checksumShort) {
encryptionElement.setAttribute("manifest:checksum-type", "SHA1/1K");
} else {
encryptionElement.setAttribute("manifest:checksum-type", "SHA1");
}
encryptionElement.setAttribute("manifest:checksum", QString(checksum.toByteArray()));
fileElement.appendChild(encryptionElement);
QDomElement algorithmElement = document.createElement("manifest:algorithm");
algorithmElement.setAttribute("manifest:algorithm-name", "Blowfish CFB");
algorithmElement.setAttribute("manifest:initialisation-vector", QString(initVector.toByteArray()));
encryptionElement.appendChild(algorithmElement);
QDomElement keyDerivationElement = document.createElement("manifest:key-derivation");
keyDerivationElement.setAttribute("manifest:key-derivation-name", "PBKDF2");
keyDerivationElement.setAttribute("manifest:iteration-count", QString::number(encData.iterationCount));
keyDerivationElement.setAttribute("manifest:salt", QString(salt.toByteArray()));
encryptionElement.appendChild(keyDerivationElement);
}
}
}
m_manifestBuffer = document.toByteArray();
m_pZip->setCompression(KZip::DeflateCompression);
if (!m_pZip->writeFile(QLatin1String(MANIFEST_FILE), m_manifestBuffer)) {
KMessage::message(KMessage::Error, i18n("The manifest file cannot be written. The document will remain unreadable. Please try and save the document again to prevent losing your work."));
m_pZip->close();
return false;
}
}
}
if (m_pZip)
return m_pZip->close();
else
return true;
}
KoEncryptedStore::~KoEncryptedStore()
{
Q_D(KoStore);
/* Finalization of an encrypted store must happen earlier than deleting the zip. This rule normally is executed by KoStore, but too late to do any good.*/
if (!d->finalized) {
finalize();
}
delete m_pZip;
if (d->fileMode == KoStorePrivate::RemoteWrite) {
KIO::NetAccess::upload(d->localFileName, d->url, d->window);
delete m_tempFile;
} else if (d->fileMode == KoStorePrivate::RemoteRead) {
KIO::NetAccess::removeTempFile(d->localFileName);
}
delete d->stream;
}
bool KoEncryptedStore::isEncrypted()
{
Q_D(KoStore);
if (d->mode == Read) {
return !m_encryptionData.isEmpty();
}
return true;
}
QStringList KoEncryptedStore::directoryList() const
{
QStringList retval;
const KArchiveDirectory *directory = m_pZip->directory();
foreach(const QString &name, directory->entries()) {
const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
if (fileArchiveEntry->isDirectory()) {
retval << name;
}
}
return retval;
}
bool KoEncryptedStore::isToBeEncrypted(const QString& name)
{
return !(name == META_FILE || name == MANIFEST_FILE || name == THUMBNAIL_FILE);
}
bool KoEncryptedStore::openRead(const QString& name)
{
Q_D(KoStore);
if (bad())
return false;
const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
if (!fileArchiveEntry) {
return false;
}
if (fileArchiveEntry->isDirectory()) {
warnStore << name << " is a directory!";
return false;
}
const KZipFileEntry* fileZipEntry = static_cast<const KZipFileEntry*>(fileArchiveEntry);
delete d->stream;
d->stream = fileZipEntry->createDevice();
d->size = fileZipEntry->size();
if (m_encryptionData.contains(name)) {
// This file is encrypted, do some decryption first
if (m_bPasswordDeclined) {
// The user has already declined to give a password
// Open the file as empty
d->stream->close();
delete d->stream;
d->stream = new QBuffer();
d->stream->open(QIODevice::ReadOnly);
d->size = 0;
return true;
}
QCA::SecureArray encryptedFile(d->stream->readAll());
if (encryptedFile.size() != d->size) {
// Read error detected
d->stream->close();
delete d->stream;
d->stream = nullptr;
warnStore << "read error";
return false;
}
d->stream->close();
delete d->stream;
d->stream = nullptr;
KoEncryptedStore_EncryptionData encData = m_encryptionData.value(name);
QCA::SecureArray decrypted;
// If we don't have a password yet, try and find one
if (m_password.isEmpty()) {
findPasswordInKWallet();
}
while (true) {
QCA::SecureArray password;
bool keepPass = false;
// I already have a password! Let's test it. If it's not good, we can dump it, anyway.
if (!m_password.isEmpty()) {
password = m_password;
m_password = QCA::SecureArray();
} else {
if (!m_filename.isNull())
keepPass = false;
KPasswordDialog dlg(d->window , keepPass ? KPasswordDialog::ShowKeepPassword : static_cast<KPasswordDialog::KPasswordDialogFlags>(0));
dlg.setPrompt(i18n("Please enter the password to open this file."));
if (! dlg.exec()) {
m_bPasswordDeclined = true;
d->stream = new QBuffer();
d->stream->open(QIODevice::ReadOnly);
d->size = 0;
return true;
}
password = QCA::SecureArray(dlg.password().toUtf8());
if (keepPass)
keepPass = dlg.keepPassword();
if (password.isEmpty()) {
continue;
}
}
decrypted = decryptFile(encryptedFile, encData, password);
if (decrypted.isEmpty()) {
errorStore << "empty decrypted file" << endl;
return false;
}
if (!encData.checksum.isEmpty()) {
QCA::SecureArray checksum;
if (encData.checksumShort && decrypted.size() > 1024) {
// TODO: Eww!!!! I don't want to convert via insecure arrays to get the first 1K characters of a secure array <- fix QCA?
checksum = QCA::Hash("sha1").hash(QCA::SecureArray(decrypted.toByteArray().left(1024)));
} else {
checksum = QCA::Hash("sha1").hash(decrypted);
}
if (checksum != encData.checksum) {
continue;
}
}
// The password passed all possible tests, so let's accept it
m_password = password;
m_bPasswordUsed = true;
if (keepPass) {
savePasswordInKWallet();
}
break;
}
QByteArray *resultArray = new QByteArray(decrypted.toByteArray());
KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType("application/x-gzip");
QIODevice *resultDevice = new KCompressionDevice(new QBuffer(resultArray, nullptr), false, type);
if (!resultDevice) {
delete resultArray;
return false;
}
static_cast<KFilterDev*>(resultDevice)->setSkipHeaders();
d->stream = resultDevice;
d->size = encData.filesize;
}
if (!d->stream->isOpen()) {
d->stream->open(QIODevice::ReadOnly);
}
return true;
}
bool KoEncryptedStore::closeRead()
{
Q_D(KoStore);
delete d->stream;
d->stream = nullptr;
return true;
}
void KoEncryptedStore::findPasswordInKWallet()
{
Q_D(KoStore);
/* About KWallet access
*
* The choice has been made to postfix every entry in a kwallet concerning passwords for opendocument files with /opendocument
* This choice has been made since, at the time of this writing, the author could not find any reference to standardized
* naming schemes for entries in the wallet. Since collision of passwords in entries should be avoided and is at least possible,
* considering remote files might be both protected by a secured web-area (konqueror makes an entry) and a password (we make an
* entry), it seems a good thing to make sure it won't happen.
*/
if (!m_filename.isNull() && !KWallet::Wallet::folderDoesNotExist(KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder()) && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder(), m_filename + "/opendocument")) {
KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), d->window ? d->window->winId() : 0);
if (wallet) {
if (wallet->setFolder(KWallet::Wallet::PasswordFolder())) {
QString pass;
wallet->readPassword(m_filename + "/opendocument", pass);
m_password = QCA::SecureArray(pass.toUtf8());
}
delete wallet;
}
}
}
void KoEncryptedStore::savePasswordInKWallet()
{
Q_D(KoStore);
KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), d->window ? d->window->winId() : 0);
if (wallet) {
if (!wallet->hasFolder(KWallet::Wallet::PasswordFolder())) {
wallet->createFolder(KWallet::Wallet::PasswordFolder());
}
if (wallet->setFolder(KWallet::Wallet::PasswordFolder())) {
if (wallet->hasEntry(m_filename + "/opendocument")) {
wallet->removeEntry(m_filename + "/opendocument");
}
wallet->writePassword(m_filename + "/opendocument", m_password.toByteArray().constData());
}
delete wallet;
}
}
QCA::SecureArray KoEncryptedStore::decryptFile(QCA::SecureArray & encryptedFile, KoEncryptedStore_EncryptionData & encData, QCA::SecureArray & password)
{
QCA::SecureArray keyhash = QCA::Hash("sha1").hash(password);
QCA::SymmetricKey key = QCA::PBKDF2("sha1").makeKey(keyhash, QCA::InitializationVector(encData.salt), 16, encData.iterationCount);
QCA::Cipher decrypter("blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Decode, key, QCA::InitializationVector(encData.initVector));
QCA::SecureArray result = decrypter.update(encryptedFile);
result += decrypter.final();
return result;
}
bool KoEncryptedStore::setPassword(const QString& password)
{
if (m_bPasswordUsed || password.isEmpty()) {
return false;
}
m_password = QCA::SecureArray(password.toUtf8());
return true;
}
QString KoEncryptedStore::password()
{
if (m_password.isEmpty()) {
return QString();
}
return QString(m_password.toByteArray());
}
bool KoEncryptedStore::openWrite(const QString& name)
{
Q_D(KoStore);
if (bad())
return false;
if (isToBeEncrypted(name)) {
// Encrypted files will be compressed by this class and should be stored in the zip as not compressed
m_pZip->setCompression(KZip::NoCompression);
} else {
m_pZip->setCompression(KZip::DeflateCompression);
}
d->stream = new QBuffer();
(static_cast< QBuffer* >(d->stream))->open(QIODevice::WriteOnly);
if (name == MANIFEST_FILE)
return true;
return m_pZip->prepareWriting(name, "", "", 0);
}
bool KoEncryptedStore::closeWrite()
{
Q_D(KoStore);
bool passWasAsked = false;
if (d->fileName == MANIFEST_FILE) {
m_manifestBuffer = static_cast<QBuffer*>(d->stream)->buffer();
return true;
}
// Find a password
// Do not accept empty passwords for compatibility with OOo
if (m_password.isEmpty()) {
findPasswordInKWallet();
}
while (m_password.isEmpty()) {
KNewPasswordDialog dlg(d->window);
dlg.setPrompt(i18n("Please enter the password to encrypt the document with."));
if (! dlg.exec()) {
// Without the first password, prevent asking again by deadsimply refusing to continue functioning
// TODO: This feels rather hackish. There should be a better way to do this.
delete m_pZip;
m_pZip = 0;
d->good = false;
return false;
}
m_password = QCA::SecureArray(dlg.password().toUtf8());
passWasAsked = true;
}
// Ask the user to save the password
if (passWasAsked && KMessageBox::questionYesNo(d->window, i18n("Do you want to save the password?")) == KMessageBox::Yes) {
savePasswordInKWallet();
}
QByteArray resultData;
if (d->fileName == THUMBNAIL_FILE) {
// TODO: Replace with a generic 'encrypted'-thumbnail
resultData = static_cast<QBuffer*>(d->stream)->buffer();
} else if (!isToBeEncrypted(d->fileName)) {
resultData = static_cast<QBuffer*>(d->stream)->buffer();
} else {
m_bPasswordUsed = true;
// Build all cryptographic data
QCA::SecureArray passwordHash = QCA::Hash("sha1").hash(m_password);
QCA::Random random;
KoEncryptedStore_EncryptionData encData;
encData.initVector = random.randomArray(8);
encData.salt = random.randomArray(16);
encData.iterationCount = 1024;
QCA::SymmetricKey key = QCA::PBKDF2("sha1").makeKey(passwordHash, QCA::InitializationVector(encData.salt), 16, encData.iterationCount);
QCA::Cipher encrypter("blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Encode, key, QCA::InitializationVector(encData.initVector));
// Get the written data
QByteArray data = static_cast<QBuffer*>(d->stream)->buffer();
encData.filesize = data.size();
// Compress the data
QBuffer compressedData;
KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType("application/x-gzip");
QIODevice *compressDevice = new KCompressionDevice(&compressedData, false, type);
if (!compressDevice) {
return false;
}
static_cast<KFilterDev*>(compressDevice)->setSkipHeaders();
if (!compressDevice->open(QIODevice::WriteOnly)) {
delete compressDevice;
return false;
}
if (compressDevice->write(data) != data.size()) {
delete compressDevice;
return false;
}
compressDevice->close();
delete compressDevice;
encData.checksum = QCA::Hash("sha1").hash(QCA::SecureArray(compressedData.buffer()));
encData.checksumShort = false;
// Encrypt the data
QCA::SecureArray result = encrypter.update(QCA::SecureArray(compressedData.buffer()));
result += encrypter.final();
resultData = result.toByteArray();
m_encryptionData.insert(d->fileName, encData);
}
if (!m_pZip->writeData(resultData.data(), resultData.size())) {
m_pZip->finishWriting(resultData.size());
return false;
}
return m_pZip->finishWriting(resultData.size());
}
bool KoEncryptedStore::enterRelativeDirectory(const QString& dirName)
{
Q_D(KoStore);
if (d->mode == Read) {
if (!m_currentDir) {
m_currentDir = m_pZip->directory(); // initialize
}
const KArchiveEntry *entry = m_currentDir->entry(dirName);
if (entry && entry->isDirectory()) {
m_currentDir = dynamic_cast<const KArchiveDirectory*>(entry);
return m_currentDir != 0;
}
return false;
} else { // Write, no checking here
return true;
}
}
bool KoEncryptedStore::enterAbsoluteDirectory(const QString& path)
{
if (path.isEmpty()) {
m_currentDir = 0;
return true;
}
m_currentDir = dynamic_cast<const KArchiveDirectory*>(m_pZip->directory()->entry(path));
return m_currentDir != 0;
}
bool KoEncryptedStore::fileExists(const QString& absPath) const
{
const KArchiveEntry *entry = m_pZip->directory()->entry(absPath);
return (entry && entry->isFile()) || (absPath == MANIFEST_FILE && !m_manifestBuffer.isNull());
}
#endif // QCA2
diff --git a/src/libs/store/KoEncryptionChecker.cpp b/src/libs/store/KoEncryptionChecker.cpp
index 6647b9cb..42367178 100644
--- a/src/libs/store/KoEncryptionChecker.cpp
+++ b/src/libs/store/KoEncryptionChecker.cpp
@@ -1,48 +1,49 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoEncryptionChecker.h"
#ifdef QCA2
// QCA headers have "slots" and "signals", which QT_NO_SIGNALS_SLOTS_KEYWORDS does not like
#define slots Q_SLOTS
#define signals Q_SIGNALS
#include <QtCrypto>
#undef slots
#undef signals
#include <StoreDebug.h>
bool KoEncryptionChecker::isEncryptionSupported()
{
QCA::Initializer* initializer = new QCA::Initializer();
bool supported = QCA::isSupported("sha1") && QCA::isSupported("pbkdf2(sha1)") && QCA::isSupported("blowfish-cfb");
if (!supported) {
warnStore << "QCA is enabled but sha1, pbkdf2(sha1) or blowfish-cfb are not supported. Encryption is disabled.";
}
delete initializer;
return supported;
}
#else
bool KoEncryptionChecker::isEncryptionSupported()
{
return false;
}
#endif
diff --git a/src/libs/store/KoLZF.cpp b/src/libs/store/KoLZF.cpp
index 95e08f67..0dcb1438 100644
--- a/src/libs/store/KoLZF.cpp
+++ b/src/libs/store/KoLZF.cpp
@@ -1,367 +1,368 @@
/* This file is part of the KDE project
Copyright (C) 2005-2006 Ariya Hidayat <ariya@kde.org>
Copyright (C) 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
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 "KoLZF.h"
#include <QByteArray>
#include <QDebug>
namespace KoLZF {
#define HASH_LOG 12
#define HASH_SIZE (1<< HASH_LOG)
#define HASH_MASK (HASH_SIZE-1)
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wcast-align"
#endif
#define UPDATE_HASH(v,p) { v = *((quint16*)p); v ^= *((quint16*)(p+1))^(v>>(16-HASH_LOG)); }
#define MAX_COPY 32
#define MAX_LEN 264 /* 256 + 8 */
#define MAX_DISTANCE 8192
// Lossless compression using LZF algorithm, this is faster on modern CPU than
// the original implementation in http://liblzf.plan9.de/
int compress(const void* input, int length, void* output, int maxout)
{
if (input == 0 || length < 1 || output == 0 || maxout < 2) {
return 0;
}
const quint8* ip = (const quint8*) input;
const quint8* ip_limit = ip + length - MAX_COPY - 4;
quint8* op = (quint8*) output;
const quint8* last_op = (quint8*) output + maxout - 1;
const quint8* htab[HASH_SIZE];
const quint8** hslot;
quint32 hval;
quint8* ref;
qint32 copy;
qint32 len;
qint32 distance;
quint8* anchor;
/* initializes hash table */
for (hslot = htab; hslot < htab + HASH_SIZE; ++hslot) {
*hslot = ip;
}
/* we start with literal copy */
copy = 0;
*op++ = MAX_COPY - 1;
/* main loop */
while (ip < ip_limit) {
/* find potential match */
UPDATE_HASH(hval, ip);
hslot = htab + (hval & HASH_MASK);
ref = (quint8*) * hslot;
/* update hash table */
*hslot = ip;
/* find itself? then it's no match */
if (ip == ref)
goto literal;
/* is this a match? check the first 2 bytes */
if (*((quint16*)ref) != *((quint16*)ip))
goto literal;
/* now check the 3rd byte */
if (ref[2] != ip[2])
goto literal;
/* calculate distance to the match */
distance = ip - ref;
/* skip if too far away */
if (distance >= MAX_DISTANCE)
goto literal;
/* here we have 3-byte matches */
anchor = (quint8*)ip;
len = 3;
ref += 3;
ip += 3;
/* now we have to check how long the match is */
if (ip < ip_limit - MAX_LEN) {
while (len < MAX_LEN - 8) {
/* unroll 8 times */
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
if (*ref++ != *ip++) break;
len += 8;
}
--ip;
}
len = ip - anchor;
/* just before the last non-matching byte */
ip = anchor + len;
/* if we have copied something, adjust the copy count */
if (copy) {
/* copy is biased, '0' means 1 byte copy */
anchor = anchor - copy - 1;
*(op - copy - 1) = copy - 1;
copy = 0;
} else {
/* back, to overwrite the copy count */
--op;
}
/* length is biased, '1' means a match of 3 bytes */
len -= 2;
/* distance is also biased */
--distance;
/* encode the match */
if (len < 7) {
if (op + 2 > last_op) {
return 0;
}
*op++ = (len << 5) + (distance >> 8);
} else {
if (op + 3 > last_op) {
return 0;
}
*op++ = (7 << 5) + (distance >> 8);
*op++ = len - 7;
}
*op++ = (distance & 255);
/* assuming next will be literal copy */
*op++ = MAX_COPY - 1;
/* update the hash at match boundary */
--ip;
UPDATE_HASH(hval, ip);
htab[hval & HASH_MASK] = ip;
++ip;
continue;
literal:
if (op + 1 > last_op) {
return 0;
}
*op++ = *ip++;
++copy;
if (copy >= MAX_COPY) {
// start next literal copy item
copy = 0;
*op++ = MAX_COPY - 1;
}
}
/* left-over as literal copy */
ip_limit = (const quint8*)input + length;
// TODO: smart calculation to see here if enough output is left
while (ip < ip_limit) {
if (op == last_op) {
return 0;
}
*op++ = *ip++;
++copy;
if (copy == MAX_COPY) {
// start next literal copy item
copy = 0;
if (ip < ip_limit) {
if (op == last_op) {
return 0;
}
*op++ = MAX_COPY - 1;
} else {
// do not write possibly out of bounds
// just pretend we moved one more, for the final treatment
++op;
}
}
}
/* if we have copied something, adjust the copy length */
if (copy) {
*(op - copy - 1) = copy - 1;
} else {
--op;
}
return op - (quint8*)output;
}
int decompress(const void* input, int length, void* output, int maxout)
{
if (input == 0 || length < 1) {
return 0;
}
if (output == 0 || maxout < 1) {
return 0;
}
const quint8* ip = (const quint8*) input;
const quint8* ip_limit = ip + length - 1;
quint8* op = (quint8*) output;
quint8* op_limit = op + maxout;
quint8* ref;
while (ip < ip_limit) {
quint32 ctrl = (*ip) + 1;
quint32 ofs = ((*ip) & 31) << 8;
quint32 len = (*ip++) >> 5;
if (ctrl < 33) {
/* literal copy */
if (op + ctrl > op_limit)
return 0;
/* crazy unrolling */
if (ctrl) {
*op++ = *ip++;
--ctrl;
if (ctrl) {
*op++ = *ip++;
--ctrl;
if (ctrl) {
*op++ = *ip++;
--ctrl;
for (;ctrl; --ctrl)
*op++ = *ip++;
}
}
}
} else {
/* back reference */
--len;
ref = op - ofs;
--ref;
if (len == 7 - 1)
len += *ip++;
ref -= *ip++;
if (op + len + 3 > op_limit)
return 0;
if (ref < (quint8 *)output)
return 0;
*op++ = *ref++;
*op++ = *ref++;
*op++ = *ref++;
if (len)
for (; len; --len)
*op++ = *ref++;
}
}
return op - (quint8*)output;
}
QByteArray compress(const QByteArray& input)
{
const void* const in_data = (const void*) input.constData();
unsigned int in_len = (unsigned int)input.size();
QByteArray output;
output.resize(in_len + 4 + 1);
// we use 4 bytes to store uncompressed length
// and 1 extra byte as flag (0=uncompressed, 1=compressed)
output[0] = in_len & 255;
output[1] = (in_len >> 8) & 255;
output[2] = (in_len >> 16) & 255;
output[3] = (in_len >> 24) & 255;
output[4] = 1;
unsigned int out_len = in_len - 1;
unsigned char* out_data = (unsigned char*) output.data() + 5;
unsigned int len = compress(in_data, in_len, out_data, out_len);
if ((len > out_len) || (len == 0)) {
// output buffer is too small, likely because the data can't
// be compressed. so here just copy without compression
output.replace(5, output.size()-5, input);
// flag must indicate "uncompressed block"
output[4] = 0;
} else {
output.resize(len + 4 + 1);
}
// minimize memory
output.squeeze();
return output;
}
// will not squeeze output
void decompress(const QByteArray& input, QByteArray& output)
{
Q_ASSERT(input.size() >= 5);
// read out first how big is the uncompressed size
unsigned int unpack_size = 0;
unpack_size |= ((quint8)input[0]);
unpack_size |= ((quint8)input[1]) << 8;
unpack_size |= ((quint8)input[2]) << 16;
unpack_size |= ((quint8)input[3]) << 24;
// prepare the output
output.resize(unpack_size);
// compression flag
quint8 flag = (quint8)input[4];
// prepare for lzf
const void* const in_data = (const void*)(input.constData() + 5);
unsigned int in_len = (unsigned int)input.size() - 5;
unsigned char* out_data = (unsigned char*) output.data();
unsigned int out_len = (unsigned int)unpack_size;
if (flag == 0) {
memcpy(output.data(), in_data, in_len);
} else {
unsigned int len = decompress(in_data, in_len, out_data, out_len);
Q_ASSERT(len == out_len);
Q_UNUSED(len)
}
}
}
diff --git a/src/libs/store/KoNetAccess.cpp b/src/libs/store/KoNetAccess.cpp
index ae4f5b89..a0a1f621 100644
--- a/src/libs/store/KoNetAccess.cpp
+++ b/src/libs/store/KoNetAccess.cpp
@@ -1,536 +1,537 @@
/*
This file is part of the KDE libraries
Copyright (C) 1997 Torben Weis (weis@kde.org)
Copyright (C) 1998 Matthias Ettrich (ettrich@kde.org)
Copyright (C) 1999 David Faure (faure@kde.org)
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 "KoNetAccess.h"
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <QtCore/QCharRef>
#include <QApplication>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QMetaClassInfo>
#include <QtCore/QTextStream>
#include <QtCore/QDataStream>
#include <qtemporaryfile.h>
#include <klocalizedstring.h>
#include <kjobwidgets.h>
#include "kio/job.h"
#include "kio/mkdirjob.h"
#include "kio/copyjob.h"
#include "kio/deletejob.h"
#include "kio/scheduler.h"
namespace KIO
{
class NetAccessPrivate
{
public:
NetAccessPrivate()
: m_metaData(0)
, bJobOK(true)
{}
UDSEntry m_entry;
QString m_mimetype;
QByteArray m_data;
QUrl m_url;
QMap<QString, QString> *m_metaData;
/**
* Whether the download succeeded or not
*/
bool bJobOK;
};
} // namespace KIO
using namespace KIO;
/**
* List of temporary files
*/
static QStringList *tmpfiles;
static QString *lastErrorMsg = 0;
static int lastErrorCode = 0;
NetAccess::NetAccess() :
d(new NetAccessPrivate)
{
}
NetAccess::~NetAccess()
{
delete d;
}
bool NetAccess::download(const QUrl &u, QString &target, QWidget *window)
{
if (u.isLocalFile()) {
// file protocol. We do not need the network
target = u.toLocalFile();
const bool readable = QFileInfo(target).isReadable();
if (!readable) {
if (!lastErrorMsg) {
lastErrorMsg = new QString;
}
*lastErrorMsg = i18n("File '%1' is not readable", target);
lastErrorCode = ERR_COULD_NOT_READ;
}
return readable;
}
if (target.isEmpty()) {
QTemporaryFile tmpFile;
tmpFile.setAutoRemove(false);
tmpFile.open();
target = tmpFile.fileName();
if (!tmpfiles) {
tmpfiles = new QStringList;
}
tmpfiles->append(target);
}
NetAccess kioNet;
const QUrl dest = QUrl::fromLocalFile(target);
return kioNet.filecopyInternal(u, dest, -1, KIO::Overwrite, window, false /*copy*/);
}
bool NetAccess::upload(const QString &src, const QUrl &target, QWidget *window)
{
if (target.isEmpty()) {
return false;
}
// If target is local... well, just copy. This can be useful
// when the client code uses a temp file no matter what.
// Let's make sure it's not the exact same file though
if (target.isLocalFile() && target.toLocalFile() == src) {
return true;
}
NetAccess kioNet;
const QUrl srcUrl = QUrl::fromLocalFile(src);
return kioNet.filecopyInternal(srcUrl, target, -1, KIO::Overwrite, window, false /*copy*/);
}
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::file_copy(const QUrl &src, const QUrl &target, QWidget *window)
{
NetAccess kioNet;
return kioNet.filecopyInternal(src, target, -1, KIO::DefaultFlags,
window, false /*copy*/);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::copy(const QUrl &src, const QUrl &target, QWidget *window)
{
return file_copy(src, target, window);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::dircopy(const QUrl &src, const QUrl &target, QWidget *window)
{
QList<QUrl> srcList;
srcList.append(src);
return NetAccess::dircopy(srcList, target, window);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::dircopy(const QList<QUrl> &srcList, const QUrl &target, QWidget *window)
{
NetAccess kioNet;
return kioNet.dircopyInternal(srcList, target, window, false /*copy*/);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::move(const QUrl &src, const QUrl &target, QWidget *window)
{
QList<QUrl> srcList;
srcList.append(src);
NetAccess kioNet;
return kioNet.dircopyInternal(srcList, target, window, true /*move*/);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::move(const QList<QUrl> &srcList, const QUrl &target, QWidget *window)
{
NetAccess kioNet;
return kioNet.dircopyInternal(srcList, target, window, true /*move*/);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::exists(const QUrl &url, bool source, QWidget *window)
{
if (url.isLocalFile()) {
return QFile::exists(url.toLocalFile());
}
NetAccess kioNet;
return kioNet.statInternal(url, 0 /*no details*/,
source ? SourceSide : DestinationSide, window);
}
#endif
bool NetAccess::exists(const QUrl &url, StatSide side, QWidget *window)
{
if (url.isLocalFile()) {
return QFile::exists(url.toLocalFile());
}
NetAccess kioNet;
return kioNet.statInternal(url, 0 /*no details*/, side, window);
}
bool NetAccess::stat(const QUrl &url, KIO::UDSEntry &entry, QWidget *window)
{
NetAccess kioNet;
bool ret = kioNet.statInternal(url, 2 /*all details*/, SourceSide, window);
if (ret) {
entry = kioNet.d->m_entry;
}
return ret;
}
QUrl NetAccess::mostLocalUrl(const QUrl &url, QWidget *window)
{
if (url.isLocalFile()) {
return url;
}
KIO::UDSEntry entry;
if (!stat(url, entry, window)) {
return url;
}
const QString path = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
if (!path.isEmpty()) {
QUrl new_url = QUrl::fromLocalFile(path);
return new_url;
}
return url;
}
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::del(const QUrl &url, QWidget *window)
{
NetAccess kioNet;
return kioNet.delInternal(url, window);
}
#endif
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool NetAccess::mkdir(const QUrl &url, QWidget *window, int permissions)
{
NetAccess kioNet;
return kioNet.mkdirInternal(url, permissions, window);
}
#endif
QString NetAccess::fish_execute(const QUrl &url, const QString &command, QWidget *window)
{
NetAccess kioNet;
return kioNet.fish_executeInternal(url, command, window);
}
bool NetAccess::synchronousRun(Job *job, QWidget *window, QByteArray *data,
QUrl *finalURL, QMap<QString, QString> *metaData)
{
NetAccess kioNet;
// Disable autodeletion until we are back from this event loop (#170963)
// We just have to hope people don't mess with setAutoDelete in slots connected to the job, though.
const bool wasAutoDelete = job->isAutoDelete();
job->setAutoDelete(false);
const bool ok = kioNet.synchronousRunInternal(job, window, data, finalURL, metaData);
if (wasAutoDelete) {
job->deleteLater();
}
return ok;
}
QString NetAccess::mimetype(const QUrl &url, QWidget *window)
{
NetAccess kioNet;
return kioNet.mimetypeInternal(url, window);
}
QString NetAccess::lastErrorString()
{
return lastErrorMsg ? *lastErrorMsg : QString();
}
int NetAccess::lastError()
{
return lastErrorCode;
}
void NetAccess::removeTempFile(const QString &name)
{
if (!tmpfiles) {
return;
}
if (tmpfiles->contains(name)) {
QFile::remove(name);
tmpfiles->removeAll(name);
}
}
bool NetAccess::filecopyInternal(const QUrl &src, const QUrl &target, int permissions,
KIO::JobFlags flags, QWidget *window, bool move)
{
d->bJobOK = true; // success unless further error occurs
KIO::Scheduler::checkSlaveOnHold(true);
KIO::Job *job = move
? KIO::file_move(src, target, permissions, flags)
: KIO::file_copy(src, target, permissions, flags);
KJobWidgets::setWindow(job, window);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
enter_loop();
return d->bJobOK;
}
bool NetAccess::dircopyInternal(const QList<QUrl> &src, const QUrl &target,
QWidget *window, bool move)
{
d->bJobOK = true; // success unless further error occurs
KIO::Job *job = move
? KIO::move(src, target)
: KIO::copy(src, target);
KJobWidgets::setWindow(job, window);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
enter_loop();
return d->bJobOK;
}
bool NetAccess::statInternal(const QUrl &url, int details, StatSide side,
QWidget *window)
{
d->bJobOK = true; // success unless further error occurs
KIO::JobFlags flags = url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags;
KIO::StatJob *job = KIO::stat(url, flags);
KJobWidgets::setWindow(job, window);
job->setDetails(details);
job->setSide(side == SourceSide ? StatJob::SourceSide : StatJob::DestinationSide);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
enter_loop();
return d->bJobOK;
}
bool NetAccess::delInternal(const QUrl &url, QWidget *window)
{
d->bJobOK = true; // success unless further error occurs
KIO::Job *job = KIO::del(url);
KJobWidgets::setWindow(job, window);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
enter_loop();
return d->bJobOK;
}
bool NetAccess::mkdirInternal(const QUrl &url, int permissions,
QWidget *window)
{
d->bJobOK = true; // success unless further error occurs
KIO::Job *job = KIO::mkdir(url, permissions);
KJobWidgets::setWindow(job, window);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
enter_loop();
return d->bJobOK;
}
QString NetAccess::mimetypeInternal(const QUrl &url, QWidget *window)
{
d->bJobOK = true; // success unless further error occurs
d->m_mimetype = QLatin1String("unknown");
KIO::Job *job = KIO::mimetype(url);
KJobWidgets::setWindow(job, window);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
connect(job, SIGNAL(mimetype(KIO::Job*,QString)),
this, SLOT(slotMimetype(KIO::Job*,QString)));
enter_loop();
return d->m_mimetype;
}
void NetAccess::slotMimetype(KIO::Job *, const QString &type)
{
d->m_mimetype = type;
}
QString NetAccess::fish_executeInternal(const QUrl &url, const QString &command, QWidget *window)
{
QString target, remoteTempFileName, resultData;
QTemporaryFile tmpFile;
tmpFile.open();
if (url.scheme() == "fish") {
// construct remote temp filename
QUrl tempPathUrl = url;
remoteTempFileName = tmpFile.fileName();
// We only need the filename. The directory might not exist on the remote side.
int pos = remoteTempFileName.lastIndexOf('/');
remoteTempFileName = "/tmp/fishexec_" + remoteTempFileName.mid(pos + 1);
tempPathUrl.setPath(remoteTempFileName);
d->bJobOK = true; // success unless further error occurs
QByteArray packedArgs;
QDataStream stream(&packedArgs, QIODevice::WriteOnly);
stream << int('X') << tempPathUrl << command;
KIO::Job *job = KIO::special(tempPathUrl, packedArgs);
KJobWidgets::setWindow(job, window);
connect(job, &KJob::result,
this, &NetAccess::slotResult);
enter_loop();
// since the KIO::special does not provide feedback we need to download the result
if (NetAccess::download(tempPathUrl, target, window)) {
QFile resultFile(target);
if (resultFile.open(QIODevice::ReadOnly)) {
QTextStream ts(&resultFile); // default encoding is Locale
resultData = ts.readAll();
resultFile.close();
NetAccess::del(tempPathUrl, window);
}
}
} else {
resultData = i18n("ERROR: Unknown protocol '%1'", url.scheme());
}
return resultData;
}
bool NetAccess::synchronousRunInternal(Job *job, QWidget *window, QByteArray *data,
QUrl *finalURL, QMap<QString, QString> *metaData)
{
KJobWidgets::setWindow(job, window);
d->m_metaData = metaData;
if (d->m_metaData) {
for (QMap<QString, QString>::iterator it = d->m_metaData->begin(); it != d->m_metaData->end(); ++it) {
job->addMetaData(it.key(), it.value());
}
}
if (finalURL) {
SimpleJob *sj = qobject_cast<SimpleJob *>(job);
if (sj) {
d->m_url = sj->url();
}
}
connect(job, &KJob::result,
this, &NetAccess::slotResult);
const QMetaObject *meta = job->metaObject();
static const char dataSignal[] = "data(KIO::Job*,QByteArray)";
if (meta->indexOfSignal(dataSignal) != -1) {
connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
this, SLOT(slotData(KIO::Job*,QByteArray)));
}
static const char redirSignal[] = "redirection(KIO::Job*,QUrl)";
if (meta->indexOfSignal(redirSignal) != -1) {
connect(job, SIGNAL(redirection(KIO::Job*,QUrl)),
this, SLOT(slotRedirection(KIO::Job*,QUrl)));
}
enter_loop();
if (finalURL) {
*finalURL = d->m_url;
}
if (data) {
*data = d->m_data;
}
return d->bJobOK;
}
void NetAccess::enter_loop()
{
QEventLoop eventLoop;
connect(this, &NetAccess::leaveModality,
&eventLoop, &QEventLoop::quit);
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
void NetAccess::slotResult(KJob *job)
{
lastErrorCode = job->error();
d->bJobOK = !job->error();
if (!d->bJobOK) {
if (!lastErrorMsg) {
lastErrorMsg = new QString;
}
*lastErrorMsg = job->errorString();
}
KIO::StatJob *statJob = qobject_cast<KIO::StatJob *>(job);
if (statJob) {
d->m_entry = statJob->statResult();
}
KIO::Job *kioJob = qobject_cast<KIO::Job *>(job);
if (kioJob && d->m_metaData) {
*d->m_metaData = kioJob->metaData();
}
emit leaveModality();
}
void NetAccess::slotData(KIO::Job *, const QByteArray &data)
{
if (data.isEmpty()) {
return;
}
unsigned offset = d->m_data.size();
d->m_data.resize(offset + data.size());
std::memcpy(d->m_data.data() + offset, data.data(), data.size());
}
void NetAccess::slotRedirection(KIO::Job *, const QUrl &url)
{
d->m_url = url;
}
diff --git a/src/libs/store/KoStore.cpp b/src/libs/store/KoStore.cpp
index 672aca91..7409149f 100644
--- a/src/libs/store/KoStore.cpp
+++ b/src/libs/store/KoStore.cpp
@@ -1,610 +1,611 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2002 David Faure <faure@kde.org>, Werner Trobin <trobin@kde.org>
Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
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 "KoStore.h"
#include "KoStore_p.h"
#include "KoTarStore.h"
#include "KoZipStore.h"
#include "KoDirectoryStore.h"
#ifdef QCA2
#include "KoEncryptedStore.h"
#endif
#include <QBuffer>
#include <QFileInfo>
#include <QFile>
#include <QUrl>
#include <StoreDebug.h>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <KoNetAccess.h>
#define DefaultFormat KoStore::Zip
static KoStore::Backend determineBackend(QIODevice *dev)
{
unsigned char buf[5];
if (dev->read((char *)buf, 4) < 4)
return DefaultFormat; // will create a "bad" store (bad()==true)
if (buf[0] == 0037 && buf[1] == 0213) // gzip -> tar.gz
return KoStore::Tar;
if (buf[0] == 'P' && buf[1] == 'K' && buf[2] == 3 && buf[3] == 4)
return KoStore::Zip;
return DefaultFormat; // fallback
}
KoStore* KoStore::createStore(const QString& fileName, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype)
{
bool automatic = false;
if (backend == Auto) {
automatic = true;
if (mode == KoStore::Write)
backend = DefaultFormat;
else {
QFileInfo inf(fileName);
if (inf.isDir())
backend = Directory;
else {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly))
backend = determineBackend(&file);
else
backend = DefaultFormat; // will create a "bad" store (bad()==true)
}
}
}
switch (backend) {
case Tar:
return new KoTarStore(fileName, mode, appIdentification, writeMimetype);
case Zip:
#ifdef QCA2
if (automatic && mode == Read) {
// When automatically detecting, this might as well be an encrypted file. We'll need to check anyway, so we'll just use the encrypted store.
return new KoEncryptedStore(fileName, Read, appIdentification, writeMimetype);
}
#endif
return new KoZipStore(fileName, mode, appIdentification, writeMimetype);
case Directory:
return new KoDirectoryStore(fileName /* should be a dir name.... */, mode, writeMimetype);
#ifdef QCA2
case Encrypted:
return new KoEncryptedStore(fileName, mode, appIdentification, writeMimetype);
#endif
default:
warnStore << "Unsupported backend requested for KoStore : " << backend;
return 0;
}
}
KoStore* KoStore::createStore(QIODevice *device, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype)
{
bool automatic = false;
if (backend == Auto) {
automatic = true;
if (mode == KoStore::Write)
backend = DefaultFormat;
else {
if (device->open(QIODevice::ReadOnly)) {
backend = determineBackend(device);
device->close();
}
}
}
switch (backend) {
case Tar:
return new KoTarStore(device, mode, appIdentification, writeMimetype);
case Directory:
errorStore << "Can't create a Directory store for a memory buffer!" << endl;
Q_FALLTHROUGH();
case Zip:
#ifdef QCA2
if (automatic && mode == Read) {
// When automatically detecting, this might as well be an encrypted file. We'll need to check anyway, so we'll just use the encrypted store.
return new KoEncryptedStore(device, Read, appIdentification, writeMimetype);
}
#endif
return new KoZipStore(device, mode, appIdentification, writeMimetype);
#ifdef QCA2
case Encrypted:
return new KoEncryptedStore(device, mode, appIdentification, writeMimetype);
#endif
default:
warnStore << "Unsupported backend requested for KoStore : " << backend;
return 0;
}
}
KoStore* KoStore::createStore(QWidget* window, const QUrl &url, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype)
{
const bool automatic = (backend == Auto);
if (url.isLocalFile())
return createStore(url.toLocalFile(), mode, appIdentification, backend, writeMimetype);
QString tmpFile;
if (mode == KoStore::Write) {
if (automatic)
backend = DefaultFormat;
} else {
const bool downloaded =
KIO::NetAccess::download(url, tmpFile, window);
if (!downloaded) {
errorStore << "Could not download file!" << endl;
backend = DefaultFormat; // will create a "bad" store (bad()==true)
} else if (automatic) {
QFile file(tmpFile);
if (file.open(QIODevice::ReadOnly)) {
backend = determineBackend(&file);
file.close();
}
}
}
switch (backend) {
case Tar:
return new KoTarStore(window, url, tmpFile, mode, appIdentification);
case Zip:
#ifdef QCA2
if (automatic && mode == Read) {
// When automatically detecting, this might as well be an encrypted file. We'll need to check anyway, so we'll just use the encrypted store.
return new KoEncryptedStore(window, url, tmpFile, Read, appIdentification, writeMimetype);
}
#endif
return new KoZipStore(window, url, tmpFile, mode, appIdentification, writeMimetype);
#ifdef QCA2
case Encrypted:
return new KoEncryptedStore(window, url, tmpFile, mode, appIdentification, writeMimetype);
#endif
default:
warnStore << "Unsupported backend requested for KoStore (QUrl) : " << backend;
KMessageBox::sorry(window,
i18n("The directory mode is not supported for remote locations."),
i18n("Calligra Storage"));
return 0;
}
}
namespace
{
const char ROOTPART[] = "root";
const char MAINNAME[] = "maindoc.xml";
}
KoStore::KoStore(Mode mode, bool writeMimetype)
: d_ptr(new KoStorePrivate(this, mode, writeMimetype))
{}
KoStore::~KoStore()
{
Q_D(KoStore);
delete d->stream;
delete d_ptr;
}
QUrl KoStore::urlOfStore() const
{
Q_D(const KoStore);
if (d->fileMode == KoStorePrivate::RemoteRead || d->fileMode == KoStorePrivate::RemoteWrite) {
return d->url;
} else {
return QUrl(d->localFileName);
}
}
bool KoStore::open(const QString & _name)
{
Q_D(KoStore);
// This also converts from relative to absolute, i.e. merges the currentPath()
d->fileName = d->toExternalNaming(_name);
if (d->isOpen) {
warnStore << "Store is already opened, missing close";
return false;
}
if (d->fileName.length() > 512) {
errorStore << "KoStore: Filename " << d->fileName << " is too long" << endl;
return false;
}
if (d->mode == Write) {
debugStore << "opening for writing" << d->fileName;
if (d->filesList.contains(d->fileName)) {
warnStore << "KoStore: Duplicate filename" << d->fileName;
return false;
}
d->filesList.append(d->fileName);
d->size = 0;
if (!openWrite(d->fileName))
return false;
} else if (d->mode == Read) {
debugStore << "Opening for reading" << d->fileName;
if (!openRead(d->fileName))
return false;
} else
return false;
d->isOpen = true;
return true;
}
bool KoStore::isOpen() const
{
Q_D(const KoStore);
return d->isOpen;
}
bool KoStore::close()
{
Q_D(KoStore);
debugStore << "Closing";
if (!d->isOpen) {
warnStore << "You must open before closing";
return false;
}
bool ret = d->mode == Write ? closeWrite() : closeRead();
delete d->stream;
d->stream = 0;
d->isOpen = false;
return ret;
}
QIODevice* KoStore::device() const
{
Q_D(const KoStore);
if (!d->isOpen)
warnStore << "You must open before asking for a device";
if (d->mode != Read)
warnStore << "Can not get device from store that is opened for writing";
return d->stream;
}
QByteArray KoStore::read(qint64 max)
{
Q_D(KoStore);
QByteArray data;
if (!d->isOpen) {
warnStore << "You must open before reading";
return data;
}
if (d->mode != Read) {
errorStore << "KoStore: Can not read from store that is opened for writing" << endl;
return data;
}
return d->stream->read(max);
}
qint64 KoStore::write(const QByteArray& data)
{
return write(data.constData(), data.size()); // see below
}
qint64 KoStore::read(char *_buffer, qint64 _len)
{
Q_D(KoStore);
if (!d->isOpen) {
errorStore << "KoStore: You must open before reading" << endl;
return -1;
}
if (d->mode != Read) {
errorStore << "KoStore: Can not read from store that is opened for writing" << endl;
return -1;
}
return d->stream->read(_buffer, _len);
}
qint64 KoStore::write(const char* _data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
int nwritten = d->stream->write(_data, _len);
Q_ASSERT(nwritten == (int)_len);
d->size += nwritten;
return nwritten;
}
qint64 KoStore::size() const
{
Q_D(const KoStore);
if (!d->isOpen) {
warnStore << "You must open before asking for a size";
return static_cast<qint64>(-1);
}
if (d->mode != Read) {
warnStore << "Can not get size from store that is opened for writing";
return static_cast<qint64>(-1);
}
return d->size;
}
bool KoStore::enterDirectory(const QString &directory)
{
Q_D(KoStore);
//debugStore <<"enterDirectory" << directory;
int pos;
bool success = true;
QString tmp(directory);
while ((pos = tmp.indexOf('/')) != -1 &&
(success = d->enterDirectoryInternal(tmp.left(pos))))
tmp.remove(0, pos + 1);
if (success && !tmp.isEmpty())
return d->enterDirectoryInternal(tmp);
return success;
}
bool KoStore::leaveDirectory()
{
Q_D(KoStore);
if (d->currentPath.isEmpty())
return false;
d->currentPath.pop_back();
return enterAbsoluteDirectory(currentPath());
}
QString KoStore::currentPath() const
{
Q_D(const KoStore);
QString path;
QStringList::ConstIterator it = d->currentPath.begin();
QStringList::ConstIterator end = d->currentPath.end();
for (; it != end; ++it) {
path += *it;
path += '/';
}
return path;
}
void KoStore::pushDirectory()
{
Q_D(KoStore);
d->directoryStack.push(currentPath());
}
void KoStore::popDirectory()
{
Q_D(KoStore);
d->currentPath.clear();
enterAbsoluteDirectory(QString());
enterDirectory(d->directoryStack.pop());
}
bool KoStore::addLocalFile(const QString &fileName, const QString &destName)
{
QFileInfo fi(fileName);
uint size = fi.size();
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
if (!open(destName)) {
return false;
}
QByteArray data;
data.resize(8 * 1024);
uint total = 0;
for (int block = 0; (block = file.read(data.data(), data.size())) > 0; total += block) {
data.resize(block);
if (write(data) != block)
return false;
data.resize(8*1024);
}
Q_ASSERT(total == size);
if (total != size) {
warnStore << "Did not write enough bytes. Expected: " << size << ", wrote" << total;
return false;
}
close();
file.close();
return true;
}
bool KoStore::addDataToFile(QByteArray &buffer, const QString &destName)
{
QBuffer file(&buffer);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
if (!open(destName)) {
return false;
}
QByteArray data;
data.resize(8 * 1024);
uint total = 0;
for (int block = 0; (block = file.read(data.data(), data.size())) > 0; total += block) {
data.resize(block);
if (write(data) != block)
return false;
data.resize(8*1024);
}
close();
file.close();
return true;
}
bool KoStore::extractFile(const QString &srcName, const QString &fileName)
{
Q_D(KoStore);
QFile file(fileName);
return d->extractFile(srcName, file);
}
bool KoStore::extractFile(const QString &srcName, QByteArray &data)
{
Q_D(KoStore);
QBuffer buffer(&data);
return d->extractFile(srcName, buffer);
}
bool KoStorePrivate::extractFile(const QString &srcName, QIODevice &buffer)
{
if (!q->open(srcName))
return false;
if (!buffer.open(QIODevice::WriteOnly)) {
q->close();
return false;
}
// ### This could use KArchive::copy or something, no?
QByteArray data;
data.resize(8 * 1024);
uint total = 0;
for (int block = 0; (block = q->read(data.data(), data.size())) > 0; total += block) {
buffer.write(data.data(), block);
}
if (q->size() != static_cast<qint64>(-1))
Q_ASSERT(total == q->size());
buffer.close();
q->close();
return true;
}
bool KoStore::seek(qint64 pos)
{
Q_D(KoStore);
return d->stream->seek(pos);
}
qint64 KoStore::pos() const
{
Q_D(const KoStore);
return d->stream->pos();
}
bool KoStore::atEnd() const
{
Q_D(const KoStore);
return d->stream->atEnd();
}
// See the specification for details of what this function does.
QString KoStorePrivate::toExternalNaming(const QString & _internalNaming) const
{
if (_internalNaming == ROOTPART)
return q->currentPath() + MAINNAME;
QString intern;
if (_internalNaming.startsWith("tar:/")) // absolute reference
intern = _internalNaming.mid(5); // remove protocol
else
intern = q->currentPath() + _internalNaming;
return intern;
}
bool KoStorePrivate::enterDirectoryInternal(const QString &directory)
{
if (q->enterRelativeDirectory(directory)) {
currentPath.append(directory);
return true;
}
return false;
}
bool KoStore::hasFile(const QString& fileName) const
{
Q_D(const KoStore);
return fileExists(d->toExternalNaming(fileName));
}
bool KoStore::finalize()
{
Q_D(KoStore);
Q_ASSERT(!d->finalized); // call this only once!
d->finalized = true;
return doFinalize();
}
void KoStore::setCompressionEnabled(bool /*e*/)
{
}
bool KoStore::isEncrypted()
{
return false;
}
bool KoStore::setPassword(const QString& /*password*/)
{
return false;
}
QString KoStore::password()
{
return QString();
}
bool KoStore::bad() const
{
Q_D(const KoStore);
return !d->good;
}
KoStore::Mode KoStore::mode() const
{
Q_D(const KoStore);
return d->mode;
}
QStringList KoStore::directoryList() const
{
return QStringList();
}
diff --git a/src/libs/store/KoStoreDevice.cpp b/src/libs/store/KoStoreDevice.cpp
index 99511c5d..cb063898 100644
--- a/src/libs/store/KoStoreDevice.cpp
+++ b/src/libs/store/KoStoreDevice.cpp
@@ -1,24 +1,25 @@
/* This file is part of the KDE project
Copyright (C) 2000 David Faure <faure@kde.org>
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 "KoStoreDevice.h"
KoStoreDevice::~KoStoreDevice()
{
}
diff --git a/src/libs/store/KoTarStore.cpp b/src/libs/store/KoTarStore.cpp
index a106f495..1577b936 100644
--- a/src/libs/store/KoTarStore.cpp
+++ b/src/libs/store/KoTarStore.cpp
@@ -1,228 +1,229 @@
/* This file is part of the KDE project
Copyright (C) 2000-2002 David Faure <faure@kde.org>
Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
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 "KoTarStore.h"
#include "KoStore_p.h"
#include <QBuffer>
#include <QByteArray>
#include <ktar.h>
#include <StoreDebug.h>
#include <QUrl>
#include <KoNetAccess.h>
KoTarStore::KoTarStore(const QString & _filename, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoTarStore Constructor filename =" << _filename
<< " mode = " << int(mode) << endl;
Q_D(KoStore);
d->localFileName = _filename;
m_pTar = new KTar(_filename, "application/x-gzip");
init(appIdentification); // open the targz file and init some vars
}
KoTarStore::KoTarStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
m_pTar = new KTar(dev);
init(appIdentification);
}
KoTarStore::KoTarStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoTarStore Constructor url=" << _url.url(QUrl::PreferLocalFile)
<< " filename = " << _filename
<< " mode = " << int(mode) << endl;
Q_D(KoStore);
d->url = _url;
d->window = window;
if (mode == KoStore::Read) {
d->fileMode = KoStorePrivate::RemoteRead;
d->localFileName = _filename;
} else {
d->fileMode = KoStorePrivate::RemoteWrite;
d->localFileName = "/tmp/kozip"; // ### FIXME with KTempFile
}
m_pTar = new KTar(d->localFileName, "application/x-gzip");
init(appIdentification); // open the targz file and init some vars
}
KoTarStore::~KoTarStore()
{
Q_D(KoStore);
if (!d->finalized)
finalize(); // ### no error checking when the app forgot to call finalize itself
delete m_pTar;
// Now we have still some job to do for remote files.
if (d->fileMode == KoStorePrivate::RemoteRead) {
KIO::NetAccess::removeTempFile(d->localFileName);
} else if (d->fileMode == KoStorePrivate::RemoteWrite) {
KIO::NetAccess::upload(d->localFileName, d->url, d->window);
// ### FIXME: delete temp file
}
}
QStringList KoTarStore::directoryList() const
{
QStringList retval;
const KArchiveDirectory *directory = m_pTar->directory();
foreach(const QString &name, directory->entries()) {
const KArchiveEntry* fileArchiveEntry = m_pTar->directory()->entry(name);
if (fileArchiveEntry->isDirectory()) {
retval << name;
}
}
return retval;
}
QByteArray KoTarStore::completeMagic(const QByteArray& appMimetype)
{
debugStore << "QCString KoTarStore::completeMagic( const QCString& appMimetype )********************";
QByteArray res("Calligra ");
res += appMimetype;
res += '\004'; // Two magic bytes to make the identification
res += '\006'; // more reliable (DF)
debugStore << "sssssssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
debugStore << " return :!!!!!!!!!!!!!!! :" << res;
return res;
}
void KoTarStore::init(const QByteArray &appIdentification)
{
Q_D(KoStore);
m_currentDir = 0;
d->good = m_pTar->open(d->mode == Write ? QIODevice::WriteOnly : QIODevice::ReadOnly);
if (!d->good)
return;
if (d->mode == Write) {
debugStore << "appIdentification :" << appIdentification;
m_pTar->setOrigFileName(completeMagic(appIdentification));
} else {
d->good = m_pTar->directory() != 0;
}
}
bool KoTarStore::doFinalize()
{
return m_pTar->close();
}
// When reading, d->stream comes directly from KArchiveFile::device()
// When writing, d->stream buffers the data into m_byteArray
bool KoTarStore::openWrite(const QString& /*name*/)
{
Q_D(KoStore);
// Prepare memory buffer for writing
m_byteArray.resize(0);
d->stream = new QBuffer(&m_byteArray);
d->stream->open(QIODevice::WriteOnly);
return true;
}
bool KoTarStore::openRead(const QString& name)
{
Q_D(KoStore);
const KArchiveEntry * entry = m_pTar->directory()->entry(name);
if (entry == 0) {
return false;
}
if (entry->isDirectory()) {
warnStore << name << " is a directory !";
return false;
}
KArchiveFile * f = (KArchiveFile *) entry;
m_byteArray.resize(0);
delete d->stream;
d->stream = f->createDevice();
d->size = f->size();
return true;
}
bool KoTarStore::closeWrite()
{
Q_D(KoStore);
// write the whole bytearray at once into the tar file
debugStore << "Writing file" << d->fileName << " into TAR archive. size" << d->size;
m_byteArray.resize(d->size); // TODO: check if really needed
if (!m_pTar->writeFile(d->fileName, m_byteArray, 0100644, QLatin1String("user"), QLatin1String("group")))
warnStore << "Failed to write " << d->fileName;
m_byteArray.resize(0); // save memory
return true;
}
bool KoTarStore::enterRelativeDirectory(const QString& dirName)
{
Q_D(KoStore);
if (d->mode == Read) {
if (!m_currentDir) {
m_currentDir = m_pTar->directory(); // initialize
Q_ASSERT(d->currentPath.isEmpty());
}
const KArchiveEntry *entry = m_currentDir->entry(dirName);
if (entry && entry->isDirectory()) {
m_currentDir = dynamic_cast<const KArchiveDirectory*>(entry);
return m_currentDir != 0;
}
return false;
} else // Write, no checking here
return true;
}
bool KoTarStore::enterAbsoluteDirectory(const QString& path)
{
Q_D(KoStore);
if (path.isEmpty()) {
m_currentDir = 0;
return true;
}
if (d->mode == Read) {
m_currentDir = dynamic_cast<const KArchiveDirectory*>(m_pTar->directory()->entry(path));
Q_ASSERT(m_currentDir);
return m_currentDir != 0;
} else
return true;
}
bool KoTarStore::fileExists(const QString& absPath) const
{
return m_pTar->directory()->entry(absPath) != 0;
}
diff --git a/src/libs/store/KoXmlNS.cpp b/src/libs/store/KoXmlNS.cpp
index 7294584e..a9b5d6a3 100644
--- a/src/libs/store/KoXmlNS.cpp
+++ b/src/libs/store/KoXmlNS.cpp
@@ -1,116 +1,117 @@
/* This file is part of the KDE project
Copyright (C) 2004 David Faure <faure@kde.org>
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 "KoXmlNS.h"
#include <string.h>
const QString KoXmlNS::office("urn:oasis:names:tc:opendocument:xmlns:office:1.0");
const QString KoXmlNS::meta("urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
const QString KoXmlNS::config("urn:oasis:names:tc:opendocument:xmlns:config:1.0");
const QString KoXmlNS::text("urn:oasis:names:tc:opendocument:xmlns:text:1.0");
const QString KoXmlNS::table("urn:oasis:names:tc:opendocument:xmlns:table:1.0");
const QString KoXmlNS::draw("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
const QString KoXmlNS::presentation("urn:oasis:names:tc:opendocument:xmlns:presentation:1.0");
const QString KoXmlNS::dr3d("urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
const QString KoXmlNS::chart("urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
const QString KoXmlNS::form("urn:oasis:names:tc:opendocument:xmlns:form:1.0");
const QString KoXmlNS::script("urn:oasis:names:tc:opendocument:xmlns:script:1.0");
const QString KoXmlNS::style("urn:oasis:names:tc:opendocument:xmlns:style:1.0");
const QString KoXmlNS::number("urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
const QString KoXmlNS::manifest("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
const QString KoXmlNS::anim("urn:oasis:names:tc:opendocument:xmlns:animation:1.0");
const QString KoXmlNS::math("http://www.w3.org/1998/Math/MathML");
const QString KoXmlNS::svg("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
const QString KoXmlNS::fo("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
const QString KoXmlNS::dc("http://purl.org/dc/elements/1.1/");
const QString KoXmlNS::xlink("http://www.w3.org/1999/xlink");
const QString KoXmlNS::VL("http://openoffice.org/2001/versions-list");
const QString KoXmlNS::smil("urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0");
const QString KoXmlNS::xhtml("http://www.w3.org/1999/xhtml");
const QString KoXmlNS::xml("http://www.w3.org/XML/1998/namespace");
const QString KoXmlNS::calligra = "http://www.calligra.org/2005/";
const QString KoXmlNS::officeooo = "http://openoffice.org/2009/office";
const QString KoXmlNS::ooo = "http://openoffice.org/2004/office";
const QString KoXmlNS::delta("http://www.deltaxml.com/ns/track-changes/delta-namespace");
const QString KoXmlNS::split("http://www.deltaxml.com/ns/track-changes/split-namespace");
const QString KoXmlNS::ac("http://www.deltaxml.com/ns/track-changes/attribute-change-namespace");
const char* KoXmlNS::nsURI2NS(const QString &nsURI)
{
if (nsURI == KoXmlNS::office)
return "office";
else if (nsURI == KoXmlNS::meta)
return "meta";
else if (nsURI == KoXmlNS::config)
return "config";
else if (nsURI == KoXmlNS::text)
return "text";
else if (nsURI == KoXmlNS::table)
return "table";
else if (nsURI == KoXmlNS::draw)
return "draw";
else if (nsURI == KoXmlNS::presentation)
return "presentation";
else if (nsURI == KoXmlNS::dr3d)
return "dr3d";
else if (nsURI == KoXmlNS::chart)
return "chart";
else if (nsURI == KoXmlNS::form)
return "form";
else if (nsURI == KoXmlNS::script)
return "script";
else if (nsURI == KoXmlNS::style)
return "style";
else if (nsURI == KoXmlNS::number)
return "number";
else if (nsURI == KoXmlNS::manifest)
return "manifest";
else if (nsURI == KoXmlNS::anim)
return "anim";
else if (nsURI == KoXmlNS::math)
return "math";
else if (nsURI == KoXmlNS::svg)
return "svg";
else if (nsURI == KoXmlNS::fo)
return "fo";
else if (nsURI == KoXmlNS::dc)
return "dc";
else if (nsURI == KoXmlNS::xlink)
return "xlink";
else if (nsURI == KoXmlNS::VL)
return "VL";
else if (nsURI == KoXmlNS::smil)
return "smil";
else if (nsURI == KoXmlNS::xhtml)
return "xhtml";
else if (nsURI == KoXmlNS::calligra)
return "calligra";
else if (nsURI == KoXmlNS::officeooo)
return "officeooo";
else if (nsURI == KoXmlNS::xml)
return "xml";
// Shouldn't happen.
return "";
}
diff --git a/src/libs/store/KoXmlReader.cpp b/src/libs/store/KoXmlReader.cpp
index 7967bbbf..06cab784 100644
--- a/src/libs/store/KoXmlReader.cpp
+++ b/src/libs/store/KoXmlReader.cpp
@@ -1,2361 +1,2362 @@
/* This file is part of the KDE project
Copyright (C) 2005-2006 Ariya Hidayat <ariya@kde.org>
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 "KoXmlReader.h"
#include "KoXmlNS.h"
/*
This is a memory-efficient DOM implementation for Calligra. See the API
documentation for details.
IMPORTANT !
* When you change this stuff, make sure it DOES NOT BREAK the test suite.
Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights
have been sacrificed for this piece of code, do not let those precious
hours wasted!
* Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal
memory read/write and any type of leak occurs. If you are not familiar
with Valgrind then RTFM first and come back again later on.
* The public API shall remain as compatible as QDom.
* All QDom-compatible methods should behave the same. All QDom-compatible
functions should return the same result. In case of doubt, run
koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h
so that the tests are performed with standard QDom.
Some differences compared to QDom:
- DOM tree in KoXmlDocument is read-only, you can not modify it. This is
sufficient for Calligra since the tree is only accessed when loading
a document to the application. For saving the document to XML file,
use KoXmlWriter.
- Because the dynamic loading and unloading, you have to use the
nodes (and therefore also elements) carefully since the whole API
(just like QDom) is reference-based, not pointer-based. If the
parent node is unloaded from memory, the reference is not valid
anymore and may give unpredictable result.
The easiest way: use the node/element in very short time only.
- Comment node (like QDomComment) is not implemented as comments are
simply ignored.
- DTD, entity and entity reference are not handled. Thus, the associated
nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also
not implemented.
- Attribute mapping node is not implemented. But of course, functions to
query attributes of an element are available.
*/
#include <QTextCodec>
#include <QTextDecoder>
#ifndef KOXML_USE_QDOM
#include <QtXml>
#include <QDomDocument>
#include <QXmlStreamReader>
#include <QXmlStreamEntityResolver>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <QHash>
#include <QPair>
#include <QStringList>
#include <QVector>
/*
Use more compact representation of in-memory nodes.
Advantages: faster iteration, can facilitate real-time compression.
Disadvantages: still buggy, eat slightly more memory.
*/
#define KOXML_COMPACT
/*
Use real-time compression. Only works in conjuction with KOXML_COMPACT
above because otherwise the non-compact layout will slow down everything.
*/
#define KOXML_COMPRESS
// prevent mistake, see above
#ifdef KOXML_COMPRESS
#ifndef KOXML_COMPACT
#error Please enable also KOXML_COMPACT
#endif
#endif
// this is used to quickly get namespaced attribute(s)
typedef QPair<QString, QString> KoXmlStringPair;
class KoQName {
public:
QString nsURI;
QString name;
explicit KoQName(const QString& nsURI_, const QString& name_)
: nsURI(nsURI_), name(name_) {}
bool operator==(const KoQName& qname) const {
// local name is more likely to differ, so compare that first
return name == qname.name && nsURI == qname.nsURI;
}
};
uint qHash(const KoQName& qname)
{
// possibly add a faster hash function that only includes some trailing
// part of the nsURI
// in case of doubt, use this:
// return qHash(qname.nsURI)^qHash(qname.name);
return qHash(qname.nsURI)^qHash(qname.name);
}
// this simplistic hash is rather fast-and-furious. it works because
// likely there is very few namespaced attributes per element
static inline uint qHash(const KoXmlStringPair &p, uint /*seed*/ = 0)
{
return qHash(p.second[0].unicode()) ^ 0x1477;
// in case of doubt, use this:
// return qHash(p.first)^qHash(p.second);
}
static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b)
{
return a.second == b.second && a.first == b.first;
}
// Older versions of OpenOffice.org used different namespaces. This function
// does translate the old namespaces into the new ones.
static QString fixNamespace(const QString &nsURI)
{
static QString office = QString::fromLatin1("http://openoffice.org/2000/office");
static QString text = QString::fromLatin1("http://openoffice.org/2000/text");
static QString style = QString::fromLatin1("http://openoffice.org/2000/style");
static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format");
static QString table = QString::fromLatin1("http://openoffice.org/2000/table");
static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing");
static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle");
static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg");
static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart");
static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d");
static QString form = QString::fromLatin1("http://openoffice.org/2000/form");
static QString script = QString::fromLatin1("http://openoffice.org/2000/script");
static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta");
static QString config = QString::fromLatin1("http://openoffice.org/2001/config");
static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation");
static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest");
if (nsURI == text)
return KoXmlNS::text;
if (nsURI == style)
return KoXmlNS::style;
if (nsURI == office)
return KoXmlNS::office;
if (nsURI == fo)
return KoXmlNS::fo;
if (nsURI == table)
return KoXmlNS::table;
if (nsURI == drawing)
return KoXmlNS::draw;
if (nsURI == datastyle)
return KoXmlNS::number;
if (nsURI == svg)
return KoXmlNS::svg;
if (nsURI == chart)
return KoXmlNS::chart;
if (nsURI == dr3d)
return KoXmlNS::dr3d;
if (nsURI == form)
return KoXmlNS::form;
if (nsURI == script)
return KoXmlNS::script;
if (nsURI == meta)
return KoXmlNS::meta;
if (nsURI == config)
return KoXmlNS::config;
if (nsURI == pres)
return KoXmlNS::presentation;
if (nsURI == manifest)
return KoXmlNS::manifest;
return nsURI;
}
// ==================================================================
//
// KoXmlPackedItem
//
// ==================================================================
// 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems
class KoXmlPackedItem
{
public:
bool attr: 1;
KoXmlNode::NodeType type: 3;
#ifdef KOXML_COMPACT
quint32 childStart: 28;
#else
unsigned depth: 28;
#endif
unsigned qnameIndex;
QString value;
// it is important NOT to have a copy constructor, so that growth is optimal
// see https://doc.qt.io/qt-5/containers.html#growth-strategies
#if 0
KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {}
#endif
};
Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE);
#ifdef KOXML_COMPRESS
static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item)
{
quint8 flag = item.attr ? 1 : 0;
s << flag;
s << (quint8) item.type;
s << item.childStart;
s << item.qnameIndex;
s << item.value;
return s;
}
static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item)
{
quint8 flag;
quint8 type;
quint32 child;
QString value;
s >> flag;
s >> type;
s >> child;
s >> item.qnameIndex;
s >> value;
item.attr = (flag != 0);
item.type = (KoXmlNode::NodeType) type;
item.childStart = child;
item.value = value;
return s;
}
#endif
// ==================================================================
//
// KoXmlPackedDocument
//
// ==================================================================
#ifdef KOXML_COMPRESS
#include "KoXmlVector.h"
// when number of buffered items reach this, compression will start
// small value will give better memory usage at the cost of speed
// bigger value will be better in term of speed, but use more memory
#define ITEMS_FULL (1*256)
typedef KoXmlVector<KoXmlPackedItem, ITEMS_FULL> KoXmlPackedGroup;
#else
typedef QVector<KoXmlPackedItem> KoXmlPackedGroup;
#endif
// growth strategy: increase every GROUP_GROW_SIZE items
// this will override standard QVector's growth strategy
#define GROUP_GROW_SHIFT 3
#define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT)
class KoXmlPackedDocument
{
public:
bool processNamespace;
#ifdef KOXML_COMPACT
// map given depth to the list of items
QHash<int, KoXmlPackedGroup> groups;
#else
QVector<KoXmlPackedItem> items;
#endif
QList<KoQName> qnameList;
QString docType;
private:
QHash<KoQName, unsigned> qnameHash;
unsigned cacheQName(const QString& name, const QString& nsURI) {
KoQName qname(nsURI, name);
const unsigned ii = qnameHash.value(qname, (unsigned)-1);
if (ii != (unsigned)-1)
return ii;
// not yet declared, so we add it
unsigned i = qnameList.count();
qnameList.append(qname);
qnameHash.insert(qname, i);
return i;
}
QHash<QString, unsigned> valueHash;
QStringList valueList;
QString cacheValue(const QString& value) {
if (value.isEmpty())
return 0;
const unsigned& ii = valueHash[value];
if (ii > 0)
return valueList[ii];
// not yet declared, so we add it
unsigned i = valueList.count();
valueList.append(value);
valueHash.insert(value, i);
return valueList[i];
}
#ifdef KOXML_COMPACT
public:
const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) {
const KoXmlPackedGroup& group = groups[depth];
return group[index];
}
unsigned itemCount(unsigned depth) {
const KoXmlPackedGroup& group = groups[depth];
return group.count();
}
/*
NOTE:
Function clear, newItem, addElement, addAttribute, addText,
addCData, addProcessing are all related. These are all necessary
for stateful manipulation of the document. See also the calls
to these function from parseDocument().
The state itself is defined by the member variables
currentDepth and the groups (see above).
*/
unsigned currentDepth;
KoXmlPackedItem& newItem(unsigned depth) {
KoXmlPackedGroup& group = groups[depth];
#ifdef KOXML_COMPRESS
KoXmlPackedItem& item = group.newItem();
#else
// reserve up front
if ((groups.size() % GROUP_GROW_SIZE) == 0)
group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT)));
group.resize(group.count() + 1);
KoXmlPackedItem& item = group[group.count()-1];
#endif
// this is necessary, because intentionally we don't want to have
// a constructor for KoXmlPackedItem
item.attr = false;
item.type = KoXmlNode::NullNode;
item.qnameIndex = 0;
item.childStart = itemCount(depth + 1);
item.value.clear();
return item;
}
void clear() {
currentDepth = 0;
qnameHash.clear();
qnameList.clear();
valueHash.clear();
valueList.clear();
groups.clear();
docType.clear();
// first node is root
KoXmlPackedItem& rootItem = newItem(0);
rootItem.type = KoXmlNode::DocumentNode;
}
void finish() {
// won't be needed anymore
qnameHash.clear();
valueHash.clear();
valueList.clear();
// optimize, see documentation on QVector::squeeze
for (int d = 0; d < groups.count(); ++d) {
KoXmlPackedGroup& group = groups[d];
group.squeeze();
}
}
// in case namespace processing, 'name' contains the prefix already
void addElement(const QString& name, const QString& nsURI) {
KoXmlPackedItem& item = newItem(currentDepth + 1);
item.type = KoXmlNode::ElementNode;
item.qnameIndex = cacheQName(name, nsURI);
++currentDepth;
}
void closeElement() {
--currentDepth;
}
void addDTD(const QString& dt) {
docType = dt;
}
void addAttribute(const QString& name, const QString& nsURI, const QString& value) {
KoXmlPackedItem& item = newItem(currentDepth + 1);
item.attr = true;
item.qnameIndex = cacheQName(name, nsURI);
//item.value = cacheValue( value );
item.value = value;
}
void addText(const QString& text) {
KoXmlPackedItem& item = newItem(currentDepth + 1);
item.type = KoXmlNode::TextNode;
item.value = text;
}
void addCData(const QString& text) {
KoXmlPackedItem& item = newItem(currentDepth + 1);
item.type = KoXmlNode::CDATASectionNode;
item.value = text;
}
void addProcessingInstruction() {
KoXmlPackedItem& item = newItem(currentDepth + 1);
item.type = KoXmlNode::ProcessingInstructionNode;
}
public:
KoXmlPackedDocument(): processNamespace(false), currentDepth(0) {
clear();
}
#else
private:
unsigned elementDepth;
public:
KoXmlPackedItem& newItem() {
unsigned count = items.count() + 512;
count = 1024 * (count >> 10);
items.reserve(count);
items.resize(items.count() + 1);
// this is necessary, because intentionally we don't want to have
// a constructor for KoXmlPackedItem
KoXmlPackedItem& item = items[items.count()-1];
item.attr = false;
item.type = KoXmlNode::NullNode;
item.qnameIndex = 0;
item.depth = 0;
return item;
}
void addElement(const QString& name, const QString& nsURI) {
// we are going one level deeper
++elementDepth;
KoXmlPackedItem& item = newItem();
item.attr = false;
item.type = KoXmlNode::ElementNode;
item.depth = elementDepth;
item.qnameIndex = cacheQName(name, nsURI);
}
void closeElement() {
// we are going up one level
--elementDepth;
}
void addDTD(const QString& dt) {
docType = dt;
}
void addAttribute(const QString& name, const QString& nsURI, const QString& value) {
KoXmlPackedItem& item = newItem();
item.attr = true;
item.type = KoXmlNode::NullNode;
item.depth = elementDepth;
item.qnameIndex = cacheQName(name, nsURI);
//item.value = cacheValue( value );
item.value = value;
}
void addText(const QString& str) {
KoXmlPackedItem& item = newItem();
item.attr = false;
item.type = KoXmlNode::TextNode;
item.depth = elementDepth + 1;
item.qnameIndex = 0;
item.value = str;
}
void addCData(const QString& str) {
KoXmlPackedItem& item = newItem();
item.attr = false;
item.type = KoXmlNode::CDATASectionNode;
item.depth = elementDepth + 1;
item.qnameIndex = 0;
item.value = str;
}
void addProcessingInstruction() {
KoXmlPackedItem& item = newItem();
item.attr = false;
item.type = KoXmlNode::ProcessingInstructionNode;
item.depth = elementDepth + 1;
item.qnameIndex = 0;
item.value.clear();
}
void clear() {
qnameHash.clear();
qnameList.clear();
valueHash.clear();
valueList.clear();
items.clear();
elementDepth = 0;
KoXmlPackedItem& rootItem = newItem();
rootItem.attr = false;
rootItem.type = KoXmlNode::DocumentNode;
rootItem.depth = 0;
rootItem.qnameIndex = 0;
}
void finish() {
qnameHash.clear();
valueList.clear();
valueHash.clear();
items.squeeze();
}
KoXmlPackedDocument(): processNamespace(false), elementDepth(0) {
}
#endif
};
namespace {
class ParseError {
public:
QString errorMsg;
int errorLine;
int errorColumn;
bool error;
ParseError() :errorLine(-1), errorColumn(-1), error(false) {}
};
void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true);
// parse one element as if this were a standalone xml document
ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true)
{
doc.clear();
ParseError error;
xml.readNext();
while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) {
switch (xml.tokenType()) {
case QXmlStreamReader::StartElement:
parseElement(xml, doc, stripSpaces);
break;
case QXmlStreamReader::DTD:
doc.addDTD(xml.dtdName().toString());
break;
case QXmlStreamReader::StartDocument:
if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) {
doc.addProcessingInstruction();
}
break;
case QXmlStreamReader::ProcessingInstruction:
doc.addProcessingInstruction();
break;
default:
break;
}
xml.readNext();
}
if (xml.hasError()) {
error.error = true;
error.errorMsg = xml.errorString();
error.errorColumn = xml.columnNumber();
error.errorLine = xml.lineNumber();
} else {
doc.finish();
}
return error;
}
void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc)
{
xml.readNext();
QString ws;
while (!xml.atEnd()) {
switch (xml.tokenType()) {
case QXmlStreamReader::EndElement:
// if an element contains only whitespace, put it in the dom
if (!ws.isEmpty()) {
doc.addText(ws);
}
return;
case QXmlStreamReader::StartElement:
// The whitespaces between > and < are also a text element
if (!ws.isEmpty()) {
doc.addText(ws);
ws.clear();
}
// Do not strip spaces
parseElement(xml, doc, false);
break;
case QXmlStreamReader::Characters:
if (xml.isCDATA()) {
doc.addCData(xml.text().toString());
} else if (!xml.isWhitespace()) {
doc.addText(xml.text().toString());
} else {
ws += xml.text();
}
break;
case QXmlStreamReader::ProcessingInstruction:
doc.addProcessingInstruction();
break;
default:
break;
}
xml.readNext();
}
}
void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc)
{
xml.readNext();
QString ws;
bool sawElement = false;
while (!xml.atEnd()) {
switch (xml.tokenType()) {
case QXmlStreamReader::EndElement:
// if an element contains only whitespace, put it in the dom
if (!ws.isEmpty() && !sawElement) {
doc.addText(ws);
}
return;
case QXmlStreamReader::StartElement:
sawElement = true;
// Do strip spaces
parseElement(xml, doc, true);
break;
case QXmlStreamReader::Characters:
if (xml.isCDATA()) {
doc.addCData(xml.text().toString());
} else if (!xml.isWhitespace()) {
doc.addText(xml.text().toString());
} else if (!sawElement) {
ws += xml.text();
}
break;
case QXmlStreamReader::ProcessingInstruction:
doc.addProcessingInstruction();
break;
default:
break;
}
xml.readNext();
}
}
void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces)
{
// Unfortunately MSVC fails using QXmlStreamReader::const_iterator
// so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368
doc.addElement(xml.qualifiedName().toString(),
fixNamespace(xml.namespaceUri().toString()));
QXmlStreamAttributes attr = xml.attributes();
for (int a = 0; a < attr.count(); a++) {
doc.addAttribute(attr[a].qualifiedName().toString(),
attr[a].namespaceUri().toString(),
attr[a].value().toString());
}
if (stripSpaces)
parseElementContentsStripSpaces(xml, doc);
else
parseElementContents(xml, doc);
// reader.tokenType() is now QXmlStreamReader::EndElement
doc.closeElement();
}
}
// ==================================================================
//
// KoXmlNodeData
//
// ==================================================================
class KoXmlNodeData
{
public:
explicit KoXmlNodeData(unsigned long initialRefCount = 1);
~KoXmlNodeData();
// generic properties
KoXmlNode::NodeType nodeType;
bool loaded;
#ifdef KOXML_COMPACT
unsigned nodeDepth;
#endif
QString tagName;
QString namespaceURI;
QString prefix;
QString localName;
void ref() {
++refCount;
}
void unref() {
if (!--refCount) {
delete this;
}
}
// type information
QString nodeName() const;
// for tree and linked-list
KoXmlNodeData* parent;
KoXmlNodeData* prev;
KoXmlNodeData* next;
KoXmlNodeData* first;
KoXmlNodeData* last;
QString text();
// node manipulation
void clear();
// attributes
inline void setAttribute(const QString& name, const QString& value);
inline QString attribute(const QString& name, const QString& def) const;
inline bool hasAttribute(const QString& name) const;
inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value);
inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const;
inline bool hasAttributeNS(const QString& nsURI, const QString& name) const;
inline void clearAttributes();
inline QStringList attributeNames() const;
inline QList< QPair<QString, QString> > attributeFullNames() const;
// for text and CDATA
QString data() const;
// reference from within the packed doc
KoXmlPackedDocument* packedDoc;
unsigned long nodeIndex;
// used when doing on-demand (re)parse
void loadChildren(int depth = 1);
void unloadChildren();
void dump();
static KoXmlNodeData null;
// compatibility
void asQDomNode(QDomDocument& ownerDoc) const;
private:
QHash<QString, QString> attr;
QHash<KoXmlStringPair, QString> attrNS;
QString textData;
// reference counting
unsigned long refCount;
friend class KoXmlElement;
};
KoXmlNodeData KoXmlNodeData::null;
KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount)
: nodeType(KoXmlNode::NullNode)
, loaded(false)
#ifdef KOXML_COMPACT
, nodeDepth(0)
#endif
, parent(0), prev(0), next(0), first(0), last(0)
, packedDoc(0), nodeIndex(0)
, refCount(initialRefCount)
{
}
KoXmlNodeData::~KoXmlNodeData()
{
clear();
}
void KoXmlNodeData::clear()
{
if (first)
for (KoXmlNodeData* node = first; node ;) {
KoXmlNodeData* next = node->next;
node->unref();
node = next;
}
// only document can delete these
// normal nodes don't "own" them
if (nodeType == KoXmlNode::DocumentNode)
delete packedDoc;
nodeType = KoXmlNode::NullNode;
tagName.clear();
prefix.clear();
namespaceURI.clear();
textData.clear();
packedDoc = 0;
attr.clear();
attrNS.clear();
parent = 0;
prev = next = 0;
first = last = 0;
loaded = false;
}
QString KoXmlNodeData::text()
{
QString t;
loadChildren();
KoXmlNodeData* node = first;
while (node) {
switch (node->nodeType) {
case KoXmlNode::ElementNode:
t += node->text(); break;
case KoXmlNode::TextNode:
t += node->data(); break;
case KoXmlNode::CDATASectionNode:
t += node->data(); break;
default: break;
}
node = node->next;
}
return t;
}
QString KoXmlNodeData::nodeName() const
{
switch (nodeType) {
case KoXmlNode::ElementNode: {
QString n(tagName);
if (!prefix.isEmpty())
n.prepend(':').prepend(prefix);
return n;
}
break;
case KoXmlNode::TextNode: return QLatin1String("#text");
case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section");
case KoXmlNode::DocumentNode: return QLatin1String("#document");
case KoXmlNode::DocumentTypeNode: return tagName;
default: return QString(); break;
}
// should not happen
return QString();
}
void KoXmlNodeData::setAttribute(const QString& name, const QString& value)
{
attr.insert(name, value);
}
QString KoXmlNodeData::attribute(const QString& name, const QString& def) const
{
return attr.value(name, def);
}
bool KoXmlNodeData::hasAttribute(const QString& name) const
{
return attr.contains(name);
}
void KoXmlNodeData::setAttributeNS(const QString& nsURI,
const QString& name, const QString& value)
{
int i = name.indexOf(':');
if (i != -1) {
QString localName(name.mid(i + 1));
KoXmlStringPair key(nsURI, localName);
attrNS.insert(key, value);
}
}
QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name,
const QString& def) const
{
KoXmlStringPair key(nsURI, name);
return attrNS.value(key, def);
}
bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const
{
KoXmlStringPair key(nsURI, name);
return attrNS.contains(key);
}
void KoXmlNodeData::clearAttributes()
{
attr.clear();
attrNS.clear();
}
// FIXME how about namespaced attributes ?
QStringList KoXmlNodeData::attributeNames() const
{
QStringList result;
result = attr.keys();
return result;
}
QList< QPair<QString, QString> > KoXmlNodeData::attributeFullNames() const
{
QList< QPair<QString, QString> > result;
result = attrNS.keys();
return result;
}
QString KoXmlNodeData::data() const
{
return textData;
}
#ifdef KOXML_COMPACT
void KoXmlNodeData::loadChildren(int depth)
{
// sanity check
if (!packedDoc) return;
// already loaded ?
if (loaded && (depth <= 1)) return;
// in case depth is different
unloadChildren();
KoXmlNodeData* lastDat = 0;
unsigned childStop = 0;
if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1)
childStop = packedDoc->itemCount(nodeDepth + 1);
else {
const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1);
childStop = next.childStart;
}
const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex);
for (unsigned i = self.childStart; i < childStop; ++i) {
const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i);
bool textItem = (item.type == KoXmlNode::TextNode);
textItem |= (item.type == KoXmlNode::CDATASectionNode);
// attribute belongs to this node
if (item.attr) {
KoQName qname = packedDoc->qnameList[item.qnameIndex];
QString value = item.value;
QString prefix;
QString qName; // with prefix
QString localName; // without prefix, i.e. local name
localName = qName = qname.name;
int i = qName.indexOf(':');
if (i != -1) prefix = qName.left(i);
if (i != -1) localName = qName.mid(i + 1);
if (packedDoc->processNamespace) {
setAttributeNS(qname.nsURI, qName, value);
setAttribute(localName, value);
} else
setAttribute(qName, value);
} else {
KoQName qname = packedDoc->qnameList[item.qnameIndex];
QString value = item.value;
QString nodeName = qname.name;
QString localName;
QString prefix;
if (packedDoc->processNamespace) {
localName = qname.name;
int di = qname.name.indexOf(':');
if (di != -1) {
localName = qname.name.mid(di + 1);
prefix = qname.name.left(di);
}
nodeName = localName;
}
// make a node out of this item
KoXmlNodeData* dat = new KoXmlNodeData;
dat->nodeIndex = i;
dat->packedDoc = packedDoc;
dat->nodeDepth = nodeDepth + 1;
dat->nodeType = item.type;
dat->tagName = nodeName;
dat->localName = localName;
dat->prefix = prefix;
dat->namespaceURI = qname.nsURI;
dat->parent = this;
dat->prev = lastDat;
dat->next = 0;
dat->first = 0;
dat->last = 0;
dat->loaded = false;
dat->textData = (textItem) ? value : QString();
// adjust our linked-list
first = (first) ? first : dat;
last = dat;
if (lastDat)
lastDat->next = dat;
lastDat = dat;
// recursive
if (depth > 1)
dat->loadChildren(depth - 1);
}
}
loaded = true;
}
#else
void KoXmlNodeData::loadChildren(int depth)
{
// sanity check
if (!packedDoc) return;
// already loaded ?
if (loaded && (depth <= 1)) return;
// cause we don't know how deep this node's children already loaded are
unloadChildren();
KoXmlNodeData* lastDat = 0;
int nodeDepth = packedDoc->items[nodeIndex].depth;
for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) {
KoXmlPackedItem& item = packedDoc->items[i];
bool textItem = (item.type == KoXmlNode::TextNode);
textItem |= (item.type == KoXmlNode::CDATASectionNode);
// element already outside our depth
if (!item.attr && (item.type == KoXmlNode::ElementNode))
if (item.depth <= (unsigned)nodeDepth)
break;
// attribute belongs to this node
if (item.attr && (item.depth == (unsigned)nodeDepth)) {
KoQName qname = packedDoc->qnameList[item.qnameIndex];
QString value = item.value;
QString prefix;
QString qName; // with prefix
QString localName; // without prefix, i.e. local name
localName = qName = qname.name;
int i = qName.indexOf(':');
if (i != -1) prefix = qName.left(i);
if (i != -1) localName = qName.mid(i + 1);
if (packedDoc->processNamespace) {
setAttributeNS(qname.nsURI, qName, value);
setAttribute(localName, value);
} else
setAttribute(qname.name, value);
}
// the child node
if (!item.attr) {
bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode);
bool ok = (textItem || instruction) ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1);
ok = (item.depth == (unsigned)nodeDepth + 1);
if (ok) {
KoQName qname = packedDoc->qnameList[item.qnameIndex];
QString value = item.value;
QString nodeName = qname.name;
QString localName;
QString prefix;
if (packedDoc->processNamespace) {
localName = qname.name;
int di = qname.name.indexOf(':');
if (di != -1) {
localName = qname.name.mid(di + 1);
prefix = qname.name.left(di);
}
nodeName = localName;
}
// make a node out of this item
KoXmlNodeData* dat = new KoXmlNodeData;
dat->nodeIndex = i;
dat->packedDoc = packedDoc;
dat->nodeType = item.type;
dat->tagName = nodeName;
dat->localName = localName;
dat->prefix = prefix;
dat->namespaceURI = qname.nsURI;
dat->count = 1;
dat->parent = this;
dat->prev = lastDat;
dat->next = 0;
dat->first = 0;
dat->last = 0;
dat->loaded = false;
dat->textData = (textItem) ? value : QString();
// adjust our linked-list
first = (first) ? first : dat;
last = dat;
if (lastDat)
lastDat->next = dat;
lastDat = dat;
// recursive
if (depth > 1)
dat->loadChildren(depth - 1);
}
}
}
loaded = true;
}
#endif
void KoXmlNodeData::unloadChildren()
{
// sanity check
if (!packedDoc) return;
if (!loaded) return;
if (first)
for (KoXmlNodeData* node = first; node ;) {
KoXmlNodeData* next = node->next;
node->unloadChildren();
node->unref();
node = next;
}
clearAttributes();
loaded = false;
first = last = 0;
}
#ifdef KOXML_COMPACT
static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc,
unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode())
{
// sanity check
if (!packedDoc)
return;
const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex);
unsigned childStop = 0;
if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1)
childStop = packedDoc->itemCount(nodeDepth + 1);
else {
const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1);
childStop = next.childStart;
}
// nothing to do here
if (self.type == KoXmlNode::NullNode)
return;
// create the element properly
if (self.type == KoXmlNode::ElementNode) {
QDomElement element;
KoQName qname = packedDoc->qnameList[self.qnameIndex];
qname.nsURI = fixNamespace(qname.nsURI);
if (packedDoc->processNamespace)
element = ownerDoc.createElementNS(qname.nsURI, qname.name);
else
element = ownerDoc.createElement(qname.name);
if ( parentNode.isNull() ) {
ownerDoc.appendChild( element );
} else {
parentNode.appendChild( element );
}
// check all subnodes for attributes
for (unsigned i = self.childStart; i < childStop; ++i) {
const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i);
bool textItem = (item.type == KoXmlNode::TextNode);
textItem |= (item.type == KoXmlNode::CDATASectionNode);
// attribute belongs to this node
if (item.attr) {
KoQName qname = packedDoc->qnameList[item.qnameIndex];
qname.nsURI = fixNamespace(qname.nsURI );
QString value = item.value;
QString prefix;
QString qName; // with prefix
QString localName; // without prefix, i.e. local name
localName = qName = qname.name;
int i = qName.indexOf(':');
if (i != -1) prefix = qName.left(i);
if (i != -1) localName = qName.mid(i + 1);
if (packedDoc->processNamespace) {
element.setAttributeNS(qname.nsURI, qName, value);
element.setAttribute(localName, value);
} else
element.setAttribute(qname.name, value);
} else {
// add it recursively
itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element);
}
}
return;
}
// create the text node
if (self.type == KoXmlNode::TextNode) {
QString text = self.value;
// FIXME: choose CDATA when the value contains special characters
QDomText textNode = ownerDoc.createTextNode(text);
if ( parentNode.isNull() ) {
ownerDoc.appendChild( textNode );
} else {
parentNode.appendChild( textNode );
}
return;
}
// nothing matches? strange...
}
void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const
{
itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex);
}
#else
static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc,
unsigned nodeIndex, QDomNode parentNode = QDomNode())
{
// sanity check
if (!packedDoc)
return;
KoXmlPackedItem& item = packedDoc->items[nodeIndex];
// nothing to do here
if (item.type == KoXmlNode::NullNode)
return;
// create the element properly
if (item.type == KoXmlNode::ElementNode) {
QDomElement element;
KoQName qname = packedDoc->qnameList[item.qnameIndex];
qname.nsURI = fixNamespace(qname.nsURI);
if (packedDoc->processNamespace)
element = ownerDoc.createElementNS(qname.nsURI, qname.name);
else
element = ownerDoc.createElement(qname.name);
if ( parentNode.isNull() ) {
ownerDoc.appendChild( element );
} else {
parentNode.appendChild( element );
}
// check all subnodes for attributes
int nodeDepth = item.depth;
for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) {
KoXmlPackedItem& item = packedDoc->items[i];
bool textItem = (item.type == KoXmlNode::TextNode);
textItem |= (item.type == KoXmlNode::CDATASectionNode);
// element already outside our depth
if (!item.attr && (item.type == KoXmlNode::ElementNode))
if (item.depth <= (unsigned)nodeDepth)
break;
// attribute belongs to this node
if (item.attr && (item.depth == (unsigned)nodeDepth)) {
KoQName qname = packedDoc->qnameList[item.qnameIndex];
qname.nsURI = fixNamespace(qname.nsURI);
QString value = item.value;
QString prefix;
QString qName; // with prefix
QString localName; // without prefix, i.e. local name
localName = qName = qname.name;
int i = qName.indexOf(':');
if (i != -1) prefix = qName.left(i);
if (i != -1) localName = qName.mid(i + 1);
if (packedDoc->processNamespace) {
element.setAttributeNS(qname.nsURI, qName, value);
element.setAttribute(localName, value);
} else
element.setAttribute(qname.name, value);
}
// direct child of this node
if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) {
// add it recursively
itemAsQDomNode(ownerDoc, packedDoc, i, element);
}
}
return;
}
// create the text node
if (item.type == KoXmlNode::TextNode) {
QString text = item.value;
// FIXME: choose CDATA when the value contains special characters
QDomText textNode = ownerDoc.createTextNode(text);
if ( parentNode.isNull() ) {
ownerDoc.appendChild( textNode );
} else {
parentNode.appendChild( textNode );
}
return;
}
// nothing matches? strange...
}
void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const
{
itemAsQDomNode(ownerDoc, packedDoc, nodeIndex);
}
#endif
void KoXmlNodeData::dump()
{
printf("NodeData %p\n", (void*)this);
printf(" nodeIndex: %d\n", (int)nodeIndex);
printf(" packedDoc: %p\n", (void*)packedDoc);
printf(" nodeType : %d\n", (int)nodeType);
printf(" tagName: %s\n", qPrintable(tagName));
printf(" namespaceURI: %s\n", qPrintable(namespaceURI));
printf(" prefix: %s\n", qPrintable(prefix));
printf(" localName: %s\n", qPrintable(localName));
printf(" parent : %p\n", (void*)parent);
printf(" prev : %p\n", (void*)prev);
printf(" next : %p\n", (void*)next);
printf(" first : %p\n", (void*)first);
printf(" last : %p\n", (void*)last);
printf(" refCount: %ld\n", refCount);
if (loaded)
printf(" loaded: TRUE\n");
else
printf(" loaded: FALSE\n");
}
// ==================================================================
//
// KoXmlNodeData
//
// ==================================================================
class KoXmlDocumentData : public KoXmlNodeData
{
public:
KoXmlDocumentData(unsigned long initialRefCount = 1);
~KoXmlDocumentData();
bool setContent(QXmlStreamReader *reader,
QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0);
KoXmlDocumentType dt;
bool emptyDocument :1;
// to read the xml with or without spaces
bool stripSpaces :1;
};
#define KOXMLDOCDATA(d) static_cast<KoXmlDocumentData*>(d)
KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount)
: KoXmlNodeData(initialRefCount)
, emptyDocument(true)
, stripSpaces(true)
{
}
KoXmlDocumentData::~KoXmlDocumentData()
{
}
bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn)
{
// sanity checks
if (!reader) return false;
if (nodeType != KoXmlNode::DocumentNode)
return false;
clear();
nodeType = KoXmlNode::DocumentNode;
packedDoc = new KoXmlPackedDocument;
packedDoc->processNamespace = reader->namespaceProcessing();
ParseError error = parseDocument(*reader, *packedDoc, stripSpaces);
if (error.error) {
// parsing error has occurred
if (errorMsg) *errorMsg = error.errorMsg;
if (errorLine) *errorLine = error.errorLine;
if (errorColumn) *errorColumn = error.errorColumn;
return false;
}
// initially load
loadChildren();
KoXmlNodeData *typeData = new KoXmlNodeData(0);
typeData->nodeType = KoXmlNode::DocumentTypeNode;
typeData->tagName = packedDoc->docType;
typeData->parent = this;
dt = KoXmlDocumentType(typeData);
return true;
}
// ==================================================================
//
// KoXmlNode
//
// ==================================================================
// Creates a null node
KoXmlNode::KoXmlNode()
{
d = &KoXmlNodeData::null;
d->ref();
}
// Destroys this node
KoXmlNode::~KoXmlNode()
{
d->unref();
}
// Creates a copy of another node
KoXmlNode::KoXmlNode(const KoXmlNode& node)
{
d = node.d;
d->ref();
}
// Creates a node for specific implementation
KoXmlNode::KoXmlNode(KoXmlNodeData* data)
{
d = data;
data->ref();
}
// Creates a shallow copy of another node
KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node)
{
if (this != &node) {
d->unref();
d = node.d;
d->ref();
}
return *this;
}
// Note: two null nodes are always equal
bool KoXmlNode::operator==(const KoXmlNode& node) const
{
if (isNull() && node.isNull()) return true;
return(d == node.d);
}
// Note: two null nodes are always equal
bool KoXmlNode::operator!=(const KoXmlNode& node) const
{
if (isNull() && !node.isNull()) return true;
if (!isNull() && node.isNull()) return true;
if (isNull() && node.isNull()) return false;
return(d != node.d);
}
KoXmlNode::NodeType KoXmlNode::nodeType() const
{
return d->nodeType;
}
bool KoXmlNode::isNull() const
{
return d->nodeType == NullNode;
}
bool KoXmlNode::isElement() const
{
return d->nodeType == ElementNode;
}
bool KoXmlNode::isText() const
{
return (d->nodeType == TextNode) || isCDATASection();
}
bool KoXmlNode::isCDATASection() const
{
return d->nodeType == CDATASectionNode;
}
bool KoXmlNode::isDocument() const
{
return d->nodeType == DocumentNode;
}
bool KoXmlNode::isDocumentType() const
{
return d->nodeType == DocumentTypeNode;
}
void KoXmlNode::clear()
{
d->unref();
d = new KoXmlNodeData;
}
QString KoXmlNode::nodeName() const
{
return d->nodeName();
}
QString KoXmlNode::prefix() const
{
return isElement() ? d->prefix : QString();
}
QString KoXmlNode::namespaceURI() const
{
return isElement() ? d->namespaceURI : QString();
}
QString KoXmlNode::localName() const
{
return isElement() ? d->localName : QString();
}
KoXmlDocument KoXmlNode::ownerDocument() const
{
KoXmlNodeData* node = d;
while (node->parent) node = node->parent;
if (node->nodeType == DocumentNode) {
return KoXmlDocument(static_cast<KoXmlDocumentData*>(node));
}
return KoXmlDocument();
}
KoXmlNode KoXmlNode::parentNode() const
{
return d->parent ? KoXmlNode(d->parent) : KoXmlNode();
}
bool KoXmlNode::hasChildNodes() const
{
if (isText())
return false;
if (!d->loaded)
d->loadChildren();
return d->first != 0 ;
}
int KoXmlNode::childNodesCount() const
{
if (isText())
return 0;
if (!d->loaded)
d->loadChildren();
KoXmlNodeData* node = d->first;
int count = 0;
while (node) {
++count;
node = node->next;
}
return count;
}
QStringList KoXmlNode::attributeNames() const
{
if (!d->loaded)
d->loadChildren();
return d->attributeNames();
}
QList< QPair<QString, QString> > KoXmlNode::attributeFullNames() const
{
if (!d->loaded)
d->loadChildren();
return d->attributeFullNames();
}
KoXmlNode KoXmlNode::firstChild() const
{
if (!d->loaded)
d->loadChildren();
return d->first ? KoXmlNode(d->first) : KoXmlNode();
}
KoXmlElement KoXmlNode::firstChildElement() const
{
KoXmlElement element;
forEachElement (element, (*this)) {
return element;
}
return KoXmlElement();
}
KoXmlNode KoXmlNode::lastChild() const
{
if (!d->loaded)
d->loadChildren();
return d->last ? KoXmlNode(d->last) : KoXmlNode();
}
KoXmlNode KoXmlNode::nextSibling() const
{
return d->next ? KoXmlNode(d->next) : KoXmlNode();
}
KoXmlNode KoXmlNode::previousSibling() const
{
return d->prev ? KoXmlNode(d->prev) : KoXmlNode();
}
KoXmlNode KoXmlNode::namedItem(const QString& name) const
{
if (!d->loaded)
d->loadChildren();
for (KoXmlNodeData* node = d->first; node; node = node->next) {
if (node->nodeName() == name)
return KoXmlNode(node);
}
// not found
return KoXmlNode();
}
KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const
{
if (!d->loaded)
d->loadChildren();
for (KoXmlNodeData* node = d->first; node; node = node->next) {
if (node->nodeType == KoXmlNode::ElementNode
&& node->localName == name
&& node->namespaceURI == nsURI
) {
return KoXmlNode(node);
}
}
// not found
return KoXmlNode();
}
KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const
{
if (!d->loaded)
d->loadChildren();
for (KoXmlNodeData* node = d->first; node; node = node->next) {
if (node->nodeType != KoXmlNode::ElementNode)
continue;
if (node->localName == name && node->namespaceURI == nsURI) {
return KoXmlNode(node);
}
bool isPrelude = false;
switch (type) {
case KoXmlTextContentPrelude:
isPrelude =
(node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) ||
(node->localName == "forms" && node->namespaceURI == KoXmlNS::office);
break;
}
if (!isPrelude) {
return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here.
}
}
// not found
return KoXmlNode();
}
KoXmlElement KoXmlNode::toElement() const
{
return isElement() ? KoXmlElement(d) : KoXmlElement();
}
KoXmlText KoXmlNode::toText() const
{
return isText() ? KoXmlText(d) : KoXmlText();
}
KoXmlCDATASection KoXmlNode::toCDATASection() const
{
return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection();
}
KoXmlDocument KoXmlNode::toDocument() const
{
if (isDocument()) {
return KoXmlDocument(static_cast<KoXmlDocumentData*>(d));
}
return KoXmlDocument();
}
void KoXmlNode::load(int depth)
{
d->loadChildren(depth);
}
void KoXmlNode::unload()
{
d->unloadChildren();
}
void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const
{
Q_ASSERT(!isDocument());
d->asQDomNode(ownerDoc);
}
// ==================================================================
//
// KoXmlElement
//
// ==================================================================
// Creates an empty element
KoXmlElement::KoXmlElement(): KoXmlNode()
{
}
KoXmlElement::~KoXmlElement()
{
}
// Creates a shallow copy of another element
KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d)
{
}
KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data)
{
}
// Copies another element
KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element)
{
KoXmlNode::operator=(element);
return *this;
}
bool KoXmlElement::operator== (const KoXmlElement& element) const
{
if (isNull() || element.isNull()) return false;
return (d == element.d);
}
bool KoXmlElement::operator!= (const KoXmlElement& element) const
{
if (isNull() && element.isNull()) return false;
if (isNull() || element.isNull()) return true;
return (d != element.d);
}
QString KoXmlElement::tagName() const
{
return isElement() ? d->tagName : QString();
}
QString KoXmlElement::text() const
{
return d->text();
}
QString KoXmlElement::attribute(const QString& name) const
{
if (!isElement())
return QString();
if (!d->loaded)
d->loadChildren();
return d->attribute(name, QString());
}
QString KoXmlElement::attribute(const QString& name,
const QString& defaultValue) const
{
if (!isElement())
return defaultValue;
if (!d->loaded)
d->loadChildren();
return d->attribute(name, defaultValue);
}
QString KoXmlElement::attributeNS(const QString& namespaceURI,
const QString& localName, const QString& defaultValue) const
{
if (!isElement())
return defaultValue;
if (!d->loaded)
d->loadChildren();
KoXmlStringPair key(namespaceURI, localName);
return d->attrNS.value(key, defaultValue);
// return d->attributeNS( namespaceURI, localName, defaultValue );
}
bool KoXmlElement::hasAttribute(const QString& name) const
{
if (!d->loaded)
d->loadChildren();
return isElement() ? d->hasAttribute(name) : false;
}
bool KoXmlElement::hasAttributeNS(const QString& namespaceURI,
const QString& localName) const
{
if (!d->loaded)
d->loadChildren();
return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false;
}
// ==================================================================
//
// KoXmlText
//
// ==================================================================
KoXmlText::KoXmlText(): KoXmlNode()
{
}
KoXmlText::~KoXmlText()
{
}
KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d)
{
}
KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data)
{
}
bool KoXmlText::isText() const
{
return true;
}
QString KoXmlText::data() const
{
return d->data();
}
KoXmlText& KoXmlText::operator=(const KoXmlText & element)
{
KoXmlNode::operator=(element);
return *this;
}
// ==================================================================
//
// KoXmlCDATASection
//
// ==================================================================
KoXmlCDATASection::KoXmlCDATASection(): KoXmlText()
{
}
KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata)
: KoXmlText(cdata)
{
}
KoXmlCDATASection::~KoXmlCDATASection()
{
}
KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata):
KoXmlText(cdata)
{
}
bool KoXmlCDATASection::isCDATASection() const
{
return true;
}
KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata)
{
KoXmlNode::operator=(cdata);
return *this;
}
// ==================================================================
//
// KoXmlDocumentType
//
// ==================================================================
KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode()
{
}
KoXmlDocumentType::~KoXmlDocumentType()
{
}
KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt):
KoXmlNode(dt.d)
{
}
QString KoXmlDocumentType::name() const
{
return nodeName();
}
KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt)
{
}
KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt)
{
KoXmlNode::operator=(dt);
return *this;
}
// ==================================================================
//
// KoXmlDocument
//
// ==================================================================
KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0))
{
KOXMLDOCDATA(d)->emptyDocument = false;
KOXMLDOCDATA(d)->stripSpaces = stripSpaces;
}
KoXmlDocument::~KoXmlDocument()
{
}
KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data)
{
KOXMLDOCDATA(d)->emptyDocument = true;
}
// Creates a copy of another document
KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d)
{
}
// Creates a shallow copy of another document
KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc)
{
KoXmlNode::operator=(doc);
return *this;
}
// Checks if this document and doc are equals
bool KoXmlDocument::operator==(const KoXmlDocument& doc) const
{
return(d == doc.d);
}
// Checks if this document and doc are not equals
bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const
{
return(d != doc.d);
}
KoXmlElement KoXmlDocument::documentElement() const
{
if (!d->loaded)
d->loadChildren();
for (KoXmlNodeData* node = d->first; node; node = node->next) {
if (node->nodeType == KoXmlNode::ElementNode) {
return KoXmlElement(node);
}
}
return KoXmlElement();
}
KoXmlDocumentType KoXmlDocument::doctype() const
{
return KOXMLDOCDATA(d)->dt;
}
QString KoXmlDocument::nodeName() const
{
return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString();
}
void KoXmlDocument::clear()
{
d->unref();
KoXmlDocumentData *dat = new KoXmlDocumentData;
dat->emptyDocument = false;
d = dat;
}
namespace {
/* Use an entity resolver that ignores undefined entities and simply
returns an empty string for them.
*/
class DumbEntityResolver : public QXmlStreamEntityResolver {
public:
QString resolveUndeclaredEntity ( const QString &) { return ""; }
};
}
bool KoXmlDocument::setContent(QXmlStreamReader *reader,
QString* errorMsg, int* errorLine, int* errorColumn)
{
if (d->nodeType != KoXmlNode::DocumentNode) {
const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
d->unref();
KoXmlDocumentData *dat = new KoXmlDocumentData;
dat->nodeType = KoXmlNode::DocumentNode;
dat->stripSpaces = stripSpaces;
d = dat;
}
const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn);
return result;
}
// no namespace processing
bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg,
int* errorLine, int* errorColumn)
{
return setContent(device, false, errorMsg, errorLine, errorColumn);
}
bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing,
QString* errorMsg, int* errorLine, int* errorColumn)
{
if (d->nodeType != KoXmlNode::DocumentNode) {
const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
d->unref();
KoXmlDocumentData *dat = new KoXmlDocumentData;
dat->nodeType = KoXmlNode::DocumentNode;
dat->stripSpaces = stripSpaces;
d = dat;
}
if (!device->isOpen()) device->open(QIODevice::ReadOnly);
QXmlStreamReader reader(device);
reader.setNamespaceProcessing(namespaceProcessing);
DumbEntityResolver entityResolver;
reader.setEntityResolver(&entityResolver);
const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn);
return result;
}
bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
{
QBuffer buffer;
buffer.setData(text);
return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn);
}
bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
{
if (d->nodeType != KoXmlNode::DocumentNode) {
const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
d->unref();
KoXmlDocumentData *dat = new KoXmlDocumentData;
dat->nodeType = KoXmlNode::DocumentNode;
dat->stripSpaces = stripSpaces;
d = dat;
}
QXmlStreamReader reader(text);
reader.setNamespaceProcessing(namespaceProcessing);
DumbEntityResolver entityResolver;
reader.setEntityResolver(&entityResolver);
const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn);
return result;
}
bool KoXmlDocument::setContent(const QString& text,
QString *errorMsg, int *errorLine, int *errorColumn)
{
return setContent(text, false, errorMsg, errorLine, errorColumn);
}
void KoXmlDocument::setWhitespaceStripping(bool stripSpaces)
{
KOXMLDOCDATA(d)->stripSpaces = stripSpaces;
}
#endif
// ==================================================================
//
// functions in KoXml namespace
//
// ==================================================================
KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI,
const QString& localName)
{
#ifdef KOXML_USE_QDOM
// David's solution for namedItemNS, only for QDom stuff
KoXmlNode n = node.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (n.isElement() && n.localName() == localName &&
n.namespaceURI() == nsURI)
return n.toElement();
}
return KoXmlElement();
#else
return node.namedItemNS(nsURI, localName).toElement();
#endif
}
KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI,
const QString& localName, KoXmlNamedItemType type)
{
#ifdef KOXML_USE_QDOM
Q_ASSERT(false);
return namedItemNS(node, nsURI, localName);
#else
return node.namedItemNS(nsURI, localName, type).toElement();
#endif
}
void KoXml::load(KoXmlNode& node, int depth)
{
#ifdef KOXML_USE_QDOM
// do nothing, QDom has no on-demand loading
Q_UNUSED(node);
Q_UNUSED(depth);
#else
node.load(depth);
#endif
}
void KoXml::unload(KoXmlNode& node)
{
#ifdef KOXML_USE_QDOM
// do nothing, QDom has no on-demand unloading
Q_UNUSED(node);
#else
node.unload();
#endif
}
int KoXml::childNodesCount(const KoXmlNode& node)
{
#ifdef KOXML_USE_QDOM
return node.childNodes().count();
#else
// compatibility function, because no need to implement
// a class like QDomNodeList
return node.childNodesCount();
#endif
}
QStringList KoXml::attributeNames(const KoXmlNode& node)
{
#ifdef KOXML_USE_QDOM
QStringList result;
QDomNamedNodeMap attrMap = node.attributes();
for (int i = 0; i < attrMap.count(); ++i)
result += attrMap.item(i).toAttr().name();
return result;
#else
// compatibility function, because no need to implement
// a class like QDomNamedNodeMap
return node.attributeNames();
#endif
}
void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node)
{
Q_ASSERT(!node.isDocument());
#ifdef KOXML_USE_QDOM
ownerDoc.appendChild(ownerDoc.importNode(node));
#else
node.asQDomNode(ownerDoc);
#endif
}
void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element)
{
KoXml::asQDomNode(ownerDoc, element);
}
QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document)
{
#ifdef KOXML_USE_QDOM
return document;
#else
QDomDocument qdoc( document.nodeName() );
if ( document.hasChildNodes() ) {
for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) {
KoXml::asQDomNode(qdoc, n);
}
}
return qdoc;
#endif
}
bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device,
bool namespaceProcessing, QString* errorMsg, int* errorLine,
int* errorColumn)
{
QXmlStreamReader reader(device);
reader.setNamespaceProcessing(namespaceProcessing);
bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn);
return result;
}
diff --git a/src/libs/store/KoXmlWriter.cpp b/src/libs/store/KoXmlWriter.cpp
index 97a419aa..ce2ac705 100644
--- a/src/libs/store/KoXmlWriter.cpp
+++ b/src/libs/store/KoXmlWriter.cpp
@@ -1,571 +1,572 @@
/* This file is part of the KDE project
Copyright (C) 2004 David Faure <faure@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
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 "KoXmlWriter.h"
#include <StoreDebug.h>
#include <QByteArray>
#include <QStack>
#include <float.h>
static const int s_indentBufferLength = 100;
static const int s_escapeBufferLen = 10000;
class Q_DECL_HIDDEN KoXmlWriter::Private
{
public:
Private(QIODevice* dev_, int indentLevel = 0) : dev(dev_), baseIndentLevel(indentLevel) {}
~Private() {
delete[] indentBuffer;
delete[] escapeBuffer;
//TODO: look at if we must delete "dev". For me we must delete it otherwise we will leak it
}
QIODevice* dev;
QStack<Tag> tags;
int baseIndentLevel;
char* indentBuffer; // maybe make it static, but then it needs a K_GLOBAL_STATIC
// and would eat 1K all the time... Maybe refcount it :)
char* escapeBuffer; // can't really be static if we want to be thread-safe
};
KoXmlWriter::KoXmlWriter(QIODevice* dev, int indentLevel)
: d(new Private(dev, indentLevel))
{
init();
}
void KoXmlWriter::init()
{
d->indentBuffer = new char[ s_indentBufferLength ];
memset(d->indentBuffer, ' ', s_indentBufferLength);
*d->indentBuffer = '\n'; // write newline before indentation, in one go
d->escapeBuffer = new char[s_escapeBufferLen];
if (!d->dev->isOpen())
d->dev->open(QIODevice::WriteOnly);
}
KoXmlWriter::~KoXmlWriter()
{
delete d;
}
void KoXmlWriter::startDocument(const char* rootElemName, const char* publicId, const char* systemId)
{
Q_ASSERT(d->tags.isEmpty());
writeCString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
// There isn't much point in a doctype if there's no DTD to refer to
// (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema)
if (publicId) {
writeCString("<!DOCTYPE ");
writeCString(rootElemName);
writeCString(" PUBLIC \"");
writeCString(publicId);
writeCString("\" \"");
writeCString(systemId);
writeCString("\"");
writeCString(">\n");
}
}
void KoXmlWriter::endDocument()
{
// just to do exactly like QDom does (newline at end of file).
writeChar('\n');
Q_ASSERT(d->tags.isEmpty());
}
// returns the value of indentInside of the parent
bool KoXmlWriter::prepareForChild()
{
if (!d->tags.isEmpty()) {
Tag& parent = d->tags.top();
if (!parent.hasChildren) {
closeStartElement(parent);
parent.hasChildren = true;
parent.lastChildIsText = false;
}
if (parent.indentInside) {
writeIndent();
}
return parent.indentInside;
}
return true;
}
void KoXmlWriter::prepareForTextNode()
{
if (d->tags.isEmpty())
return;
Tag& parent = d->tags.top();
if (!parent.hasChildren) {
closeStartElement(parent);
parent.hasChildren = true;
parent.lastChildIsText = true;
}
}
void KoXmlWriter::startElement(const char* tagName, bool indentInside)
{
Q_ASSERT(tagName != 0);
// Tell parent that it has children
bool parentIndent = prepareForChild();
d->tags.push(Tag(tagName, parentIndent && indentInside));
writeChar('<');
writeCString(tagName);
//kDebug(s_area) << tagName;
}
void KoXmlWriter::addCompleteElement(const char* cstr)
{
prepareForChild();
writeCString(cstr);
}
void KoXmlWriter::addCompleteElement(QIODevice* indev)
{
prepareForChild();
const bool wasOpen = indev->isOpen();
// Always (re)open the device in readonly mode, it might be
// already open but for writing, and we need to rewind.
const bool openOk = indev->open(QIODevice::ReadOnly);
Q_ASSERT(openOk);
if (!openOk) {
warnStore << "Failed to re-open the device! wasOpen=" << wasOpen;
return;
}
static const int MAX_CHUNK_SIZE = 8 * 1024; // 8 KB
QByteArray buffer;
buffer.resize(MAX_CHUNK_SIZE);
while (!indev->atEnd()) {
qint64 len = indev->read(buffer.data(), buffer.size());
if (len <= 0) // e.g. on error
break;
d->dev->write(buffer.data(), len);
}
if (!wasOpen) {
// Restore initial state
indev->close();
}
}
void KoXmlWriter::endElement()
{
if (d->tags.isEmpty())
warnStore << "EndElement() was called more times than startElement(). "
"The generated XML will be invalid! "
"Please report this bug (by saving the document to another format...)" << endl;
Tag tag = d->tags.pop();
if (!tag.hasChildren) {
writeCString("/>");
} else {
if (tag.indentInside && !tag.lastChildIsText) {
writeIndent();
}
writeCString("</");
Q_ASSERT(tag.tagName != 0);
writeCString(tag.tagName);
writeChar('>');
}
}
void KoXmlWriter::addTextNode(const QByteArray& cstr)
{
// Same as the const char* version below, but here we know the size
prepareForTextNode();
char* escaped = escapeForXML(cstr.constData(), cstr.size());
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
}
void KoXmlWriter::addTextNode(const char* cstr)
{
prepareForTextNode();
char* escaped = escapeForXML(cstr, -1);
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
}
void KoXmlWriter::addProcessingInstruction(const char* cstr)
{
prepareForTextNode();
writeCString("<?");
addTextNode(cstr);
writeCString("?>");
}
void KoXmlWriter::addAttribute(const char* attrName, const QByteArray& value)
{
// Same as the const char* one, but here we know the size
writeChar(' ');
writeCString(attrName);
writeCString("=\"");
char* escaped = escapeForXML(value.constData(), value.size());
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
writeChar('"');
}
void KoXmlWriter::addAttribute(const char* attrName, const char* value)
{
writeChar(' ');
writeCString(attrName);
writeCString("=\"");
char* escaped = escapeForXML(value, -1);
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
writeChar('"');
}
void KoXmlWriter::addAttribute(const char* attrName, double value)
{
QByteArray str;
str.setNum(value, 'f', 11);
addAttribute(attrName, str.data());
}
void KoXmlWriter::addAttribute(const char* attrName, float value)
{
QByteArray str;
str.setNum(value, 'f', FLT_DIG);
addAttribute(attrName, str.data());
}
void KoXmlWriter::addAttributePt(const char* attrName, double value)
{
QByteArray str;
str.setNum(value, 'f', 11);
str += "pt";
addAttribute(attrName, str.data());
}
void KoXmlWriter::addAttributePt(const char* attrName, float value)
{
QByteArray str;
str.setNum(value, 'f', FLT_DIG);
str += "pt";
addAttribute(attrName, str.data());
}
void KoXmlWriter::writeIndent()
{
// +1 because of the leading '\n'
d->dev->write(d->indentBuffer, qMin(indentLevel() + 1,
s_indentBufferLength));
}
void KoXmlWriter::writeString(const QString& str)
{
// cachegrind says .utf8() is where most of the time is spent
const QByteArray cstr = str.toUtf8();
d->dev->write(cstr);
}
// In case of a reallocation (ret value != d->buffer), the caller owns the return value,
// it must delete it (with [])
char* KoXmlWriter::escapeForXML(const char* source, int length = -1) const
{
// we're going to be pessimistic on char length; so lets make the outputLength less
// the amount one char can take: 6
char* destBoundary = d->escapeBuffer + s_escapeBufferLen - 6;
char* destination = d->escapeBuffer;
char* output = d->escapeBuffer;
const char* src = source; // src moves, source remains
for (;;) {
if (destination >= destBoundary) {
// When we come to realize that our escaped string is going to
// be bigger than the escape buffer (this shouldn't happen very often...),
// we drop the idea of using it, and we allocate a bigger buffer.
// Note that this if() can only be hit once per call to the method.
if (length == -1)
length = qstrlen(source); // expensive...
uint newLength = length * 6 + 1; // worst case. 6 is due to &quot; and &apos;
char* buffer = new char[ newLength ];
destBoundary = buffer + newLength;
uint amountOfCharsAlreadyCopied = destination - d->escapeBuffer;
memcpy(buffer, d->escapeBuffer, amountOfCharsAlreadyCopied);
output = buffer;
destination = buffer + amountOfCharsAlreadyCopied;
}
switch (*src) {
case 60: // <
memcpy(destination, "&lt;", 4);
destination += 4;
break;
case 62: // >
memcpy(destination, "&gt;", 4);
destination += 4;
break;
case 34: // "
memcpy(destination, "&quot;", 6);
destination += 6;
break;
#if 0 // needed?
case 39: // '
memcpy(destination, "&apos;", 6);
destination += 6;
break;
#endif
case 38: // &
memcpy(destination, "&amp;", 5);
destination += 5;
break;
case 0:
*destination = '\0';
return output;
// Control codes accepted in XML 1.0 documents.
case 9:
case 10:
case 13:
*destination++ = *src++;
continue;
default:
// Don't add control codes not accepted in XML 1.0 documents.
if (*src > 0 && *src < 32) {
++src;
} else {
*destination++ = *src++;
}
continue;
}
++src;
}
// NOTREACHED (see case 0)
return output;
}
void KoXmlWriter::addManifestEntry(const QString& fullPath, const QString& mediaType)
{
startElement("manifest:file-entry");
addAttribute("manifest:media-type", mediaType);
addAttribute("manifest:full-path", fullPath);
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, const QString& value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "string");
addTextNode(value);
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, bool value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "boolean");
addTextNode(value ? "true" : "false");
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, int value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "int");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, double value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "double");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, float value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "double");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, long value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "long");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, short value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "short");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addTextSpan(const QString& text)
{
QMap<int, int> tabCache;
addTextSpan(text, tabCache);
}
void KoXmlWriter::addTextSpan(const QString& text, const QMap<int, int>& tabCache)
{
int len = text.length();
int nrSpaces = 0; // number of consecutive spaces
bool leadingSpace = false;
QString str;
str.reserve(len);
// Accumulate chars either in str or in nrSpaces (for spaces).
// Flush str when writing a subelement (for spaces or for another reason)
// Flush nrSpaces when encountering two or more consecutive spaces
for (int i = 0; i < len ; ++i) {
QChar ch = text[i];
ushort unicode = ch.unicode();
if (unicode == ' ') {
if (i == 0)
leadingSpace = true;
++nrSpaces;
} else {
if (nrSpaces > 0) {
// For the first space we use ' '.
// "it is good practice to use (text:s) for the second and all following SPACE
// characters in a sequence." (per the ODF spec)
// however, per the HTML spec, "authors should not rely on user agents to render
// white space immediately after a start tag or immediately before an end tag"
// (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...)
if (!leadingSpace) {
str += ' ';
--nrSpaces;
}
if (nrSpaces > 0) { // there are more spaces
if (!str.isEmpty())
addTextNode(str);
str.clear();
startElement("text:s");
if (nrSpaces > 1) // it's 1 by default
addAttribute("text:c", nrSpaces);
endElement();
}
}
nrSpaces = 0;
leadingSpace = false;
switch (unicode) {
case '\t':
if (!str.isEmpty())
addTextNode(str);
str.clear();
startElement("text:tab");
if (tabCache.contains(i))
addAttribute("text:tab-ref", tabCache[i] + 1);
endElement();
break;
// gracefully handle \f form feed in text input.
// otherwise the xml will not be valid.
// \f can be added e.g. in ascii import filter.
case '\f':
case '\n':
case QChar::LineSeparator:
if (!str.isEmpty())
addTextNode(str);
str.clear();
startElement("text:line-break");
endElement();
break;
default:
// don't add stuff that is not allowed in xml. The stuff we need we have already handled above
if (ch.unicode() >= 0x20) {
str += text[i];
}
break;
}
}
}
// either we still have text in str or we have spaces in nrSpaces
if (!str.isEmpty()) {
addTextNode(str);
}
if (nrSpaces > 0) { // there are more spaces
startElement("text:s");
if (nrSpaces > 1) // it's 1 by default
addAttribute("text:c", nrSpaces);
endElement();
}
}
QIODevice *KoXmlWriter::device() const
{
return d->dev;
}
int KoXmlWriter::indentLevel() const
{
return d->tags.size() + d->baseIndentLevel;
}
QList<const char*> KoXmlWriter::tagHierarchy() const
{
QList<const char*> answer;
foreach(const Tag & tag, d->tags)
answer.append(tag.tagName);
return answer;
}
QString KoXmlWriter::toString() const
{
Q_ASSERT(!d->dev->isSequential());
if (d->dev->isSequential())
return QString();
bool wasOpen = d->dev->isOpen();
qint64 oldPos = -1;
if (wasOpen) {
oldPos = d->dev->pos();
if (oldPos > 0)
d->dev->seek(0);
} else {
const bool openOk = d->dev->open(QIODevice::ReadOnly);
Q_ASSERT(openOk);
if (!openOk)
return QString();
}
QString s = QString::fromUtf8(d->dev->readAll());
if (wasOpen)
d->dev->seek(oldPos);
else
d->dev->close();
return s;
}
diff --git a/src/libs/store/KoZipStore.cpp b/src/libs/store/KoZipStore.cpp
index 6af1c6f1..6b9fe50b 100644
--- a/src/libs/store/KoZipStore.cpp
+++ b/src/libs/store/KoZipStore.cpp
@@ -1,242 +1,243 @@
/* This file is part of the KDE project
Copyright (C) 2000-2002 David Faure <faure@kde.org>
Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
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 "KoZipStore.h"
#include "KoStore_p.h"
#include <QBuffer>
#include <QByteArray>
#include <kzip.h>
#include <StoreDebug.h>
#include <QUrl>
#include <KoNetAccess.h>
KoZipStore::KoZipStore(const QString & _filename, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoZipStore Constructor filename =" << _filename
<< " mode = " << int(mode)
<< " mimetype = " << appIdentification << endl;
Q_D(KoStore);
d->localFileName = _filename;
m_pZip = new KZip(_filename);
init(appIdentification); // open the zip file and init some vars
}
KoZipStore::KoZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
m_pZip = new KZip(dev);
init(appIdentification);
}
KoZipStore::KoZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoZipStore Constructor url" << _url.url(QUrl::PreferLocalFile)
<< " filename = " << _filename
<< " mode = " << int(mode)
<< " mimetype = " << appIdentification << endl;
Q_D(KoStore);
d->url = _url;
d->window = window;
if (mode == KoStore::Read) {
d->fileMode = KoStorePrivate::RemoteRead;
d->localFileName = _filename;
} else {
d->fileMode = KoStorePrivate::RemoteWrite;
d->localFileName = QLatin1String("/tmp/kozip"); // ### FIXME with KTempFile
}
m_pZip = new KZip(d->localFileName);
init(appIdentification); // open the zip file and init some vars
}
KoZipStore::~KoZipStore()
{
Q_D(KoStore);
debugStore << "KoZipStore::~KoZipStore";
if (!d->finalized)
finalize(); // ### no error checking when the app forgot to call finalize itself
delete m_pZip;
// Now we have still some job to do for remote files.
if (d->fileMode == KoStorePrivate::RemoteRead) {
KIO::NetAccess::removeTempFile(d->localFileName);
} else if (d->fileMode == KoStorePrivate::RemoteWrite) {
KIO::NetAccess::upload(d->localFileName, d->url, d->window);
// ### FIXME: delete temp file
}
}
void KoZipStore::init(const QByteArray& appIdentification)
{
Q_D(KoStore);
m_currentDir = 0;
d->good = m_pZip->open(d->mode == Write ? QIODevice::WriteOnly : QIODevice::ReadOnly);
if (!d->good)
return;
if (d->mode == Write) {
//debugStore <<"KoZipStore::init writing mimetype" << appIdentification;
m_pZip->setCompression(KZip::NoCompression);
m_pZip->setExtraField(KZip::NoExtraField);
// Write identification
if (d->writeMimetype) {
(void)m_pZip->writeFile(QLatin1String("mimetype"), appIdentification);
}
m_pZip->setCompression(KZip::DeflateCompression);
// We don't need the extra field in Calligra - so we leave it as "no extra field".
} else {
d->good = m_pZip->directory() != 0;
}
}
void KoZipStore::setCompressionEnabled(bool e)
{
if (e) {
m_pZip->setCompression(KZip::DeflateCompression);
} else {
m_pZip->setCompression(KZip::NoCompression);
}
}
bool KoZipStore::doFinalize()
{
return m_pZip->close();
}
bool KoZipStore::openWrite(const QString& name)
{
Q_D(KoStore);
d->stream = 0; // Don't use!
return m_pZip->prepareWriting(name, "", "" /*m_pZip->rootDir()->user(), m_pZip->rootDir()->group()*/, 0);
}
bool KoZipStore::openRead(const QString& name)
{
Q_D(KoStore);
const KArchiveEntry * entry = m_pZip->directory()->entry(name);
if (entry == 0) {
return false;
}
if (entry->isDirectory()) {
warnStore << name << " is a directory !";
return false;
}
// Must cast to KZipFileEntry, not only KArchiveFile, because device() isn't virtual!
const KZipFileEntry * f = static_cast<const KZipFileEntry *>(entry);
delete d->stream;
d->stream = f->createDevice();
d->size = f->size();
return true;
}
qint64 KoZipStore::write(const char* _data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
//debugStore <<"KoZipStore::write" << _len;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
d->size += _len;
if (m_pZip->writeData(_data, _len)) // writeData returns a bool!
return _len;
return 0;
}
QStringList KoZipStore::directoryList() const
{
QStringList retval;
const KArchiveDirectory *directory = m_pZip->directory();
foreach(const QString &name, directory->entries()) {
const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
if (fileArchiveEntry->isDirectory()) {
retval << name;
}
}
return retval;
}
bool KoZipStore::closeWrite()
{
Q_D(KoStore);
debugStore << "Wrote file" << d->fileName << " into ZIP archive. size" << d->size;
return m_pZip->finishWriting(d->size);
}
bool KoZipStore::enterRelativeDirectory(const QString& dirName)
{
Q_D(KoStore);
if (d->mode == Read) {
if (!m_currentDir) {
m_currentDir = m_pZip->directory(); // initialize
Q_ASSERT(d->currentPath.isEmpty());
}
const KArchiveEntry *entry = m_currentDir->entry(dirName);
if (entry && entry->isDirectory()) {
m_currentDir = dynamic_cast<const KArchiveDirectory*>(entry);
return m_currentDir != 0;
}
return false;
} else // Write, no checking here
return true;
}
bool KoZipStore::enterAbsoluteDirectory(const QString& path)
{
if (path.isEmpty()) {
m_currentDir = 0;
return true;
}
m_currentDir = dynamic_cast<const KArchiveDirectory*>(m_pZip->directory()->entry(path));
Q_ASSERT(m_currentDir);
return m_currentDir != 0;
}
bool KoZipStore::fileExists(const QString& absPath) const
{
const KArchiveEntry *entry = m_pZip->directory()->entry(absPath);
return entry && entry->isFile();
}
diff --git a/src/libs/store/StoreDebug.cpp b/src/libs/store/StoreDebug.cpp
index 691c14e8..2e735a9f 100644
--- a/src/libs/store/StoreDebug.cpp
+++ b/src/libs/store/StoreDebug.cpp
@@ -1,27 +1,28 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "StoreDebug.h"
const QLoggingCategory &STORE_LOG() \
{
static const QLoggingCategory category("calligra.plan.lib.store");
return category;
}
diff --git a/src/libs/store/tests/TestKoLZF.cpp b/src/libs/store/tests/TestKoLZF.cpp
index 94e0251f..2f7b3275 100644
--- a/src/libs/store/tests/TestKoLZF.cpp
+++ b/src/libs/store/tests/TestKoLZF.cpp
@@ -1,248 +1,249 @@
/* This file is part of the KDE project
* Copyright 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* 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 "TestKoLZF.h"
#include <KoLZF.h>
#include <QTest>
void TestKoLZF::testArrayCompressionEmpty_data()
{
QTest::addColumn<char>("canary");
QTest::newRow("00") << '\0';
QTest::newRow("FF") << '\xFF';
}
void TestKoLZF::testArrayCompressionEmpty()
{
QFETCH(char, canary);
const char inputData[] = "test";
// use one more byte and see if it stays untouched
char outputdata[1];
outputdata[0] = canary;
const int compressedDataLength = KoLZF::compress(inputData, 0, outputdata, 0);
QCOMPARE(compressedDataLength, 0);
QCOMPARE(outputdata[0], canary);
}
void TestKoLZF::testArrayCompressionNullPointerInput()
{
char outputdata[4];
const int compressedDataLength = KoLZF::compress(0, 4, outputdata, 4);
QCOMPARE(compressedDataLength, 0);
}
void TestKoLZF::testArrayCompressionNullPointerOutput()
{
const char inputData[] = "test";
const int compressedDataLength = KoLZF::compress(inputData, 4, 0, 4);
QCOMPARE(compressedDataLength, 0);
}
void TestKoLZF::testArrayDecompressionEmpty_data()
{
QTest::addColumn<char>("canary");
QTest::newRow("00") << '\0';
QTest::newRow("FF") << '\xFF';
}
void TestKoLZF::testArrayDecompressionEmpty()
{
QFETCH(char, canary);
const char inputData[] = "test";
char outputdata[1];
outputdata[0] = canary;
const int uncompressedDataLength = KoLZF::decompress(inputData, 0, outputdata, 0);
QCOMPARE(uncompressedDataLength, 0);
QCOMPARE(outputdata[0], canary);
}
void TestKoLZF::testArrayDecompressionNullPointerInput()
{
char outputdata[4];
const int uncompressedDataLength = KoLZF::decompress(0, 0, outputdata, 4);
QCOMPARE(uncompressedDataLength, 0);
}
void TestKoLZF::testArrayDecompressionNullPointerOutput()
{
const char inputData[] = "test";
const int uncompressedDataLength = KoLZF::decompress(inputData, 4, 0, 4);
QCOMPARE(uncompressedDataLength, 0);
}
Q_DECLARE_METATYPE(char*)
void TestKoLZF::testArrayRoundtripDifferentSizes_data()
{
QTest::addColumn<char*>("data");
QTest::addColumn<int>("size");
QTest::addColumn<char>("canary");
static const char canary[] = {'\0', '\xFF'};
static const int canaryCount = sizeof( canary ) / sizeof( canary[0] );
static const int fillMethodCount = 2;
static const char * const fillMethodName[fillMethodCount] = {"uni", "series"};
for(int c = 0; c < canaryCount; ++c) {
for(int i = 1; i < 512; ++i) {
for (int f = 0; f < 2; ++f) {
char *data = new char[i];
if (f == 0) {
memset(data, 't', i);
} else {
for (int b = 0; b < i; ++b) {
data[b] = b % 256;
}
}
QByteArray ba = QByteArray::number(i)+'-'+QByteArray(1, canary[c]).toHex()+'-'+QByteArray(fillMethodName[f]);
QTest::newRow(ba.constData())
<< data << i << canary[c];
}
}
}
}
void TestKoLZF::testArrayRoundtripDifferentSizes()
{
QFETCH(char*, data);
QFETCH(int, size);
QFETCH(char, canary);
char * compressedData = new char[size+1];
compressedData[size] = canary;
// try
const int compressedDataLength = KoLZF::compress(data, size, compressedData, size);
QCOMPARE(compressedData[size], canary);
const bool compressed = (compressedDataLength != 0);
// done with testing if not compressed
if (!compressed) {
delete [] data;
delete [] compressedData;
return;
}
// now try uncompressing
char * uncompressedData = new char[size+1];
uncompressedData[size] = canary;
const int uncompressedDataLength =
KoLZF::decompress(compressedData, compressedDataLength, uncompressedData, size);
QVERIFY(uncompressedDataLength != 0);
QCOMPARE(uncompressedDataLength, size);
QCOMPARE(memcmp(uncompressedData, data, size), 0 );
QCOMPARE(uncompressedData[size], canary);
delete [] data;
delete [] compressedData;
delete [] uncompressedData;
}
void TestKoLZF::testByteArrayCompressionEmpty()
{
const QByteArray empty;
// try
const QByteArray compressed = KoLZF::compress(empty);
// only metadata
QCOMPARE(compressed.size(), 5);
// size is 0
QCOMPARE(compressed.at(0), static_cast<char>(0));
QCOMPARE(compressed.at(1), static_cast<char>(0));
QCOMPARE(compressed.at(2), static_cast<char>(0));
QCOMPARE(compressed.at(3), static_cast<char>(0));
// uncompressed
QCOMPARE(compressed.at(4), static_cast<char>(0));
}
void TestKoLZF::testByteArrayDecompressionEmpty()
{
const char emptyCompressedRaw[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
const QByteArray emptyCompressed = QByteArray::fromRawData(emptyCompressedRaw, 5);
QByteArray uncompressed;
// try
KoLZF::decompress(emptyCompressed, uncompressed);
// empty
QCOMPARE(uncompressed.size(), 0);
}
void TestKoLZF::testByteArrayRoundtripDifferentSizes_data()
{
QTest::addColumn<QByteArray>("data");
static const int fillMethodCount = 2;
static const char * const fillMethodName[fillMethodCount] = {"uni", "series"};
for(int i = 0; i < 512; ++i) {
for (int f = 0; f < 2; ++f) {
QByteArray data;
if (f == 0) {
data = QByteArray(i, 't');
} else {
data.resize(i);
for (int b = 0; b < i; ++b) {
data[b] = b % 256;
}
}
QByteArray ba = QByteArray::number(i)+'-'+fillMethodName[f];
QTest::newRow(ba.constData()) << data;
}
}
}
void TestKoLZF::testByteArrayRoundtripDifferentSizes()
{
QFETCH(QByteArray, data);
// try
const QByteArray compressed = KoLZF::compress(data);
QByteArray uncompressed;
KoLZF::decompress(compressed, uncompressed);
QCOMPARE(uncompressed, data);
}
QTEST_GUILESS_MAIN(TestKoLZF)
diff --git a/src/libs/store/tests/TestKoXmlVector.cpp b/src/libs/store/tests/TestKoXmlVector.cpp
index 2ac91bcf..350c8967 100644
--- a/src/libs/store/tests/TestKoXmlVector.cpp
+++ b/src/libs/store/tests/TestKoXmlVector.cpp
@@ -1,162 +1,163 @@
/* This file is part of the KDE project
* Copyright 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* 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 "TestKoXmlVector.h"
#include <KoXmlVector.h>
#include <QTest>
#include <QDebug>
enum TestEnum
{
FirstType = 0,
SecondType = 1,
ThirdType = 2,
FourthType = 3,
FifthType = 4
};
class TestStruct
{
public:
bool attr: 1;
TestEnum type: 3;
unsigned int number: 28;
QString string;
};
Q_DECLARE_TYPEINFO(TestStruct, Q_MOVABLE_TYPE);
static QDataStream& operator<<(QDataStream& s, const TestStruct& item)
{
quint8 flag = item.attr ? 1 : 0;
s << flag;
s << (quint8) item.type;
s << item.number;
s << item.string;
return s;
}
static QDataStream& operator>>(QDataStream& s, TestStruct& item)
{
quint8 flag;
quint8 type;
int number;
QString string;
s >> flag;
s >> type;
s >> number;
s >> string;
item.attr = (flag != 0);
item.type = (TestEnum) type;
item.number = number;
item.string = string;
return s;
}
void TestKoXmlVector::simpleConstructor()
{
KoXmlVector<TestStruct> vector;
QCOMPARE(vector.count(), 0);
QCOMPARE(vector.size(), 0);
QCOMPARE(vector.isEmpty(), true);
}
static const int writeAndReadUncompressedCount = 5;
void TestKoXmlVector::writeAndRead_data()
{
QTest::addColumn<unsigned int>("itemCount");
for(unsigned int i = 0; i < writeAndReadUncompressedCount*3+1; ++i) {
QTest::newRow(QByteArray::number(i)) << i;
}
}
void TestKoXmlVector::writeAndRead()
{
QFETCH(unsigned int, itemCount);
KoXmlVector<TestStruct, writeAndReadUncompressedCount+1> vector;
// add 3x items than what would not be compressed
for (unsigned int i = 0; i < itemCount; ++i) {
// test adding
TestStruct &item = vector.newItem();
const bool attr = (i % 2) == 0;
const TestEnum type = (TestEnum)(i % 5);
const unsigned int number = i;
const QString string = QString::number(i);
item.attr = attr;
item.type = type;
item.number = number;
item.string = string;
QCOMPARE(vector.count(), (signed)i+1);
QCOMPARE(vector.size(), (signed)i+1);
QCOMPARE(vector.isEmpty(), false);
}
vector.squeeze();
// now check all in a row again, so including all the uncompressed
for (unsigned int i = 0; i < itemCount; ++i) {
const bool attr = (i % 2) == 0;
const TestEnum type = (TestEnum)(i % 5);
const unsigned int number = i;
const QString string = QString::number(i);
const TestStruct &readItem = vector[i];
QCOMPARE(readItem.attr, attr);
QCOMPARE(readItem.type, type);
QCOMPARE(readItem.number, number);
QCOMPARE(readItem.string, string);
}
// and backwards
for (unsigned int ri = itemCount; ri > 0; --ri) {
const unsigned int i = ri-1;
const bool attr = (i % 2) == 0;
const TestEnum type = (TestEnum)(i % 5);
const unsigned int number = i;
const QString string = QString::number(i);
const TestStruct &readItem = vector[i];
QCOMPARE(readItem.attr, attr);
QCOMPARE(readItem.type, type);
QCOMPARE(readItem.number, number);
QCOMPARE(readItem.string, string);
}
}
QTEST_GUILESS_MAIN(TestKoXmlVector)
diff --git a/src/libs/store/tests/storedroptest.cpp b/src/libs/store/tests/storedroptest.cpp
index e7a33fb4..44c2ccb1 100644
--- a/src/libs/store/tests/storedroptest.cpp
+++ b/src/libs/store/tests/storedroptest.cpp
@@ -1,172 +1,173 @@
/* This file is part of the KDE project
* Copyright (C) 2004 David Faure <faure@kde.org>
*
* 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 <KoStore.h>
#include <QStringList>
#include <QBuffer>
#include <QMimeData>
#include <QClipboard>
#include <QTextBrowser>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QKeyEvent>
#include <QDropEvent>
#include <QApplication>
#include <QCommandLineParser>
class StoreDropTest : public QTextBrowser
{
Q_OBJECT
public:
StoreDropTest(QWidget* parent);
protected:
virtual void contentsDragEnterEvent(QDragEnterEvent * e);
virtual void contentsDragMoveEvent(QDragMoveEvent * e);
virtual void contentsDropEvent(QDropEvent * e);
virtual void keyPressEvent(QKeyEvent * e);
virtual void paste();
private:
bool processMimeData(const QMimeData* mimeData);
void showZipContents(QByteArray data, const QString &mimeType, bool oasis);
QString loadTextFile(KoStore* store, const QString& fileName);
};
int main(int argc, char** argv)
{
QApplication app(argc, argv);
StoreDropTest* window = new StoreDropTest(0);
window->resize(500, 500);
window->show();
QObject::connect(qApp, SIGNAL(lastWindowClosed()), qApp, SLOT(quit()));
return app.exec();
}
StoreDropTest::StoreDropTest(QWidget* parent)
: QTextBrowser(parent)
{
setText("KoStore drop/paste test\nDrop or paste a selection from a Calligra application into this widget to see the ZIP contents");
setAcceptDrops(true);
}
void StoreDropTest::contentsDragEnterEvent(QDragEnterEvent * ev)
{
ev->acceptProposedAction();
}
void StoreDropTest::contentsDragMoveEvent(QDragMoveEvent * ev)
{
ev->acceptProposedAction();
}
void StoreDropTest::keyPressEvent(QKeyEvent * e)
{
if (((e->modifiers() & Qt::ShiftModifier) && e->key() == Qt::Key_Insert) ||
((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_V))
paste();
//else
// QTextBrowser::keyPressEvent( e );
}
void StoreDropTest::paste()
{
qDebug("paste");
const QMimeData* m = QApplication::clipboard()->mimeData();
if (!m)
return;
const QString acceptMimeType("application/vnd.oasis.opendocument.");
QStringList formats = m->formats();
foreach(QString fmt, formats) {
bool oasis = fmt.startsWith(acceptMimeType);
if (oasis || fmt == "application/x-kpresenter") {
QByteArray data = m->data(fmt);
showZipContents(data, fmt.toLatin1(), oasis);
return;
}
}
setText("No acceptable format found. All I got was:\n" + formats.join("\n"));
}
void StoreDropTest::contentsDropEvent(QDropEvent *ev)
{
if (processMimeData(ev->mimeData()))
ev->acceptProposedAction();
else
ev->ignore();
}
bool StoreDropTest::processMimeData(const QMimeData* mimeData)
{
const QString acceptMimeType("application/vnd.oasis.opendocument.");
foreach (const QString &format, mimeData->formats()) {
bool oasis = format.startsWith(acceptMimeType);
if (oasis || format == "application/x-kpresenter") {
const QByteArray data = mimeData->data(format);
showZipContents(data, format, oasis);
return true;
}
}
setText("No acceptable format found. All I got was:\n" + mimeData->formats().join("\n"));
return false;
}
void StoreDropTest::showZipContents(QByteArray data, const QString &mimeType, bool oasis)
{
if (data.isEmpty()) {
setText("No data!");
return;
}
QBuffer buffer(&data);
KoStore * store = KoStore::createStore(&buffer, KoStore::Read);
if (store->bad()) {
setText("Invalid ZIP!");
delete store;
return;
}
QString txt = QString("Valid ZIP file found for format ") + mimeType + "\n";
if (oasis) {
txt += loadTextFile(store, "content.xml");
txt += loadTextFile(store, "styles.xml");
txt += loadTextFile(store, "settings.xml");
txt += loadTextFile(store, "META-INF/manifest.xml");
} else {
txt += loadTextFile(store, "maindoc.xml");
}
setText(txt);
delete store;
}
QString StoreDropTest::loadTextFile(KoStore* store, const QString& fileName)
{
if (!store->open(fileName))
return QString("%1 not found\n").arg(fileName);
QByteArray data = store->device()->readAll();
store->close();
QString txt = QString("Found %1: \n").arg(fileName);
txt += QString::fromUtf8(data.data(), data.size());
txt += "\n";
return txt;
}
#include "storedroptest.moc"
diff --git a/src/libs/ui/Help.cpp b/src/libs/ui/Help.cpp
index 67655512..968d0f49 100644
--- a/src/libs/ui/Help.cpp
+++ b/src/libs/ui/Help.cpp
@@ -1,85 +1,86 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "Help.h"
#include <QEvent>
#include <QWhatsThisClickedEvent>
#include <QDesktopServices>
#include <QWidget>
#include <QDebug>
namespace KPlato
{
Help* Help::self = 0;
Help::Help(const QString &docpath)
{
if (!self) {
self = this;
m_docpath = docpath;
}
}
Help::~Help()
{
self = 0;
}
void Help::add(QWidget *widget, const QString &text)
{
widget->installEventFilter(new WhatsThisClickedEventHandler(widget));
widget->setWhatsThis(text);
}
QString Help::page(const QString &page)
{
if (!self) {
new Help("https://userbase.kde.org/Plan");
}
QString url = self->m_docpath;
if (!page.isEmpty()) {
url = QString("%1/%2").arg(url, page);
}
return url;
}
WhatsThisClickedEventHandler::WhatsThisClickedEventHandler(QObject *parent)
: QObject(parent)
{
}
bool WhatsThisClickedEventHandler::eventFilter(QObject *object, QEvent *event)
{
Q_UNUSED(object);
if (event->type() == QEvent::WhatsThisClicked) {
QWhatsThisClickedEvent *e = static_cast<QWhatsThisClickedEvent*>(event);
QUrl url(e->href());
if (url.isValid()) {
QDesktopServices::openUrl(url);
}
return true;
}
return false;
}
} // namespace KPlato
diff --git a/src/libs/ui/ResourceAllocationView.cpp b/src/libs/ui/ResourceAllocationView.cpp
index 3d9bf5f4..81074f0a 100644
--- a/src/libs/ui/ResourceAllocationView.cpp
+++ b/src/libs/ui/ResourceAllocationView.cpp
@@ -1,164 +1,165 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "ResourceAllocationView.h"
#include "kptnode.h"
#include "kptresource.h"
#include "kptnodeitemmodel.h"
#include "kptresourcemodel.h"
#include "kptcommand.h"
#include <KoDocument.h>
#include <KLocalizedString>
#include <QAction>
#include <QMenu>
#include <QModelIndexList>
#include <QContextMenuEvent>
#include <QDebug>
namespace KPlato
{
ResourceAllocationView::ResourceAllocationView(KoDocument *doc, QWidget *parent)
: QTreeView(parent)
, m_doc(doc)
{
m_allocateAction = new QAction(i18n("Allocate"), this);
connect(m_allocateAction, &QAction::triggered, this, &ResourceAllocationView::slotAllocate);
}
QList<Resource*> ResourceAllocationView::selectedResources() const
{
QList<Resource*> resources;
ResourceItemModel *m = qobject_cast<ResourceItemModel*>(model());
if (m) {
foreach(const QModelIndex &idx, selectionModel()->selectedRows()) {
Resource *r = m->resource(idx);
if (r) {
resources << r;
}
}
}
return resources;
}
void ResourceAllocationView::setSelectedTasks(const QItemSelection &selected, const QItemSelection &deselected)
{
for (QModelIndex &idx : deselected.indexes()) {
if (m_tasks.contains(idx)) {
m_tasks.removeAt(m_tasks.indexOf(idx));
}
}
QModelIndexList tasks = selected.indexes();
if (tasks.isEmpty()) {
return;
}
const NodeItemModel *m = qobject_cast<const NodeItemModel*>(tasks.first().model());
if (!m) {
return;
}
for (const QModelIndex &idx : tasks) {
if (idx.column() != NodeModel::NodeAllocation) {
continue;
}
Node *n = m->node(idx);
if (n->type() != Node::Type_Task) {
continue;
}
m_tasks << QPersistentModelIndex(idx);
}
}
void ResourceAllocationView::contextMenuEvent(QContextMenuEvent *event)
{
if (m_tasks.isEmpty()) {
return;
}
if (selectedResources().isEmpty()) {
return;
}
QMenu menu;
menu.move(event->globalPos());
menu.addAction(m_allocateAction);
menu.exec();
return;
}
void ResourceAllocationView::slotAllocate()
{
if (!m_doc) {
warnPlan<<"ResourceAllocationView has no document, commands cannot be executed";
return;
}
QList<QPersistentModelIndex> lst;
for (QPersistentModelIndex &idx : m_tasks) {
if (idx.isValid()) {
lst << idx;
}
}
if (lst.isEmpty()) {
return;
}
QList<Resource*> resources = selectedResources();
if (resources.isEmpty()) {
return;
}
const NodeItemModel *m = qobject_cast<const NodeItemModel*>(lst.first().model());
MacroCommand *cmd = new MacroCommand();
for (QPersistentModelIndex &idx : lst) {
Node *n = m->node(idx);
if (!n || n->type() != Node::Type_Task) {
continue;
}
Task *t = static_cast<Task*>(n);
// remove any requests before adding new ones
foreach(ResourceGroupRequest *r, t->requests().requests()) {
RemoveResourceGroupRequestCmd *c = new RemoveResourceGroupRequestCmd(r);
c->execute(); // need to remove everyting before we add anything
cmd->addCommand(c);
}
QHash<ResourceGroup*, ResourceGroupRequest*> groups;
for (Resource *r : resources) {
if (!groups.contains(r->parentGroup())) {
groups[r->parentGroup()] = new ResourceGroupRequest(r->parentGroup());
AddResourceGroupRequestCmd *c = new AddResourceGroupRequestCmd(*t, groups[r->parentGroup()]);
c->execute();
cmd->addCommand(c);
}
ResourceRequest *rr = new ResourceRequest(r);
rr->setUnits(100); // defaults to 100%
AddResourceRequestCmd *c = new AddResourceRequestCmd(groups[r->parentGroup()], rr);
c->execute();
cmd->addCommand(c);
}
}
if (cmd->isEmpty()) {
delete cmd;
} else {
MacroCommand *m = new MacroCommand(kundo2_i18n("Modify resource allocations"));
m_doc->addCommand(m);
m->addCommand(cmd);
}
}
} // namespace KPlato
diff --git a/src/libs/ui/TasksEditController.cpp b/src/libs/ui/TasksEditController.cpp
index b63a44c0..4c5676a3 100644
--- a/src/libs/ui/TasksEditController.cpp
+++ b/src/libs/ui/TasksEditController.cpp
@@ -1,78 +1,79 @@
/* This file is part of the KDE project
Copyright (C) 2002 Bo Thorsen bo@sonofthor.dk
Copyright (C) 2004 -2010 Dag Andersen <danders@get2net.dk>
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 "TasksEditController.h"
#include "TasksEditDialog.h"
#include "kpttaskcostpanel.h"
#include "kpttaskgeneralpanel.h"
#include "kptrequestresourcespanel.h"
#include "kptdocumentspanel.h"
#include "kpttaskdescriptiondialog.h"
#include "kptcommand.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptproject.h"
#include <KoVBox.h>
#include <KLocalizedString>
namespace KPlato
{
TasksEditController::TasksEditController(Project &project, const QList<Task*> &tasks, QObject *p)
: QObject(p)
, m_project(project)
, m_tasks(tasks)
, m_dlg(0)
{
}
TasksEditController::~TasksEditController()
{
delete m_dlg;
}
void TasksEditController::activate()
{
m_dlg = new TasksEditDialog(m_project, m_tasks);
connect(m_dlg, &QDialog::finished, this, &TasksEditController::finish);
m_dlg->show();
m_dlg->raise();
m_dlg->activateWindow();
}
void TasksEditController::finish(int result)
{
if (!m_dlg || sender() != m_dlg) {
return;
}
if (result == QDialog::Accepted) {
MacroCommand *m = m_dlg->buildCommand();
if (m) {
emit addCommand(m);
}
}
m_dlg->hide();
deleteLater();
}
} //KPlato namespace
diff --git a/src/libs/ui/TasksEditDialog.cpp b/src/libs/ui/TasksEditDialog.cpp
index fba53920..94cb2f37 100644
--- a/src/libs/ui/TasksEditDialog.cpp
+++ b/src/libs/ui/TasksEditDialog.cpp
@@ -1,100 +1,101 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "TasksEditDialog.h"
#include "kpttaskcostpanel.h"
#include "kpttaskgeneralpanel.h"
#include "kptrequestresourcespanel.h"
#include "kptdocumentspanel.h"
#include "kpttaskdescriptiondialog.h"
#include "kptcommand.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptproject.h"
#include <KoVBox.h>
#include <KLocalizedString>
namespace KPlato
{
TasksEditDialog::TasksEditDialog(Project &project, const QList<Task*> &tasks, QWidget *p)
: KPageDialog(p)
, m_project(project)
, m_tasks(tasks)
{
m_task = new Task(project.taskDefaults());
setWindowTitle(i18n("Tasks Settings"));
setFaceType(KPageDialog::Tabbed);
KoVBox *page;
// Create all the tabs.
page = new KoVBox();
addPage(page, i18n("&Resources"));
m_resourcesTab = new RequestResourcesPanel(page, project, *m_task);
resize(size().expandedTo(QSize(200, 75)));
}
void TasksEditDialog::setButtonOkEnabled(bool enabled)
{
buttonBox()->button(QDialogButtonBox::Ok)->setEnabled(enabled);
}
void TasksEditDialog::slotCurrentChanged(KPageWidgetItem *current, KPageWidgetItem *prev)
{
Q_UNUSED(current)
Q_UNUSED(prev)
}
void TasksEditDialog::slotTaskRemoved(Node *node)
{
if (node->type() == Node::Type_Task && m_tasks.contains(static_cast<Task*>(node))) {
reject();
}
}
MacroCommand *TasksEditDialog::buildCommand()
{
MacroCommand *m = new MacroCommand(kundo2_i18n("Modify tasks"));
bool modified = false;
for (Task *t : m_tasks) {
MacroCommand *c = m_resourcesTab->buildCommand(t);
if (c) {
m->addCommand(c);
modified = true;
}
}
if (!modified) {
delete m;
m = 0;
}
return m;
}
void TasksEditDialog::accept()
{
KPageDialog::accept();
}
} //KPlato namespace
diff --git a/src/libs/ui/TasksGeneralPanel.cpp b/src/libs/ui/TasksGeneralPanel.cpp
index 28a2fead..73439e83 100644
--- a/src/libs/ui/TasksGeneralPanel.cpp
+++ b/src/libs/ui/TasksGeneralPanel.cpp
@@ -1,572 +1,573 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "TasksGeneralPanel.h"
#include "kpttaskdialog.h"
#include "kpttask.h"
#include "kptcommand.h"
#include "kptduration.h"
#include "kptdurationspinbox.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptproject.h"
#include <KLocalizedString>
#ifdef PLAN_KDEPIMLIBS_FOUND
#include <akonadi/contact/emailaddressselectiondialog.h>
#include <akonadi/contact/emailaddressselectionwidget.h>
#include <akonadi/contact/emailaddressselection.h>
#endif
#include <QDateTime>
#include <kptdebug.h>
namespace KPlato
{
TasksGeneralPanel::TasksGeneralPanel(Project &project, QList<Task*> &tasks, QWidget *p, const char *n)
: TasksGeneralPanelImpl(p, n),
m_tasks(tasks),
m_project(project)
{
Q_ASSERT(!tasks.isEmpty());
useTime = true;
setStartValues( tasks.at(0) );
// if ( task.isBaselined( BASELINESCHEDULE ) ) {
// schedulingGroup->setEnabled( false );
// }
}
void TasksGeneralPanel::setStartValues( Task *task ) {
m_estimate = m_duration = task->estimate()->expectedValue();
leaderfield->setText(task->leader());
int cal = 0;
m_calendars.clear();
calendarCombo->addItem(i18n("None"));
m_calendars.insert(0, 0);
QList<Calendar*> list = m_project.allCalendars();
int i=1;
foreach (Calendar *c, list) {
calendarCombo->insertItem(i, c->name());
m_calendars.insert(i, c);
if (c == task->estimate()->calendar()) {
cal = i;
}
++i;
}
calendarCombo->setCurrentIndex(cal);
estimate->setMinimumUnit( (Duration::Unit)(m_project.config().minimumDurationUnit()) );
estimate->setMaximumUnit( (Duration::Unit)(m_project.config().maximumDurationUnit()) );
estimate->setUnit( task->estimate()->unit() );
setEstimateType(task->estimate()->type());
if (task->estimate()->type() == Estimate::Type_Effort && task->estimate()->expectedEstimate() == 0.0) {
setEstimateType(2 /*Milestone*/);
}
setSchedulingType(task->constraint());
if (task->constraintStartTime().isValid()) {
setStartDateTime(task->constraintStartTime());
} else {
QDate date = QDate::currentDate();
setStartDateTime(QDateTime(date, QTime(), Qt::LocalTime));
}
if (task->constraintEndTime().isValid()) {
setEndDateTime(task->constraintEndTime());
} else {
setEndDateTime(QDateTime(startDate().addDays(1), QTime(), Qt::LocalTime));
}
//debugPlan<<"Estimate:"<<task->estimate()->expected().toString();
setEstimate(task->estimate()->expectedEstimate());
setOptimistic(task->estimate()->optimisticRatio());
setPessimistic(task->estimate()->pessimisticRatio());
setRisktype(task->estimate()->risktype());
namefield->setFocus();
}
MacroCommand *TasksGeneralPanel::buildCommand() {
MacroCommand *cmd = new MacroCommand(kundo2_i18n("Modify Tasks"));
bool modified = false;
for (Task *t : m_tasks) {
if (!leaderfield->isHidden() && t->leader() != leaderfield->text()) {
cmd->addCommand(new NodeModifyLeaderCmd(*t, leaderfield->text()));
modified = true;
}
Node::ConstraintType c = (Node::ConstraintType)schedulingType();
if (c != t->constraint()) {
cmd->addCommand(new NodeModifyConstraintCmd(*t, c));
modified = true;
}
if (startDateTime() != t->constraintStartTime() &&
(c == Node::FixedInterval || c == Node::StartNotEarlier || c == Node::MustStartOn)) {
cmd->addCommand(new NodeModifyConstraintStartTimeCmd(*t, startDateTime()));
modified = true;
}
if (endDateTime() != t->constraintEndTime() &&
(c == Node::FinishNotLater || c == Node::FixedInterval || c == Node::MustFinishOn)) {
cmd->addCommand(new NodeModifyConstraintEndTimeCmd(*t, endDateTime()));
modified = true;
}
int et = estimationType();
if (et == 2 /*Milestome*/) {
et = 0; /*Effort*/
}
if (et != t->estimate()->type()) {
cmd->addCommand(new ModifyEstimateTypeCmd(*t, t->estimate()->type(), et));
modified = true;
}
bool unitchanged = estimate->unit() != t->estimate()->unit();
if ( unitchanged ) {
cmd->addCommand( new ModifyEstimateUnitCmd( *t, t->estimate()->unit(), estimate->unit() ) );
modified = true;
}
bool expchanged = estimationValue() != t->estimate()->expectedEstimate();
if ( expchanged ) {
cmd->addCommand(new ModifyEstimateCmd(*t, t->estimate()->expectedEstimate(), estimationValue()));
modified = true;
}
int x = optimistic();
if ( x != t->estimate()->optimisticRatio() || expchanged || unitchanged ) {
cmd->addCommand(new EstimateModifyOptimisticRatioCmd(*t, t->estimate()->optimisticRatio(), x));
modified = true;
}
x = pessimistic();
if ( x != t->estimate()->pessimisticRatio() || expchanged || unitchanged ) {
cmd->addCommand(new EstimateModifyPessimisticRatioCmd(*t, t->estimate()->pessimisticRatio(), x));
modified = true;
}
if (t->estimate()->risktype() != risktype()) {
cmd->addCommand(new EstimateModifyRiskCmd(*t, t->estimate()->risktype(), risktype()));
modified = true;
}
if (t->estimate()->calendar() != calendar()) {
cmd->addCommand(new ModifyEstimateCalendarCmd(*t, t->estimate()->calendar(), calendar()));
modified = true;
}
}
if (!modified) {
delete cmd;
return 0;
}
return cmd;
}
bool TasksGeneralPanel::ok() {
return true;
}
void TasksGeneralPanel::estimationTypeChanged(int type) {
if (type == 0 /*Effort*/) {
estimate->setEnabled(true);
calendarCombo->setEnabled(false);
} else if ( type == 1 /*Duration*/ ) {
calendarCombo->setEnabled(false);
if (schedulingType() == 6) { /*Fixed interval*/
estimate->setEnabled(false);
} else {
estimate->setEnabled(true);
calendarCombo->setEnabled(true);
}
} else if ( type == 2 /* Milestone */ ) {
estimate->setValue( 0 );
estimate->setEnabled(false);
calendarCombo->setEnabled(false);
}
TasksGeneralPanelImpl::estimationTypeChanged(type);
}
void TasksGeneralPanel::scheduleTypeChanged(int value)
{
if (value == 6 /*Fixed interval*/) {
if (estimateType->currentIndex() == 1/*duration*/){
// setEstimateScales(24);
estimate->setEnabled(false);
//TODO setEstimate( DateTime( endDateTime(), KDateTime::UTC) - DateTime( startDateTime(), KDateTime::UTC ) );
}
} else {
estimate->setEnabled(true);
}
TasksGeneralPanelImpl::scheduleTypeChanged(value);
}
//-----------------------------
TasksGeneralPanelImpl::TasksGeneralPanelImpl(QWidget *p, const char *n)
: QWidget(p) {
setObjectName(n);
setupUi(this);
#ifndef PLAN_KDEPIMLIBS_FOUND
chooseLeader->hide();
#endif
// FIXME
// [Bug 311940] New: Plan crashes when typing a text in the filter textbox before the textbook is fully loaded when selecting a contact from the adressbook
chooseLeader->hide();
connect(namefield, SIGNAL(textChanged(QString)), SLOT(checkAllFieldsFilled()));
connect(leaderfield, SIGNAL(textChanged(QString)), SLOT(checkAllFieldsFilled()));
connect(chooseLeader, SIGNAL(clicked()), SLOT(changeLeader()));
connect(estimateType, SIGNAL(activated(int)), SLOT(estimationTypeChanged(int)));
connect(scheduleType, SIGNAL(activated(int)), SLOT(scheduleTypeChanged(int)));
connect(scheduleStartDate, SIGNAL(dateChanged(QDate)), SLOT(startDateChanged()));
connect(scheduleStartTime, SIGNAL(timeChanged(QTime)), SLOT(startTimeChanged(QTime)));
connect(scheduleEndDate, SIGNAL(dateChanged(QDate)), SLOT(endDateChanged()));
connect(scheduleEndTime, SIGNAL(timeChanged(QTime)), SLOT(endTimeChanged(QTime)));
connect(estimate, SIGNAL(valueChanged(double)), SLOT(checkAllFieldsFilled()));
connect(optimisticValue, SIGNAL(valueChanged(int)), SLOT(checkAllFieldsFilled()));
connect(pessimisticValue, SIGNAL(valueChanged(int)), SLOT(checkAllFieldsFilled()));
connect(risk, SIGNAL(activated(int)), SLOT(checkAllFieldsFilled()));
connect(calendarCombo, SIGNAL(activated(int)), SLOT(calendarChanged(int)));
}
void TasksGeneralPanelImpl::setSchedulingType(int type)
{
enableDateTime(type);
scheduleType->setCurrentIndex(type);
emit schedulingTypeChanged(type);
}
int TasksGeneralPanelImpl::schedulingType() const
{
return scheduleType->currentIndex();
}
void TasksGeneralPanelImpl::changeLeader()
{
#ifdef PLAN_KDEPIMLIBS_FOUND
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog( this );
if ( dlg->exec() && dlg ) {
QStringList names;
const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
foreach ( const Akonadi::EmailAddressSelection &selection, selections ) {
QString s = selection.name();
if ( ! selection.email().isEmpty() ) {
if ( ! selection.name().isEmpty() ) {
s += " <";
}
s += selection.email();
if ( ! selection.name().isEmpty() ) {
s += '>';
}
if ( ! s.isEmpty() ) {
names << s;
}
}
}
if ( ! names.isEmpty() ) {
leaderfield->setText( names.join( ", " ) );
}
}
#endif
}
void TasksGeneralPanelImpl::setEstimationType( int type )
{
estimateType->setCurrentIndex(type);
}
int TasksGeneralPanelImpl::estimationType() const
{
return estimateType->currentIndex();
}
void TasksGeneralPanelImpl::setOptimistic( int value )
{
optimisticValue->setValue(value);
}
void TasksGeneralPanelImpl::setPessimistic( int value )
{
pessimisticValue->setValue(value);
}
int TasksGeneralPanelImpl::optimistic() const
{
return optimisticValue->value();
}
int TasksGeneralPanelImpl::pessimistic()
{
return pessimisticValue->value();
}
void TasksGeneralPanelImpl::enableDateTime( int scheduleType )
{
scheduleStartTime->setEnabled(false);
scheduleEndTime->setEnabled(false);
scheduleStartDate->setEnabled(false);
scheduleEndDate->setEnabled(false);
switch (scheduleType)
{
case 0: //ASAP
case 1: //ALAP
break;
case 2: //Must start on
case 4: // Start not earlier
if (useTime) {
scheduleStartTime->setEnabled(true);
scheduleEndTime->setEnabled(false);
}
scheduleStartDate->setEnabled(true);
scheduleEndDate->setEnabled(false);
break;
case 3: //Must finish on
case 5: // Finish not later
if (useTime) {
scheduleStartTime->setEnabled(false);
scheduleEndTime->setEnabled(true);
}
scheduleStartDate->setEnabled(false);
scheduleEndDate->setEnabled(true);
break;
case 6: //Fixed interval
if (useTime) {
scheduleStartTime->setEnabled(true);
scheduleEndTime->setEnabled(true);
}
scheduleStartDate->setEnabled(true);
scheduleEndDate->setEnabled(true);
break;
default:
break;
}
}
void TasksGeneralPanelImpl::estimationTypeChanged( int /*type*/ )
{
checkAllFieldsFilled();
}
void TasksGeneralPanelImpl::calendarChanged( int /*index*/ )
{
checkAllFieldsFilled();
}
void TasksGeneralPanelImpl::setEstimate( double duration)
{
estimate->setValue( duration );
}
void TasksGeneralPanelImpl::setEstimateType( int type)
{
estimateType->setCurrentIndex(type);
estimationTypeChanged( type );
}
void TasksGeneralPanelImpl::checkAllFieldsFilled()
{
emit changed();
emit obligatedFieldsFilled(true); // do not block save even if name is not filled
}
double TasksGeneralPanelImpl::estimationValue()
{
return estimate->value();
}
void TasksGeneralPanelImpl::startDateChanged()
{
if (!scheduleStartDate->isEnabled()) {
return;
}
QDate date = startDate();
if (startDateTime() > endDateTime())
{
scheduleEndTime->blockSignals(true);
scheduleEndDate->blockSignals(true);
setEndDate(date);
setEndTime(startTime());
scheduleEndTime->blockSignals(false);
scheduleEndDate->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TasksGeneralPanelImpl::startTimeChanged( const QTime &time )
{
if (!scheduleStartTime->isEnabled()) {
return;
}
if (startDateTime() > endDateTime())
{
scheduleEndTime->blockSignals(true);
setEndTime(time);
scheduleEndTime->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TasksGeneralPanelImpl::endDateChanged()
{
if (!scheduleEndDate->isEnabled()) {
return;
}
QDate date = endDate();
if (endDateTime() < startDateTime())
{
scheduleStartTime->blockSignals(true);
scheduleStartDate->blockSignals(true);
setStartDate(date);
setStartTime(endTime());
scheduleStartTime->blockSignals(false);
scheduleStartDate->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TasksGeneralPanelImpl::endTimeChanged( const QTime &time )
{
if (!scheduleEndTime->isEnabled()) {
return;
}
if (endDateTime() < startDateTime())
{
scheduleStartTime->blockSignals(true);
setStartTime(time);
scheduleStartTime->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TasksGeneralPanelImpl::scheduleTypeChanged( int value )
{
estimationTypeChanged(estimateType->currentIndex());
enableDateTime(value);
checkAllFieldsFilled();
}
QDateTime TasksGeneralPanelImpl::startDateTime()
{
return QDateTime(startDate(), startTime(), Qt::LocalTime);
}
QDateTime TasksGeneralPanelImpl::endDateTime()
{
return QDateTime(endDate(), endTime(), Qt::LocalTime);
}
void TasksGeneralPanelImpl::setStartTime( const QTime &time )
{
scheduleStartTime->setTime( QTime( time.hour(), time.minute(), 0 ) );
}
void TasksGeneralPanelImpl::setEndTime( const QTime &time )
{
scheduleEndTime->setTime( QTime( time.hour(), time.minute(), 0 ) );
}
QTime TasksGeneralPanelImpl::startTime() const
{
QTime t = scheduleStartTime->time();
t.setHMS( t.hour(), t.minute(), 0 );
return t;
}
QTime TasksGeneralPanelImpl::endTime()
{
QTime t = scheduleEndTime->time();
t.setHMS( t.hour(), t.minute(), 0 );
return t;
}
QDate TasksGeneralPanelImpl::startDate()
{
return scheduleStartDate->date();
}
QDate TasksGeneralPanelImpl::endDate()
{
return scheduleEndDate->date();
}
void TasksGeneralPanelImpl::setStartDateTime( const QDateTime &dt )
{
setStartDate(dt.date());
setStartTime(dt.time());
}
void TasksGeneralPanelImpl::setEndDateTime( const QDateTime &dt )
{
setEndDate(dt.date());
setEndTime(dt.time());
}
void TasksGeneralPanelImpl::setStartDate( const QDate &date )
{
scheduleStartDate->setDate(date);
}
void TasksGeneralPanelImpl::setEndDate( const QDate &date )
{
scheduleEndDate->setDate(date);
}
void TasksGeneralPanelImpl::setRisktype( int r )
{
risk->setCurrentIndex(r);
}
int TasksGeneralPanelImpl::risktype() const
{
return risk->currentIndex();
}
Calendar *TasksGeneralPanelImpl::calendar() const
{
return m_calendars.value( calendarCombo->currentIndex() );
}
} //KPlato namespace
diff --git a/src/libs/ui/kmessagebox_copy.cpp b/src/libs/ui/kmessagebox_copy.cpp
index 29e2199f..21eac3fc 100644
--- a/src/libs/ui/kmessagebox_copy.cpp
+++ b/src/libs/ui/kmessagebox_copy.cpp
@@ -1,57 +1,58 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Waldo Bastian (bastian@kde.org)
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; version 2
of the License.
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.
*/
// NOTE: This is copied from kmessagebox.cpp
+// clazy:excludeall=qstring-arg
#include <QMessageBox>
#include <KIconLoader>
#include <KoIcon.h>
static QIcon themedMessageBoxIcon(QMessageBox::Icon icon)
{
const char *icon_name = 0;
switch (icon) {
case QMessageBox::NoIcon:
return QIcon();
break;
case QMessageBox::Information:
icon_name = koIconNameCStr("dialog-information");
break;
case QMessageBox::Warning:
icon_name = koIconNameCStr("dialog-warning");
break;
case QMessageBox::Critical:
icon_name = koIconNameCStr("dialog-error");
break;
default:
break;
}
QIcon ret = KIconLoader::global()->loadIcon(QLatin1String(icon_name), KIconLoader::NoGroup, KIconLoader::SizeHuge, KIconLoader::DefaultState, QStringList(), 0, true);
if (ret.isNull()) {
return QMessageBox::standardIcon(icon);
} else {
return ret;
}
}
diff --git a/src/libs/ui/kptaccountseditor.cpp b/src/libs/ui/kptaccountseditor.cpp
index 0112bf27..f55f2c62 100644
--- a/src/libs/ui/kptaccountseditor.cpp
+++ b/src/libs/ui/kptaccountseditor.cpp
@@ -1,382 +1,383 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net>
Copyright (C) 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptaccountseditor.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptaccount.h"
#include "kptdatetime.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <KoPageLayoutWidget.h>
#include <KoIcon.h>
#include <QList>
#include <QVBoxLayout>
#include <QContextMenuEvent>
#include <QMenu>
#include <QAction>
#include <QHeaderView>
#include <KLocalizedString>
#include <kactioncollection.h>
namespace KPlato
{
AccountseditorConfigDialog::AccountseditorConfigDialog( ViewBase *view, AccountTreeView *treeview, QWidget *p, bool selectPrint)
: KPageDialog(p),
m_view( view ),
m_treeview( treeview )
{
setWindowTitle( i18n("Settings") );
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_headerfooter = ViewBase::createHeaderFooterWidget( view );
m_headerfooter->setOptions( view->printingOptions() );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
KPageWidgetItem *page = addPage( tab, i18n( "Printing" ) );
page->setHeader( i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, &QDialog::accepted, this, &AccountseditorConfigDialog::slotOk);
}
void AccountseditorConfigDialog::slotOk()
{
debugPlan;
m_view->setPageLayout( m_pagelayout->pageLayout() );
m_view->setPrintingOptions( m_headerfooter->options() );
}
//--------------------
AccountTreeView::AccountTreeView( QWidget *parent )
: TreeViewBase( parent )
{
header()->setContextMenuPolicy( Qt::CustomContextMenu );
setModel( new AccountItemModel( this ) );
setSelectionModel( new QItemSelectionModel( model() ) );
setSelectionMode( QAbstractItemView::SingleSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
setAcceptDrops( false );
setDropIndicatorShown( false );
connect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotHeaderContextMenuRequested(QPoint)) );
}
void AccountTreeView::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan<<header()->logicalIndexAt(pos)<<" at"<<pos;
}
void AccountTreeView::contextMenuEvent ( QContextMenuEvent *event )
{
debugPlan;
emit contextMenuRequested( indexAt(event->pos()), event->globalPos() );
}
void AccountTreeView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel )
{
debugPlan<<sel.indexes().count();
foreach( const QModelIndex &i, selectionModel()->selectedIndexes() ) {
debugPlan<<i.row()<<","<<i.column();
}
QTreeView::selectionChanged( sel, desel );
emit selectionChanged( selectionModel()->selectedIndexes() );
}
void AccountTreeView::currentChanged( const QModelIndex & current, const QModelIndex & previous )
{
debugPlan;
QTreeView::currentChanged( current, previous );
emit currentChanged( current );
// possible bug in qt: in QAbstractItemView::SingleSelection you can select multiple items/rows
selectionModel()->select( current, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
}
Account *AccountTreeView::currentAccount() const
{
return model()->account( currentIndex() );
}
Account *AccountTreeView::selectedAccount() const
{
QModelIndexList lst = selectionModel()->selectedRows();
if ( lst.count() == 1 ) {
return model()->account( lst.first() );
}
return 0;
}
QList<Account*> AccountTreeView::selectedAccounts() const
{
QList<Account*> lst;
foreach ( const QModelIndex &i, selectionModel()->selectedRows() ) {
Account *a = model()->account( i );
if ( a ) {
lst << a;
}
}
return lst;
}
//-----------------------------------
AccountsEditor::AccountsEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
setupGui();
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new AccountTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &TreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &TreeViewBase::slotCollapse);
l->addWidget( m_view );
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentChanged(QModelIndex)) );
connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), this, SLOT(slotSelectionChanged(QModelIndexList)) );
connect( m_view, SIGNAL(contextMenuRequested(QModelIndex,QPoint)), this, SLOT(slotContextMenuRequested(QModelIndex,QPoint)) );
connect( m_view, SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Cost Breakdown Structure Editor</title>"
"<para>"
"The Cost Breakdown Structure (CBS) consists of accounts"
" organized into a tree structure."
" Accounts can be tied to tasks or resources."
" Usually there will be two top accounts, one for aggregating costs from tasks"
" and one for aggregating costs from resources."
"</para><para>"
"This view supports printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Cost_Breakdown_Structure_Editor")));
}
void AccountsEditor::updateReadWrite( bool readwrite )
{
m_view->setReadWrite( readwrite );
}
void AccountsEditor::draw( Project &project )
{
m_view->setProject( &project );
}
void AccountsEditor::draw()
{
}
void AccountsEditor::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate ) {
if ( !m_view->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
slotSelectionChanged( m_view->selectionModel()->selectedRows() );
}
}
void AccountsEditor::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
m_view->setContextMenuIndex(index);
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
}
void AccountsEditor::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
Account *AccountsEditor::currentAccount() const
{
return m_view->currentAccount();
}
void AccountsEditor::slotCurrentChanged( const QModelIndex &curr )
{
debugPlan<<curr.row()<<","<<curr.column();
//slotEnableActions( curr.isValid() );
}
void AccountsEditor::slotSelectionChanged( const QModelIndexList& list)
{
debugPlan<<list.count();
updateActionsEnabled( true );
}
void AccountsEditor::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void AccountsEditor::updateActionsEnabled( bool on )
{
QList<Account*> lst = m_view->selectedAccounts();
bool one = lst.count() == 1;
bool more = lst.count() > 1;
actionAddAccount->setEnabled( on && !more );
actionAddSubAccount->setEnabled( on && one );
bool baselined = project() ? project()->isBaselined() : false;
actionDeleteSelection->setEnabled( on && one && ! baselined );
}
void AccountsEditor::setupGui()
{
QString name = "accountseditor_edit_list";
actionAddAccount = new QAction(koIcon("document-new"), i18n("Add Account"), this);
actionCollection()->addAction("add_account", actionAddAccount );
actionCollection()->setDefaultShortcut(actionAddAccount, Qt::CTRL + Qt::Key_I);
connect( actionAddAccount, &QAction::triggered, this, &AccountsEditor::slotAddAccount );
addAction( name, actionAddAccount );
actionAddSubAccount = new QAction(koIcon("document-new"), i18n("Add Subaccount"), this);
actionCollection()->addAction("add_subaccount", actionAddSubAccount );
actionCollection()->setDefaultShortcut(actionAddSubAccount, Qt::SHIFT + Qt::CTRL + Qt::Key_I);
connect( actionAddSubAccount, &QAction::triggered, this, &AccountsEditor::slotAddSubAccount );
addAction( name, actionAddSubAccount );
actionDeleteSelection = new QAction(koIcon("edit-delete"), i18nc("@action", "Delete"), this);
actionCollection()->addAction("delete_selection", actionDeleteSelection );
actionCollection()->setDefaultShortcut(actionDeleteSelection, Qt::Key_Delete);
connect( actionDeleteSelection, &QAction::triggered, this, &AccountsEditor::slotDeleteSelection );
addAction( name, actionDeleteSelection );
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionPrint | ViewBase::OptionPrintPreview | ViewBase::OptionPrintPdf | ViewBase::OptionPrintConfig);
}
void AccountsEditor::slotOptions()
{
debugPlan;
AccountseditorConfigDialog *dlg = new AccountseditorConfigDialog( this, m_view, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void AccountsEditor::slotAddAccount()
{
debugPlan;
int row = -1;
Account *parent = m_view->selectedAccount(); // sibling
if ( parent ) {
row = parent->parent() ? parent->parent()->indexOf( parent ) : project()->accounts().indexOf( parent );
if ( row >= 0 ) {
++row;
}
parent = parent->parent();
}
insertAccount( new Account(), parent, row );
}
void AccountsEditor::slotAddSubAccount()
{
debugPlan;
insertAccount( new Account(), m_view->selectedAccount(), -1 );
}
void AccountsEditor::insertAccount( Account *account, Account *parent, int row )
{
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
QModelIndex i = m_view->model()->insertAccount( account, parent, row );
if ( i.isValid() ) {
QModelIndex p = m_view->model()->parent( i );
if (parent) debugPlan<<" parent="<<parent->name()<<":"<<p.row()<<","<<p.column();
debugPlan<<i.row()<<","<<i.column();
if ( p.isValid() ) {
m_view->setExpanded( p, true );
}
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}
}
void AccountsEditor::slotDeleteSelection()
{
debugPlan;
m_view->model()->removeAccounts( m_view->selectedAccounts() );
}
void AccountsEditor::slotAccountsOk()
{
debugPlan<<"Account Editor : slotAccountsOk";
//QModelList
//QModelIndex i = m_view->model()->insertGroup( g );
}
KoPrintJob *AccountsEditor::createPrintJob()
{
return m_view->createPrintJob( this );
}
bool AccountsEditor::loadContext(const KoXmlElement &context)
{
m_view->loadContext(model()->columnMap(), context);
return true;
}
void AccountsEditor::saveContext(QDomElement &context) const
{
m_view->saveContext(model()->columnMap(), context);
}
} // namespace KPlato
diff --git a/src/libs/ui/kptaccountsview.cpp b/src/libs/ui/kptaccountsview.cpp
index 55004da1..cb958cde 100644
--- a/src/libs/ui/kptaccountsview.cpp
+++ b/src/libs/ui/kptaccountsview.cpp
@@ -1,337 +1,338 @@
/* This file is part of the KDE project
Copyright (C) 2005 - 2006, 2012 Dag Andersen danders@get2net>
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 "kptaccountsview.h"
#include "kptaccountsviewconfigdialog.h"
#include "kptdatetime.h"
#include "kptproject.h"
#include "kpteffortcostmap.h"
#include "kptaccountsmodel.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <KoXmlReader.h>
#include <QVBoxLayout>
#include <QPrinter>
#include <QPrintDialog>
#include <QHeaderView>
#include <QMenu>
namespace KPlato
{
AccountsTreeView::AccountsTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
debugPlan<<"---------------"<<this<<"------------------";
setSelectionMode( QAbstractItemView::ExtendedSelection );
CostBreakdownItemModel *m = new CostBreakdownItemModel( this );
setModel( m );
QHeaderView *v = m_leftview->header();
v->setStretchLastSection( false );
v->setSectionResizeMode( CostBreakdownItemModel::Description, QHeaderView::Stretch );
v->setSectionResizeMode ( CostBreakdownItemModel::Total, QHeaderView::ResizeToContents );
v = m_rightview->header();
v->setSectionResizeMode ( QHeaderView::ResizeToContents );
v->setStretchLastSection( false );
m_leftHidden = QList<int>() << CostBreakdownItemModel::Planned << CostBreakdownItemModel::Actual << -1;
slotModelReset();
connect( m, &QAbstractItemModel::modelReset, this, &AccountsTreeView::slotModelReset );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Cost Breakdown View</title>"
"<para>"
"Displays aggregated total cost as well as cost distribution over time."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Cost_Breakdown_View")));
}
void AccountsTreeView::slotModelReset()
{
hideColumns( m_leftHidden );
QHeaderView *v = m_leftview->header();
debugPlan<<v->sectionSize(2)<<v->sectionSizeHint(2)<<v->defaultSectionSize()<<v->minimumSectionSize();
CostBreakdownItemModel *m = static_cast<CostBreakdownItemModel*>(model());
QList<int> cols;
for (int c = 0; c < m->propertyCount(); ++c) {
cols << c;
}
hideColumns( m_rightview, cols);
}
CostBreakdownItemModel *AccountsTreeView::model() const
{
return static_cast<CostBreakdownItemModel*>( DoubleTreeViewBase::model() );
}
bool AccountsTreeView::cumulative() const
{
return model()->cumulative();
}
void AccountsTreeView::setCumulative( bool on )
{
model()->setCumulative( on );
}
int AccountsTreeView::periodType() const
{
return model()->periodType();
}
void AccountsTreeView::setPeriodType( int period )
{
model()->setPeriodType( period );
}
int AccountsTreeView::startMode() const
{
return model()->startMode();
}
void AccountsTreeView::setStartMode( int mode )
{
model()->setStartMode( mode );
}
int AccountsTreeView::endMode() const
{
return model()->endMode();
}
void AccountsTreeView::setEndMode( int mode )
{
model()->setEndMode( mode );
}
QDate AccountsTreeView::startDate() const
{
return model()->startDate();
}
void AccountsTreeView::setStartDate( const QDate &date )
{
model()->setStartDate( date );
}
QDate AccountsTreeView::endDate() const
{
return model()->endDate();
}
void AccountsTreeView::setEndDate( const QDate &date )
{
model()->setEndDate( date );
}
int AccountsTreeView::showMode() const
{
return model()->showMode();
}
void AccountsTreeView::setShowMode( int show )
{
model()->setShowMode( show );
}
//------------------------
AccountsView::AccountsView(KoPart *part, Project *project, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent),
m_project(project),
m_manager( 0 )
{
init();
setupGui();
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &AccountsView::slotContextMenuRequested );
connect( m_view, SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) );
}
void AccountsView::setZoom( double zoom )
{
Q_UNUSED( zoom );
}
void AccountsView::init()
{
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin(0);
m_view = new AccountsTreeView( this );
l->addWidget( m_view );
setProject( m_project );
}
void AccountsView::setupGui()
{
createOptionActions(ViewBase::OptionAll);
}
void AccountsView::slotContextMenuRequested( const QModelIndex &index, const QPoint &pos )
{
debugPlan;
m_view->setContextMenuIndex(index);
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
}
void AccountsView::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
void AccountsView::slotOptions()
{
debugPlan;
AccountsviewConfigDialog *dlg = new AccountsviewConfigDialog( this, m_view, this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void AccountsView::setProject( Project *project )
{
model()->setProject( project );
m_project = project;
}
void AccountsView::setScheduleManager( ScheduleManager *sm )
{
if (!sm && m_manager) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
bool tryexpand = sm && !m_manager;
bool expand = sm && m_manager && sm != m_manager;
QDomDocument doc;
if (expand) {
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
m_manager = sm;
model()->setScheduleManager( sm );
if (expand) {
m_view->masterView()->doExpand(doc);
} else if (tryexpand) {
m_view->masterView()->doExpand(m_domdoc);
}
}
CostBreakdownItemModel *AccountsView::model() const
{
return static_cast<CostBreakdownItemModel*>( m_view->model() );
}
#if 0
void AccountsView::print( QPrinter &printer, QPrintDialog &printDialog )
{
//debugPlan;
uint top, left, bottom, right;
printer.margins( &top, &left, &bottom, &right );
//debugPlan<<m.width()<<"x"<<m.height()<<" :"<<top<<","<<left<<","<<bottom<<","<<right<<" :"<<size();
QPainter p;
p.begin( &printer );
p.setViewport( left, top, printer.width() - left - right, printer.height() - top - bottom );
p.setClipRect( left, top, printer.width() - left - right, printer.height() - top - bottom );
QRect preg = p.clipRegion().boundingRect();
//debugPlan<<"p="<<preg;
//p.drawRect(preg.x(), preg.y(), preg.width()-1, preg.height()-1);
double scale = qMin( ( double ) preg.width() / ( double ) size().width(), ( double ) preg.height() / ( double ) ( size().height() ) );
//debugPlan<<"scale="<<scale;
if ( scale < 1.0 ) {
p.scale( scale, scale );
}
QPixmap labelPixmap = QPixmap::grabWidget( m_label );
p.drawPixmap( m_label->pos(), labelPixmap );
p.translate( 0, m_label->size().height() );
m_dlv->paintContents( &p );
p.end();
}
#endif
bool AccountsView::loadContext( const KoXmlElement &context )
{
//debugPlan;
ViewBase::loadContext( context );
m_view->setShowMode( context.attribute( "show-mode" ).toInt() );
m_view->setCumulative( (bool)( context.attribute( "cumulative" ).toInt() ) );
m_view->setPeriodType( context.attribute( "period-type", "0" ).toInt() );
m_view->setStartDate( QDate::fromString( context.attribute( "start-date", "" ), Qt::ISODate ) );
m_view->setStartMode( context.attribute( "start-mode", "0" ).toInt() );
m_view->setEndDate( QDate::fromString( context.attribute( "end-date", "" ), Qt::ISODate ) );
m_view->setEndMode( context.attribute( "end-mode", "0" ).toInt() );
//debugPlan<<m_view->startMode()<<m_view->startDate()<<m_view->endMode()<<m_view->endDate();
m_view->masterView()->setObjectName("AccountsView");
m_view->loadContext(model()->columnMap(), context);
return true;
}
void AccountsView::saveContext( QDomElement &context ) const
{
//debugPlan;
ViewBase::saveContext( context );
context.setAttribute( "show-mode", QString::number(m_view->showMode()) );
context.setAttribute( "cumulative", QString::number(m_view->cumulative()) );
context.setAttribute( "period-type", QString::number(m_view->periodType()) );
context.setAttribute( "start-mode", QString::number(m_view->startMode()) );
context.setAttribute( "start-date", m_view->startDate().toString( Qt::ISODate ) );
context.setAttribute( "end-mode", QString::number(m_view->endMode()) );
context.setAttribute( "end-date", m_view->endDate().toString( Qt::ISODate ) );
m_view->saveContext(model()->columnMap(), context);
}
KoPrintJob *AccountsView::createPrintJob()
{
return m_view->createPrintJob( this );
}
} //KPlato namespace
diff --git a/src/libs/ui/kptaccountsviewconfigdialog.cpp b/src/libs/ui/kptaccountsviewconfigdialog.cpp
index 015b73b5..df75d6d8 100644
--- a/src/libs/ui/kptaccountsviewconfigdialog.cpp
+++ b/src/libs/ui/kptaccountsviewconfigdialog.cpp
@@ -1,157 +1,158 @@
/* This file is part of the KDE project
Copyright (C) 2005, 2012 Dag Andersen <danders@get2net.dk>
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 "kptaccountsviewconfigdialog.h"
#include "kptaccountsview.h"
#include "kptaccountsmodel.h"
#include "kptviewbase.h"
#include "kptdebug.h"
#include "KoPageLayoutWidget.h"
#include <KLocalizedString>
#include <QPushButton>
#include <QCheckBox>
#include <QString>
namespace KPlato
{
AccountsviewConfigDialog::AccountsviewConfigDialog( ViewBase *view, AccountsTreeView *treeview, QWidget *p, bool selectPrint)
: KPageDialog(p),
m_view( view ),
m_treeview( treeview )
{
setWindowTitle( i18n("Settings") );
m_panel = new AccountsviewConfigPanel( this );
switch ( treeview->startMode() ) {
case CostBreakdownItemModel::StartMode_Project:
m_panel->ui_projectstartBtn->setChecked( true );
m_panel->ui_startdate->setEnabled( false );
break;
case CostBreakdownItemModel::StartMode_Date:
m_panel->ui_startdateBtn->setChecked( true );
break;
}
switch ( treeview->endMode() ) {
case CostBreakdownItemModel::EndMode_Project:
m_panel->ui_projectendBtn->setChecked( true );
m_panel->ui_enddate->setEnabled( false );
break;
case CostBreakdownItemModel::EndMode_Date:
m_panel->ui_enddateBtn->setChecked( true );
break;
case CostBreakdownItemModel::EndMode_CurrentDate:
m_panel->ui_currentdateBtn->setChecked( true );
m_panel->ui_enddate->setEnabled( false );
break;
}
m_panel->ui_startdate->setDate( treeview->startDate() );
m_panel->ui_enddate->setDate( treeview->endDate() );
m_panel->ui_periodBox->setCurrentIndex( treeview->periodType() );
m_panel->ui_cumulative->setChecked( treeview->cumulative() );
m_panel->ui_showBox->setCurrentIndex( treeview->showMode() );
KPageWidgetItem *page = addPage( m_panel, i18n( "General" ) );
page->setHeader( i18n( "View Settings" ) );
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_headerfooter = ViewBase::createHeaderFooterWidget( view );
m_headerfooter->setOptions( view->printingOptions() );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
page = addPage( tab, i18n( "Printing" ) );
page->setHeader( i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, &QDialog::accepted, this, &AccountsviewConfigDialog::slotOk);
connect(m_panel, &AccountsviewConfigPanel::changed, this, &AccountsviewConfigDialog::enableOkButton);
}
void AccountsviewConfigDialog::enableOkButton(bool enabled)
{
button( QDialogButtonBox::Ok )->setEnabled( enabled );
}
void AccountsviewConfigDialog::slotOk()
{
debugPlan;
m_treeview->setPeriodType( m_panel->ui_periodBox->currentIndex() );
m_treeview->setCumulative( m_panel->ui_cumulative->isChecked() );
m_treeview->setShowMode( m_panel->ui_showBox->currentIndex() );
if ( m_panel->ui_startdateBtn->isChecked() ) {
m_treeview->setStartDate( m_panel->ui_startdate->date() );
m_treeview->setStartMode( CostBreakdownItemModel::StartMode_Date );
} else {
m_treeview->setStartMode( CostBreakdownItemModel::StartMode_Project );
}
if ( m_panel->ui_enddateBtn->isChecked() ) {
m_treeview->setEndDate( m_panel->ui_enddate->date() );
m_treeview->setEndMode( CostBreakdownItemModel::EndMode_Date );
} else if ( m_panel->ui_currentdateBtn->isChecked() ) {
m_treeview->setEndMode( CostBreakdownItemModel::EndMode_CurrentDate );
} else {
m_treeview->setEndMode( CostBreakdownItemModel::EndMode_Project );
}
m_view->setPageLayout( m_pagelayout->pageLayout() );
m_view->setPrintingOptions( m_headerfooter->options() );
}
//----------------------------
AccountsviewConfigPanel::AccountsviewConfigPanel(QWidget *parent)
: AccountsviewConfigurePanelBase(parent)
{
connect(ui_startdate, &QDateTimeEdit::dateChanged, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_enddate, &QDateTimeEdit::dateChanged, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_periodBox, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(ui_cumulative, &QAbstractButton::clicked, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_projectstartBtn, &QAbstractButton::clicked, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_startdateBtn, &QAbstractButton::clicked, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_projectendBtn, &QAbstractButton::clicked, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_currentdateBtn, &QAbstractButton::clicked, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_enddateBtn, &QAbstractButton::clicked, this, &AccountsviewConfigPanel::slotChanged);
connect(ui_showBox, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(ui_startdateBtn, &QAbstractButton::toggled, ui_startdate, &QWidget::setEnabled);
connect(ui_enddateBtn, &QAbstractButton::toggled, ui_enddate, &QWidget::setEnabled);
}
void AccountsviewConfigPanel::slotChanged() {
emit changed(true);
}
} //KPlato namespace
diff --git a/src/libs/ui/kptcalendareditor.cpp b/src/libs/ui/kptcalendareditor.cpp
index 1fab98cf..63616371 100644
--- a/src/libs/ui/kptcalendareditor.cpp
+++ b/src/libs/ui/kptcalendareditor.cpp
@@ -1,872 +1,873 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2012 Dag Andersen <danders@get2net>
* Copyright (C) 2017 Dag Andersen <danders@get2net>
*
* 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 "kptcalendareditor.h"
#include "kcalendar/kdatepicker.h"
#include "kcalendar/kdatetable.h"
//#include "kptcalendarpanel.h"
#include "kptcommand.h"
#include "kptcalendarmodel.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptdatetime.h"
#include "kptintervaledit.h"
#include "kptitemviewsettup.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <QDragMoveEvent>
#include <QList>
#include <QSplitter>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QAction>
#include <QMenu>
#include <KLocalizedString>
#include <kactioncollection.h>
#include <KoDocument.h>
namespace KPlato
{
//--------------------
CalendarTreeView::CalendarTreeView( QWidget *parent )
: TreeViewBase( parent )
{
header()->setContextMenuPolicy( Qt::CustomContextMenu );
setModel( new CalendarItemModel() );
setSelectionBehavior( QAbstractItemView::SelectRows );
setSelectionMode( QAbstractItemView::SingleSelection );
setSelectionModel( new QItemSelectionModel( model() ) );
setItemDelegateForColumn( CalendarItemModel::Scope, new EnumDelegate( this ) );
setItemDelegateForColumn( CalendarItemModel::TimeZone, new EnumDelegate( this ) ); // timezone
#ifdef HAVE_KHOLIDAYS
setItemDelegateForColumn( CalendarItemModel::HolidayRegion, new EnumDelegate( this ) );
#endif
connect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotHeaderContextMenuRequested(QPoint)) );
}
void CalendarTreeView::slotHeaderContextMenuRequested( const QPoint &pos )
{
emit contextMenuRequested(QModelIndex(), mapToGlobal(pos));
}
void CalendarTreeView::contextMenuEvent ( QContextMenuEvent *event )
{
emit contextMenuRequested( indexAt(event->pos()), event->globalPos() );
}
void CalendarTreeView::focusInEvent ( QFocusEvent *event )
{
//debugPlan;
TreeViewBase::focusInEvent( event );
emit focusChanged();
}
void CalendarTreeView::focusOutEvent ( QFocusEvent * event )
{
//debugPlan;
TreeViewBase::focusInEvent( event );
emit focusChanged();
}
void CalendarTreeView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel )
{
//debugPlan<<sel.indexes().count();
//foreach( const QModelIndex &i, selectionModel()->selectedIndexes() ) { debugPlan<<i.row()<<","<<i.column(); }
TreeViewBase::selectionChanged( sel, desel );
emit sigSelectionChanged( selectionModel()->selectedIndexes() );
}
void CalendarTreeView::currentChanged( const QModelIndex & current, const QModelIndex & previous )
{
//debugPlan;
TreeViewBase::currentChanged( current, previous );
// possible bug in qt: in QAbstractItemView::SingleSelection you can select multiple items/rows
selectionModel()->select( current, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
emit currentChanged( current );
}
Calendar *CalendarTreeView::currentCalendar() const
{
return model()->calendar( currentIndex() );
}
Calendar *CalendarTreeView::selectedCalendar() const
{
QModelIndexList lst = selectionModel()->selectedRows();
if ( lst.count() == 1 ) {
return model()->calendar( lst.first() );
}
return 0;
}
QList<Calendar*> CalendarTreeView::selectedCalendars() const
{
QList<Calendar *> lst;
foreach ( const QModelIndex &i, selectionModel()->selectedRows() ) {
Calendar *a = model()->calendar( i );
if ( a ) {
lst << a;
}
}
return lst;
}
void CalendarTreeView::dragMoveEvent(QDragMoveEvent *event)
{
if (dragDropMode() == InternalMove && (event->source() != this || !(event->possibleActions() & Qt::MoveAction))) {
return;
}
TreeViewBase::dragMoveEvent( event );
if ( ! event->isAccepted() ) {
return;
}
// QTreeView thinks it's ok to drop, but it might not be...
event->ignore();
QModelIndex index = indexAt( event->pos() );
if ( ! index.isValid() ) {
if ( model()->dropAllowed( 0, event->mimeData() ) ) {
event->accept();
}
return;
}
Calendar *c = model()->calendar( index );
if ( c == 0 ) {
errorPlan<<"no calendar to drop on!";
return; // hmmm
}
switch ( dropIndicatorPosition() ) {
case AboveItem:
case BelowItem:
// c == sibling
// if siblings parent is me or child of me: illegal
if ( model()->dropAllowed( c->parentCal(), event->mimeData() ) ) {
event->accept();
}
break;
case OnItem:
// c == new parent
if ( model()->dropAllowed( c, event->mimeData() ) ) {
event->accept();
}
break;
default:
break;
}
}
//--------------------
CalendarDayView::CalendarDayView( QWidget *parent )
: QTableView( parent ),
m_readwrite( false )
{
setTabKeyNavigation( false );
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
m_model = new CalendarDayItemModel( this );
setModel(m_model);
verticalHeader()->hide();
actionSetWork = new QAction( i18n( "Work..." ), this );
connect( actionSetWork, &QAction::triggered, this, &CalendarDayView::slotSetWork );
actionSetVacation = new QAction( i18n( "Non-working" ), this );
connect( actionSetVacation, &QAction::triggered, this, &CalendarDayView::slotSetVacation );
actionSetUndefined = new QAction( i18n( "Undefined" ), this );
connect( actionSetUndefined, &QAction::triggered, this, &CalendarDayView::slotSetUndefined );
}
QSize CalendarDayView::sizeHint() const
{
QSize s = QTableView::sizeHint();
s.setHeight( horizontalHeader()->height() + rowHeight( 0 ) + frameWidth() * 2 );
return s;
}
void CalendarDayView::slotSetWork()
{
debugPlan;
if ( receivers( SIGNAL(executeCommand(KUndo2Command*)) ) == 0 ) {
return;
}
Calendar *cal = model()->calendar();
if ( cal == 0 ) {
return;
}
QModelIndexList lst = selectionModel()->selectedIndexes();
if ( lst.isEmpty() ) {
lst << currentIndex();
}
if ( lst.isEmpty() ) {
return;
}
QList<CalendarDay*> days;
foreach ( const QModelIndex &i, lst ) {
CalendarDay *day = model()->day( i );
if ( day == 0 ) {
continue;
}
days << day;
}
IntervalEditDialog *dlg = new IntervalEditDialog( cal, days, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotIntervalEditDialogFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void CalendarDayView::slotIntervalEditDialogFinished( int result )
{
IntervalEditDialog *dlg = qobject_cast<IntervalEditDialog*>( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == QDialog::Accepted ) {
MacroCommand *cmd = dlg->buildCommand();
if ( cmd ) {
emit executeCommand( cmd );
}
}
dlg->deleteLater();
}
void CalendarDayView::slotSetVacation()
{
debugPlan;
if ( receivers( SIGNAL(executeCommand(KUndo2Command*)) ) == 0 ) {
return;
}
QModelIndexList lst = selectionModel()->selectedIndexes();
if ( lst.isEmpty() ) {
lst << currentIndex();
}
if ( lst.isEmpty() ) {
return;
}
bool mod = false;
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify Weekday State" ) );
foreach ( const QModelIndex &i, lst ) {
CalendarDay *day = model()->day( i );
if ( day == 0 || day->state() == CalendarDay::NonWorking ) {
continue;
}
mod = true;
m->addCommand( new CalendarModifyStateCmd( model()->calendar(), day, CalendarDay::NonWorking ) );
}
if ( mod ) {
emit executeCommand( m );
} else {
delete m;
}
}
void CalendarDayView::slotSetUndefined()
{
debugPlan;
if ( receivers( SIGNAL(executeCommand(KUndo2Command*)) ) == 0 ) {
return;
}
QModelIndexList lst = selectionModel()->selectedIndexes();
if ( lst.isEmpty() ) {
lst << currentIndex();
}
if ( lst.isEmpty() ) {
return;
}
bool mod = false;
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify Weekday State" ) );
foreach ( const QModelIndex &i, lst ) {
CalendarDay *day = model()->day( i );
if ( day == 0 || day->state() == CalendarDay::Undefined ) {
continue;
}
mod = true;
m->addCommand( new CalendarModifyStateCmd( model()->calendar(), day, CalendarDay::Undefined ) );
}
if ( mod ) {
emit executeCommand( m );
} else {
delete m;
}
}
void CalendarDayView::setCurrentCalendar( Calendar *calendar )
{
model()->setCalendar( calendar );
}
void CalendarDayView::contextMenuEvent ( QContextMenuEvent *event )
{
//debugPlan;
if ( !isReadWrite() || !model()->calendar() || model()->calendar()->isShared() ) {
return;
}
QMenu menu;
menu.addAction( actionSetWork );
menu.addAction( actionSetVacation );
menu.addAction( actionSetUndefined );
menu.exec( event->globalPos(), actionSetWork );
//emit contextMenuRequested( indexAt(event->pos()), event->globalPos() );
}
void CalendarDayView::focusInEvent ( QFocusEvent *event )
{
//debugPlan;
QTableView::focusInEvent( event );
emit focusChanged();
}
void CalendarDayView::focusOutEvent ( QFocusEvent * event )
{
//debugPlan;
QTableView::focusInEvent( event );
emit focusChanged();
}
void CalendarDayView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel )
{
//debugPlan<<sel.indexes().count();
//foreach( QModelIndex i, selectionModel()->selectedIndexes() ) { debugPlan<<i.row()<<","<<i.column(); }
QTableView::selectionChanged( sel, desel );
emit sigSelectionChanged( selectionModel()->selectedIndexes() );
}
void CalendarDayView::currentChanged( const QModelIndex & current, const QModelIndex & previous )
{
//debugPlan;
QTableView::currentChanged( current, previous );
// selectionModel()->select( current, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
emit currentChanged( current );
}
CalendarDay *CalendarDayView::selectedDay() const
{
QModelIndexList lst = selectionModel()->selectedIndexes();
if ( lst.count() == 1 ) {
return model()->day( lst.first() );
}
return 0;
}
//-----------------------------------
CalendarEditor::CalendarEditor(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent ),
m_model( new DateTableDataModel( this ) )
{
Help::add(this,
xi18nc( "@info:whatsthis",
"<title>Work & Vacation Editor</title>"
"<para>"
"A calendar defines availability for resources or tasks of type <emphasis>Duration</emphasis>. "
"A calendar can be specific to a resource or task, or shared by multiple resources or tasks. "
"A day can be of type <emphasis>Undefined</emphasis>, <emphasis>Non-working day</emphasis> or <emphasis>Working day</emphasis>. "
"A working day has one or more work intervals defined. "
"</para><para>"
"A calendar can have sub calendars. If a day is undefined in a calendar, the parent calendar is checked. "
"An <emphasis>Undefined</emphasis> day defaults to <emphasis>Non-working</emphasis> if used by a resource, or <emphasis>available all day</emphasis> if used by a task."
"</para><para>"
"A calendar can be defined as the <emphasis>Default calendar</emphasis>. "
"The default calendar is used by a working resource, when the resources calendar is not explicitly set."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Work_and_Vacation_Editor")));
setupGui();
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin( 0 );
QSplitter *sp = new QSplitter( this );
l->addWidget( sp );
m_calendarview = new CalendarTreeView( sp );
connect(this, &ViewBase::expandAll, m_calendarview, &TreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_calendarview, &TreeViewBase::slotCollapse);
QFrame *f = new QFrame( sp );
l = new QVBoxLayout( f );
l->setMargin( 0 );
m_dayview = new CalendarDayView( f );
l->addWidget( m_dayview );
sp = new QSplitter( f );
l->addWidget( sp );
m_datePicker = new KDatePicker( sp );
m_datePicker->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
m_datePicker->dateTable()->setWeekNumbersEnabled( true );
m_datePicker->dateTable()->setGridEnabled( true );
m_datePicker->dateTable()->setSelectionMode( KDateTable::ExtendedSelection );
m_datePicker->dateTable()->setDateDelegate( new DateTableDateDelegate( m_datePicker->dateTable() ) );
m_datePicker->dateTable()->setModel( m_model );
m_datePicker->dateTable()->setPopupMenuEnabled( true );
m_calendarview->setDragDropMode( QAbstractItemView::InternalMove );
m_calendarview->setDropIndicatorShown( true );
m_calendarview->setDragEnabled ( true );
m_calendarview->setAcceptDrops( true );
m_calendarview->setAcceptDropsOnView( true );
connect( m_datePicker->dateTable(), SIGNAL(aboutToShowContextMenu(QMenu*,QDate)), SLOT(slotContextMenuDate(QMenu*,QDate)) );
connect( m_datePicker->dateTable(), SIGNAL(aboutToShowContextMenu(QMenu*,QList<QDate>)), SLOT(slotContextMenuDate(QMenu*,QList<QDate>)) );
/* const QDate date(2007,7,19);
const QColor fgColor(Qt::darkGray);
KDateTable::BackgroundMode bgMode = KDateTable::CircleMode;
const QColor bgColor( Qt::lightGray);
m_datePicker->dateTable()->setCustomDatePainting( date, fgColor, bgMode, bgColor );*/
m_calendarview->setEditTriggers( m_calendarview->editTriggers() | QAbstractItemView::EditKeyPressed );
m_dayview->setEditTriggers( m_dayview->editTriggers() | QAbstractItemView::EditKeyPressed );
m_calendarview->setDragDropMode( QAbstractItemView::InternalMove );
m_calendarview->setDropIndicatorShown ( true );
m_calendarview->setDragEnabled ( true );
m_calendarview->setAcceptDrops( true );
connect( m_calendarview->model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_dayview->model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_dayview, &CalendarDayView::executeCommand, doc, &KoDocument::addCommand );
connect( m_calendarview, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentCalendarChanged(QModelIndex)) );
connect( m_calendarview, &CalendarTreeView::sigSelectionChanged, this, &CalendarEditor::slotCalendarSelectionChanged );
connect( m_calendarview, SIGNAL(contextMenuRequested(QModelIndex,QPoint)), this, SLOT(slotContextMenuCalendar(QModelIndex,QPoint)) );
connect( m_dayview, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentDayChanged(QModelIndex)) );
connect( m_dayview, &CalendarDayView::sigSelectionChanged, this, &CalendarEditor::slotDaySelectionChanged );
connect( m_dayview, &CalendarDayView::contextMenuRequested, this, &CalendarEditor::slotContextMenuDay );
connect( m_dayview->model(), &QAbstractItemModel::dataChanged, this, &CalendarEditor::slotEnableActions );
connect( m_calendarview, &CalendarTreeView::focusChanged, this, &CalendarEditor::slotEnableActions );
connect( m_dayview, &CalendarDayView::focusChanged, this, &CalendarEditor::slotEnableActions );
}
void CalendarEditor::draw( Project &project )
{
m_calendarview->setProject( &project );
m_dayview->setProject( &project );
}
void CalendarEditor::draw()
{
}
void CalendarEditor::setGuiActive( bool activate )
{
//debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate ) {
if ( !m_calendarview->currentIndex().isValid() ) {
m_calendarview->selectionModel()->setCurrentIndex(m_calendarview->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
//slotSelectionChanged( m_calendarview->selectionModel()->selectedRows() );
}
}
void CalendarEditor::slotContextMenuDate( QMenu *menu, const QList<QDate> &dates )
{
if ( ! isReadWrite() ) {
return;
}
if (!currentCalendar() || currentCalendar()->isShared()) {
return;
}
if ( dates.isEmpty() ) {
m_currentMenuDateList << m_datePicker->date();
} else {
m_currentMenuDateList = dates;
}
menu->addAction( actionSetWork );
menu->addAction( actionSetVacation );
menu->addAction( actionSetUndefined );
}
void CalendarEditor::slotContextMenuDate( QMenu *menu, const QDate &date )
{
debugPlan<<menu<<date;
if ( ! isReadWrite() || ! date.isValid() ) {
return;
}
if (!currentCalendar() || currentCalendar()->isShared()) {
return;
}
m_currentMenuDateList << date;
menu->addAction( actionSetWork );
menu->addAction( actionSetVacation );
menu->addAction( actionSetUndefined );
}
void CalendarEditor::slotContextMenuCalendar( const QModelIndex &index, const QPoint& pos )
{
if (!index.isValid()) {
slotHeaderContextMenuRequested(pos);
return;
}
if ( ! isReadWrite() || !currentCalendar() ) {
return;
}
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
QString name;
/* if ( index.isValid() ) {
Calendar *a = m_calendarview->model()->calendar( index );
if ( a ) {
name = "calendareditor_calendar_popup";
}
}*/
//debugPlan<<name;
m_calendarview->setContextMenuIndex(index);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested(pos);
m_calendarview->setContextMenuIndex(QModelIndex());
return;
}
emit requestPopupMenu( name, pos );
m_calendarview->setContextMenuIndex(QModelIndex());
}
void CalendarEditor::slotContextMenuDay( const QModelIndex &index, const QPoint& pos )
{
if ( ! isReadWrite() ) {
return;
}
debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
/* QString name;
if ( index.isValid() ) {
if ( m_dayview->model()->day( index ) ) {
name = "calendareditor_day_popup";
}
}
debugPlan<<name;
if ( name.isEmpty() ) {
return;
}
emit requestPopupMenu( name, pos );*/
}
bool CalendarEditor::loadContext( const KoXmlElement &context )
{
return m_calendarview->loadContext(m_calendarview->model()->columnMap(), context);
}
void CalendarEditor::saveContext( QDomElement &context ) const
{
m_calendarview->saveContext(m_calendarview->model()->columnMap(), context);
}
Calendar *CalendarEditor::currentCalendar() const
{
return m_calendarview->currentCalendar();
}
void CalendarEditor::slotCurrentCalendarChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
m_dayview->setCurrentCalendar( currentCalendar() );
if ( m_model ) {
m_model->setCalendar( currentCalendar() );
}
}
void CalendarEditor::slotCalendarSelectionChanged( const QModelIndexList& /*list */)
{
//debugPlan<<list.count();
updateActionsEnabled( true );
}
void CalendarEditor::slotCurrentDayChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
}
void CalendarEditor::slotDaySelectionChanged( const QModelIndexList& )
{
//debugPlan<<list.count();
updateActionsEnabled( true );
}
void CalendarEditor::slotEnableActions()
{
updateActionsEnabled( true );
}
void CalendarEditor::updateActionsEnabled( bool on )
{
QList<Calendar *> lst = m_calendarview->selectedCalendars();
bool one = lst.count() == 1;
bool more = lst.count() > 1;
actionAddCalendar ->setEnabled( on && !more );
actionAddSubCalendar ->setEnabled( on && one );
actionDeleteSelection->setEnabled( on && ( one || more ) );
}
void CalendarEditor::setupGui()
{
KActionCollection *coll = actionCollection();
QString name = "calendareditor_calendar_list";
actionAddCalendar = new QAction(koIcon("resource-calendar-insert"), i18n("Add Calendar"), this);
coll->addAction("add_calendar", actionAddCalendar );
coll->setDefaultShortcut(actionAddCalendar, Qt::CTRL + Qt::Key_I);
connect( actionAddCalendar , &QAction::triggered, this, &CalendarEditor::slotAddCalendar );
actionAddSubCalendar = new QAction(koIcon("resource-calendar-child-insert"), i18n("Add Subcalendar"), this);
coll->addAction("add_subcalendar", actionAddSubCalendar );
coll->setDefaultShortcut(actionAddSubCalendar, Qt::SHIFT + Qt::CTRL + Qt::Key_I);
connect( actionAddSubCalendar , &QAction::triggered, this, &CalendarEditor::slotAddSubCalendar );
actionDeleteSelection = new QAction(koIcon("edit-delete"), xi18nc("@action", "Delete"), this);
coll->addAction("delete_calendar_selection", actionDeleteSelection );
coll->setDefaultShortcut(actionDeleteSelection, Qt::Key_Delete);
connect( actionDeleteSelection, &QAction::triggered, this, &CalendarEditor::slotDeleteCalendar );
addAction( name, actionAddCalendar );
addAction( name, actionAddSubCalendar );
addAction( name, actionDeleteSelection );
actionSetWork = new QAction( i18n( "Work..." ), this );
connect( actionSetWork, &QAction::triggered, this, &CalendarEditor::slotSetWork );
actionSetVacation = new QAction( i18n( "Non-working" ), this );
connect( actionSetVacation, &QAction::triggered, this, &CalendarEditor::slotSetVacation );
actionSetUndefined = new QAction( i18n( "Undefined" ), this );
connect( actionSetUndefined, &QAction::triggered, this, &CalendarEditor::slotSetUndefined );
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig);
}
void CalendarEditor::slotOptions()
{
ItemViewSettupDialog *dlg = new ItemViewSettupDialog( this, m_calendarview, false, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void CalendarEditor::updateReadWrite( bool readwrite )
{
m_calendarview->setReadWrite( readwrite );
m_dayview->setReadWrite( readwrite );
ViewBase::updateReadWrite( readwrite );
}
void CalendarEditor::slotAddCalendar ()
{
//debugPlan;
// get parent through sibling
Calendar *cal = m_calendarview->selectedCalendar();
Calendar *parent = cal ? cal->parentCal() : 0;
int pos = parent ? parent->indexOf( cal ) : project()->indexOf( cal );
if ( pos >= 0 ) {
++pos; // after selected calendar
}
insertCalendar ( new Calendar(), parent, pos );
}
void CalendarEditor::slotAddSubCalendar ()
{
//debugPlan;
insertCalendar ( new Calendar (), m_calendarview->selectedCalendar () );
}
void CalendarEditor::insertCalendar ( Calendar *calendar, Calendar *parent, int pos )
{
m_calendarview->closePersistentEditor( m_calendarview->selectionModel()->currentIndex() );
QModelIndex i = m_calendarview->model()->insertCalendar ( calendar, pos, parent );
if ( i.isValid() ) {
QModelIndex p = m_calendarview->model()->parent( i );
//if (parent) debugPlan<<" parent="<<parent->name()<<":"<<p.row()<<","<<p.column();
//debugPlan<<i.row()<<","<<i.column();
m_calendarview->setExpanded( p, true );
m_calendarview->setCurrentIndex( i );
m_calendarview->edit( i );
}
}
void CalendarEditor::slotDeleteCalendar()
{
//debugPlan;
m_calendarview->model()->removeCalendar( m_calendarview->selectedCalendar() );
}
void CalendarEditor::slotAddInterval ()
{
//debugPlan;
/* CalendarDay *parent = m_dayview->selectedDay ();
if ( parent == 0 ) {
TimeInterval *ti = m_dayview->selectedInterval();
if ( ti == 0 ) {
return;
}
parent = m_dayview->model()->parentDay( ti );
if ( parent == 0 ) {
return;
}
}
QModelIndex i = m_dayview->model()->insertInterval( new TimeInterval(), parent );
if ( i.isValid() ) {
QModelIndex p = m_dayview->model()->index( parent );
m_dayview->setExpanded( p, true );
m_dayview->setCurrentIndex( i );
m_dayview->edit( i );
}*/
}
void CalendarEditor::slotDeleteDaySelection()
{
//debugPlan;
/* TimeInterval *ti = m_dayview->selectedInterval();
if ( ti != 0 ) {
m_dayview->model()->removeInterval( ti );
return;
}
CalendarDay *day = m_dayview->selectedDay();
if ( day != 0 ) {
m_dayview->model()->removeDay( day );
}*/
}
void CalendarEditor::slotAddDay ()
{
//debugPlan;
/* Calendar *c = currentCalendar();
if ( c == 0 ) {
return;
}
QDate date = QDate::currentDate();
while ( c->day( date ) ) {
date = date.addDays( 1 );
}
QModelIndex i = m_dayview->model()->insertDay( new CalendarDay(date, CalendarDay::NonWorking ) );
if ( i.isValid() ) {
QModelIndex p = m_dayview->model()->parent( i );
m_dayview->setExpanded( p, true );
m_dayview->setCurrentIndex( i );
m_dayview->edit( i );
}*/
}
void CalendarEditor::slotSetWork()
{
debugPlan<<currentCalendar()<<m_currentMenuDateList;
if ( currentCalendar() == 0 || m_currentMenuDateList.isEmpty() ) {
return;
}
IntervalEditDialog *dlg = new IntervalEditDialog( currentCalendar(), m_currentMenuDateList, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotIntervalEditDialogFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
m_currentMenuDateList.clear();
}
void CalendarEditor::slotIntervalEditDialogFinished( int result )
{
IntervalEditDialog *dlg = qobject_cast<IntervalEditDialog*>( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == QDialog::Accepted ) {
MacroCommand *cmd = dlg->buildCommand();
if ( cmd ) {
part()->addCommand( cmd );
}
}
dlg->deleteLater();
}
void CalendarEditor::slotSetVacation()
{
debugPlan<<m_currentMenuDateList;
if ( m_currentMenuDateList.isEmpty() || currentCalendar() == 0 ) {
return;
}
bool mod = false;
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify Calendar" ) );
foreach ( const QDate &date, m_currentMenuDateList ) {
debugPlan<<"handle:"<<date;
CalendarDay *day = currentCalendar()->findDay( date );
if ( day == 0 ) {
mod = true;
day = new CalendarDay( date, CalendarDay::NonWorking );
m->addCommand( new CalendarAddDayCmd( currentCalendar(), day ) );
if ( m_currentMenuDateList.count() == 1 ) {
m->setText( kundo2_i18n( "%1: Set to Non-Working", date.toString() ) );
}
} else if ( day->state() != CalendarDay::NonWorking ) {
mod = true;
m->addCommand( new CalendarModifyStateCmd( currentCalendar(), day, CalendarDay::NonWorking ) );
if ( m_currentMenuDateList.count() == 1 ) {
m->setText( kundo2_i18n( "%1: Set to Non-Working", date.toString() ) );
}
}
}
if ( mod ) {
part()->addCommand( m );
} else {
delete m;
}
m_currentMenuDateList.clear();
}
void CalendarEditor::slotSetUndefined()
{
debugPlan;
if ( m_currentMenuDateList.isEmpty() || currentCalendar() == 0 ) {
return;
}
bool mod = false;
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify Calendar" ) );
foreach ( const QDate &date, m_currentMenuDateList ) {
CalendarDay *day = currentCalendar()->findDay( date );
if ( day && day->state() != CalendarDay::Undefined ) {
mod = true;
m->addCommand( new CalendarRemoveDayCmd( currentCalendar(), day ) );
if ( m_currentMenuDateList.count() == 1 ) {
m->setText( kundo2_i18n( "Set %1 to Undefined", date.toString() ) );
}
}
}
if ( mod ) {
part()->addCommand( m );
} else {
delete m;
}
m_currentMenuDateList.clear();
}
} // namespace KPlato
diff --git a/src/libs/ui/kptdependencyeditor.cpp b/src/libs/ui/kptdependencyeditor.cpp
index cd2db426..2e9f456d 100644
--- a/src/libs/ui/kptdependencyeditor.cpp
+++ b/src/libs/ui/kptdependencyeditor.cpp
@@ -1,2448 +1,2449 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
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;
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 "kptdependencyeditor.h"
#include "PlanMacros.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptitemmodelbase.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptrelation.h"
#include "kptschedule.h"
#include "kptdebug.h"
#include "config.h"
#include "Help.h"
#include "KoPageLayoutWidget.h"
#include <KoIcon.h>
#include <QGraphicsSceneMouseEvent>
#include <QItemSelectionModel>
#include <QModelIndex>
#include <QPainterPath>
#include <QPalette>
#include <QStyleOptionViewItem>
#include <QVBoxLayout>
#include <QWidget>
#include <QKeyEvent>
#include <QAction>
#include <QMenu>
#include <KLocalizedString>
#include <kactionmenu.h>
#include <kactioncollection.h>
#define ConnectCursor Qt::DragLinkCursor
namespace KPlato
{
void plan_paintFocusSelectedItem( QPainter *painter, const QStyleOptionGraphicsItem *option )
{
if ( option->state & ( QStyle::State_Selected | QStyle::State_HasFocus ) ) {
painter->save();
if (option->state & QStyle::State_Selected) {
debugPlanDepEditor<<"selected";
QPalette::ColorGroup cg = option->state & QStyle::State_Enabled
? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(option->state & QStyle::State_Active))
cg = QPalette::Inactive;
QLinearGradient g( 0.0, option->rect.top(), 0.0, option->rect.bottom() );
QColor col = option->palette.brush(cg, QPalette::Highlight).color();
g.setColorAt( 0.0, col.lighter( 125 ) );
g.setColorAt( 1.0, col.lighter( 60 ) );
painter->setPen( Qt::NoPen );
painter->setBrush( QBrush( g ) );
painter->drawRect( option->exposedRect );
}
if ( option->state & QStyle::State_HasFocus ) {
debugPlanDepEditor<<"has focus";
QPalette::ColorGroup cg = option->state & QStyle::State_Enabled
? QPalette::Active : QPalette::Disabled;
if (cg == QPalette::Active && !(option->state & QStyle::State_Active))
cg = QPalette::Inactive;
QPen p( Qt::DotLine );
p.setWidthF( 2. );
if ( option->state & QStyle::State_Selected ) {
p.setColor( option->palette.color( cg, QPalette::Shadow ) );
debugPlanDepEditor<<"focus: selected"<<p.color();
} else {
p.setColor( option->palette.color( cg, QPalette::Highlight ) );
debugPlanDepEditor<<"focus: not selected"<<p.color();
}
painter->setPen( p );
painter->setBrush( Qt::NoBrush );
painter->drawRect( option->exposedRect );
}
painter->restore();
}
}
//----------------------
DependecyViewPrintingDialog::DependecyViewPrintingDialog( ViewBase *parent, DependencyView *view )
: PrintingDialog( parent ),
m_depview( view )
{
debugPlanDepEditor<<this;
}
int DependecyViewPrintingDialog::documentLastPage() const
{
//TODO
return documentFirstPage();
}
QList<QWidget*> DependecyViewPrintingDialog::createOptionWidgets() const
{
QList<QWidget*> lst;
lst << createPageLayoutWidget();
lst += PrintingDialog::createOptionWidgets();
return lst;
}
void DependecyViewPrintingDialog::printPage( int page, QPainter &painter )
{
painter.save();
QRect hRect = headerRect();
QRect fRect = footerRect();
QRect pageRect = printer().pageRect();
pageRect.moveTo( 0, 0 );
debugPlanDepEditor<<pageRect<<hRect<<fRect;
painter.translate( pageRect.topLeft() );
painter.setClipping( true );
paintHeaderFooter( painter, printingOptions(), page, *(m_depview->project()) );
int gap = 8;
int pageHeight = pageRect.height();
if ( hRect.isValid() ) {
pageHeight -= ( hRect.height() + gap );
}
if ( fRect.isValid() ) {
pageHeight -= ( fRect.height() + gap );
}
painter.translate( 0, hRect.height() + gap );
QRect r( 0, 0, pageRect.width(), pageHeight );
m_depview->itemScene()->render( &painter, r );
painter.restore();
}
DependencyLinkItemBase::DependencyLinkItemBase( QGraphicsItem *parent )
: QGraphicsPathItem( parent ),
m_editable( false ),
predItem( 0 ),
succItem( 0 ),
relation( 0 ),
m_arrow( new QGraphicsPathItem( this ) )
{
}
DependencyLinkItemBase::DependencyLinkItemBase( DependencyNodeItem *predecessor, DependencyNodeItem *successor, Relation *rel, QGraphicsItem *parent )
: QGraphicsPathItem( parent ),
m_editable( false ),
predItem( predecessor ),
succItem( successor ),
relation( rel ),
m_arrow( new QGraphicsPathItem( this ) )
{
}
DependencyLinkItemBase::~DependencyLinkItemBase()
{
}
DependencyScene *DependencyLinkItemBase::itemScene() const
{
return static_cast<DependencyScene*>( scene() );
}
void DependencyLinkItemBase::createPath( const QPointF &sp, int starttype, const QPointF &ep, int endtype )
{
//if ( predItem && succItem ) debugPlanDepEditor<<predItem->text()<<" ->"<<succItem->text()<<" visible="<<isVisible();
if ( ! isVisible() ) {
return;
}
qreal hgap = itemScene()->horizontalGap();
bool up = sp.y() > ep.y();
bool right = sp.x() < ep.x();
bool same = sp.x() == ep.x();
QPainterPath link( sp );
qreal x = sp.x();
qreal y = sp.y();
if ( right && starttype == DependencyNodeItem::Finish) {
x = ep.x();
x += endtype == DependencyNodeItem::Start ? - hgap/2 - 6 : hgap/2 - 6;
link.lineTo( x, y );
x += 6;
QPointF cp( x, y );
y += up ? -6 : +6;
link.quadTo( cp, QPointF( x, y ) );
y = up ? ep.y() + 6 : ep.y() - 6;
link.lineTo( x, y );
y = ep.y();
cp = QPointF( x, y );
x += endtype == DependencyNodeItem::Start ? 6 : -6;
link.quadTo( cp, QPointF( x, y ) );
} else if ( right && starttype == DependencyNodeItem::Start ) {
x = sp.x() - hgap/2 + 6;
link.lineTo( x, y );
x -= 6;
QPointF cp( x, y );
y += up ? -6 : +6;
link.quadTo( cp, QPointF( x, y ) );
y = up ? ep.y() + 6 : ep.y() - 6;
link.lineTo( x, y );
y = ep.y();
cp = QPointF( x, y );
x += endtype == DependencyNodeItem::Start ? 6 : -6;
link.quadTo( cp, QPointF( x, y ) );
} else if ( same ) {
x = ep.x();
x += endtype == DependencyNodeItem::Start ? - hgap/2 + 6 : hgap/2 - 6;
link.lineTo( x, y );
x += endtype == DependencyNodeItem::Start ? -6 : +6;
QPointF cp( x, y );
y += up ? -6 : 6;
link.quadTo( cp, QPointF( x, y ) );
y = up ? ep.y() + 6 : ep.y() - 6;
link.lineTo( x, y );
y = ep.y();
cp = QPointF( x, y );
if ( endtype == DependencyNodeItem::Start ) {
x += 6;
} else {
x -= 6;
}
link.quadTo( cp, QPointF( x, y ) );
} else {
x = ep.x();
x += endtype == DependencyNodeItem::Start ? - hgap/2 + 6 : hgap/2 + 6;
link.lineTo( x, y );
x -= 6;
QPointF cp( x, y );
y += up ? -6 : 6;
link.quadTo( cp, QPointF( x, y ) );
y = up ? ep.y() + 6 : ep.y() - 6;
link.lineTo( x, y );
y = ep.y();
cp = QPointF( x, y );
x += endtype == DependencyNodeItem::Start ? 6 : -6;
link.quadTo( cp, QPointF( x, y ) );
}
link.lineTo( ep );
setPath( link );
QPainterPath arrow;
x = endtype == DependencyNodeItem::Start ? -6 : 6;
arrow.moveTo( ep );
arrow.lineTo( ep.x() + x, ep.y() - 3 );
arrow.lineTo( ep.x() + x, ep.y() + 3 );
arrow.lineTo( ep );
m_arrow->setPath( arrow );
m_arrow->show();
}
//--------------------------------
DependencyLinkItem::DependencyLinkItem( DependencyNodeItem *predecessor, DependencyNodeItem *successor, Relation *rel, QGraphicsItem *parent )
: DependencyLinkItemBase( predecessor, successor, rel, parent )
{
setZValue( 100.0 );
setAcceptHoverEvents( true );
//debugPlanDepEditor<<predecessor->text()<<"("<<predecessor->column()<<") -"<<successor->text();
predItem->addChildRelation( this );
succItem->addParentRelation( this );
succItem->setColumn();
m_arrow->setBrush( Qt::black );
m_pen = pen();
}
DependencyLinkItem::~DependencyLinkItem()
{
if ( predItem ) {
predItem->takeChildRelation( this );
}
if ( succItem ) {
succItem->takeParentRelation( this );
}
}
int DependencyLinkItem::newChildColumn() const
{
int col = predItem->column();
if ( relation->type() == Relation::FinishStart ) {
++col;
}
//debugPlanDepEditor<<"new col="<<col;
return col;
}
void DependencyLinkItem::setItemVisible( bool show )
{
setVisible( show && predItem->isVisible() && succItem->isVisible() );
}
void DependencyLinkItem::createPath()
{
setVisible( predItem->isVisible() && succItem->isVisible() );
if ( ! isVisible() ) {
//debugPlanDepEditor<<"Visible="<<isVisible()<<":"<<predItem->node()->name()<<" -"<<succItem->node()->name();
return;
}
QPointF sp = startPoint();
QPointF ep = endPoint();
int stype = 0, etype = 0;
switch ( relation->type() ) {
case Relation::StartStart:
stype = DependencyNodeItem::Start; etype = DependencyNodeItem::Start;
break;
case Relation::FinishStart:
stype = DependencyNodeItem::Finish; etype = DependencyNodeItem::Start;
break;
case Relation::FinishFinish:
stype = DependencyNodeItem::Finish; etype = DependencyNodeItem::Finish;
break;
default:
break;
}
DependencyLinkItemBase::createPath( sp, stype, ep, etype );
}
QPointF DependencyLinkItem::startPoint() const
{
if ( relation->type() == Relation::StartStart ) {
return predItem->connectorPoint( DependencyNodeItem::Start );
}
return predItem->connectorPoint( DependencyNodeItem::Finish );
}
QPointF DependencyLinkItem::endPoint() const
{
if ( relation->type() == Relation::FinishFinish ) {
return succItem->connectorPoint( DependencyNodeItem::Finish );
}
return succItem->connectorPoint( DependencyNodeItem::Start );
}
void DependencyLinkItem::hoverEnterEvent( QGraphicsSceneHoverEvent * /*event*/ )
{
setZValue( zValue() + 1 );
setPen( QPen( Qt::black, 2 ) );
m_arrow->setPen( pen() );
update();
}
void DependencyLinkItem::hoverLeaveEvent( QGraphicsSceneHoverEvent * /*event*/ )
{
resetHooverIndication();
}
void DependencyLinkItem::resetHooverIndication()
{
setZValue( zValue() - 1 );
setPen( m_pen );
m_arrow->setPen( m_pen );
update();
}
void DependencyLinkItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
//debugPlanDepEditor;
QGraphicsItem::GraphicsItemFlags f = flags();
if ( isEditable() && itemScene()->connectionMode() ) {
itemScene()->clearConnection();
setFlags( f & ~QGraphicsItem::ItemIsSelectable );
}
QGraphicsPathItem::mousePressEvent( event );
if ( f != flags() ) {
setFlags( f );
}
}
//--------------------
DependencyCreatorItem::DependencyCreatorItem( QGraphicsItem *parent )
: DependencyLinkItemBase( parent ),
predConnector( 0 ),
succConnector( 0 ),
m_editable( false )
{
setZValue( 1000.0 );
clear();
setPen( QPen( Qt::blue, 2 ) );
m_arrow->setBrush( Qt::blue );
m_arrow->setPen( QPen( Qt::blue, 2 ) );
}
void DependencyCreatorItem::clear()
{
hide();
if ( predConnector && predConnector->parentItem() ) {
static_cast<DependencyNodeItem*>( predConnector->parentItem() )->setConnectorHoverMode( true );
} else if ( succConnector && succConnector->parentItem() ) {
static_cast<DependencyNodeItem*>( succConnector->parentItem() )->setConnectorHoverMode( true );
}
predConnector = 0;
succConnector = 0;
setPath( QPainterPath() );
m_arrow->setPath( QPainterPath() );
}
void DependencyCreatorItem::setPredConnector( DependencyConnectorItem *item )
{
predConnector = item;
//static_cast<DependencyNodeItem*>( item->parentItem() )->setConnectorHoverMode( false );
}
void DependencyCreatorItem::setSuccConnector( DependencyConnectorItem *item )
{
succConnector = item;
}
void DependencyCreatorItem::createPath()
{
if ( predConnector == 0 ) {
return;
}
if ( succConnector == 0 ) {
return;
}
QPointF sp = predConnector->connectorPoint();
QPointF ep = succConnector->connectorPoint();
DependencyLinkItemBase::createPath( sp, predConnector->ctype(), ep, succConnector->ctype() );
}
void DependencyCreatorItem::createPath( const QPointF &ep )
{
m_arrow->hide();
if ( succConnector ) {
return createPath();
}
if ( predConnector == 0 ) {
return;
}
QPointF sp = predConnector->connectorPoint();
QPainterPath link( sp );
link.lineTo( ep );
setPath( link );
}
QPointF DependencyCreatorItem::startPoint() const
{
return predConnector == 0 ? QPointF() : predConnector->connectorPoint();
}
QPointF DependencyCreatorItem::endPoint() const
{
return succConnector == 0 ? QPointF() : succConnector->connectorPoint();
}
//--------------------
DependencyConnectorItem::DependencyConnectorItem( DependencyNodeItem::ConnectorType type, DependencyNodeItem *parent )
: QGraphicsRectItem( parent ),
m_ctype( type ),
m_editable( false )
{
setCursor( ConnectCursor);
setAcceptHoverEvents( true );
setZValue( 500.0 );
setFlag( QGraphicsItem::ItemIsFocusable );
}
DependencyScene *DependencyConnectorItem::itemScene() const
{
return static_cast<DependencyScene*>( scene() );
}
DependencyNodeItem *DependencyConnectorItem::nodeItem() const
{
return static_cast<DependencyNodeItem*>( parentItem() );
}
Node *DependencyConnectorItem::node() const
{
return static_cast<DependencyNodeItem*>( parentItem() )->node();
}
QPointF DependencyConnectorItem::connectorPoint() const
{
QRectF r = rect();
return QPointF( r.x()+r.width(), r.y() + r.height()/2 );
}
void DependencyConnectorItem::hoverEnterEvent( QGraphicsSceneHoverEvent * /*event*/ )
{
itemScene()->connectorEntered( this, true );
}
void DependencyConnectorItem::hoverLeaveEvent( QGraphicsSceneHoverEvent * /*event*/ )
{
itemScene()->connectorEntered( this, false );
}
void DependencyConnectorItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if ( ! isEditable() ) {
event->ignore();
return;
}
if (event->button() == Qt::LeftButton ) {
m_mousePressPos = event->pos();
} else {
event->ignore();
}
}
void DependencyConnectorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
m_mousePressPos = QPointF();
if (event->button() != Qt::LeftButton ) {
event->ignore();
return;
}
if ( rect().contains( event->scenePos() ) ) {
// user clicked on this item
bool multiSelect = (event->modifiers() & Qt::ControlModifier) != 0;
if (multiSelect) {
itemScene()->multiConnectorClicked( this );
} else {
itemScene()->singleConnectorClicked( this );
}
return;
}
QGraphicsItem *item = 0;
foreach ( QGraphicsItem *i, itemScene()->items( event->scenePos() ) ) {
if ( i->type() == DependencyConnectorItem::Type ) {
item = i;
break;
}
}
if ( item == 0 || item == itemScene()->fromItem() ) {
itemScene()->setFromItem( 0 );
return;
}
itemScene()->singleConnectorClicked( static_cast<DependencyConnectorItem*>( item ) );
}
void DependencyConnectorItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (event->buttons() == Qt::LeftButton ) {
if ( ! m_mousePressPos.isNull() ) {
itemScene()->setFromItem( this );
m_mousePressPos = QPointF();
}
QGraphicsItem *item = 0;
foreach ( QGraphicsItem *i, itemScene()->items( event->scenePos() ) ) {
if ( i->type() == DependencyConnectorItem::Type ) {
item = i;
break;
}
}
if ( item != this ) {
itemScene()->connectorEntered( this, false );
}
if ( item != 0 ) {
itemScene()->connectorEntered( static_cast<DependencyConnectorItem*>( item ), true );
}
} else {
event->ignore();
}
}
void DependencyConnectorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget */*widget*/)
{
//debugPlanDepEditor;
QStyleOptionGraphicsItem opt( *option );
opt.exposedRect = rect();
if ( itemScene()->fromItem() == this ) {
opt.state |= QStyle::State_Selected;
}
if ( itemScene()->focusItem() == this ) {
opt.state |= QStyle::State_HasFocus;
}
plan_paintFocusSelectedItem( painter, &opt );
QRectF r = rect();
if ( ctype() == DependencyNodeItem::Start ) {
r.setRect( r.right() - (r.width()/2.0) + 1.0, r.y() + ( r.height() * 0.33 ), r.width() / 2.0, r.height() * 0.33 );
} else {
r.setRect( r.right() - (r.width()/2.0) - 1.0, r.y() + ( r.height() * 0.33 ), r.width() / 2.0, r.height() * 0.33 );
}
painter->fillRect( r, Qt::black );
}
QList<DependencyLinkItem*> DependencyConnectorItem::predecessorItems() const
{
return nodeItem()->predecessorItems( m_ctype );
}
QList<DependencyLinkItem*> DependencyConnectorItem::successorItems() const
{
return nodeItem()->successorItems( m_ctype );
}
//--------------------
DependencyNodeItem::DependencyNodeItem( Node *node, DependencyNodeItem *parent )
: QGraphicsRectItem( parent ),
m_node( node ),
m_parent( 0 ),
m_editable( false )
{
setAcceptHoverEvents( true );
setZValue( 400.0 );
setParentItem( parent );
m_start = new DependencyConnectorItem( DependencyNodeItem::Start, this );
m_finish = new DependencyConnectorItem( DependencyNodeItem::Finish, this );
m_text = new QGraphicsTextItem( this );
m_textFont = m_text->font();
m_textFont.setPointSize( 10 );
m_text->setFont( m_textFont );
setText();
setFlags( QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable );
// do not attach this item to the scene as it gives continuous paint events when a node item is selected
m_symbol = new DependencyNodeSymbolItem();
m_symbol->setZValue( zValue() + 10.0 );
setSymbol();
m_treeIndicator = new QGraphicsPathItem( this );
m_treeIndicator->setPen( QPen( Qt::gray ) );
}
DependencyNodeItem::~DependencyNodeItem()
{
qDeleteAll( m_childrelations );
qDeleteAll( m_parentrelations );
//qDeleteAll( m_children );
delete m_symbol;
}
void DependencyNodeItem::setText()
{
m_text->setPlainText( m_node == 0 ? QString() : QString( "%1 %2").arg( m_node->wbsCode() ).arg(m_node->name() ) );
}
DependencyScene *DependencyNodeItem::itemScene() const
{
return static_cast<DependencyScene*>( scene() );
}
void DependencyNodeItem::setSymbol()
{
m_symbol->setSymbol( m_node->type(), itemScene()->symbolRect() );
}
QPointF DependencyNodeItem::connectorPoint( DependencyNodeItem::ConnectorType type ) const
{
QRectF r;
if ( type == Start ) {
return m_start->connectorPoint();
}
return m_finish->connectorPoint();
}
void DependencyNodeItem::setConnectorHoverMode( bool mode )
{
m_start->setAcceptHoverEvents( mode );
m_finish->setAcceptHoverEvents( mode );
}
void DependencyNodeItem::setParentItem( DependencyNodeItem *parent )
{
if ( m_parent ) {
m_parent->takeChild( this );
}
m_parent = parent;
if ( m_parent ) {
m_parent->addChild( this );
}
}
void DependencyNodeItem::setExpanded( bool mode )
{
foreach ( DependencyNodeItem *ch, m_children ) {
itemScene()->setItemVisible( ch, mode );
ch->setExpanded( mode );
}
}
void DependencyNodeItem::setItemVisible( bool show )
{
setVisible( show );
//debugPlanDepEditor<<isVisible()<<","<<node()->name();
foreach ( DependencyLinkItem *i, m_parentrelations ) {
i->setItemVisible( show );
}
foreach ( DependencyLinkItem *i, m_childrelations ) {
i->setItemVisible( show );
}
}
DependencyNodeItem *DependencyNodeItem::takeChild( DependencyNodeItem *ch )
{
int i = m_children.indexOf( ch );
if ( i == -1 ) {
return 0;
}
return m_children.takeAt( i );
}
void DependencyNodeItem::setRectangle( const QRectF &rect )
{
//debugPlanDepEditor<<text()<<":"<<rect;
setRect( rect );
qreal connection = static_cast<DependencyScene*>( scene() )->connectorWidth();
m_start->setRect( rect.x() + connection, rect.y(), -connection, rect.height() );
m_finish->setRect( rect.right() - connection, rect.y(), connection, rect.height() );
m_text->setPos( m_finish->rect().right() + 2.0, itemScene()->gridY( row() ) );
m_symbol->setPos( rect.topLeft() + QPointF( connection, 0 ) + QPointF( 2.0, 2.0 ) );
}
void DependencyNodeItem::moveToY( qreal y )
{
QRectF r = rect();
r. moveTop( y );
setRectangle( r );
//debugPlanDepEditor<<text()<<" move to="<<y<<" new pos:"<<rect();
foreach ( DependencyLinkItem *i, m_parentrelations ) {
i->createPath();
}
foreach ( DependencyLinkItem *i, m_childrelations ) {
i->createPath();
}
DependencyNodeItem *par = this;
while ( par->parentItem() ) {
par = par->parentItem();
}
par->setTreeIndicator( true );
}
void DependencyNodeItem::setRow( int row )
{
moveToY( itemScene()->itemY( row ) );
}
int DependencyNodeItem::row() const
{
return itemScene()->row( rect().y() );
}
void DependencyNodeItem::moveToX( qreal x )
{
QRectF r = rect();
r. moveLeft( x );
setRectangle( r );
//debugPlanDepEditor<<m_text->toPlainText()<<" to="<<x<<" new pos:"<<rect();
foreach ( DependencyLinkItem *i, m_parentrelations ) {
i->createPath();
}
foreach ( DependencyLinkItem *i, m_childrelations ) {
i->createPath();
}
DependencyNodeItem *par = this;
while ( par->parentItem() ) {
par = par->parentItem();
}
par->setTreeIndicator( true );
}
void DependencyNodeItem::setColumn()
{
int col = m_parent == 0 ? 0 : m_parent->column() + 1;
//debugPlanDepEditor<<this<<text();
foreach ( DependencyLinkItem *i, m_parentrelations ) {
col = qMax( col, i->newChildColumn() );
}
if ( col != column() ) {
setColumn( col );
foreach ( DependencyLinkItem *i, m_childrelations ) {
i->succItem->setColumn();
}
//debugPlanDepEditor<<m_children.count()<<"Column="<<column()<<","<<text();
foreach ( DependencyNodeItem *i, m_children ) {
i->setColumn();
}
}
}
void DependencyNodeItem::setColumn( int col )
{
moveToX( itemScene()->itemX( col ) );
}
int DependencyNodeItem::column() const
{
return itemScene()->column( rect().x() );
}
DependencyLinkItem *DependencyNodeItem::takeParentRelation( DependencyLinkItem *r )
{
int i = m_parentrelations.indexOf( r );
if ( i == -1 ) {
return 0;
}
DependencyLinkItem *dep = m_parentrelations.takeAt( i );
setColumn();
return dep;
}
DependencyLinkItem *DependencyNodeItem::takeChildRelation( DependencyLinkItem *r )
{
int i = m_childrelations.indexOf( r );
if ( i == -1 ) {
return 0;
}
return m_childrelations.takeAt( i );
}
void DependencyNodeItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
debugPlanDepEditor;
QGraphicsItem::GraphicsItemFlags f = flags();
if ( itemScene()->connectionMode() ) {
itemScene()->clearConnection();
setFlags( f & ~QGraphicsItem::ItemIsSelectable );
}
QGraphicsRectItem::mousePressEvent( event );
if ( f != flags() ) {
setFlags( f );
}
}
void DependencyNodeItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * )
{
//debugPlanDepEditor;
QLinearGradient g( 0.0, rect().top(), 0.0, rect().bottom() );
g.setColorAt( 0.0, option->palette.color( QPalette::Midlight ) );
g.setColorAt( 1.0, option->palette.color( QPalette::Dark ) );
QBrush b( g );
painter->setBrush( b );
painter->setPen( QPen( Qt::NoPen ) );
painter->drawRect( rect() );
QStyleOptionGraphicsItem opt( *option );
opt.exposedRect = rect().adjusted( -m_start->rect().width(), 0.0, -m_finish->rect().width(), 0.0 );
if ( this == itemScene()->focusItem() ) {
opt.state |= QStyle::State_HasFocus;
}
plan_paintFocusSelectedItem( painter, &opt );
// paint the symbol
m_symbol->paint( itemScene()->project(), painter, &opt );
}
DependencyConnectorItem *DependencyNodeItem::connectorItem( ConnectorType ctype ) const
{
switch ( ctype ) {
case Start: return m_start;
case Finish: return m_finish;
default: break;
}
return 0;
}
QList<DependencyLinkItem*> DependencyNodeItem::predecessorItems( ConnectorType ctype ) const
{
QList<DependencyLinkItem*> lst;
foreach ( DependencyLinkItem *i, m_parentrelations ) {
if ( ctype == Start && ( i->relation->type() == Relation::StartStart || i->relation->type() == Relation::FinishStart ) ) {
lst << i;
}
if ( ctype == Finish && i->relation->type() == Relation::FinishFinish ) {
lst << i;
}
}
return lst;
}
QList<DependencyLinkItem*> DependencyNodeItem::successorItems( ConnectorType ctype ) const
{
QList<DependencyLinkItem*> lst;
foreach ( DependencyLinkItem *i, m_childrelations ) {
if ( ctype == Start && i->relation->type() == Relation::StartStart ) {
lst << i;
}
if ( ctype == Finish && ( i->relation->type() == Relation::FinishFinish || i->relation->type() == Relation::FinishStart ) ) {
lst << i;
}
}
return lst;
}
qreal DependencyNodeItem::treeIndicatorX() const
{
return rect().x() + 18.0;
}
void DependencyNodeItem::setTreeIndicator( bool on )
{
paintTreeIndicator( on );
foreach ( DependencyNodeItem *i, m_children ) {
if ( i->isVisible() ) {
i->setTreeIndicator( on );
}
}
}
void DependencyNodeItem::paintTreeIndicator( bool on )
{
if ( ! on ) {
m_treeIndicator->hide();
return;
}
QPainterPath p;
qreal y1 = itemScene()->gridY( row() );
qreal y2 = itemScene()->gridY( row() + 1 );
for ( DependencyNodeItem *par = m_parent; par; par = par->parentItem() ) {
qreal x = par->treeIndicatorX();
p.moveTo( x, y1 );
if ( par == m_parent ) {
p.lineTo( x, (y1 + y2) / 2.0 );
p.lineTo( x + 6, (y1 + y2) / 2.0 );
if ( m_node->siblingAfter() ) {
p.moveTo( x, (y1 + y2) / 2.0 );
p.lineTo( x, y2 );
}
} else {
const QList<DependencyNodeItem*> &children = par->children();
if ( children.last()->rect().y() > rect().y() ) {
p.lineTo( x, (y1 + y2) / 2.0 );
p.lineTo( x, y2 );
}
}
}
if ( ! m_children.isEmpty() ) {
qreal x = treeIndicatorX();
qreal y = rect().bottom();
p.moveTo( x, y );
p.lineTo( x, itemScene()->gridY( row() + 1 ) );
}
if ( p.isEmpty() ) {
m_treeIndicator->hide();
} else {
m_treeIndicator->setPath( p );
m_treeIndicator->show();
}
//debugPlanDepEditor<<text()<<rect()<<p;
}
//--------------------
void DependencyNodeSymbolItem::setSymbol( int type, const QRectF &rect )
{
m_nodetype = type;
m_itemtype = KGantt::TypeNone;
QPainterPath p;
switch ( type ) {
case Node::Type_Summarytask:
m_itemtype = KGantt::TypeSummary;
p.moveTo( rect.topLeft() );
p.lineTo( rect.topRight() );
p.lineTo( rect.left() + rect.width() / 2.0, rect.bottom() );
p.closeSubpath();
break;
case Node::Type_Task:
m_itemtype = KGantt::TypeTask;
p.moveTo( rect.topLeft() );
p.lineTo( rect.topRight() );
p.lineTo( rect.bottomRight() );
p.lineTo( rect.bottomLeft() );
p.closeSubpath();
break;
case Node::Type_Milestone:
m_itemtype = KGantt::TypeEvent;
p.moveTo( rect.left() + ( rect.width() / 2.0 ), rect.top() );
p.lineTo( rect.right(), rect.top() + ( rect.height() / 2.0 ) );
p.lineTo( rect.left() + ( rect.width() / 2.0 ), rect.bottom() );
p.lineTo( rect.left(), rect.top() + ( rect.height() / 2.0 ) );
p.closeSubpath();
break;
default:
break;
}
setPath( p );
}
void DependencyNodeSymbolItem::paint( Project *p, QPainter *painter, const QStyleOptionGraphicsItem *option )
{
if ( p ) {
switch ( m_nodetype ) {
case Node::Type_Summarytask:
painter->setBrush( p->config().summaryTaskDefaultColor() );
break;
case Node::Type_Task:
painter->setBrush( p->config().taskNormalColor() );
break;
case Node::Type_Milestone:
painter->setBrush( p->config().milestoneNormalColor() );
break;
default:
painter->setBrush( m_delegate.defaultBrush( m_itemtype ) );
break;
}
} else {
painter->setBrush( m_delegate.defaultBrush( m_itemtype ) );
}
painter->setPen( Qt::NoPen );
painter->translate( option->exposedRect.x() + 2.0, option->exposedRect.y() + 2.0 );
painter->drawPath( path() );
}
//--------------------
DependencyScene::DependencyScene( QWidget *parent )
: QGraphicsScene( parent ),
m_model( 0 ),
m_readwrite( false )
{
setSceneRect( QRectF() );
m_connectionitem = new DependencyCreatorItem();
addItem( m_connectionitem );
//debugPlanDepEditor;
m_connectionitem->hide();
}
DependencyScene::~DependencyScene()
{
//debugPlanDepEditor<<" DELETED";
clearScene();
}
void DependencyScene::setFromItem( DependencyConnectorItem *item )
{
DependencyConnectorItem *old = fromItem();
m_connectionitem->clear();
if ( old && old->parentItem() ) {
old->parentItem()->update();
}
if ( item ) {
foreach ( QGraphicsItem *i, items() ) {
if ( i != m_connectionitem && i->type() != DependencyConnectorItem::Type ) {
i->setAcceptHoverEvents( false );
if ( i->type() == DependencyLinkItem::Type ) {
static_cast<DependencyLinkItem*>( i )->resetHooverIndication();
}
}
}
item->setCursor( ConnectCursor );
m_connectionitem->setPredConnector( item );
m_connectionitem->show();
} else {
foreach ( QGraphicsItem *i, items() ) {
if ( i != m_connectionitem && i->type() != DependencyConnectorItem::Type )
i->setAcceptHoverEvents( true );
}
}
if ( item && item->parentItem() ) {
item->parentItem()->update();
}
}
bool DependencyScene::connectionIsValid( DependencyConnectorItem *pred, DependencyConnectorItem *succ )
{
if ( pred->ctype() == DependencyNodeItem::Start && succ->ctype() == DependencyNodeItem::Finish ) {
return false;
}
Node *par = static_cast<DependencyNodeItem*>( pred->parentItem() )->node();
Node *ch = static_cast<DependencyNodeItem*>( succ->parentItem() )->node();
return m_project->linkExists( par, ch ) || m_project->legalToLink( par, ch );
}
void DependencyScene::connectorEntered( DependencyConnectorItem *item, bool entered )
{
//debugPlanDepEditor<<entered;
item->setCursor( ConnectCursor );
if ( ! entered ) {
// when we leave a connector we don't have a successor
m_connectionitem->setSuccConnector( 0 );
return;
}
if ( m_connectionitem->predConnector == item ) {
// when inside the predecessor, clicking is allowed (deselects connector)
item->setCursor( ConnectCursor );
return;
}
if ( ! m_connectionitem->isVisible() ) {
// we are not in connection mode
return;
}
if ( m_connectionitem->predConnector == 0 ) {
// nothing we can do if we don't have a predecessor (shouldn't happen)
return;
}
if ( item->parentItem() == m_connectionitem->predConnector->parentItem() ) {
// not allowed to connect to the same node
item->setCursor( Qt::ForbiddenCursor );
return;
}
if ( ! ( connectionIsValid( m_connectionitem->predConnector, item ) ) ) {
// invalid connection (circular dependency, connecting to parent node, etc)
item->setCursor( Qt::ForbiddenCursor );
return;
}
m_connectionitem->setSuccConnector( item );
m_connectionitem->createPath();
}
void DependencyScene::drawBackground ( QPainter *painter, const QRectF &rect )
{
QGraphicsScene::drawBackground( painter, rect );
QStyleOptionViewItem opt;
QBrush br( opt.palette.brush( QPalette::AlternateBase ) );
int first = row( rect.y() );
int last = row( rect.bottom() );
for ( int r = first; r <= last; ++r ) {
if ( r % 2 == 1 ) {
qreal oy = gridY( r );
QRectF rct( rect.x(), oy, rect.width(), gridHeight() );
painter->fillRect( rct, br );
//debugPlanDepEditor<<r<<": oy="<<oy<<""<<rct;
}
}
}
QList<QGraphicsItem*> DependencyScene::itemList( int type ) const
{
QList<QGraphicsItem*> lst;
foreach ( QGraphicsItem *i, items() ) {
if ( i->type() == type ) {
lst << i;
}
}
return lst;
}
void DependencyScene::clearScene()
{
m_connectionitem->clear();
QList<QGraphicsItem*> its, deps;
foreach ( QGraphicsItem *i, items() ) {
if ( i->type() == DependencyNodeItem::Type && i->parentItem() == 0 ) {
its << i;
} else if ( i->type() == DependencyLinkItem::Type ) {
deps << i;
}
}
qDeleteAll( deps );
qDeleteAll( its );
removeItem( m_connectionitem );
qDeleteAll( items() );
setSceneRect( QRectF() );
addItem( m_connectionitem );
//debugPlanDepEditor;
}
QList<DependencyNodeItem*> DependencyScene::removeChildItems( DependencyNodeItem *item )
{
QList<DependencyNodeItem*> lst;
foreach ( DependencyNodeItem *i, item->children() ) {
m_allItems.removeAt( m_allItems.indexOf( i ) );
lst << i;
lst += removeChildItems( i );
}
return lst;
}
void DependencyScene::moveItem( DependencyNodeItem *item, const QList<Node*> &lst )
{
//debugPlanDepEditor<<item->text();
int idx = m_allItems.indexOf( item );
int ndx = lst.indexOf( item->node() );
Q_ASSERT( idx != -1 && ndx != -1 );
Node *oldParent = item->parentItem() == 0 ? 0 : item->parentItem()->node();
Node *newParent = item->node()->parentNode();
if ( newParent == m_project ) {
newParent = 0;
} else debugPlanDepEditor<<newParent->name()<<newParent->level();
if ( idx != ndx || oldParent != newParent ) {
// If I have children, these must be moved too.
QList<DependencyNodeItem*> items = removeChildItems( item );
m_allItems.removeAt( idx );
m_allItems.insert( ndx, item );
item->setParentItem( m_allItems.value( lst.indexOf( newParent ) ) );
item->setColumn();
//debugPlanDepEditor<<item->text()<<":"<<idx<<"->"<<ndx<<", "<<item->column()<<r;
if ( ! items.isEmpty() ) {
foreach ( DependencyNodeItem *i, items ) {
m_allItems.insert( ++ndx, i );
i->setColumn();
//debugPlanDepEditor<<i->text()<<": ->"<<ndx<<", "<<i->column()<<r;
}
}
}
}
void DependencyScene::setItemVisible( DependencyNodeItem *item, bool show )
{
//debugPlanDepEditor<<"Visible count="<<m_visibleItems.count()<<" total="<<m_allItems.count();
item->setItemVisible( show );
int row = m_allItems.indexOf( item );
if ( row == -1 ) {
debugPlanDepEditor<<"Unknown item!!";
return;
}
if (show && CONTAINS(m_hiddenItems, item)) {
moveItem( item, m_project->flatNodeList() ); // might have been moved
}
m_hiddenItems.clear();
m_visibleItems.clear();
int viewrow = 0;
for ( int i = 0; i < m_allItems.count(); ++i ) {
DependencyNodeItem *itm = m_allItems[ i ];
if ( itm->isVisible() ) {
m_visibleItems.insert( i, itm );
//debugPlanDepEditor<<itm->text()<<":"<<i<<viewrow;
itm->setRow( viewrow );
++viewrow;
} else {
m_hiddenItems.insert( i, itm );
}
}
}
DependencyNodeItem *DependencyScene::findPrevItem( Node *node ) const
{
if ( node->numChildren() == 0 ) {
return findItem( node );
}
return findPrevItem( node->childNodeIterator().last() );
}
DependencyNodeItem *DependencyScene::itemBefore( DependencyNodeItem *parent, Node *node ) const
{
Node *sib = node->siblingBefore();
DependencyNodeItem *bef = parent;
if ( sib ) {
bef = findPrevItem( sib );
}
return bef;
}
DependencyNodeItem *DependencyScene::createItem( Node *node )
{
DependencyNodeItem *parent = findItem( node->parentNode() );
DependencyNodeItem *after = itemBefore( parent, node );
int i = m_allItems.count()-1;
if ( after ) {
i = m_allItems.indexOf( after );
//debugPlanDepEditor<<"after="<<after->node()->name()<<" pos="<<i;
}
DependencyNodeItem *item = new DependencyNodeItem( node, parent );
if ( item->scene() != this ) {
addItem( item );
}
item->setEditable( m_readwrite );
item->startConnector()->setEditable( m_readwrite );
item->finishConnector()->setEditable( m_readwrite );
//debugPlanDepEditor<<item->text()<<item;
int col = 0;
if ( parent ) {
col += parent->column() + 1;
}
item->setRectangle( QRectF( itemX( col ), itemY(), itemWidth(), itemHeight() ) );
m_allItems.insert( i+1, item );
setItemVisible( item, true );
return item;
}
DependencyLinkItem *DependencyScene::findItem( const Relation* rel ) const
{
foreach ( QGraphicsItem *i, itemList( DependencyLinkItem::Type ) ) {
if ( static_cast<DependencyLinkItem*>( i )->relation == rel ) {
return static_cast<DependencyLinkItem*>( i );
}
}
return 0;
}
DependencyLinkItem *DependencyScene::findItem( const DependencyConnectorItem *c1, const DependencyConnectorItem *c2, bool exact ) const
{
DependencyNodeItem *n1 = c1->nodeItem();
DependencyNodeItem *n2 = c2->nodeItem();
foreach ( QGraphicsItem *i, itemList( DependencyLinkItem::Type ) ) {
DependencyLinkItem *link = static_cast<DependencyLinkItem*>( i );
if ( link->predItem == n1 && link->succItem == n2 ) {
switch ( link->relation->type() ) {
case Relation::StartStart:
if ( c1->ctype() == DependencyNodeItem::Start && c2->ctype() == DependencyNodeItem::Start ) {
return link;
}
break;
case Relation::FinishStart:
if ( c1->ctype() == DependencyNodeItem::Finish && c2->ctype() == DependencyNodeItem::Start ) {
return link;
}
break;
case Relation::FinishFinish:
if ( c1->ctype() == DependencyNodeItem::Finish && c2->ctype() == DependencyNodeItem::Finish ) {
return link;
}
break;
default:
break;
}
return 0;
}
if ( link->predItem == n2 && link->succItem == n1 ) {
if ( exact ) {
return 0;
}
switch ( link->relation->type() ) {
case Relation::StartStart:
if ( c2->ctype() == DependencyNodeItem::Start && c1->ctype() == DependencyNodeItem::Start ) {
return link;
}
break;
case Relation::FinishStart:
if ( c2->ctype() == DependencyNodeItem::Finish && c1->ctype() == DependencyNodeItem::Start ) {
return link;
}
break;
case Relation::FinishFinish:
if ( c2->ctype() == DependencyNodeItem::Finish && c1->ctype() == DependencyNodeItem::Finish ) {
return link;
}
break;
default:
break;
}
return 0;
}
}
return 0;
}
DependencyNodeItem *DependencyScene::findItem( const Node *node ) const
{
foreach ( QGraphicsItem *i, itemList( DependencyNodeItem::Type ) ) {
if ( static_cast<DependencyNodeItem*>( i )->node() == node ) {
return static_cast<DependencyNodeItem*>( i );
}
}
return 0;
}
void DependencyScene::createLinks()
{
foreach ( DependencyNodeItem *i, m_allItems ) {
createLinks( i );
}
}
void DependencyScene::createLinks( DependencyNodeItem *item )
{
foreach ( Relation *rel, item->node()->dependChildNodes() ) {
createLink( item, rel );
}
}
void DependencyScene::createLink( DependencyNodeItem *parent, Relation *rel )
{
DependencyNodeItem *child = findItem( rel->child() );
if ( parent == 0 || child == 0 ) {
return;
}
DependencyLinkItem *dep = new DependencyLinkItem( parent, child, rel );
dep->setEditable( m_readwrite );
addItem( dep );
//debugPlanDepEditor;
dep->createPath();
}
void DependencyScene::mouseMoveEvent( QGraphicsSceneMouseEvent *mouseEvent )
{
if ( m_connectionitem->isVisible() ) {
int x = qMin( qMax( sceneRect().left() + 2, mouseEvent->scenePos().x() ), sceneRect().right() - 4 );
int y = qMin( qMax( sceneRect().top() + 2, mouseEvent->scenePos().y() ), sceneRect().bottom() - 4 );
m_connectionitem->createPath( QPoint( x, y ) );
}
QGraphicsScene::mouseMoveEvent( mouseEvent );
//debugPlanDepEditor<<mouseEvent->scenePos()<<","<<mouseEvent->isAccepted();
}
void DependencyScene::keyPressEvent( QKeyEvent *keyEvent )
{
//debugPlanDepEditor<<focusItem();
if ( m_visibleItems.isEmpty() ) {
return QGraphicsScene::keyPressEvent( keyEvent );
}
QGraphicsItem *fitem = focusItem();
if ( fitem == 0 ) {
setFocusItem( m_visibleItems.first() );
if ( focusItem() ) {
focusItem()->update();
}
emit focusItemChanged( focusItem() );
return;
}
switch ( keyEvent->key() ) {
case Qt::Key_Left: {
if ( fitem->type() == DependencyNodeItem::Type ) {
DependencyConnectorItem *item = static_cast<DependencyNodeItem*>( fitem )->startConnector();
if ( item ) {
setFocusItem( item );
}
} else if ( fitem->type() == DependencyConnectorItem::Type ) {
DependencyConnectorItem *citem = static_cast<DependencyConnectorItem*>( fitem );
if ( citem->ctype() == DependencyNodeItem::Start ) {
//Goto prev nodes finishConnector
DependencyNodeItem *nitem = static_cast<DependencyNodeItem*>( citem->parentItem() );
DependencyNodeItem *item = nodeItem( nitem->row() - 1 );
if ( item ) {
setFocusItem( item->finishConnector() );
}
} else {
// Goto node item (parent)
setFocusItem( citem->parentItem() );
}
}
break;
}
case Qt::Key_Right: {
if ( fitem->type() == DependencyNodeItem::Type ) {
DependencyConnectorItem *item = static_cast<DependencyNodeItem*>( fitem )->finishConnector();
if ( item ) {
setFocusItem( item );
}
} else if ( fitem->type() == DependencyConnectorItem::Type ) {
DependencyConnectorItem *citem = static_cast<DependencyConnectorItem*>( fitem );
if ( citem->ctype() == DependencyNodeItem::Finish ) {
//Goto prev nodes startConnector
DependencyNodeItem *nitem = static_cast<DependencyNodeItem*>( citem->parentItem() );
DependencyNodeItem *item = nodeItem( nitem->row() + 1 );
if ( item ) {
setFocusItem( item->startConnector() );
}
} else {
// Goto node item (parent)
setFocusItem( citem->parentItem() );
}
}
break;
}
case Qt::Key_Up: {
if ( fitem->type() == DependencyNodeItem::Type ) {
DependencyNodeItem *item = nodeItem( static_cast<DependencyNodeItem*>( fitem )->row() - 1 );
if ( item ) {
setFocusItem( item );
}
} else if ( fitem->type() == DependencyConnectorItem::Type ) {
DependencyConnectorItem *citem = static_cast<DependencyConnectorItem*>( fitem );
DependencyNodeItem *nitem = static_cast<DependencyNodeItem*>( citem->parentItem() );
if ( citem->ctype() == DependencyNodeItem::Finish ) {
DependencyNodeItem *item = nodeItem( nitem->row() - 1 );
if ( item ) {
setFocusItem( item->finishConnector() );
}
} else {
DependencyNodeItem *item = nodeItem( static_cast<DependencyNodeItem*>( fitem )->row() - 1 );
if ( item ) {
setFocusItem( item->startConnector() );
}
}
}
break;
}
case Qt::Key_Down: {
if ( fitem->type() == DependencyNodeItem::Type ) {
DependencyNodeItem *item = nodeItem( static_cast<DependencyNodeItem*>( fitem )->row() + 1 );
if ( item ) {
setFocusItem( item );
}
} else if ( fitem->type() == DependencyConnectorItem::Type ) {
DependencyConnectorItem *citem = static_cast<DependencyConnectorItem*>( fitem );
DependencyNodeItem *nitem = static_cast<DependencyNodeItem*>( citem->parentItem() );
if ( citem->ctype() == DependencyNodeItem::Finish ) {
DependencyNodeItem *item = nodeItem( nitem->row() + 1 );
if ( item ) {
setFocusItem( item->finishConnector() );
}
} else {
DependencyNodeItem *item = nodeItem( static_cast<DependencyNodeItem*>( fitem )->row() + 1 );
if ( item ) {
setFocusItem( item->startConnector() );
}
}
}
break;
}
case Qt::Key_Space:
case Qt::Key_Select: {
if ( fitem->type() == DependencyConnectorItem::Type ) {
singleConnectorClicked( static_cast<DependencyConnectorItem*>( fitem ) );
} else if ( fitem->type() == DependencyNodeItem::Type ) {
singleConnectorClicked( 0 );
foreach ( QGraphicsItem *i, selectedItems() ) {
i->setSelected( false );
}
fitem->setSelected( true );
}
return;
}
default:
QGraphicsScene::keyPressEvent( keyEvent );
}
if ( fitem ) {
fitem->parentItem() ? fitem->parentItem()->update() : fitem->update();
}
if ( focusItem() ) {
focusItem()->parentItem() ? focusItem()->parentItem()->update() : focusItem()->update();
}
if ( fitem != focusItem() ) {
emit focusItemChanged( focusItem() );
}
}
DependencyNodeItem *DependencyScene::nodeItem( int row ) const
{
if ( row < 0 || m_visibleItems.isEmpty() ) {
return 0;
}
foreach ( DependencyNodeItem *i, m_visibleItems ) {
if ( i->row() == row ) {
return i;
}
}
return 0;
}
void DependencyScene::singleConnectorClicked( DependencyConnectorItem *item )
{
//debugPlanDepEditor;
clearSelection();
QList<DependencyConnectorItem*> lst;
if ( item == 0 || item == fromItem() ) {
setFromItem( 0 );
m_clickedItems = lst;
} else if ( fromItem() == 0 ) {
setFromItem( item );
} else if ( connectionIsValid( fromItem(), item ) ) {
emit connectItems( fromItem(), item );
setFromItem( 0 );
} else {
setFromItem( 0 );
}
emit connectorClicked( item );
}
void DependencyScene::multiConnectorClicked( DependencyConnectorItem *item )
{
//debugPlanDepEditor;
singleConnectorClicked( item );
}
void DependencyScene::clearConnection()
{
setFromItem( 0 );
m_clickedItems.clear();
}
void DependencyScene::mousePressEvent( QGraphicsSceneMouseEvent *mouseEvent )
{
//debugPlanDepEditor;
QGraphicsScene::mousePressEvent( mouseEvent );
if ( ! mouseEvent->isAccepted() ) {
clearConnection();
}
}
void DependencyScene::mouseDoubleClickEvent ( QGraphicsSceneMouseEvent *event )
{
//debugPlanDepEditor<<event->pos()<<event->scenePos()<<event->screenPos();
QGraphicsScene::mouseDoubleClickEvent( event );
emit itemDoubleClicked( itemAt( event->scenePos(), QTransform() ) );
}
void DependencyScene::contextMenuEvent ( QGraphicsSceneContextMenuEvent *event )
{
if ( event->reason() == QGraphicsSceneContextMenuEvent::Mouse ) {
debugPlanDepEditor<<"Mouse:"<<itemAt( event->scenePos(), QTransform())<<event->pos()<<event->scenePos()<<event->screenPos();
emit contextMenuRequested( itemAt( event->scenePos(), QTransform() ), event->screenPos() );
return;
}
if ( focusItem() ) {
if ( focusItem()->type() == DependencyConnectorItem::Type ) {
DependencyConnectorItem *to = static_cast<DependencyConnectorItem*>( focusItem() );
DependencyConnectorItem *from = fromItem();
debugPlanDepEditor<<"DependencyConnectorItem:"<<from<<to;
if ( from ) {
DependencyLinkItem *link = findItem( from, to );
if ( link ) {
emit dependencyContextMenuRequested( link, to );
setFromItem( 0 ); // avoid showing spurious DependencyCreatorItem
return;
} else debugPlanDepEditor<<"No link";
}
} else debugPlanDepEditor<<"Not connector type"<<focusItem();
} else debugPlanDepEditor<<"No focusItem";
emit contextMenuRequested( focusItem() );
}
void DependencyScene::setReadWrite( bool on )
{
m_readwrite = on;
foreach ( QGraphicsItem *i, items() ) {
if ( i->type() == DependencyConnectorItem::Type ) {
static_cast<DependencyConnectorItem*>( i )->setEditable( on );
} else if ( i->type() == DependencyLinkItem::Type ) {
static_cast<DependencyLinkItem*>( i )->setEditable( on );
}
}
}
//--------------------
DependencyView::DependencyView( QWidget *parent )
: QGraphicsView( parent ),
m_project( 0 ),
m_dirty( false ),
m_active( false )
{
setItemScene( new DependencyScene( this ) );
setAlignment( Qt::AlignLeft | Qt::AlignTop );
connect( scene(), &QGraphicsScene::selectionChanged, this, &DependencyView::slotSelectionChanged );
connect( scene(), SIGNAL(connectItems(DependencyConnectorItem*,DependencyConnectorItem*)), this, SIGNAL(makeConnection(DependencyConnectorItem*,DependencyConnectorItem*)) );
connect( scene(), SIGNAL(contextMenuRequested(QGraphicsItem*)), this, SLOT(slotContextMenuRequested(QGraphicsItem*)) );
connect( scene(), SIGNAL(dependencyContextMenuRequested(DependencyLinkItem*,DependencyConnectorItem*)), this, SLOT(slotDependencyContextMenuRequested(DependencyLinkItem*,DependencyConnectorItem*)) );
connect( scene(), SIGNAL(contextMenuRequested(QGraphicsItem*,QPoint)), this, SIGNAL(contextMenuRequested(QGraphicsItem*,QPoint)) );
connect( itemScene(), SIGNAL(focusItemChanged(QGraphicsItem*)), this, SLOT(slotFocusItemChanged(QGraphicsItem*)) );
m_autoScrollTimer.start( 100 );
connect( &m_autoScrollTimer, &QTimer::timeout, this, &DependencyView::slotAutoScroll );
}
void DependencyView::slotContextMenuRequested( QGraphicsItem *item )
{
if ( item ) {
debugPlanDepEditor<<item<<item->boundingRect()<<(item->mapToScene( item->pos() ).toPoint())<<(mapToGlobal( item->mapToParent( item->pos() ).toPoint()));
emit contextMenuRequested( item, mapToGlobal( item->mapToScene( item->boundingRect().topRight() ).toPoint() ) );
}
}
void DependencyView::slotDependencyContextMenuRequested( DependencyLinkItem *item, DependencyConnectorItem */*connector */)
{
if ( item ) {
debugPlanDepEditor<<item<<item->boundingRect()<<(item->mapToScene( item->pos() ).toPoint())<<(mapToGlobal( item->mapToParent( item->pos() ).toPoint()));
emit contextMenuRequested( item, mapToGlobal( item->mapToScene( item->boundingRect().topRight() ).toPoint() ) );
}
}
void DependencyView::slotConnectorClicked( DependencyConnectorItem *item )
{
if ( itemScene()->fromItem() == 0 ) {
itemScene()->setFromItem( item );
} else {
//debugPlanDepEditor<<"emit makeConnection:"<<static_cast<DependencyNodeItem*>( item->parentItem() )->text();
emit makeConnection( itemScene()->fromItem(), item );
}
}
void DependencyView::slotSelectionChanged()
{
//HACK because of tt bug 160653
QTimer::singleShot(0, this, &DependencyView::slotSelectedItems);
}
void DependencyView::slotSelectedItems()
{
emit selectionChanged( itemScene()->selectedItems() );
}
void DependencyView::slotFocusItemChanged( QGraphicsItem *item )
{
ensureVisible( item, 10, 10 );
}
void DependencyView::setItemScene( DependencyScene *scene )
{
setScene( scene );
scene->setProject( m_project );
//slotResizeScene( m_treeview->viewport()->size() );
if ( m_project ) {
createItems();
}
}
void DependencyView::setActive( bool activate )
{
m_active = activate;
if ( m_active && m_dirty ) {
createItems();
}
}
void DependencyView::setProject( Project *project )
{
if ( m_project ) {
disconnect( m_project, &Project::relationAdded, this, &DependencyView::slotRelationAdded );
disconnect( m_project, &Project::relationRemoved, this, &DependencyView::slotRelationRemoved );
disconnect( m_project, &Project::relationModified, this, &DependencyView::slotRelationModified );
disconnect( m_project, &Project::nodeAdded, this, &DependencyView::slotNodeAdded );
disconnect( m_project, &Project::nodeRemoved, this, &DependencyView::slotNodeRemoved );
disconnect( m_project, &Project::nodeChanged, this, &DependencyView::slotNodeChanged );
disconnect( m_project, &Project::nodeMoved, this, &DependencyView::slotNodeMoved );
if ( itemScene() ) {
itemScene()->clearScene();
}
}
m_project = project;
if ( project ) {
connect( m_project, &Project::relationAdded, this, &DependencyView::slotRelationAdded );
connect( m_project, &Project::relationRemoved, this, &DependencyView::slotRelationRemoved );
connect( m_project, &Project::relationModified, this, &DependencyView::slotRelationModified );
connect( m_project, &Project::nodeAdded, this, &DependencyView::slotNodeAdded );
connect( m_project, &Project::nodeRemoved, this, &DependencyView::slotNodeRemoved );
connect( m_project, &Project::nodeChanged, this, &DependencyView::slotNodeChanged );
connect( m_project, &Project::nodeMoved, this, &DependencyView::slotNodeMoved );
connect( m_project, &Project::wbsDefinitionChanged, this, &DependencyView::slotWbsCodeChanged );
if ( itemScene() ) {
itemScene()->setProject( project );
if ( m_active ) {
createItems();
} else {
m_dirty = true;
}
}
}
}
DependencyLinkItem *DependencyView::findItem( const Relation* rel ) const
{
return itemScene()->findItem( rel );
}
DependencyNodeItem *DependencyView::findItem( const Node *node ) const
{
return itemScene()->findItem( node );
}
void DependencyView::slotRelationAdded( Relation* rel )
{
if ( m_dirty ) {
return;
}
DependencyLinkItem *item = findItem( rel );
if ( item == 0 ) {
DependencyNodeItem *p = findItem( rel->parent() );
DependencyNodeItem *c = findItem( rel->child() );
DependencyLinkItem *r = new DependencyLinkItem( p, c, rel );
scene()->addItem( r );
//debugPlanDepEditor;
r->createPath();
r->setVisible( c->isVisible() && p->isVisible() );
} else debugPlanDepEditor<<"Relation already exists!";
}
void DependencyView::slotRelationRemoved( Relation* rel )
{
if ( m_dirty ) {
return;
}
DependencyLinkItem *item = findItem( rel );
if ( item ) {
scene()->removeItem( item );
delete item;
} else debugPlanDepEditor<<"Relation does not exist!";
}
void DependencyView::slotRelationModified( Relation* rel )
{
//debugPlanDepEditor;
if ( m_dirty ) {
return;
}
slotRelationRemoved( rel );
slotRelationAdded( rel );
}
void DependencyView::slotNodeAdded( Node *node )
{
//debugPlanDepEditor;
if ( m_dirty ) {
return;
}
DependencyNodeItem *item = findItem( node );
if ( item == 0 ) {
item = createItem( node );
} else {
//debugPlanDepEditor<<node->name();
itemScene()->setItemVisible( item, true );
}
ensureVisible( item );
slotWbsCodeChanged();
}
void DependencyView::slotNodeRemoved( Node *node )
{
if ( m_dirty ) {
return;
}
DependencyNodeItem *item = findItem( node );
if ( item ) {
//debugPlanDepEditor<<node->name();
itemScene()->setItemVisible( item, false );
} else debugPlanDepEditor<<"Node does not exist!";
slotWbsCodeChanged();
}
void DependencyView::slotNodeChanged( Node *node )
{
if ( m_dirty ) {
return;
}
DependencyNodeItem *item = findItem( node );
if ( item && item->isVisible() ) {
item->setText();
item->setSymbol();
} else debugPlanDepEditor<<"Node does not exist!";
}
void DependencyView::slotWbsCodeChanged()
{
if ( m_dirty ) {
return;
}
foreach( DependencyNodeItem *i, itemScene()->nodeItems() ) {
if ( i->isVisible() ) {
i->setText();
}
}
}
void DependencyView::slotNodeMoved( Node *node )
{
if ( m_dirty ) {
return;
}
slotNodeRemoved( node );
slotNodeAdded( node );
}
void DependencyView::setItemExpanded( int , bool )
{
}
void DependencyView::createItems()
{
itemScene()->clearScene();
m_dirty = false;
if ( m_project == 0 ) {
return;
}
scene()->addLine( 0.0, 0.0, 1.0, 0.0 );
createItems( m_project );
createLinks();
}
DependencyNodeItem *DependencyView::createItem( Node *node )
{
return itemScene()->createItem( node );
}
void DependencyView::createItems( Node *node )
{
if ( node != m_project ) {
//debugPlanDepEditor<<node->name()<<" ("<<node->numChildren()<<")";
DependencyNodeItem *i = createItem( node );
if ( i == 0 ) {
return;
}
}
foreach ( Node *n, node->childNodeIterator() ) {
createItems( n );
}
}
void DependencyView::createLinks()
{
//debugPlanDepEditor;
itemScene()->createLinks();
}
void DependencyView::keyPressEvent(QKeyEvent *event)
{
if ( event->modifiers() & Qt::ControlModifier ) {
switch ( event->key() ) {
case Qt::Key_Plus:
return scale( 1.1, 1.1 );
case Qt::Key_Minus:
return scale( 0.9, 0.9 );
default: break;
}
}
QGraphicsView::keyPressEvent(event);
}
void DependencyView::mouseMoveEvent( QMouseEvent *mouseEvent )
{
m_cursorPos = mouseEvent->pos();
if ( itemScene()->connectionMode() && itemScene()->mouseGrabberItem() ) {
QPointF spos = mapToScene( m_cursorPos );
Qt::CursorShape c = Qt::ArrowCursor;
foreach ( QGraphicsItem *i, itemScene()->items( spos ) ) {
if ( i->type() == DependencyConnectorItem::Type ) {
if ( i == itemScene()->fromItem() ) {
c = ConnectCursor;
} else {
if ( itemScene()->connectionIsValid( itemScene()->fromItem(), static_cast<DependencyConnectorItem*>( i ) ) ) {
c = ConnectCursor;
} else {
c = Qt::ForbiddenCursor;
}
}
}
}
if ( viewport()->cursor().shape() != c ) {
viewport()->setCursor( c );
}
}
QGraphicsView::mouseMoveEvent( mouseEvent );
//debugPlanDepEditor<<mouseEvent->scenePos()<<","<<mouseEvent->isAccepted();
}
void DependencyView::slotAutoScroll()
{
if ( itemScene()->connectionMode() ) {
ensureVisible( QRectF( mapToScene( m_cursorPos ), QSizeF( 1, 1 ) ), 2, 2 );
}
}
//-----------------------------------
DependencyeditorConfigDialog::DependencyeditorConfigDialog( ViewBase *view, QWidget *p, bool selectPrint)
: KPageDialog(p),
m_view( view )
{
setWindowTitle( i18n("Settings") );
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_headerfooter = ViewBase::createHeaderFooterWidget( view );
m_headerfooter->setOptions( view->printingOptions() );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
KPageWidgetItem *page = addPage( tab, i18n( "Printing" ) );
page->setHeader( i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, &QDialog::accepted, this, &DependencyeditorConfigDialog::slotOk);
}
void DependencyeditorConfigDialog::slotOk()
{
debugPlan;
m_view->setPageLayout( m_pagelayout->pageLayout() );
m_view->setPrintingOptions( m_headerfooter->options() );
}
//--------------------
DependencyEditor::DependencyEditor(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent),
m_currentnode( 0 ),
m_manager( 0 )
{
setupGui();
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new DependencyView( this );
l->addWidget( m_view );
connect( m_view, &DependencyView::makeConnection, this, &DependencyEditor::slotCreateRelation );
connect( m_view, SIGNAL(selectionChanged(QList<QGraphicsItem*>)), this, SLOT(slotSelectionChanged(QList<QGraphicsItem*>)) );
connect( m_view->itemScene(), &DependencyScene::itemDoubleClicked, this, &DependencyEditor::slotItemDoubleClicked );
connect( m_view, &DependencyView::contextMenuRequested, this, &DependencyEditor::slotContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Task Dependency Editor</title>"
"<para>"
"Edit dependencies between tasks."
"Dependencies can be added by dragging a connection area (start or finish)"
" from one task to a connection area of a different task."
" You can edit or delete a dependency using the context menu."
"</para><para>"
"This view supports printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Task_Dependency_Editor_(Graphical)")));
}
void DependencyEditor::updateReadWrite( bool on )
{
m_view->itemScene()->setReadWrite( on );
ViewBase::updateReadWrite( on );
}
void DependencyEditor::slotItemDoubleClicked( QGraphicsItem *item )
{
//debugPlanDepEditor;
if ( ! isReadWrite() ) {
return;
}
if ( item && item->type() == DependencyLinkItem::Type ) {
emit modifyRelation( static_cast<DependencyLinkItem*>( item )->relation );
return;
}
if ( item && item->type() == DependencyNodeItem::Type ) {
emit editNode( static_cast<DependencyNodeItem*>( item )->node() );
return;
}
if ( item && item->type() == DependencyNodeSymbolItem::Type ) {
emit editNode( static_cast<DependencyNodeItem*>( item->parentItem() )->node() );
return;
}
}
void DependencyEditor::slotCreateRelation( DependencyConnectorItem *pred, DependencyConnectorItem *succ )
{
//debugPlanDepEditor;
if ( ! isReadWrite() ) {
return;
}
Node *par = pred->node();
Node *ch = succ->node();
Relation::Type type = Relation::FinishStart;
if ( pred->ctype() == DependencyNodeItem::Start ) {
if ( succ->ctype() == DependencyNodeItem::Start ) {
type = Relation::StartStart;
}
} else {
if ( succ->ctype() == DependencyNodeItem::Start ) {
type = Relation::FinishStart;
} else {
type = Relation::FinishFinish;
}
}
Relation *rel = ch->findRelation( par );
if ( rel == 0 ) {
//debugPlanDepEditor<<"New:"<<par->name()<<" ->"<<ch->name()<<","<<type;
emit addRelation( par, ch, type );
} else if ( rel->type() != type ) {
//debugPlanDepEditor<<"Mod:"<<par->name()<<" ->"<<ch->name()<<","<<type;
emit modifyRelation( rel, type );
}
}
void DependencyEditor::draw( Project &project )
{
m_view->setProject( &project );
}
void DependencyEditor::draw()
{
}
void DependencyEditor::setGuiActive( bool activate )
{
//debugPlanDepEditor<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
m_view->setActive( activate );
/* if ( activate && !m_view->selectionModel()->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}*/
}
void DependencyEditor::slotCurrentChanged( const QModelIndex &, const QModelIndex & )
{
//debugPlanDepEditor<<curr.row()<<","<<curr.column();
slotEnableActions();
}
void DependencyEditor::slotSelectionChanged( const QList<QGraphicsItem*>& )
{
//debugPlanDepEditor<<lst.count();
slotEnableActions();
}
int DependencyEditor::selectedNodeCount() const
{
return selectedNodes().count();
}
QList<Node*> DependencyEditor::selectedNodes() const {
QList<Node*> lst;
foreach ( QGraphicsItem *i, m_view->itemScene()->selectedItems() ) {
if ( i->type() == DependencyNodeItem::Type ) {
lst << static_cast<DependencyNodeItem*>( i )->node();
}
}
return lst;
}
Node *DependencyEditor::selectedNode() const
{
QList<Node*> lst = selectedNodes();
if ( lst.count() != 1 ) {
return 0;
}
return lst.first();
}
Node *DependencyEditor::currentNode() const {
return m_currentnode;
/* Node * n = 0;
QGraphicsItem *i = m_view->itemScene()->focusItem();
if ( i && i->type() == DependencyNodeItem::Type ) {
n = static_cast<DependencyNodeItem*>( i )->node();
}
if ( n == 0 || n->type() == Node::Type_Project ) {
return 0;
}
return n;*/
}
Relation *DependencyEditor::currentRelation() const {
return m_currentrelation;
}
void DependencyEditor::setScheduleManager( ScheduleManager *sm )
{
m_manager = sm;
}
void DependencyEditor::slotContextMenuRequested( QGraphicsItem *item, const QPoint& pos )
{
//debugPlanDepEditor<<item<<","<<pos;
if ( ! isReadWrite() ) {
return;
}
QString name;
if ( item && item->type() == DependencyNodeSymbolItem::Type ) {
item = item->parentItem();
}
if ( item ) {
if ( item->type() == DependencyNodeItem::Type ) {
m_currentnode = static_cast<DependencyNodeItem*>( item )->node();
if ( m_currentnode == 0 ) {
//debugPlanDepEditor<<"No node";
return;
}
bool scheduled = m_manager != 0 && m_currentnode->isScheduled( m_manager->scheduleId() );
switch ( m_currentnode->type() ) {
case Node::Type_Task:
name = scheduled ? "task_popup" : "task_edit_popup";
break;
case Node::Type_Milestone:
name = scheduled ? "taskeditor_milestone_popup" : "task_edit_popup";
break;
case Node::Type_Summarytask:
name = "summarytask_popup";
break;
default:
break;
}
//debugPlanDepEditor<<m_currentnode->name()<<" :"<<pos;
} else if ( item->type() == DependencyLinkItem::Type ) {
m_currentrelation = static_cast<DependencyLinkItem*>( item )->relation;
if ( m_currentrelation ) {
name = "relation_popup";
}
} else if ( item->type() == DependencyConnectorItem::Type ) {
DependencyConnectorItem *c = static_cast<DependencyConnectorItem*>( item );
QList<DependencyLinkItem*> items;
QList<QAction*> actions;
QMenu menu;
foreach ( DependencyLinkItem *i, c->predecessorItems() ) {
items << i;
actions << menu.addAction(koIcon("document-properties"), i->predItem->text());
}
menu.addSeparator();
foreach ( DependencyLinkItem *i, c->successorItems() ) {
items << i;
actions << menu.addAction(koIcon("document-properties"), i->succItem->text());
}
if ( ! actions.isEmpty() ) {
QAction *action = menu.exec( pos );
if ( action && actions.contains( action ) ) {
emit modifyRelation( items[ actions.indexOf( action ) ]->relation );
return;
}
}
}
}
//debugPlanDepEditor<<name;
if ( ! name.isEmpty() ) {
emit requestPopupMenu( name, pos );
} else {
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
m_currentnode = 0;
m_currentrelation = 0;
}
void DependencyEditor::slotEnableActions()
{
updateActionsEnabled( true );
}
void DependencyEditor::updateActionsEnabled( bool on )
{
if ( ! on || ! isReadWrite() ) { //FIXME: read-write is not set properly
menuAddTask->setEnabled( false );
actionAddTask->setEnabled( false );
actionAddMilestone->setEnabled( false );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( false );
return;
}
int selCount = selectedNodeCount();
if ( selCount == 0 ) {
menuAddTask->setEnabled( true );
actionAddTask->setEnabled( true );
actionAddMilestone->setEnabled( true );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( false );
return;
}
Node *n = selectedNode();
if ( n && n->type() != Node::Type_Task && n->type() != Node::Type_Milestone && n->type() != Node::Type_Summarytask ) {
n = 0;
}
if ( selCount == 1 && n == 0 ) {
// only project selected
menuAddTask->setEnabled( true );
actionAddTask->setEnabled( true );
actionAddMilestone->setEnabled( true );
menuAddSubTask->setEnabled( true );
actionAddSubtask->setEnabled( true );
actionAddSubMilestone->setEnabled( true );
actionDeleteTask->setEnabled( false );
return;
}
bool baselined = false;
Project *p = m_view->project();
if ( p && p->isBaselined() ) {
foreach ( Node *n, selectedNodes() ) {
if ( n->isBaselined() ) {
baselined = true;
break;
}
}
}
if ( selCount == 1 ) {
menuAddTask->setEnabled( true );
actionAddTask->setEnabled( true );
actionAddMilestone->setEnabled( true );
menuAddSubTask->setEnabled( ! baselined || n->type() == Node::Type_Summarytask );
actionAddSubtask->setEnabled( ! baselined || n->type() == Node::Type_Summarytask );
actionAddSubMilestone->setEnabled( ! baselined || n->type() == Node::Type_Summarytask );
actionDeleteTask->setEnabled( ! baselined );
return;
}
// selCount > 1
menuAddTask->setEnabled( false );
actionAddTask->setEnabled( false );
actionAddMilestone->setEnabled( false );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( ! baselined );
}
void DependencyEditor::setupGui()
{
KActionCollection *coll = actionCollection();
QString name = "taskeditor_add_list";
menuAddTask = new KActionMenu(koIcon("view-task-add"), i18n("Add Task"), this);
coll->addAction("add_task", menuAddTask );
connect( menuAddTask, &QAction::triggered, this, &DependencyEditor::slotAddTask );
addAction( name, menuAddTask );
actionAddTask = new QAction( i18n("Add Task..."), this);
actionAddTask->setShortcut( Qt::CTRL + Qt::Key_I );
connect( actionAddTask, &QAction::triggered, this, &DependencyEditor::slotAddTask );
menuAddTask->addAction( actionAddTask );
actionAddMilestone = new QAction( i18n("Add Milestone..."), this );
actionAddMilestone->setShortcut( Qt::CTRL + Qt::ALT + Qt::Key_I );
connect( actionAddMilestone, &QAction::triggered, this, &DependencyEditor::slotAddMilestone );
menuAddTask->addAction( actionAddMilestone );
menuAddSubTask = new KActionMenu(koIcon("view-task-child-add"), i18n("Add Sub-Task"), this);
coll->addAction("add_subtask", menuAddTask );
connect( menuAddSubTask, &QAction::triggered, this, &DependencyEditor::slotAddSubtask );
addAction( name, menuAddSubTask );
actionAddSubtask = new QAction( i18n("Add Sub-Task..."), this );
actionAddSubtask->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_I );
connect( actionAddSubtask, &QAction::triggered, this, &DependencyEditor::slotAddSubtask );
menuAddSubTask->addAction( actionAddSubtask );
actionAddSubMilestone = new QAction( i18n("Add Sub-Milestone..."), this );
actionAddSubMilestone->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_I );
connect( actionAddSubMilestone, &QAction::triggered, this, &DependencyEditor::slotAddSubMilestone );
menuAddSubTask->addAction( actionAddSubMilestone );
actionDeleteTask = new QAction(koIcon("edit-delete"), xi18nc("@action", "Delete"), this);
coll->addAction("delete_task", actionDeleteTask );
coll->setDefaultShortcut(actionDeleteTask, Qt::Key_Delete);
connect( actionDeleteTask, &QAction::triggered, this, &DependencyEditor::slotDeleteTask );
addAction( name, actionDeleteTask );
createOptionActions(ViewBase::OptionPrint | ViewBase::OptionPrintPreview | ViewBase::OptionPrintPdf | ViewBase::OptionPrintConfig);
}
void DependencyEditor::slotOptions()
{
debugPlan;
DependencyeditorConfigDialog *dlg = new DependencyeditorConfigDialog( this, this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void DependencyEditor::slotAddTask()
{
//debugPlanDepEditor;
m_currentnode = selectedNode();
emit addTask();
m_currentnode = 0;
}
void DependencyEditor::slotAddMilestone()
{
//debugPlanDepEditor;
m_currentnode = selectedNode(); // sibling
emit addMilestone();
m_currentnode = 0;
}
void DependencyEditor::slotAddSubtask()
{
//debugPlanDepEditor;
m_currentnode = selectedNode();
if ( m_currentnode == 0 ) {
return;
}
emit addSubtask();
m_currentnode = 0;
}
void DependencyEditor::slotAddSubMilestone()
{
debugPlanDepEditor;
m_currentnode = selectedNode();
if ( m_currentnode == 0 ) {
return;
}
emit addSubMilestone();
m_currentnode = 0;
}
void DependencyEditor::edit( const QModelIndex &i )
{
if ( i.isValid() ) {
/* QModelIndex p = m_view->itemModel()->parent( i );
m_view->treeView()->setExpanded( p, true );
m_view->treeView()->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->treeView()->edit( i );*/
}
}
void DependencyEditor::slotDeleteTask()
{
//debugPlanDepEditor;
QList<Node*> lst = selectedNodes();
while ( true ) {
// remove children of selected tasks, as parents delete their children
Node *ch = 0;
foreach ( Node *n1, lst ) {
foreach ( Node *n2, lst ) {
if ( n2->isChildOf( n1 ) ) {
ch = n2;
break;
}
}
if ( ch != 0 ) {
break;
}
}
if ( ch == 0 ) {
break;
}
lst.removeAt( lst.indexOf( ch ) );
}
foreach ( Node* n, lst ) { debugPlanDepEditor<<n->name(); }
emit deleteTaskList( lst );
}
KoPrintJob *DependencyEditor::createPrintJob()
{
DependecyViewPrintingDialog *dia = new DependecyViewPrintingDialog( this, m_view );
dia->printer().setCreator( QString( "Plan %1" ).arg( PLAN_VERSION_STRING ) );
// dia->printer().setFullPage(true); // ignore printer margins
return dia;
}
} // namespace KPlato
diff --git a/src/libs/ui/kptdocumentseditor.cpp b/src/libs/ui/kptdocumentseditor.cpp
index 1b860618..3a80cca2 100644
--- a/src/libs/ui/kptdocumentseditor.cpp
+++ b/src/libs/ui/kptdocumentseditor.cpp
@@ -1,301 +1,302 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen danders@get2net>
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 "kptdocumentseditor.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptdocuments.h"
#include "kptdatetime.h"
#include "kptitemviewsettup.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <QMenu>
#include <QList>
#include <QVBoxLayout>
#include <QAction>
#include <KLocalizedString>
#include <kactioncollection.h>
#include <KoDocument.h>
namespace KPlato
{
//--------------------
DocumentTreeView::DocumentTreeView( QWidget *parent )
: TreeViewBase( parent )
{
// header()->setContextMenuPolicy( Qt::CustomContextMenu );
setStretchLastSection( true );
DocumentItemModel *m = new DocumentItemModel();
setModel( m );
setRootIsDecorated ( false );
setSelectionBehavior( QAbstractItemView::SelectRows );
setSelectionMode( QAbstractItemView::SingleSelection );
createItemDelegates( m );
setAcceptDrops( true );
setDropIndicatorShown( true );
connect( selectionModel(), &QItemSelectionModel::selectionChanged, this, &DocumentTreeView::slotSelectionChanged );
setColumnHidden( DocumentModel::Property_Status, true ); // not used atm
}
Document *DocumentTreeView::currentDocument() const
{
return model()->document( selectionModel()->currentIndex() );
}
QModelIndexList DocumentTreeView::selectedRows() const
{
return selectionModel()->selectedRows();
}
void DocumentTreeView::slotSelectionChanged( const QItemSelection &selected )
{
emit selectionChanged( selected.indexes() );
}
QList<Document*> DocumentTreeView::selectedDocuments() const
{
QList<Document*> lst;
foreach ( const QModelIndex &i, selectionModel()->selectedRows() ) {
Document *doc = model()->document( i );
if ( doc ) {
lst << doc;
}
}
return lst;
}
//-----------------------------------
DocumentsEditor::DocumentsEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
setupGui();
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new DocumentTreeView( this );
l->addWidget( m_view );
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentChanged(QModelIndex)) ); // clazy:exclude=old-style-connect
connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), this, SLOT(slotSelectionChanged(QModelIndexList)) ); // clazy:exclude=old-style-connect
connect( m_view, &TreeViewBase::contextMenuRequested, this, &DocumentsEditor::slotContextMenuRequested );
connect( m_view, SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) ); // clazy:exclude=old-style-connect
}
void DocumentsEditor::updateReadWrite( bool readwrite )
{
debugPlan<<isReadWrite()<<"->"<<readwrite;
ViewBase::updateReadWrite( readwrite );
m_view->setReadWrite( readwrite );
updateActionsEnabled( readwrite );
}
void DocumentsEditor::draw( Documents &docs )
{
m_view->setDocuments( &docs );
}
void DocumentsEditor::draw()
{
}
void DocumentsEditor::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void DocumentsEditor::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
QString name;
if ( index.isValid() ) {
Document *obj = m_view->model()->document( index );
if ( obj ) {
name = "documentseditor_popup";
}
}
m_view->setContextMenuIndex(index);
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
void DocumentsEditor::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
Document *DocumentsEditor::currentDocument() const
{
return m_view->currentDocument();
}
void DocumentsEditor::slotCurrentChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
// slotEnableActions();
}
void DocumentsEditor::slotSelectionChanged( const QModelIndexList &list )
{
debugPlan<<list.count();
updateActionsEnabled( true );
}
void DocumentsEditor::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void DocumentsEditor::updateActionsEnabled( bool on )
{
QList<Document*> lst = m_view->selectedDocuments();
if ( lst.isEmpty() || lst.count() > 1 ) {
actionEditDocument->setEnabled( false );
actionViewDocument->setEnabled( false );
return;
}
Document *doc = lst.first();
actionViewDocument->setEnabled( on );
actionEditDocument->setEnabled( on && doc->type() == Document::Type_Product && isReadWrite() );
}
void DocumentsEditor::setupGui()
{
QString name = "documentseditor_edit_list";
actionEditDocument = new QAction(koIcon("document-properties"), i18n("Edit..."), this);
actionCollection()->addAction("edit_documents", actionEditDocument );
// actionCollection()->setDefaultShortcut(actionEditDocument, Qt::CTRL + Qt::SHIFT + Qt::Key_I);
connect( actionEditDocument, &QAction::triggered, this, &DocumentsEditor::slotEditDocument );
addAction( name, actionEditDocument );
actionViewDocument = new QAction(koIcon("document-preview"), xi18nc("@action View a document", "View..."), this);
actionCollection()->addAction("view_documents", actionViewDocument );
// actionCollection()->setDefaultShortcut(actionViewDocument, Qt::CTRL + Qt::SHIFT + Qt::Key_I);
connect( actionViewDocument, &QAction::triggered, this, &DocumentsEditor::slotViewDocument );
addAction( name, actionViewDocument );
/* actionDeleteSelection = new QAction(koIcon("edit-delete"), i18n("Delete"), this);
actionCollection()->addAction("delete_selection", actionDeleteSelection );
actionCollection()->setDefaultShortcut(actionDeleteSelection, Qt::Key_Delete);
connect( actionDeleteSelection, SIGNAL(triggered(bool)), SLOT(slotDeleteSelection()) );
addAction( name, actionDeleteSelection );*/
// Add the context menu actions for the view options
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig);
}
void DocumentsEditor::slotOptions()
{
debugPlan;
ItemViewSettupDialog dlg( this, m_view/*->masterView()*/ );
dlg.exec();
}
void DocumentsEditor::slotEditDocument()
{
QList<Document*> dl = m_view->selectedDocuments();
if ( dl.isEmpty() ) {
return;
}
debugPlan<<dl;
emit editDocument( dl.first() );
}
void DocumentsEditor::slotViewDocument()
{
QList<Document*> dl = m_view->selectedDocuments();
if ( dl.isEmpty() ) {
return;
}
debugPlan<<dl;
emit viewDocument( dl.first() );
}
void DocumentsEditor::slotAddDocument()
{
//debugPlan;
QList<Document*> dl = m_view->selectedDocuments();
Document *after = 0;
if ( dl.count() > 0 ) {
after = dl.last();
}
Document *doc = new Document();
QModelIndex i = m_view->model()->insertDocument( doc, after );
if ( i.isValid() ) {
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}
}
void DocumentsEditor::slotDeleteSelection()
{
QList<Document*> lst = m_view->selectedDocuments();
//debugPlan<<lst.count()<<" objects";
if ( ! lst.isEmpty() ) {
emit deleteDocumentList( lst );
}
}
bool DocumentsEditor::loadContext( const KoXmlElement &context )
{
return m_view->loadContext( m_view->model()->columnMap(), context );
}
void DocumentsEditor::saveContext( QDomElement &context ) const
{
m_view->saveContext( m_view->model()->columnMap(), context );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptdocumentspanel.cpp b/src/libs/ui/kptdocumentspanel.cpp
index c9e21c83..44a673c6 100644
--- a/src/libs/ui/kptdocumentspanel.cpp
+++ b/src/libs/ui/kptdocumentspanel.cpp
@@ -1,237 +1,238 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptdocumentspanel.h"
#include "kptdocumentseditor.h"
#include "kptdocumentmodel.h"
#include "kptnode.h"
#include "kptcommand.h"
#include "kptdebug.h"
#include <QDialog>
#include <QString>
#include <QModelIndex>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <kurlrequesterdialog.h>
#include <kmessagebox.h>
namespace KPlato
{
DocumentsPanel::DocumentsPanel( Node &node, QWidget *parent )
: QWidget( parent ),
m_node( node ),
m_docs( node.documents() )
{
widget.setupUi( this );
QVBoxLayout *l = new QVBoxLayout( widget.itemViewHolder );
m_view = new DocumentTreeView( widget.itemViewHolder );
l->setMargin(0);
l->addWidget( m_view );
m_view->setDocuments( &m_docs );
m_view->setReadWrite( true );
currentChanged( QModelIndex() );
foreach ( Document *doc, m_docs.documents() ) {
m_orgurl.insert( doc, doc->url() );
}
connect( widget.pbAdd, &QAbstractButton::clicked, this, &DocumentsPanel::slotAddUrl );
connect( widget.pbChange, &QAbstractButton::clicked, this, &DocumentsPanel::slotChangeUrl );
connect( widget.pbRemove, &QAbstractButton::clicked, this, &DocumentsPanel::slotRemoveUrl );
connect( widget.pbView, &QAbstractButton::clicked, this, &DocumentsPanel::slotViewUrl );
connect( m_view->model(), &QAbstractItemModel::dataChanged, this, &DocumentsPanel::dataChanged );
connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), SLOT(slotSelectionChanged(QModelIndexList)) ); // clazy:exclude=old-style-connect
}
DocumentItemModel *DocumentsPanel::model() const
{
return m_view->model();
}
void DocumentsPanel::dataChanged( const QModelIndex &index )
{
Document *doc = m_docs.value( index.row() );
if ( doc == 0 ) {
return;
}
m_state.insert( doc, (State)( m_state[ doc ] | Modified ) );
emit changed();
debugPlan<<index<<doc<<m_state[ doc ];
}
void DocumentsPanel::slotSelectionChanged( const QModelIndexList & )
{
QModelIndexList list = m_view->selectedRows();
debugPlan<<list;
widget.pbChange->setEnabled( list.count() == 1 );
widget.pbRemove->setEnabled( ! list.isEmpty() );
widget.pbView->setEnabled( false ); //TODO
}
void DocumentsPanel::currentChanged( const QModelIndex &index )
{
widget.pbChange->setEnabled( index.isValid() );
widget.pbRemove->setEnabled( index.isValid() );
widget.pbView->setEnabled( false ); //TODO
}
Document *DocumentsPanel::selectedDocument() const
{
QList<Document*> lst = m_view->selectedDocuments();
return lst.isEmpty() ? 0 : lst.first();
}
void DocumentsPanel::slotAddUrl()
{
QPointer<KUrlRequesterDialog> dlg = new KUrlRequesterDialog( QUrl(), QString(), this );
dlg->setWindowTitle( xi18nc( "@title:window", "Attach Document" ) );
if ( dlg->exec() == QDialog::Accepted && dlg ) {
if ( m_docs.findDocument( dlg->selectedUrl() ) ) {
warnPlan<<"Document (url) already exists: "<<dlg->selectedUrl();
KMessageBox::sorry( this, xi18nc( "@info", "Document is already attached:<br/><filename>%1</filename>", dlg->selectedUrl().toDisplayString() ), xi18nc( "@title:window", "Cannot Attach Document" ) );
} else {
Document *doc = new Document( dlg->selectedUrl() );
//DocumentAddCmd *cmd = new DocumentAddCmd( m_docs, doc, kundo2_i18n( "Add document" ) );
//m_cmds.push( cmd );
m_docs.addDocument( doc );
m_state.insert( doc, Added );
model()->setDocuments( &m_docs ); // refresh
emit changed();
}
}
delete dlg;
}
void DocumentsPanel::slotChangeUrl()
{
Document *doc = selectedDocument();
if ( doc == 0 ) {
return slotAddUrl();
}
KUrlRequesterDialog *dlg = new KUrlRequesterDialog( doc->url(), QString(), this );
dlg->setWindowTitle( xi18nc( "@title:window", "Modify Url" ) );
if ( dlg->exec() == QDialog::Accepted ) {
if ( doc->url() != dlg->selectedUrl() ) {
if ( m_docs.findDocument( dlg->selectedUrl() ) ) {
warnPlan<<"Document url already exists";
KMessageBox::sorry( this, i18n( "Document url already exists: %1", dlg->selectedUrl().toDisplayString() ), i18n( "Cannot Modify Url" ) );
} else {
debugPlan<<"Modify url: "<<doc->url()<<" : "<<dlg->selectedUrl();
doc->setUrl( dlg->selectedUrl() );
m_state.insert( doc, (State)( m_state[ doc ] | Modified ) );
model()->setDocuments( &m_docs );
emit changed();
debugPlan<<"State: "<<doc->url()<<" : "<<m_state[ doc ];
}
}
}
delete dlg;
}
void DocumentsPanel::slotRemoveUrl()
{
QList<Document*> lst = m_view->selectedDocuments();
bool mod = false;
foreach ( Document *doc, lst ) {
if ( doc == 0 ) {
continue;
}
m_docs.takeDocument( doc );
if ( m_state.contains( doc ) && m_state[ doc ] & Added ) {
m_state.remove( doc );
} else {
m_state.insert( doc, Removed );
}
mod = true;
}
if ( mod ) {
model()->setDocuments( &m_docs ); // refresh
emit changed();
}
}
void DocumentsPanel::slotViewUrl()
{
}
MacroCommand *DocumentsPanel::buildCommand()
{
if ( m_docs == m_node.documents() ) {
debugPlan<<"No changes to save";
return 0;
}
Documents &docs = m_node.documents();
Document *d = 0;
KUndo2MagicString txt = kundo2_i18n( "Modify documents" );
MacroCommand *m = 0;
QMap<Document*, State>::const_iterator i = m_state.constBegin();
for ( ; i != m_state.constEnd(); ++i) {
debugPlan<<i.key()<<i.value();
if ( i.value() & Removed ) {
d = docs.findDocument( m_orgurl[ i.key() ] );
Q_ASSERT( d );
if ( m == 0 ) m = new MacroCommand( txt );
debugPlan<<"remove document "<<i.key();
m->addCommand( new DocumentRemoveCmd( m_node.documents(), d, kundo2_i18n( "Remove document" ) ) );
} else if ( ( i.value() & Added ) == 0 && i.value() & Modified ) {
d = docs.findDocument( m_orgurl[ i.key() ] );
Q_ASSERT( d );
// do plain modifications before additions
debugPlan<<"modify document "<<d;
if ( i.key()->url() != d->url() ) {
if ( m == 0 ) m = new MacroCommand( txt );
m->addCommand( new DocumentModifyUrlCmd( d, i.key()->url(), kundo2_i18n( "Modify document url" ) ) );
}
if ( i.key()->type() != d->type() ) {
if ( m == 0 ) m = new MacroCommand( txt );
m->addCommand( new DocumentModifyTypeCmd( d, i.key()->type(), kundo2_i18n( "Modify document type" ) ) );
}
if ( i.key()->status() != d->status() ) {
if ( m == 0 ) m = new MacroCommand( txt );
m->addCommand( new DocumentModifyStatusCmd( d, i.key()->status(), kundo2_i18n( "Modify document status" ) ) );
}
if ( i.key()->sendAs() != d->sendAs() ) {
if ( m == 0 ) m = new MacroCommand( txt );
m->addCommand( new DocumentModifySendAsCmd( d, i.key()->sendAs(), kundo2_i18n( "Modify document send control" ) ) );
}
if ( i.key()->name() != d->name() ) {
if ( m == 0 ) m = new MacroCommand( txt );
m->addCommand( new DocumentModifyNameCmd( d, i.key()->name()/*, kundo2_i18n( "Modify document name" )*/ ) );
}
} else if ( i.value() & Added ) {
if ( m == 0 ) m = new MacroCommand( txt );
debugPlan<<i.key()<<m_docs.documents();
d = m_docs.takeDocument( i.key() );
debugPlan<<"add document "<<d;
m->addCommand( new DocumentAddCmd( docs, d, kundo2_i18n( "Add document" ) ) );
}
}
return m;
}
} //namespace KPlato
diff --git a/src/libs/ui/kptganttitemdelegate.cpp b/src/libs/ui/kptganttitemdelegate.cpp
index 57468158..e8ba9790 100644
--- a/src/libs/ui/kptganttitemdelegate.cpp
+++ b/src/libs/ui/kptganttitemdelegate.cpp
@@ -1,1033 +1,1034 @@
/* This file is part of the KDE project
Copyright (C) 2008 - 2011 Dag Andersen <danders@get2net.dk>
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 "kptganttitemdelegate.h"
#include "kptnodeitemmodel.h"
#include "kptnode.h"
#include "kptresourceappointmentsmodel.h"
#include "kptdebug.h"
#include <QModelIndex>
#include <QApplication>
#include <QPainter>
#include <QLocale>
#include <KLocalizedString>
#include <KGanttGlobal>
#include <KGanttStyleOptionGanttItem>
#include <KGanttConstraint>
#include <KGanttAbstractGrid>
/// The main namespace
namespace KPlato
{
// convert horizontal alignment from KGantt::StyleOptionGanttItem to Qt
// assuming only Left/Center/Right are used
static Qt::Alignment qAlignment( KGantt::StyleOptionGanttItem::Position kganttPosition)
{
return
(kganttPosition == KGantt::StyleOptionGanttItem::Left) ? Qt::AlignLeft :
(kganttPosition == KGantt::StyleOptionGanttItem::Right) ? Qt::AlignRight :
/* KGantt::StyleOptionGanttItem::Center*/ Qt::AlignCenter;
}
GanttItemDelegate::GanttItemDelegate( QObject *parent )
: KGantt::ItemDelegate( parent ),
showResources( false ),
showTaskName( true ),
showTaskLinks( true ),
showProgress( false ),
showPositiveFloat( false ),
showNegativeFloat( false ),
showCriticalPath( false ),
showCriticalTasks( false ),
showAppointments( false ),
showTimeConstraint( false ),
showSchedulingError( false ),
m_constraintXOffset(0.0)
{
QLinearGradient b( 0., 0., 0., QApplication::fontMetrics().height() );
b.setColorAt( 0., Qt::red );
b.setColorAt( 1., Qt::darkRed );
m_criticalBrush = QBrush( b );
QLinearGradient sb( 0., 0., 0., QApplication::fontMetrics().height() );
sb.setColorAt( 0., Qt::yellow );
sb.setColorAt( 1., Qt::darkYellow );
m_schedulingErrorBrush = QBrush( sb );
}
QString GanttItemDelegate::toolTip( const QModelIndex &idx ) const
{
if ( !idx.isValid() || ! idx.model() ) {
return QString();
}
const QAbstractItemModel* model = idx.model();
if ( data( idx, NodeModel::NodeFinished, Qt::EditRole ).toBool() ) {
// finished
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Actual finish: %2<nl/>"
"Planned finish: %3<nl/>"
"Status: %4",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeActualFinish, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeEndTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStatus, Qt::DisplayRole ).toString() );
}
if ( data( idx, NodeModel::NodeStarted, Qt::EditRole ).toBool() ) {
// started
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Completion: %2 %<nl/>"
"Actual start: %3<nl/>"
"Planned: %4 - %5<nl/>"
"Status: %6",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeCompleted, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeActualStart, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeEndTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStatus, Qt::DisplayRole ).toString() );
}
// Planned
KGantt::StyleOptionGanttItem opt;
int typ = data( idx, NodeModel::NodeType, Qt::EditRole ).toInt();
switch ( typ ) {
case Node::Type_Task: {
int ctyp = data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt();
switch ( ctyp ) {
case Node::MustStartOn:
case Node::StartNotEarlier:
case Node::FixedInterval:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2 - %3<nl/>"
"Status: %4<nl/>"
"Constraint type: %5<nl/>"
"Constraint time: %6<nl/>"
"Negative float: %7 h",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeEndTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeSchedulingStatus, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraint, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraintStart, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeNegativeFloat, Qt::DisplayRole ).toString()
);
case Node::MustFinishOn:
case Node::FinishNotLater:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2 - %3<nl/>"
"Status: %4<nl/>"
"Constraint type: %5<nl/>"
"Constraint time: %6<nl/>"
"Negative float: %7 h",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeEndTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeSchedulingStatus, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraint, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraintEnd, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeNegativeFloat, Qt::DisplayRole ).toString()
);
default:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2 - %3<nl/>"
"Status: %4<nl/>"
"Scheduling: %5",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeEndTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeSchedulingStatus, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraint, Qt::DisplayRole ).toString()
);
}
}
case Node::Type_Milestone: {
int ctyp = data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt();
switch ( ctyp ) {
case Node::MustStartOn:
case Node::StartNotEarlier:
case Node::FixedInterval:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2<nl/>"
"Status: %3<nl/>"
"Constraint type: %4<nl/>"
"Constraint time: %5<nl/>"
"Negative float: %6 h",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeSchedulingStatus, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraint, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraintStart, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeNegativeFloat, Qt::DisplayRole ).toString()
);
case Node::MustFinishOn:
case Node::FinishNotLater:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2<nl/>"
"Status: %3<nl/>"
"Constraint type: %4<nl/>"
"Constraint time: %5<nl/>"
"Negative float: %6 h",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeSchedulingStatus, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraint, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraintEnd, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeNegativeFloat, Qt::DisplayRole ).toString()
);
default:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2<nl/>"
"Status: %3<nl/>"
"Scheduling: %4",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeSchedulingStatus, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeConstraint, Qt::DisplayRole ).toString()
);
}
}
case Node::Type_Summarytask:
return xi18nc( "@info:tooltip",
"Name: %1<nl/>"
"Planned: %2 - %3",
model->data( idx, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeStartTime, Qt::DisplayRole ).toString(),
data( idx, NodeModel::NodeEndTime, Qt::DisplayRole ).toString()
);
}
return QString();
}
QVariant GanttItemDelegate::data( const QModelIndex& idx, int column, int role ) const
{
//debugPlan<<idx<<column<<role;
QModelIndex i = idx.model()->index( idx.row(), column, idx.parent() );
return i.data( role );
}
QString GanttItemDelegate::itemText( const QModelIndex& idx, int type ) const
{
if ( idx.model()->data( idx, GanttItemModel::SpecialItemTypeRole ).toInt() > 0 ) {
return QString();
}
QString txt;
if ( showTaskName ) {
txt = data( idx, NodeModel::NodeName, Qt::DisplayRole ).toString();
}
if ( type == KGantt::TypeTask && showResources ) {
if ( ! txt.isEmpty() ) {
txt += ' ';
}
txt += '(' + data( idx, NodeModel::NodeAssignments, Qt::DisplayRole ).toString() + ')';
}
//debugPlan<<txt;
return txt;
}
QRectF GanttItemDelegate::itemPositiveFloatRect( const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx ) const
{
QRectF r;
double fl = data( idx, NodeModel::NodePositiveFloat, Qt::EditRole ).toDouble();
if ( fl == 0.0 ) {
return r;
}
QDateTime et = data( idx, NodeModel::NodeEndTime, Qt::EditRole ).toDateTime();
if ( ! et.isValid() ) {
return r;
}
QDateTime dt = ( DateTime( et ) + Duration( fl, Duration::Unit_h ) );
qreal delta = opt.itemRect.height() / 6.0;
r.setLeft( opt.itemRect.right() );
r.setWidth( opt.grid->mapToChart( dt ) - opt.grid->mapToChart( et ) );
r.setTop( opt.itemRect.bottom() - delta );
r.setHeight( delta );
return r;
}
QRectF GanttItemDelegate::itemNegativeFloatRect( const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx ) const
{
QRectF r;
double fl = data( idx, NodeModel::NodeNegativeFloat, Qt::EditRole ).toDouble();
if ( fl == 0.0 ) {
return r;
}
QDateTime st = data( idx, NodeModel::NodeStartTime, Qt::EditRole ).toDateTime();
if ( ! st.isValid() ) {
return r;
}
QDateTime dt = ( DateTime( st ) - Duration( fl, Duration::Unit_h ) );
qreal delta = opt.itemRect.height() / 6.0;
r.setLeft( opt.itemRect.left() - qAbs( opt.grid->mapToChart( st ) - opt.grid->mapToChart( dt ) ) );
r.setRight( opt.itemRect.left() );
r.setTop( opt.itemRect.bottom() - delta );
r.setHeight( delta );
return r;
}
bool GanttItemDelegate::hasStartConstraint( const QModelIndex& idx ) const
{
//debugPlan<<data( idx, NodeModel::NodeName ).toString()<<data( idx, NodeModel::NodeConstraint ).toString()<<data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt();
switch ( data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt() ) {
case Node::FixedInterval:
case Node::StartNotEarlier:
case Node::MustStartOn: return true;
default: break;
}
return false;
}
QRectF GanttItemDelegate::itemStartConstraintRect( const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx ) const
{
QRectF r;
QDateTime dt;
if ( hasStartConstraint( idx ) ) {
dt = data( idx, NodeModel::NodeConstraintStart, Qt::EditRole ).toDateTime();
}
if ( ! dt.isValid() ) {
return r;
}
QDateTime st = data( idx, NodeModel::NodeStartTime, Qt::EditRole ).toDateTime();
if ( ! st.isValid() ) {
return r;
}
qreal delta = opt.itemRect.height() / 2.0;
r.setX( opt.grid->mapToChart( dt ) - opt.grid->mapToChart( st ) - delta );
r.setY( opt.itemRect.y() + ( delta / 2.0 ) );
r.setWidth( delta );
r.setHeight( delta );
return r;
}
bool GanttItemDelegate::hasEndConstraint( const QModelIndex& idx ) const
{
//debugPlan<<data( idx, NodeModel::NodeName ).toString()<<data( idx, NodeModel::NodeConstraint ).toString()<<data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt();
switch ( data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt() ) {
case Node::FixedInterval:
case Node::FinishNotLater:
case Node::MustFinishOn: return true;
default: break;
}
return false;
}
QRectF GanttItemDelegate::itemEndConstraintRect( const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx ) const
{
QRectF r;
QDateTime dt;
if ( hasEndConstraint( idx ) ) {
dt = data( idx, NodeModel::NodeConstraintEnd, Qt::EditRole ).toDateTime();
}
if ( ! dt.isValid() ) {
return r;
}
QDateTime et = data( idx, NodeModel::NodeEndTime, Qt::EditRole ).toDateTime();
if ( ! et.isValid() ) {
return r;
}
qreal delta = opt.itemRect.height() / 2.0;
r.setX( opt.itemRect.right() + ( opt.grid->mapToChart( dt ) - opt.grid->mapToChart( et ) ) );
r.setY( opt.itemRect.y() + ( delta / 2.0 ) );
r.setWidth( delta );
r.setHeight( delta );
return r;
}
KGantt::Span GanttItemDelegate::itemBoundingSpan( const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx ) const
{
//debugPlan<<opt<<idx;
if ( !idx.isValid() ) return KGantt::Span();
QRectF optRect = opt.itemRect;
QRectF itemRect = opt.itemRect;
int typ = idx.model()->data( idx, KGantt::ItemTypeRole ).toInt();
QString txt = itemText( idx, typ );
int tw = 0;
if ( ! txt.isEmpty() ) {
tw = opt.fontMetrics.width( txt );
tw += static_cast<int>( itemRect.height()/1.5 );
}
if ( showPositiveFloat ) {
QRectF fr = itemPositiveFloatRect( opt, idx );
if ( fr.isValid() ) {
itemRect = itemRect.united( fr );
}
}
if ( showNegativeFloat ) {
QRectF fr = itemNegativeFloatRect( opt, idx );
if ( fr.isValid() ) {
itemRect = itemRect.united( fr );
}
}
if ( showTimeConstraint ) {
QRectF cr = itemStartConstraintRect( opt, idx );
if ( cr.isValid() ) {
itemRect = itemRect.united( cr );
}
cr = itemEndConstraintRect( opt, idx );
if ( cr.isValid() ) {
itemRect = itemRect.united( cr );
}
}
if ( idx.model()->data( idx, GanttItemModel::SpecialItemTypeRole ).toInt() > 0 ) {
itemRect = itemRect.united( QRectF( opt.rect.left()-itemRect.height()/4., itemRect.top(), itemRect.height()/2., itemRect.height() ) );
} else if ( typ == KGantt::TypeEvent ) {
optRect = QRectF( opt.rect.left()-itemRect.height()/2., itemRect.top(), itemRect.height(), itemRect.height() );
itemRect = itemRect.united( optRect );
}
switch ( opt.displayPosition ) {
case KGantt::StyleOptionGanttItem::Left:
itemRect = itemRect.united( optRect.adjusted( -tw, 0.0, 0.0, 0.0 ) );
break;
case KGantt::StyleOptionGanttItem::Right:
itemRect = itemRect.united( optRect.adjusted( 0.0, 0.0, tw, 0.0 ) );
break;
case KGantt::StyleOptionGanttItem::Center:
if ( optRect.width() < tw ) {
itemRect = itemRect.united( optRect.adjusted( 0.0, 0.0, tw - optRect.width(), 0.0 ) );
}
break;
case KGantt::StyleOptionGanttItem::Hidden: // unused here
break;
}
return KGantt::Span( itemRect.left(), itemRect.width() );
}
/*! Paints the gantt item \a idx using \a painter and \a opt
*/
void GanttItemDelegate::paintGanttItem( QPainter* painter, const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx )
{
if ( !idx.isValid() ) return;
int specialtype = idx.model()->data( idx, GanttItemModel::SpecialItemTypeRole ).toInt();
if ( specialtype > 0 ) {
return paintSpecialItem( painter, opt, idx, specialtype );
}
const KGantt::ItemType typ = static_cast<KGantt::ItemType>( idx.model()->data( idx, 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<int>( 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 );
if ( showPositiveFloat ) {
QRectF fr = itemPositiveFloatRect( opt, idx );
if ( fr.isValid() ) {
painter->fillRect( fr, painter->pen().brush() );
}
}
if ( showNegativeFloat ) {
QRectF fr = itemNegativeFloatRect( opt, idx );
if ( fr.isValid() ) {
painter->fillRect( fr, painter->pen().brush() );
}
}
bool normal = true;
if ( showSchedulingError ) {
QList<int> lst;
lst << NodeModel::NodeAssignmentMissing
<< NodeModel::NodeResourceOverbooked
<< NodeModel::NodeResourceUnavailable
<< NodeModel::NodeConstraintsError
<< NodeModel::NodeEffortNotMet
<< NodeModel::NodeSchedulingError;
foreach ( int i, lst ) {
QVariant v = data( idx, i, Qt::EditRole );
//debugPlan<<idx.data(NodeModel::NodeName).toString()<<": showSchedulingError"<<i<<v;
if ( v.toBool() ) {
QVariant br = data( idx, i, Role::Foreground );
painter->setBrush( br.isValid() ? br.value<QBrush>() : m_schedulingErrorBrush );
normal = false;
break;
}
}
} 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<QBrush>() : m_criticalBrush );
normal = false;
}
}
if ( normal ) {
QVariant br = idx.data( Role::Foreground );
painter->setBrush( br.isValid() ? br.value<QBrush>() : defaultBrush( typ ) );
}
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();
if ( showTimeConstraint ) {
//debugPlan<<data( idx, NodeModel::NodeName ).toString()<<data( idx, NodeModel::NodeConstraint ).toString()<<r<<boundingRect;
painter->save();
painter->setBrush( QBrush( Qt::darkGray ) );
painter->setPen( Qt::black );
QRectF cr = itemStartConstraintRect( opt, idx );
if ( cr.isValid() ) {
QPainterPath p;
p.moveTo( cr.topLeft() );
p.lineTo( cr.bottomLeft() );
p.lineTo( cr.right(), cr.top() + ( cr.height() / 2.0 ) );
p.closeSubpath();
painter->drawPath( p );
if ( data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt() != Node::StartNotEarlier ) {
painter->fillPath( p, QBrush( Qt::red ) );
}
}
cr = itemEndConstraintRect( opt, idx );
if ( cr.isValid() ) {
QPainterPath p;
p.moveTo( cr.topRight() );
p.lineTo( cr.bottomRight() );
p.lineTo( cr.left(), cr.top() + ( cr.height() / 2.0 ) );
p.closeSubpath();
painter->drawPath( p );
if ( data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt() != Node::FinishNotLater ) {
painter->fillPath( p, QBrush( Qt::red ) );
}
}
painter->restore();
}
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( textRect, ta, txt );
}
break;
case KGantt::TypeSummary:
if ( opt.itemRect.isValid() ) {
pw-=1;
const QRectF r = QRectF( itemRect ).adjusted( -pw, -pw, pw, pw );
QPainterPath path;
const qreal deltaY = r.height()/2.;
const qreal deltaX = qMin( r.width()/qreal(2), deltaY );
path.moveTo( r.topLeft() );
path.lineTo( r.topRight() );
path.lineTo( QPointF( r.right(), r.top() + 2.*deltaY ) );
//path.lineTo( QPointF( r.right()-3./2.*delta, r.top() + delta ) );
path.quadTo( QPointF( r.right()-.5*deltaX, r.top() + deltaY ), QPointF( r.right()-2.*deltaX, r.top() + deltaY ) );
//path.lineTo( QPointF( r.left()+3./2.*delta, r.top() + delta ) );
path.lineTo( QPointF( r.left() + 2.*deltaX, r.top() + deltaY ) );
path.quadTo( QPointF( r.left()+.5*deltaX, r.top() + deltaY ), QPointF( r.left(), r.top() + 2.*deltaY ) );
path.closeSubpath();
painter->setBrushOrigin( itemRect.topLeft() );
painter->save();
QVariant br = idx.data( Role::Foreground );
painter->setBrush( br.isValid() ? br.value<QBrush>() : defaultBrush( typ ) );
painter->translate( 0.5, 0.5 );
painter->drawPath( path );
painter->restore();
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( textRect, ta | Qt::AlignVCenter, txt );
}
break;
case KGantt::TypeEvent:
//debugPlan << opt.boundingRect << opt.itemRect;
if ( opt.boundingRect.isValid() ) {
pw-=1;
painter->save();
QRectF ir = itemRect;
// do the same as for TypeTask
ir.translate( 0., ir.height()/6. );
ir.setHeight( 2.*ir.height()/3. );
painter->setBrushOrigin( ir.topLeft() );
painter->translate( 0.5, 0.5 );
if ( showPositiveFloat ) {
QRectF fr = itemPositiveFloatRect( opt, idx );
if ( fr.isValid() ) {
painter->fillRect( fr, painter->pen().brush() );
}
}
if ( showNegativeFloat ) {
QRectF fr = itemNegativeFloatRect( opt, idx );
if ( fr.isValid() ) {
painter->fillRect( fr, painter->pen().brush() );
}
}
painter->restore();
bool normal = true;
if ( showSchedulingError ) {
QList<int> lst;
lst << NodeModel::NodeAssignmentMissing
<< NodeModel::NodeResourceOverbooked
<< NodeModel::NodeResourceUnavailable
<< NodeModel::NodeConstraintsError
<< NodeModel::NodeEffortNotMet
<< NodeModel::NodeSchedulingError;
foreach ( int i, lst ) {
QVariant v = data( idx, i, Qt::EditRole );
//debugPlan<<idx.data(NodeModel::NodeName).toString()<<": showSchedulingError"<<i<<v;
if ( v.toBool() ) {
QVariant br = data( idx, i, Role::Foreground );
painter->setBrush( br.isValid() ? br.value<QBrush>() : m_schedulingErrorBrush );
normal = false;
break;
}
}
} 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<QBrush>() : m_criticalBrush );
normal = false;
}
}
if ( normal ) {
QVariant br = idx.data( Role::Foreground );
painter->setBrush( br.isValid() ? br.value<QBrush>() : defaultBrush( typ ) );
}
const qreal delta = static_cast< int >( ( itemRect.height() - pw ) / 2 );
// HACK: KGantt uses constraint start/end for more than painting,
// so to avoid muddling the waters we store an offset here to enable
// us to not paint the constraint on top/under the milestone.
// This only works if we use the same delegate to paint constraints, of course...
m_constraintXOffset = delta;
QPainterPath path;
path.moveTo( 0., 0. );
path.lineTo( delta, delta );
path.lineTo( 0., 2.*delta );
path.lineTo( -delta, delta );
path.closeSubpath();
painter->save();
painter->translate( itemRect.left(), itemRect.top() + pw );
painter->translate( 0.5, 0.5 );
painter->drawPath( path );
painter->restore();
if ( showTimeConstraint ) {
//debugPlan<<data( idx, NodeModel::NodeName ).toString()<<data( idx, NodeModel::NodeConstraint ).toString()<<r<<boundingRect;
painter->save();
painter->setBrush( QBrush( Qt::darkGray ) );
painter->setPen( Qt::black );
QRectF cr = itemStartConstraintRect( opt, idx );
if ( cr.isValid() ) {
QPainterPath p;
p.moveTo( cr.topLeft() );
p.lineTo( cr.bottomLeft() );
p.lineTo( cr.right(), cr.top() + ( cr.height() / 2.0 ) );
p.closeSubpath();
painter->drawPath( p );
if ( data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt() != Node::StartNotEarlier ) {
painter->fillPath( p, QBrush( Qt::red ) );
}
}
cr = itemEndConstraintRect( opt, idx );
if ( cr.isValid() ) {
QPainterPath p;
p.moveTo( cr.topRight() );
p.lineTo( cr.bottomRight() );
p.lineTo( cr.left(), cr.top() + ( cr.height() / 2.0 ) );
p.closeSubpath();
painter->drawPath( p );
if ( data( idx, NodeModel::NodeConstraint, Qt::EditRole ).toInt() != Node::FinishNotLater ) {
painter->fillPath( p, QBrush( Qt::red ) );
}
}
painter->restore();
}
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( textRect, ta | Qt::AlignVCenter, txt );
}
break;
default:
break;
}
painter->restore();
}
/*! Paints the gantt item \a idx using \a painter and \a opt
*/
void GanttItemDelegate::paintSpecialItem( QPainter* painter, const KGantt::StyleOptionGanttItem& opt, const QModelIndex& /*idx*/, int typ )
{
QRectF itemRect = opt.itemRect;
QRectF boundingRect = opt.boundingRect;
boundingRect.setY( itemRect.y() );
boundingRect.setHeight( itemRect.height() );
painter->save();
QPen pen = defaultPen( KGantt::TypeEvent );
if ( opt.state & QStyle::State_Selected ) pen.setWidth( 2*pen.width() );
painter->setPen( pen );
qreal pw = painter->pen().width()/2.;
switch( typ ) {
case 1: // early start
if ( boundingRect.isValid() ) {
painter->setBrush( QBrush( "red" ) );
pw-=1;
QRectF r = boundingRect;
r.setHeight( r.height()/3. );
painter->setBrushOrigin( boundingRect.topLeft() );
painter->translate( 0.5, 0.5 );
QPainterPath p( r.topLeft() );
p.lineTo( r.topRight() );
p.lineTo( r.left() + ( r.width()/2.0 ), r.bottom() );
p.closeSubpath();
painter->fillPath( p, painter->brush() );
//painter->drawPath( p );
}
break;
case 2: // late finish
if ( boundingRect.isValid() ) {
painter->setBrush( QBrush( "blue" ) );
pw-=1;
QRectF r = boundingRect;
r.setTop( r.bottom() - r.height()/2.8 );
painter->setBrushOrigin( boundingRect.topLeft() );
painter->translate( 0.5, -0.5 );
QPainterPath p( r.bottomLeft() );
p.lineTo( r.bottomRight() );
p.lineTo( r.left() + ( r.width()/2.0 ), r.top() );
p.closeSubpath();
painter->fillPath( p, painter->brush() );
//painter->drawPath( p );
}
break;
case 3: // late start
if ( boundingRect.isValid() ) {
painter->setBrush( QBrush( "red" ) );
pw-=1;
QRectF r = boundingRect;
r.setTop( r.bottom() - r.height()/2.8 );
painter->setBrushOrigin( boundingRect.topLeft() );
painter->translate( 0.5, -0.5 );
QPainterPath p( r.bottomLeft() );
p.lineTo( r.bottomRight() );
p.lineTo( r.left() + ( r.width()/2.0 ), r.top() );
p.closeSubpath();
painter->fillPath( p, painter->brush() );
//painter->drawPath( p );
}
break;
case 4: // early finish
if ( boundingRect.isValid() ) {
painter->setBrush( QBrush( "blue" ) );
pw-=1;
QRectF r = boundingRect;
r.setHeight( r.height()/3. );
painter->setBrushOrigin( boundingRect.topLeft() );
painter->translate( 0.5, 0.5 );
QPainterPath p( r.topLeft() );
p.lineTo( r.topRight() );
p.lineTo( r.left() + ( r.width()/2.0 ), r.bottom() );
p.closeSubpath();
painter->fillPath( p, painter->brush() );
//painter->drawPath( p );
}
break;
default:
break;
}
painter->restore();
}
void GanttItemDelegate::paintConstraintItem( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const KGantt::Constraint &constraint )
{
if ( ! showTaskLinks ) {
return;
}
KGantt::Constraint c( constraint );
QPen pen(opt.palette.text().color());
if ( showCriticalPath &&
data( c.startIndex(), NodeModel::NodeCriticalPath ).toBool() &&
data( c.endIndex(), NodeModel::NodeCriticalPath ).toBool() )
{
//debugPlan<<data( c.startIndex(), NodeModel::NodeName ).toString()<<data( c.endIndex(), NodeModel::NodeName ).toString()<<"critical path";
pen = QPen( Qt::red );
// FIXME How to make sure it's not obscured by other constraints?
}
c.setData( KGantt::Constraint::ValidConstraintPen, pen );
c.setData( KGantt::Constraint::InvalidConstraintPen, pen );
QPointF sp = start;
QPointF ep = end;
// HACK: to fix painting of dependencies ending at a milestone
if (m_constraintXOffset != 0.0) {
switch (constraint.relationType()) {
case KGantt::Constraint::FinishStart:
case KGantt::Constraint::StartStart: {
QModelIndex idx = constraint.endIndex();
idx = idx.sibling(idx.row(), NodeModel::NodeType);
if (idx.data(KGantt::ItemTypeRole).toInt() == KGantt::TypeEvent) {
ep.setX(ep.x() - m_constraintXOffset);
}
break;
}
case KGantt::Constraint::FinishFinish:
case KGantt::Constraint::StartFinish:
QModelIndex idx = constraint.endIndex();
idx = idx.sibling(idx.row(), NodeModel::NodeType);
if (idx.data(KGantt::ItemTypeRole).toInt() == KGantt::TypeEvent) {
ep.setX(ep.x() + m_constraintXOffset);
}
break;
}
}
KGantt::ItemDelegate::paintConstraintItem( painter, opt, sp, ep, c );
}
//------------------------
ResourceGanttItemDelegate::ResourceGanttItemDelegate( QObject *parent )
: KGantt::ItemDelegate( parent )
{
QLinearGradient b( 0., 0., 0., QApplication::fontMetrics().height() );
b.setColorAt( 0., Qt::red );
b.setColorAt( 1., Qt::darkRed );
m_overloadBrush = QBrush( b );
b.setColorAt( 0., QColor( Qt::yellow ).lighter( 175 ) );
b.setColorAt( 1., QColor( Qt::yellow ).darker( 125 ) );
m_underloadBrush = QBrush( b );
}
QVariant ResourceGanttItemDelegate::data( const QModelIndex& idx, int column, int role ) const
{
QModelIndex i = idx.model()->index( idx.row(), column, idx.parent() );
return i.data( role );
}
/*! Paints the gantt item \a idx using \a painter and \a opt
*/
void ResourceGanttItemDelegate::paintGanttItem( QPainter* painter, const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx )
{
if ( !idx.isValid() ) return;
const KGantt::ItemType typ = static_cast<KGantt::ItemType>( idx.model()->data( idx, KGantt::ItemTypeRole ).toInt() );
const QString& txt = opt.text;
QRectF itemRect = opt.itemRect;
QRectF boundingRect = opt.boundingRect;
boundingRect.setY( itemRect.y() );
boundingRect.setHeight( itemRect.height() );
painter->save();
QPen pen = defaultPen( typ );
if ( opt.state & QStyle::State_Selected ) pen.setWidth( 2*pen.width() );
painter->setPen( pen );
painter->setBrush( defaultBrush( typ ) );
qreal pw = painter->pen().width()/2.;
switch( typ ) {
case KGantt::TypeTask:
if ( itemRect.isValid() ) {
qreal pw = painter->pen().width()/2.;
pw-=1;
QRectF r = itemRect;
r.translate( 0., r.height()/12. );
r.setHeight( 5.*r.height()/6. );
painter->setBrushOrigin( itemRect.topLeft() );
painter->save();
if ( idx.data( Role::ObjectType ).toInt() == OT_External ) {
painter->setBrush( Qt::blue );
}
painter->translate( 0.5, 0.5 );
painter->drawRect( r );
painter->restore();
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( boundingRect, ta, txt );
}
break;
case KGantt::TypeSummary:
if ( idx.data( Role::ObjectType ).toInt() == OT_Resource ) {
paintResourceItem( painter, opt, idx );
} else if ( itemRect.isValid() ) {
pw-=1;
const QRectF r = QRectF( opt.itemRect ).adjusted( -pw, -pw, pw, pw );
QPainterPath path;
const qreal deltaY = r.height()/2.;
const qreal deltaX = qMin( r.width()/qreal(2), deltaY );
path.moveTo( r.topLeft() );
path.lineTo( r.topRight() );
path.lineTo( QPointF( r.right(), r.top() + 2.*deltaY ) );
//path.lineTo( QPointF( r.right()-3./2.*delta, r.top() + delta ) );
path.quadTo( QPointF( r.right()-.5*deltaX, r.top() + deltaY ), QPointF( r.right()-2.*deltaX, r.top() + deltaY ) );
//path.lineTo( QPointF( r.left()+3./2.*delta, r.top() + delta ) );
path.lineTo( QPointF( r.left() + 2.*deltaX, r.top() + deltaY ) );
path.quadTo( QPointF( r.left()+.5*deltaX, r.top() + deltaY ), QPointF( r.left(), r.top() + 2.*deltaY ) );
path.closeSubpath();
painter->setBrushOrigin( itemRect.topLeft() );
painter->save();
painter->translate( 0.5, 0.5 );
painter->drawPath( path );
painter->restore();
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( boundingRect, ta | Qt::AlignVCenter, txt );
}
break;
case KGantt::TypeEvent: /* TODO */
if ( opt.boundingRect.isValid() ) {
const qreal pw = painter->pen().width() / 2. - 1;
const QRectF r = QRectF( opt.rect ).adjusted( -pw, -pw, pw, pw );
QPainterPath path;
const qreal delta = static_cast< int >( r.height() / 2 );
path.moveTo( delta, 0. );
path.lineTo( 2.*delta, delta );
path.lineTo( delta, 2.*delta );
path.lineTo( 0., delta );
path.closeSubpath();
painter->save();
painter->translate( r.topLeft() );
painter->translate( 0.5, 0.5 );
painter->drawPath( path );
painter->restore();
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( boundingRect, ta | Qt::AlignVCenter, txt );
}
break;
default:
break;
}
painter->restore();
}
void ResourceGanttItemDelegate::paintResourceItem( QPainter* painter, const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx )
{
if ( ! opt.itemRect.isValid() ) {
return;
}
qreal pw = painter->pen().width()/2.;
pw-=1;
QRectF r = opt.itemRect;
QRectF boundingRect = opt.boundingRect;
boundingRect.setY( r.y() );
boundingRect.setHeight( r.height() );
r.translate( 0., r.height()/12. );
r.setHeight( 5.*r.height()/6. );
painter->setBrushOrigin( opt.itemRect.topLeft() );
qreal x0 = opt.grid->mapToChart( idx.data( KGantt::StartTimeRole ).toDateTime() );
Appointment *external = static_cast<Appointment*>( idx.data( Role::ExternalAppointments ).value<void*>() );
Appointment *internal = static_cast<Appointment*>( idx.data( Role::InternalAppointments ).value<void*>() );
int rl = idx.data( Role::Maximum ).toInt(); //TODO check calendar
Appointment tot = *external + *internal;
painter->save();
// TODO check load vs units properly, it's not as simple as below!
QLocale locale;
foreach ( const AppointmentInterval &i, tot.intervals().map() ) {
int il = i.load();
QString txt = locale.toString( (double)il / (double)rl, 'f', 1 );
QPen pen = painter->pen();
if ( il > rl ) {
painter->setBrush( m_overloadBrush );
pen.setColor( Qt::white );
} else if ( il < rl ) {
painter->setBrush( m_underloadBrush );
} else {
painter->setBrush( defaultBrush( KGantt::TypeTask ) );
}
qreal v1 = opt.grid->mapToChart( i.startTime() );
qreal v2 = opt.grid->mapToChart( i.endTime() );
QRectF rr( v1 - x0, r.y(), v2 - v1, r.height() );
painter->drawRect( rr );
if ( painter->boundingRect( rr, Qt::AlignCenter, txt ).width() < rr.width() ) {
QPen pn = painter->pen();
painter->setPen( pen );
painter->drawText( rr, Qt::AlignCenter, txt );
painter->setPen( pn );
}
}
painter->restore();
const Qt::Alignment ta = qAlignment( opt.displayPosition );
painter->setPen(QPen(opt.palette.text().color()));
painter->drawText( boundingRect, ta, opt.text );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptganttview.cpp b/src/libs/ui/kptganttview.cpp
index a4fbc4c1..2029501b 100644
--- a/src/libs/ui/kptganttview.cpp
+++ b/src/libs/ui/kptganttview.cpp
@@ -1,1460 +1,1461 @@
/* This file is part of the KDE project
Copyright (C) 2002 - 2007, 2012 Dag Andersen <danders@get2net.dk>
Copyright (C) 2006 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 "kptganttview.h"
#include "kptnodeitemmodel.h"
#include "kptappointment.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptrelation.h"
#include "kptschedule.h"
#include "kptviewbase.h"
#include "kptitemviewsettup.h"
#include "kptduration.h"
#include "kptdatetime.h"
#include "kptresourceappointmentsmodel.h"
#include "Help.h"
#include "kptdebug.h"
#include <KGanttProxyModel>
#include <KGanttConstraintModel>
#include <KGanttConstraint>
#include <KGanttDateTimeGrid>
#include <KGanttGraphicsView>
#include <KGanttTreeViewRowController>
#include <KoDocument.h>
#include <KoXmlReader.h>
#include <KoPageLayoutWidget.h>
#include <QSplitter>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QDateTime>
#include <QModelIndex>
#include <QPainter>
#include <QTabWidget>
#include <QPushButton>
#include <QLocale>
#include <QAction>
#include <QMenu>
#include <QHoverEvent>
#include <QScrollBar>
#include <ktoggleaction.h>
#include <KGanttGlobal>
#include <KGanttStyleOptionGanttItem>
/// The main namespace
namespace KPlato
{
class GanttItemDelegate;
//-------------------------------------------------
GanttChartDisplayOptionsPanel::GanttChartDisplayOptionsPanel( GanttItemDelegate *delegate, QWidget *parent )
: QWidget( parent ),
m_delegate( delegate )
{
setupUi( this );
setValues( *delegate );
connect( ui_showTaskName, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showResourceNames, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showDependencies, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showPositiveFloat, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showNegativeFloat, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showCriticalPath, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showCriticalTasks, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showCompletion, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showSchedulingError, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
connect( ui_showTimeConstraint, &QCheckBox::stateChanged, this, &GanttChartDisplayOptionsPanel::changed );
}
void GanttChartDisplayOptionsPanel::slotOk()
{
m_delegate->showTaskName = ui_showTaskName->checkState() == Qt::Checked;
m_delegate->showResources = ui_showResourceNames->checkState() == Qt::Checked;
m_delegate->showTaskLinks = ui_showDependencies->checkState() == Qt::Checked;
m_delegate->showPositiveFloat = ui_showPositiveFloat->checkState() == Qt::Checked;
m_delegate->showNegativeFloat = ui_showNegativeFloat->checkState() == Qt::Checked;
m_delegate->showCriticalPath = ui_showCriticalPath->checkState() == Qt::Checked;
m_delegate->showCriticalTasks = ui_showCriticalTasks->checkState() == Qt::Checked;
m_delegate->showProgress = ui_showCompletion->checkState() == Qt::Checked;
m_delegate->showSchedulingError = ui_showSchedulingError->checkState() == Qt::Checked;
m_delegate->showTimeConstraint = ui_showTimeConstraint->checkState() == Qt::Checked;
}
void GanttChartDisplayOptionsPanel::setValues( const GanttItemDelegate &del )
{
ui_showTaskName->setCheckState( del.showTaskName ? Qt::Checked : Qt::Unchecked );
ui_showResourceNames->setCheckState( del.showResources ? Qt::Checked : Qt::Unchecked );
ui_showDependencies->setCheckState( del.showTaskLinks ? Qt::Checked : Qt::Unchecked );
ui_showPositiveFloat->setCheckState( del.showPositiveFloat ? Qt::Checked : Qt::Unchecked );
ui_showNegativeFloat->setCheckState( del.showNegativeFloat ? Qt::Checked : Qt::Unchecked );
ui_showCriticalPath->setCheckState( del.showCriticalPath ? Qt::Checked : Qt::Unchecked );
ui_showCriticalTasks->setCheckState( del.showCriticalTasks ? Qt::Checked : Qt::Unchecked );
ui_showCompletion->setCheckState( del.showProgress ? Qt::Checked : Qt::Unchecked );
ui_showSchedulingError->setCheckState( del.showSchedulingError ? Qt::Checked : Qt::Unchecked );
ui_showTimeConstraint->setCheckState( del.showTimeConstraint ? Qt::Checked : Qt::Unchecked );
}
void GanttChartDisplayOptionsPanel::setDefault()
{
GanttItemDelegate del;
setValues( del );
}
//----
GanttViewSettingsDialog::GanttViewSettingsDialog( GanttViewBase *gantt, GanttItemDelegate *delegate, ViewBase *view, bool selectPrint )
: ItemViewSettupDialog( view, gantt->treeView(), true, view ),
m_gantt( gantt )
{
GanttChartDisplayOptionsPanel *panel = new GanttChartDisplayOptionsPanel( delegate );
/*KPageWidgetItem *page = */insertWidget( 1, panel, i18n( "Chart" ), i18n( "Gantt Chart Settings" ) );
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_printingoptions = new GanttPrintingOptionsWidget( this );
m_printingoptions->setOptions( gantt->printingOptions() );
tab->addTab( m_printingoptions, m_printingoptions->windowTitle() );
KPageWidgetItem *page = insertWidget( 2, tab, i18n( "Printing" ), i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, SIGNAL(accepted()), this, SLOT(slotOk()) );
connect( this, &QDialog::accepted, panel, &GanttChartDisplayOptionsPanel::slotOk );
connect( button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, panel, &GanttChartDisplayOptionsPanel::setDefault );
}
void GanttViewSettingsDialog::slotOk()
{
debugPlan;
m_gantt->setPrintingOptions( m_printingoptions->options());
ItemViewSettupDialog::slotOk();
}
//-------------------------
GanttPrintingOptions::GanttPrintingOptions()
: printRowLabels( true ),
singlePage( true )
{
}
bool GanttPrintingOptions::loadContext( const KoXmlElement &settings )
{
KoXmlElement e = settings.namedItem( "print-options" ).toElement();
if ( ! e.isNull() ) {
printRowLabels = (bool)( e.attribute( "print-rowlabels", "0" ).toInt() );
singlePage = (bool)( e.attribute( "print-singlepage", "0" ).toInt() );
}
debugPlan <<"..........."<<printRowLabels<<singlePage;
return true;
}
void GanttPrintingOptions::saveContext( QDomElement &settings ) const
{
QDomElement e = settings.ownerDocument().createElement( "print-options" );
settings.appendChild( e );
e.setAttribute( "print-rowlabels", QString::number(printRowLabels) );
e.setAttribute( "print-singlepage", QString::number(singlePage) );
}
GanttPrintingOptionsWidget::GanttPrintingOptionsWidget( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
setWindowTitle( xi18nc( "@title:tab", "Chart" ) );
}
GanttPrintingOptions GanttPrintingOptionsWidget::options() const
{
GanttPrintingOptions opt;
opt.printRowLabels = printRowLabels();
opt.singlePage = singlePage();
return opt;
}
void GanttPrintingOptionsWidget::setOptions( const GanttPrintingOptions &opt )
{
setPrintRowLabels( opt.printRowLabels );
setSinglePage(opt.singlePage );
}
//----------------
GanttPrintingDialog::GanttPrintingDialog( ViewBase *view, GanttViewBase *gantt )
: PrintingDialog( view ),
m_gantt( gantt ),
m_options( 0 )
{
m_headerHeight = gantt->treeView()->header()->height(); // same header hight
m_sceneRect = gantt->graphicsView()->sceneRect();
m_horPages = 1;
qreal c = m_sceneRect.width() - printer().pageRect().width();
while ( c > 0 ) {
++m_horPages;
c -= printer().pageRect().width();
}
m_vertPages = 1;
c = m_sceneRect.height() - printer().pageRect().height() - m_headerHeight;
while ( c > 0 ) {
++m_vertPages;
c -= printer().pageRect().height();
}
debugPlan<<m_sceneRect<<printer().pageRect()<<m_horPages<<m_vertPages;
printer().setFromTo( documentFirstPage(), documentLastPage() );
}
void GanttPrintingDialog::startPrinting(RemovePolicy removePolicy )
{
QList<int> pages;
if ( printer().fromPage() > 0 ) {
pages << printer().fromPage();
if ( ! m_gantt->m_printOptions.singlePage ) {
int last = printer().toPage();
for ( int i = pages.first() + 1; i <= last; ++i ) {
pages << i;
}
if (m_vertPages > 1) {
m_image = QImage(m_sceneRect.width(), m_sceneRect.height() + m_headerHeight, QImage::Format_ARGB32);
m_image.fill(Qt::white);
QPainter p(&m_image);
m_gantt->print(&p, m_image.rect(), m_gantt->m_printOptions.printRowLabels, true);
}
}
}
setPageRange( pages );
PrintingDialog::startPrinting( removePolicy );
}
QList<QWidget*> GanttPrintingDialog::createOptionWidgets() const
{
//debugPlan;
GanttPrintingOptionsWidget *w = new GanttPrintingOptionsWidget();
w->setPrintRowLabels( m_gantt->m_printOptions.printRowLabels );
connect(w->ui_printRowLabels, &QAbstractButton::toggled, this, &GanttPrintingDialog::slotPrintRowLabelsToogled);
w->setSinglePage( m_gantt->m_printOptions.singlePage );
connect(w->ui_singlePage, &QAbstractButton::toggled, this, &GanttPrintingDialog::slotSinglePageToogled);
const_cast<GanttPrintingDialog*>( this )->m_options = w;
return QList<QWidget*>() << createPageLayoutWidget() << m_options;
}
void GanttPrintingDialog::slotPrintRowLabelsToogled( bool on )
{
m_gantt->m_printOptions.printRowLabels = on;
}
void GanttPrintingDialog::slotSinglePageToogled( bool on )
{
m_gantt->m_printOptions.singlePage = on;
printer().setFromTo( documentFirstPage(), documentLastPage() );
}
int GanttPrintingDialog::documentLastPage() const
{
//debugPlan<<m_gantt->m_printOptions.singlePage<<m_horPages<<m_vertPages;
return m_gantt->m_printOptions.singlePage ? documentFirstPage() : m_horPages * m_vertPages;
}
void GanttPrintingDialog::printPage( int page, QPainter &painter )
{
debugPlan<<"page:"<<page<<"first"<<documentFirstPage()<<"last:"<<documentLastPage()<<m_horPages<<m_vertPages;
int p = page - documentFirstPage();
QRectF pageRect = printer().pageRect();
pageRect.moveTo( 0, 0 );
bool singlePage = m_gantt->m_printOptions.singlePage;
int vert = singlePage ? 0 : p / m_horPages;
int hor = singlePage ? 0 : p % m_horPages;
// painter.setClipRect( pageRect.adjusted( -1.0, -1.0, 1.0, 1.0 ) );
if (singlePage) {
// single page: use KGantt
m_gantt->print( &painter, m_sceneRect.left(), m_sceneRect.right(), pageRect, m_gantt->m_printOptions.printRowLabels, true );
} else if (m_vertPages == 1) {
// single vertical page: use KGantt
qreal hh = vert == 0 ? m_headerHeight : 0;
qreal ho = vert > 0 ? m_headerHeight : 0;
QRectF sourceRect = QRectF( m_sceneRect.x() + ( pageRect.width() * hor ), m_sceneRect.y() + ( ( pageRect.height() * vert ) - ho ), pageRect.width(), pageRect.height() - hh );
debugPlan<<p<<hor<<vert<<sourceRect;
m_gantt->print( &painter, sourceRect.left(), sourceRect.right(), pageRect, hor == 0 && m_gantt->m_printOptions.printRowLabels, true );
} else {
// print on multiple vertical pages: use pixmap
// QT5TODO Make KGantt able to print multiple pages vertically
QRectF sourceRect = m_image.rect();
qreal hh = vert == 0 ? m_headerHeight : 0;
qreal ho = vert > 0 ? m_headerHeight : 0;
sourceRect = QRectF( sourceRect.x() + ( pageRect.width() * hor ), sourceRect.y() + ( ( pageRect.height() * vert ) - ho ), pageRect.width(), pageRect.height() - hh );
debugPlan<<p<<hor<<vert<<sourceRect;
painter.drawImage(pageRect, m_image, sourceRect);
}
}
//---------------------
class HeaderView : public QHeaderView
{
public:
explicit HeaderView( QWidget* parent=0 ) : QHeaderView( Qt::Horizontal, parent ) {
}
QSize sizeHint() const { QSize s = QHeaderView::sizeHint(); s.rheight() *= 2; return s; }
};
GanttTreeView::GanttTreeView( QWidget* parent )
: TreeViewBase( parent )
{
disconnect( header() );
setHeader( new HeaderView );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setTreePosition(-1); // always visual index 0
header()->setContextMenuPolicy( Qt::CustomContextMenu );
connect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotHeaderContextMenuRequested(QPoint)) );
}
//-------------------------------------------
GanttZoomWidget::GanttZoomWidget( QWidget *parent )
: QSlider( parent ), m_hide( true ), m_grid( 0 )
{
setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
setGeometry( 0, 0, 200, minimumSizeHint().height() );
setContextMenuPolicy( Qt::PreventContextMenu );
setOrientation( Qt::Horizontal );
setPageStep( 5 );
setMaximum( 125 );
connect(this, &QAbstractSlider::valueChanged, this, &GanttZoomWidget::sliderValueChanged);
}
void GanttZoomWidget::setEnableHideOnLeave( bool hide )
{
m_hide = hide;
}
void GanttZoomWidget::setGrid( KGantt::DateTimeGrid *grid )
{
m_grid = grid;
if ( grid ) {
int pos = -1; // daywidth always >= 0.1
for ( qreal dw = grid->dayWidth(); dw >= 0.1 && pos < maximum(); ++pos ) {
dw *= 1.0 / 1.1;
}
blockSignals( true );
setValue( pos );
blockSignals( false );
}
}
void GanttZoomWidget::leaveEvent( QEvent *e )
{
if ( m_hide ) {
setVisible( false );
}
QSlider::leaveEvent( e );
}
void GanttZoomWidget::sliderValueChanged( int value )
{
//debugPlan<<m_grid<<value;
if ( m_grid ) {
int v = qMax(1.0, qPow( 1.1, value ) * 0.1);
m_grid->setDayWidth( v );
}
}
//-------------------------------------------
GanttViewBase::GanttViewBase( QWidget *parent )
: KGantt::View( parent )
{
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
g->setUserDefinedUpperScale( new KGantt::DateTimeScaleFormatter(KGantt::DateTimeScaleFormatter::Month, QString::fromLatin1("yyyy-MMMM")));
g->setUserDefinedLowerScale( new KGantt::DateTimeScaleFormatter(KGantt::DateTimeScaleFormatter::Day, QString::fromLatin1("ddd")));
QLocale locale;
g->setWeekStart( locale.firstDayOfWeek() );
const QList<Qt::DayOfWeek> weekdays = locale.weekdays();
QSet<Qt::DayOfWeek> fd;
for ( int i = Qt::Monday; i <= Qt::Sunday; ++i ) {
if (!weekdays.contains(static_cast<Qt::DayOfWeek>(i))) {
fd << static_cast<Qt::DayOfWeek>( i );
}
}
g->setFreeDays( fd );
m_zoomwidget = new GanttZoomWidget( graphicsView() );
m_zoomwidget->setGrid( g );
m_zoomwidget->setEnableHideOnLeave( true );
m_zoomwidget->hide();
m_zoomwidget->move( 6, 6 );
graphicsView()->installEventFilter(this);
graphicsView()->setMouseTracking(true);
}
GanttViewBase::~GanttViewBase()
{
// HACK: avoid crash due to access of graphicsview scrollbar after death
// KGantt tries to sync leftview scrollbar with graphicsview scrollbar
// and seems sometimes graphicsview has already been deleted.
// Note: this will be fixed in next KGantt release
leftView()->verticalScrollBar()->disconnect();
}
GanttTreeView *GanttViewBase::treeView() const
{
GanttTreeView *tv = qobject_cast<GanttTreeView*>(const_cast<QAbstractItemView*>(leftView()));
Q_ASSERT(tv);
return tv;
}
bool GanttViewBase::eventFilter(QObject *obj, QEvent *event)
{
if (obj != graphicsView()) {
return false;
}
if (event->type() == QEvent::HoverMove) {
QHoverEvent *e = static_cast<QHoverEvent*>( event );
if (e->pos().y() > 7 && e->pos().y() < m_zoomwidget->height() + 5 && e->pos().x() > 7 && e->pos().x() < m_zoomwidget->width() + 5 ) {
if ( !m_zoomwidget->isVisible()) {
m_zoomwidget->show();
m_zoomwidget->setFocus();
}
return true;
}
}
return false;
}
bool GanttViewBase::loadContext( const KoXmlElement &settings )
{
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
g->setScale( static_cast<KGantt::DateTimeGrid::Scale>( settings.attribute( "chart-scale", "0" ).toInt() ) );
g->setDayWidth( settings.attribute( "chart-daywidth", "30" ).toDouble() );
return true;
}
void GanttViewBase::saveContext( QDomElement &settings ) const
{
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
settings.setAttribute( "chart-scale", QString::number(g->scale()) );
settings.setAttribute( "chart-daywidth", QString::number(g->dayWidth()) );
}
//-------------------------------------------
NodeGanttViewBase::NodeGanttViewBase( QWidget *parent )
: GanttViewBase( parent ),
m_project( 0 ),
m_ganttdelegate( new GanttItemDelegate( this ) )
{
debugPlan<<"------------------- create NodeGanttViewBase -----------------------";
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 );
NodeSortFilterProxyModel *m = new NodeSortFilterProxyModel( &m_defaultModel, this );
KGantt::View::setModel( m );
}
NodeGanttViewBase::~NodeGanttViewBase()
{
delete m_rowController;
}
NodeSortFilterProxyModel *NodeGanttViewBase::sfModel() const
{
return static_cast<NodeSortFilterProxyModel*>( KGantt::View::model() );
}
void NodeGanttViewBase::setItemModel( ItemModelBase *model )
{
sfModel()->setSourceModel( model );
}
ItemModelBase *NodeGanttViewBase::model() const
{
return sfModel()->itemModel();
}
void NodeGanttViewBase::setProject( Project *project )
{
model()->setProject( project );
m_project = project;
}
bool NodeGanttViewBase::loadContext( const KoXmlElement &settings )
{
treeView()->loadContext( model()->columnMap(), settings );
KoXmlElement e = settings.namedItem( "ganttchart" ).toElement();
if ( ! e.isNull() ) {
m_ganttdelegate->showTaskLinks = (bool)( e.attribute( "show-dependencies", "0" ).toInt() );
m_ganttdelegate->showTaskName = (bool)( e.attribute( "show-taskname", "0" ).toInt() );
m_ganttdelegate->showResources = (bool)( e.attribute( "show-resourcenames", "0" ).toInt() );
m_ganttdelegate->showProgress = (bool)( e.attribute( "show-completion", "0" ).toInt() );
m_ganttdelegate->showCriticalPath = (bool)( e.attribute( "show-criticalpath", "0" ).toInt() );
m_ganttdelegate->showCriticalTasks = (bool)( e.attribute( "show-criticaltasks", "0" ).toInt() );
m_ganttdelegate->showPositiveFloat = (bool)( e.attribute( "show-positivefloat", "0" ).toInt() );
m_ganttdelegate->showSchedulingError = (bool)( e.attribute( "show-schedulingerror", "0" ).toInt() );
m_ganttdelegate->showTimeConstraint = (bool)( e.attribute( "show-timeconstraint", "0" ).toInt() );
m_ganttdelegate->showNegativeFloat = (bool)( e.attribute( "show-negativefloat", "0" ).toInt() );
GanttViewBase::loadContext( e );
m_printOptions.loadContext( e );
}
return true;
}
void NodeGanttViewBase::saveContext( QDomElement &settings ) const
{
debugPlan;
treeView()->saveContext( model()->columnMap(), settings );
QDomElement e = settings.ownerDocument().createElement( "ganttchart" );
settings.appendChild( e );
e.setAttribute( "show-dependencies", QString::number(m_ganttdelegate->showTaskLinks) );
e.setAttribute( "show-taskname", QString::number(m_ganttdelegate->showTaskName) );
e.setAttribute( "show-resourcenames", QString::number(m_ganttdelegate->showResources) );
e.setAttribute( "show-completion", QString::number(m_ganttdelegate->showProgress) );
e.setAttribute( "show-criticalpath", QString::number(m_ganttdelegate->showCriticalPath) );
e.setAttribute( "show-criticaltasks",QString::number(m_ganttdelegate->showCriticalTasks) );
e.setAttribute( "show-positivefloat", QString::number(m_ganttdelegate->showPositiveFloat) );
e.setAttribute( "show-schedulingerror", QString::number(m_ganttdelegate->showSchedulingError) );
e.setAttribute( "show-timeconstraint", QString::number(m_ganttdelegate->showTimeConstraint) );
e.setAttribute( "show-negativefloat", QString::number(m_ganttdelegate->showNegativeFloat) );
GanttViewBase::saveContext( e );
m_printOptions.saveContext( e );
}
//-------------------------------------------
MyKGanttView::MyKGanttView( QWidget *parent )
: NodeGanttViewBase( parent ),
m_manager( 0 )
{
debugPlan<<"------------------- create MyKGanttView -----------------------";
GanttItemModel *gm = new GanttItemModel( this );
setItemModel( gm );
treeView()->createItemDelegates( gm );
QList<int> show;
show << NodeModel::NodeName
<< NodeModel::NodeCompleted
<< NodeModel::NodeStartTime
<< NodeModel::NodeEndTime;
treeView()->setDefaultColumns( show );
for ( int i = 0; i < model()->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
treeView()->hideColumn( i );
}
}
setConstraintModel( new KGantt::ConstraintModel( this ) );
KGantt::ProxyModel *m = static_cast<KGantt::ProxyModel*>( 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->removeColumn( Qt::DisplayRole );
m->setColumn( KGantt::ItemTypeRole, NodeModel::NodeType );
m->setColumn( KGantt::StartTimeRole, NodeModel::NodeStartTime );
m->setColumn( KGantt::EndTimeRole, NodeModel::NodeEndTime );
m->setColumn( KGantt::TaskCompletionRole, NodeModel::NodeCompleted );
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
g->setDayWidth( 30 );
// TODO: extend QLocale/KGantt to support formats for hourly time display
// see bug #349030
// removed custom code here
connect( model(), &NodeItemModel::nodeInserted, this, &MyKGanttView::slotNodeInserted );
}
GanttItemModel *MyKGanttView::model() const
{
return static_cast<GanttItemModel*>( NodeGanttViewBase::model() );
}
void MyKGanttView::setProject( Project *proj )
{
clearDependencies();
if ( project() ) {
disconnect( project(), &Project::relationToBeModified, this, &MyKGanttView::removeDependency);
disconnect( project(), &Project::relationModified, this, &MyKGanttView::addDependency);
disconnect( project(), &Project::relationAdded, this, &MyKGanttView::addDependency );
disconnect( project(), &Project::relationToBeRemoved, this, &MyKGanttView::removeDependency );
disconnect( project(), &Project::projectCalculated, this, &MyKGanttView::slotProjectCalculated );
}
NodeGanttViewBase::setProject( proj );
if ( proj ) {
connect( project(), &Project::relationToBeModified, this, &MyKGanttView::removeDependency);
connect( project(), &Project::relationModified, this, &MyKGanttView::addDependency);
connect( proj, &Project::relationAdded, this, &MyKGanttView::addDependency );
connect( proj, &Project::relationToBeRemoved, this, &MyKGanttView::removeDependency );
connect( proj, &Project::projectCalculated, this, &MyKGanttView::slotProjectCalculated );
}
createDependencies();
}
void MyKGanttView::slotProjectCalculated( ScheduleManager *sm )
{
if ( m_manager == sm ) {
setScheduleManager( sm );
}
}
void MyKGanttView::setScheduleManager( ScheduleManager *sm )
{
clearDependencies();
m_manager = sm;
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
if ( sm && project() ) {
QDateTime start = project()->startTime( sm->scheduleId() );
if ( g->startDateTime() != start ) {
g->setStartDateTime( start );
}
}
if ( ! g->startDateTime().isValid() ) {
g->setStartDateTime( QDateTime::currentDateTime() );
}
model()->setScheduleManager( sm );
createDependencies();
}
void MyKGanttView::slotNodeInserted( Node *node )
{
foreach( Relation *r, node->dependChildNodes() ) {
addDependency( r );
}
foreach( Relation *r, node->dependParentNodes() ) {
addDependency( r );
}
}
void MyKGanttView::addDependency( Relation *rel )
{
QModelIndex par = sfModel()->mapFromSource( model()->index( rel->parent() ) );
QModelIndex ch = sfModel()->mapFromSource( model()->index( rel->child() ) );
// debugPlan<<"addDependency() "<<model()<<par.model();
if ( par.isValid() && ch.isValid() ) {
KGantt::Constraint con( par, ch, KGantt::Constraint::TypeSoft,
static_cast<KGantt::Constraint::RelationType>( rel->type() )/*NOTE!!*/
);
if ( ! constraintModel()->hasConstraint( con ) ) {
constraintModel()->addConstraint( con );
}
}
}
void MyKGanttView::removeDependency( Relation *rel )
{
QModelIndex par = sfModel()->mapFromSource( model()->index( rel->parent() ) );
QModelIndex ch = sfModel()->mapFromSource( model()->index( rel->child() ) );
KGantt::Constraint con( par, ch, KGantt::Constraint::TypeSoft,
static_cast<KGantt::Constraint::RelationType>( rel->type() )/*NOTE!!*/
);
constraintModel()->removeConstraint( con );
}
void MyKGanttView::clearDependencies()
{
constraintModel()->clear();
// Remove old deps from view
// NOTE: This should be handled by KGantt
graphicsView()->updateScene();
}
void MyKGanttView::createDependencies()
{
clearDependencies();
if ( project() == 0 || m_manager == 0 ) {
return;
}
foreach ( Node* n, project()->allNodes() ) {
foreach ( Relation *r, n->dependChildNodes() ) {
addDependency( r );
}
}
}
//------------------------------------------
GanttView::GanttView(KoPart *part, KoDocument *doc, QWidget *parent, bool readWrite)
: ViewBase(part, doc, parent),
m_readWrite( readWrite ),
m_project( 0 )
{
debugPlan <<" ---------------- KPlato: Creating GanttView ----------------";
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin( 0 );
m_splitter = new QSplitter( this );
l->addWidget( m_splitter );
m_splitter->setOrientation( Qt::Vertical );
m_gantt = new MyKGanttView( m_splitter );
connect(this, &ViewBase::expandAll, m_gantt->treeView(), &TreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_gantt->treeView(), &TreeViewBase::slotCollapse);
setupGui();
updateReadWrite( readWrite );
//connect( m_gantt->constraintModel(), SIGNAL(constraintAdded(Constraint)), this, SLOT(update()) );
debugPlan <<m_gantt->constraintModel();
connect( m_gantt->treeView(), &TreeViewBase::contextMenuRequested, this, &GanttView::slotContextMenuRequested );
connect( m_gantt->treeView(), &TreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Gantt View</title>"
"<para>"
"Displays scheduled tasks in a Gantt diagram."
" The chart area can be zoomed in and out with a slider"
" positioned in the upper left corner of the time scale."
" <note>You need to hoover over it with the mouse for it to show.</note>"
"</para><para>"
"This view supports configuration and printing using the context menu of the tree view."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Task_Gantt_View")));
}
KoPrintJob *GanttView::createPrintJob()
{
return new GanttPrintingDialog( this, m_gantt );
}
void GanttView::setZoom( double )
{
//debugPlan <<"setting gantt zoom:" << zoom;
//m_gantt->setZoomFactor(zoom,true); NO!!! setZoomFactor() is something else
}
void GanttView::setupGui()
{
// create context menu actions
actionShowProject = new KToggleAction( i18n( "Show Project" ), this );
// FIXME: Dependencies depend on these methods being called in the correct order
connect(actionShowProject, &QAction::triggered, m_gantt, &MyKGanttView::clearDependencies);
connect(actionShowProject, &QAction::triggered, m_gantt->model(), &NodeItemModel::setShowProject);
connect(actionShowProject, &QAction::triggered, m_gantt, &MyKGanttView::createDependencies);
addContextAction( actionShowProject );
createOptionActions(ViewBase::OptionAll);
}
void GanttView::slotOptions()
{
debugPlan;
GanttViewSettingsDialog *dlg = new GanttViewSettingsDialog( m_gantt, m_gantt->delegate(), this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void GanttView::slotOptionsFinished( int result )
{
GanttViewSettingsDialog *dlg = qobject_cast<GanttViewSettingsDialog*>( sender() );
if ( dlg && result == QDialog::Accepted ) {
m_gantt->graphicsView()->updateScene();
}
ViewBase::slotOptionsFinished( result );
}
void GanttView::clear()
{
// m_gantt->clear();
}
void GanttView::setShowResources( bool on )
{
m_gantt->delegate()->showResources = on;
}
void GanttView::setShowTaskName( bool on )
{
m_gantt->delegate()->showTaskName = on;
}
void GanttView::setShowProgress( bool on )
{
m_gantt->delegate()->showProgress = on;
}
void GanttView::setShowPositiveFloat( bool on )
{
m_gantt->delegate()->showPositiveFloat = on;
}
void GanttView::setShowCriticalTasks( bool on )
{
m_gantt->delegate()->showCriticalTasks = on;
}
void GanttView::setShowCriticalPath( bool on )
{
m_gantt->delegate()->showCriticalPath = on;
}
void GanttView::setShowNoInformation( bool on )
{
m_gantt->delegate()->showNoInformation = on;
}
void GanttView::setShowAppointments( bool on )
{
m_gantt->delegate()->showAppointments = on;
}
void GanttView::setShowTaskLinks( bool on )
{
m_gantt->delegate()->showTaskLinks = on;
}
void GanttView::setProject( Project *project )
{
m_gantt->setProject( project );
}
void GanttView::setScheduleManager( ScheduleManager *sm )
{
if (!sm && scheduleManager()) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
m_gantt->treeView()->saveExpanded(element);
}
bool tryexpand = sm && !scheduleManager();
bool expand = sm && scheduleManager() && sm != scheduleManager();
QDomDocument doc;
if (expand) {
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
m_gantt->treeView()->saveExpanded(element);
}
ViewBase::setScheduleManager(sm);
m_gantt->setScheduleManager( sm );
if (expand) {
m_gantt->treeView()->doExpand(doc);
} else if (tryexpand) {
m_gantt->treeView()->doExpand(m_domdoc);
}
}
void GanttView::draw( Project &project )
{
setProject( &project );
}
void GanttView::drawChanges( Project &project )
{
if ( m_project != &project ) {
setProject( &project );
}
}
Node *GanttView::currentNode() const
{
QModelIndex idx = m_gantt->treeView()->selectionModel()->currentIndex();
return m_gantt->model()->node( m_gantt->sfModel()->mapToSource( idx ) );
}
void GanttView::slotContextMenuRequested( const QModelIndex &idx, const QPoint &pos )
{
debugPlan;
QString name;
Node *node = m_gantt->model()->node( m_gantt->sfModel()->mapToSource( idx ) );
if ( node ) {
switch ( node->type() ) {
case Node::Type_Task:
name = "taskview_popup";
break;
case Node::Type_Milestone:
name = "taskview_milestone_popup";
break;
case Node::Type_Summarytask:
name = "taskview_summary_popup";
break;
default:
break;
}
} else debugPlan<<"No node";
m_gantt->treeView()->setContextMenuIndex(idx);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
m_gantt->treeView()->setContextMenuIndex(QModelIndex());
debugPlan<<"No menu";
return;
}
emit requestPopupMenu( name, pos );
m_gantt->treeView()->setContextMenuIndex(QModelIndex());
}
bool GanttView::loadContext( const KoXmlElement &settings )
{
debugPlan;
ViewBase::loadContext( settings );
bool show = (bool)(settings.attribute( "show-project", "0" ).toInt() );
actionShowProject->setChecked( show );
m_gantt->model()->setShowProject( show ); // why is this not called by the action?
return m_gantt->loadContext( settings );
}
void GanttView::saveContext( QDomElement &settings ) const
{
debugPlan;
ViewBase::saveContext( settings );
settings.setAttribute( "show-project", QString::number(actionShowProject->isChecked()) );
m_gantt->saveContext( settings );
}
void GanttView::updateReadWrite( bool on )
{
// TODO: KGanttView needs read/write mode
m_readWrite = on;
}
//----
MilestoneGanttViewSettingsDialog::MilestoneGanttViewSettingsDialog( GanttViewBase *gantt, ViewBase *view, bool selectPrint )
: ItemViewSettupDialog( view, gantt->treeView(), true, view ),
m_gantt( gantt )
{
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_printingoptions = new GanttPrintingOptionsWidget( this );
m_printingoptions->setOptions( gantt->printingOptions() );
tab->addTab( m_printingoptions, m_printingoptions->windowTitle() );
KPageWidgetItem *page = insertWidget( -1, tab, i18n( "Printing" ), i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, SIGNAL(accepted()), this, SLOT(slotOk()) );
}
void MilestoneGanttViewSettingsDialog::slotOk()
{
debugPlan;
m_gantt->setPrintingOptions( m_printingoptions->options());
ItemViewSettupDialog::slotOk();
}
//------------------------
MilestoneKGanttView::MilestoneKGanttView( QWidget *parent )
: NodeGanttViewBase( parent ),
m_manager( 0 )
{
debugPlan<<"------------------- create MilestoneKGanttView -----------------------";
MilestoneItemModel *mm = new MilestoneItemModel( this );
setItemModel( mm );
treeView()->createItemDelegates( mm );
sfModel()->setFilterRole ( Qt::EditRole );
sfModel()->setFilterFixedString( QString::number( Node::Type_Milestone ) );
sfModel()->setFilterKeyColumn( NodeModel::NodeType );
QList<int> show;
show << NodeModel::NodeWBSCode
<< NodeModel::NodeName
<< NodeModel::NodeStartTime;
treeView()->setDefaultColumns( show );
for ( int i = 0; i < model()->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
treeView()->hideColumn( i );
}
}
treeView()->header()->moveSection(NodeModel::NodeWBSCode, show.indexOf(NodeModel::NodeWBSCode));
treeView()->setRootIsDecorated ( false );
KGantt::ProxyModel *m = static_cast<KGantt::ProxyModel*>( 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->removeColumn( Qt::DisplayRole );
m->setColumn( KGantt::ItemTypeRole, NodeModel::NodeType );
m->setColumn( KGantt::StartTimeRole, NodeModel::NodeStartTime );
m->setColumn( KGantt::EndTimeRole, NodeModel::NodeEndTime );
m->setColumn( KGantt::TaskCompletionRole, NodeModel::NodeCompleted );
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
g->setDayWidth( 30 );
// TODO: extend QLocale/KGantt to support formats for hourly time display
// see bug #349030
// removed custom code here
// TODO: add to context
treeView()->sortByColumn(NodeModel::NodeWBSCode, Qt::AscendingOrder);
treeView()->setSortingEnabled(true);
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Milestone Gantt View</title>"
"<para>"
"Displays scheduled milestones in a Gantt diagram."
" The chart area can be zoomed in and out with a slider"
" positioned in the upper left corner of the time scale."
" <note>You need to hoover over it with the mouse for it to show.</note>"
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Milestone_Gantt_View")));
}
MilestoneItemModel *MilestoneKGanttView::model() const
{
return static_cast<MilestoneItemModel*>( NodeGanttViewBase::model() );
}
void MilestoneKGanttView::setProject( Project *proj )
{
if ( project() ) {
disconnect( project(), &Project::projectCalculated, this, &MilestoneKGanttView::slotProjectCalculated );
}
NodeGanttViewBase::setProject( proj );
if ( proj ) {
connect( proj, &Project::projectCalculated, this, &MilestoneKGanttView::slotProjectCalculated );
}
}
void MilestoneKGanttView::slotProjectCalculated( ScheduleManager *sm )
{
if ( m_manager == sm ) {
setScheduleManager( sm );
}
}
void MilestoneKGanttView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan<<id<<endl;
model()->setScheduleManager( 0 );
m_manager = sm;
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
if ( sm && m_project ) {
QDateTime start;
foreach ( const Node *n, model()->mileStones() ) {
QDateTime nt = n->startTime( sm->scheduleId() );
if ( ! nt.isValid() ) {
continue;
}
if ( ! start.isValid() || start > nt ) {
start = nt;
debugPlan<<n->name()<<start;
}
}
if ( ! start.isValid() ) {
start = project()->startTime( sm->scheduleId() );
}
if ( g->startDateTime() != start ) {
g->setStartDateTime( start );
}
}
if ( ! g->startDateTime().isValid() ) {
g->setStartDateTime( QDateTime::currentDateTime() );
}
model()->setScheduleManager( sm );
}
//------------------------------------------
MilestoneGanttView::MilestoneGanttView(KoPart *part, KoDocument *doc, QWidget *parent, bool readWrite)
: ViewBase(part, doc, parent),
m_readWrite( readWrite ),
m_project( 0 )
{
debugPlan <<" ---------------- Plan: Creating Milesone GanttView ----------------";
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin( 0 );
m_splitter = new QSplitter( this );
l->addWidget( m_splitter );
m_splitter->setOrientation( Qt::Vertical );
setupGui();
m_gantt = new MilestoneKGanttView( m_splitter );
m_showTaskName = false; // FIXME
m_showProgress = false; //FIXME
m_showPositiveFloat = false; //FIXME
m_showCriticalTasks = false; //FIXME
m_showNoInformation = false; //FIXME
updateReadWrite( readWrite );
connect( m_gantt->treeView(), &TreeViewBase::contextMenuRequested, this, &MilestoneGanttView::slotContextMenuRequested );
connect( m_gantt->treeView(), &TreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
}
void MilestoneGanttView::setZoom( double )
{
//debugPlan <<"setting gantt zoom:" << zoom;
//m_gantt->setZoomFactor(zoom,true); NO!!! setZoomFactor() is something else
}
void MilestoneGanttView::show()
{
}
void MilestoneGanttView::clear()
{
}
void MilestoneGanttView::setProject( Project *project )
{
m_gantt->setProject( project );
}
void MilestoneGanttView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan<<id<<endl;
m_gantt->setScheduleManager( sm );
}
void MilestoneGanttView::draw( Project &project )
{
setProject( &project );
}
void MilestoneGanttView::drawChanges( Project &project )
{
if ( m_project != &project ) {
setProject( &project );
}
}
Node *MilestoneGanttView::currentNode() const
{
QModelIndex idx = m_gantt->treeView()->selectionModel()->currentIndex();
return m_gantt->model()->node( m_gantt->sfModel()->mapToSource( idx ) );
}
void MilestoneGanttView::setupGui()
{
createOptionActions(ViewBase::OptionAll);
}
void MilestoneGanttView::slotContextMenuRequested( const QModelIndex &idx, const QPoint &pos )
{
debugPlan;
QString name;
Node *node = m_gantt->model()->node( m_gantt->sfModel()->mapToSource( idx ) );
if ( node ) {
switch ( node->type() ) {
case Node::Type_Task:
name = "taskview_popup";
break;
case Node::Type_Milestone:
name = "taskview_milestone_popup";
break;
case Node::Type_Summarytask:
name = "taskview_summary_popup";
break;
default:
break;
}
} else debugPlan<<"No node";
m_gantt->treeView()->setContextMenuIndex(idx);
if ( name.isEmpty() ) {
debugPlan<<"No menu";
slotHeaderContextMenuRequested( pos );
m_gantt->treeView()->setContextMenuIndex(QModelIndex());
return;
}
emit requestPopupMenu( name, pos );
m_gantt->treeView()->setContextMenuIndex(QModelIndex());
}
void MilestoneGanttView::slotOptions()
{
debugPlan;
MilestoneGanttViewSettingsDialog *dlg = new MilestoneGanttViewSettingsDialog( m_gantt, this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool MilestoneGanttView::loadContext( const KoXmlElement &settings )
{
debugPlan;
ViewBase::loadContext( settings );
return m_gantt->loadContext( settings );
}
void MilestoneGanttView::saveContext( QDomElement &settings ) const
{
debugPlan;
ViewBase::saveContext( settings );
return m_gantt->saveContext( settings );
}
void MilestoneGanttView::updateReadWrite( bool on )
{
m_readWrite = on;
}
KoPrintJob *MilestoneGanttView::createPrintJob()
{
return new GanttPrintingDialog( this, m_gantt );
}
//--------------------
ResourceAppointmentsGanttViewSettingsDialog::ResourceAppointmentsGanttViewSettingsDialog( GanttViewBase *gantt, ViewBase *view, bool selectPrint )
: ItemViewSettupDialog( view, gantt->treeView(), true, view )
, m_gantt(gantt)
{
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_printingoptions = new GanttPrintingOptionsWidget( this );
m_printingoptions->setOptions( gantt->printingOptions() );
tab->addTab( m_printingoptions, m_printingoptions->windowTitle() );
KPageWidgetItem *page = insertWidget( -1, tab, i18n( "Printing" ), i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, SIGNAL(accepted()), this, SLOT(slotOk()) );
}
void ResourceAppointmentsGanttViewSettingsDialog::slotOk()
{
debugPlan;
m_gantt->setPrintingOptions( m_printingoptions->options());
ItemViewSettupDialog::slotOk();
}
//------------------------------------------
ResourceAppointmentsGanttView::ResourceAppointmentsGanttView(KoPart *part, KoDocument *doc, QWidget *parent, bool readWrite)
: ViewBase(part, doc, parent),
m_project( 0 ),
m_model( new ResourceAppointmentsGanttModel( this ) )
{
debugPlan <<" ---------------- KPlato: Creating ResourceAppointmentsGanttView ----------------";
m_gantt = new GanttViewBase( this );
m_gantt->graphicsView()->setItemDelegate( new ResourceGanttItemDelegate( m_gantt ) );
GanttTreeView *tv = new GanttTreeView( m_gantt );
tv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
tv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
tv->setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); // needed since qt 4.2
m_gantt->setLeftView( tv );
connect(this, &ViewBase::expandAll, tv, &TreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, tv, &TreeViewBase::slotCollapse);
m_rowController = new KGantt::TreeViewRowController( tv, m_gantt->ganttProxyModel() );
m_gantt->setRowController( m_rowController );
tv->header()->setStretchLastSection( true );
tv->setTreePosition(-1);
KGantt::ProxyModel *m = static_cast<KGantt::ProxyModel*>( m_gantt->ganttProxyModel() );
m->setRole( KGantt::ItemTypeRole, KGantt::ItemTypeRole );
m->setRole( KGantt::StartTimeRole, KGantt::StartTimeRole );
m->setRole( KGantt::EndTimeRole, KGantt::EndTimeRole );
m->setRole( KGantt::TaskCompletionRole, KGantt::TaskCompletionRole );
m_gantt->setModel( m_model );
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin( 0 );
l->addWidget( m_gantt );
setupGui();
updateReadWrite( readWrite );
connect( m_gantt->leftView(), SIGNAL(contextMenuRequested(QModelIndex,QPoint,QModelIndexList)), SLOT(slotContextMenuRequested(QModelIndex,QPoint)) );
connect( m_gantt->leftView(), SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Resource Assignments (Gantt)</title>"
"<para>"
"Displays the scheduled resource - task assignments in a Gantt diagram."
" The chart area can be zoomed in and out with a slider"
" positioned in the upper left corner of the time scale."
" <note>You need to hoover over it with the mouse for it to show.</note>"
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Resource_Assignment_Gantt_View")));
}
ResourceAppointmentsGanttView::~ResourceAppointmentsGanttView()
{
delete m_rowController;
}
void ResourceAppointmentsGanttView::setZoom( double )
{
//debugPlan <<"setting gantt zoom:" << zoom;
//m_gantt->setZoomFactor(zoom,true); NO!!! setZoomFactor() is something else
}
Project *ResourceAppointmentsGanttView::project() const
{
return m_model->project();
}
void ResourceAppointmentsGanttView::setProject( Project *project )
{
m_model->setProject( project );
}
void ResourceAppointmentsGanttView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan<<id<<endl;
if (!sm && scheduleManager()) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
treeView()->saveExpanded(element);
}
bool tryexpand = sm && !scheduleManager();
bool expand = sm && scheduleManager() && sm != scheduleManager();
QDomDocument doc;
if (expand) {
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
treeView()->saveExpanded(element);
}
ViewBase::setScheduleManager(sm);
m_model->setScheduleManager( sm );
if (expand) {
treeView()->doExpand(doc);
} else if (tryexpand) {
treeView()->doExpand(m_domdoc);
}
}
void ResourceAppointmentsGanttView::setupGui()
{
createOptionActions(ViewBase::OptionAll);
}
Node *ResourceAppointmentsGanttView::currentNode() const
{
QModelIndex idx = treeView()->selectionModel()->currentIndex();
return m_model->node( idx );
}
void ResourceAppointmentsGanttView::slotContextMenuRequested( const QModelIndex &idx, const QPoint &pos )
{
debugPlan<<idx;
QString name;
if ( idx.isValid() ) {
Node *n = m_model->node( idx );
if ( n ) {
name = "taskview_popup";
}
}
m_gantt->treeView()->setContextMenuIndex(idx);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
m_gantt->treeView()->setContextMenuIndex(QModelIndex());
return;
}
emit requestPopupMenu( name, pos );
m_gantt->treeView()->setContextMenuIndex(QModelIndex());
}
void ResourceAppointmentsGanttView::slotOptions()
{
debugPlan;
ItemViewSettupDialog *dlg = new ResourceAppointmentsGanttViewSettingsDialog(m_gantt, this, sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool ResourceAppointmentsGanttView::loadContext( const KoXmlElement &settings )
{
debugPlan;
ViewBase::loadContext( settings );
m_gantt->loadContext( settings );
return treeView()->loadContext( m_model->columnMap(), settings );
}
void ResourceAppointmentsGanttView::saveContext( QDomElement &settings ) const
{
debugPlan;
ViewBase::saveContext( settings );
m_gantt->saveContext( settings );
treeView()->saveContext( m_model->columnMap(), settings );
}
void ResourceAppointmentsGanttView::updateReadWrite( bool on )
{
m_readWrite = on;
}
KoPrintJob *ResourceAppointmentsGanttView::createPrintJob()
{
return new GanttPrintingDialog( this, m_gantt );
}
} //KPlato namespace
#include "moc_kptganttview.cpp"
diff --git a/src/libs/ui/kpthtmlview.cpp b/src/libs/ui/kpthtmlview.cpp
index d58accdb..73b0c6c4 100644
--- a/src/libs/ui/kpthtmlview.cpp
+++ b/src/libs/ui/kpthtmlview.cpp
@@ -1,99 +1,100 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kpthtmlview.h"
#include <KoDocument.h>
#include <QVBoxLayout>
#include <QUrl>
#include <khtmlview.h>
#include "kptdebug.h"
namespace KPlato
{
//-----------------------------------
HtmlView::HtmlView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
m_htmlPart = new KHTMLPart( this );
m_htmlPart->view()->setFrameStyle( QFrame::StyledPanel );
m_htmlPart->view()->setFrameShadow( QFrame::Sunken );
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
l->addWidget( m_htmlPart->view() );
m_htmlPart->show();
setupGui();
KParts::BrowserExtension *ext = m_htmlPart->browserExtension();
if ( ext ) {
connect( ext, &KParts::BrowserExtension::openUrlRequest, this, &HtmlView::slotOpenUrlRequest );
}
}
void HtmlView::slotOpenUrlRequest(const QUrl &url, const KParts::OpenUrlArguments &/*arguments*/, const KParts::BrowserArguments &/*browserArguments*/)
{
emit openUrlRequest( this, url );
}
bool HtmlView::openHtml( const QUrl &url )
{
return m_htmlPart->openUrl( url );
}
void HtmlView::updateReadWrite( bool /*readwrite */)
{
}
void HtmlView::setGuiActive( bool activate )
{
debugPlan<<activate;
}
void HtmlView::slotContextMenuRequested( const QModelIndex &/*index*/, const QPoint& /*pos */)
{
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
}
void HtmlView::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void HtmlView::updateActionsEnabled( bool /*on */)
{
}
void HtmlView::setupGui()
{
// Add the context menu actions for the view options
}
KoPrintJob *HtmlView::createPrintJob()
{
return 0;//m_view->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptintervaledit.cpp b/src/libs/ui/kptintervaledit.cpp
index c1f82aef..e005e8bd 100644
--- a/src/libs/ui/kptintervaledit.cpp
+++ b/src/libs/ui/kptintervaledit.cpp
@@ -1,280 +1,281 @@
/* This file is part of the KDE project
Copyright (C) 2004 - 2010 Dag Andersen <danders@get2net.dk>
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 "kptintervaledit.h"
#include "intervalitem.h"
#include "kptcommand.h"
#include "kptproject.h"
#include <KoIcon.h>
#include <KLocalizedString>
#include <QTreeWidget>
#include <QList>
namespace KPlato
{
IntervalEdit::IntervalEdit( CalendarDay *day, QWidget *parent)
: IntervalEditImpl(parent)
{
//debugPlan;
if ( day ) {
const QList<TimeInterval*> &intervals = day->timeIntervals();
setIntervals( intervals );
if ( ! intervals.isEmpty() ) {
startTime->setTime( intervals.last()->endTime() );
qreal l = ( intervals.last()->endTime().msecsTo( QTime().addMSecs( -1 ) ) + 1 ) / (1000.0*60.0*60.0);
length->setValue( qMin( l, (qreal) 8.0 ) );
}
}
enableButtons();
startTime->setFocus();
}
//--------------------------------------------
IntervalEditImpl::IntervalEditImpl(QWidget *parent)
: IntervalEditBase(parent)
{
intervalList->setColumnCount( 2 );
QStringList lst;
lst << i18nc( "Interval start time", "Start" )
<< i18nc( "Interval length", "Length" );
intervalList->setHeaderLabels( lst );
intervalList->setRootIsDecorated( false );
intervalList->setSortingEnabled( true );
intervalList->sortByColumn( 0, Qt::AscendingOrder );
bAddInterval->setIcon(koIcon("list-add"));
bRemoveInterval->setIcon(koIcon("list-remove"));
bClear->setIcon(koIcon("edit-clear-list"));
connect(bClear, &QAbstractButton::clicked, this, &IntervalEditImpl::slotClearClicked);
connect(bAddInterval, &QAbstractButton::clicked, this, &IntervalEditImpl::slotAddIntervalClicked);
connect(bRemoveInterval, &QAbstractButton::clicked, this, &IntervalEditImpl::slotRemoveIntervalClicked);
connect(intervalList, &QTreeWidget::itemSelectionChanged, this, &IntervalEditImpl::slotIntervalSelectionChanged);
connect( startTime, &QDateTimeEdit::timeChanged, this, &IntervalEditImpl::enableButtons );
connect( length, SIGNAL(valueChanged(double)), SLOT(enableButtons()) );
}
void IntervalEditImpl::slotClearClicked() {
bool c = intervalList->topLevelItemCount() > 0;
intervalList->clear();
enableButtons();
if (c)
emit changed();
}
void IntervalEditImpl::slotAddIntervalClicked() {
new IntervalItem(intervalList, startTime->time(), (int)(length->value() * 1000. * 60. *60.) );
enableButtons();
emit changed();
}
void IntervalEditImpl::slotRemoveIntervalClicked() {
IntervalItem *item = static_cast<IntervalItem*>( intervalList->currentItem() );
if ( item == 0) {
return;
}
intervalList->takeTopLevelItem( intervalList->indexOfTopLevelItem( item ) );
delete item;
enableButtons();
emit changed();
}
void IntervalEditImpl::slotIntervalSelectionChanged() {
QList<QTreeWidgetItem*> lst = intervalList->selectedItems();
if (lst.count() == 0)
return;
IntervalItem *ii = static_cast<IntervalItem *>(lst[0]);
startTime->setTime(ii->interval().first);
length->setValue((double)(ii->interval().second) / (1000.*60.*60.));
enableButtons();
}
QList<TimeInterval*> IntervalEditImpl::intervals() const {
QList<TimeInterval*> l;
int cnt = intervalList->topLevelItemCount();
for (int i=0; i < cnt; ++i) {
IntervalItem *item = static_cast<IntervalItem*>(intervalList->topLevelItem(i));
l.append(new TimeInterval(item->interval().first, item->interval().second));
}
return l;
}
void IntervalEditImpl::setIntervals(const QList<TimeInterval*> &intervals) {
intervalList->clear();
foreach (TimeInterval *i, intervals) {
new IntervalItem(intervalList, i->first, i->second);
}
enableButtons();
}
void IntervalEditImpl::enableButtons() {
bClear->setEnabled( ! intervals().isEmpty() );
bRemoveInterval->setEnabled( intervalList->currentItem() );
if ( length->value() == 0.0 ) {
bAddInterval->setEnabled( false );
return;
}
if ( QTime( 0, 0, 0 ).secsTo( startTime->time() ) + (int)(length->value() * 60. * 60.) > 24 * 60 * 60 ) {
bAddInterval->setEnabled( false );
return;
}
TimeInterval ti( startTime->time(), (int)(length->value() * 1000. * 60. *60.) );
foreach (TimeInterval *i, intervals()) {
if ( i->intersects( ti ) ) {
bAddInterval->setEnabled( false );
return;
}
}
bAddInterval->setEnabled( true );
}
//-------------------------------------------------------------
IntervalEditDialog::IntervalEditDialog( Calendar *calendar, const QList<CalendarDay*> &days, QWidget *parent)
: KoDialog( parent ),
m_calendar( calendar ),
m_days( days )
{
//debugPlan;
setCaption( i18n("Edit Work Intervals") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
//debugPlan<<&p;
m_panel = new IntervalEdit( days.value( 0 ), this );
setMainWidget( m_panel );
enableButtonOk( false );
connect( m_panel, &IntervalEditImpl::changed, this, &IntervalEditDialog::slotChanged );
connect( calendar->project(), &Project::calendarRemoved, this, &IntervalEditDialog::slotCalendarRemoved );
}
IntervalEditDialog::IntervalEditDialog( Calendar *calendar, const QList<QDate> &dates, QWidget *parent)
: KoDialog( parent ),
m_calendar( calendar ),
m_dates( dates )
{
//debugPlan;
setCaption( i18n("Edit Work Intervals") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
//debugPlan<<&p;
foreach ( const QDate &d, dates ) {
CalendarDay *day = calendar->findDay( d );
if ( day ) {
m_days << day;
}
}
m_panel = new IntervalEdit( m_days.value( 0 ), this );
setMainWidget( m_panel );
enableButtonOk( false );
connect( m_panel, &IntervalEditImpl::changed, this, &IntervalEditDialog::slotChanged );
connect( calendar->project(), &Project::calendarRemoved, this, &IntervalEditDialog::slotCalendarRemoved );
}
void IntervalEditDialog::slotCalendarRemoved( const Calendar *cal )
{
if ( m_calendar == cal ) {
reject();
}
}
void IntervalEditDialog::slotChanged()
{
enableButtonOk( true );
}
MacroCommand *IntervalEditDialog::buildCommand()
{
MacroCommand *cmd = new MacroCommand( kundo2_i18n( "Modify Work Interval" ) );
foreach ( const QDate &d, m_dates ) {
// these are dates, weekdays don't have date
CalendarDay *day = m_calendar->findDay( d );
if ( day == 0 ) {
// create a new day
day = new CalendarDay( d );
cmd->addCommand( new CalendarAddDayCmd( m_calendar, day ) );
}
MacroCommand *c = buildCommand( m_calendar, day );
if ( c ) {
cmd->addCommand( c );
}
}
if ( m_dates.isEmpty() ) {
// weekdays
foreach ( CalendarDay *day, m_days ) {
MacroCommand *c = buildCommand( m_calendar, day );
if ( c ) {
cmd->addCommand( c );
}
}
}
if ( cmd->isEmpty() ) {
delete cmd;
return 0;
}
return cmd;
}
MacroCommand *IntervalEditDialog::buildCommand( Calendar *calendar, CalendarDay *day )
{
//debugPlan;
const QList<TimeInterval*> lst = m_panel->intervals();
if ( lst == day->timeIntervals() ) {
return 0;
}
MacroCommand *cmd = 0;
// Set to Undefined. This will also clear any intervals
CalendarModifyStateCmd *c = new CalendarModifyStateCmd( calendar, day, CalendarDay::Undefined );
if (cmd == 0) cmd = new MacroCommand(KUndo2MagicString());
cmd->addCommand(c);
//debugPlan<<"Set Undefined";
foreach ( TimeInterval *i, lst ) {
CalendarAddTimeIntervalCmd *c = new CalendarAddTimeIntervalCmd( calendar, day, i );
if (cmd == 0) cmd = new MacroCommand(KUndo2MagicString());
cmd->addCommand(c);
}
if ( ! lst.isEmpty() ) {
CalendarModifyStateCmd *c = new CalendarModifyStateCmd( calendar, day, CalendarDay::Working );
if (cmd == 0) cmd = new MacroCommand(KUndo2MagicString());
cmd->addCommand(c);
}
if (cmd) {
cmd->setText( kundo2_i18n( "Modify Work Interval" ) );
}
return cmd;
}
} //KPlato namespace
diff --git a/src/libs/ui/kptitemviewsettup.cpp b/src/libs/ui/kptitemviewsettup.cpp
index c19efcca..f749123f 100644
--- a/src/libs/ui/kptitemviewsettup.cpp
+++ b/src/libs/ui/kptitemviewsettup.cpp
@@ -1,290 +1,291 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptitemviewsettup.h"
#include "kptitemmodelbase.h"
#include "kptviewbase.h"
#include "kptdebug.h"
#include "KoPageLayoutWidget.h"
#include <QHeaderView>
#include <QPushButton>
namespace KPlato
{
ItemViewSettup::Item::Item( int column, const QString &text )
: QListWidgetItem( text ),
m_column( column )
{
}
int ItemViewSettup::Item::column() const
{
return m_column;
}
bool ItemViewSettup::Item::operator<( const QListWidgetItem & other ) const
{
return m_column < static_cast<const Item&>( other ).column();
}
//--------------------------
ItemViewSettup::ItemViewSettup( TreeViewBase *view, bool includeColumn0, QWidget *parent )
: QWidget( parent ),
m_view( view ),
m_includeColumn0( includeColumn0 )
{
setupUi( this );
stretchLastSection->setChecked( view->header()->stretchLastSection() );
QAbstractItemModel *model = view->model();
QMap<int, Item*> map;
int c = includeColumn0 ? 0 : 1;
debugPlan<<includeColumn0<<c;
for ( ; c < model->columnCount(); ++c ) {
Item *item = new Item( c, model->headerData( c, Qt::Horizontal ).toString() );
item->setToolTip( model->headerData( c, Qt::Horizontal, Qt::ToolTipRole ).toString() );
if ( view->isColumnHidden( c ) ) {
selector->availableListWidget()->addItem( item );
} else {
map.insert( view->section( c ), item );
}
}
foreach( Item *i, map ) {
selector->selectedListWidget()->addItem( i );
}
connect( stretchLastSection, &QCheckBox::stateChanged, this, &ItemViewSettup::slotChanged );
connect( selector, &KActionSelector::added, this, &ItemViewSettup::slotChanged );
connect( selector, &KActionSelector::removed, this, &ItemViewSettup::slotChanged );
connect( selector, &KActionSelector::movedUp, this, &ItemViewSettup::slotChanged );
connect( selector, &KActionSelector::movedDown, this, &ItemViewSettup::slotChanged );
}
void ItemViewSettup::slotChanged()
{
emit enableButtonOk( true );
}
void ItemViewSettup::slotOk()
{
debugPlan;
QListWidget *lst = selector->availableListWidget();
for ( int r = 0; r < lst->count(); ++r ) {
int c = static_cast<Item*>( lst->item( r ) )->column();
m_view->hideColumn( c );
}
lst = selector->selectedListWidget();
for ( int r = 0; r < lst->count(); ++r ) {
int c = static_cast<Item*>( lst->item( r ) )->column();
m_view->mapToSection( c, r );
m_view->showColumn( c );
}
m_view->setStretchLastSection( stretchLastSection->isChecked() );
}
void ItemViewSettup::setDefault()
{
debugPlan;
selector->availableListWidget()->clear();
selector->selectedListWidget()->clear();
QAbstractItemModel *model = m_view->model();
int c = m_includeColumn0 ? 0 : 1;
QList<int> def = m_view->defaultColumns();
for ( ; c < model->columnCount(); ++c ) {
if ( ! def.contains( c ) ) {
Item *item = new Item( c, model->headerData( c, Qt::Horizontal ).toString() );
item->setToolTip( model->headerData( c, Qt::Horizontal, Qt::ToolTipRole ).toString() );
selector->availableListWidget()->addItem( item );
}
}
foreach ( int i, def ) {
Item *item = new Item( i, model->headerData( i, Qt::Horizontal ).toString() );
item->setToolTip( model->headerData( i, Qt::Horizontal, Qt::ToolTipRole ).toString() );
selector->selectedListWidget()->addItem( item );
}
}
//---------------------------
ItemViewSettupDialog::ItemViewSettupDialog( ViewBase *view, TreeViewBase *treeview, bool includeColumn0, QWidget *parent )
: KPageDialog( parent ),
m_view( view ),
m_treeview( treeview ),
m_pagelayout( 0 ),
m_headerfooter( 0 )
{
setWindowTitle( i18n("View Settings") );
setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
button(QDialogButtonBox::Ok)->setDefault(true);
button( QDialogButtonBox::RestoreDefaults )->setEnabled( ! treeview->defaultColumns().isEmpty() );
m_panel = new ItemViewSettup( treeview, includeColumn0 );
KPageWidgetItem *page = new KPageWidgetItem( m_panel, i18n( "Tree View" ) );
page->setHeader( i18n( "Tree View Column Configuration" ) );
addPage( page );
m_pageList.append( page );
connect(this, &QDialog::accepted, this, &ItemViewSettupDialog::slotOk);
connect(this, &QDialog::accepted, m_panel, &ItemViewSettup::slotOk);
connect(button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, m_panel, &ItemViewSettup::setDefault);
}
void ItemViewSettupDialog::slotOk()
{
debugPlan<<m_view<<m_pagelayout<<m_headerfooter;
if ( ! m_view ) {
return;
}
if ( m_pagelayout ) {
m_view->setPageLayout( m_pagelayout->pageLayout() );
}
if ( m_headerfooter ) {
m_view->setPrintingOptions( m_headerfooter->options() );
}
}
KPageWidgetItem *ItemViewSettupDialog::insertWidget( int index, QWidget *widget, const QString &name, const QString &header )
{
KPageWidgetItem *before = m_pageList.value( index );
KPageWidgetItem *page = new KPageWidgetItem( widget, name );
page->setHeader( header );
if ( before ) {
insertPage( before, page );
m_pageList.insert( index, page );
} else {
addPage( page );
m_pageList.append( page );
}
return page;
}
void ItemViewSettupDialog::addPrintingOptions(bool setAsCurrent)
{
if ( ! m_view ) {
return;
}
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( m_view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_headerfooter = ViewBase::createHeaderFooterWidget( m_view );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
KPageWidgetItem *itm = insertWidget( -1, tab, i18n( "Printing" ), i18n( "Printing Options" ) );
if (setAsCurrent) {
setCurrentPage(itm);
}
}
//-------------------------------
SplitItemViewSettupDialog::SplitItemViewSettupDialog( ViewBase *view, DoubleTreeViewBase *treeview, QWidget *parent )
: KPageDialog( parent ),
m_view( view ),
m_treeview( treeview ),
m_pagelayout( 0 ),
m_headerfooter( 0 )
{
setWindowTitle( i18n("View Settings") );
setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
button(QDialogButtonBox::Ok)->setDefault(true);
bool nodef = treeview->masterView()->defaultColumns().isEmpty() || treeview->slaveView()->defaultColumns().isEmpty();
button( QDialogButtonBox::Ok )->setEnabled( ! nodef );
m_page1 = new ItemViewSettup( treeview->masterView(), true );
KPageWidgetItem *page = new KPageWidgetItem( m_page1, i18n( "Main View" ) );
page->setHeader( i18n( "Main View Column Configuration" ) );
addPage( page );
m_pageList.append( page );
m_page2 = new ItemViewSettup( treeview->slaveView(), true );
page = new KPageWidgetItem( m_page2, i18n( "Auxiliary View" ) );
page->setHeader( i18n( "Auxiliary View Column Configuration" ) );
addPage( page );
m_pageList.append( page );
//connect( m_page1, SIGNAL(enableButtonOk(bool)), this, SLOT(enableButtonOk(bool)) );
//connect( m_page2, SIGNAL(enableButtonOk(bool)), this, SLOT(enableButtonOk(bool)) );
connect( this, &QDialog::accepted, this, &SplitItemViewSettupDialog::slotOk );
connect( this, &QDialog::accepted, m_page1, &ItemViewSettup::slotOk );
connect( this, &QDialog::accepted, m_page2, &ItemViewSettup::slotOk );
connect( button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, m_page1, &ItemViewSettup::setDefault );
connect( button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, m_page2, &ItemViewSettup::setDefault );
}
void SplitItemViewSettupDialog::slotOk()
{
debugPlan;
if ( ! m_view ) {
return;
}
m_view->setPageLayout( m_pagelayout->pageLayout() );
m_view->setPrintingOptions( m_headerfooter->options() );
}
KPageWidgetItem *SplitItemViewSettupDialog::insertWidget( int index, QWidget *widget, const QString &name, const QString &header )
{
KPageWidgetItem *before = m_pageList.value( index );
KPageWidgetItem *page = new KPageWidgetItem( widget, name );
page->setHeader( header );
if ( before ) {
insertPage( before, page );
m_pageList.insert( index, page );
} else {
addPage( page );
m_pageList.append( page );
}
return page;
}
void SplitItemViewSettupDialog::addPrintingOptions(bool setAsCurrent)
{
if ( ! m_view ) {
return;
}
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( m_view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_pagelayout->setPageLayout( m_view->pageLayout() );
m_headerfooter = ViewBase::createHeaderFooterWidget( m_view );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
m_headerfooter->setOptions( m_view->printingOptions() );
KPageWidgetItem *itm = insertWidget( -1, tab, i18n( "Printing" ), i18n( "Printing Options" ) );
if (setAsCurrent) {
setCurrentPage(itm);
}
}
} //namespace KPlato
diff --git a/src/libs/ui/kptlocaleconfigmoneydialog.cpp b/src/libs/ui/kptlocaleconfigmoneydialog.cpp
index 3881e696..f5ad0fc6 100644
--- a/src/libs/ui/kptlocaleconfigmoneydialog.cpp
+++ b/src/libs/ui/kptlocaleconfigmoneydialog.cpp
@@ -1,63 +1,64 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "kptlocaleconfigmoneydialog.h"
#include "locale/localemon.h"
#include "kptcommand.h"
#include "kptlocale.h"
namespace KPlato
{
LocaleConfigMoneyDialog::LocaleConfigMoneyDialog( Locale *locale, QWidget *p)
: KoDialog( p)
{
setCaption( i18n("Currency Settings") );
setButtons( Ok|Cancel );
showButtonSeparator( true );
m_panel = new LocaleConfigMoney( locale, this);
setMainWidget(m_panel);
enableButtonOk(false);
connect(m_panel, &LocaleConfigMoney::localeChanged, this, &LocaleConfigMoneyDialog::slotChanged);
}
void LocaleConfigMoneyDialog::slotChanged() {
enableButtonOk(true);
}
KUndo2Command *LocaleConfigMoneyDialog::buildCommand( Project &project ) {
MacroCommand *m = new ModifyProjectLocaleCmd( project, kundo2_i18n( "Modify currency settings" ) );
MacroCommand *cmd = m_panel->buildCommand();
if (cmd) {
m->addCommand(cmd);
}
if ( m->isEmpty() ) {
delete m;
return 0;
}
return m;
}
} //KPlato namespace
diff --git a/src/libs/ui/kptmainprojectdialog.cpp b/src/libs/ui/kptmainprojectdialog.cpp
index 5841a978..8513a29c 100644
--- a/src/libs/ui/kptmainprojectdialog.cpp
+++ b/src/libs/ui/kptmainprojectdialog.cpp
@@ -1,91 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 2003 - 2007 Dag Andersen <danders@get2net.dk>
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 "kptmainprojectdialog.h"
#include "kptproject.h"
#include "kptmainprojectpanel.h"
#include "kptcommand.h"
#include <KLocalizedString>
namespace KPlato
{
MainProjectDialog::MainProjectDialog(Project &p, QWidget *parent, bool edit)
: KoDialog( parent),
project(p)
{
setWindowTitle( i18n("Project Settings") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
panel = new MainProjectPanel(project, this);
panel->projectsLoadBtn->setVisible(edit);
panel->projectsClearBtn->setVisible(edit);
setMainWidget(panel);
enableButtonOk(false);
resize( QSize(500, 410).expandedTo(minimumSizeHint()));
connect(this, &QDialog::rejected, this, &MainProjectDialog::slotRejected);
connect(this, &QDialog::accepted, this, &MainProjectDialog::slotOk);
connect(panel, &MainProjectPanel::obligatedFieldsFilled, this, &KoDialog::enableButtonOk);
connect(panel, &MainProjectPanel::loadResourceAssignments, this, &MainProjectDialog::loadResourceAssignments);
connect(panel, &MainProjectPanel::clearResourceAssignments, this, &MainProjectDialog::clearResourceAssignments);
}
void MainProjectDialog::slotRejected()
{
emit dialogFinished(QDialog::Rejected);
}
void MainProjectDialog::slotOk() {
if (!panel->ok()) {
return;
}
if (panel->loadSharedResources()) {
QString file = panel->resourcesFile->text();
if (file.startsWith('/')) {
file.prepend("file:/");
}
QString place = panel->projectsPlace->text();
if (panel->projectsType->currentIndex() == 0 /*dir*/ && !place.isEmpty() && !place.endsWith('/')) {
place.append('/');
}
QUrl url(place);
emit sigLoadSharedResources(file, url, panel->projectsLoadAtStartup->isChecked());
}
emit dialogFinished(QDialog::Accepted);
}
MacroCommand *MainProjectDialog::buildCommand() {
MacroCommand *m = 0;
KUndo2MagicString c = kundo2_i18n("Modify main project");
MacroCommand *cmd = panel->buildCommand();
if (cmd) {
if (!m) m = new MacroCommand(c);
m->addCommand(cmd);
}
return m;
}
} //KPlato namespace
diff --git a/src/libs/ui/kptmainprojectpanel.cpp b/src/libs/ui/kptmainprojectpanel.cpp
index 5aa23c94..234df93c 100644
--- a/src/libs/ui/kptmainprojectpanel.cpp
+++ b/src/libs/ui/kptmainprojectpanel.cpp
@@ -1,309 +1,310 @@
/* This file is part of the KDE project
Copyright (C) 2004-2007, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptmainprojectpanel.h"
#include "kptdebug.h"
#include <KLocalizedString>
#ifdef PLAN_KDEPIMLIBS_FOUND
#include <akonadi/contact/emailaddressselectiondialog.h>
#include <akonadi/contact/emailaddressselectionwidget.h>
#include <akonadi/contact/emailaddressselection.h>
#endif
#include "kptproject.h"
#include "kptcommand.h"
#include "kptschedule.h"
#include "kpttaskdescriptiondialog.h"
#include <QFileDialog>
namespace KPlato
{
MainProjectPanel::MainProjectPanel(Project &p, QWidget *parent)
: QWidget(parent),
project(p)
{
setupUi(this);
#ifndef PLAN_KDEPIMLIBS_FOUND
chooseLeader->hide();
#endif
// FIXME
// [Bug 311940] New: Plan crashes when typing a text in the filter textbox before the textbook is fully loaded when selecting a contact from the adressbook
chooseLeader->hide();
QString s = i18n( "The Work Breakdown Structure introduces numbering for all tasks in the project, according to the task structure.\nThe WBS code is auto-generated.\nYou can define the WBS code pattern using the Define WBS Pattern command in the Tools menu." );
wbslabel->setWhatsThis( s );
wbs->setWhatsThis( s );
namefield->setText(project.name());
leaderfield->setText(project.leader());
// useSharedResources->setEnabled(!project.isSharedResourcesLoaded());
useSharedResources->setChecked(project.useSharedResources());
resourcesFile->setText(project.sharedResourcesFile());
projectsPlace->setText(project.sharedProjectsUrl().toDisplayString());
qInfo()<<Q_FUNC_INFO<<project.sharedProjectsUrl();
m_description = new TaskDescriptionPanel( p, tabWidget->widget(1) );
m_description->namefield->hide();
m_description->namelabel->hide();
wbs->setText(project.wbsCode());
if ( wbs->text().isEmpty() ) {
wbslabel->hide();
wbs->hide();
}
DateTime st = project.constraintStartTime();
DateTime et = project.constraintEndTime();
startDate->setDate(st.date());
startTime->setTime( QTime( st.time().hour(), st.time().minute(), 0 ) );
endDate->setDate(et.date());
endTime->setTime( QTime( et.time().hour(), et.time().minute(), 0 ) );
enableDateTime();
namefield->setFocus();
useSharedResources->setToolTip(xi18nc("@info:tooltip", "Enables sharing resources with other projects"));
useSharedResources->setWhatsThis(xi18nc("@info:whatsthis",
"<title>Shared resources</title>"
"Resources can be shared between projects"
" to avoid overbooking resources across projects."
" Shared resources must be defined in a separate file,"
" and you must have at least read access to it."
" The projects that share the resources must also be"
" accessible by you."
));
s = xi18nc("@info:tooltip", "File where shared resources are defined");
resourcesLabel->setToolTip(s);
resourcesType->setToolTip(s);
resourcesFile->setToolTip(s);
s = xi18nc("@info:tooltip", "Directory where all the projects that share resources can be found");
projectsLabel->setToolTip(s);
projectsType->setToolTip(s);
projectsPlace->setToolTip(s);
projectsLoadAtStartup->setChecked(project.loadProjectsAtStartup());
projectsLoadAtStartup->setToolTip(xi18nc("@info:tooltip", "Load shared resource assignments at startup"));
projectsLoadBtn->setToolTip(xi18nc("@info:tooltip", "Load (or re-load) shared resource assignments"));
projectsClearBtn->setToolTip(xi18nc("@info:tooltip", "Clear shared resource assignments"));
// signals and slots connections
connect( m_description, &TaskDescriptionPanelImpl::textChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( endDate, &QDateTimeEdit::dateChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( endTime, &QDateTimeEdit::timeChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( startDate, &QDateTimeEdit::dateChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( startTime, &QDateTimeEdit::timeChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( namefield, &QLineEdit::textChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( leaderfield, &QLineEdit::textChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( useSharedResources, &QGroupBox::toggled, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( resourcesFile, &QLineEdit::textChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect( projectsPlace, &QLineEdit::textChanged, this, &MainProjectPanel::slotCheckAllFieldsFilled );
connect(projectsLoadAtStartup, &QAbstractButton::toggled, this, &MainProjectPanel::slotCheckAllFieldsFilled);
connect( chooseLeader, &QAbstractButton::clicked, this, &MainProjectPanel::slotChooseLeader );
connect(resourcesBrowseBtn, &QAbstractButton::clicked, this, &MainProjectPanel::openResourcesFile);
connect(projectsBrowseBtn, &QAbstractButton::clicked, this, &MainProjectPanel::openProjectsPlace);
connect(projectsLoadBtn, &QAbstractButton::clicked, this, &MainProjectPanel::loadProjects);
connect(projectsClearBtn, &QAbstractButton::clicked, this, &MainProjectPanel::clearProjects);
}
bool MainProjectPanel::ok() {
if (useSharedResources->isChecked() && resourcesFile->text().isEmpty()) {
return false;
}
return true;
}
MacroCommand *MainProjectPanel::buildCommand() {
MacroCommand *m = 0;
KUndo2MagicString c = kundo2_i18n("Modify main project");
if (project.name() != namefield->text()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new NodeModifyNameCmd(project, namefield->text()));
}
if (project.leader() != leaderfield->text()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new NodeModifyLeaderCmd(project, leaderfield->text()));
}
if (startDateTime() != project.constraintStartTime()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new ProjectModifyStartTimeCmd(project, startDateTime()));
}
if (endDateTime() != project.constraintEndTime()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new ProjectModifyEndTimeCmd(project, endDateTime()));
}
if (project.useSharedResources() != useSharedResources->isChecked()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new UseSharedResourcesCmd(&project, useSharedResources->isChecked()));
}
if (project.sharedResourcesFile() != resourcesFile->text()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new SharedResourcesFileCmd( &project, resourcesFile->text()));
}
QString place = projectsPlace->text();
if (projectsType->currentIndex() == 0 /*dir*/ && !place.isEmpty() && !place.endsWith('/')) {
place.append('/');
}
QUrl sharedProjectsUrl(place);
if (project.sharedProjectsUrl() != sharedProjectsUrl) {
if (!m) m = new MacroCommand(c);
m->addCommand(new SharedProjectsUrlCmd( &project, sharedProjectsUrl));
}
if (project.loadProjectsAtStartup() != projectsLoadAtStartup->isChecked()) {
if (!m) m = new MacroCommand(c);
m->addCommand(new LoadProjectsAtStartupCmd( &project, projectsLoadAtStartup->isChecked()));
}
MacroCommand *cmd = m_description->buildCommand();
if ( cmd ) {
if (!m) m = new MacroCommand(c);
m->addCommand( cmd );
}
return m;
}
void MainProjectPanel::slotCheckAllFieldsFilled()
{
emit changed();
bool state = !namefield->text().isEmpty();
if (state && useSharedResources->isChecked()) {
state = !resourcesFile->text().isEmpty();
if (state && projectsLoadAtStartup->isChecked()) {
state = !projectsPlace->text().isEmpty();
}
}
emit obligatedFieldsFilled(state);
}
void MainProjectPanel::slotChooseLeader()
{
#ifdef PLAN_KDEPIMLIBS_FOUND
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog( this );
if ( dlg->exec() && dlg ) {
QStringList names;
const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
foreach ( const Akonadi::EmailAddressSelection &selection, selections ) {
QString s = selection.name();
if ( ! selection.email().isEmpty() ) {
if ( ! selection.name().isEmpty() ) {
s += " <";
}
s += selection.email();
if ( ! selection.name().isEmpty() ) {
s += '>';
}
if ( ! s.isEmpty() ) {
names << s;
}
}
}
if ( ! names.isEmpty() ) {
leaderfield->setText( names.join( ", " ) );
}
}
#endif
}
void MainProjectPanel::slotStartDateClicked()
{
enableDateTime();
}
void MainProjectPanel::slotEndDateClicked()
{
enableDateTime();
}
void MainProjectPanel::enableDateTime()
{
debugPlan;
startTime->setEnabled(true);
startDate->setEnabled(true);
endTime->setEnabled(true);
endDate->setEnabled(true);
}
QDateTime MainProjectPanel::startDateTime()
{
return QDateTime(startDate->date(), startTime->time(), Qt::LocalTime);
}
QDateTime MainProjectPanel::endDateTime()
{
return QDateTime(endDate->date(), endTime->time(), Qt::LocalTime);
}
void MainProjectPanel::openResourcesFile()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Resources"), "", tr("Resources file (*.plan)"));
resourcesFile->setText(fileName);
}
void MainProjectPanel::openProjectsPlace()
{
if (projectsType->currentIndex() == 0 /*Directory*/) {
qInfo()<<Q_FUNC_INFO<<"Directory";
QString dirName = QFileDialog::getExistingDirectory(this, tr("Projects Directory"));
projectsPlace->setText(dirName);
return;
}
if (projectsType->currentIndex() == 1 /*File*/) {
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Projects"), "", tr("Projects file (*)"));
projectsPlace->setText(fileName);
return;
}
Q_ASSERT(false); // Unimplemented projects type
}
bool MainProjectPanel::loadSharedResources() const
{
return useSharedResources->isChecked();
}
void MainProjectPanel::loadProjects()
{
QString place = projectsPlace->text();
if (projectsType->currentIndex() == 0 /*dir*/ && !place.isEmpty() && !place.endsWith('/')) {
place.append('/');
}
QUrl url(place);
emit loadResourceAssignments(url);
}
void MainProjectPanel::clearProjects()
{
emit clearResourceAssignments();
}
} //KPlato namespace
diff --git a/src/libs/ui/kptmilestoneprogressdialog.cpp b/src/libs/ui/kptmilestoneprogressdialog.cpp
index d970ee64..95a71fe0 100644
--- a/src/libs/ui/kptmilestoneprogressdialog.cpp
+++ b/src/libs/ui/kptmilestoneprogressdialog.cpp
@@ -1,71 +1,72 @@
/* This file is part of the KDE project
Copyright (C) 2005 - 2007 Dag Andersen <danders@get2net.dk>
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 "kptmilestoneprogressdialog.h"
#include "kptmilestoneprogresspanel.h"
#include "kptnode.h"
#include "kptproject.h"
#include <KLocalizedString>
namespace KPlato
{
class MacroCommand;
MilestoneProgressDialog::MilestoneProgressDialog(Task &task, QWidget *p)
: KoDialog(p),
m_node( &task )
{
setCaption( i18n("Milestone Progress") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
m_panel = new MilestoneProgressPanel(task, this);
setMainWidget(m_panel);
enableButtonOk(false);
connect(m_panel, &MilestoneProgressPanelImpl::changed, this, &MilestoneProgressDialog::slotChanged);
Project *proj = static_cast<Project*>( task.projectNode() );
if ( proj ) {
connect(proj, &Project::nodeRemoved, this, &MilestoneProgressDialog::slotNodeRemoved);
}
}
void MilestoneProgressDialog::slotNodeRemoved( Node *node )
{
if ( m_node == node ) {
reject();
}
}
void MilestoneProgressDialog::slotChanged() {
enableButtonOk(true);
}
MacroCommand *MilestoneProgressDialog::buildCommand() {
return m_panel->buildCommand();
}
} //KPlato namespace
diff --git a/src/libs/ui/kptmilestoneprogresspanel.cpp b/src/libs/ui/kptmilestoneprogresspanel.cpp
index 6a6b7b90..1b2d2536 100644
--- a/src/libs/ui/kptmilestoneprogresspanel.cpp
+++ b/src/libs/ui/kptmilestoneprogresspanel.cpp
@@ -1,111 +1,112 @@
/* This file is part of the KDE project
Copyright (C) 2005-2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptmilestoneprogresspanel.h"
#include <QCheckBox>
#include <QDateTime>
#include <KLocalizedString>
#include "kpttask.h"
#include "kptcommand.h"
#include "kptdebug.h"
namespace KPlato
{
MilestoneProgressPanel::MilestoneProgressPanel(Task &task, QWidget *parent, const char *name)
: MilestoneProgressPanelImpl(parent, name),
m_task(task),
m_completion( task.completion() )
{
debugPlan;
finished->setChecked(m_completion.isFinished());
finishTime->setDateTime(m_completion.finishTime());
enableWidgets();
finished->setFocus();
}
MacroCommand *MilestoneProgressPanel::buildCommand() {
MacroCommand *cmd = 0;
KUndo2MagicString c = kundo2_i18n("Modify milestone completion");
if ( m_completion.isFinished() != finished->isChecked() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionStartedCmd( m_completion, finished->isChecked()) );
cmd->addCommand( new ModifyCompletionFinishedCmd( m_completion, finished->isChecked()) );
}
if ( m_completion.finishTime() != finishTime->dateTime() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionStartTimeCmd( m_completion, finishTime->dateTime() ) );
cmd->addCommand( new ModifyCompletionFinishTimeCmd( m_completion, finishTime->dateTime() ) );
}
if ( finished->isChecked() && finishTime->dateTime().isValid() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionPercentFinishedCmd( m_completion, finishTime->dateTime().date(), 100 ) );
} else {
Completion::EntryList::ConstIterator entriesIt = m_completion.entries().constBegin();
const Completion::EntryList::ConstIterator entriesEnd = m_completion.entries().constEnd();
for (; entriesIt != entriesEnd; ++entriesIt) {
const QDate &date = entriesIt.key();
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new RemoveCompletionEntryCmd( m_completion, date ) );
}
}
return cmd;
}
//-------------------------------------
MilestoneProgressPanelImpl::MilestoneProgressPanelImpl(QWidget *parent, const char *name)
: QWidget(parent) {
setObjectName(name);
setupUi(this);
connect(finished, &QAbstractButton::toggled, this, &MilestoneProgressPanelImpl::slotFinishedChanged);
connect(finished, &QAbstractButton::toggled, this, &MilestoneProgressPanelImpl::slotChanged);
connect(finishTime, &QDateTimeEdit::dateTimeChanged, this, &MilestoneProgressPanelImpl::slotChanged);
}
void MilestoneProgressPanelImpl::slotChanged() {
emit changed();
}
void MilestoneProgressPanelImpl::slotFinishedChanged(bool state) {
if (state) {
finishTime->setDateTime(QDateTime::currentDateTime());
}
enableWidgets();
}
void MilestoneProgressPanelImpl::enableWidgets() {
finished->setEnabled(true);
finishTime->setEnabled(finished->isChecked());
}
} //KPlato namespace
diff --git a/src/libs/ui/kptperformancetablewidget.cpp b/src/libs/ui/kptperformancetablewidget.cpp
index dafeb8f6..571fd4c7 100644
--- a/src/libs/ui/kptperformancetablewidget.cpp
+++ b/src/libs/ui/kptperformancetablewidget.cpp
@@ -1,78 +1,79 @@
/* This file is part of the KDE project
Copyright (C) 2011 Dag Andersen <danders@get2net.dk>
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 "kptperformancetablewidget.h"
#include <QHeaderView>
namespace KPlato
{
PerformanceTableWidget::PerformanceTableWidget( QWidget *parent )
: QTableWidget( parent )
{
horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
verticalHeader()->setSectionResizeMode( QHeaderView::Fixed );
}
QSize PerformanceTableWidget::sizeHint() const
{
QSize s = QTableView::sizeHint();
int h = horizontalHeader()->height();
for ( int r = 0; r < rowCount(); ++r ) {
if ( ! verticalHeader()->isSectionHidden( r ) ) {
h += verticalHeader()->sectionSize( r );
}
}
s.setHeight( h + frameWidth() * 2 );
return s;
}
QSize PerformanceTableWidget::minimumSizeHint() const
{
return sizeHint();
}
//---------
PerformanceTableView::PerformanceTableView( QWidget *parent )
: QTableView( parent )
{
horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
verticalHeader()->setSectionResizeMode( QHeaderView::Fixed );
}
QSize PerformanceTableView::sizeHint() const
{
QSize s = QTableView::sizeHint();
int h = horizontalHeader()->height();
for ( int r = 0; r < verticalHeader()->count(); ++r ) {
if ( ! verticalHeader()->isSectionHidden( r ) ) {
h += verticalHeader()->sectionSize( r );
}
}
s.setHeight( h + frameWidth() * 2 );
return s;
}
QSize PerformanceTableView::minimumSizeHint() const
{
return sizeHint();
}
} // namespace KPlato
diff --git a/src/libs/ui/kptperteditor.cpp b/src/libs/ui/kptperteditor.cpp
index e6e54ab5..c133fae9 100644
--- a/src/libs/ui/kptperteditor.cpp
+++ b/src/libs/ui/kptperteditor.cpp
@@ -1,439 +1,440 @@
/* This file is part of the KDE project
Copyright (C) 2007 Florian Piquemal <flotueur@yahoo.fr>
Copyright (C) 2007 Alexis Ménard <darktears31@gmail.com>
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net>
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 "kptperteditor.h"
#include "kptproject.h"
#include "kptrelationeditor.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <KoPart.h>
#include <KoIcon.h>
#include <QModelIndex>
namespace KPlato
{
//-----------------------------------
PertEditor::PertEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent),
m_project( 0 )
{
debugPlan <<" ---------------- KPlato: Creating PertEditor ----------------";
widget.setupUi(this);
m_tasktree = widget.taskList;
m_tasktree->setSelectionMode( QAbstractItemView::SingleSelection );
m_availableList = widget.available;
m_availableList->setSelectionMode( QAbstractItemView::SingleSelection );
m_requiredList = widget.required;
m_requiredList->hideColumn( 1 ); // child node name
m_requiredList->setEditTriggers( QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed );
connect( m_requiredList->model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
updateReadWrite( doc->isReadWrite() );
widget.addBtn->setIcon(koIcon("arrow-right"));
widget.removeBtn->setIcon(koIcon("arrow-left"));
slotAvailableChanged( 0 );
slotRequiredChanged( QModelIndex() );
connect( m_tasktree, &QTreeWidget::currentItemChanged, this, &PertEditor::slotCurrentTaskChanged );
connect( m_availableList, &QTreeWidget::currentItemChanged, this, &PertEditor::slotAvailableChanged );
connect( m_requiredList->selectionModel(), &QItemSelectionModel::currentChanged, this, &PertEditor::slotRequiredChanged );
connect( widget.addBtn, &QAbstractButton::clicked, this, &PertEditor::slotAddClicked );
connect( widget.removeBtn, &QAbstractButton::clicked, this, &PertEditor::slotRemoveClicked );
connect( this, &PertEditor::executeCommand, doc, &KoDocument::addCommand );
// TODO: need to use TreeViewBase here
// connect(this, SIGNAL(expandAll()), m_tasktree, SLOT(slotExpand()));
// connect(this, SIGNAL(collapseAll()), m_tasktree, SLOT(slotCollapse()));
}
void PertEditor::slotCurrentTaskChanged( QTreeWidgetItem *curr, QTreeWidgetItem *prev )
{
//debugPlan<<curr<<prev;
if ( curr == 0 ) {
m_availableList->clear();
loadRequiredTasksList( 0 );
} else if ( prev == 0 ) {
dispAvailableTasks();
} else {
updateAvailableTasks();
loadRequiredTasksList( itemToNode( curr ) );
}
slotAvailableChanged( m_availableList->currentItem() );
}
void PertEditor::slotAvailableChanged( QTreeWidgetItem *item )
{
//debugPlan<<(item?item->text(0):"nil")<<(item?item->data( 0, EnabledRole ).toBool():false);
if ( item == 0 || item == m_availableList->currentItem() ) {
widget.addBtn->setEnabled( item != 0 && item->data( 0, EnabledRole ).toBool() );
}
}
void PertEditor::slotRequiredChanged( const QModelIndex &item )
{
//debugPlan<<item;
widget.removeBtn->setEnabled( item.isValid() );
}
void PertEditor::slotAddClicked()
{
if ( ! isReadWrite() ) {
return;
}
QTreeWidgetItem *item = m_availableList->currentItem();
//debugPlan<<item;
addTaskInRequiredList( item );
updateAvailableTasks( item );
}
void PertEditor::addTaskInRequiredList(QTreeWidgetItem * currentItem)
{
//debugPlan<<currentItem;
if ( currentItem == 0 ) {
return;
}
if ( m_project == 0 ) {
return;
}
// add the relation between the current task and the current task
QTreeWidgetItem *selectedTask = m_tasktree->currentItem();
if ( selectedTask == 0 ) {
return;
}
Node *par = itemToNode( currentItem );
Node *child = itemToNode( selectedTask );
if ( par == 0 || child == 0 || ! m_project->legalToLink( par, child ) ) {
return;
}
Relation *rel = new Relation ( par, child );
AddRelationCmd *addCmd = new AddRelationCmd( *m_project, rel, kundo2_noi18n(currentItem->text( 0 )) );
emit executeCommand( addCmd );
}
void PertEditor::slotRemoveClicked()
{
if ( ! isReadWrite() ) {
return;
}
Node *n = 0;
Relation *r = m_requiredList->currentRelation();
if ( r ) {
n = r->parent();
}
removeTaskFromRequiredList();
setAvailableItemEnabled( n );
}
void PertEditor::removeTaskFromRequiredList()
{
//debugPlan;
Relation *r = m_requiredList->currentRelation();
if ( r == 0 ) {
return;
}
// remove the relation
emit executeCommand( new DeleteRelationCmd( *m_project, r, kundo2_i18n( "Remove task dependency" ) ) );
}
void PertEditor::setProject( Project *project )
{
if ( m_project ) {
disconnect( m_project, &Project::nodeAdded, this, &PertEditor::slotNodeAdded );
disconnect( m_project, &Project::nodeToBeRemoved, this, &PertEditor::slotNodeRemoved );
disconnect( m_project, &Project::nodeMoved, this, &PertEditor::slotNodeMoved );
disconnect( m_project, &Project::nodeChanged, this, &PertEditor::slotNodeChanged );
disconnect( m_project, &Project::relationAdded, this, &PertEditor::slotRelationAdded );
disconnect( m_project, &Project::relationRemoved, this, &PertEditor::slotRelationRemoved );
}
m_project = project;
if ( m_project ) {
connect( m_project, &Project::nodeAdded, this, &PertEditor::slotNodeAdded );
connect( m_project, &Project::nodeToBeRemoved, this, &PertEditor::slotNodeRemoved );
connect( m_project, &Project::nodeMoved, this, &PertEditor::slotNodeMoved );
connect( m_project, &Project::nodeChanged, this, &PertEditor::slotNodeChanged );
connect( m_project, &Project::relationAdded, this, &PertEditor::slotRelationAdded );
connect( m_project, &Project::relationRemoved, this, &PertEditor::slotRelationRemoved );
}
m_requiredList->setProject( project );
draw();
}
void PertEditor::slotRelationAdded( Relation *rel )
{
debugPlan<<rel;
if ( rel->child() == itemToNode( m_tasktree->currentItem() ) ) {
QTreeWidgetItem *item = findNodeItem( rel->parent(), m_availableList->invisibleRootItem() );
updateAvailableTasks( item );
}
}
void PertEditor::slotRelationRemoved( Relation *rel )
{
debugPlan<<rel;
if ( rel->child() == itemToNode( m_tasktree->currentItem() ) ) {
QTreeWidgetItem *item = findNodeItem( rel->parent(), m_availableList->invisibleRootItem() );
updateAvailableTasks( item );
}
}
void PertEditor::slotNodeAdded( Node *node )
{
debugPlan<<node->name()<<node->childNodeIterator();
Node *parent = node->parentNode();
int index = parent->indexOf( node );
QTreeWidgetItem *pitem = findNodeItem( parent, m_tasktree->invisibleRootItem() );
if ( pitem == 0 ) {
pitem = m_tasktree->invisibleRootItem();
}
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText( 0, node->name() );
item->setData( 0, NodeRole, node->id() );
pitem->insertChild( index, item );
pitem = findNodeItem( parent, m_availableList->invisibleRootItem() );
if ( pitem == 0 ) {
pitem = m_availableList->invisibleRootItem();
}
item = new QTreeWidgetItem();
item->setText( 0, node->name() );
item->setData( 0, NodeRole, node->id() );
item->setData( 0, EnabledRole, true );
pitem->insertChild( index, item );
setAvailableItemEnabled( item );
}
void PertEditor::slotNodeRemoved( Node *node )
{
//debugPlan;
QTreeWidgetItem *item = findNodeItem( node, m_tasktree->invisibleRootItem() );
if ( item ) {
QTreeWidgetItem *parent = item->parent();
if ( parent == 0 ) {
parent = m_tasktree->invisibleRootItem();
}
Q_ASSERT( parent );
parent->removeChild( item );
delete item;
}
item = findNodeItem( node, m_availableList->invisibleRootItem() );
if ( item ) {
QTreeWidgetItem *parent = item->parent();
if ( parent == 0 ) {
parent = m_availableList->invisibleRootItem();
}
Q_ASSERT( parent );
parent->removeChild( item );
delete item;
}
}
void PertEditor::slotNodeMoved( Node */*node */)
{
//debugPlan;
draw();
}
void PertEditor::slotNodeChanged( Node *node )
{
QTreeWidgetItem *item = findNodeItem( node, m_tasktree->invisibleRootItem() );
if ( item ) {
item->setText( 0, node->name() );
}
item = findNodeItem( node, m_availableList->invisibleRootItem() );
if ( item ) {
item->setText( 0, node->name() );
}
}
void PertEditor::draw( Project &project)
{
setProject( &project );
draw();
}
void PertEditor::draw()
{
m_tasktree->clear();
if ( m_project == 0 ) {
return;
}
drawSubTasksName( m_tasktree->invisibleRootItem(), m_project );
}
void PertEditor::drawSubTasksName( QTreeWidgetItem *parent, Node * currentNode)
{
foreach(Node * currentChild, currentNode->childNodeIterator()){
QTreeWidgetItem * item = new QTreeWidgetItem( parent );
item->setText( 0, currentChild->name());
item->setData( 0, NodeRole, currentChild->id() );
//debugPlan<<"Added task"<<currentChild->name()<<"parent"<<currentChild->parent();
drawSubTasksName( item, currentChild);
}
}
void PertEditor::updateReadWrite( bool rw )
{
m_requiredList->setReadWrite( rw );
ViewBase::updateReadWrite( rw );
}
QTreeWidgetItem *PertEditor::findNodeItem( Node *node, QTreeWidgetItem *item ) {
if ( node->id() == item->data( 0, NodeRole ).toString() ) {
return item;
}
for ( int i = 0; i < item->childCount(); ++i ) {
QTreeWidgetItem *itm = findNodeItem( node, item->child( i ) );
if ( itm != 0 ) {
return itm;
}
}
return 0;
}
void PertEditor::dispAvailableTasks( Relation */*rel*/ ){
dispAvailableTasks();
}
void PertEditor::dispAvailableTasks( Node *parent, Node *selectedTask )
{
QTreeWidgetItem *pitem = findNodeItem( parent, m_availableList->invisibleRootItem() );
if ( pitem == 0 ) {
pitem = m_availableList->invisibleRootItem();
}
foreach(Node * currentNode, parent->childNodeIterator() )
{
//debugPlan<<currentNode->name()<<"level="<<currentNode->level();
QTreeWidgetItem *item = new QTreeWidgetItem( QStringList()<<currentNode->name() );
item->setData( 0, NodeRole, currentNode->id() );
pitem->addChild(item);
// Checks it isn't the same as the selected task in the m_tasktree
setAvailableItemEnabled( item );
dispAvailableTasks( currentNode, selectedTask );
}
}
void PertEditor::dispAvailableTasks()
{
m_availableList->clear();
if ( m_project == 0 ) {
return;
}
Node *selectedTask = itemToNode( m_tasktree->currentItem() );
loadRequiredTasksList(selectedTask);
dispAvailableTasks( m_project, selectedTask );
}
void PertEditor::updateAvailableTasks( QTreeWidgetItem *item )
{
//debugPlan<<m_project<<item;
if ( m_project == 0 ) {
return;
}
if ( item == 0 ) {
item = m_availableList->invisibleRootItem();
} else {
setAvailableItemEnabled( item );
}
for ( int i=0; i < item->childCount(); ++i ) {
updateAvailableTasks( item->child( i ) );
}
}
void PertEditor::setAvailableItemEnabled( QTreeWidgetItem *item )
{
//debugPlan<<item;
Node *node = itemToNode( item );
if ( node == 0 ) {
return;
}
Node *selected = itemToNode( m_tasktree->currentItem() );
if ( selected == 0 || ! m_project->legalToLink( node, selected ) ) {
//debugPlan<<"Disable:"<<node->name();
item->setData( 0, EnabledRole, false );
QFont f = item->font( 0 );
f.setItalic( true );
item->setFont( 0, f );
} else {
//debugPlan<<"Enable:"<<node->name();
item->setData( 0, EnabledRole, true );
QFont f = item->font( 0 );
f.setItalic( false );
item->setFont( 0, f );
}
slotAvailableChanged( item );
}
void PertEditor::setAvailableItemEnabled( Node *node )
{
//debugPlan<<node->name();
setAvailableItemEnabled( nodeToItem( node, m_availableList->invisibleRootItem() ) );
}
QTreeWidgetItem *PertEditor::nodeToItem( Node *node, QTreeWidgetItem *item )
{
if ( itemToNode( item ) == node ) {
return item;
}
for ( int i=0; i < item->childCount(); ++i ) {
QTreeWidgetItem *itm = nodeToItem( node, item->child( i ) );
if ( itm ) {
return itm;
}
}
return 0;
}
Node * PertEditor::itemToNode( QTreeWidgetItem *item )
{
if ( m_project == 0 || item == 0 ) {
return 0;
}
return m_project->findNode( item->data( 0, NodeRole ).toString() );
}
void PertEditor::loadRequiredTasksList(Node * taskNode)
{
slotRequiredChanged( QModelIndex() );
m_requiredList->setNode( taskNode );
}
void PertEditor::slotUpdate()
{
draw();
}
} // namespace KPlato
diff --git a/src/libs/ui/kptpertresult.cpp b/src/libs/ui/kptpertresult.cpp
index b3a7122d..ee2d65ae 100644
--- a/src/libs/ui/kptpertresult.cpp
+++ b/src/libs/ui/kptpertresult.cpp
@@ -1,599 +1,600 @@
/* This file is part of the KDE project
Copyright (C) 2007 Florian Piquemal <flotueur@yahoo.fr>
Copyright (C) 2007 Alexis Ménard <darktears31@gmail.com>
Copyright (C) 2007, 2012 Dag Andersen <danders@get2net>
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 "kptpertresult.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptnode.h"
#include "kptschedule.h"
#include "kptitemviewsettup.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <QAbstractItemView>
#include <QMenu>
#include <QAction>
#include <QLocale>
#include <math.h>
namespace KPlato
{
class Project;
class Node;
class Task;
static const double dist[][2] = {
{0.00, 0.5000}, {0.02, 0.5080}, {0.04, 0.5160}, {0.06, 0.5239}, {0.08, 0.5319},
{0.10, 0.5398}, {0.12, 0.5478}, {0.14, 0.5557}, {0.16, 0.5636}, {0.18, 0.5714},
{0.20, 0.5793}, {0.22, 0.5871}, {0.24, 0.5948}, {0.26, 0.6026}, {0.28, 0.6103},
{0.30, 0.6179}, {0.32, 0.6255}, {0.34, 0.6331}, {0.36, 0.6406}, {0.38, 0.6480},
{0.40, 0.6554}, {0.42, 0.6628}, {0.44, 0.6700}, {0.46, 0.6772}, {0.48, 0.6844},
{0.50, 0.6915}, {0.52, 0.6985}, {0.54, 0.7054}, {0.56, 0.7123}, {0.58, 0.7190},
{0.60, 0.7257}, {0.62, 0.7324}, {0.64, 0.7389}, {0.66, 0.7454}, {0.68, 0.7517},
{0.70, 0.7580}, {0.72, 0.7642}, {0.74, 0.7704}, {0.76, 0.7764}, {0.78, 0.7823},
{0.80, 0.7881}, {0.82, 0.7939}, {0.84, 0.7995}, {0.86, 0.8051}, {0.88, 0.8106},
{0.90, 0.8159}, {0.92, 0.8212}, {0.94, 0.8264}, {0.96, 0.8315}, {0.98, 0.8365},
{1.00, 0.8413}, {1.02, 0.8461}, {1.04, 0.8508}, {1.06, 0.8554}, {1.08, 0.8599},
{1.10, 0.8643}, {1.12, 0.8686}, {1.14, 0.8729}, {1.16, 0.8770}, {1.18, 0.8810},
{1.20, 0.8849}, {1.22, 0.8888}, {1.24, 0.8925}, {1.26, 0.8962}, {1.28, 0.8997},
{1.30, 0.9032}, {1.32, 0.9066}, {1.34, 0.9099}, {1.36, 0.9131}, {1.38, 0.9162},
{1.40, 0.9192}, {1.42, 0.9222}, {1.44, 0.9251}, {1.46, 0.9279}, {1.48, 0.9306},
{1.50, 0.9332}, {1.52, 0.9357}, {1.54, 0.9382}, {1.56, 0.9406}, {1.58, 0.9429},
{1.60, 0.9452}, {1.62, 0.9474}, {1.64, 0.9495}, {1.66, 0.9515}, {1.68, 0.9535},
{1.70, 0.9554}, {1.72, 0.9573}, {1.74, 0.9591}, {1.76, 0.9608}, {1.78, 0.9625},
{1.80, 0.9641}, {1.82, 0.9656}, {1.84, 0.9671}, {1.86, 0.9686}, {1.88, 0.9699},
{1.90, 0.9713}, {1.92, 0.9726}, {1.94, 0.9738}, {1.96, 0.9750}, {1.98, 0.9761},
{2.00, 0.9772}, {2.02, 0.9783}, {2.34, 0.9793}, {2.36, 0.9803}, {2.38, 0.9812},
{2.10, 0.9821}, {2.12, 0.9830}, {2.34, 0.9838}, {2.36, 0.9846}, {2.38, 0.9854},
{2.20, 0.9861}, {2.22, 0.9868}, {2.34, 0.9875}, {2.36, 0.9881}, {2.38, 0.9887},
{2.30, 0.9893}, {2.32, 0.9898}, {2.34, 0.9904}, {2.36, 0.9909}, {2.38, 0.9913},
{2.40, 0.9918}, {2.42, 0.9922}, {2.44, 0.9927}, {2.46, 0.9931}, {2.48, 0.9934},
{2.50, 0.9938}, {2.52, 0.9941}, {2.54, 0.9945}, {2.56, 0.9948}, {2.58, 0.9951},
{2.60, 0.9953}, {2.62, 0.9956}, {2.64, 0.9959}, {2.66, 0.9961}, {2.68, 0.9963},
{2.70, 0.9965}, {2.72, 0.9967}, {2.74, 0.9969}, {2.76, 0.9971}, {2.78, 0.9973},
{2.80, 0.9974}, {2.82, 0.9976}, {2.84, 0.9977}, {2.86, 0.9979}, {2.88, 0.9980},
{2.90, 0.9981}, {2.92, 0.9982}, {2.94, 0.9984}, {2.96, 0.9985}, {2.98, 0.9986},
{3.00, 0.9987}
};
//-----------------------------------
PertResult::PertResult(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent ),
m_node( 0 ),
m_project( 0 ),
current_schedule( 0 )
{
debugPlan << " ---------------- KPlato: Creating PertResult ----------------";
widget.setupUi(this);
PertResultItemModel *m = new PertResultItemModel( widget.treeWidgetTaskResult );
widget.treeWidgetTaskResult->setModel( m );
widget.treeWidgetTaskResult->setStretchLastSection( false );
widget.treeWidgetTaskResult->setSelectionMode( QAbstractItemView::ExtendedSelection );
setupGui();
QList<int> lst1; lst1 << 1 << -1; // only display column 0 (NodeName) in left view
QList<int> show;
show << NodeModel::NodeEarlyStart
<< NodeModel::NodeEarlyFinish
<< NodeModel::NodeLateStart
<< NodeModel::NodeLateFinish
<< NodeModel::NodePositiveFloat
<< NodeModel::NodeFreeFloat
<< NodeModel::NodeNegativeFloat
<< NodeModel::NodeStartFloat
<< NodeModel::NodeFinishFloat;
QList<int> lst2;
for ( int i = 0; i < m->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
widget.treeWidgetTaskResult->hideColumns( lst1, lst2 );
widget.treeWidgetTaskResult->masterView()->setDefaultColumns( QList<int>() << 0 );
widget.treeWidgetTaskResult->slaveView()->setDefaultColumns( show );
connect( widget.treeWidgetTaskResult, &DoubleTreeViewBase::contextMenuRequested, this, &PertResult::slotContextMenuRequested );
connect( widget.treeWidgetTaskResult, SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) );
connect(this, &ViewBase::expandAll, widget.treeWidgetTaskResult, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, widget.treeWidgetTaskResult, &DoubleTreeViewBase::slotCollapse);
}
void PertResult::draw( Project &project)
{
setProject( &project );
//draw();
}
void PertResult::draw()
{
debugPlan<<m_project;
widget.scheduleName->setText( i18n( "None" ) );
widget.totalFloat->clear();
if ( m_project && model()->manager() && model()->manager()->isScheduled() ) {
long id = model()->manager()->scheduleId();
if ( id == -1 ) {
return;
}
widget.scheduleName->setText( model()->manager()->name() );
Duration f;
foreach ( Node *n, m_project->allNodes() ) {
if ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) {
f += static_cast<Task*>( n )->positiveFloat( id );
}
}
widget.totalFloat->setText( QLocale().toString( f.toDouble( Duration::Unit_h ), 'f', 2 ) );
}
}
void PertResult::setupGui()
{
// Add the context menu actions for the view options
connect(widget.treeWidgetTaskResult->actionSplitView(), &QAction::triggered, this, &PertResult::slotSplitView);
addContextAction( widget.treeWidgetTaskResult->actionSplitView() );
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig);
}
void PertResult::slotSplitView()
{
debugPlan;
widget.treeWidgetTaskResult->setViewSplitMode( ! widget.treeWidgetTaskResult->isViewSplit() );
emit optionsModified();
}
Node *PertResult::currentNode() const
{
return model()->node( widget.treeWidgetTaskResult->selectionModel()->currentIndex() );
}
void PertResult::slotContextMenuRequested( const QModelIndex& index, const QPoint& pos )
{
debugPlan<<index<<pos;
Node *node = model()->node( index );
if ( node == 0 ) {
slotHeaderContextMenuRequested( pos );
return;
}
debugPlan<<node->name()<<" :"<<pos;
QString name;
switch ( node->type() ) {
case Node::Type_Task:
name = "task_popup";
break;
case Node::Type_Milestone:
name = "taskeditor_milestone_popup";
break;
case Node::Type_Summarytask:
name = "summarytask_popup";
break;
case Node::Type_Project:
break;
default:
name = "node_popup";
break;
}
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
debugPlan<<name;
emit requestPopupMenu( name, pos );
}
void PertResult::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
void PertResult::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, widget.treeWidgetTaskResult, this );
// Note: printing needs fixes in SplitterView/ScheduleHandlerView
//dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void PertResult::slotUpdate(){
draw();
}
void PertResult::slotScheduleSelectionChanged( ScheduleManager *sm )
{
current_schedule = sm;
model()->setManager( sm );
draw();
}
void PertResult::slotProjectCalculated(KPlato::ScheduleManager *sm)
{
if ( sm && sm == model()->manager() ) {
//draw();
slotScheduleSelectionChanged( sm );
}
}
void PertResult::slotScheduleManagerToBeRemoved( const ScheduleManager *sm )
{
if ( sm == model()->manager() ) {
current_schedule = 0;
model()->setManager( 0 );
}
}
void PertResult::slotScheduleManagerChanged( ScheduleManager *sm )
{
if ( current_schedule && current_schedule == sm ) {
slotScheduleSelectionChanged( sm );
}
}
void PertResult::setProject( Project *project )
{
if ( m_project ) {
disconnect( m_project, &Project::nodeChanged, this, &PertResult::slotUpdate );
disconnect( m_project, &Project::projectCalculated, this, &PertResult::slotProjectCalculated );
disconnect( m_project, &Project::scheduleManagerToBeRemoved, this, &PertResult::slotScheduleManagerToBeRemoved );
disconnect( m_project, &Project::scheduleManagerChanged, this, &PertResult::slotScheduleManagerChanged );
}
m_project = project;
model()->setProject( m_project );
if ( m_project ) {
connect( m_project, &Project::nodeChanged, this, &PertResult::slotUpdate );
connect( m_project, &Project::projectCalculated, this, &PertResult::slotProjectCalculated );
connect( m_project, &Project::scheduleManagerToBeRemoved, this, &PertResult::slotScheduleManagerToBeRemoved );
connect( m_project, &Project::scheduleManagerChanged, this, &PertResult::slotScheduleManagerChanged );
}
draw();
}
bool PertResult::loadContext( const KoXmlElement &context )
{
debugPlan;
ViewBase::loadContext( context );
return widget.treeWidgetTaskResult->loadContext( model()->columnMap(), context );
}
void PertResult::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
widget.treeWidgetTaskResult->saveContext( model()->columnMap(), context );
}
KoPrintJob *PertResult::createPrintJob()
{
return widget.treeWidgetTaskResult->createPrintJob( this );
}
//--------------------
PertCpmView::PertCpmView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent),
m_project( 0 ),
current_schedule( 0 ),
block( false )
{
debugPlan << " ---------------- KPlato: Creating PertCpmView ----------------";
widget.setupUi(this);
widget.cpmTable->setSelectionMode( QAbstractItemView::ExtendedSelection );
widget.probabilityFrame->setVisible( false );
widget.cpmTable->setStretchLastSection ( false );
CriticalPathItemModel *m = new CriticalPathItemModel( widget.cpmTable );
widget.cpmTable->setModel( m );
setupGui();
QList<int> lst1; lst1 << 1 << -1; // only display first column (NodeName) in left view
widget.cpmTable->masterView()->setDefaultColumns( QList<int>() << 0 );
QList<int> show;
show << NodeModel::NodeDuration
<< NodeModel::NodeVarianceDuration
<< NodeModel::NodeOptimisticDuration
<< NodeModel::NodePessimisticDuration
<< NodeModel::NodeEstimate
<< NodeModel::NodeExpected
<< NodeModel::NodeVarianceEstimate
<< NodeModel::NodeOptimistic
<< NodeModel::NodePessimistic;
QList<int> lst2;
for ( int i = 0; i < m->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
widget.cpmTable->hideColumns( lst1, lst2 );
for ( int s = 0; s < show.count(); ++s ) {
widget.cpmTable->slaveView()->mapToSection( show[s], s );
}
widget.cpmTable->slaveView()->setDefaultColumns( show );
connect( widget.cpmTable, &DoubleTreeViewBase::contextMenuRequested, this, &PertCpmView::slotContextMenuRequested );
connect( widget.cpmTable, SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) );
connect( widget.finishTime, &QDateTimeEdit::dateTimeChanged, this, &PertCpmView::slotFinishTimeChanged );
connect( widget.probability, SIGNAL(valueChanged(int)), SLOT(slotProbabilityChanged(int)) );
}
void PertCpmView::setupGui()
{
// Add the context menu actions for the view options
connect(widget.cpmTable->actionSplitView(), &QAction::triggered, this, &PertCpmView::slotSplitView);
addContextAction( widget.cpmTable->actionSplitView() );
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig);
}
void PertCpmView::slotSplitView()
{
debugPlan;
widget.cpmTable->setViewSplitMode( ! widget.cpmTable->isViewSplit() );
emit optionsModified();
}
Node *PertCpmView::currentNode() const
{
return model()->node( widget.cpmTable->selectionModel()->currentIndex() );
}
void PertCpmView::slotContextMenuRequested( const QModelIndex& index, const QPoint& pos )
{
debugPlan<<index<<pos;
Node *node = model()->node( index );
if ( node == 0 ) {
slotHeaderContextMenuRequested( pos );
return;
}
debugPlan<<node->name()<<" :"<<pos;
QString name;
switch ( node->type() ) {
case Node::Type_Task:
name = "task_popup";
break;
case Node::Type_Milestone:
name = "taskeditor_milestone_popup";
break;
case Node::Type_Summarytask:
name = "summarytask_popup";
break;
case Node::Type_Project:
break;
default:
name = "node_popup";
break;
}
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
debugPlan<<name;
emit requestPopupMenu( name, pos );
}
void PertCpmView::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
void PertCpmView::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, widget.cpmTable, this );
// Note: printing needs fixes in SplitterView/ScheduleHandlerView
//dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void PertCpmView::slotScheduleSelectionChanged( ScheduleManager *sm )
{
bool enbl = sm && sm->isScheduled() && sm->usePert();
debugPlan<<sm<<(sm?sm->isScheduled():false)<<(sm?sm->usePert():false)<<enbl;
widget.probabilityFrame->setVisible( enbl );
current_schedule = sm;
model()->setManager( sm );
draw();
}
void PertCpmView::slotProjectCalculated( ScheduleManager *sm )
{
if ( sm && sm == model()->manager() ) {
slotScheduleSelectionChanged( sm );
}
}
void PertCpmView::slotScheduleManagerChanged( ScheduleManager *sm )
{
if ( current_schedule == sm ) {
slotScheduleSelectionChanged( sm );
}
}
void PertCpmView::slotScheduleManagerToBeRemoved( const ScheduleManager *sm )
{
if ( sm == current_schedule ) {
current_schedule = 0;
model()->setManager( 0 );
widget.probabilityFrame->setVisible( false );
}
}
void PertCpmView::setProject( Project *project )
{
if ( m_project ) {
disconnect( m_project, &Project::nodeChanged, this, &PertCpmView::slotUpdate );
disconnect( m_project, &Project::projectCalculated, this, &PertCpmView::slotProjectCalculated );
disconnect( m_project, &Project::scheduleManagerToBeRemoved, this, &PertCpmView::slotScheduleManagerToBeRemoved );
disconnect( m_project, &Project::scheduleManagerChanged, this, &PertCpmView::slotScheduleManagerChanged );
}
m_project = project;
model()->setProject( m_project );
if ( m_project ) {
connect( m_project, &Project::nodeChanged, this, &PertCpmView::slotUpdate );
connect( m_project, &Project::projectCalculated, this, &PertCpmView::slotProjectCalculated );
connect( m_project, &Project::scheduleManagerToBeRemoved, this, &PertCpmView::slotScheduleManagerToBeRemoved );
connect( m_project, &Project::scheduleManagerChanged, this, &PertCpmView::slotScheduleManagerChanged );
}
draw();
}
void PertCpmView::draw( Project &project )
{
setProject( &project );
// draw()
}
void PertCpmView::draw()
{
widget.scheduleName->setText( i18n( "None" ) );
bool enbl = m_project && current_schedule && current_schedule->isScheduled() && current_schedule->usePert();
widget.probabilityFrame->setVisible( enbl );
if ( m_project && current_schedule && current_schedule->isScheduled() ) {
long id = current_schedule->scheduleId();
if ( id == -1 ) {
return;
}
widget.scheduleName->setText( current_schedule->name() );
widget.finishTime->setDateTime( m_project->endTime( id ) );
bool ro = model()->variance( Qt::EditRole ).toDouble() == 0.0;
if ( ro ) {
widget.probability->setValue( 50 );
}
widget.finishTime->setReadOnly( ro );
widget.probability->setEnabled( ! ro );
}
}
void PertCpmView::slotFinishTimeChanged( const QDateTime &dt )
{
debugPlan<<dt;
if ( block || m_project == 0 || current_schedule == 0 ) {
return;
}
block = true;
double var = model()->variance( Qt::EditRole ).toDouble();
double dev = sqrt( var );
DateTime et = m_project->endTime( current_schedule->scheduleId() );
DateTime t = DateTime( dt );
double d = ( et - t ).toDouble();
d = t < et ? -d : d;
double z = d / dev;
double v = probability( z );
widget.probability->setValue( (int)( v * 100 ) );
//debugPlan<<z<<", "<<v;
block = false;
}
void PertCpmView::slotProbabilityChanged( int value )
{
debugPlan<<value;
if ( value == 0 || block || m_project == 0 || current_schedule == 0 ) {
return;
}
block = true;
double var = model()->variance( Qt::EditRole ).toDouble();
double dev = sqrt( var );
DateTime et = m_project->endTime( current_schedule->scheduleId() );
double p = valueZ( value );
DateTime t = et + Duration( qint64( p * dev ) );
widget.finishTime->setDateTime( t );
//debugPlan<<p<<", "<<t.toString();
block = false;
}
double PertCpmView::probability( double z ) const
{
double p = 1.0;
int i = 1;
for ( ; i < 151; ++i ) {
if ( qAbs( z ) <= dist[i][0] ) {
break;
}
}
p = dist[i-1][1] + ( ( dist[i][1] - dist[i-1][1] ) * ( ( qAbs(z) - dist[i-1][0] ) / (dist[i][0] - dist[i-1][0] ) ) );
//debugPlan<<i<<":"<<z<<dist[i][0]<<dist[i][1]<<"="<<p;
return z < 0 ? 1 - p : p;
}
double PertCpmView::valueZ( double pr ) const
{
double p = ( pr >= 50.0 ? pr : 100.0 - pr ) / 100.0;
double z = 3.0;
int i = 1;
for ( ; i < 151; ++i ) {
if ( p < dist[i][1] ) {
break;
}
}
z = dist[i-1][0] + ( ( dist[i][0] - dist[i-1][0] ) * ( ( p - dist[i-1][1] ) / (dist[i][1] - dist[i-1][1] ) ) );
//debugPlan<<i<<":"<<pr<<p<<dist[i][0]<<dist[i][1]<<"="<<z;
return pr < 50.0 ? -z : z;
}
void PertCpmView::slotUpdate()
{
draw();
}
bool PertCpmView::loadContext( const KoXmlElement &context )
{
debugPlan<<objectName();
ViewBase::loadContext( context );
return widget.cpmTable->loadContext( model()->columnMap(), context );
}
void PertCpmView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
widget.cpmTable->saveContext( model()->columnMap(), context );
}
KoPrintJob *PertCpmView::createPrintJob()
{
return widget.cpmTable->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptrecalculatedialog.cpp b/src/libs/ui/kptrecalculatedialog.cpp
index ac22133e..798fe7b6 100644
--- a/src/libs/ui/kptrecalculatedialog.cpp
+++ b/src/libs/ui/kptrecalculatedialog.cpp
@@ -1,59 +1,60 @@
/* This file is part of the KDE project
Copyright (C) 2003 - 2007 Dag Andersen <danders@get2net.dk>
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 "kptrecalculatedialog.h"
namespace KPlato
{
RecalculateDialogImpl::RecalculateDialogImpl (QWidget *parent)
: QWidget(parent)
{
setupUi(this);
QDateTime ct = QDateTime::currentDateTime();
ct.setTime( QTime( ct.time().hour(), ct.time().minute(), 0 ) ); // clear secs/msecs
dateTimeEdit->setDateTime( ct );
btnCurrent->setChecked( true );
dateTimeEdit->setEnabled( false );
connect( btnFrom, &QAbstractButton::toggled, dateTimeEdit, &QWidget::setEnabled );
}
////////////////// ResourceDialog ////////////////////////
RecalculateDialog::RecalculateDialog( QWidget *parent )
: KoDialog(parent)
{
setCaption( i18n("Re-calculate Schedule") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
dia = new RecalculateDialogImpl(this);
setMainWidget(dia);
}
QDateTime RecalculateDialog::dateTime() const {
return dia->btnFrom->isChecked() ? dia->dateTimeEdit->dateTime() : QDateTime::currentDateTime();
}
} //KPlato namespace
diff --git a/src/libs/ui/kptrelationdialog.cpp b/src/libs/ui/kptrelationdialog.cpp
index a6942f69..6240ce5a 100644
--- a/src/libs/ui/kptrelationdialog.cpp
+++ b/src/libs/ui/kptrelationdialog.cpp
@@ -1,195 +1,196 @@
/* This file is part of the KDE project
Copyright (C) 2003 - 2010 Dag Andersen <danders@get2net.dk>
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 "kptrelationdialog.h"
#include "kptrelation.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kptcommand.h"
#include <KLocalizedString>
namespace KPlato
{
RelationPanel::RelationPanel(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
lagLabel->setText( xi18nc( "@label:spinbox Time lag", "Lag:" ) );
QString tt = xi18nc( "@info:tooltip", "<emphasis>Lag</emphasis> is the time the dependent task is delayed" );
lagLabel->setToolTip( tt );
lag->setToolTip( tt );
}
AddRelationDialog::AddRelationDialog(Project &project, Relation *rel, QWidget *p, const QString& caption, ButtonCodes buttons)
: KoDialog(p),
m_project( project ),
m_relation( rel ),
m_deleterelation( true )
{
setCaption( caption );
setButtons( buttons );
setDefaultButton( Ok );
showButtonSeparator( true );
if ( caption.isEmpty() ) {
setCaption( xi18nc( "@title:window", "Add Dependency" ) );
}
m_relation = rel;
m_panel = new RelationPanel(this);
setMainWidget(m_panel);
m_panel->activateWindow();
m_panel->fromName->setText(rel->parent()->name());
m_panel->toName->setText(rel->child()->name());
if (rel->type() == Relation::FinishStart) {
m_panel->bFinishStart->setChecked(true);
} else if (rel->type() == Relation::FinishFinish) {
m_panel->bFinishFinish->setChecked(true);
} else if (rel->type() == Relation::StartStart) {
m_panel->bStartStart->setChecked(true);
}
m_panel->lag->setUnit( Duration::Unit_h );
m_panel->lag->setValue(rel->lag().toDouble( Duration::Unit_h ) ); //FIXME store user input
m_panel->relationType->setFocus();
enableButtonOk(true);
//connect(m_panel->relationType, SIGNAL(clicked(int)), SLOT(typeClicked(int)));
connect(m_panel->bFinishStart, &QAbstractButton::toggled, this, &AddRelationDialog::slotFinishStartToggled);
connect(m_panel->bFinishFinish, &QAbstractButton::toggled, this, &AddRelationDialog::slotFinishFinishToggled);
connect(m_panel->bStartStart, &QAbstractButton::toggled, this, &AddRelationDialog::slotStartStartToggled);
connect(m_panel->lag, SIGNAL(valueChanged(double)), SLOT(lagChanged()));
connect(&project, &Project::nodeRemoved, this, &AddRelationDialog::slotNodeRemoved);
}
AddRelationDialog::~AddRelationDialog()
{
if ( m_deleterelation ) {
delete m_relation; //in case of cancel
}
}
void AddRelationDialog::slotNodeRemoved( Node *node )
{
if ( m_relation->parent() == node || m_relation->child() == node ) {
reject();
}
}
MacroCommand *AddRelationDialog::buildCommand() {
MacroCommand *c = new MacroCommand( kundo2_i18n("Add task dependency") );
c->addCommand( new AddRelationCmd(m_project, m_relation ) );
m_deleterelation = false; // don't delete
return c;
}
void AddRelationDialog::slotOk() {
accept();
}
void AddRelationDialog::slotFinishStartToggled(bool ch) {
//debugPlan<<ch;
if (ch && m_relation->type() != Relation::FinishStart)
enableButtonOk(true);
}
void AddRelationDialog::slotFinishFinishToggled(bool ch) {
//debugPlan<<ch;
if (ch && m_relation->type() != Relation::FinishFinish)
enableButtonOk(true);
}
void AddRelationDialog::slotStartStartToggled(bool ch) {
//debugPlan<<ch;
if (ch && m_relation->type() != Relation::StartStart)
enableButtonOk(true);
}
void AddRelationDialog::lagChanged() {
enableButtonOk(true);
}
void AddRelationDialog::typeClicked(int id) {
if (id != m_relation->type())
enableButtonOk(true);
}
int AddRelationDialog::selectedRelationType() const {
if (m_panel->bStartStart->isChecked())
return Relation::StartStart;
else if (m_panel->bFinishFinish->isChecked())
return Relation::FinishFinish;
return Relation::FinishStart;
}
//////////////////
ModifyRelationDialog::ModifyRelationDialog(Project &project, Relation *rel, QWidget *p)
: AddRelationDialog(project, rel, p, xi18nc( "@title:window", "Edit Dependency"), Ok|Cancel|User1)
{
m_deleterelation = false;
setButtonText( KoDialog::User1, xi18nc( "@action:button", "Delete") );
m_deleted = false;
enableButtonOk(false);
connect(this, &KoDialog::user1Clicked, this, &ModifyRelationDialog::slotUser1);
connect(&project, &Project::relationRemoved, this, &ModifyRelationDialog::slotRelationRemoved);
}
void ModifyRelationDialog::slotRelationRemoved( Relation *relation )
{
if ( m_relation == relation ) {
reject();
}
}
// Delete
void ModifyRelationDialog::slotUser1() {
m_deleted = true;
accept();
}
MacroCommand *ModifyRelationDialog::buildCommand() {
MacroCommand *cmd=0;
if ( m_deleted ) {
cmd = new MacroCommand( kundo2_i18n( "Delete task dependency" ) );
cmd ->addCommand( new DeleteRelationCmd( m_project, m_relation ) );
return cmd;
}
KUndo2MagicString s = kundo2_i18n( "Modify task dependency" );
if (selectedRelationType() != m_relation->type()) {
if (cmd == 0)
cmd = new MacroCommand( s );
cmd->addCommand(new ModifyRelationTypeCmd(m_relation, (Relation::Type)(selectedRelationType())));
//debugPlan<<m_panel->relationType->selectedId();
}
Duration d(m_panel->lag->value(), m_panel->lag->unit());
if (m_relation->lag() != d) {
if (cmd == 0)
cmd = new MacroCommand( s );
cmd->addCommand(new ModifyRelationLagCmd(m_relation, d));
}
return cmd;
}
} //KPlato namespace
diff --git a/src/libs/ui/kptrelationeditor.cpp b/src/libs/ui/kptrelationeditor.cpp
index dff04397..9bd779bb 100644
--- a/src/libs/ui/kptrelationeditor.cpp
+++ b/src/libs/ui/kptrelationeditor.cpp
@@ -1,224 +1,225 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptrelationeditor.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptitemviewsettup.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <QAbstractItemView>
#include <QItemSelectionModel>
#include <QModelIndex>
#include <QVBoxLayout>
#include <QMenu>
#include <QAction>
namespace KPlato
{
//--------------------
RelationTreeView::RelationTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
setViewSplitMode( false );
RelationItemModel *m = new RelationItemModel( this );
setModel( m );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
setArrowKeyNavigation( true );
setRootIsDecorated ( false );
createItemDelegates( m );
//HACK to simulate SingleSelection *and* get indication of current item
connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &RelationTreeView::slotCurrentChanged );
}
void RelationTreeView::slotCurrentChanged(const QModelIndex &curr, const QModelIndex& )
{
selectionModel()->select( curr, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
}
//-----------------------------------
RelationEditor::RelationEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
debugPlan<<"----------------- Create RelationEditor ----------------------";
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new RelationTreeView( this );
l->addWidget( m_view );
//debugPlan<<m_view->actionSplitView();
setupGui();
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &RelationEditor::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &RelationEditor::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &RelationEditor::slotContextMenuRequested );
connect( m_view, SIGNAL(headerContextMenuRequested(QPoint)), SLOT(slotHeaderContextMenuRequested(QPoint)) );
connect(model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand);
}
void RelationEditor::updateReadWrite( bool rw )
{
m_view->setReadWrite( rw );
}
void RelationEditor::draw( Project &project )
{
m_view->setProject( &project );
}
void RelationEditor::draw()
{
}
void RelationEditor::setGuiActive( bool /*activate */)
{
}
void RelationEditor::slotCurrentChanged( const QModelIndex &/*curr*/, const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
slotEnableActions();
}
void RelationEditor::slotSelectionChanged( const QModelIndexList& /*list*/)
{
//debugPlan<<list.count();
slotEnableActions();
}
Relation *RelationEditor::currentRelation() const
{
//debugPlan;
return m_view->currentRelation();
}
void RelationEditor::slotContextMenuRequested( const QModelIndex& index, const QPoint& pos )
{
Relation *rel = m_view->model()->relation( index );
if ( rel == 0 ) {
slotHeaderContextMenuRequested( pos );
return;
}
QString name = "relation_popup";
emit requestPopupMenu( name, pos );
}
void RelationEditor::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
void RelationEditor::slotEnableActions()
{
updateActionsEnabled( true );
}
void RelationEditor::updateActionsEnabled( bool /*on */)
{
}
void RelationEditor::setupGui()
{
// Add the context menu actions for the view options
connect(m_view->actionSplitView(), &QAction::triggered, this, &RelationEditor::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig);
}
void RelationEditor::slotSplitView()
{
//debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
}
void RelationEditor::slotOptions()
{
debugPlan;
bool col0 = false;
TreeViewBase *v = m_view->slaveView();
if ( v->isHidden() ) {
v = m_view->masterView();
col0 = true;
}
ItemViewSettupDialog *dlg = new ItemViewSettupDialog( this, v, col0, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void RelationEditor::slotAddRelation()
{
debugPlan;
}
void RelationEditor::edit( const QModelIndex &i )
{
if ( i.isValid() ) {
// QModelIndex p = m_view->model()->parent( i );
// m_view->setExpanded( p );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}
}
void RelationEditor::slotDeleteRelation( Relation *r)
{
emit deleteRelation( r );
}
bool RelationEditor::loadContext( const KoXmlElement &context )
{
debugPlan;
ViewBase::loadContext( context );
return m_view->loadContext( m_view->model()->columnMap(), context );
}
void RelationEditor::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
m_view->saveContext( m_view->model()->columnMap(), context );
}
KoPrintJob *RelationEditor::createPrintJob()
{
return m_view->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptrequestresourcespanel.cpp b/src/libs/ui/kptrequestresourcespanel.cpp
index 088c5230..4c6e2b10 100644
--- a/src/libs/ui/kptrequestresourcespanel.cpp
+++ b/src/libs/ui/kptrequestresourcespanel.cpp
@@ -1,177 +1,178 @@
/* This file is part of the KDE project
Copyright (C) 2003 - 2009 Dag Andersen <danders@get2net.dk>
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 "kptrequestresourcespanel.h"
#include "kpttask.h"
#include "kptproject.h"
#include "kptresource.h"
#include "kptcalendar.h"
#include "kptresourceallocationeditor.h"
#include <kptcommand.h>
#include <QHeaderView>
#include <QHash>
namespace KPlato
{
RequestResourcesPanel::RequestResourcesPanel(QWidget *parent, Project &project, Task &task, bool)
: QWidget(parent)
{
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin(0);
m_view = new ResourceAllocationTreeView( this );
m_view->setViewSplitMode( false );
m_view->masterView()->header()->moveSection( ResourceAllocationModel::RequestType, m_view->masterView()->header()->count() - 1 );
m_view->setReadWrite( true );
l->addWidget( m_view );
m_view->setProject( &project );
m_view->setTask( &task );
m_view->slotExpand();
m_view->masterView()->header()->resizeSections( QHeaderView::ResizeToContents );
connect( m_view, &ResourceAllocationTreeView::dataChanged, this, &RequestResourcesPanel::changed );
}
bool RequestResourcesPanel::ok()
{
return true;
}
MacroCommand *RequestResourcesPanel::buildCommand()
{
Task *task = m_view->task();
if ( task == 0 ) {
return 0;
}
MacroCommand *cmd = new MacroCommand( kundo2_i18n( "Modify resource allocations" ) );
const QHash<const Resource*, ResourceRequest*> &rmap = m_view->resourceCache();
// First remove all that should be removed
for( QHash<const Resource*, ResourceRequest*>::const_iterator rit = rmap.constBegin(); rit != rmap.constEnd(); ++rit ) {
if ( rit.value()->units() == 0 ) {
ResourceRequest *rr = task->requests().find( rit.key() );
if ( rr ) {
cmd->addCommand( new RemoveResourceRequestCmd( rr->parent(), rr ) );
}
}
}
QHash<const ResourceGroup*, ResourceGroupRequest*> groups;
// Add/modify
const QHash<const ResourceGroup*, ResourceGroupRequest*> &gmap = m_view->groupCache();
for( QHash<const ResourceGroup*, ResourceGroupRequest*>::const_iterator git = gmap.constBegin(); git != gmap.constEnd(); ++git ) {
ResourceGroupRequest *gr = task->requests().find( git.key() );
if ( gr == 0 ) {
if ( git.value()->units() > 0 ) {
gr = new ResourceGroupRequest( const_cast<ResourceGroup*>( git.key() ), git.value()->units() );
cmd->addCommand( new AddResourceGroupRequestCmd( *task, gr ) );
groups[ git.key() ] = gr;
} // else nothing
} else {
cmd->addCommand( new ModifyResourceGroupRequestUnitsCmd( gr, gr->units(), git.value()->units() ) );
}
}
for( QHash<const Resource*, ResourceRequest*>::const_iterator rit = rmap.constBegin(); rit != rmap.constEnd(); ++rit ) {
Resource *resource = const_cast<Resource*>( rit.key() );
ResourceGroup *group = resource->parentGroup();
if ( rit.value()->units() > 0 ) {
ResourceRequest *rr = task->requests().find( resource );
if ( rr == 0 ) {
ResourceGroupRequest *gr = task->requests().find( group );
if ( gr == 0 ) {
if ( groups.contains( group ) ) {
gr = groups[ group ];
} else {
gr = new ResourceGroupRequest( group, 0 );
groups[ group ] = gr;
cmd->addCommand( new AddResourceGroupRequestCmd( *task, gr ) );
}
}
ResourceRequest *rr = new ResourceRequest( resource, rit.value()->units() );
rr->setRequiredResources( rit.value()->requiredResources() );
cmd->addCommand( new AddResourceRequestCmd( gr, rr ) );
} else {
if ( rit.value()->units() != rr->units() ) {
cmd->addCommand( new ModifyResourceRequestUnitsCmd( rr, rr->units(), rit.value()->units() ) );
}
if ( rit.value()->requiredResources() != rr->requiredResources() ) {
cmd->addCommand( new ModifyResourceRequestRequiredCmd( rr, rit.value()->requiredResources() ) );
}
}
}
}
if ( cmd->isEmpty() ) {
delete cmd;
cmd = 0;
}
return cmd;
}
MacroCommand *RequestResourcesPanel::buildCommand(Task *task)
{
if ( task == 0 ) {
return 0;
}
MacroCommand *cmd = new MacroCommand( kundo2_i18n( "Modify resource allocations" ) );
const QHash<const Resource*, ResourceRequest*> &rmap = m_view->resourceCache();
// First remove all
foreach(ResourceGroupRequest *g, task->requests().requests()) {
cmd->addCommand(new RemoveResourceGroupRequestCmd(g));
}
QHash<const ResourceGroup*, ResourceGroupRequest*> groups;
// Add possible requests to groups
const QHash<const ResourceGroup*, ResourceGroupRequest*> &gmap = m_view->groupCache();
for( QHash<const ResourceGroup*, ResourceGroupRequest*>::const_iterator git = gmap.constBegin(); git != gmap.constEnd(); ++git ) {
if (git.value()->units() > 0) {
ResourceGroupRequest *gr = new ResourceGroupRequest(const_cast<ResourceGroup*>(git.key() ), git.value()->units());
cmd->addCommand(new AddResourceGroupRequestCmd(*task, gr));
groups[git.key()] = gr;
}
}
// Add possible requests to resources
for( QHash<const Resource*, ResourceRequest*>::const_iterator rit = rmap.constBegin(); rit != rmap.constEnd(); ++rit ) {
Resource *resource = const_cast<Resource*>(rit.key());
ResourceGroup *group = resource->parentGroup();
if ( rit.value()->units() > 0 ) {
ResourceGroupRequest *gr = groups.value(group);
// Check if there is already a request to the group
if (gr == 0) {
gr = new ResourceGroupRequest(group, 0);
cmd->addCommand(new AddResourceGroupRequestCmd(*task, gr));
groups[group] = gr;
}
ResourceRequest *rr = new ResourceRequest(resource, rit.value()->units());
rr->setRequiredResources(rit.value()->requiredResources());
cmd->addCommand( new AddResourceRequestCmd(gr, rr));
}
}
if (cmd->isEmpty()) {
delete cmd;
cmd = 0;
}
return cmd;
}
} //KPlato namespace
diff --git a/src/libs/ui/kptresourceallocationeditor.cpp b/src/libs/ui/kptresourceallocationeditor.cpp
index 235f306c..a8598fa2 100644
--- a/src/libs/ui/kptresourceallocationeditor.cpp
+++ b/src/libs/ui/kptresourceallocationeditor.cpp
@@ -1,218 +1,219 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptresourceallocationeditor.h"
#include "kptresourcemodel.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptitemviewsettup.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <QList>
#include <QVBoxLayout>
#include <QAction>
namespace KPlato
{
ResourceAllocationTreeView::ResourceAllocationTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
// header()->setContextMenuPolicy( Qt::CustomContextMenu );
ResourceAllocationItemModel *m = new ResourceAllocationItemModel( this );
setModel( m );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
createItemDelegates( m );
connect( m, &QAbstractItemModel::dataChanged, this, &ResourceAllocationTreeView::dataChanged );
}
QObject *ResourceAllocationTreeView::currentObject() const
{
return model()->object( selectionModel()->currentIndex() );
}
//-----------------------------------
ResourceAllocationEditor::ResourceAllocationEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new ResourceAllocationTreeView( this );
l->addWidget( m_view );
setupGui();
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
QList<int> lst1; lst1 << 1 << -1;
QList<int> lst2; lst2 << 0;
m_view->hideColumns( lst1, lst2 );
m_view->masterView()->setDefaultColumns( QList<int>() << 0 );
QList<int> show;
for ( int c = 1; c < model()->columnCount(); ++c ) {
show << c;
}
m_view->slaveView()->setDefaultColumns( show );
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &ResourceAllocationEditor::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &ResourceAllocationEditor::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &ResourceAllocationEditor::slotContextMenuRequested );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
}
void ResourceAllocationEditor::updateReadWrite( bool readwrite )
{
m_view->setReadWrite( readwrite );
}
void ResourceAllocationEditor::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void ResourceAllocationEditor::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
QString name;
if ( index.isValid() ) {
QObject *obj = m_view->model()->object( index );
ResourceGroup *g = qobject_cast<ResourceGroup*>( obj );
if ( g ) {
//name = "resourceeditor_group_popup";
} else {
Resource *r = qobject_cast<Resource*>( obj );
if ( r ) {
//name = "resourceeditor_resource_popup";
}
}
}
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
emit requestPopupMenu( name, pos );
}
Resource *ResourceAllocationEditor::currentResource() const
{
return qobject_cast<Resource*>( m_view->currentObject() );
}
ResourceGroup *ResourceAllocationEditor::currentResourceGroup() const
{
return qobject_cast<ResourceGroup*>( m_view->currentObject() );
}
void ResourceAllocationEditor::slotCurrentChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
// slotEnableActions();
}
void ResourceAllocationEditor::slotSelectionChanged( const QModelIndexList& )
{
//debugPlan<<list.count();
updateActionsEnabled();
}
void ResourceAllocationEditor::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void ResourceAllocationEditor::updateActionsEnabled( bool /*on */)
{
}
void ResourceAllocationEditor::setupGui()
{
// Add the context menu actions for the view options
connect(m_view->actionSplitView(), &QAction::triggered, this, &ResourceAllocationEditor::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionAll);
}
void ResourceAllocationEditor::slotSplitView()
{
debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
emit optionsModified();
}
void ResourceAllocationEditor::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, m_view, this );
dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool ResourceAllocationEditor::loadContext( const KoXmlElement &context )
{
debugPlan<<objectName();
ViewBase::loadContext( context );
return m_view->loadContext( model()->columnMap(), context );
}
void ResourceAllocationEditor::saveContext( QDomElement &context ) const
{
debugPlan<<objectName();
ViewBase::saveContext( context );
m_view->saveContext( model()->columnMap(), context );
}
KoPrintJob *ResourceAllocationEditor::createPrintJob()
{
return m_view->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptresourceappointmentsview.cpp b/src/libs/ui/kptresourceappointmentsview.cpp
index a6160a37..db941b36 100644
--- a/src/libs/ui/kptresourceappointmentsview.cpp
+++ b/src/libs/ui/kptresourceappointmentsview.cpp
@@ -1,428 +1,429 @@
/* This file is part of the KDE project
Copyright (C) 2005 - 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptresourceappointmentsview.h"
#include "kptappointment.h"
#include "kptcommand.h"
#include "kpteffortcostmap.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptitemviewsettup.h"
#include "kptviewbase.h"
#include "Help.h"
#include "kptdebug.h"
#include "KoPageLayoutWidget.h"
#include <KoDocument.h>
#include <KoXmlReader.h>
#include <KLocalizedString>
#include <QList>
#include <QVBoxLayout>
#include <QTabWidget>
namespace KPlato
{
//--------------------
ResourceAppointmentsDisplayOptionsPanel::ResourceAppointmentsDisplayOptionsPanel( ResourceAppointmentsItemModel *model, QWidget *parent )
: QWidget( parent ),
m_model( model )
{
setupUi( this );
setValues( *model );
connect( ui_internalAppointments, &QCheckBox::stateChanged, this, &ResourceAppointmentsDisplayOptionsPanel::changed );
connect( ui_externalAppointments, &QCheckBox::stateChanged, this, &ResourceAppointmentsDisplayOptionsPanel::changed );
}
void ResourceAppointmentsDisplayOptionsPanel::slotOk()
{
m_model->setShowInternalAppointments( ui_internalAppointments->checkState() == Qt::Checked );
m_model->setShowExternalAppointments( ui_externalAppointments->checkState() == Qt::Checked );
}
void ResourceAppointmentsDisplayOptionsPanel::setValues( const ResourceAppointmentsItemModel &m )
{
ui_internalAppointments->setCheckState( m.showInternalAppointments() ? Qt::Checked : Qt::Unchecked );
ui_externalAppointments->setCheckState( m.showExternalAppointments() ? Qt::Checked : Qt::Unchecked );
}
void ResourceAppointmentsDisplayOptionsPanel::setDefault()
{
ResourceAppointmentsItemModel m;
setValues( m );
}
//----
ResourceAppointmentsSettingsDialog::ResourceAppointmentsSettingsDialog( ViewBase *view, ResourceAppointmentsItemModel *model, QWidget *parent, bool selectPrint )
: KPageDialog( parent ),
m_view( view )
{
ResourceAppointmentsDisplayOptionsPanel *panel = new ResourceAppointmentsDisplayOptionsPanel( model );
KPageWidgetItem *page = addPage( panel, i18n( "General" ) );
page->setHeader( i18n( "Resource Assignments View Settings" ) );
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( view );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_headerfooter = ViewBase::createHeaderFooterWidget( view );
m_headerfooter->setOptions( view->printingOptions() );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
page = addPage( tab, i18n( "Printing" ) );
page->setHeader( i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, &QDialog::accepted, this, &ResourceAppointmentsSettingsDialog::slotOk);
connect( this, &QDialog::accepted, panel, &ResourceAppointmentsDisplayOptionsPanel::slotOk);
//TODO: there was no default button configured, should there?
// connect( button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked(bool)), panel, SLOT(setDefault()));
}
void ResourceAppointmentsSettingsDialog::slotOk()
{
m_view->setPageLayout( m_pagelayout->pageLayout() );
m_view->setPrintingOptions( m_headerfooter->options() );
}
//---------------------------------------
ResourceAppointmentsTreeView::ResourceAppointmentsTreeView( QWidget *parent )
: DoubleTreeViewBase( true, parent )
{
// header()->setContextMenuPolicy( Qt::CustomContextMenu );
m_rightview->setStretchLastSection( false );
ResourceAppointmentsItemModel *m = new ResourceAppointmentsItemModel( this );
setModel( m );
setSelectionMode( QAbstractItemView::ExtendedSelection );
QList<int> lst1; lst1 << 2 << -1;
QList<int> lst2; lst2 << 0 << 1;
hideColumns( lst1, lst2 );
m_leftview->resizeColumnToContents ( 1 );
connect( m, &QAbstractItemModel::modelReset, this, &ResourceAppointmentsTreeView::slotRefreshed );
m_rightview->setObjectName( "ResourceAppointments" );
}
bool ResourceAppointmentsTreeView::loadContext( const KoXmlElement &context )
{
debugPlan;
KoXmlElement e = context.namedItem( "common" ).toElement();
if ( ! e.isNull() ) {
model()->setShowInternalAppointments( (bool)( e.attribute( "show-internal-appointments", "0" ).toInt() ) );
model()->setShowExternalAppointments( (bool)( e.attribute( "show-external-appointments", "0" ).toInt() ) );
}
DoubleTreeViewBase::loadContext(QMetaEnum(), context);
return true;
}
void ResourceAppointmentsTreeView::saveContext( QDomElement &settings ) const
{
debugPlan;
QDomElement e = settings.ownerDocument().createElement( "common" );
settings.appendChild( e );
e.setAttribute( "show-internal-appointments", QString::number(model()->showInternalAppointments()) );
e.setAttribute( "show-external-appointments", QString::number(model()->showExternalAppointments()) );
DoubleTreeViewBase::saveContext(QMetaEnum(), settings);
}
void ResourceAppointmentsTreeView::slotRefreshed()
{
//debugPlan<<model()->columnCount()<<", "<<m_leftview->header()->count()<<", "<<m_rightview->header()->count()<<", "<<m_leftview->header()->hiddenSectionCount()<<", "<<m_rightview->header()->hiddenSectionCount();
ResourceAppointmentsItemModel *m = model();
setModel( 0 );
setModel( m );
setSelectionMode( QAbstractItemView::ExtendedSelection );
QList<int> lst1; lst1 << 2 << -1;
QList<int> lst2; lst2 << 0 << 1;
hideColumns( lst1, lst2 );
}
QModelIndex ResourceAppointmentsTreeView::currentIndex() const
{
return selectionModel()->currentIndex();
}
//-----------------------------------
ResourceAppointmentsView::ResourceAppointmentsView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
debugPlan<<"------------------- ResourceAppointmentsView -----------------------";
setupGui();
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new ResourceAppointmentsTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
l->addWidget( m_view );
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &ResourceAppointmentsView::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &ResourceAppointmentsView::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &ResourceAppointmentsView::slotContextMenuRequested );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Resource Assignments View</title>"
"<para>"
"Displays the scheduled resource - task assignments."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Resource_Assignment_Gantt_View")));
}
void ResourceAppointmentsView::draw( Project &project )
{
setProject( &project );
}
void ResourceAppointmentsView::setProject( Project *project )
{
m_view->setProject( project );
}
void ResourceAppointmentsView::setScheduleManager( ScheduleManager *sm )
{
if (!sm && scheduleManager()) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
bool tryexpand = sm && !scheduleManager();
bool expand = sm && scheduleManager() && sm != scheduleManager();
QDomDocument doc;
if (expand) {
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
ViewBase::setScheduleManager(sm);
m_view->setScheduleManager( sm );
if (expand) {
m_view->masterView()->doExpand(doc);
} else if (tryexpand) {
m_view->masterView()->doExpand(m_domdoc);
}
}
void ResourceAppointmentsView::draw()
{
}
void ResourceAppointmentsView::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void ResourceAppointmentsView::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
debugPlan<<index<<pos;
QString name;
if ( index.isValid() ) {
Node *n = m_view->model()->node( index );
if ( n ) {
name = "taskview_popup";
}
}
m_view->setContextMenuIndex(index);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
return;
}
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
Node *ResourceAppointmentsView::currentNode() const
{
return m_view->model()->node( m_view->currentIndex() );
}
Resource *ResourceAppointmentsView::currentResource() const
{
//return qobject_cast<Resource*>( m_view->currentObject() );
return 0;
}
ResourceGroup *ResourceAppointmentsView::currentResourceGroup() const
{
//return qobject_cast<ResourceGroup*>( m_view->currentObject() );
return 0;
}
void ResourceAppointmentsView::slotCurrentChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<", "<<curr.column();
// slotEnableActions();
}
void ResourceAppointmentsView::slotSelectionChanged( const QModelIndexList& )
{
//debugPlan<<list.count();
updateActionsEnabled();
}
void ResourceAppointmentsView::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void ResourceAppointmentsView::updateActionsEnabled( bool /*on*/ )
{
/* bool o = on && m_view->project();
QList<ResourceGroup*> groupList = m_view->selectedGroups();
bool nogroup = groupList.isEmpty();
bool group = groupList.count() == 1;
QList<Resource*> resourceList = m_view->selectedResources();
bool noresource = resourceList.isEmpty();
bool resource = resourceList.count() == 1;
bool any = !nogroup || !noresource;
actionAddResource->setEnabled( o && ( (group && noresource) || (resource && nogroup) ) );
actionAddGroup->setEnabled( o );
actionDeleteSelection->setEnabled( o && any );*/
}
void ResourceAppointmentsView::setupGui()
{
// Add the context menu actions for the view options
createOptionActions(ViewBase::OptionAll);
}
void ResourceAppointmentsView::slotOptions()
{
debugPlan;
ResourceAppointmentsSettingsDialog *dlg = new ResourceAppointmentsSettingsDialog( this, m_view->model(), this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void ResourceAppointmentsView::slotAddResource()
{
//debugPlan;
/* QList<ResourceGroup*> gl = m_view->selectedGroups();
if ( gl.count() > 1 ) {
return;
}
ResourceGroup *g = 0;
if ( !gl.isEmpty() ) {
g = gl.first();
} else {
QList<Resource*> rl = m_view->selectedResources();
if ( rl.count() != 1 ) {
return;
}
g = rl.first()->parentGroup();
}
if ( g == 0 ) {
return;
}
Resource *r = new Resource();
QModelIndex i = m_view->model()->insertResource( g, r );
if ( i.isValid() ) {
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}
*/
}
void ResourceAppointmentsView::slotAddGroup()
{
//debugPlan;
/* ResourceGroup *g = new ResourceGroup();
QModelIndex i = m_view->model()->insertGroup( g );
if ( i.isValid() ) {
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}*/
}
void ResourceAppointmentsView::slotDeleteSelection()
{
/* QObjectList lst = m_view->selectedObjects();
//debugPlan<<lst.count()<<" objects";
if ( ! lst.isEmpty() ) {
emit deleteObjectList( lst );
}*/
}
bool ResourceAppointmentsView::loadContext( const KoXmlElement &context )
{
ViewBase::loadContext( context );
return m_view->loadContext( context );
}
void ResourceAppointmentsView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
m_view->saveContext( context );
}
KoPrintJob *ResourceAppointmentsView::createPrintJob()
{
return m_view->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptresourceassignmentview.cpp b/src/libs/ui/kptresourceassignmentview.cpp
index c64a778f..1483b23f 100644
--- a/src/libs/ui/kptresourceassignmentview.cpp
+++ b/src/libs/ui/kptresourceassignmentview.cpp
@@ -1,410 +1,411 @@
/* This file is part of the KDE project
Copyright (C) 2006 - 2007 Frederic BECQUIER <frederic.becquier@gmail.com>
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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;
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 "kptresourceassignmentview.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptrelation.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <KLocalizedString>
#include <QSplitter>
#include <QStringList>
#include <QCursor>
namespace KPlato
{
ResourcesList::ResourcesList( QWidget * parent )
: QTreeWidget( parent )
{
setContextMenuPolicy( Qt::CustomContextMenu );
//connect( this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotContextMenuRequested(QPoint)) );
}
void ResourceAssignmentView::slotRequestPopupMenu( const QPoint &p )
{
debugPlan << p;
emit requestPopupMenu( "resourceassigment_popup", QCursor::pos() );
}
void ResourceAssignmentView::draw( Project &project )
{
m_project = &project;
m_resList->clear();
foreach ( ResourceGroup * gr, project.resourceGroups() ) {
QTreeWidgetItem * item = new QTreeWidgetItem( m_resList );
item->setText( 0, gr->name() );
drawResourcesName( item, gr );
debugPlan <<"GROUP FOUND";
}
}
/*This function is called for the left panel*/
void ResourceAssignmentView::drawResourcesName( QTreeWidgetItem *parent, ResourceGroup *group )
{
/*for each resource*/
foreach ( Resource * res, group->resources() ) {
QTreeWidgetItem * item = new QTreeWidgetItem( parent );
/*Determine the name and the type of the resource*/
switch ( res->type() ) {
case Resource::Type_Work:
item->setText( 0, res->name() );
item->setText( 1, i18n( "Work" ) );
break;
case Resource::Type_Material:
item->setText( 0, res->name() );
item->setText( 1, i18n( "Material" ) );
break;
default:
break;
}
}
}
/*Constructor*/
ResourceAssignmentView::ResourceAssignmentView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent),
m_project( 0 )
{
debugPlan <<" ---------------- KPlato: Creating ResourceAssignmentView ----------------";
widget.setupUi(this);
/* QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin( 0 );
m_splitter = new QSplitter( this );
l->addWidget( m_splitter );
m_splitter->setOrientation( Qt::Horizontal );
m_resList = new ResourcesList( m_splitter );
QStringList sl;
sl << i18n( "Name" ) << i18n( "Type" );
m_resList->setHeaderLabels( sl );
m_taskList = new ResourcesList( m_splitter );
m_tasktreeroot = new QTreeWidgetItem ( m_taskList );
QStringList sl2;
sl2 << i18n( "Task" ); << i18n( "Completed" );
m_taskList->setHeaderLabels( sl2 );*/
/* m_resList = widget.assign( m_resList );
m_taskList = widget.assign( m_taskList );
m_tasktreeroot = widget.assign( m_tasktreeroot );*/
m_selectedItem = 0;
m_splitter = widget.m_splitter;
m_resList = (ResourcesList *)widget.m_resList;
m_taskList = (ResourcesList *)widget.m_taskList;
m_part = doc;
m_tasktreeroot = new QTreeWidgetItem ( m_taskList );
connect( m_resList, SIGNAL(itemSelectionChanged()), SLOT(resSelectionChanged()) );
connect( m_taskList, &QWidget::customContextMenuRequested, this, &ResourceAssignmentView::slotRequestPopupMenu );
}
/*Store the selected item*/
void ResourceAssignmentView::resSelectionChanged()
{
QTreeWidgetItem * item = 0;
QList<QTreeWidgetItem*> selList = m_resList->selectedItems();
if ( !selList.isEmpty() )
item = selList.first();
resSelectionChanged( item );
}
/*Update tasks attributed to the selected item*/
void ResourceAssignmentView::resSelectionChanged( QTreeWidgetItem *item )
{
QTreeWidgetItem * resItem = item;
if ( resItem ) {
m_selectedItem = resItem;
updateTasks();
return ;
}
m_selectedItem = 0;
// updateTasks(); that method uses m_selectedItem, so this will always crash... CID 3206
}
/**/
void ResourceAssignmentView::updateTasks()
{
Q_ASSERT(m_selectedItem);
/*Find Selected Item*/
Resource* ItemRes = 0;
ResourceGroup* ItemGrp = 0;
QString name = m_selectedItem->text(0);
QString type = m_selectedItem->text(1);
if(!type.isEmpty()){
debugPlan <<"Item Selected:" << name <<" / Type:" << type;
}
else{
debugPlan <<"Group Selected:" << name;
}
m_taskList->clear();
if ( m_project == 0 ) {
return;
}
/*Find tasks attributed to the selected item*/
/*The selected item is a resource*/
if(!type.isEmpty())
{
foreach ( ResourceGroup * gr, m_project->resourceGroups() ) {
foreach ( Resource * res, gr->resources() ) {
if (name == res->name())
{
ItemRes = res;
debugPlan <<"Selected Resource found";
}
else
{
debugPlan <<"Not found";
}
}
}
drawTasksAttributedToAResource(ItemRes,m_tasktreeroot);
}
else
/*The selected item is a group*/
{
foreach ( ResourceGroup * gr, m_project->resourceGroups() ) {
if (name == gr->name())
{
ItemGrp = gr;
debugPlan <<"[void KPlato::ResourceAssignmentView::updateTasks()] Selected Group founded";
}
else
{
debugPlan <<"[void KPlato::ResourceAssignmentView::updateTasks()] Group Not founded";
}
}
drawTasksAttributedToAGroup(ItemGrp,m_tasktreeroot);
}
}
void ResourceAssignmentView::drawTasksAttributedToAResource (Resource *res, QTreeWidgetItem */*parent*/)
{
QString taskName;
Task *currentTask;
/*Differents state regrouping tasks*/
QTreeWidgetItem *notStarted;
QTreeWidgetItem *started;
QTreeWidgetItem *finished;
QString advance ;
/*Task node*/
QTreeWidgetItem * item;
/*Put the name of the resource on the node*/
/*Case: the resource has no task attributed*/
if((res->requests()).isEmpty())
{
QTreeWidgetItem * item = new QTreeWidgetItem( m_taskList );
item->setText( 0, i18n( "No task attributed" ) );
}
else
/*Case: the resource has tasks attributed*/
{
/*Creation of 3 categories of task*/
notStarted = new QTreeWidgetItem( m_taskList );
started = new QTreeWidgetItem( m_taskList );
finished = new QTreeWidgetItem( m_taskList );
/*Set names of categories*/
notStarted->setText( 0, i18n( "Not Started" ) );
started->setText( 0, i18n( "Started" ) );
finished->setText( 0, i18n( "Finished" ) );
/*For each task attributed to the current resource*/
foreach ( ResourceRequest * rr , res->requests() ){
/*get name*/
currentTask = (rr->parent())->task();
taskName = currentTask->name();
/*get status*/
/*State: started*/
if ((((rr->parent())->task())->completion().isStarted()) && !(((rr->parent())->task())->completion().isFinished()))
{
debugPlan <<"[void KPlato::ResourceAssignmentView::drawTasksAttributedToAResource()] task started";
/*adding to the tree*/
item = new QTreeWidgetItem( started );
item->setText( 0, taskName );
/*Determine the task's advance*/
int percent = ((rr->parent())->task())->completion().percentFinished();
//debugPlan <<"[void KPlato::ResourceAssignmentView::drawTasksAttributedToAResource()]" << percent <<"";
advance.setNum(percent);
advance += '%';
item->setText( 1, advance );
}
/*State: Finished*/
else if (((rr->parent())->task())->completion().isFinished())
{
/*adding to the tree*/
debugPlan <<"[void KPlato::ResourceAssignmentView::drawTasksAttributedToAResource()] task finished";
item = new QTreeWidgetItem( finished );
item->setText( 0, taskName );
}
/*State not started*/
else
{
/*adding to the tree*/
debugPlan <<"[void KPlato::ResourceAssignmentView::drawTasksAttributedToAResource()] task not started";
item = new QTreeWidgetItem( notStarted );
item->setText( 0, taskName );
}
}
}
}
void ResourceAssignmentView::drawTasksAttributedToAGroup (ResourceGroup *group, QTreeWidgetItem *parent)
{
QString taskName;
Task *currentTask;
bool alreadyStored;
/*Task node*/
QTreeWidgetItem * item;
/*Differents state regrouping tasks*/
QTreeWidgetItem *notStarted;
QTreeWidgetItem *started;
QTreeWidgetItem *finished;
QString advance ;
if((group->resources()).isEmpty())
{
QTreeWidgetItem * groupnode = new QTreeWidgetItem( parent );
groupnode->setText( 0, i18n( "No resource attributed" ) );
}
else
{
/*Creation of 3 categories of task*/
notStarted = new QTreeWidgetItem( m_taskList );
started = new QTreeWidgetItem( m_taskList );
finished = new QTreeWidgetItem( m_taskList );
/*Set names of categories*/
notStarted->setText( 0, i18n( "Not Started" ) );
started->setText( 0, i18n( "Started" ) );
finished->setText( 0, i18n( "Finished" ) );
foreach ( Resource * res, group->resources() ) {
foreach ( ResourceRequest * rr , res->requests() ) {
/*get name*/
currentTask = (rr->parent())->task();
taskName = currentTask->name();
alreadyStored = false;
/*store tasks in the tree*/
if ((((rr->parent())->task())->completion().isStarted()) && !(((rr->parent())->task())->completion().isFinished()))
{
for (int i = 0; i < started->childCount();i++)
{
if (started->child(i)->text(0) == taskName)
{ alreadyStored = true ;}
}
if ( !alreadyStored )
{
item = new QTreeWidgetItem( started );
item->setText( 0, taskName );
/*Determine the task's advance*/
int percent = ((rr->parent())->task())->completion().percentFinished();
advance.setNum(percent);
advance += '%';
item->setText( 1, advance );
}
}
else if (((rr->parent())->task())->completion().isFinished())
{
for (int i = 0; i < finished->childCount();i++)
{
if (finished->child(i)->text(0) == taskName)
{ alreadyStored = true ;}
}
if ( !alreadyStored )
{
item = new QTreeWidgetItem( finished );
item->setText( 0, taskName );
}
}
else
{
for (int i = 0; i < notStarted->childCount();i++)
{
if (notStarted->child(i)->text(0) == taskName)
{ alreadyStored = true ;}
}
if ( !alreadyStored )
{
item = new QTreeWidgetItem( notStarted );
item->setText( 0, taskName );
}
}
}
}
}
}
void ResourceAssignmentView::setGuiActive( bool activate )
{
debugPlan<<activate;
// updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
}
void ResourceAssignmentView::slotUpdate(){
draw(*m_project);
}
} //KPlato namespace
diff --git a/src/libs/ui/kptresourcedialog.cpp b/src/libs/ui/kptresourcedialog.cpp
index 937940db..1e84c32c 100644
--- a/src/libs/ui/kptresourcedialog.cpp
+++ b/src/libs/ui/kptresourcedialog.cpp
@@ -1,468 +1,469 @@
/* This file is part of the KDE project
Copyright (C) 2003 - 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptresourcedialog.h"
#include "kptlocale.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptresource.h"
#include "kptcalendar.h"
#include "kptresourcemodel.h"
#include "kptdebug.h"
#include <QList>
#include <QStringList>
#include <QSortFilterProxyModel>
#include <QStandardItem>
#ifdef PLAN_KDEPIMLIBS_FOUND
#include <akonadi/contact/emailaddressselectiondialog.h>
#include <akonadi/contact/emailaddressselectionwidget.h>
#include <akonadi/contact/emailaddressselection.h>
#endif
namespace KPlato
{
ResourceDialogImpl::ResourceDialogImpl( const Project &project, Resource &resource, bool baselined, QWidget *parent )
: QWidget(parent),
m_project( project ),
m_resource( resource )
{
setupUi(this);
#ifndef PLAN_KDEPIMLIBS_FOUND
chooseBtn->hide();
#endif
// FIXME
// [Bug 311940] New: Plan crashes when typing a text in the filter textbox before the textbook is fully loaded when selecting a contact from the adressbook
chooseBtn->hide();
QSortFilterProxyModel *pr = new QSortFilterProxyModel( ui_teamView );
QStandardItemModel *m = new QStandardItemModel( ui_teamView );
pr->setSourceModel( new QStandardItemModel( ui_teamView ) );
ui_teamView->setModel( m );
m->setHorizontalHeaderLabels( QStringList() << xi18nc( "title:column", "Select team members" ) << xi18nc( "title:column", "Group" ) );
foreach ( Resource *r, m_project.resourceList() ) {
if ( r->type() != Resource::Type_Work || r->id() == m_resource.id() ) {
continue;
}
QList<QStandardItem *> items;
QStandardItem *item = new QStandardItem( r->name() );
item->setCheckable( true );
item->setCheckState( m_resource.teamMemberIds().contains( r->id() ) ? Qt::Checked : Qt::Unchecked );
items << item;
item = new QStandardItem( r->parentGroup()->name() );
items << item;
// Add id so we can find the resource
item = new QStandardItem( r->id() );
items << item;
m->appendRow( items );
}
if ( baselined ) {
type->setEnabled( false );
rateEdit->setEnabled( false );
overtimeEdit->setEnabled( false );
account->setEnabled( false );
}
// hide resource identity (last column)
ui_teamView->setColumnHidden( m->columnCount() - 1, true );
ui_teamView->resizeColumnToContents( 0 );
ui_teamView->sortByColumn( 0, Qt::AscendingOrder );
slotTypeChanged( resource.type() );
connect( m, &QAbstractItemModel::dataChanged, this, &ResourceDialogImpl::slotTeamChanged);
connect(group, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(type, SIGNAL(activated(int)), SLOT(slotTypeChanged(int)));
connect(units, SIGNAL(valueChanged(int)), SLOT(slotChanged()));
connect(nameEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
connect(initialsEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
connect(emailEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
connect(calendarList, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(rateEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
connect(overtimeEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
connect(chooseBtn, &QAbstractButton::clicked, this, &ResourceDialogImpl::slotChooseResource);
connect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotChanged);
connect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotChanged);
connect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableFromChanged);
connect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableUntilChanged);
connect(ui_rbfrom, &QAbstractButton::toggled, this, &ResourceDialogImpl::slotChanged);
connect(ui_rbuntil, &QAbstractButton::toggled, this, &ResourceDialogImpl::slotChanged);
connect(ui_rbfrom, &QAbstractButton::toggled, availableFrom, &QWidget::setEnabled);
connect(ui_rbuntil, &QAbstractButton::toggled, availableUntil, &QWidget::setEnabled);
connect( useRequired, &QCheckBox::stateChanged, this, &ResourceDialogImpl::slotUseRequiredChanged );
connect(account, SIGNAL(activated(int)), SLOT(slotChanged()));
}
void ResourceDialogImpl::slotTeamChanged( const QModelIndex &index ) {
if ( ! index.isValid() ) {
return;
}
bool checked = (bool)(index.data( Qt::CheckStateRole ).toInt());
int idCol = index.model()->columnCount() - 1;
QString id = index.model()->index( index.row(), idCol ).data().toString();
if ( checked ) {
if ( ! m_resource.teamMemberIds().contains( id ) ) {
m_resource.addTeamMemberId( id );
}
} else {
m_resource.removeTeamMemberId( id );
}
emit changed();
}
void ResourceDialogImpl::slotTypeChanged( int index ) {
switch ( index ) {
case Resource::Type_Work:
ui_stackedWidget->setCurrentIndex( 0 );
useRequired->setEnabled( true );
slotUseRequiredChanged( useRequired->checkState() );
break;
case Resource::Type_Material:
ui_stackedWidget->setCurrentIndex( 0 );
useRequired->setEnabled( false );
slotUseRequiredChanged( false );
break;
case Resource::Type_Team:
ui_stackedWidget->setCurrentIndex( 1 );
break;
}
emit changed();
}
void ResourceDialogImpl::slotChanged() {
emit changed();
}
void ResourceDialogImpl::setCurrentIndexes( const QModelIndexList &lst )
{
m_currentIndexes.clear();
foreach ( const QModelIndex &idx, lst ) {
m_currentIndexes << QPersistentModelIndex( idx );
}
useRequired->setCheckState( m_currentIndexes.isEmpty() ? Qt::Unchecked : Qt::Checked );
if ( useRequired->isChecked() ) {
required->setCurrentIndexes( m_currentIndexes );
}
required->setEnabled( useRequired->isChecked() );
}
void ResourceDialogImpl::slotUseRequiredChanged( int state )
{
required->setEnabled( state );
if ( state ) {
required->setCurrentIndexes( m_currentIndexes );
} else {
m_currentIndexes = required->currentIndexes();
required->setCurrentIndexes( QList<QPersistentModelIndex>() );
}
slotChanged();
}
void ResourceDialogImpl::slotAvailableFromChanged(const QDateTime&) {
if (availableUntil->dateTime() < availableFrom->dateTime()) {
disconnect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableUntilChanged);
//debugPlan<<"From:"<<availableFrom->dateTime().toString()<<" until="<<availableUntil->dateTime().toString();
availableUntil->setDateTime(availableFrom->dateTime());
connect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableUntilChanged);
}
}
void ResourceDialogImpl::slotAvailableUntilChanged(const QDateTime&) {
if (availableFrom->dateTime() > availableUntil->dateTime()) {
disconnect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableFromChanged);
//debugPlan<<"Until:"<<availableUntil->dateTime().toString()<<" from="<<availableFrom->dateTime().toString();
availableFrom->setDateTime(availableUntil->dateTime());
connect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableFromChanged);
}
}
void ResourceDialogImpl::slotCalculationNeeded(const QString&) {
emit calculate();
emit changed();
}
void ResourceDialogImpl::slotChooseResource()
{
#ifdef PLAN_KDEPIMLIBS_FOUND
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog( this );
if ( dlg->exec() && dlg ) {
QStringList s;
const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
if ( ! selections.isEmpty() ) {
const Akonadi::EmailAddressSelection s = selections.first();
nameEdit->setText( s.name() );
emailEdit->setText( s.email() );
const QStringList l = s.name().split(' ');
QString in;
QStringList::ConstIterator it = l.begin();
for (/*int i = 0*/; it != l.end(); ++it) {
in += (*it)[0];
}
initialsEdit->setText(in);
}
}
#endif
}
////////////////// ResourceDialog ////////////////////////
ResourceDialog::ResourceDialog(Project &project, Resource *resource, QWidget *parent, const char *name)
: KoDialog(parent),
m_project( project ),
m_original(resource),
m_resource(resource),
m_calculationNeeded(false)
{
setObjectName(name);
setCaption( i18n("Resource Settings") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
dia = new ResourceDialogImpl(project, m_resource, resource->isBaselined(), this);
setMainWidget(dia);
KoDialog::enableButtonOk(false);
if ( resource->parentGroup() == 0 ) {
//HACK to handle calls from ResourcesPanel
dia->groupLabel->hide();
dia->group->hide();
} else {
foreach ( ResourceGroup *g, project.resourceGroups() ) {
m_groups.insert( g->name(), g );
}
dia->group->addItems( m_groups.keys() );
dia->group->setCurrentIndex( m_groups.values().indexOf( resource->parentGroup() ) ); // clazy:exclude=container-anti-pattern
}
dia->nameEdit->setText(resource->name());
dia->initialsEdit->setText(resource->initials());
dia->emailEdit->setText(resource->email());
dia->type->setCurrentIndex((int)resource->type()); // NOTE: must match enum
dia->units->setValue(resource->units());
DateTime dt = resource->availableFrom();
if ( dt.isValid() ) {
dia->ui_rbfrom->click();
} else {
dia->ui_rbfromunlimited->click();
}
dia->availableFrom->setDateTime( dt.isValid() ? dt : QDateTime( QDate::currentDate(), QTime( 0, 0, 0 ), Qt::LocalTime ) );
dia->availableFrom->setEnabled( dt.isValid() );
dt = resource->availableUntil();
if ( dt.isValid() ) {
dia->ui_rbuntil->click();
} else {
dia->ui_rbuntilunlimited->click();
}
dia->availableUntil->setDateTime( dt.isValid() ? dt : QDateTime( QDate::currentDate().addYears( 2 ), QTime( 0, 0, 0 ), Qt::LocalTime ) );
dia->availableUntil->setEnabled( dt.isValid() );
dia->rateEdit->setText(project.locale()->formatMoney(resource->normalRate()));
dia->overtimeEdit->setText(project.locale()->formatMoney(resource->overtimeRate()));
int cal = 0;
dia->calendarList->addItem(i18n("None"));
m_calendars.insert(0, 0);
QList<Calendar*> list = project.allCalendars();
int i=1;
foreach (Calendar *c, list) {
dia->calendarList->insertItem(i, c->name());
m_calendars.insert(i, c);
if (c == resource->calendar(true)) {
cal = i;
}
++i;
}
dia->calendarList->setCurrentIndex(cal);
ResourceItemSFModel *m = new ResourceItemSFModel( this );
m->setProject( &project );
dia->required->setModel( m );
dia->required->view()->expandAll();
QItemSelectionModel *sm = dia->required->view()->selectionModel();
foreach ( Resource *r, resource->requiredResources() ) {
sm->select( m->index( r ), QItemSelectionModel::Select | QItemSelectionModel::Rows );
}
dia->setCurrentIndexes( sm->selectedRows() );
QStringList lst;
lst << i18n( "None" ) << m_project.accounts().costElements();
dia->account->addItems( lst );
if ( resource->account() ) {
dia->account->setCurrentIndex( lst.indexOf( resource->account()->name() ) );
}
connect(dia, SIGNAL(changed()), SLOT(enableButtonOk()));
connect(dia, &ResourceDialogImpl::calculate, this, &ResourceDialog::slotCalculationNeeded);
connect(dia->calendarList, SIGNAL(activated(int)), SLOT(slotCalendarChanged(int)));
connect(dia->required, SIGNAL(changed()), SLOT(enableButtonOk()));
connect(dia->account, SIGNAL(currentIndexChanged(QString)), SLOT(slotAccountChanged(QString)));
connect(&project, &Project::resourceRemoved, this, &ResourceDialog::slotResourceRemoved);
}
void ResourceDialog::slotResourceRemoved( const Resource *resource )
{
if ( m_original == resource ) {
reject();
}
}
void ResourceDialog::enableButtonOk() {
KoDialog::enableButtonOk(true);
}
void ResourceDialog::slotCalculationNeeded() {
m_calculationNeeded = true;
}
void ResourceDialog::slotButtonClicked(int button) {
if (button == KoDialog::Ok) {
slotOk();
} else {
KoDialog::slotButtonClicked(button);
}
}
void ResourceDialog::slotOk() {
if ( ! m_groups.isEmpty() ) {
//HACK to handle calls from ResourcesPanel
m_resource.setParentGroup( m_groups.value( dia->group->currentText() ) );
}
m_resource.setName(dia->nameEdit->text());
m_resource.setInitials(dia->initialsEdit->text());
m_resource.setEmail(dia->emailEdit->text());
m_resource.setType((Resource::Type)(dia->type->currentIndex()));
m_resource.setUnits(dia->units->value());
m_resource.setNormalRate(m_project.locale()->readMoney(dia->rateEdit->text()));
m_resource.setOvertimeRate(m_project.locale()->readMoney(dia->overtimeEdit->text()));
m_resource.setCalendar(m_calendars[dia->calendarList->currentIndex()]);
m_resource.setAvailableFrom( dia->ui_rbfrom->isChecked() ? dia->availableFrom->dateTime() : QDateTime() );
m_resource.setAvailableUntil( dia->ui_rbuntil->isChecked() ? dia->availableUntil->dateTime() : QDateTime() );
ResourceItemSFModel *m = static_cast<ResourceItemSFModel*>( dia->required->model() );
QStringList lst;
foreach ( const QModelIndex &i, dia->required->currentIndexes() ) {
Resource *r = m->resource( i );
if ( r ) lst << r->id();
}
m_resource.setRequiredIds( lst );
accept();
}
void ResourceDialog::slotCalendarChanged(int /*cal*/) {
}
void ResourceDialog::slotAccountChanged( const QString &name )
{
m_resource.setAccount( m_project.accounts().findAccount( name ) );
}
MacroCommand *ResourceDialog::buildCommand() {
return buildCommand(m_original, m_resource);
}
// static
MacroCommand *ResourceDialog::buildCommand(Resource *original, Resource &resource) {
MacroCommand *m=0;
KUndo2MagicString n = kundo2_i18n("Modify Resource");
if (resource.parentGroup() != 0 && resource.parentGroup() != original->parentGroup()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new MoveResourceCmd(resource.parentGroup(), original));
}
if (resource.name() != original->name()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceNameCmd(original, resource.name()));
}
if (resource.initials() != original->initials()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceInitialsCmd(original, resource.initials()));
}
if (resource.email() != original->email()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceEmailCmd(original, resource.email()));
}
if (resource.type() != original->type()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceTypeCmd(original, resource.type()));
}
if (resource.units() != original->units()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceUnitsCmd(original, resource.units()));
}
if (resource.availableFrom() != original->availableFrom()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceAvailableFromCmd(original, resource.availableFrom()));
}
if (resource.availableUntil() != original->availableUntil()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceAvailableUntilCmd(original, resource.availableUntil()));
}
if (resource.normalRate() != original->normalRate()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceNormalRateCmd(original, resource.normalRate()));
}
if (resource.overtimeRate() != original->overtimeRate()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceOvertimeRateCmd(original, resource.overtimeRate()));
}
if (resource.calendar(true) != original->calendar(true)) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyResourceCalendarCmd(original, resource.calendar(true)));
}
if (resource.requiredIds() != original->requiredIds()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ModifyRequiredResourcesCmd(original, resource.requiredIds()));
}
if (resource.account() != original->account()) {
if (!m) m = new MacroCommand(n);
m->addCommand(new ResourceModifyAccountCmd(*original, original->account(), resource.account()));
}
if ( resource.type() == Resource::Type_Team ) {
//debugPlan<<original->teamMembers()<<resource.teamMembers();
foreach ( const QString &id, resource.teamMemberIds() ) {
if ( ! original->teamMemberIds().contains( id ) ) {
if (!m) m = new MacroCommand(n);
m->addCommand( new AddResourceTeamCmd( original, id ) );
}
}
foreach ( const QString &id, original->teamMemberIds() ) {
if ( ! resource.teamMemberIds().contains( id ) ) {
if (!m) m = new MacroCommand(n);
m->addCommand( new RemoveResourceTeamCmd( original, id ) );
}
}
}
return m;
}
} //KPlato namespace
diff --git a/src/libs/ui/kptresourceeditor.cpp b/src/libs/ui/kptresourceeditor.cpp
index a4cb56dc..6ed39ddb 100644
--- a/src/libs/ui/kptresourceeditor.cpp
+++ b/src/libs/ui/kptresourceeditor.cpp
@@ -1,408 +1,409 @@
/* This file is part of the KDE project
Copyright (C) 2006 - 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptresourceeditor.h"
#include "kptresourcemodel.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdatetime.h"
#include "kptitemviewsettup.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <KoIcon.h>
#include <QList>
#include <QVBoxLayout>
#include <QDragMoveEvent>
#include <QAction>
#include <QMenu>
#include <KLocalizedString>
#include <kactioncollection.h>
namespace KPlato
{
ResourceTreeView::ResourceTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
// header()->setContextMenuPolicy( Qt::CustomContextMenu );
setStretchLastSection( false );
ResourceItemModel *m = new ResourceItemModel( this );
setModel( m );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
createItemDelegates( m );
connect( this, &DoubleTreeViewBase::dropAllowed, this, &ResourceTreeView::slotDropAllowed );
}
void ResourceTreeView::slotDropAllowed( const QModelIndex &index, int dropIndicatorPosition, QDragMoveEvent *event )
{
event->ignore();
if ( model()->dropAllowed( index, dropIndicatorPosition, event->mimeData() ) ) {
event->accept();
}
}
QObject *ResourceTreeView::currentObject() const
{
return model()->object( selectionModel()->currentIndex() );
}
QList<QObject*> ResourceTreeView::selectedObjects() const
{
QList<QObject*> lst;
foreach (const QModelIndex &i, selectionModel()->selectedRows() ) {
lst << static_cast<QObject*>( i.internalPointer() );
}
return lst;
}
QList<ResourceGroup*> ResourceTreeView::selectedGroups() const
{
QList<ResourceGroup*> gl;
foreach ( QObject *o, selectedObjects() ) {
ResourceGroup* g = qobject_cast<ResourceGroup*>( o );
if ( g ) {
gl << g;
}
}
return gl;
}
QList<Resource*> ResourceTreeView::selectedResources() const
{
QList<Resource*> rl;
foreach ( QObject *o, selectedObjects() ) {
Resource* r = qobject_cast<Resource*>( o );
if ( r ) {
rl << r;
}
}
return rl;
}
//-----------------------------------
ResourceEditor::ResourceEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Resource Editor</title>"
"<para>"
"Resources are organized in a Resource Breakdown Structure. "
"Resources can be of type <emphasis>Work</emphasis> or <emphasis>Material</emphasis>. "
"When assigned to a task, a resource of type <emphasis>Work</emphasis> can affect the duration of the task, while a resource of type <emphasis>Material</emphasis> does not. "
"A resource must refer to a <emphasis>Calendar</emphasis> defined in the <emphasis>Work and Vacation Editor</emphasis>."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Resource_Editor")));
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new ResourceTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
l->addWidget( m_view );
setupGui();
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
m_view->setDragDropMode( QAbstractItemView::DragDrop );
m_view->setDropIndicatorShown( true );
m_view->setDragEnabled ( true );
m_view->setAcceptDrops( true );
// m_view->setAcceptDropsOnView( true );
QList<int> lst1; lst1 << 1 << -1;
QList<int> lst2; lst2 << 0;
m_view->hideColumns( lst1, lst2 );
m_view->masterView()->setDefaultColumns( QList<int>() << 0 );
QList<int> show;
for ( int c = 1; c < model()->columnCount(); ++c ) {
show << c;
}
m_view->slaveView()->setDefaultColumns( show );
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &ResourceEditor::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &ResourceEditor::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &ResourceEditor::slotContextMenuRequested );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
}
void ResourceEditor::updateReadWrite( bool readwrite )
{
m_view->setReadWrite( readwrite );
}
void ResourceEditor::setProject( Project *project )
{
debugPlan<<project;
m_view->setProject( project );
ViewBase::setProject( project );
}
void ResourceEditor::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void ResourceEditor::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
QString name;
if ( index.isValid() ) {
QObject *obj = m_view->model()->object( index );
ResourceGroup *g = qobject_cast<ResourceGroup*>( obj );
if ( g ) {
//name = "resourceeditor_group_popup";
} else {
Resource *r = qobject_cast<Resource*>( obj );
if ( r && !r->isShared() ) {
name = "resourceeditor_resource_popup";
}
}
}
m_view->setContextMenuIndex(index);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
return;
}
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
Resource *ResourceEditor::currentResource() const
{
return qobject_cast<Resource*>( m_view->currentObject() );
}
ResourceGroup *ResourceEditor::currentResourceGroup() const
{
return qobject_cast<ResourceGroup*>( m_view->currentObject() );
}
void ResourceEditor::slotCurrentChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
// slotEnableActions();
}
void ResourceEditor::slotSelectionChanged( const QModelIndexList& )
{
//debugPlan<<list.count();
updateActionsEnabled();
}
void ResourceEditor::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void ResourceEditor::updateActionsEnabled( bool on )
{
bool o = on && m_view->project();
QList<ResourceGroup*> groupList = m_view->selectedGroups();
bool nogroup = groupList.isEmpty();
bool group = groupList.count() == 1;
QList<Resource*> resourceList = m_view->selectedResources();
bool noresource = resourceList.isEmpty();
bool resource = resourceList.count() == 1;
bool any = !nogroup || !noresource;
actionAddResource->setEnabled( o && ( (group && noresource) || (resource && nogroup) ) );
actionAddGroup->setEnabled( o );
if ( o && any ) {
foreach ( ResourceGroup *g, groupList ) {
if ( g->isBaselined() ) {
o = false;
break;
}
}
}
if ( o && any ) {
foreach ( Resource *r, resourceList ) {
if ( r->isBaselined() ) {
o = false;
break;
}
}
}
actionDeleteSelection->setEnabled( o && any );
}
void ResourceEditor::setupGui()
{
QString name = "resourceeditor_edit_list";
actionAddGroup = new QAction(koIcon("resource-group-new"), i18n("Add Resource Group"), this);
actionCollection()->addAction("add_group", actionAddGroup );
actionCollection()->setDefaultShortcut(actionAddGroup, Qt::CTRL + Qt::Key_I);
connect( actionAddGroup, &QAction::triggered, this, &ResourceEditor::slotAddGroup );
addAction( name, actionAddGroup );
actionAddResource = new QAction(koIcon("list-add-user"), i18n("Add Resource"), this);
actionCollection()->addAction("add_resource", actionAddResource );
actionCollection()->setDefaultShortcut(actionAddResource, Qt::CTRL + Qt::SHIFT + Qt::Key_I);
connect( actionAddResource, &QAction::triggered, this, &ResourceEditor::slotAddResource );
addAction( name, actionAddResource );
actionDeleteSelection = new QAction(koIcon("edit-delete"), xi18nc("@action", "Delete"), this);
actionCollection()->addAction("delete_selection", actionDeleteSelection );
actionCollection()->setDefaultShortcut(actionDeleteSelection, Qt::Key_Delete);
connect( actionDeleteSelection, &QAction::triggered, this, &ResourceEditor::slotDeleteSelection );
addAction( name, actionDeleteSelection );
// Add the context menu actions for the view options
connect(m_view->actionSplitView(), &QAction::triggered, this, &ResourceEditor::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionAll);
}
void ResourceEditor::slotSplitView()
{
debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
emit optionsModified();
}
void ResourceEditor::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, m_view, this );
dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void ResourceEditor::slotAddResource()
{
//debugPlan;
QList<ResourceGroup*> gl = m_view->selectedGroups();
if ( gl.count() > 1 ) {
return;
}
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
ResourceGroup *g = 0;
if ( !gl.isEmpty() ) {
g = gl.first();
} else {
QList<Resource*> rl = m_view->selectedResources();
if ( rl.count() != 1 ) {
return;
}
g = rl.first()->parentGroup();
}
if ( g == 0 ) {
return;
}
Resource *r = new Resource();
if ( g->type() == ResourceGroup::Type_Material ) {
r->setType( Resource::Type_Material );
}
QModelIndex i = m_view->model()->insertResource( g, r );
if ( i.isValid() ) {
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}
}
void ResourceEditor::slotAddGroup()
{
//debugPlan;
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
ResourceGroup *g = new ResourceGroup();
QModelIndex i = m_view->model()->insertGroup( g );
if ( i.isValid() ) {
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->edit( i );
}
}
void ResourceEditor::slotDeleteSelection()
{
QObjectList lst = m_view->selectedObjects();
//debugPlan<<lst.count()<<" objects";
if ( ! lst.isEmpty() ) {
emit deleteObjectList( lst );
QModelIndex i = m_view->selectionModel()->currentIndex();
if ( i.isValid() ) {
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
}
}
}
bool ResourceEditor::loadContext( const KoXmlElement &context )
{
debugPlan<<objectName();
ViewBase::loadContext( context );
return m_view->loadContext( model()->columnMap(), context );
}
void ResourceEditor::saveContext( QDomElement &context ) const
{
debugPlan<<objectName();
ViewBase::saveContext( context );
m_view->saveContext( model()->columnMap(), context );
}
KoPrintJob *ResourceEditor::createPrintJob()
{
return m_view->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptscheduleeditor.cpp b/src/libs/ui/kptscheduleeditor.cpp
index 773ae873..501da8cb 100644
--- a/src/libs/ui/kptscheduleeditor.cpp
+++ b/src/libs/ui/kptscheduleeditor.cpp
@@ -1,856 +1,857 @@
/* This file is part of the KDE project
Copyright (C) 2006-2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptscheduleeditor.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptduration.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptschedule.h"
#include "kptdatetime.h"
#include "kptpertresult.h"
#include "kptitemviewsettup.h"
#include "kptrecalculatedialog.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <KoIcon.h>
#include <QList>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QApplication>
#include <QClipboard>
#include <QContextMenuEvent>
#include <QAction>
#include <QMenu>
#include <KLocalizedString>
#include <kactioncollection.h>
#include <ktoggleaction.h>
namespace KPlato
{
ScheduleTreeView::ScheduleTreeView( QWidget *parent )
: TreeViewBase( parent )
{
header()->setStretchLastSection ( false );
ScheduleItemModel *m = new ScheduleItemModel( this );
setModel( m );
//setSelectionBehavior( QAbstractItemView::SelectItems );
setSelectionMode( QAbstractItemView::SingleSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
setTreePosition(-1); // always visual index 0
createItemDelegates( m );
}
void ScheduleTreeView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel )
{
//debugPlan<<sel.indexes().count();
foreach( const QModelIndex &i, selectionModel()->selectedIndexes() ) {
Q_UNUSED(i);
//debugPlan<<i.row()<<","<<i.column();
}
QTreeView::selectionChanged( sel, desel );
emit selectionChanged( selectionModel()->selectedIndexes() );
}
void ScheduleTreeView::currentChanged( const QModelIndex & current, const QModelIndex & previous )
{
//debugPlan<<current.row()<<","<<current.column();
QTreeView::currentChanged( current, previous );
emit currentChanged( current );
// possible bug in qt: in QAbstractItemView::SingleSelection you can select multiple items/rows
selectionModel()->select( current, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
}
ScheduleManager *ScheduleTreeView::manager( const QModelIndex &idx ) const
{
return model()->manager( idx );
}
ScheduleManager *ScheduleTreeView::currentManager() const
{
return model()->manager( currentIndex() );
}
QModelIndexList ScheduleTreeView::selectedRows() const
{
QModelIndexList lst = selectionModel()->selectedRows();
debugPlan<<lst;
return lst;
}
ScheduleManager *ScheduleTreeView::selectedManager() const
{
ScheduleManager *sm = 0;
QModelIndexList lst = selectedRows();
if ( lst.count() == 1 ) {
sm = model()->manager( lst.first() );
}
return sm;
}
//-----------------------------------
ScheduleEditor::ScheduleEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
setupGui();
slotEnableActions();
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_schedulingRange = new SchedulingRange(doc, this);
l->addWidget( m_schedulingRange );
m_view = new ScheduleTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &TreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &TreeViewBase::slotCollapse);
l->addWidget( m_view );
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
QList<int> show;
show << ScheduleModel::ScheduleName
<< ScheduleModel::ScheduleState
<< ScheduleModel::ScheduleDirection
<< ScheduleModel::ScheduleOverbooking
<< ScheduleModel::ScheduleDistribution
<< ScheduleModel::SchedulePlannedStart
<< ScheduleModel::SchedulePlannedFinish
<< ScheduleModel::ScheduleScheduler
<< ScheduleModel::ScheduleGranularity
;
QList<int> lst;
for ( int c = 0; c < model()->columnCount(); ++c ) {
if ( ! show.contains( c ) ) {
lst << c;
}
}
m_view->setColumnsHidden( lst );
m_view->setDefaultColumns( show );
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentChanged(QModelIndex)) );
connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), this, SLOT(slotSelectionChanged(QModelIndexList)) );
connect( model(), &QAbstractItemModel::dataChanged, this, &ScheduleEditor::updateActionsEnabled );
connect( m_view, &TreeViewBase::contextMenuRequested, this, &ScheduleEditor::slotContextMenuRequested );
connect( m_view, &TreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Schedule Editor</title>"
"<para>"
"The Schedule Editor is used to create, edit, calculate and delete schedules. "
"A schedule can have sub-schedules. A sub-schedule can use the projects progress data"
" in order to reschedule only tasks that are not yet finished."
" Rescheduling will then use e.g. actual start and remaining effort for the tasks."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Schedule_Editor")));
}
void ScheduleEditor::draw( Project &project )
{
m_view->setProject( &project );
m_schedulingRange->setProject(&project);
}
void ScheduleEditor::draw()
{
}
void ScheduleEditor::setGuiActive( bool activate )
{
//debugPlan<<activate;
ViewBase::setGuiActive( activate );
if ( activate && !m_view->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void ScheduleEditor::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
QString name;
m_view->setContextMenuIndex(index);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
return;
}
debugPlan<<name;
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
void ScheduleEditor::slotCurrentChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
}
void ScheduleEditor::slotSelectionChanged( const QModelIndexList &/*list*/)
{
//debugPlan<<list.count();
// Note: Don't use list as it includes all columns in a row
QModelIndexList lst = m_view->selectedRows(); // gets column 0 in each row (should be 1 or 0 rows)
if ( lst.count() == 1 ) {
ScheduleManager *sm = m_view->model()->manager( lst.first() );
emit scheduleSelectionChanged( sm );
} else {
emit scheduleSelectionChanged( 0 );
}
slotEnableActions();
}
void ScheduleEditor::updateActionsEnabled( const QModelIndex &index )
{
debugPlan<<index;
slotEnableActions();
}
void ScheduleEditor::slotEnableActions()
{
if ( ! isReadWrite() ) {
actionAddSchedule->setEnabled( false );
actionAddSubSchedule->setEnabled( false );
actionDeleteSelection->setEnabled( false );
actionCalculateSchedule->setEnabled( false );
actionBaselineSchedule->setEnabled( false );
actionMoveLeft->setEnabled( false );
return;
}
QModelIndexList lst = m_view->selectedRows();
if ( lst.isEmpty() ) {
actionAddSchedule->setEnabled( true );
actionAddSubSchedule->setEnabled( false );
actionDeleteSelection->setEnabled( false );
actionCalculateSchedule->setEnabled( false );
actionBaselineSchedule->setEnabled( false );
actionMoveLeft->setEnabled( false );
return;
}
if ( lst.count() > 1 ) {
actionAddSchedule->setEnabled( false );
actionAddSubSchedule->setEnabled( false );
actionDeleteSelection->setEnabled( false );
actionCalculateSchedule->setEnabled( false );
actionBaselineSchedule->setEnabled( false );
actionMoveLeft->setEnabled( false );
return;
}
// one and only one manager selected
ScheduleManager *sm = m_view->manager( lst.first() );
Q_ASSERT( sm );
actionAddSchedule->setEnabled( true );
actionAddSubSchedule->setEnabled( sm->isScheduled() );
actionDeleteSelection->setEnabled( ! ( sm->isBaselined() || sm->isChildBaselined() ) );
actionCalculateSchedule->setEnabled( ! sm->scheduling() && sm->childCount() == 0 && ! ( sm->isBaselined() || sm->isChildBaselined() ) );
const char *const actionBaselineScheduleIconName =
sm->isBaselined() ? koIconNameCStr("view-time-schedule-baselined-remove") : koIconNameCStr("view-time-schedule-baselined-add");
actionBaselineSchedule->setIcon(QIcon::fromTheme(QLatin1String(actionBaselineScheduleIconName)));
// enable if scheduled and no one else is baselined
bool en = sm->isScheduled() && ( sm->isBaselined() || ! m_view->project()->isBaselined() );
actionBaselineSchedule->setEnabled( en );
actionMoveLeft->setEnabled( sm->parentManager() );
}
void ScheduleEditor::setupGui()
{
QString name = "scheduleeditor_edit_list";
actionAddSchedule = new QAction(koIcon("view-time-schedule-insert"), i18n("Add Schedule"), this);
actionCollection()->setDefaultShortcut(actionAddSchedule, Qt::CTRL + Qt::Key_I);
actionCollection()->addAction("add_schedule", actionAddSchedule );
connect( actionAddSchedule, &QAction::triggered, this, &ScheduleEditor::slotAddSchedule );
addAction( name, actionAddSchedule );
actionAddSubSchedule = new QAction(koIcon("view-time-schedule-child-insert"), i18n("Add Sub-schedule"), this);
actionCollection()->setDefaultShortcut(actionAddSubSchedule, Qt::CTRL + Qt::SHIFT + Qt::Key_I);
actionCollection()->addAction("add_subschedule", actionAddSubSchedule );
connect( actionAddSubSchedule, &QAction::triggered, this, &ScheduleEditor::slotAddSubSchedule );
addAction( name, actionAddSubSchedule );
actionDeleteSelection = new QAction(koIcon("edit-delete"), xi18nc("@action", "Delete"), this );
actionCollection()->setDefaultShortcut(actionDeleteSelection, Qt::Key_Delete);
actionCollection()->addAction("schedule_delete_selection", actionDeleteSelection );
connect( actionDeleteSelection, &QAction::triggered, this, &ScheduleEditor::slotDeleteSelection );
addAction( name, actionDeleteSelection );
actionCalculateSchedule = new QAction(koIcon("view-time-schedule-calculus"), i18n("Calculate"), this);
// actionCollection()->setDefaultShortcut(actionCalculateSchedule, Qt::CTRL + Qt::Key_C);
actionCollection()->addAction("calculate_schedule", actionCalculateSchedule );
connect( actionCalculateSchedule, &QAction::triggered, this, &ScheduleEditor::slotCalculateSchedule );
addAction( name, actionCalculateSchedule );
actionBaselineSchedule = new QAction(koIcon("view-time-schedule-baselined-add"), i18n("Baseline"), this);
// actionCollection()->setDefaultShortcut(actionBaselineSchedule, Qt::CTRL + Qt::Key_B);
actionCollection()->addAction("schedule_baseline", actionBaselineSchedule );
connect( actionBaselineSchedule, &QAction::triggered, this, &ScheduleEditor::slotBaselineSchedule );
addAction( name, actionBaselineSchedule );
actionMoveLeft = new QAction(koIcon("go-first"), xi18nc("@action", "Detach"), this);
actionCollection()->addAction("schedule_move_left", actionMoveLeft );
connect( actionMoveLeft, &QAction::triggered, this, &ScheduleEditor::slotMoveLeft );
addAction( name, actionMoveLeft );
// Add the context menu actions for the view options
createOptionActions(ViewBase::OptionExpand | ViewBase::OptionCollapse | ViewBase::OptionViewConfig);
}
void ScheduleEditor::updateReadWrite( bool readwrite )
{
debugPlan<<readwrite;
ViewBase::updateReadWrite( readwrite );
m_view->setReadWrite( readwrite );
slotEnableActions();
}
void ScheduleEditor::slotOptions()
{
debugPlan;
ItemViewSettupDialog *dlg = new ItemViewSettupDialog( this, m_view, true, this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void ScheduleEditor::slotCalculateSchedule()
{
//debugPlan;
ScheduleManager *sm = m_view->selectedManager();
if ( sm == 0 ) {
return;
}
if ( sm->parentManager() ) {
RecalculateDialog dlg;
if ( dlg.exec() == QDialog::Rejected ) {
return;
}
sm->setRecalculate( true );
sm->setRecalculateFrom( DateTime( dlg.dateTime() ) );
}
emit calculateSchedule( m_view->project(), sm );
}
void ScheduleEditor::slotAddSchedule()
{
//debugPlan;
int idx = -1;
ScheduleManager *sm = m_view->selectedManager();
if ( sm ) {
idx = sm->parentManager() ? sm->parentManager()->indexOf( sm ) : m_view->project()->indexOf( sm );
if ( idx >= 0 ) {
++idx;
}
}
if ( sm && sm->parentManager() ) {
sm = sm->parentManager();
ScheduleManager *m = m_view->project()->createScheduleManager( sm->name() + QString(".%1").arg( sm->children().count() + 1 ) );
part()->addCommand( new AddScheduleManagerCmd( sm, m, idx, kundo2_i18n( "Create sub-schedule" ) ) );
QModelIndex idx = model()->index( m );
if ( idx.isValid() ) {
m_view->setFocus();
m_view->scrollTo( idx );
m_view->selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
}
} else {
Project *p = m_view->project();
ScheduleManager *m = p->createScheduleManager();
AddScheduleManagerCmd *cmd = new AddScheduleManagerCmd( *p, m, idx, kundo2_i18n( "Add schedule %1", m->name() ) );
part() ->addCommand( cmd );
QModelIndex idx = model()->index( m );
if ( idx.isValid() ) {
m_view->setFocus();
m_view->scrollTo( idx );
m_view->selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
}
}
}
void ScheduleEditor::slotAddSubSchedule()
{
//debugPlan;
ScheduleManager *sm = m_view->selectedManager();
if ( sm ) {
int row = sm->parentManager() ? sm->parentManager()->indexOf( sm ) : m_view->project()->indexOf( sm );
if ( row >= 0 ) {
++row;
}
ScheduleManager *m = m_view->project()->createScheduleManager( sm->name() + QString(".%1").arg( sm->children().count() + 1 ) );
part()->addCommand( new AddScheduleManagerCmd( sm, m, row, kundo2_i18n( "Create sub-schedule" ) ) );
m_view->expand( model()->index( sm ) );
QModelIndex idx = model()->index( m );
if ( idx.isValid() ) {
m_view->selectionModel()->select( idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate );
}
} else {
slotAddSchedule();
}
}
void ScheduleEditor::slotBaselineSchedule()
{
//debugPlan;
ScheduleManager *sm = m_view->selectedManager();
if ( sm ) {
emit baselineSchedule( m_view->project(), sm );
}
}
void ScheduleEditor::slotDeleteSelection()
{
//debugPlan;
ScheduleManager *sm = m_view->selectedManager();
if ( sm ) {
emit deleteScheduleManager( m_view->project(), sm );
}
}
void ScheduleEditor::slotMoveLeft()
{
ScheduleManager *sm = m_view->selectedManager();
if ( sm ) {
int index = -1;
for ( ScheduleManager *m = sm; m != 0; m = m->parentManager() ) {
if ( m->parentManager() == 0 ) {
index = m->project().indexOf( m ) + 1;
}
}
debugPlan<<sm->name()<<index;
emit moveScheduleManager( sm, 0, index );
}
}
bool ScheduleEditor::loadContext( const KoXmlElement &context )
{
debugPlan;
return m_view->loadContext( model()->columnMap(), context );
}
void ScheduleEditor::saveContext( QDomElement &context ) const
{
m_view->saveContext( model()->columnMap(), context );
}
KoPrintJob *ScheduleEditor::createPrintJob()
{
return m_view->createPrintJob( this );
}
//-----------------------------------------
ScheduleLogTreeView::ScheduleLogTreeView( QWidget *parent )
: QTreeView( parent )
{
header()->setStretchLastSection ( true );
header()->setContextMenuPolicy( Qt::CustomContextMenu );
m_model = new QSortFilterProxyModel( this );
m_model->setFilterRole( Qt::UserRole+1 );
m_model->setFilterKeyColumn ( 2 ); // severity
m_model->setFilterWildcard( "[^0]" ); // Filter out Debug
m_model->setSourceModel( new ScheduleLogItemModel( this ) );
setModel( m_model );
setRootIsDecorated( false );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
setAlternatingRowColors( true );
connect( header(), &QWidget::customContextMenuRequested, this, &ScheduleLogTreeView::headerContextMenuRequested );
actionShowDebug = new KToggleAction( xi18nc( "@action", "Show Debug Information" ), this );
connect( actionShowDebug, &QAction::toggled, this, &ScheduleLogTreeView::slotShowDebug);
}
void ScheduleLogTreeView::setFilterWildcard( const QString &filter )
{
m_model->setFilterWildcard( filter );
}
QRegExp ScheduleLogTreeView::filterRegExp() const
{
return m_model->filterRegExp();
}
void ScheduleLogTreeView::slotShowDebug( bool on )
{
on ? setFilterWildcard( QString() ) : setFilterWildcard("[^0]" );
}
void ScheduleLogTreeView::contextMenuEvent ( QContextMenuEvent *e )
{
debugPlan<<indexAt(e->pos())<<" at"<<e->pos();
emit contextMenuRequested( indexAt( e->pos() ), e->globalPos() );
}
void ScheduleLogTreeView::headerContextMenuRequested( const QPoint &pos )
{
//debugPlan<<header()->logicalIndexAt(pos)<<" at"<<pos;
QMenu *m = new QMenu( this );
m->addAction( actionShowDebug );
m->exec( mapToGlobal( pos ) );
delete m;
}
void ScheduleLogTreeView::selectionChanged( const QItemSelection &sel, const QItemSelection &desel )
{
//debugPlan<<sel.indexes().count();
foreach( const QModelIndex &i, selectionModel()->selectedIndexes() ) {
Q_UNUSED(i);
//debugPlan<<i.row()<<","<<i.column();
}
QTreeView::selectionChanged( sel, desel );
emit selectionChanged( selectionModel()->selectedIndexes() );
}
void ScheduleLogTreeView::currentChanged( const QModelIndex & current, const QModelIndex & previous )
{
//debugPlan<<current.row()<<","<<current.column();
QTreeView::currentChanged( current, previous );
emit currentChanged( current );
// selectionModel()->select( current, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
}
void ScheduleLogTreeView::slotEditCopy()
{
QStringList lst;
// int row = 0;
QString s;
QHeaderView *h = header();
foreach( const QModelIndex &i, selectionModel()->selectedRows() ) {
QString s;
for ( int section = 0; section < h->count(); ++section ) {
QModelIndex idx = model()->index( i.row(), h->logicalIndex( section ) );
if ( ! idx.isValid() || isColumnHidden( idx.column() ) ) {
continue;
}
if ( ! s.isEmpty() ) {
s += ' ';
}
s = QString( "%1%2" ).arg( s ).arg( idx.data().toString(), -10 );
}
if ( ! s.isEmpty() ) {
lst << s;
}
}
if ( ! lst.isEmpty() ) {
QApplication::clipboard()->setText( lst.join( "\n" ) );
}
}
//-----------------------------------
ScheduleLogView::ScheduleLogView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent )
{
setupGui();
slotEnableActions( 0 );
QVBoxLayout * l = new QVBoxLayout( this );
m_view = new ScheduleLogTreeView( this );
l->addWidget( m_view );
// m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
connect( m_view, SIGNAL(currentChanged(QModelIndex)), this, SLOT(slotCurrentChanged(QModelIndex)) );
connect( m_view, SIGNAL(selectionChanged(QModelIndexList)), this, SLOT(slotSelectionChanged(QModelIndexList)) );
connect( baseModel(), &QAbstractItemModel::dataChanged, this, &ScheduleLogView::updateActionsEnabled );
connect( m_view, &ScheduleLogTreeView::contextMenuRequested, this, &ScheduleLogView::slotContextMenuRequested );
}
void ScheduleLogView::setProject( Project *project )
{
m_view->setProject( project );
}
void ScheduleLogView::draw( Project &project )
{
setProject( &project );
}
void ScheduleLogView::setGuiActive( bool activate )
{
//debugPlan<<activate;
ViewBase::setGuiActive( activate );
/* if ( activate && !m_view->currentIndex().isValid() ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}*/
}
void ScheduleLogView::slotEdit()
{
QString id = sender()->property( "p_identity" ).toString();
if ( id.isEmpty() ) {
emit editNode( project() );
return;
}
Node *n = project()->findNode( id );
if ( n ) {
emit editNode( n );
return;
}
Resource *r = project()->findResource( id );
if ( r ) {
emit editResource( r );
return;
}
warnPlan<<"No object";
}
void ScheduleLogView::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
if ( ! isReadWrite() || ! index.isValid() ) {
return;
}
QMenu *m = new QMenu( this );
QString id = index.data( ScheduleLogItemModel::IdentityRole ).toString();
if ( id.isEmpty() ) {
return;
}
QAction *a = new QAction(koIcon("document-edit"), i18n( "Edit..." ), m);
a->setProperty( "p_identity", id );
m->addAction( a );
connect(a, &QAction::triggered, this, &ScheduleLogView::slotEdit);
m->addSeparator();
m->exec( pos );
delete m;
}
void ScheduleLogView::slotScheduleSelectionChanged( ScheduleManager *sm )
{
baseModel()->setManager( sm );
}
void ScheduleLogView::slotCurrentChanged( const QModelIndex & )
{
//debugPlan<<curr.row()<<","<<curr.column();
}
void ScheduleLogView::slotSelectionChanged( const QModelIndexList &list)
{
debugPlan<<list.count();
}
void ScheduleLogView::updateActionsEnabled( const QModelIndex &index )
{
debugPlan<<index;
}
void ScheduleLogView::slotEnableActions( const ScheduleManager * )
{
}
void ScheduleLogView::setupGui()
{
// Add the context menu actions for the view options
createOptionActions(0);
}
void ScheduleLogView::updateReadWrite( bool readwrite )
{
debugPlan<<readwrite;
ViewBase::updateReadWrite( readwrite );
// m_view->setReadWrite( readwrite );
//slotEnableActions( m_view->currentManager() );
}
void ScheduleLogView::slotOptions()
{
debugPlan;
}
void ScheduleLogView::slotEditCopy()
{
m_view->slotEditCopy();
}
bool ScheduleLogView::loadContext( const KoXmlElement &/*context */)
{
debugPlan;
return true;//m_view->loadContext( model()->columnMap(), context );
}
void ScheduleLogView::saveContext( QDomElement &/*context */) const
{
//m_view->saveContext( model()->columnMap(), context );
}
//---------------------------
ScheduleHandlerView::ScheduleHandlerView(KoPart *part, KoDocument *doc, QWidget *parent )
: SplitterView(part, doc, parent)
{
debugPlan<<"---------------- Create ScheduleHandlerView ------------------";
m_scheduleEditor = new ScheduleEditor(part, doc, this );
m_scheduleEditor->setObjectName( "ScheduleEditor" );
addView( m_scheduleEditor );
QTabWidget *tab = addTabWidget();
PertResult *p = new PertResult(part, doc, tab);
p->setObjectName( "PertResult" );
addView( p, tab, i18n( "Result" ) );
connect( m_scheduleEditor, &ScheduleEditor::scheduleSelectionChanged, p, &PertResult::slotScheduleSelectionChanged );
PertCpmView *c = new PertCpmView(part, doc, tab);
c->setObjectName( "PertCpmView" );
addView( c, tab, i18n( "Critical Path" ) );
connect( m_scheduleEditor, &ScheduleEditor::scheduleSelectionChanged, c, &PertCpmView::slotScheduleSelectionChanged );
ScheduleLogView *v = new ScheduleLogView(part, doc, tab);
v->setObjectName( "ScheduleLogView" );
addView( v, tab, i18n( "Scheduling Log" ) );
connect( m_scheduleEditor, SIGNAL(scheduleSelectionChanged(KPlato::ScheduleManager*)), v, SLOT(slotScheduleSelectionChanged(KPlato::ScheduleManager*)) );
connect(v, &ScheduleLogView::editNode, this, &ScheduleHandlerView::editNode);
connect(v, &ScheduleLogView::editResource, this, &ScheduleHandlerView::editResource);
}
void ScheduleHandlerView::currentTabChanged( int )
{
}
ViewBase *ScheduleHandlerView::hitView( const QPoint &/*glpos */)
{
//debugPlan<<this<<glpos<<"->"<<mapFromGlobal( glpos )<<"in"<<frameGeometry();
return this;
}
void ScheduleHandlerView::setGuiActive( bool active ) // virtual slot
{
foreach ( ViewBase *v, findChildren<ViewBase*>() ) {
v->setGuiActive( active );
}
m_activeview = active ? this : 0;
emit guiActivated( this, active );
}
void ScheduleHandlerView::slotGuiActivated( ViewBase *, bool )
{
}
QStringList ScheduleHandlerView::actionListNames() const
{
QStringList lst;
foreach ( ViewBase *v, findChildren<ViewBase*>() ) {
lst += v->actionListNames();
}
return lst;
}
QList<QAction*> ScheduleHandlerView::actionList( const QString &name ) const
{
//debugPlan<<name;
QList<QAction*> lst;
foreach ( ViewBase *v, findChildren<ViewBase*>() ) {
lst += v->actionList( name );
}
return lst;
}
SchedulingRange::SchedulingRange(KoDocument *doc, QWidget *parent)
: QWidget(parent)
, m_doc(doc)
, m_project(0)
{
setupUi(this);
connect(targetStartTime, &QAbstractSpinBox::editingFinished, this, &SchedulingRange::slotStartChanged);
connect(targetEndTime, &QAbstractSpinBox::editingFinished, this, &SchedulingRange::slotEndChanged);
}
void SchedulingRange::setProject(Project *project)
{
if (m_project) {
disconnect(m_project, &Project::nodeChanged, this, &SchedulingRange::slotProjectChanged);
}
m_project = project;
if (m_project) {
connect(m_project, &Project::nodeChanged, this, &SchedulingRange::slotProjectChanged);
slotProjectChanged(m_project);
}
}
void SchedulingRange::slotProjectChanged(Node *node)
{
if (node != m_project) {
return;
}
if (targetStartTime->dateTime() != m_project->constraintStartTime()) {
targetStartTime->setDateTime(m_project->constraintStartTime());
}
if (targetEndTime->dateTime() != m_project->constraintEndTime()) {
targetEndTime->setDateTime(m_project->constraintEndTime());
}
}
void SchedulingRange::slotStartChanged()
{
if (!m_project || !m_doc) {
return;
}
if (targetStartTime->dateTime() == m_project->constraintStartTime()) {
return;
}
ProjectModifyStartTimeCmd *cmd = new ProjectModifyStartTimeCmd(*m_project, targetStartTime->dateTime(), kundo2_i18n("Modify project target start time"));
m_doc->addCommand(cmd);
}
void SchedulingRange::slotEndChanged()
{
if (!m_project || !m_doc) {
return;
}
if (targetEndTime->dateTime() == m_project->constraintEndTime()) {
return;
}
ProjectModifyEndTimeCmd *cmd = new ProjectModifyEndTimeCmd(*m_project, targetEndTime->dateTime(), kundo2_i18n("Modify project target end time"));
m_doc->addCommand(cmd);
}
} // namespace KPlato
diff --git a/src/libs/ui/kptsplitterview.cpp b/src/libs/ui/kptsplitterview.cpp
index 31c18753..a8861f23 100644
--- a/src/libs/ui/kptsplitterview.cpp
+++ b/src/libs/ui/kptsplitterview.cpp
@@ -1,340 +1,341 @@
/* This file is part of the KDE project
Copyright (C) 2007. 2012 Dag Andersen <danders@get2net.dk>
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 "kptsplitterview.h"
#include "KoDocument.h"
#include <KoXmlReader.h>
#include <QVBoxLayout>
#include <QSplitter>
#include <QTabWidget>
#include "kptdebug.h"
namespace KPlato
{
SplitterView::SplitterView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent),
m_activeview( 0 )
{
QVBoxLayout *b = new QVBoxLayout( this );
b->setContentsMargins( 0, 0, 0, 0 );
m_splitter = new QSplitter( this );
m_splitter->setOrientation( Qt::Vertical );
b->addWidget( m_splitter );
}
QTabWidget *SplitterView::addTabWidget( )
{
QTabWidget *w = new QTabWidget( m_splitter );
m_splitter->addWidget( w );
connect( w, &QTabWidget::currentChanged, this, &SplitterView::currentTabChanged );
return w;
}
void SplitterView::currentTabChanged( int )
{
ViewBase *v = qobject_cast<ViewBase*>( qobject_cast<QTabWidget*>( sender() )->currentWidget() );
if ( v && v != m_activeview ) {
if ( m_activeview ) {
m_activeview->setGuiActive( false );
}
v->setGuiActive( true );
}
}
void SplitterView::addView( ViewBase *view )
{
m_splitter->addWidget( view );
connect( view, &ViewBase::guiActivated, this, &SplitterView::slotGuiActivated );
connect( view, &ViewBase::requestPopupMenu, this, &ViewBase::requestPopupMenu );
connect(view, &ViewBase::optionsModified, this, &ViewBase::optionsModified);
}
void SplitterView::addView( ViewBase *view, QTabWidget *tab, const QString &label )
{
tab->addTab( view, label );
connect( view, &ViewBase::guiActivated, this, &SplitterView::slotGuiActivated );
connect( view, &ViewBase::requestPopupMenu, this, &ViewBase::requestPopupMenu );
connect(view, &ViewBase::optionsModified, this, &ViewBase::optionsModified);
}
// reimp
void SplitterView::setGuiActive( bool active ) // virtual slot
{
debugPlan<<active<<m_activeview;
if ( m_activeview ) {
m_activeview->setGuiActive( active );
} else {
emit guiActivated( this, active );
}
}
void SplitterView::slotGuiActivated( ViewBase *v, bool active )
{
debugPlan<<active<<m_activeview<<" -> "<<v;
if ( active ) {
if ( m_activeview ) {
emit guiActivated( m_activeview, false );
}
m_activeview = v;
} else {
m_activeview = 0;
}
emit guiActivated( v, active );
}
ViewBase *SplitterView::findView( const QPoint &pos ) const
{
for ( int i = 0; i < m_splitter->count(); ++i ) {
ViewBase *w = dynamic_cast<ViewBase*>( m_splitter->widget( i ) );
if ( w && w->frameGeometry().contains( pos ) ) {
debugPlan<<pos<<" in "<<w->frameGeometry();
return w;
}
QTabWidget *tw = dynamic_cast<QTabWidget*>( m_splitter->widget( i ) );
if (tw && tw->frameGeometry().contains( pos ) ) {
w = dynamic_cast<ViewBase*>( tw->currentWidget() );
if ( w ) {
debugPlan<<pos<<" in "<<w->frameGeometry();
return w;
}
}
}
return const_cast<SplitterView*>( this );
}
void SplitterView::setProject( Project *project )
{
foreach ( ViewBase *v, findChildren<ViewBase*>() ) {
v->setProject( project );
}
ViewBase::setProject( project );
}
void SplitterView::setScheduleManager( ScheduleManager *sm )
{
foreach ( ViewBase *v, findChildren<ViewBase*>() ) {
v->setScheduleManager( sm );
}
ViewBase::setScheduleManager( sm );
}
void SplitterView::draw()
{
for ( int i = 0; i < m_splitter->count(); ++i ) {
ViewBase *v = dynamic_cast<ViewBase*>( m_splitter->widget( i ) );
if ( v ) {
v->draw();
} else {
QTabWidget *tw = dynamic_cast<QTabWidget*>( m_splitter->widget( i ) );
if (tw ) {
for ( int j = 0; j < tw->count(); ++j ) {
v = dynamic_cast<ViewBase*>( tw->widget( j ) );
if ( v ) {
v->draw();
}
}
}
}
}
}
void SplitterView::draw( Project &project )
{
for ( int i = 0; i < m_splitter->count(); ++i ) {
ViewBase *v = dynamic_cast<ViewBase*>( m_splitter->widget( i ) );
if ( v ) {
v->draw( project );
} else {
QTabWidget *tw = dynamic_cast<QTabWidget*>( m_splitter->widget( i ) );
if (tw ) {
for ( int j = 0; j < tw->count(); ++j ) {
v = dynamic_cast<ViewBase*>( tw->widget( j ) );
if ( v ) {
v->draw( project );
}
}
}
}
}
}
void SplitterView::updateReadWrite( bool mode )
{
for ( int i = 0; i < m_splitter->count(); ++i ) {
ViewBase *v = dynamic_cast<ViewBase*>( m_splitter->widget( i ) );
if ( v ) {
v->updateReadWrite( mode );
} else {
QTabWidget *tw = dynamic_cast<QTabWidget*>( m_splitter->widget( i ) );
if (tw ) {
for ( int j = 0; j < tw->count(); ++j ) {
v = dynamic_cast<ViewBase*>( tw->widget( j ) );
if ( v ) {
v->updateReadWrite( mode );
}
}
}
}
}
}
ViewBase *SplitterView::focusView() const
{
QList<ViewBase*> lst = findChildren<ViewBase*>();
debugPlan<<lst;
foreach ( ViewBase *v, lst ) {
if ( v->isActive() ) {
debugPlan<<v;
return v;
}
}
return 0;
}
QStringList SplitterView::actionListNames() const
{
QStringList lst = ViewActionLists::actionListNames();
ViewBase *view = focusView();
if ( view && view != this ) {
lst << view->actionListNames();
}
return lst;
}
QList<QAction*> SplitterView::actionList( const QString &name ) const
{
QList<QAction*> lst = ViewActionLists::actionList( name );
if ( lst.isEmpty() ) {
ViewBase *view = focusView();
if ( view && view != this ) {
lst = view->actionList( name );
}
}
return lst;
}
QList<QAction*> SplitterView::contextActionList() const
{
ViewBase *view = focusView();
debugPlan<<this<<view;
if ( view ) {
return view->contextActionList();
}
return QList<QAction*>();
}
Node* SplitterView::currentNode() const
{
ViewBase *view = focusView();
if ( view ) {
return view->currentNode();
}
return 0;
}
Resource* SplitterView::currentResource() const
{
ViewBase *view = focusView();
if ( view ) {
return view->currentResource();
}
return 0;
}
ResourceGroup* SplitterView::currentResourceGroup() const
{
ViewBase *view = focusView();
if ( view ) {
return view->currentResourceGroup();
}
return 0;
}
Calendar* SplitterView::currentCalendar() const
{
ViewBase *view = focusView();
if ( view ) {
return view->currentCalendar();
}
return 0;
}
Relation *SplitterView::currentRelation() const
{
ViewBase *view = focusView();
if ( view ) {
return view->currentRelation();
}
return 0;
}
bool SplitterView::loadContext( const KoXmlElement &context )
{
KoXmlElement e = context.namedItem( "views" ).toElement();
if ( e.isNull() ) {
return true;
}
#ifndef KOXML_USE_QDOM
foreach ( const QString &s, e.attributeNames() ) {
ViewBase *v = findChildren<ViewBase*>( s ).value( 0 );
if ( v == 0 ) {
continue;
}
KoXmlElement e1 = e.namedItem( s ).toElement();
if ( e1.isNull() ) {
continue;
}
v->loadContext( e1 );
}
#endif
return true;
}
void SplitterView::saveContext( QDomElement &context ) const
{
QList<ViewBase*> lst = findChildren<ViewBase*>();
if ( lst.isEmpty() ) {
return;
}
QDomElement e = context.ownerDocument().createElement( "views" );
context.appendChild( e );
foreach ( ViewBase *v, lst ) {
e.setAttribute( v->objectName(), "" );
}
foreach ( ViewBase *v, lst ) {
QDomElement e1 = e.ownerDocument().createElement( v->objectName() );
e.appendChild( e1 );
v->saveContext( e1 );
}
}
void SplitterView::slotEditCopy()
{
ViewBase *v = focusView();
if ( v ) {
v->slotEditCopy();
}
}
} // namespace KPlato
diff --git a/src/libs/ui/kptstandardworktimedialog.cpp b/src/libs/ui/kptstandardworktimedialog.cpp
index ff8c0033..e0224e28 100644
--- a/src/libs/ui/kptstandardworktimedialog.cpp
+++ b/src/libs/ui/kptstandardworktimedialog.cpp
@@ -1,199 +1,200 @@
/* This file is part of the KDE project
Copyright (C) 2004 - 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptstandardworktimedialog.h"
#include "kptduration.h"
#include "kptproject.h"
#include "kptcalendar.h"
#include "kptcommand.h"
#include "kptdebug.h"
#include <QTreeWidgetItem>
#include <QLocale>
namespace KPlato
{
class WeekdayListItem : public QTreeWidgetItem
{
public:
WeekdayListItem(Calendar *cal, int wd, QTreeWidget *parent, const QString& name, QTreeWidgetItem *after)
: QTreeWidgetItem(parent, after),
original(cal->weekday(wd)),
calendar(cal),
weekday(wd)
{
setText(0, name);
day = new CalendarDay(original);
if (day->state() == CalendarDay::NonWorking) {
setHours();
} else {
setText(1, QLocale().toString(day->duration().toDouble(Duration::Unit_h), 'f', 2));
}
}
~WeekdayListItem() {
delete day;
}
void setHours() {
setText(1, "-");
day->clearIntervals();
}
void setIntervals(QList<TimeInterval*> intervals) {
day->setIntervals(intervals);
setText(1, QLocale().toString(day->duration().toDouble(Duration::Unit_h), 'f', 2));
}
void setState(int st) {
day->setState(st+1);
}
MacroCommand *save() {
MacroCommand *cmd=0;
if (*original != *day) {
cmd = new MacroCommand();
cmd->addCommand( new CalendarModifyWeekdayCmd(calendar, weekday, day) );
day = 0;
}
return cmd;
}
CalendarDay *day;
CalendarDay *original;
Calendar *calendar;
int weekday;
};
StandardWorktimeDialog::StandardWorktimeDialog(Project &p, QWidget *parent)
: KoDialog(parent),
project(p)
{
setCaption( i18n("Estimate Conversions") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
//debugPlan<<&p;
m_original = p.standardWorktime();
dia = new StandardWorktimeDialogImpl(m_original, this);
setMainWidget(dia);
enableButtonOk(false);
connect(dia, &StandardWorktimeDialogImpl::obligatedFieldsFilled, this, &KoDialog::enableButtonOk);
connect(dia, &StandardWorktimeDialogImpl::enableButtonOk, this, &KoDialog::enableButtonOk);
connect(this,&KoDialog::okClicked,this,&StandardWorktimeDialog::slotOk);
}
MacroCommand *StandardWorktimeDialog::buildCommand() {
//debugPlan;
KUndo2MagicString n = kundo2_i18n("Modify Estimate Conversions");
MacroCommand *cmd = 0;
if (m_original->year() != dia->inYear()) {
if (cmd == 0) cmd = new MacroCommand(n);
cmd->addCommand(new ModifyStandardWorktimeYearCmd(m_original, m_original->year(), dia->inYear()));
}
if (m_original->month() != dia->inMonth()) {
if (cmd == 0) cmd = new MacroCommand(n);
cmd->addCommand(new ModifyStandardWorktimeMonthCmd(m_original, m_original->month(), dia->inMonth()));
}
if (m_original->week() != dia->inWeek()) {
if (cmd == 0) cmd = new MacroCommand(n);
cmd->addCommand(new ModifyStandardWorktimeWeekCmd(m_original, m_original->week(), dia->inWeek()));
}
if (m_original->day() != dia->inDay()) {
if (cmd == 0) cmd = new MacroCommand(n);
cmd->addCommand(new ModifyStandardWorktimeDayCmd(m_original, m_original->day(), dia->inDay()));
}
return cmd;
}
void StandardWorktimeDialog::slotOk() {
accept();
}
StandardWorktimeDialogImpl::StandardWorktimeDialogImpl(StandardWorktime *std, QWidget *parent)
: QWidget(parent),
m_std(std) {
setupUi(this);
if (!std) {
m_std = new StandardWorktime();
}
m_year = m_std->year();
m_month = m_std->month();
m_week = m_std->week();
m_day = m_std->day();
debugPlan<<"y="<<m_year<<" m="<<m_month<<" w="<<m_week<<" d="<<m_day;
year->setValue(m_year);
month->setValue(m_month);
week->setValue(m_week);
day->setValue(m_day);
connect(year, SIGNAL(valueChanged(double)), SLOT(slotYearChanged(double)));
connect(month, SIGNAL(valueChanged(double)), SLOT(slotMonthChanged(double)));
connect(week, SIGNAL(valueChanged(double)), SLOT(slotWeekChanged(double)));
connect(day, SIGNAL(valueChanged(double)), SLOT(slotDayChanged(double)));
}
void StandardWorktimeDialogImpl::slotEnableButtonOk(bool on) {
emit enableButtonOk(on);
}
void StandardWorktimeDialogImpl::slotCheckAllFieldsFilled() {
emit obligatedFieldsFilled(true);
}
void StandardWorktimeDialogImpl::slotYearChanged(double value) {
//debugPlan<<value;
m_year = value;
if (month->value() > value)
month->setValue(value);
slotEnableButtonOk(true);
}
void StandardWorktimeDialogImpl::slotMonthChanged(double value) {
m_month = value;
if (year->value() < value)
year->setValue(value);
if (week->value() > value)
week->setValue(value);
slotEnableButtonOk(true);
}
void StandardWorktimeDialogImpl::slotWeekChanged(double value) {
m_week = value;
if (month->value() < value)
month->setValue(value);
if (day->value() > value)
day->setValue(value);
slotEnableButtonOk(true);
}
void StandardWorktimeDialogImpl::slotDayChanged(double value) {
m_day = value;
if (week->value() < value)
week->setValue(value);
slotEnableButtonOk(true);
}
} //KPlato namespace
diff --git a/src/libs/ui/kptsummarytaskdialog.cpp b/src/libs/ui/kptsummarytaskdialog.cpp
index 213079bc..75baa8e1 100644
--- a/src/libs/ui/kptsummarytaskdialog.cpp
+++ b/src/libs/ui/kptsummarytaskdialog.cpp
@@ -1,88 +1,89 @@
/* This file is part of the KDE project
Copyright (C) 2002 Bo Thorsen bo@sonofthor.dk
Copyright (C) 2004 - 2010 Dag Andersen <danders@get2net.dk>
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 "kptsummarytaskdialog.h"
#include "kptsummarytaskgeneralpanel.h"
#include "kptcommand.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptproject.h"
#include <KLocalizedString>
namespace KPlato
{
SummaryTaskDialog::SummaryTaskDialog(Task &task, QWidget *p)
: KoDialog(p),
m_node( &task )
{
setCaption( i18n("Summary Task Settings") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
m_generalTab = new SummaryTaskGeneralPanel(task, this);
setMainWidget(m_generalTab);
enableButtonOk(false);
connect(m_generalTab, &SummaryTaskGeneralPanel::obligatedFieldsFilled, this, &KoDialog::enableButtonOk);
Project *proj = static_cast<Project*>( task.projectNode() );
if ( proj ) {
connect(proj, &Project::nodeRemoved, this, &SummaryTaskDialog::slotTaskRemoved);
}
}
void SummaryTaskDialog::slotTaskRemoved( Node *node )
{
if ( node == m_node ) {
reject();
}
}
MacroCommand *SummaryTaskDialog::buildCommand() {
MacroCommand *m = new MacroCommand(kundo2_i18n("Modify Summary Task"));
bool modified = false;
MacroCommand *cmd = m_generalTab->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
if (!modified) {
delete m;
return 0;
}
return m;
}
void SummaryTaskDialog::slotButtonClicked(int button) {
if (button == KoDialog::Ok) {
if (!m_generalTab->ok())
return;
accept();
} else {
KoDialog::slotButtonClicked(button);
}
}
} //KPlato namespace
diff --git a/src/libs/ui/kptsummarytaskgeneralpanel.cpp b/src/libs/ui/kptsummarytaskgeneralpanel.cpp
index c75320cc..090e7ae1 100644
--- a/src/libs/ui/kptsummarytaskgeneralpanel.cpp
+++ b/src/libs/ui/kptsummarytaskgeneralpanel.cpp
@@ -1,149 +1,150 @@
/* This file is part of the KDE project
Copyright (C) 2004 - 2007, 2011 Dag Andersen <danders@get2net.dk>
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 "kptsummarytaskgeneralpanel.h"
#include "kptsummarytaskdialog.h"
#include "kpttask.h"
#include "kptcommand.h"
#include "kpttaskdescriptiondialog.h"
#include <KLocalizedString>
#ifdef PLAN_KDEPIMLIBS_FOUND
#include <akonadi/contact/emailaddressselectiondialog.h>
#include <akonadi/contact/emailaddressselectionwidget.h>
#include <akonadi/contact/emailaddressselection.h>
#endif
namespace KPlato
{
SummaryTaskGeneralPanel::SummaryTaskGeneralPanel(Task &task, QWidget *p, const char *n)
: QWidget(p),
m_task(task)
{
setObjectName(n);
setupUi(this);
#ifndef PLAN_KDEPIMLIBS_FOUND
chooseLeader->hide();
#endif
// FIXME
// [Bug 311940] New: Plan crashes when typing a text in the filter textbox before the textbook is fully loaded when selecting a contact from the adressbook
chooseLeader->hide();
m_description = new TaskDescriptionPanel( task, this );
m_description->namefield->hide();
m_description->namelabel->hide();
layout()->addWidget( m_description );
QString s = i18n( "The Work Breakdown Structure introduces numbering for all tasks in the project, according to the task structure.\nThe WBS code is auto-generated.\nYou can define the WBS code pattern using the Define WBS Pattern command in the Tools menu." );
wbslabel->setWhatsThis( s );
wbsfield->setWhatsThis( s );
setStartValues(task);
connect(namefield, &QLineEdit::textChanged, this, &SummaryTaskGeneralPanel::slotObligatedFieldsFilled);
connect(leaderfield, &QLineEdit::textChanged, this, &SummaryTaskGeneralPanel::slotObligatedFieldsFilled);
connect(m_description, &TaskDescriptionPanelImpl::textChanged, this, &SummaryTaskGeneralPanel::slotObligatedFieldsFilled);
connect(chooseLeader, &QAbstractButton::clicked, this, &SummaryTaskGeneralPanel::slotChooseResponsible);
}
void SummaryTaskGeneralPanel::setStartValues(Task &task) {
namefield->setText(task.name());
leaderfield->setText(task.leader());
m_description->descriptionfield->setTextOrHtml(task.description());
wbsfield->setText(task.wbsCode());
namefield->setFocus();
}
void SummaryTaskGeneralPanel::slotObligatedFieldsFilled() {
emit obligatedFieldsFilled(true); // never block save
}
MacroCommand *SummaryTaskGeneralPanel::buildCommand() {
MacroCommand *cmd = new MacroCommand(kundo2_i18n("Modify task"));
bool modified = false;
if (!namefield->isHidden() && m_task.name() != namefield->text()) {
cmd->addCommand(new NodeModifyNameCmd(m_task, namefield->text()));
modified = true;
}
if (!leaderfield->isHidden() && m_task.leader() != leaderfield->text()) {
cmd->addCommand(new NodeModifyLeaderCmd(m_task, leaderfield->text()));
modified = true;
}
/* if (!descriptionfield->isHidden() &&
m_task.description() != descriptionfield->text()) {
cmd->addCommand(new NodeModifyDescriptionCmd(m_task, descriptionfield->text()));
modified = true;
}*/
MacroCommand *m = m_description->buildCommand();
if ( m ) {
cmd->addCommand( m );
modified = true;
}
if (!modified) {
delete cmd;
return 0;
}
return cmd;
}
bool SummaryTaskGeneralPanel::ok() {
return true;
}
void SummaryTaskGeneralPanel::slotChooseResponsible()
{
#ifdef PLAN_KDEPIMLIBS_FOUND
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog( this );
if ( dlg->exec() && dlg ) {
QStringList names;
const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
foreach ( const Akonadi::EmailAddressSelection &selection, selections ) {
QString s = selection.name();
if ( ! selection.email().isEmpty() ) {
if ( ! selection.name().isEmpty() ) {
s += " <";
}
s += selection.email();
if ( ! selection.name().isEmpty() ) {
s += '>';
}
if ( ! s.isEmpty() ) {
names << s;
}
}
}
if ( ! names.isEmpty() ) {
leaderfield->setText( names.join( ", " ) );
}
}
#endif
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskcostpanel.cpp b/src/libs/ui/kpttaskcostpanel.cpp
index 48c60cb0..cabe6538 100644
--- a/src/libs/ui/kpttaskcostpanel.cpp
+++ b/src/libs/ui/kpttaskcostpanel.cpp
@@ -1,172 +1,173 @@
/* This file is part of the KDE project
Copyright (C) 2005-2007 Dag Andersen <danders@get2net.dk>
Copyright (C) 20011 Dag Andersen <danders@get2net.dk>
Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
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 Cost Public License for more details.
You should have received a copy of the GNU Library Cost 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 "kpttaskcostpanel.h"
#include "kptaccount.h"
#include "kpttask.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptlocale.h"
namespace KPlato
{
TaskCostPanel::TaskCostPanel(Task &task, Accounts &accounts, QWidget *p, const char *n)
: TaskCostPanelImpl(p, n),
m_task(task),
m_accounts(accounts)
{
const Project *project = qobject_cast<const Project*>( task.projectNode() );
if ( project ) {
m_locale = project->locale();
m_localeIsOwn = false;
} else {
m_locale = new Locale();
m_localeIsOwn = true;
}
m_accountList << i18n("None");
m_accountList += accounts.costElements();
if ( task.isBaselined( BASELINESCHEDULE ) ) {
runningGroup->setEnabled( false );
startupGroup->setEnabled( false );
shutdownGroup->setEnabled( false );
}
setStartValues(task);
}
TaskCostPanel::~TaskCostPanel()
{
if (m_localeIsOwn) {
delete m_locale;
}
}
void TaskCostPanel::setStartValues(Task &task) {
runningAccount->addItems(m_accountList);
m_oldrunning = m_accounts.findRunningAccount(task);
if (m_oldrunning) {
setCurrentItem(runningAccount, m_oldrunning->name());
}
startupCost->setText(m_locale->formatMoney(task.startupCost()));
startupAccount->addItems(m_accountList);
m_oldstartup = m_accounts.findStartupAccount(task);
if (m_oldstartup) {
setCurrentItem(startupAccount, m_oldstartup->name());
}
shutdownCost->setText(m_locale->formatMoney(task.shutdownCost()));
shutdownAccount->addItems(m_accountList);
m_oldshutdown = m_accounts.findShutdownAccount(task);
if (m_oldshutdown) {
setCurrentItem(shutdownAccount, m_oldshutdown->name());
}
}
void TaskCostPanel::setCurrentItem(QComboBox *box, const QString& name) {
box->setCurrentIndex(0);
for (int i = 0; i < box->count(); ++i) {
if (name == box->itemText(i)) {
box->setCurrentIndex(i);
break;
}
}
}
MacroCommand *TaskCostPanel::buildCommand() {
MacroCommand *cmd = new MacroCommand(kundo2_i18n("Modify Task Cost"));
bool modified = false;
if ((m_oldrunning == 0 && runningAccount->currentIndex() != 0) ||
(m_oldrunning && m_oldrunning->name() != runningAccount->currentText())) {
cmd->addCommand(new NodeModifyRunningAccountCmd(m_task, m_oldrunning, m_accounts.findAccount(runningAccount->currentText())));
modified = true;
}
if ((m_oldstartup == 0 && startupAccount->currentIndex() != 0) ||
(m_oldstartup && m_oldstartup->name() != startupAccount->currentText())) {
cmd->addCommand(new NodeModifyStartupAccountCmd(m_task, m_oldstartup, m_accounts.findAccount(startupAccount->currentText())));
modified = true;
}
if ((m_oldshutdown == 0 && shutdownAccount->currentIndex() != 0) ||
(m_oldshutdown && m_oldshutdown->name() != shutdownAccount->currentText())) {
cmd->addCommand(new NodeModifyShutdownAccountCmd(m_task, m_oldshutdown, m_accounts.findAccount(shutdownAccount->currentText())));
modified = true;
}
double money = m_locale->readMoney(startupCost->text());
if (money != m_task.startupCost()) {
cmd->addCommand(new NodeModifyStartupCostCmd(m_task, money));
modified = true;
}
money = m_locale->readMoney(shutdownCost->text());
if (money != m_task.shutdownCost()) {
cmd->addCommand(new NodeModifyShutdownCostCmd(m_task, money));
modified = true;
}
if (!modified) {
delete cmd;
return 0;
}
return cmd;
}
bool TaskCostPanel::ok() {
if (runningAccount->currentIndex() == 0 ||
m_accounts.findAccount(runningAccount->currentText()) == 0) {
//message
return false;
}
if (startupAccount->currentIndex() == 0 ||
m_accounts.findAccount(startupAccount->currentText()) == 0) {
//message
return false;
}
if (shutdownAccount->currentIndex() == 0 ||
m_accounts.findAccount(shutdownAccount->currentText()) == 0) {
//message
return false;
}
return true;
}
TaskCostPanelImpl::TaskCostPanelImpl(QWidget *p, const char *n)
: QWidget(p)
{
setObjectName(n);
setupUi(this);
connect(runningAccount, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(startupAccount, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(shutdownAccount, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(startupCost, &QLineEdit::textChanged, this, &TaskCostPanelImpl::slotChanged);
connect(shutdownCost, &QLineEdit::textChanged, this, &TaskCostPanelImpl::slotChanged);
}
void TaskCostPanelImpl::slotChanged() {
emit changed();
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskdescriptiondialog.cpp b/src/libs/ui/kpttaskdescriptiondialog.cpp
index 99ef5831..6fe9f376 100644
--- a/src/libs/ui/kpttaskdescriptiondialog.cpp
+++ b/src/libs/ui/kpttaskdescriptiondialog.cpp
@@ -1,171 +1,172 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <calligra-devel@kde.org
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 "kpttaskdescriptiondialog.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptcommand.h"
#include <KLocalizedString>
#include <ktextedit.h>
#include <kactioncollection.h>
namespace KPlato
{
TaskDescriptionPanel::TaskDescriptionPanel(Node &node, QWidget *p, bool readOnly )
: TaskDescriptionPanelImpl( node, p )
{
initDescription( readOnly );
setStartValues( node );
descriptionfield->setFocus();
}
void TaskDescriptionPanel::setStartValues( Node &node )
{
namefield->setText(node.name());
descriptionfield->setTextOrHtml( node.description() );
}
MacroCommand *TaskDescriptionPanel::buildCommand()
{
KUndo2MagicString s = kundo2_i18n("Modify task description");
if ( m_node.type() == Node::Type_Milestone ) {
s = kundo2_i18n("Modify milestone description");
} else if ( m_node.type() == Node::Type_Summarytask ) {
s = kundo2_i18n("Modify summary task description");
} else if ( m_node.type() == Node::Type_Project ) {
s = kundo2_i18n("Modify project description");
}
MacroCommand *cmd = new MacroCommand(s);
bool modified = false;
if ( m_node.description() != descriptionfield->textOrHtml() ) {
cmd->addCommand(new NodeModifyDescriptionCmd(m_node, descriptionfield->textOrHtml()));
modified = true;
}
if (!modified) {
delete cmd;
return 0;
}
return cmd;
}
bool TaskDescriptionPanel::ok() {
return true;
}
void TaskDescriptionPanel::initDescription( bool readOnly )
{
toolbar->setVisible( ! readOnly );
toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
KActionCollection *collection = new KActionCollection( this ); //krazy:exclude=tipsandthis
descriptionfield->setRichTextSupport( KRichTextWidget::SupportBold |
KRichTextWidget::SupportItalic |
KRichTextWidget::SupportUnderline |
KRichTextWidget::SupportStrikeOut |
KRichTextWidget::SupportChangeListStyle |
KRichTextWidget::SupportAlignment |
KRichTextWidget::SupportFormatPainting );
collection->addActions(descriptionfield->createActions());
toolbar->addAction( collection->action( "format_text_bold" ) );
toolbar->addAction( collection->action( "format_text_italic" ) );
toolbar->addAction( collection->action( "format_text_underline" ) );
toolbar->addAction( collection->action( "format_text_strikeout" ) );
toolbar->addSeparator();
toolbar->addAction( collection->action( "format_list_style" ) );
toolbar->addSeparator();
toolbar->addAction( collection->action( "format_align_left" ) );
toolbar->addAction( collection->action( "format_align_center" ) );
toolbar->addAction( collection->action( "format_align_right" ) );
toolbar->addAction( collection->action( "format_align_justify" ) );
toolbar->addSeparator();
// toolbar->addAction( collection->action( "format_painter" ) );
descriptionfield->append( "" );
descriptionfield->setReadOnly( readOnly );
descriptionfield->setOverwriteMode( false );
descriptionfield->setLineWrapMode( KTextEdit::WidgetWidth );
descriptionfield->setTabChangesFocus( true );
}
//-----------------------------
TaskDescriptionPanelImpl::TaskDescriptionPanelImpl( Node &node, QWidget *p )
: QWidget(p),
m_node(node)
{
setupUi(this);
connect( descriptionfield, &QTextEdit::textChanged, this, &TaskDescriptionPanelImpl::slotChanged );
}
void TaskDescriptionPanelImpl::slotChanged()
{
emit textChanged( descriptionfield->textOrHtml() != m_node.description() );
}
//-----------------------------
TaskDescriptionDialog::TaskDescriptionDialog( Task &task, QWidget *p, bool readOnly )
: KoDialog(p)
{
setCaption( i18n( "Task Description" ) );
if ( readOnly ) {
setButtons( Close );
} else {
setButtons( Ok|Cancel );
setDefaultButton( Ok );
}
showButtonSeparator( true );
m_descriptionTab = new TaskDescriptionPanel( task, this, readOnly );
setMainWidget(m_descriptionTab);
enableButtonOk(false);
connect( m_descriptionTab, &TaskDescriptionPanelImpl::textChanged, this, &KoDialog::enableButtonOk );
}
MacroCommand *TaskDescriptionDialog::buildCommand()
{
return m_descriptionTab->buildCommand();
}
void TaskDescriptionDialog::slotButtonClicked( int button )
{
if (button == KoDialog::Ok) {
if ( ! m_descriptionTab->ok() ) {
return;
}
accept();
} else {
KoDialog::slotButtonClicked( button );
}
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskdialog.cpp b/src/libs/ui/kpttaskdialog.cpp
index 7f043d58..e8cdad04 100644
--- a/src/libs/ui/kpttaskdialog.cpp
+++ b/src/libs/ui/kpttaskdialog.cpp
@@ -1,226 +1,227 @@
/* This file is part of the KDE project
Copyright (C) 2002 Bo Thorsen bo@sonofthor.dk
Copyright (C) 2004 -2010 Dag Andersen <danders@get2net.dk>
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 "kpttaskdialog.h"
#include "kpttaskcostpanel.h"
#include "kpttaskgeneralpanel.h"
#include "kptrequestresourcespanel.h"
#include "kptdocumentspanel.h"
#include "kpttaskdescriptiondialog.h"
#include "kptcommand.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptproject.h"
#include <KoVBox.h>
#include <KLocalizedString>
namespace KPlato
{
TaskDialog::TaskDialog( Project &project, Task &task, Accounts &accounts, QWidget *p)
: KPageDialog(p),
m_project( project ),
m_node( &task )
{
setWindowTitle( i18n("Task Settings") );
setFaceType( KPageDialog::Tabbed );
KoVBox *page;
// Create all the tabs.
page = new KoVBox();
addPage(page, i18n("&General"));
m_generalTab = new TaskGeneralPanel(project, task, page);
page = new KoVBox();
addPage(page, i18n("&Resources"));
m_resourcesTab = new RequestResourcesPanel(page, project, task);
page = new KoVBox();
addPage(page, i18n("&Documents"));
m_documentsTab = new DocumentsPanel( task, page );
page = new KoVBox();
addPage(page, i18n("&Cost"));
m_costTab = new TaskCostPanel(task, accounts, page);
page = new KoVBox();
addPage(page, i18n("D&escription"));
m_descriptionTab = new TaskDescriptionPanel(task, page);
m_descriptionTab->namefield->hide();
m_descriptionTab->namelabel->hide();
setButtonOkEnabled(false);
connect(this, &KPageDialog::currentPageChanged, this, &TaskDialog::slotCurrentChanged);
connect(m_generalTab, &TaskGeneralPanelImpl::obligatedFieldsFilled, this, &TaskDialog::setButtonOkEnabled);
connect(m_resourcesTab, &RequestResourcesPanel::changed, m_generalTab, &TaskGeneralPanelImpl::checkAllFieldsFilled);
connect(m_documentsTab, &DocumentsPanel::changed, m_generalTab, &TaskGeneralPanelImpl::checkAllFieldsFilled);
connect(m_costTab, &TaskCostPanelImpl::changed, m_generalTab, &TaskGeneralPanelImpl::checkAllFieldsFilled);
connect(m_descriptionTab, &TaskDescriptionPanelImpl::textChanged, m_generalTab, &TaskGeneralPanelImpl::checkAllFieldsFilled);
connect(&project, &Project::nodeRemoved, this, &TaskDialog::slotTaskRemoved);
}
void TaskDialog::setButtonOkEnabled(bool enabled) {
buttonBox()->button(QDialogButtonBox::Ok)->setEnabled(enabled);
}
void TaskDialog::slotCurrentChanged( KPageWidgetItem *current, KPageWidgetItem */*prev*/ )
{
//debugPlan<<current->widget()<<m_descriptionTab->parent();
// HACK: KPageDialog grabs focus when a tab is clicked.
// KRichTextWidget still flashes the caret so the user thinks it has the focus.
// For now, just give the KRichTextWidget focus.
if ( current->widget() == m_descriptionTab->parent() ) {
m_descriptionTab->descriptionfield->setFocus();
}
}
void TaskDialog::slotTaskRemoved( Node *node )
{
if ( node == m_node ) {
reject();
}
}
MacroCommand *TaskDialog::buildCommand() {
MacroCommand *m = new MacroCommand(kundo2_i18n("Modify task"));
bool modified = false;
MacroCommand *cmd = m_generalTab->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
cmd = m_resourcesTab->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
cmd = m_documentsTab->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
cmd = m_costTab->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
cmd = m_descriptionTab->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
if (!modified) {
delete m;
return 0;
}
return m;
}
void TaskDialog::accept() {
if (!m_generalTab->ok())
return;
if (!m_resourcesTab->ok())
return;
if (!m_descriptionTab->ok())
return;
KPageDialog::accept();
}
//---------------------------
TaskAddDialog::TaskAddDialog(Project &project, Task &task, Node *currentNode, Accounts &accounts, QWidget *p)
: TaskDialog(project, task, accounts, p)
{
m_currentnode = currentNode;
// do not know wbs code yet
m_generalTab->hideWbs();
connect(&project, &Project::nodeRemoved, this, &TaskAddDialog::slotNodeRemoved);
}
TaskAddDialog::~TaskAddDialog()
{
delete m_node; // in case of cancel
}
void TaskAddDialog::slotNodeRemoved( Node *node )
{
if ( m_currentnode == node ) {
reject();
}
}
MacroCommand *TaskAddDialog::buildCommand()
{
MacroCommand *c = new MacroCommand( kundo2_i18n( "Add task" ) );
c->addCommand( new TaskAddCmd( &m_project, m_node, m_currentnode ) );
MacroCommand *m = TaskDialog::buildCommand();
if ( m ) {
c->addCommand( m );
}
m_node = 0; // don't delete task
return c;
}
//---------------------------
SubTaskAddDialog::SubTaskAddDialog(Project &project, Task &task, Node *currentNode, Accounts &accounts, QWidget *p)
: TaskDialog(project, task, accounts, p)
{
m_currentnode = currentNode;
// do not know wbs code yet
m_generalTab->hideWbs();
connect(&project, &Project::nodeRemoved, this, &SubTaskAddDialog::slotNodeRemoved);
}
SubTaskAddDialog::~SubTaskAddDialog()
{
delete m_node; // in case of cancel
}
void SubTaskAddDialog::slotNodeRemoved( Node *node )
{
if ( m_currentnode == node ) {
reject();
}
}
MacroCommand *SubTaskAddDialog::buildCommand()
{
KUndo2MagicString s = kundo2_i18n( "Add sub-task" );
if ( m_currentnode == 0 ) {
s = kundo2_i18n( "Add task" ); // it will be added to project
}
MacroCommand *c = new MacroCommand( s );
c->addCommand( new SubtaskAddCmd( &m_project, m_node, m_currentnode ) );
MacroCommand *m = TaskDialog::buildCommand();
if ( m ) {
c->addCommand( m );
}
m_node = 0; // don't delete task
return c;
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskeditor.cpp b/src/libs/ui/kpttaskeditor.cpp
index 6d43a3d8..bc5afc7e 100644
--- a/src/libs/ui/kpttaskeditor.cpp
+++ b/src/libs/ui/kpttaskeditor.cpp
@@ -1,1711 +1,1712 @@
/* This file is part of the KDE project
Copyright (C) 2006 - 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kpttaskeditor.h"
#include "kptglobal.h"
#include "kptcommonstrings.h"
#include "kptnodeitemmodel.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptitemviewsettup.h"
#include "kptworkpackagesenddialog.h"
#include "kptworkpackagesendpanel.h"
#include "kptdatetime.h"
#include "kptdebug.h"
#include "kptresourcemodel.h"
#include "kptresourceallocationmodel.h"
#include "ResourceAllocationView.h"
#include "kpttaskdialog.h"
#include "TasksEditController.h"
#include "Help.h"
#include <KoXmlReader.h>
#include <KoDocument.h>
#include <KoIcon.h>
#include <QItemSelectionModel>
#include <QModelIndex>
#include <QVBoxLayout>
#include <QDragMoveEvent>
#include <QAction>
#include <QHeaderView>
#include <QMenu>
#include <kactionmenu.h>
#include <KLocalizedString>
#include <ktoggleaction.h>
#include <kactioncollection.h>
namespace KPlato
{
//--------------------
TaskEditorItemModel::TaskEditorItemModel( QObject *parent )
: NodeItemModel( parent )
{
}
Qt::ItemFlags TaskEditorItemModel::flags( const QModelIndex &index ) const
{
if ( index.column() == NodeModel::NodeType ) {
if ( ! m_readWrite || isColumnReadOnly( index.column() ) ) {
return QAbstractItemModel::flags( index );
}
Node *n = node( index );
bool baselined = n ? n->isBaselined() : false;
if ( n && ! baselined && ( n->type() == Node::Type_Task || n->type() == Node::Type_Milestone ) ) {
return QAbstractItemModel::flags( index ) | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
}
return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
}
return NodeItemModel::flags( index );
}
QVariant TaskEditorItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal && section == NodeModel::NodeType ) {
if ( role == Qt::ToolTipRole ) {
return xi18nc( "@info:tooltip", "The type of task or the estimate type of the task" );
} else if ( role == Qt::WhatsThisRole ) {
return xi18nc( "@info:whatsthis",
"<p>Indicates the type of task or the estimate type of the task.</p>"
"The type can be set to <emphasis>Milestone</emphasis>, <emphasis>Effort</emphasis> or <emphasis>Duration</emphasis>.<nl/>"
"<note>If the type is <emphasis>Summary</emphasis> or <emphasis>Project</emphasis> the type is not editable.</note>");
}
}
return NodeItemModel::headerData(section, orientation, role);
}
QVariant TaskEditorItemModel::data( const QModelIndex &index, int role ) const
{
if ( role == Qt::TextAlignmentRole ) {
return NodeItemModel::data( index, role );
}
Node *n = node( index );
if ( n != 0 && index.column() == NodeModel::NodeType ) {
return type( n, role );
}
return NodeItemModel::data( index, role );
}
bool TaskEditorItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
Node *n = node( index );
if ( n != 0 && role == Qt::EditRole && index.column() == NodeModel::NodeType ) {
return setType( n, value, role );
}
return NodeItemModel::setData( index, value, role );
}
QVariant TaskEditorItemModel::type( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole: {
if ( node->type() == Node::Type_Task ) {
return node->estimate()->typeToString( true );
}
return node->typeToString( true );
}
case Qt::EditRole:
return node->type();
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
case Qt::ToolTipRole: {
if ( node->type() == Node::Type_Task ) {
return xi18nc( "@info:tooltip", "Task with estimate type: %1", node->estimate()->typeToString( true ) );
}
return xi18nc( "@info:tooltip", "Task type: %1", node->typeToString( true ) );
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
case Role::EnumListValue: {
if ( node->type() == Node::Type_Milestone ) {
return 0;
}
if ( node->type() == Node::Type_Task ) {
return node->estimate()->type() + 1;
}
return -1;
}
case Role::EnumList: {
QStringList lst;
lst << Node::typeToString( Node::Type_Milestone, true );
lst += Estimate::typeToStringList( true );
return lst;
}
}
return QVariant();
}
bool TaskEditorItemModel::setType( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
if ( node->type() == Node::Type_Summarytask ) {
return false;
}
int v = value.toInt();
switch ( v ) {
case 0: { // Milestone
NamedCommand *cmd = 0;
if ( node->constraint() == Node::FixedInterval ) {
cmd = new NodeModifyConstraintEndTimeCmd( *node, node->constraintStartTime(), kundo2_i18n( "Set type to Milestone" ) );
} else {
cmd = new ModifyEstimateCmd( *node, node->estimate()->expectedEstimate(), 0.0, kundo2_i18n( "Set type to Milestone" ) );
}
emit executeCommand( cmd );
return true;
}
default: { // Estimate
--v;
MacroCommand *m = new MacroCommand( kundo2_i18n( "Set type to %1", Estimate::typeToString( (Estimate::Type)v, true ) ) );
m->addCommand( new ModifyEstimateTypeCmd( *node, node->estimate()->type(), v ) );
if ( node->type() == Node::Type_Milestone ) {
if ( node->constraint() == Node::FixedInterval ) {
m->addCommand( new NodeModifyConstraintEndTimeCmd( *node, node->constraintStartTime().addDays( 1 ) ) );
} else {
m->addCommand( new ModifyEstimateUnitCmd( *node, node->estimate()->unit(), Duration::Unit_d ) );
m->addCommand( new ModifyEstimateCmd( *node, node->estimate()->expectedEstimate(), 1.0 ) );
}
}
emit executeCommand( m );
return true;
}
}
break;
}
default: break;
}
return false;
}
//--------------------
TaskEditorTreeView::TaskEditorTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
TaskEditorItemModel *m = new TaskEditorItemModel( this );
setModel( m );
//setSelectionBehavior( QAbstractItemView::SelectItems );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
createItemDelegates( m );
setItemDelegateForColumn( NodeModel::NodeType, new EnumDelegate( this ) );
connect( this, &DoubleTreeViewBase::dropAllowed, this, &TaskEditorTreeView::slotDropAllowed );
}
NodeItemModel *TaskEditorTreeView::baseModel() const
{
NodeSortFilterProxyModel *pr = proxyModel();
if ( pr ) {
return static_cast<NodeItemModel*>( pr->sourceModel() );
}
return static_cast<NodeItemModel*>( model() );
}
void TaskEditorTreeView::slotDropAllowed( const QModelIndex &index, int dropIndicatorPosition, QDragMoveEvent *event )
{
QModelIndex idx = index;
NodeSortFilterProxyModel *pr = proxyModel();
if ( pr ) {
idx = pr->mapToSource( index );
}
event->ignore();
if ( baseModel()->dropAllowed( idx, dropIndicatorPosition, event->mimeData() ) ) {
event->accept();
}
}
//--------------------
NodeTreeView::NodeTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
NodeItemModel *m = new NodeItemModel( this );
setModel( m );
//setSelectionBehavior( QAbstractItemView::SelectItems );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
createItemDelegates( m );
connect( this, &DoubleTreeViewBase::dropAllowed, this, &NodeTreeView::slotDropAllowed );
}
NodeItemModel *NodeTreeView::baseModel() const
{
NodeSortFilterProxyModel *pr = proxyModel();
if ( pr ) {
return static_cast<NodeItemModel*>( pr->sourceModel() );
}
return static_cast<NodeItemModel*>( model() );
}
void NodeTreeView::slotDropAllowed( const QModelIndex &index, int dropIndicatorPosition, QDragMoveEvent *event )
{
QModelIndex idx = index;
NodeSortFilterProxyModel *pr = proxyModel();
if ( pr ) {
idx = pr->mapToSource( index );
}
event->ignore();
if ( baseModel()->dropAllowed( idx, dropIndicatorPosition, event->mimeData() ) ) {
event->accept();
}
}
//-----------------------------------
TaskEditor::TaskEditor(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent )
{
debugPlan<<"----------------- Create TaskEditor ----------------------";
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new TaskEditorTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
l->addWidget( m_view );
debugPlan<<m_view->actionSplitView();
setupGui();
m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
m_view->setDragDropMode( QAbstractItemView::DragDrop );
m_view->setDropIndicatorShown( true );
m_view->setDragEnabled ( true );
m_view->setAcceptDrops( true );
m_view->setAcceptDropsOnView( true );
QList<int> lst1; lst1 << 1 << -1; // only display column 0 (NodeName) in left view
QList<int> show;
show << NodeModel::NodeResponsible
<< NodeModel::NodeAllocation
<< NodeModel::NodeType
<< NodeModel::NodeEstimateCalendar
<< NodeModel::NodeEstimate
<< NodeModel::NodeOptimisticRatio
<< NodeModel::NodePessimisticRatio
<< NodeModel::NodeRisk
<< NodeModel::NodeConstraint
<< NodeModel::NodeConstraintStart
<< NodeModel::NodeConstraintEnd
<< NodeModel::NodeRunningAccount
<< NodeModel::NodeStartupAccount
<< NodeModel::NodeStartupCost
<< NodeModel::NodeShutdownAccount
<< NodeModel::NodeShutdownCost
<< NodeModel::NodeDescription;
QList<int> lst2;
for ( int i = 0; i < model()->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
for ( int i = 0; i < show.count(); ++i ) {
int sec = m_view->slaveView()->header()->visualIndex( show[ i ] );
//debugPlan<<"move section:"<<i<<show[i]<<sec;
if ( i != sec ) {
m_view->slaveView()->header()->moveSection( sec, i );
}
}
m_view->hideColumns( lst1, lst2 );
m_view->masterView()->setDefaultColumns( QList<int>() << NodeModel::NodeName );
m_view->slaveView()->setDefaultColumns( show );
connect( model(), SIGNAL(executeCommand(KUndo2Command*)), doc, SLOT(addCommand(KUndo2Command*)) );
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &TaskEditor::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &TaskEditor::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &TaskEditor::slotContextMenuRequested );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
connect(baseModel(), &NodeItemModel::projectShownChanged, this, &TaskEditor::slotProjectShown);
connect(model(), &QAbstractItemModel::rowsMoved, this, &TaskEditor::slotEnableActions);
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Task Editor</title>"
"<para>"
"The Task Editor is used to create, edit, and delete tasks. "
"Tasks are organized into a Work Breakdown Structure (WBS) to any depth."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Task_Editor")));
}
void TaskEditor::slotProjectShown( bool on )
{
debugPlan<<proxyModel();
QModelIndex idx;
if ( proxyModel() ) {
if ( proxyModel()->rowCount() > 0 ) {
idx = proxyModel()->index( 0, 0 );
m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows );
}
} else if ( baseModel() && baseModel()->rowCount() > 0 ) {
idx = baseModel()->index( 0, 0 );
m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows );
}
if ( on && idx.isValid() ) {
m_view->masterView()->expand( idx );
}
slotEnableActions();
}
void TaskEditor::updateReadWrite( bool rw )
{
m_view->setReadWrite( rw );
ViewBase::updateReadWrite( rw );
}
void TaskEditor::setProject( Project *project )
{
debugPlan<<project;
m_view->setProject( project );
ViewBase::setProject( project );
}
void TaskEditor::createDockers()
{
// Add dockers
DockWidget *ds = 0;
{
ds = new DockWidget( this, "Allocations", xi18nc( "@title resource allocations", "Allocations" ) );
QTreeView *x = new QTreeView( ds );
AllocatedResourceItemModel *m1 = new AllocatedResourceItemModel( x );
x->setModel( m1 );
m1->setProject( project() );
// x->setHeaderHidden( true );
x->setSelectionBehavior( QAbstractItemView::SelectRows );
x->setSelectionMode( QAbstractItemView::ExtendedSelection );
x->expandAll();
x->resizeColumnToContents( 0 );
x->setDragDropMode( QAbstractItemView::DragOnly );
x->setDragEnabled ( true );
ds->setWidget( x );
connect(this, &ViewBase::projectChanged, m1, &AllocatedResourceItemModel::setProject);
connect(this, &TaskEditor::taskSelected, m1, &AllocatedResourceItemModel::setTask);
connect(m1, &AllocatedResourceItemModel::expandAll, x, &QTreeView::expandAll);
connect(m1, &AllocatedResourceItemModel::resizeColumnToContents, x, &QTreeView::resizeColumnToContents);
addDocker( ds );
}
{
ds = new DockWidget( this, "Resources", xi18nc( "@title", "Resources" ) );
ds->setToolTip( xi18nc( "@info:tooltip",
"Drag resources into the Task Editor"
" and drop into the allocations- or responsible column" ) );
ResourceAllocationView *e = new ResourceAllocationView(part(), ds );
ResourceItemModel *m = new ResourceItemModel( e );
e->setModel( m );
m->setProject( project() );
m->setReadWrite( isReadWrite() );
QList<int> show; show << ResourceModel::ResourceName;
for ( int i = m->columnCount() - 1; i >= 0; --i ) {
e->setColumnHidden( i, ! show.contains( i ) );
}
e->setHeaderHidden( true );
e->setSelectionBehavior( QAbstractItemView::SelectRows );
e->setSelectionMode( QAbstractItemView::ExtendedSelection );
e->expandAll();
e->resizeColumnToContents( ResourceModel::ResourceName );
e->setDragDropMode( QAbstractItemView::DragOnly );
e->setDragEnabled ( true );
ds->setWidget( e );
connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, e, &ResourceAllocationView::setSelectedTasks);
connect(this, SIGNAL(projectChanged(KPlato::Project*)), m, SLOT(setProject(KPlato::Project*)));
connect(this, &ViewBase::readWriteChanged, m, &ItemModelBase::setReadWrite);
connect(m, &ItemModelBase::executeCommand, part(), &KoDocument::addCommand);
addDocker( ds );
}
{
ds = new DockWidget( this, "Taskmodules", xi18nc( "@title", "Task Modules" ) );
ds->setToolTip( xi18nc( "@info:tooltip", "Drag a task module into the <emphasis>Task Editor</emphasis> to add it to the project" ) );
ds->setLocation( Qt::LeftDockWidgetArea );
ds->setShown(false); // hide by default
QTreeView *e = new QTreeView( ds );
QSortFilterProxyModel *sf = new QSortFilterProxyModel(e);
TaskModuleModel *m = new TaskModuleModel(sf);
sf->setSourceModel(m);
e->setModel(sf);
e->sortByColumn(0, Qt::AscendingOrder);
e->setSortingEnabled(true);
e->setHeaderHidden( true );
e->setRootIsDecorated( false );
e->setSelectionBehavior( QAbstractItemView::SelectRows );
e->setSelectionMode( QAbstractItemView::SingleSelection );
// e->resizeColumnToContents( 0 );
e->setDragDropMode( QAbstractItemView::DragDrop );
e->setAcceptDrops( true );
e->setDragEnabled ( true );
ds->setWidget( e );
connect(e, &QAbstractItemView::doubleClicked, this, &TaskEditor::taskModuleDoubleClicked);
connect(this, &TaskEditor::loadTaskModules, m, &TaskModuleModel::loadTaskModules);
connect(m, &TaskModuleModel::saveTaskModule, this, &TaskEditor::saveTaskModule);
connect(m, &TaskModuleModel::removeTaskModule, this, &TaskEditor::removeTaskModule);
addDocker( ds );
}
}
void TaskEditor::taskModuleDoubleClicked(QModelIndex idx)
{
QUrl url = idx.data(Qt::UserRole).toUrl();
if (url.isValid()) {
emit openDocument(url);
}
}
void TaskEditor::setTaskModules(const QStringList& files)
{
emit loadTaskModules( files );
}
void TaskEditor::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() && m_view->model()->rowCount() > 0 ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void TaskEditor::slotCurrentChanged( const QModelIndex &curr, const QModelIndex & )
{
debugPlan<<curr.row()<<","<<curr.column();
slotEnableActions();
}
void TaskEditor::slotSelectionChanged( const QModelIndexList &list)
{
debugPlan<<list.count();
slotEnableActions();
emit taskSelected( dynamic_cast<Task*>( selectedNode() ) );
}
QModelIndexList TaskEditor::selectedRows() const
{
#if 0
// Qt bug?
return m_view->selectionModel()->selectedRows();
#else
QModelIndexList lst;
foreach ( QModelIndex i, m_view->selectionModel()->selectedIndexes() ) {
if ( i.column() == 0 ) {
lst << i;
}
}
return lst;
#endif
}
int TaskEditor::selectedRowCount() const
{
return selectedRows().count();
}
QList<Node*> TaskEditor::selectedNodes() const {
QList<Node*> lst;
foreach ( const QModelIndex &i, selectedRows() ) {
Node * n = m_view->baseModel()->node( i );
if ( n != 0 && n->type() != Node::Type_Project ) {
lst.append( n );
}
}
return lst;
}
Node *TaskEditor::selectedNode() const
{
QList<Node*> lst = selectedNodes();
if ( lst.count() != 1 ) {
return 0;
}
return lst.first();
}
Node *TaskEditor::currentNode() const {
Node * n = m_view->baseModel()->node( m_view->selectionModel()->currentIndex() );
if ( n == 0 || n->type() == Node::Type_Project ) {
return 0;
}
return n;
}
void TaskEditor::slotContextMenuRequested( const QModelIndex& index, const QPoint& pos, const QModelIndexList &rows )
{
QString name;
if (rows.count() > 1) {
debugPlan<<rows;
QList<Task*> summarytasks;
QList<Task*> tasks;
QList<Task*> milestones;
for (const QModelIndex &idx : rows) {
Node *node = m_view->baseModel()->node( idx );
if (node) {
switch ( node->type() ) {
case Node::Type_Task:
tasks << static_cast<Task*>(node); break;
case Node::Type_Milestone:
milestones << static_cast<Task*>(node); break;
case Node::Type_Summarytask:
summarytasks << static_cast<Task*>(node); break;
default: break;
}
}
}
if (!tasks.isEmpty()) {
editTasks(tasks, pos);
return;
}
return;
}
Node *node = m_view->baseModel()->node( index );
if ( node == 0 ) {
return;
}
debugPlan<<node->name()<<" :"<<pos;
switch ( node->type() ) {
case Node::Type_Project:
name = "task_edit_popup";
break;
case Node::Type_Task:
name = node->isScheduled( baseModel()->id() ) ? "task_popup" : "task_edit_popup";
break;
case Node::Type_Milestone:
name = node->isScheduled( baseModel()->id() ) ? "taskeditor_milestone_popup" : "task_edit_popup";
break;
case Node::Type_Summarytask:
name = "summarytask_popup";
break;
default:
name = "node_popup";
break;
}
m_view->setContextMenuIndex(index);
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
return;
}
debugPlan<<name;
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
void TaskEditor::editTasks(const QList<Task*> &tasks, const QPoint &pos)
{
QList<QAction*> lst;
QAction tasksEdit(i18n( "Edit..."), nullptr);
if (!tasks.isEmpty()) {
TasksEditController *ted = new TasksEditController(*project(), tasks, this);
connect(&tasksEdit, &QAction::triggered, ted, &TasksEditController::activate);
connect(ted, &TasksEditController::addCommand, koDocument(), &KoDocument::addCommand);
lst << &tasksEdit;
}
lst += contextActionList();
if (!lst.isEmpty()) {
QMenu::exec( lst, pos, lst.first() );
}
}
void TaskEditor::setScheduleManager( ScheduleManager *sm )
{
if (!sm && scheduleManager()) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
bool tryexpand = sm && !scheduleManager();
QDomDocument doc;
bool expand = sm && scheduleManager();
if (expand) {
m_view->masterView()->setObjectName("TaskEditor");
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
ViewBase::setScheduleManager(sm);
m_view->baseModel()->setScheduleManager( sm );
if (expand) {
m_view->masterView()->doExpand(doc);
} else if (tryexpand) {
m_view->masterView()->doExpand(m_domdoc);
}
}
void TaskEditor::slotEnableActions()
{
updateActionsEnabled( isReadWrite() );
}
void TaskEditor::updateActionsEnabled( bool on )
{
// debugPlan<<selectedRowCount()<<selectedNode()<<currentNode();
if ( ! on ) {
menuAddTask->setEnabled( false );
actionAddTask->setEnabled( false );
actionAddMilestone->setEnabled( false );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( false );
actionMoveTaskUp->setEnabled( false );
actionMoveTaskDown->setEnabled( false );
actionIndentTask->setEnabled( false );
actionUnindentTask->setEnabled( false );
return;
}
int selCount = selectedRowCount();
if ( selCount == 0 ) {
if ( currentNode() ) {
// there are tasks but none is selected
menuAddTask->setEnabled( false );
actionAddTask->setEnabled( false );
actionAddMilestone->setEnabled( false );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( false );
actionMoveTaskUp->setEnabled( false );
actionMoveTaskDown->setEnabled( false );
actionIndentTask->setEnabled( false );
actionUnindentTask->setEnabled( false );
} else {
// we need to be able to add the first task
menuAddTask->setEnabled( true );
actionAddTask->setEnabled( true );
actionAddMilestone->setEnabled( true );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( false );
actionMoveTaskUp->setEnabled( false );
actionMoveTaskDown->setEnabled( false );
actionIndentTask->setEnabled( false );
actionUnindentTask->setEnabled( false );
}
return;
}
Node *n = selectedNode(); // 0 if not a single task, summarytask or milestone
if ( selCount == 1 && n == 0 ) {
// only project selected
menuAddTask->setEnabled( true );
actionAddTask->setEnabled( true );
actionAddMilestone->setEnabled( true );
menuAddSubTask->setEnabled( true );
actionAddSubtask->setEnabled( true );
actionAddSubMilestone->setEnabled( true );
actionDeleteTask->setEnabled( false );
actionMoveTaskUp->setEnabled( false );
actionMoveTaskDown->setEnabled( false );
actionIndentTask->setEnabled( false );
actionUnindentTask->setEnabled( false );
return;
}
if ( selCount == 1 && n != currentNode() ) {
// multi selection in progress
menuAddTask->setEnabled( false );
actionAddTask->setEnabled( false );
actionAddMilestone->setEnabled( false );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( false );
actionMoveTaskUp->setEnabled( false );
actionMoveTaskDown->setEnabled( false );
actionIndentTask->setEnabled( false );
actionUnindentTask->setEnabled( false );
return;
}
bool baselined = false;
Project *p = m_view->project();
if ( p && p->isBaselined() ) {
foreach ( Node *n, selectedNodes() ) {
if ( n->isBaselined() ) {
baselined = true;
break;
}
}
}
if ( selCount == 1 ) {
menuAddTask->setEnabled( true );
actionAddTask->setEnabled( true );
actionAddMilestone->setEnabled( true );
menuAddSubTask->setEnabled( ! baselined || n->type() == Node::Type_Summarytask );
actionAddSubtask->setEnabled( ! baselined || n->type() == Node::Type_Summarytask );
actionAddSubMilestone->setEnabled( ! baselined || n->type() == Node::Type_Summarytask );
actionDeleteTask->setEnabled( ! baselined );
Node *s = n->siblingBefore();
actionMoveTaskUp->setEnabled( s );
actionMoveTaskDown->setEnabled( n->siblingAfter() );
s = n->siblingBefore();
actionIndentTask->setEnabled( ! baselined && s && ! s->isBaselined() );
actionUnindentTask->setEnabled( ! baselined && n->level() > 1 );
return;
}
// selCount > 1
menuAddTask->setEnabled( false );
actionAddTask->setEnabled( false );
actionAddMilestone->setEnabled( false );
menuAddSubTask->setEnabled( false );
actionAddSubtask->setEnabled( false );
actionAddSubMilestone->setEnabled( false );
actionDeleteTask->setEnabled( ! baselined );
actionMoveTaskUp->setEnabled( false );
actionMoveTaskDown->setEnabled( false );
actionIndentTask->setEnabled( false );
actionUnindentTask->setEnabled( false );
}
void TaskEditor::setupGui()
{
QString name = "taskeditor_add_list";
menuAddTask = new KActionMenu(koIcon("view-task-add"), i18n("Add Task"), this);
actionCollection()->addAction("add_task", menuAddTask );
connect( menuAddTask, &QAction::triggered, this, &TaskEditor::slotAddTask );
addAction( name, menuAddTask );
actionAddTask = new QAction( i18n( "Add Task" ), this);
actionAddTask->setShortcut( Qt::CTRL + Qt::Key_I );
connect( actionAddTask, &QAction::triggered, this, &TaskEditor::slotAddTask );
menuAddTask->addAction( actionAddTask );
actionAddMilestone = new QAction( i18n( "Add Milestone" ), this );
actionAddMilestone->setShortcut( Qt::CTRL + Qt::ALT + Qt::Key_I );
connect( actionAddMilestone, &QAction::triggered, this, &TaskEditor::slotAddMilestone );
menuAddTask->addAction( actionAddMilestone );
menuAddSubTask = new KActionMenu(koIcon("view-task-child-add"), i18n("Add Sub-Task"), this);
actionCollection()->addAction("add_subtask", menuAddTask );
connect( menuAddSubTask, &QAction::triggered, this, &TaskEditor::slotAddSubtask );
addAction( name, menuAddSubTask );
actionAddSubtask = new QAction( i18n( "Add Sub-Task" ), this );
actionAddSubtask->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_I );
connect( actionAddSubtask, &QAction::triggered, this, &TaskEditor::slotAddSubtask );
menuAddSubTask->addAction( actionAddSubtask );
actionAddSubMilestone = new QAction( i18n( "Add Sub-Milestone" ), this );
actionAddSubMilestone->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_I );
connect( actionAddSubMilestone, &QAction::triggered, this, &TaskEditor::slotAddSubMilestone );
menuAddSubTask->addAction( actionAddSubMilestone );
actionDeleteTask = new QAction(koIcon("edit-delete"), xi18nc("@action", "Delete"), this);
actionCollection()->setDefaultShortcut( actionDeleteTask, Qt::Key_Delete );
actionCollection()->addAction("delete_task", actionDeleteTask );
connect( actionDeleteTask, &QAction::triggered, this, &TaskEditor::slotDeleteTask );
addAction( name, actionDeleteTask );
name = "taskeditor_move_list";
actionIndentTask = new QAction(koIcon("format-indent-more"), i18n("Indent Task"), this);
actionCollection()->addAction("indent_task", actionIndentTask );
connect(actionIndentTask, &QAction::triggered, this, &TaskEditor::slotIndentTask);
addAction( name, actionIndentTask );
actionUnindentTask = new QAction(koIcon("format-indent-less"), i18n("Unindent Task"), this);
actionCollection()->addAction("unindent_task", actionUnindentTask );
connect(actionUnindentTask, &QAction::triggered, this, &TaskEditor::slotUnindentTask);
addAction( name, actionUnindentTask );
actionMoveTaskUp = new QAction(koIcon("arrow-up"), i18n("Move Up"), this);
actionCollection()->addAction("move_task_up", actionMoveTaskUp );
connect(actionMoveTaskUp, &QAction::triggered, this, &TaskEditor::slotMoveTaskUp);
addAction( name, actionMoveTaskUp );
actionMoveTaskDown = new QAction(koIcon("arrow-down"), i18n("Move Down"), this);
actionCollection()->addAction("move_task_down", actionMoveTaskDown );
connect(actionMoveTaskDown, &QAction::triggered, this, &TaskEditor::slotMoveTaskDown);
addAction( name, actionMoveTaskDown );
// Add the context menu actions for the view options
actionShowProject = new KToggleAction( i18n( "Show Project" ), this );
connect(actionShowProject, &QAction::triggered, baseModel(), &NodeItemModel::setShowProject);
addContextAction( actionShowProject );
connect(m_view->actionSplitView(), &QAction::triggered, this, &TaskEditor::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionAll);
createDockers();
}
void TaskEditor::slotSplitView()
{
debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
emit optionsModified();
}
void TaskEditor::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, m_view, this );
dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void TaskEditor::slotAddTask()
{
debugPlan;
if ( selectedRowCount() == 0 || ( selectedRowCount() == 1 && selectedNode() == 0 ) ) {
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
Task *t = m_view->project()->createTask( m_view->project()->taskDefaults() );
QModelIndex idx = m_view->baseModel()->insertSubtask( t, m_view->project() );
Q_ASSERT( idx.isValid() );
edit( idx );
return;
}
Node *sib = selectedNode();
if ( sib == 0 ) {
return;
}
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
Task *t = m_view->project()->createTask( m_view->project()->taskDefaults() );
QModelIndex idx = m_view->baseModel()->insertTask( t, sib );
Q_ASSERT( idx.isValid() );
edit( idx );
}
void TaskEditor::slotAddMilestone()
{
debugPlan;
if ( selectedRowCount() == 0 || ( selectedRowCount() == 1 && selectedNode() == 0 ) ) {
// None selected or only project selected: insert under main project
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
Task *t = m_view->project()->createTask();
t->estimate()->clear();
QModelIndex idx = m_view->baseModel()->insertSubtask( t, m_view->project() );
Q_ASSERT( idx.isValid() );
edit( idx );
return;
}
Node *sib = selectedNode(); // sibling
if ( sib == 0 ) {
return;
}
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
Task *t = m_view->project()->createTask();
t->estimate()->clear();
QModelIndex idx = m_view->baseModel()->insertTask( t, sib );
Q_ASSERT( idx.isValid() );
edit( idx );
}
void TaskEditor::slotAddSubMilestone()
{
debugPlan;
Node *parent = selectedNode();
if ( parent == 0 && selectedRowCount() == 1 ) {
// project selected
parent = m_view->project();
}
if ( parent == 0 ) {
return;
}
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
Task *t = m_view->project()->createTask( m_view->project()->taskDefaults() );
t->estimate()->clear();
QModelIndex idx = m_view->baseModel()->insertSubtask( t, parent );
Q_ASSERT( idx.isValid() );
edit( idx );
}
void TaskEditor::slotAddSubtask()
{
debugPlan;
Node *parent = selectedNode();
if ( parent == 0 && selectedRowCount() == 1 ) {
// project selected
parent = m_view->project();
}
if ( parent == 0 ) {
return;
}
m_view->closePersistentEditor( m_view->selectionModel()->currentIndex() );
Task *t = m_view->project()->createTask( m_view->project()->taskDefaults() );
QModelIndex idx = m_view->baseModel()->insertSubtask( t, parent );
Q_ASSERT( idx.isValid() );
edit( idx );
}
void TaskEditor::edit( const QModelIndex &i )
{
if ( i.isValid() ) {
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->setParentsExpanded( i, true ); // in case treeview does not have focus
m_view->edit( i );
}
}
void TaskEditor::slotDeleteTask()
{
//debugPlan;
QList<Node*> lst = selectedNodes();
while ( true ) {
// remove children of selected tasks, as parents delete their children
Node *ch = 0;
foreach ( Node *n1, lst ) {
foreach ( Node *n2, lst ) {
if ( n2->isChildOf( n1 ) ) {
ch = n2;
break;
}
}
if ( ch != 0 ) {
break;
}
}
if ( ch == 0 ) {
break;
}
lst.removeAt( lst.indexOf( ch ) );
}
//foreach ( Node* n, lst ) { debugPlan<<n->name(); }
emit deleteTaskList( lst );
QModelIndex i = m_view->selectionModel()->currentIndex();
if ( i.isValid() ) {
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
}
}
void TaskEditor::slotIndentTask()
{
debugPlan;
Node *n = selectedNode();
if ( n ) {
emit indentTask();
QModelIndex i = baseModel()->index( n );
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
m_view->setParentsExpanded( i, true );
}
}
void TaskEditor::slotUnindentTask()
{
debugPlan;
Node *n = selectedNode();
if ( n ) {
emit unindentTask();
QModelIndex i = baseModel()->index( n );
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
}
}
void TaskEditor::slotMoveTaskUp()
{
debugPlan;
Node *n = selectedNode();
if ( n ) {
emit moveTaskUp();
QModelIndex i = baseModel()->index( n );
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
}
}
void TaskEditor::slotMoveTaskDown()
{
debugPlan;
Node *n = selectedNode();
if ( n ) {
emit moveTaskDown();
QModelIndex i = baseModel()->index( n );
m_view->selectionModel()->select( i, QItemSelectionModel::Rows | QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect );
m_view->selectionModel()->setCurrentIndex( i, QItemSelectionModel::NoUpdate );
}
}
bool TaskEditor::loadContext( const KoXmlElement &context )
{
ViewBase::loadContext( context );
bool show = (bool)(context.attribute( "show-project", "0" ).toInt() );
actionShowProject->setChecked( show );
baseModel()->setShowProject( show ); // why is this not called by the action?
bool res = m_view->loadContext( baseModel()->columnMap(), context );
return res;
}
void TaskEditor::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
context.setAttribute( "show-project", QString::number(baseModel()->projectShown()) );
m_view->saveContext( baseModel()->columnMap(), context );
}
KoPrintJob *TaskEditor::createPrintJob()
{
return m_view->createPrintJob( this );
}
//-----------------------------------
TaskView::TaskView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new NodeTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
NodeSortFilterProxyModel *p = new NodeSortFilterProxyModel( m_view->baseModel(), m_view );
m_view->setModel( p );
l->addWidget( m_view );
setupGui();
//m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
m_view->setDragDropMode( QAbstractItemView::InternalMove );
m_view->setDropIndicatorShown( false );
m_view->setDragEnabled ( true );
m_view->setAcceptDrops( false );
m_view->setAcceptDropsOnView( false );
QList<int> readonly;
readonly << NodeModel::NodeName
<< NodeModel::NodeResponsible
<< NodeModel::NodeAllocation
<< NodeModel::NodeEstimateType
<< NodeModel::NodeEstimateCalendar
<< NodeModel::NodeEstimate
<< NodeModel::NodeOptimisticRatio
<< NodeModel::NodePessimisticRatio
<< NodeModel::NodeRisk
<< NodeModel::NodeConstraint
<< NodeModel::NodeConstraintStart
<< NodeModel::NodeConstraintEnd
<< NodeModel::NodeRunningAccount
<< NodeModel::NodeStartupAccount
<< NodeModel::NodeStartupCost
<< NodeModel::NodeShutdownAccount
<< NodeModel::NodeShutdownCost
<< NodeModel::NodeDescription;
foreach ( int c, readonly ) {
m_view->baseModel()->setReadOnly( c, true );
}
QList<int> lst1; lst1 << 1 << -1;
QList<int> show;
show << NodeModel::NodeStatus
<< NodeModel::NodeCompleted
<< NodeModel::NodeResponsible
<< NodeModel::NodeAssignments
<< NodeModel::NodePerformanceIndex
<< NodeModel::NodeBCWS
<< NodeModel::NodeBCWP
<< NodeModel::NodeACWP
<< NodeModel::NodeDescription;
for ( int s = 0; s < show.count(); ++s ) {
m_view->slaveView()->mapToSection( show[s], s );
}
QList<int> lst2;
for ( int i = 0; i < m_view->model()->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
m_view->hideColumns( lst1, lst2 );
m_view->masterView()->setDefaultColumns( QList<int>() << 0 );
m_view->slaveView()->setDefaultColumns( show );
connect( m_view->baseModel(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &TaskView::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &TaskView::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &TaskView::slotContextMenuRequested );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Task Execution View</title>"
"<para>"
"The view is used to edit and inspect task progress during project execution."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Task_Execution_View")));
}
void TaskView::updateReadWrite( bool rw )
{
m_view->setReadWrite( rw );
ViewBase::updateReadWrite( rw );
}
void TaskView::draw( Project &project )
{
m_view->setProject( &project );
}
void TaskView::draw()
{
}
void TaskView::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() && m_view->model()->rowCount() > 0 ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void TaskView::slotCurrentChanged( const QModelIndex &curr, const QModelIndex & )
{
debugPlan<<curr.row()<<","<<curr.column();
slotEnableActions();
}
void TaskView::slotSelectionChanged( const QModelIndexList &list)
{
debugPlan<<list.count();
slotEnableActions();
}
int TaskView::selectedNodeCount() const
{
QItemSelectionModel* sm = m_view->selectionModel();
return sm->selectedRows().count();
}
QList<Node*> TaskView::selectedNodes() const {
QList<Node*> lst;
QItemSelectionModel* sm = m_view->selectionModel();
if ( sm == 0 ) {
return lst;
}
foreach ( const QModelIndex &i, sm->selectedRows() ) {
Node * n = m_view->baseModel()->node( proxyModel()->mapToSource( i ) );
if ( n != 0 && n->type() != Node::Type_Project ) {
lst.append( n );
}
}
return lst;
}
Node *TaskView::selectedNode() const
{
QList<Node*> lst = selectedNodes();
if ( lst.count() != 1 ) {
return 0;
}
return lst.first();
}
Node *TaskView::currentNode() const {
Node * n = m_view->baseModel()->node( proxyModel()->mapToSource( m_view->selectionModel()->currentIndex() ) );
if ( n == 0 || n->type() == Node::Type_Project ) {
return 0;
}
return n;
}
void TaskView::slotContextMenuRequested( const QModelIndex& index, const QPoint& pos )
{
QString name;
Node *node = m_view->baseModel()->node( proxyModel()->mapToSource( index ) );
if ( node ) {
switch ( node->type() ) {
case Node::Type_Task:
name = "taskview_popup";
break;
case Node::Type_Milestone:
name = "taskview_milestone_popup";
break;
case Node::Type_Summarytask:
name = "taskview_summary_popup";
break;
default:
break;
}
} else debugPlan<<"No node: "<<index;
if ( name.isEmpty() ) {
debugPlan<<"No menu";
slotHeaderContextMenuRequested( pos );
return;
}
m_view->setContextMenuIndex(index);
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
void TaskView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan<<endl;
if (!sm && scheduleManager()) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
bool tryexpand = sm && !scheduleManager();
QDomDocument doc;
bool expand = sm && scheduleManager() && sm != scheduleManager();
if (expand) {
m_view->masterView()->setObjectName("TaskEditor");
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
ViewBase::setScheduleManager(sm);
m_view->baseModel()->setScheduleManager( sm );
if (expand) {
m_view->masterView()->doExpand(doc);
} else if (tryexpand) {
m_view->masterView()->doExpand(m_domdoc);
}
}
void TaskView::slotEnableActions()
{
updateActionsEnabled( true );
}
void TaskView::updateActionsEnabled( bool /*on*/ )
{
}
void TaskView::setupGui()
{
// KActionCollection *coll = actionCollection();
// Add the context menu actions for the view options
actionShowProject = new KToggleAction( i18n( "Show Project" ), this );
connect(actionShowProject, &QAction::triggered, baseModel(), &NodeItemModel::setShowProject);
addContextAction( actionShowProject );
connect(m_view->actionSplitView(), &QAction::triggered, this, &TaskView::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionAll);
}
void TaskView::slotSplitView()
{
debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
emit optionsModified();
}
void TaskView::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, m_view, this );
dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool TaskView::loadContext( const KoXmlElement &context )
{
ViewBase::loadContext( context );
bool show = (bool)(context.attribute( "show-project", "0" ).toInt() );
actionShowProject->setChecked( show );
baseModel()->setShowProject( show ); // why is this not called by the action?
return m_view->loadContext( m_view->baseModel()->columnMap(), context );
}
void TaskView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
context.setAttribute( "show-project", QString::number(baseModel()->projectShown()) );
m_view->saveContext( m_view->baseModel()->columnMap(), context );
}
KoPrintJob *TaskView::createPrintJob()
{
return m_view->createPrintJob( this );
}
//---------------------------------
WorkPackageTreeView::WorkPackageTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
debugPlan<<"----------"<<this<<"----------";
m = new WorkPackageProxyModel( this );
setModel( m );
//setSelectionBehavior( QAbstractItemView::SelectItems );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSelectionBehavior( QAbstractItemView::SelectRows );
createItemDelegates( baseModel() );
setSortingEnabled( true );
sortByColumn( NodeModel::NodeWBSCode, Qt::AscendingOrder );
connect( this, &DoubleTreeViewBase::dropAllowed, this, &WorkPackageTreeView::slotDropAllowed );
}
NodeItemModel *WorkPackageTreeView::baseModel() const
{
return m->baseModel();
}
void WorkPackageTreeView::slotDropAllowed( const QModelIndex &index, int dropIndicatorPosition, QDragMoveEvent *event )
{
Q_UNUSED(index);
Q_UNUSED(dropIndicatorPosition);
Q_UNUSED(event);
/* QModelIndex idx = index;
NodeSortFilterProxyModel *pr = proxyModel();
if ( pr ) {
idx = pr->mapToSource( index );
}
event->ignore();
if ( baseModel()->dropAllowed( idx, dropIndicatorPosition, event->mimeData() ) ) {
event->accept();
}*/
}
//--------------------------------
TaskWorkPackageView::TaskWorkPackageView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent ),
m_cmd( 0 )
{
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new WorkPackageTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
l->addWidget( m_view );
setupGui();
//m_view->setEditTriggers( m_view->editTriggers() | QAbstractItemView::EditKeyPressed );
m_view->setDragDropMode( QAbstractItemView::InternalMove );
m_view->setDropIndicatorShown( false );
m_view->setDragEnabled ( true );
m_view->setAcceptDrops( false );
m_view->setAcceptDropsOnView( false );
QList<int> readonly;
readonly << NodeModel::NodeName
<< NodeModel::NodeResponsible
<< NodeModel::NodeAllocation
<< NodeModel::NodeEstimateType
<< NodeModel::NodeEstimateCalendar
<< NodeModel::NodeEstimate
<< NodeModel::NodeOptimisticRatio
<< NodeModel::NodePessimisticRatio
<< NodeModel::NodeRisk
<< NodeModel::NodeConstraint
<< NodeModel::NodeConstraintStart
<< NodeModel::NodeConstraintEnd
<< NodeModel::NodeRunningAccount
<< NodeModel::NodeStartupAccount
<< NodeModel::NodeStartupCost
<< NodeModel::NodeShutdownAccount
<< NodeModel::NodeShutdownCost
<< NodeModel::NodeDescription;
foreach ( int c, readonly ) {
m_view->baseModel()->setReadOnly( c, true );
}
QList<int> lst1; lst1 << 1 << -1;
QList<int> show;
show << NodeModel::NodeStatus
<< NodeModel::NodeCompleted
<< NodeModel::NodeResponsible
<< NodeModel::NodeAssignments
<< NodeModel::NodeDescription;
for ( int s = 0; s < show.count(); ++s ) {
m_view->slaveView()->mapToSection( show[s], s );
}
QList<int> lst2;
for ( int i = 0; i < m_view->model()->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
m_view->hideColumns( lst1, lst2 );
m_view->masterView()->setDefaultColumns( QList<int>() << 0 );
m_view->slaveView()->setDefaultColumns( show );
connect( m_view->baseModel(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, &DoubleTreeViewBase::currentChanged, this, &TaskWorkPackageView::slotCurrentChanged );
connect( m_view, &DoubleTreeViewBase::selectionChanged, this, &TaskWorkPackageView::slotSelectionChanged );
connect( m_view, &DoubleTreeViewBase::contextMenuRequested, this, &TaskWorkPackageView::slotContextMenuRequested );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
}
Project *TaskWorkPackageView::project() const
{
return m_view->project();
}
void TaskWorkPackageView::setProject( Project *project )
{
m_view->setProject( project );
}
WorkPackageProxyModel *TaskWorkPackageView::proxyModel() const
{
return m_view->proxyModel();
}
void TaskWorkPackageView::updateReadWrite( bool rw )
{
m_view->setReadWrite( rw );
ViewBase::updateReadWrite( rw );
}
void TaskWorkPackageView::setGuiActive( bool activate )
{
debugPlan<<activate;
updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
if ( activate && !m_view->selectionModel()->currentIndex().isValid() && m_view->model()->rowCount() > 0 ) {
m_view->selectionModel()->setCurrentIndex(m_view->model()->index( 0, 0 ), QItemSelectionModel::NoUpdate);
}
}
void TaskWorkPackageView::slotRefreshView()
{
emit checkForWorkPackages();
}
void TaskWorkPackageView::slotCurrentChanged( const QModelIndex &curr, const QModelIndex & )
{
debugPlan<<curr.row()<<","<<curr.column();
slotEnableActions();
}
void TaskWorkPackageView::slotSelectionChanged( const QModelIndexList &list)
{
debugPlan<<list.count();
slotEnableActions();
}
int TaskWorkPackageView::selectedNodeCount() const
{
QItemSelectionModel* sm = m_view->selectionModel();
return sm->selectedRows().count();
}
QList<Node*> TaskWorkPackageView::selectedNodes() const {
QList<Node*> lst;
QItemSelectionModel* sm = m_view->selectionModel();
if ( sm == 0 ) {
return lst;
}
foreach ( const QModelIndex &i, sm->selectedRows() ) {
Node * n = proxyModel()->taskFromIndex( i );
if ( n != 0 && n->type() != Node::Type_Project ) {
lst.append( n );
}
}
return lst;
}
Node *TaskWorkPackageView::selectedNode() const
{
QList<Node*> lst = selectedNodes();
if ( lst.count() != 1 ) {
return 0;
}
return lst.first();
}
Node *TaskWorkPackageView::currentNode() const {
Node * n = proxyModel()->taskFromIndex( m_view->selectionModel()->currentIndex() );
if ( n == 0 || n->type() == Node::Type_Project ) {
return 0;
}
return n;
}
void TaskWorkPackageView::slotContextMenuRequested( const QModelIndex& index, const QPoint& pos )
{
QString name;
Node *node = proxyModel()->taskFromIndex( index );
if ( node ) {
switch ( node->type() ) {
case Node::Type_Task:
name = "workpackage_popup";
break;
case Node::Type_Milestone:
name = "taskview_milestone_popup";
break;
case Node::Type_Summarytask:
name = "taskview_summary_popup";
break;
default:
break;
}
} else debugPlan<<"No node: "<<index;
if ( name.isEmpty() ) {
debugPlan<<"No menu";
slotHeaderContextMenuRequested( pos );
return;
}
m_view->setContextMenuIndex(index);
emit requestPopupMenu( name, pos );
m_view->setContextMenuIndex(QModelIndex());
}
void TaskWorkPackageView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan<<endl;
m_view->baseModel()->setScheduleManager( sm );
}
void TaskWorkPackageView::slotEnableActions()
{
updateActionsEnabled( true );
}
void TaskWorkPackageView::updateActionsEnabled( bool on )
{
bool o = ! selectedNodes().isEmpty();
actionMailWorkpackage->setEnabled( o && on );
}
void TaskWorkPackageView::setupGui()
{
// KActionCollection *coll = actionCollection();
QString name = "workpackage_list";
actionMailWorkpackage = new QAction(koIcon("mail-send"), i18n("Send..."), this);
actionCollection()->setDefaultShortcut( actionMailWorkpackage, Qt::CTRL + Qt::Key_M );
actionCollection()->addAction("send_workpackage", actionMailWorkpackage );
connect( actionMailWorkpackage, &QAction::triggered, this, &TaskWorkPackageView::slotMailWorkpackage );
addAction( name, actionMailWorkpackage );
// Add the context menu actions for the view options
connect(m_view->actionSplitView(), &QAction::triggered, this, &TaskWorkPackageView::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionAll);
}
void TaskWorkPackageView::slotMailWorkpackage()
{
QList<Node*> lst = selectedNodes();
if ( ! lst.isEmpty() ) {
// TODO find a better way to log to avoid undo/redo
m_cmd = new MacroCommand( kundo2_i18n( "Log Send Workpackage" ) );
QPointer<WorkPackageSendDialog> dlg = new WorkPackageSendDialog( lst, scheduleManager(), this );
connect ( dlg->panel(), &WorkPackageSendPanel::sendWorkpackages, this, &TaskWorkPackageView::mailWorkpackages );
connect ( dlg->panel(), &WorkPackageSendPanel::sendWorkpackages, this, &TaskWorkPackageView::slotWorkPackageSent );
dlg->exec();
delete dlg;
if ( ! m_cmd->isEmpty() ) {
part()->addCommand( m_cmd );
m_cmd = 0;
}
delete m_cmd;
m_cmd = 0;
}
}
void TaskWorkPackageView::slotWorkPackageSent( const QList<Node*> &nodes, Resource *resource )
{
foreach ( Node *n, nodes ) {
WorkPackage *wp = new WorkPackage( static_cast<Task*>( n )->workPackage() );
wp->setOwnerName( resource->name() );
wp->setOwnerId( resource->id() );
wp->setTransmitionTime( DateTime::currentDateTime() );
wp->setTransmitionStatus( WorkPackage::TS_Send );
m_cmd->addCommand( new WorkPackageAddCmd( static_cast<Project*>( n->projectNode() ), n, wp ) );
}
}
void TaskWorkPackageView::slotSplitView()
{
debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
emit optionsModified();
}
void TaskWorkPackageView::slotOptions()
{
debugPlan;
SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog( this, m_view, this );
dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool TaskWorkPackageView::loadContext( const KoXmlElement &context )
{
debugPlan;
ViewBase::loadContext( context );
return m_view->loadContext( m_view->baseModel()->columnMap(), context );
}
void TaskWorkPackageView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
m_view->saveContext( m_view->baseModel()->columnMap(), context );
}
KoPrintJob *TaskWorkPackageView::createPrintJob()
{
return m_view->createPrintJob( this );
}
} // namespace KPlato
diff --git a/src/libs/ui/kpttaskgeneralpanel.cpp b/src/libs/ui/kpttaskgeneralpanel.cpp
index db920146..69319dcc 100644
--- a/src/libs/ui/kpttaskgeneralpanel.cpp
+++ b/src/libs/ui/kpttaskgeneralpanel.cpp
@@ -1,577 +1,578 @@
/* This file is part of the KDE project
Copyright (C) 2004 - 2007, 2011 Dag Andersen <danders@get2net.dk>
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 "kpttaskgeneralpanel.h"
#include "kpttaskdialog.h"
#include "kpttask.h"
#include "kptcommand.h"
#include "kptduration.h"
#include "kptdurationspinbox.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptproject.h"
#include <KLocalizedString>
#ifdef PLAN_KDEPIMLIBS_FOUND
#include <akonadi/contact/emailaddressselectiondialog.h>
#include <akonadi/contact/emailaddressselectionwidget.h>
#include <akonadi/contact/emailaddressselection.h>
#endif
#include <QDateTime>
#include <kptdebug.h>
namespace KPlato
{
TaskGeneralPanel::TaskGeneralPanel(Project &project, Task &task, QWidget *p, const char *n)
: TaskGeneralPanelImpl(p, n),
m_task(task),
m_project( project )
{
useTime = true;
setStartValues( task );
QString s = i18n( "The Work Breakdown Structure introduces numbering for all tasks in the project, according to the task structure.\nThe WBS code is auto-generated.\nYou can define the WBS code pattern using the Define WBS Pattern command in the Tools menu." );
wbslabel->setWhatsThis( s );
wbsfield->setWhatsThis( s );
if ( task.isBaselined( BASELINESCHEDULE ) ) {
schedulingGroup->setEnabled( false );
}
}
void TaskGeneralPanel::setStartValues( Task &task ) {
m_estimate = m_duration = task.estimate()->expectedValue();
namefield->setText(task.name());
leaderfield->setText(task.leader());
wbsfield->setText(task.wbsCode());
int cal = 0;
m_calendars.clear();
calendarCombo->addItem(i18n("None"));
m_calendars.insert(0, 0);
QList<Calendar*> list = m_project.allCalendars();
int i=1;
foreach (Calendar *c, list) {
calendarCombo->insertItem(i, c->name());
m_calendars.insert(i, c);
if (c == task.estimate()->calendar()) {
cal = i;
}
++i;
}
calendarCombo->setCurrentIndex(cal);
estimate->setMinimumUnit( (Duration::Unit)(m_project.config().minimumDurationUnit()) );
estimate->setMaximumUnit( (Duration::Unit)(m_project.config().maximumDurationUnit()) );
estimate->setUnit( task.estimate()->unit() );
setEstimateType(task.estimate()->type());
if (task.estimate()->type() == Estimate::Type_Effort && task.estimate()->expectedEstimate() == 0.0) {
setEstimateType(2 /*Milestone*/);
}
setSchedulingType(task.constraint());
if (task.constraintStartTime().isValid()) {
setStartDateTime(task.constraintStartTime());
} else {
QDate date = QDate::currentDate();
setStartDateTime(QDateTime(date, QTime(), Qt::LocalTime));
}
if (task.constraintEndTime().isValid()) {
setEndDateTime(task.constraintEndTime());
} else {
setEndDateTime(QDateTime(startDate().addDays(1), QTime(), Qt::LocalTime));
}
//debugPlan<<"Estimate:"<<task.estimate()->expected().toString();
setEstimate(task.estimate()->expectedEstimate());
setOptimistic(task.estimate()->optimisticRatio());
setPessimistic(task.estimate()->pessimisticRatio());
setRisktype(task.estimate()->risktype());
namefield->setFocus();
}
MacroCommand *TaskGeneralPanel::buildCommand() {
MacroCommand *cmd = new MacroCommand(kundo2_i18n("Modify Task"));
bool modified = false;
if (!namefield->isHidden() && m_task.name() != namefield->text()) {
cmd->addCommand(new NodeModifyNameCmd(m_task, namefield->text()));
modified = true;
}
if (!leaderfield->isHidden() && m_task.leader() != leaderfield->text()) {
cmd->addCommand(new NodeModifyLeaderCmd(m_task, leaderfield->text()));
modified = true;
}
Node::ConstraintType c = (Node::ConstraintType)schedulingType();
if (c != m_task.constraint()) {
cmd->addCommand(new NodeModifyConstraintCmd(m_task, c));
modified = true;
}
if (startDateTime() != m_task.constraintStartTime() &&
(c == Node::FixedInterval || c == Node::StartNotEarlier || c == Node::MustStartOn)) {
cmd->addCommand(new NodeModifyConstraintStartTimeCmd(m_task, startDateTime()));
modified = true;
}
if (endDateTime() != m_task.constraintEndTime() &&
(c == Node::FinishNotLater || c == Node::FixedInterval || c == Node::MustFinishOn)) {
cmd->addCommand(new NodeModifyConstraintEndTimeCmd(m_task, endDateTime()));
modified = true;
}
int et = estimationType();
if (et == 2 /*Milestome*/) {
et = 0; /*Effort*/
}
if (et != m_task.estimate()->type()) {
cmd->addCommand(new ModifyEstimateTypeCmd(m_task, m_task.estimate()->type(), et));
modified = true;
}
bool unitchanged = estimate->unit() != m_task.estimate()->unit();
if ( unitchanged ) {
cmd->addCommand( new ModifyEstimateUnitCmd( m_task, m_task.estimate()->unit(), estimate->unit() ) );
modified = true;
}
bool expchanged = estimationValue() != m_task.estimate()->expectedEstimate();
if ( expchanged ) {
cmd->addCommand(new ModifyEstimateCmd(m_task, m_task.estimate()->expectedEstimate(), estimationValue()));
modified = true;
}
int x = optimistic();
if ( x != m_task.estimate()->optimisticRatio() || expchanged || unitchanged ) {
cmd->addCommand(new EstimateModifyOptimisticRatioCmd(m_task, m_task.estimate()->optimisticRatio(), x));
modified = true;
}
x = pessimistic();
if ( x != m_task.estimate()->pessimisticRatio() || expchanged || unitchanged ) {
cmd->addCommand(new EstimateModifyPessimisticRatioCmd(m_task, m_task.estimate()->pessimisticRatio(), x));
modified = true;
}
if (m_task.estimate()->risktype() != risktype()) {
cmd->addCommand(new EstimateModifyRiskCmd(m_task, m_task.estimate()->risktype(), risktype()));
modified = true;
}
if (m_task.estimate()->calendar() != calendar()) {
cmd->addCommand(new ModifyEstimateCalendarCmd(m_task, m_task.estimate()->calendar(), calendar()));
modified = true;
}
if (!modified) {
delete cmd;
return 0;
}
return cmd;
}
bool TaskGeneralPanel::ok() {
return true;
}
void TaskGeneralPanel::estimationTypeChanged(int type) {
if (type == 0 /*Effort*/) {
estimate->setEnabled(true);
calendarCombo->setEnabled(false);
} else if ( type == 1 /*Duration*/ ) {
calendarCombo->setEnabled(false);
if (schedulingType() == 6) { /*Fixed interval*/
estimate->setEnabled(false);
} else {
estimate->setEnabled(true);
calendarCombo->setEnabled(true);
}
} else if ( type == 2 /* Milestone */ ) {
estimate->setValue( 0 );
estimate->setEnabled(false);
calendarCombo->setEnabled(false);
}
TaskGeneralPanelImpl::estimationTypeChanged(type);
}
void TaskGeneralPanel::scheduleTypeChanged(int value)
{
if (value == 6 /*Fixed interval*/) {
if (estimateType->currentIndex() == 1/*duration*/){
// setEstimateScales(24);
estimate->setEnabled(false);
//TODO setEstimate( DateTime( endDateTime(), KDateTime::UTC) - DateTime( startDateTime(), KDateTime::UTC ) );
}
} else {
estimate->setEnabled(true);
}
TaskGeneralPanelImpl::scheduleTypeChanged(value);
}
//-----------------------------
TaskGeneralPanelImpl::TaskGeneralPanelImpl(QWidget *p, const char *n)
: QWidget(p) {
setObjectName(n);
setupUi(this);
#ifndef PLAN_KDEPIMLIBS_FOUND
chooseLeader->hide();
#endif
// FIXME
// [Bug 311940] New: Plan crashes when typing a text in the filter textbox before the textbook is fully loaded when selecting a contact from the adressbook
chooseLeader->hide();
connect(namefield, &QLineEdit::textChanged, this, &TaskGeneralPanelImpl::checkAllFieldsFilled);
connect(leaderfield, &QLineEdit::textChanged, this, &TaskGeneralPanelImpl::checkAllFieldsFilled);
connect(chooseLeader, &QAbstractButton::clicked, this, &TaskGeneralPanelImpl::changeLeader);
connect(estimateType, SIGNAL(activated(int)), SLOT(estimationTypeChanged(int)));
connect(scheduleType, SIGNAL(activated(int)), SLOT(scheduleTypeChanged(int)));
connect(scheduleStartDate, &QDateTimeEdit::dateChanged, this, &TaskGeneralPanelImpl::startDateChanged);
connect(scheduleStartTime, &QDateTimeEdit::timeChanged, this, &TaskGeneralPanelImpl::startTimeChanged);
connect(scheduleEndDate, &QDateTimeEdit::dateChanged, this, &TaskGeneralPanelImpl::endDateChanged);
connect(scheduleEndTime, &QDateTimeEdit::timeChanged, this, &TaskGeneralPanelImpl::endTimeChanged);
connect(estimate, SIGNAL(valueChanged(double)), SLOT(checkAllFieldsFilled()));
connect(optimisticValue, SIGNAL(valueChanged(int)), SLOT(checkAllFieldsFilled()));
connect(pessimisticValue, SIGNAL(valueChanged(int)), SLOT(checkAllFieldsFilled()));
connect(risk, SIGNAL(activated(int)), SLOT(checkAllFieldsFilled()));
connect(calendarCombo, SIGNAL(activated(int)), SLOT(calendarChanged(int)));
}
void TaskGeneralPanelImpl::setSchedulingType(int type)
{
enableDateTime(type);
scheduleType->setCurrentIndex(type);
emit schedulingTypeChanged(type);
}
int TaskGeneralPanelImpl::schedulingType() const
{
return scheduleType->currentIndex();
}
void TaskGeneralPanelImpl::changeLeader()
{
#ifdef PLAN_KDEPIMLIBS_FOUND
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog( this );
if ( dlg->exec() && dlg ) {
QStringList names;
const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
foreach ( const Akonadi::EmailAddressSelection &selection, selections ) {
QString s = selection.name();
if ( ! selection.email().isEmpty() ) {
if ( ! selection.name().isEmpty() ) {
s += " <";
}
s += selection.email();
if ( ! selection.name().isEmpty() ) {
s += '>';
}
if ( ! s.isEmpty() ) {
names << s;
}
}
}
if ( ! names.isEmpty() ) {
leaderfield->setText( names.join( ", " ) );
}
}
#endif
}
void TaskGeneralPanelImpl::setEstimationType( int type )
{
estimateType->setCurrentIndex(type);
}
int TaskGeneralPanelImpl::estimationType() const
{
return estimateType->currentIndex();
}
void TaskGeneralPanelImpl::setOptimistic( int value )
{
optimisticValue->setValue(value);
}
void TaskGeneralPanelImpl::setPessimistic( int value )
{
pessimisticValue->setValue(value);
}
int TaskGeneralPanelImpl::optimistic() const
{
return optimisticValue->value();
}
int TaskGeneralPanelImpl::pessimistic()
{
return pessimisticValue->value();
}
void TaskGeneralPanelImpl::enableDateTime( int scheduleType )
{
scheduleStartTime->setEnabled(false);
scheduleEndTime->setEnabled(false);
scheduleStartDate->setEnabled(false);
scheduleEndDate->setEnabled(false);
switch (scheduleType)
{
case 0: //ASAP
case 1: //ALAP
break;
case 2: //Must start on
case 4: // Start not earlier
if (useTime) {
scheduleStartTime->setEnabled(true);
scheduleEndTime->setEnabled(false);
}
scheduleStartDate->setEnabled(true);
scheduleEndDate->setEnabled(false);
break;
case 3: //Must finish on
case 5: // Finish not later
if (useTime) {
scheduleStartTime->setEnabled(false);
scheduleEndTime->setEnabled(true);
}
scheduleStartDate->setEnabled(false);
scheduleEndDate->setEnabled(true);
break;
case 6: //Fixed interval
if (useTime) {
scheduleStartTime->setEnabled(true);
scheduleEndTime->setEnabled(true);
}
scheduleStartDate->setEnabled(true);
scheduleEndDate->setEnabled(true);
break;
default:
break;
}
}
void TaskGeneralPanelImpl::estimationTypeChanged( int /*type*/ )
{
checkAllFieldsFilled();
}
void TaskGeneralPanelImpl::calendarChanged( int /*index*/ )
{
checkAllFieldsFilled();
}
void TaskGeneralPanelImpl::setEstimate( double duration)
{
estimate->setValue( duration );
}
void TaskGeneralPanelImpl::setEstimateType( int type)
{
estimateType->setCurrentIndex(type);
estimationTypeChanged( type );
}
void TaskGeneralPanelImpl::checkAllFieldsFilled()
{
emit changed();
emit obligatedFieldsFilled(true); // do not block save even if name is not filled
}
double TaskGeneralPanelImpl::estimationValue()
{
return estimate->value();
}
void TaskGeneralPanelImpl::startDateChanged()
{
if (!scheduleStartDate->isEnabled()) {
return;
}
QDate date = startDate();
if (startDateTime() > endDateTime())
{
scheduleEndTime->blockSignals(true);
scheduleEndDate->blockSignals(true);
setEndDate(date);
setEndTime(startTime());
scheduleEndTime->blockSignals(false);
scheduleEndDate->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TaskGeneralPanelImpl::startTimeChanged( const QTime &time )
{
if (!scheduleStartTime->isEnabled()) {
return;
}
if (startDateTime() > endDateTime())
{
scheduleEndTime->blockSignals(true);
setEndTime(time);
scheduleEndTime->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TaskGeneralPanelImpl::endDateChanged()
{
if (!scheduleEndDate->isEnabled()) {
return;
}
QDate date = endDate();
if (endDateTime() < startDateTime())
{
scheduleStartTime->blockSignals(true);
scheduleStartDate->blockSignals(true);
setStartDate(date);
setStartTime(endTime());
scheduleStartTime->blockSignals(false);
scheduleStartDate->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TaskGeneralPanelImpl::endTimeChanged( const QTime &time )
{
if (!scheduleEndTime->isEnabled()) {
return;
}
if (endDateTime() < startDateTime())
{
scheduleStartTime->blockSignals(true);
setStartTime(time);
scheduleStartTime->blockSignals(false);
}
if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
{
estimationTypeChanged(estimateType->currentIndex());
}
checkAllFieldsFilled();
}
void TaskGeneralPanelImpl::scheduleTypeChanged( int value )
{
estimationTypeChanged(estimateType->currentIndex());
enableDateTime(value);
checkAllFieldsFilled();
}
QDateTime TaskGeneralPanelImpl::startDateTime()
{
return QDateTime(startDate(), startTime(), Qt::LocalTime);
}
QDateTime TaskGeneralPanelImpl::endDateTime()
{
return QDateTime(endDate(), endTime(), Qt::LocalTime);
}
void TaskGeneralPanelImpl::setStartTime( const QTime &time )
{
scheduleStartTime->setTime( QTime( time.hour(), time.minute(), 0 ) );
}
void TaskGeneralPanelImpl::setEndTime( const QTime &time )
{
scheduleEndTime->setTime( QTime( time.hour(), time.minute(), 0 ) );
}
QTime TaskGeneralPanelImpl::startTime() const
{
QTime t = scheduleStartTime->time();
t.setHMS( t.hour(), t.minute(), 0 );
return t;
}
QTime TaskGeneralPanelImpl::endTime()
{
QTime t = scheduleEndTime->time();
t.setHMS( t.hour(), t.minute(), 0 );
return t;
}
QDate TaskGeneralPanelImpl::startDate()
{
return scheduleStartDate->date();
}
QDate TaskGeneralPanelImpl::endDate()
{
return scheduleEndDate->date();
}
void TaskGeneralPanelImpl::setStartDateTime( const QDateTime &dt )
{
setStartDate(dt.date());
setStartTime(dt.time());
}
void TaskGeneralPanelImpl::setEndDateTime( const QDateTime &dt )
{
setEndDate(dt.date());
setEndTime(dt.time());
}
void TaskGeneralPanelImpl::setStartDate( const QDate &date )
{
scheduleStartDate->setDate(date);
}
void TaskGeneralPanelImpl::setEndDate( const QDate &date )
{
scheduleEndDate->setDate(date);
}
void TaskGeneralPanelImpl::setRisktype( int r )
{
risk->setCurrentIndex(r);
}
int TaskGeneralPanelImpl::risktype() const
{
return risk->currentIndex();
}
Calendar *TaskGeneralPanelImpl::calendar() const
{
return m_calendars.value( calendarCombo->currentIndex() );
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskprogressdialog.cpp b/src/libs/ui/kpttaskprogressdialog.cpp
index 2b7ba9f6..50d034d9 100644
--- a/src/libs/ui/kpttaskprogressdialog.cpp
+++ b/src/libs/ui/kpttaskprogressdialog.cpp
@@ -1,80 +1,81 @@
/* This file is part of the KDE project
Copyright (C) 2005-2010 Dag Andersen <danders@get2net.dk>
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 "kpttaskprogressdialog.h"
#include "kpttaskprogresspanel.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptnode.h"
#include <KLocalizedString>
namespace KPlato
{
TaskProgressDialog::TaskProgressDialog(Task &task, ScheduleManager *sm, StandardWorktime *workTime, QWidget *p)
: KoDialog( p),
m_node( &task )
{
setCaption( i18n("Task Progress") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
m_panel = new TaskProgressPanel(task, sm, workTime, this);
setMainWidget(m_panel);
enableButtonOk(false);
connect(m_panel, &TaskProgressPanelImpl::changed, this, &TaskProgressDialog::slotChanged);
Project *proj = static_cast<Project*>( task.projectNode() );
if ( proj ) {
connect(proj, &Project::nodeRemoved, this, &TaskProgressDialog::slotNodeRemoved);
}
}
void TaskProgressDialog::slotNodeRemoved( Node *node )
{
if ( m_node == node ) {
reject();
}
}
void TaskProgressDialog::slotChanged() {
enableButtonOk(true);
}
MacroCommand *TaskProgressDialog::buildCommand() {
MacroCommand *m = new MacroCommand(kundo2_i18n("Modify Task Progress"));
bool modified = false;
MacroCommand *cmd = m_panel->buildCommand();
if (cmd) {
m->addCommand(cmd);
modified = true;
}
if (!modified) {
delete m;
return 0;
}
return m;
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskprogresspanel.cpp b/src/libs/ui/kpttaskprogresspanel.cpp
index e12cf065..603a7835 100644
--- a/src/libs/ui/kpttaskprogresspanel.cpp
+++ b/src/libs/ui/kpttaskprogresspanel.cpp
@@ -1,437 +1,438 @@
/* This file is part of the KDE project
Copyright (C) 2004 - 2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kpttaskprogresspanel.h"
#include "kptusedefforteditor.h"
#include <KoIcon.h>
#include <QDate>
#include <QDateTime>
#include <KLocalizedString>
#include "kpttask.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptresource.h"
#include "kptdurationspinbox.h"
#include "kptschedule.h"
#include "kptproject.h"
#include "kptdebug.h"
namespace KPlato
{
//-----------------
TaskProgressPanel::TaskProgressPanel( Task &task, ScheduleManager *sm, StandardWorktime *workTime, QWidget *parent )
: TaskProgressPanelImpl( task, parent )
{
debugPlan;
started->setChecked(m_completion.isStarted());
finished->setChecked(m_completion.isFinished());
startTime->setDateTime(m_completion.startTime());
finishTime->setDateTime(m_completion.finishTime());
finishTime->setMinimumDateTime( qMax( startTime->dateTime(), QDateTime(m_completion.entryDate(), QTime(), Qt::LocalTime) ) );
if (workTime) {
debugPlan<<"daylength="<<workTime->durationDay().hours();
m_dayLength = workTime->durationDay().hours();
setEstimateScales(m_dayLength);
}
scheduledEffort = task.estimate()->expectedValue();
setYear( QDate::currentDate().year() );
if ( m_completion.usedEffortMap().isEmpty() || m_task.requests().isEmpty() ) {
foreach ( ResourceGroupRequest *g, m_task.requests().requests() ) {
foreach ( ResourceRequest *r, g->resourceRequests() ) {
m_completion.addUsedEffort( r->resource() );
}
}
}
if ( m_completion.isStarted() ) {
tabWidget->setCurrentWidget( completionTab );
}
enableWidgets();
started->setFocus();
connect( weekNumber, SIGNAL(currentIndexChanged(int)), SLOT(slotWeekNumberChanged(int)) );
connect( addResource, &QAbstractButton::clicked, this, &TaskProgressPanel::slotAddResource );
connect( addEntryBtn, &QAbstractButton::clicked, entryTable, &CompletionEntryEditor::addEntry );
connect( removeEntryBtn, &QAbstractButton::clicked, entryTable, &CompletionEntryEditor::removeEntry );
entryTable->model()->setManager( sm );
entryTable->model()->setTask( &task );
entryTable->setCompletion( &m_completion );
connect( entryTable, &CompletionEntryEditor::rowInserted, this, &TaskProgressPanel::slotEntryAdded );
resourceTable->setProject( static_cast<Project*>( task.projectNode() ) );
resourceTable->setCompletion( &m_completion );
slotWeekNumberChanged( weekNumber->currentIndex() );
addResource->setEnabled( resourceTable->hasFreeResources() );
//resourceTable->resizeColumnsToContents();
connect(started, &QAbstractButton::toggled, this, &TaskProgressPanelImpl::slotStartedChanged);
connect(started, &QAbstractButton::toggled, this, &TaskProgressPanelImpl::slotChanged);
connect(finished, &QAbstractButton::toggled, this, &TaskProgressPanelImpl::slotFinishedChanged);
connect(finished, &QAbstractButton::toggled, this, &TaskProgressPanelImpl::slotChanged);
connect(startTime, &QDateTimeEdit::dateTimeChanged, this, &TaskProgressPanelImpl::slotChanged);
connect(startTime, &QDateTimeEdit::dateTimeChanged, this, &TaskProgressPanelImpl::slotStartTimeChanged);
connect(finishTime, &QDateTimeEdit::dateTimeChanged, this, &TaskProgressPanelImpl::slotChanged);
connect(finishTime, &QDateTimeEdit::dateTimeChanged, this, &TaskProgressPanelImpl::slotFinishTimeChanged);
}
MacroCommand *TaskProgressPanel::buildCommand()
{
Project *project = dynamic_cast<Project*>( m_task.projectNode() );
if ( project == 0 ) {
return 0;
}
return buildCommand( *project, m_original, m_completion );
}
MacroCommand *TaskProgressPanel::buildCommand( const Project &project, Completion &org, Completion &curr )
{
MacroCommand *cmd = 0;
KUndo2MagicString c = kundo2_i18n("Modify task completion");
if ( org.entrymode() != curr.entrymode() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionEntrymodeCmd(org, curr.entrymode() ) );
}
if ( org.startTime() != curr.startTime() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionStartTimeCmd(org, curr.startTime() ) );
}
if ( org.finishTime() != curr.finishTime() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionFinishTimeCmd(org, curr.finishTime() ) );
}
if ( org.isStarted() != curr.isStarted() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionStartedCmd(org, curr.isStarted() ) );
}
if ( org.isFinished() != curr.isFinished() ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new ModifyCompletionFinishedCmd(org, curr.isFinished() ) );
}
QList<QDate> orgdates = org.entries().keys();
QList<QDate> currdates = curr.entries().keys();
foreach ( const QDate &d, orgdates ) {
if ( currdates.contains( d ) ) {
if ( curr.entry( d ) == org.entry( d ) ) {
continue;
}
if ( cmd == 0 ) cmd = new MacroCommand( c );
debugPlan<<"modify entry "<<d;
Completion::Entry *e = new Completion::Entry( *( curr.entry( d ) ) );
cmd->addCommand( new ModifyCompletionEntryCmd(org, d, e ) );
} else {
if ( cmd == 0 ) cmd = new MacroCommand( c );
debugPlan<<"remove entry "<<d;
cmd->addCommand( new RemoveCompletionEntryCmd(org, d ) );
}
}
foreach ( const QDate &d, currdates ) {
if ( ! orgdates.contains( d ) ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
Completion::Entry *e = new Completion::Entry( * ( curr.entry( d ) ) );
debugPlan<<"add entry "<<d<<e;
cmd->addCommand( new AddCompletionEntryCmd(org, d, e ) );
}
}
const Completion::ResourceUsedEffortMap &map = curr.usedEffortMap();
Completion::ResourceUsedEffortMap::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
Resource *r = project.findResource(it.key()->id());
if ( r == 0 ) {
warnPlan<<"Can't find resource:"<<it.key()->id()<<it.key()->name();
continue;
}
Completion::UsedEffort *ue = map[ r ];
if ( ue == 0 ) {
continue;
}
if ( org.usedEffort( r ) == 0 || *ue != *(org.usedEffort( r )) ) {
if ( cmd == 0 ) cmd = new MacroCommand( c );
cmd->addCommand( new AddCompletionUsedEffortCmd( org, r, new Completion::UsedEffort( *ue ) ) );
}
}
return cmd;
}
void TaskProgressPanel::setEstimateScales( int day )
{
QVariantList lst;
lst << QVariant( day );
// remainingEffort->setScales( lst );
// remainingEffort->setFieldScale(0, day);
// remainingEffort->setFieldRightscale(0, day);
// remainingEffort->setFieldLeftscale(1, day);
// actualEffort->setScales( QVariant( lst ) );
/* actualEffort->setFieldScale(0, day);
actualEffort->setFieldRightscale(0, day);
actualEffort->setFieldLeftscale(1, day);*/
}
void TaskProgressPanel::slotWeekNumberChanged( int index )
{
debugPlan<<index<<","<<m_weekOffset;
QDate date = QDate( m_year, 1, 1 ).addDays( Qt::Monday - QDate( m_year, 1, 1 ).dayOfWeek() );
date = date.addDays( index * 7 );
resourceTable->setCurrentMonday( date );
}
void TaskProgressPanel::slotAddResource()
{
debugPlan;
resourceTable->addResource();
addResource->setEnabled( resourceTable->hasFreeResources() );
}
void TaskProgressPanel::slotEntryAdded( const QDate &date )
{
debugPlan<<date;
}
//-------------------------------------
TaskProgressPanelImpl::TaskProgressPanelImpl( Task &task, QWidget *parent )
: QWidget(parent),
m_task(task),
m_original( task.completion() ),
m_completion( m_original ),
m_dayLength(24),
m_firstIsPrevYear( false ),
m_lastIsNextYear( false )
{
setupUi(this);
addEntryBtn->setIcon(koIcon("list-add"));
removeEntryBtn->setIcon(koIcon("list-remove"));
connect(entryTable, &CompletionEntryEditor::selectedItemsChanged, this, &TaskProgressPanelImpl::slotSelectionChanged );
removeEntryBtn->setEnabled( false );
editmode->setCurrentIndex( m_original.entrymode() - 1 );
connect( editmode, SIGNAL(currentIndexChanged(int)), SLOT(slotEditmodeChanged(int)) );
connect( editmode, SIGNAL(activated(int)), SLOT(slotChanged()) );
connect(resourceTable, &UsedEffortEditor::changed, this, &TaskProgressPanelImpl::slotChanged );
connect(resourceTable, &UsedEffortEditor::resourceAdded, this, &TaskProgressPanelImpl::slotChanged );
connect(entryTable, &CompletionEntryEditor::changed, this, &TaskProgressPanelImpl::slotChanged );
connect(entryTable, &CompletionEntryEditor::rowInserted, this, &TaskProgressPanelImpl::slotChanged );
connect(entryTable, &CompletionEntryEditor::changed, this, &TaskProgressPanelImpl::slotEntryChanged );
connect(entryTable, &CompletionEntryEditor::rowInserted, this, &TaskProgressPanelImpl::slotEntryChanged );
connect(entryTable, &CompletionEntryEditor::rowRemoved, this, &TaskProgressPanelImpl::slotEntryChanged );
connect( prevWeekBtn, &QAbstractButton::clicked, this, &TaskProgressPanelImpl::slotPrevWeekBtnClicked );
connect( nextWeekBtn, &QAbstractButton::clicked, this, &TaskProgressPanelImpl::slotNextWeekBtnClicked );
connect ( ui_year, SIGNAL(valueChanged(int)), SLOT(slotFillWeekNumbers(int)) );
int y = 0;
int wn = QDate::currentDate().weekNumber( &y );
setYear( y );
weekNumber->setCurrentIndex( wn - m_weekOffset );
}
void TaskProgressPanelImpl::slotChanged() {
emit changed();
}
void TaskProgressPanelImpl::slotEditmodeChanged( int idx )
{
m_completion.setEntrymode( static_cast<Completion::Entrymode>( idx + 1 ) );
entryTable->model()->slotDataChanged();
enableWidgets();
}
void TaskProgressPanelImpl::slotStartedChanged(bool state) {
m_completion.setStarted( state );
if (state) {
QTime t = QTime::currentTime();
t.setHMS( t.hour(), t.minute(), 0 );
m_completion.setStartTime( QDateTime(QDate::currentDate(), t, Qt::LocalTime) );
startTime->setDateTime( m_completion.startTime() );
slotCalculateEffort();
}
enableWidgets();
}
void TaskProgressPanelImpl::setFinished() {
QTime t = QTime::currentTime();
t.setHMS( t.hour(), t.minute(), 0 );
finishTime->setDateTime( QDateTime(QDate::currentDate(), t, Qt::LocalTime) );
slotFinishTimeChanged( finishTime->dateTime() );
}
void TaskProgressPanelImpl::slotFinishedChanged(bool state) {
debugPlan<<state;
m_completion.setFinished( state );
if (state) {
debugPlan<<state;
setFinished();
debugPlan<<finishTime->dateTime();
slotCalculateEffort();
}
enableWidgets();
}
void TaskProgressPanelImpl::slotFinishTimeChanged( const QDateTime &dt )
{
if ( ! m_completion.isFinished() ) {
return;
}
m_completion.setFinishTime( dt );
if ( m_completion.percentFinished() < 100 ) {
m_completion.setPercentFinished( dt.date(), 100 );
}
entryTable->setCompletion( &m_completion ); // for refresh
}
void TaskProgressPanelImpl::slotStartTimeChanged( const QDateTime &dt )
{
m_completion.setStartTime( dt );
finishTime->setMinimumDateTime( qMax( startTime->dateTime(), QDateTime(m_completion.entryDate(), QTime(), Qt::LocalTime) ) );
}
void TaskProgressPanelImpl::slotEntryChanged()
{
finishTime->setMinimumDateTime( qMax( startTime->dateTime(), QDateTime(m_completion.entryDate(), QTime(), Qt::LocalTime) ) );
}
void TaskProgressPanelImpl::enableWidgets() {
editmode->setEnabled( !finished->isChecked() );
started->setEnabled(!finished->isChecked());
finished->setEnabled(started->isChecked());
finishTime->setEnabled(finished->isChecked());
startTime->setEnabled(started->isChecked() && !finished->isChecked());
addEntryBtn->setEnabled( started->isChecked() && !finished->isChecked() );
removeEntryBtn->setEnabled( ! entryTable->selectionModel()->selectedIndexes().isEmpty() && started->isChecked() && ! finished->isChecked() );
if ( finished->isChecked() ) {
for ( int i = 0; i < entryTable->model()->columnCount(); ++i ) {
entryTable->model()->setFlags( i, Qt::NoItemFlags );
}
}
resourceTable->model()->setReadOnly( ( ! started->isChecked() ) || finished->isChecked() || m_completion.entrymode() != Completion::EnterEffortPerResource );
}
void TaskProgressPanelImpl::slotPercentFinishedChanged( int ) {
slotCalculateEffort();
}
void TaskProgressPanelImpl::slotCalculateEffort()
{
}
void TaskProgressPanelImpl::slotPrevWeekBtnClicked()
{
debugPlan;
int i = weekNumber->currentIndex();
if ( i == 0 ) {
debugPlan<<i;
int decr = m_firstIsPrevYear ? 2 : 1;
setYear( ui_year->value() - 1 );
if ( m_lastIsNextYear ) {
decr = 2;
}
weekNumber->setCurrentIndex( weekNumber->count() - decr );
} else {
weekNumber->setCurrentIndex( i - 1 );
}
}
void TaskProgressPanelImpl::slotNextWeekBtnClicked()
{
int i = weekNumber->currentIndex();
debugPlan<<i<<weekNumber->count();
if ( i == weekNumber->count() - 1 ) {
debugPlan<<i;
int index = m_lastIsNextYear ? 1 : 0;
setYear( ui_year->value() + 1 );
if ( m_firstIsPrevYear ) {
index = 1;
}
weekNumber->setCurrentIndex( index );
} else {
weekNumber->setCurrentIndex( i + 1 );
}
}
void TaskProgressPanelImpl::setYear( int year )
{
debugPlan;
ui_year->setValue( year );
}
void TaskProgressPanelImpl::slotFillWeekNumbers( int year )
{
debugPlan;
weekNumber->clear();
m_year = year;
m_weekOffset = 1;
int y = 0;
QDate date( year, 1, 1 );
int wn = date.weekNumber( &y );
m_firstIsPrevYear = false;
debugPlan<<date<<wn<<y<<year;
if ( y < year ) {
weekNumber->addItem( i18nc( "Week number (year)", "Week %1 (%2)", wn, y ) );
m_weekOffset = 0;
m_firstIsPrevYear = true;
debugPlan<<"Added last week of prev year";
}
for ( int i=1; i <= 52; ++i ) {
weekNumber->addItem( i18nc( "Week number", "Week %1", i ) );
}
date = QDate( year, 12, 31 );
wn = date.weekNumber( &y );
debugPlan<<date<<wn<<y<<year;
m_lastIsNextYear = false;
if ( wn == 53 ) {
weekNumber->addItem( i18nc( "Week number", "Week %1", wn ) );
} else if ( wn == 1 ) {
weekNumber->addItem( i18nc( "Week number (year)", "Week %1 (%2)", wn, y ) );
m_lastIsNextYear = true;
}
}
void TaskProgressPanelImpl::slotSelectionChanged( const QItemSelection &sel )
{
removeEntryBtn->setEnabled( ! sel.isEmpty() && started->isChecked() && ! finished->isChecked() );
}
} //KPlato namespace
diff --git a/src/libs/ui/kpttaskstatusview.cpp b/src/libs/ui/kpttaskstatusview.cpp
index e74e1b6b..65510372 100644
--- a/src/libs/ui/kpttaskstatusview.cpp
+++ b/src/libs/ui/kpttaskstatusview.cpp
@@ -1,1496 +1,1497 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kpttaskstatusview.h"
#include "kpttaskstatusmodel.h"
#include "kptglobal.h"
#include "kptlocale.h"
#include "kptcommonstrings.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kpteffortcostmap.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoXmlReader.h>
#include "KoDocument.h"
#include "KoPageLayoutWidget.h"
#include <QDragMoveEvent>
#include <QModelIndex>
#include <QVBoxLayout>
#include <QPushButton>
#include <QItemSelection>
#include <QApplication>
#include <QResizeEvent>
#include <QTimer>
#include <QAction>
#include <KChartChart>
#include <KChartAbstractCoordinatePlane>
#include <KChartBarDiagram>
#include <KChartLineDiagram>
#include <KChartCartesianAxis>
#include <KChartCartesianCoordinatePlane>
#include <KChartLegend>
#include <KChartBackgroundAttributes>
#include <KChartGridAttributes>
using namespace KChart;
namespace KPlato
{
TaskStatusTreeView::TaskStatusTreeView( QWidget *parent )
: DoubleTreeViewBase( parent )
{
setContextMenuPolicy( Qt::CustomContextMenu );
TaskStatusItemModel *m = new TaskStatusItemModel( this );
setModel( m );
//setSelectionBehavior( QAbstractItemView::SelectItems );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setStretchLastSection( false );
createItemDelegates( m );
QList<int> lst1; lst1 << 1 << -1; // only display column 0 (NodeName) in left view
masterView()->setDefaultColumns( QList<int>() << 0 );
QList<int> show;
show << NodeModel::NodeCompleted
<< NodeModel::NodeActualEffort
<< NodeModel::NodeRemainingEffort
<< NodeModel::NodePlannedEffort
<< NodeModel::NodePlannedCost
<< NodeModel::NodeActualCost
<< NodeModel::NodeStatus
<< NodeModel::NodeActualStart
<< NodeModel::NodeActualFinish
<< NodeModel::NodeStatusNote;
QList<int> lst2;
for ( int i = 0; i < m->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
hideColumns( lst1, lst2 );
slaveView()->setDefaultColumns( show );
}
int TaskStatusTreeView::weekday() const
{
return model()->weekday();
}
void TaskStatusTreeView::setWeekday( int day )
{
model()->setWeekday( day );
refresh();
}
int TaskStatusTreeView::defaultPeriodType() const
{
return TaskStatusItemModel::UseCurrentDate;
}
int TaskStatusTreeView::periodType() const
{
return model()->periodType();
}
void TaskStatusTreeView::setPeriodType( int type )
{
model()->setPeriodType( type );
refresh();
}
int TaskStatusTreeView::period() const
{
return model()->period();
}
void TaskStatusTreeView::setPeriod( int days )
{
model()->setPeriod( days );
refresh();
}
TaskStatusItemModel *TaskStatusTreeView::model() const
{
return static_cast<TaskStatusItemModel*>( DoubleTreeViewBase::model() );
}
Project *TaskStatusTreeView::project() const
{
return model()->project();
}
void TaskStatusTreeView::setProject( Project *project )
{
model()->setProject( project );
}
void TaskStatusTreeView::dragMoveEvent(QDragMoveEvent */*event*/)
{
/* if (dragDropMode() == InternalMove
&& (event->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 ) {
errorPlan<<"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;
}*/
}
//-----------------------------------
TaskStatusView::TaskStatusView(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent),
m_id( -1 )
{
debugPlan<<"-------------------- creating TaskStatusView -------------------";
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new TaskStatusTreeView( this );
connect(this, &ViewBase::expandAll, m_view, &DoubleTreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view, &DoubleTreeViewBase::slotCollapse);
l->addWidget( m_view );
setupGui();
connect( model(), &ItemModelBase::executeCommand, doc, &KoDocument::addCommand );
connect( m_view, SIGNAL(contextMenuRequested(QModelIndex,QPoint,QModelIndexList)), SLOT(slotContextMenuRequested(QModelIndex,QPoint)) );
connect( m_view, &DoubleTreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Task Status View</title>"
"<para>"
"The Task Status View is used to inspect task progress information. "
"The tasks are divided into groups dependent on the task status:"
"<list>"
"<item>Not Started Tasks that should have been started by now.</item>"
"<item>Running Tasks that has been started, but not yet finished.</item>"
"<item>Finished Tasks that where finished in this period.</item>"
"<item>Next Period Tasks that is scheduled to be started in the next period.</item>"
"</list>"
"The time period is configurable."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Task_Status_View")));
}
void TaskStatusView::updateReadWrite( bool rw )
{
m_view->setReadWrite( rw );
}
void TaskStatusView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan;
if (!sm && scheduleManager()) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
bool tryexpand = sm && !scheduleManager();
bool expand = sm && scheduleManager() && sm != scheduleManager();
QDomDocument doc;
if (expand) {
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
m_view->masterView()->saveExpanded(element);
}
ViewBase::setScheduleManager(sm);
static_cast<TaskStatusItemModel*>( m_view->model() )->setScheduleManager( sm );
if (expand) {
m_view->masterView()->doExpand(doc);
} else if (tryexpand) {
m_view->masterView()->doExpand(m_domdoc);
}
}
Node *TaskStatusView::currentNode() const
{
return m_view->model()->node( m_view->selectionModel()->currentIndex() );
}
void TaskStatusView::setProject( Project *project )
{
m_project = project;
m_view->model()->setProject( m_project );
}
void TaskStatusView::draw( Project &project )
{
setProject( &project );
}
void TaskStatusView::setGuiActive( bool activate )
{
debugPlan<<activate;
// updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
}
void TaskStatusView::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
debugPlan<<index<<pos;
if ( ! index.isValid() ) {
slotHeaderContextMenuRequested( pos );
return;
}
m_view->setContextMenuIndex(index);
Node *node = m_view->model()->node( index );
if ( node == 0 ) {
slotHeaderContextMenuRequested( pos );
m_view->setContextMenuIndex(QModelIndex());
return;
}
slotContextMenuRequested( node, pos );
m_view->setContextMenuIndex(QModelIndex());
}
void TaskStatusView::slotContextMenuRequested( Node *node, const QPoint& pos )
{
debugPlan<<node->name()<<" :"<<pos;
QString name;
switch ( node->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;
}
debugPlan<<name;
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
emit requestPopupMenu( name, pos );
}
void TaskStatusView::setupGui()
{
// Add the context menu actions for the view options
connect(m_view->actionSplitView(), &QAction::triggered, this, &TaskStatusView::slotSplitView);
addContextAction( m_view->actionSplitView() );
createOptionActions(ViewBase::OptionAll);
}
void TaskStatusView::slotSplitView()
{
debugPlan;
m_view->setViewSplitMode( ! m_view->isViewSplit() );
emit optionsModified();
}
void TaskStatusView::slotRefreshView()
{
model()->refresh();
}
void TaskStatusView::slotOptions()
{
debugPlan;
TaskStatusViewSettingsDialog *dlg = new TaskStatusViewSettingsDialog( this, m_view, this );
dlg->addPrintingOptions(sender()->objectName() == "print options");
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool TaskStatusView::loadContext( const KoXmlElement &context )
{
debugPlan;
ViewBase::loadContext( context );
m_view->setPeriod( context.attribute( "period", QString("%1").arg( m_view->defaultPeriod() ) ).toInt() );
m_view->setPeriodType( context.attribute( "periodtype", QString("%1").arg( m_view->defaultPeriodType() ) ).toInt() );
m_view->setWeekday( context.attribute( "weekday", QString("%1").arg( m_view->defaultWeekday() ) ).toInt() );
return m_view->loadContext( model()->columnMap(), context );
}
void TaskStatusView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
context.setAttribute( "period", QString::number(m_view->period()) );
context.setAttribute( "periodtype", QString::number(m_view->periodType()) );
context.setAttribute( "weekday", QString::number(m_view->weekday()) );
m_view->saveContext( model()->columnMap(), context );
}
KoPrintJob *TaskStatusView::createPrintJob()
{
return m_view->createPrintJob( this );
}
//------------------------------------------------
TaskStatusViewSettingsPanel::TaskStatusViewSettingsPanel( TaskStatusTreeView *view, QWidget *parent )
: QWidget( parent ),
m_view( view )
{
setupUi( this );
QStringList lst;
QLocale locale;
for ( int i = 1; i <= 7; ++i ) {
lst << locale.dayName( i, QLocale::ShortFormat );
}
weekdays->addItems( lst );
period->setValue( view->period() );
switch ( view->periodType() ) {
case TaskStatusItemModel::UseCurrentDate: useCurrentDate->setChecked( true ); break;
case TaskStatusItemModel::UseWeekday: useWeekday->setChecked( true ); break;
default: break;
}
weekdays->setCurrentIndex( m_view->weekday() - 1 );
connect( period, SIGNAL(valueChanged(int)), SIGNAL(changed()) );
connect( useWeekday, &QAbstractButton::toggled, this, &TaskStatusViewSettingsPanel::changed );
connect( useCurrentDate, &QAbstractButton::toggled, this, &TaskStatusViewSettingsPanel::changed );
connect( weekdays, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()) );
}
void TaskStatusViewSettingsPanel::slotOk()
{
if ( period->value() != m_view->period() ) {
m_view->setPeriod( period->value() );
}
if ( weekdays->currentIndex() != m_view->weekday() - 1 ) {
m_view->setWeekday( weekdays->currentIndex() + 1 );
}
if ( useCurrentDate->isChecked() && m_view->periodType() != TaskStatusItemModel::UseCurrentDate ) {
m_view->setPeriodType( TaskStatusItemModel::UseCurrentDate );
} else if ( useWeekday->isChecked() && m_view->periodType() != TaskStatusItemModel::UseWeekday ) {
m_view->setPeriodType( TaskStatusItemModel::UseWeekday );
}
}
void TaskStatusViewSettingsPanel::setDefault()
{
period->setValue( m_view->defaultPeriod() );
switch ( m_view->defaultPeriodType() ) {
case TaskStatusItemModel::UseCurrentDate: useCurrentDate->setChecked( true ); break;
case TaskStatusItemModel::UseWeekday: useWeekday->setChecked( true ); break;
default: break;
}
weekdays->setCurrentIndex( m_view->defaultWeekday() - 1 );
}
TaskStatusViewSettingsDialog::TaskStatusViewSettingsDialog( ViewBase *view, TaskStatusTreeView *treeview, QWidget *parent )
: SplitItemViewSettupDialog( view, treeview, parent )
{
TaskStatusViewSettingsPanel *panel = new TaskStatusViewSettingsPanel( treeview );
KPageWidgetItem *page = insertWidget( 0, panel, i18n( "General" ), i18n( "General Settings" ) );
setCurrentPage( page );
//connect( panel, SIGNAL(changed(bool)), this, SLOT(enableButtonOk(bool)) );
connect( this, &QDialog::accepted, panel, &TaskStatusViewSettingsPanel::slotOk );
connect( button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, panel, &TaskStatusViewSettingsPanel::setDefault );
}
//-----------------------------------
ProjectStatusView::ProjectStatusView(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent),
m_project( 0 )
{
debugPlan<<"-------------------- creating ProjectStatusView -------------------";
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new PerformanceStatusBase( this );
l->addWidget( m_view );
setupGui();
connect( m_view, &QWidget::customContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Project Performance View</title>"
"<para>"
"Displays performance data aggregated to the project level."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Project_Performance_View")));
}
void ProjectStatusView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan;
m_view->setScheduleManager( sm );
m_view->model()->clearNodes();
if ( m_project ) {
m_view->setNodes( QList<Node*>() << m_project );
}
}
void ProjectStatusView::setProject( Project *project )
{
m_project = project;
m_view->model()->clearNodes();
m_view->setProject( project );
}
void ProjectStatusView::setGuiActive( bool activate )
{
debugPlan<<activate;
// updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
}
void ProjectStatusView::setupGui()
{
// Add the context menu actions for the view options
createOptionActions(ViewBase::OptionPrint | ViewBase::OptionPrintPreview | ViewBase::OptionPrintConfig | ViewBase::OptionViewConfig);
}
void ProjectStatusView::slotOptions()
{
ProjectStatusViewSettingsDialog *dlg = new ProjectStatusViewSettingsDialog( this, m_view, this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool ProjectStatusView::loadContext( const KoXmlElement &context )
{
debugPlan;
ViewBase::loadContext( context );
return m_view->loadContext( context );
}
void ProjectStatusView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
m_view->saveContext( context );
}
KoPrintJob *ProjectStatusView::createPrintJob()
{
return m_view->createPrintJob( this );
}
//----------------------
PerformanceStatusPrintingDialog::PerformanceStatusPrintingDialog( ViewBase *view, PerformanceStatusBase *chart, Project *project )
: PrintingDialog( view ),
m_chart( chart ),
m_project( project )
{
}
int PerformanceStatusPrintingDialog::documentLastPage() const
{
return documentFirstPage();
}
QList<QWidget*> PerformanceStatusPrintingDialog::createOptionWidgets() const
{
QList<QWidget*> lst;
lst << createPageLayoutWidget();
lst += PrintingDialog::createOptionWidgets();
return lst;
}
void PerformanceStatusPrintingDialog::printPage( int page, QPainter &painter )
{
//debugPlan<<page<<printer().pageRect()<<printer().paperRect()<<printer().margins()<<printer().fullPage();
painter.save();
QRect rect = printer().pageRect();
rect.moveTo( 0, 0 ); // the printer already has margins set
QRect header = headerRect();
QRect footer = footerRect();
paintHeaderFooter( painter, printingOptions(), page, *m_project );
int gap = 8;
if ( header.isValid() ) {
rect.setTop( header.height() + gap );
}
if ( footer.isValid() ) {
rect.setBottom( rect.bottom() - footer.height() - gap );
}
QSize s = m_chart->ui_chart->geometry().size();
qreal r = (qreal)s.width() / (qreal)s.height();
if ( rect.height() > rect.width() && r > 0.0 ) {
rect.setHeight( rect.width() / r );
}
debugPlan<<s<<rect;
m_chart->ui_chart->paint( &painter, rect );
painter.restore();
}
//-----------------------------------
PerformanceStatusBase::PerformanceStatusBase( QWidget *parent )
: QWidget( parent ),
m_project( 0 ),
m_manager( 0 )
{
setupUi( this );
ui_performancetable->setModel( new PerformanceDataCurrentDateModel( this ) );
BackgroundAttributes backgroundAttrs( ui_chart->backgroundAttributes() );
backgroundAttrs.setVisible( true );
backgroundAttrs.setBrush( Qt::white );
ui_chart->setBackgroundAttributes( backgroundAttrs );
m_legend = new Legend( ui_chart );
ui_chart->replaceLegend( m_legend );
m_legend->setObjectName( "Chart legend" );
backgroundAttrs = m_legend->backgroundAttributes();
m_legend->setBackgroundAttributes( backgroundAttrs );
backgroundAttrs.setVisible( true );
backgroundAttrs.setBrush( Qt::white );
m_legend->setPosition( Position::East );
//m_legend->setAlignment( (Qt::Alignment)(Qt::AlignTop | Qt::AlignCenter) );
m_legenddiagram.setModel( &m_chartmodel );
m_legenddiagram.setObjectName( "Legend diagram" );
m_legend->setDiagram( &m_legenddiagram );
// get rid of the default coordinate plane
AbstractCoordinatePlane *p = ui_chart->coordinatePlane();
ui_chart->takeCoordinatePlane( p );
delete p;
createBarChart();
createLineChart();
setupChart();
#ifdef PLAN_CHART_DEBUG
ui_tableView->setModel( &m_chartmodel );
#endif
connect(&m_chartmodel, &QAbstractItemModel::modelReset, this, &PerformanceStatusBase::slotUpdate);
setContextMenuPolicy ( Qt::DefaultContextMenu );
}
void PerformanceStatusBase::setChartInfo( const PerformanceChartInfo &info )
{
if ( info != m_chartinfo ) {
m_chartinfo = info;
setupChart();
}
}
void PerformanceStatusBase::refreshChart()
{
ui_performancetable->resize( QSize() );
// NOTE: Force grid/axis recalculation, couldn't find a better way :(
QResizeEvent event( ui_chart->size(), QSize() );
QApplication::sendEvent( ui_chart, &event );
m_legend->forceRebuild();
}
void PerformanceStatusBase::createBarChart()
{
m_barchart.effortplane = new CartesianCoordinatePlane( ui_chart );
m_barchart.effortplane->setObjectName( "Bar chart, Effort" );
m_barchart.costplane = new CartesianCoordinatePlane( ui_chart );
m_barchart.costplane->setObjectName( "Bar chart, Cost" );
BarDiagram *effortdiagram = new BarDiagram( ui_chart, m_barchart.effortplane );
effortdiagram->setObjectName( "Effort diagram" );
m_barchart.dateaxis = new CartesianAxis();
m_barchart.dateaxis->setPosition( CartesianAxis::Bottom );
m_barchart.effortaxis = new CartesianAxis( effortdiagram );
m_barchart.effortaxis->setPosition( CartesianAxis::Right );
effortdiagram->addAxis( m_barchart.effortaxis );
m_barchart.effortplane->addDiagram( effortdiagram );
// Hide cost in effort diagram
effortdiagram->setHidden( 0, true );
effortdiagram->setHidden( 1, true );
effortdiagram->setHidden( 2, true );
m_barchart.effortproxy.setZeroColumns( QList<int>() << 0 << 1 << 2 );
m_barchart.effortproxy.setSourceModel( &m_chartmodel );
effortdiagram->setModel( &(m_barchart.effortproxy) );
BarDiagram *costdiagram = new BarDiagram( ui_chart, m_barchart.costplane );
costdiagram->setObjectName( "Cost diagram" );
m_barchart.costaxis = new CartesianAxis( costdiagram );
m_barchart.costaxis->setPosition( CartesianAxis::Left );
costdiagram->addAxis( m_barchart.costaxis );
m_barchart.costplane->addDiagram( costdiagram );
// Hide effort in cost diagram
costdiagram->setHidden( 3, true );
costdiagram->setHidden( 4, true );
costdiagram->setHidden( 5, true );
m_barchart.costproxy.setZeroColumns( QList<int>() << 3 << 4 << 5 );
m_barchart.costproxy.setObjectName( "Bar: Cost" );
m_barchart.costproxy.setSourceModel( &m_chartmodel );
costdiagram->setModel( &(m_barchart.costproxy) );
m_barchart.effortdiagram = effortdiagram;
m_barchart.costdiagram = costdiagram;
m_barchart.piplane = new CartesianCoordinatePlane( ui_chart );
m_barchart.piplane->setObjectName( "Performance Indices" );
BarDiagram *pidiagram = new BarDiagram( ui_chart, m_barchart.piplane );
pidiagram->setObjectName( "PI diagram" );
m_barchart.piaxis = new CartesianAxis( pidiagram );
pidiagram->addAxis( m_barchart.piaxis );
m_barchart.piplane->addDiagram( pidiagram );
m_barchart.piproxy.setSourceModel( &m_chartmodel );
pidiagram->setModel( &( m_barchart.piproxy ) );
}
void PerformanceStatusBase::createLineChart()
{
m_linechart.effortplane = new CartesianCoordinatePlane( ui_chart );
m_linechart.effortplane->setObjectName( "Line chart, Effort" );
m_linechart.effortplane->setRubberBandZoomingEnabled( true );
m_linechart.costplane = new CartesianCoordinatePlane( ui_chart );
m_linechart.costplane->setObjectName( "Line chart, Cost" );
m_linechart.costplane->setRubberBandZoomingEnabled( true );
LineDiagram *effortdiagram = new LineDiagram( ui_chart, m_linechart.effortplane );
effortdiagram->setObjectName( "Effort diagram" );
m_linechart.dateaxis = new CartesianAxis();
m_linechart.dateaxis->setPosition( CartesianAxis::Bottom );
m_linechart.effortaxis = new CartesianAxis( effortdiagram );
m_linechart.effortaxis->setPosition( CartesianAxis::Right );
effortdiagram->addAxis( m_linechart.effortaxis );
m_linechart.effortplane->addDiagram( effortdiagram );
// Hide cost in effort diagram
effortdiagram->setHidden( 0, true );
effortdiagram->setHidden( 1, true );
effortdiagram->setHidden( 2, true );
m_linechart.effortproxy.setZeroColumns( QList<int>() << 0 << 1 << 2 );
m_linechart.effortproxy.setObjectName( "Line: Effort" );
m_linechart.effortproxy.setSourceModel( &m_chartmodel );
effortdiagram->setModel( &(m_linechart.effortproxy) );
LineDiagram *costdiagram = new LineDiagram( ui_chart, m_linechart.costplane );
costdiagram->setObjectName( "Cost diagram" );
m_linechart.costaxis = new CartesianAxis( costdiagram );
m_linechart.costaxis->setPosition( CartesianAxis::Left );
costdiagram->addAxis( m_linechart.costaxis );
m_linechart.costplane->addDiagram( costdiagram );
// Hide effort in cost diagram
costdiagram->setHidden( 3, true );
costdiagram->setHidden( 4, true );
costdiagram->setHidden( 5, true );
m_linechart.costproxy.setObjectName( "Line: Cost" );
m_linechart.costproxy.setZeroColumns( QList<int>() << 3 << 4 << 5 );
m_linechart.costproxy.setSourceModel( &m_chartmodel );
costdiagram->setModel( &(m_linechart.costproxy) );
m_linechart.effortdiagram = effortdiagram;
m_linechart.costdiagram = costdiagram;
m_linechart.piplane = new CartesianCoordinatePlane( ui_chart );
m_linechart.piplane->setObjectName( "Performance Indices" );
m_linechart.piplane->setRubberBandZoomingEnabled( true );
LineDiagram *pidiagram = new LineDiagram( ui_chart, m_linechart.piplane );
pidiagram->setObjectName( "PI diagram" );
m_linechart.piaxis = new CartesianAxis( pidiagram );
pidiagram->addAxis( m_linechart.piaxis );
m_linechart.piplane->addDiagram( pidiagram );
m_linechart.piproxy.setSourceModel( &m_chartmodel );
pidiagram->setModel( &( m_linechart.piproxy ) );
}
void PerformanceStatusBase::setupChart()
{
while ( ! ui_chart->coordinatePlanes().isEmpty() ) {
const CoordinatePlaneList &planes = ui_chart->coordinatePlanes();
ui_chart->takeCoordinatePlane(planes.last());
}
if ( m_chartinfo.showBarChart ) {
setupChart( m_barchart );
} else if ( m_chartinfo.showLineChart ) {
setupChart( m_linechart );
} else {
#ifdef PLAN_CHART_DEBUG
ui_stack->setCurrentIndex( 1 );
refreshChart();
return;
#else
setupChart( m_linechart );
#endif
}
ui_stack->setCurrentIndex( 0 );
debugPlan<<"Planes:"<<ui_chart->coordinatePlanes();
foreach ( AbstractCoordinatePlane *pl, ui_chart->coordinatePlanes() ) {
CartesianCoordinatePlane *p = dynamic_cast<CartesianCoordinatePlane*>( pl );
if ( p == 0 ) continue;
GridAttributes ga = p->globalGridAttributes();
ga.setGridVisible( p->referenceCoordinatePlane() == 0 );
p->setGlobalGridAttributes( ga );
}
m_legend->setDatasetHidden( 0, ! ( m_chartinfo.showBaseValues && m_chartinfo.showCost && m_chartinfo.showBCWSCost ) );
m_legend->setDatasetHidden( 1, ! ( m_chartinfo.showBaseValues && m_chartinfo.showCost && m_chartinfo.showBCWPCost ) );
m_legend->setDatasetHidden( 2, ! ( m_chartinfo.showBaseValues && m_chartinfo.showCost && m_chartinfo.showACWPCost ) );
m_legend->setDatasetHidden( 3, ! ( m_chartinfo.showBaseValues && m_chartinfo.showEffort && m_chartinfo.showBCWSEffort ) );
m_legend->setDatasetHidden( 4, ! ( m_chartinfo.showBaseValues && m_chartinfo.showEffort && m_chartinfo.showBCWPEffort ) );
m_legend->setDatasetHidden( 5, ! ( m_chartinfo.showBaseValues && m_chartinfo.showEffort && m_chartinfo.showACWPEffort ) );
// spi/cpi
m_legend->setDatasetHidden( 6, ! ( m_chartinfo.showIndices && m_chartinfo.showSpiCost ) );
m_legend->setDatasetHidden( 7, ! ( m_chartinfo.showIndices && m_chartinfo.showCpiCost ) );
m_legend->setDatasetHidden( 8, ! ( m_chartinfo.showIndices && m_chartinfo.showSpiEffort ) );
m_legend->setDatasetHidden( 9, ! ( m_chartinfo.showIndices && m_chartinfo.showCpiEffort ) );
setEffortValuesVisible( m_chartinfo.effortShown() );
setCostValuesVisible( m_chartinfo.costShown() );
refreshChart();
}
void PerformanceStatusBase::setEffortValuesVisible( bool visible )
{
ui_performancetable->verticalHeader()->setSectionHidden( 1, ! visible );
ui_performancetable->setMaximumHeight( ui_performancetable->sizeHint().height() );
}
void PerformanceStatusBase::setCostValuesVisible( bool visible )
{
ui_performancetable->verticalHeader()->setSectionHidden( 0, ! visible );
ui_performancetable->setMaximumHeight( ui_performancetable->sizeHint().height() );
}
void PerformanceStatusBase::setupChart( ChartContents &cc )
{
QList<int> erc, ezc, crc, czc; // sourcemodel column numbers
int effort_start_column = 3; // proxy column number
const PerformanceChartInfo &info = m_chartinfo;
debugPlan<<"cost="<<info.showCost<<"effort="<<info.showEffort;
static_cast<AbstractCartesianDiagram*>( cc.effortplane->diagram() )->takeAxis( cc.dateaxis );
static_cast<AbstractCartesianDiagram*>( cc.costplane->diagram() )->takeAxis( cc.dateaxis );
static_cast<AbstractCartesianDiagram*>( cc.piplane->diagram() )->takeAxis( cc.dateaxis );
cc.costplane->setReferenceCoordinatePlane( 0 );
if ( info.showBaseValues ) {
if ( info.showEffort ) {
// filter cost columns if cost is *not* shown, else hide them and zero out
if ( ! info.showCost ) {
erc << 0 << 1 << 2;
effort_start_column = 0; // no cost, so effort start at 0
} else {
ezc << 0 << 1 << 2;
cc.effortplane->diagram()->setHidden( 0, true );
cc.effortplane->diagram()->setHidden( 1, true );
cc.effortplane->diagram()->setHidden( 2, true );
}
// always disable spi/cpi
erc << 6 << 7 << 8 << 9;
ezc << 6 << 7 << 8 << 9;
// if cost is shown don't return a cost value or else it goes into the effort axis scale calculation
//cc.effortproxy.setZeroColumns( info.showCost ? QList<int>() << 0 << 1 << 2 : QList<int>() << 3 << 4 << 5 );
cc.effortaxis->setPosition( info.showCost ? CartesianAxis::Right : CartesianAxis::Left );
ui_chart->addCoordinatePlane( cc.effortplane );
static_cast<AbstractCartesianDiagram*>( cc.effortplane->diagram() )->addAxis( cc.dateaxis );
cc.effortplane->setGridNeedsRecalculate();
}
if ( info.showCost ) {
// Should never get any effort values in cost diagram
czc << 3 << 4 << 5;
// remove effort columns from cost if no effort is shown, else hide them
if ( ! info.showEffort ) {
crc << 3 << 4 << 5;
} else {
cc.costplane->diagram()->setHidden( 3, true );
cc.costplane->diagram()->setHidden( 4, true );
cc.costplane->diagram()->setHidden( 5, true );
}
// always disable spi/cpi
erc << 6 << 7 << 8 << 9;
ezc << 6 << 7 << 8 << 9;
cc.costplane->setReferenceCoordinatePlane( info.showEffort ? cc.effortplane : 0 );
ui_chart->addCoordinatePlane( cc.costplane );
static_cast<AbstractCartesianDiagram*>( cc.costplane->diagram() )->addAxis( cc.dateaxis );
cc.costplane->setGridNeedsRecalculate();
cc.costplane->diagram()->setHidden( 0, ! info.showBCWSCost );
cc.costplane->diagram()->setHidden( 1, ! info.showBCWPCost );
cc.costplane->diagram()->setHidden( 2, ! info.showACWPCost );
}
if ( info.showEffort ) {
cc.effortplane->diagram()->setHidden( effort_start_column, ! info.showBCWSEffort );
cc.effortplane->diagram()->setHidden( effort_start_column+1, ! info.showBCWPEffort );
cc.effortplane->diagram()->setHidden( effort_start_column+2, ! info.showACWPEffort );
cc.effortaxis->setCachedSizeDirty();
cc.effortproxy.reset();
cc.effortproxy.setZeroColumns( ezc );
cc.effortproxy.setRejectColumns( erc );
}
if ( info.showCost ) {
cc.costaxis->setCachedSizeDirty();
cc.costproxy.reset();
cc.costproxy.setZeroColumns( czc );
cc.costproxy.setRejectColumns( crc );
}
} else if ( info.showIndices ) {
cc.piaxis->setPosition( CartesianAxis::Left );
ui_chart->addCoordinatePlane( cc.piplane );
static_cast<AbstractCartesianDiagram*>( cc.piplane->diagram() )->addAxis( cc.dateaxis );
cc.piplane->setGridNeedsRecalculate();
cc.piaxis->setCachedSizeDirty();
cc.piproxy.reset();
QList<int> reject; reject << 0 << 1 << 2 << 3 << 4 << 5;
if ( ! info.showSpiCost ) {
reject << ChartItemModel::SPICost;
}
if ( ! info.showCpiCost ) {
reject << ChartItemModel::CPICost;
}
if ( ! info.showSpiEffort ) {
reject << ChartItemModel::SPIEffort;
}
if ( ! info.showCpiEffort ) {
reject << ChartItemModel::CPIEffort;
}
cc.piproxy.setRejectColumns( reject );
}
#if 0
debugPlan<<"Effort:"<<info.showEffort;
if ( info.showEffort && cc.effortproxy.rowCount() > 0 ) {
debugPlan<<"Effort:"<<info.showEffort<<"columns ="<<cc.effortproxy.columnCount()
<<"reject="<<cc.effortproxy.rejectColumns()
<<"zero="<<cc.effortproxy.zeroColumns();
int row = cc.effortproxy.rowCount()-1;
for ( int i = 0; i < cc.effortproxy.columnCount(); ++i ) {
debugPlan<<"data ("<<row<<","<<i<<":"<<cc.effortproxy.index(row,i).data().toString()<<(cc.effortplane->diagram()->isHidden(i)?"hide":"show");
}
}
debugPlan<<"Cost:"<<info.showCost;
if ( info.showCost && cc.costproxy.rowCount() > 0 ) {
debugPlan<<"Cost:"<<info.showCost<<"columns ="<<cc.costproxy.columnCount()
<<"reject="<<cc.costproxy.rejectColumns()
<<"zero="<<cc.costproxy.zeroColumns();
int row = cc.costproxy.rowCount()-1;
for ( int i = 0; i < cc.costproxy.columnCount(); ++i ) {
debugPlan<<"data ("<<row<<","<<i<<":"<<cc.costproxy.index(row,i).data().toString()<<(cc.costplane->diagram()->isHidden(i)?"hide":"show");
}
}
foreach( AbstractCoordinatePlane *p, ui_chart->coordinatePlanes() ) {
debugPlan<<p<<"references:"<<p->referenceCoordinatePlane();
foreach ( AbstractDiagram *d, p->diagrams() ) {
debugPlan<<p<<"diagram:"<<d;
}
}
#endif
}
void PerformanceStatusBase::contextMenuEvent( QContextMenuEvent *event )
{
debugPlan<<event->globalPos();
emit customContextMenuRequested( event->globalPos() );
}
void PerformanceStatusBase::slotUpdate()
{
//debugPlan;
refreshChart();
}
void PerformanceStatusBase::setScheduleManager( ScheduleManager *sm )
{
//debugPlan;
if (sm == m_manager) {
return;
}
m_manager = sm;
m_chartmodel.setScheduleManager( sm );
static_cast<PerformanceDataCurrentDateModel*>( ui_performancetable->model() )->setScheduleManager( sm );
}
void PerformanceStatusBase::setProject( Project *project )
{
if ( m_project ) {
disconnect( m_project, &Project::localeChanged, this, &PerformanceStatusBase::slotLocaleChanged );
}
m_project = project;
if ( m_project ) {
connect( m_project, &Project::localeChanged, this, &PerformanceStatusBase::slotLocaleChanged );
}
m_chartmodel.setProject( project );
static_cast<PerformanceDataCurrentDateModel*>( ui_performancetable->model() )->setProject( project );
slotLocaleChanged();
}
void PerformanceStatusBase::slotLocaleChanged()
{
debugPlan;
const QString currencySymbol = m_project->locale()->currencySymbol();
m_linechart.costaxis->setTitleText( i18nc( "Chart axis title 1=currency symbol", "Cost (%1)", currencySymbol ) );
m_linechart.effortaxis->setTitleText( i18nc( "Chart axis title", "Effort (hours)" ) );
m_barchart.costaxis->setTitleText( i18nc( "Chart axis title 1=currency symbol", "Cost (%1)", currencySymbol ) );
m_barchart.effortaxis->setTitleText( i18nc( "Chart axis title", "Effort (hours)" ) );
}
bool PerformanceStatusBase::loadContext( const KoXmlElement &context )
{
debugPlan;
m_chartinfo.showBarChart = context.attribute( "show-bar-chart", "0" ).toInt();
m_chartinfo.showLineChart = context.attribute( "show-line-chart", "1" ).toInt();
m_chartinfo.showTableView = context.attribute( "show-table-view", "0" ).toInt();
m_chartinfo.showBaseValues = context.attribute( "show-base-values", "1" ).toInt();
m_chartinfo.showIndices = context.attribute( "show-indeces", "0" ).toInt();
m_chartinfo.showCost = context.attribute( "show-cost", "1" ).toInt();
m_chartinfo.showBCWSCost = context.attribute( "show-bcws-cost", "1" ).toInt();
m_chartinfo.showBCWPCost = context.attribute( "show-bcwp-cost", "1" ).toInt();
m_chartinfo.showACWPCost = context.attribute( "show-acwp-cost", "1" ).toInt();
m_chartinfo.showEffort = context.attribute( "show-effort", "1" ).toInt();
m_chartinfo.showBCWSEffort = context.attribute( "show-bcws-effort", "1" ).toInt();
m_chartinfo.showBCWPEffort = context.attribute( "show-bcwp-effort", "1" ).toInt();
m_chartinfo.showACWPEffort = context.attribute( "show-acwp-effort", "1" ).toInt();
m_chartinfo.showSpiCost = context.attribute( "show-spi-cost", "1" ).toInt();
m_chartinfo.showCpiCost = context.attribute( "show-cpi-cost", "1" ).toInt();
m_chartinfo.showSpiEffort = context.attribute( "show-spi-effort", "1" ).toInt();
m_chartinfo.showCpiEffort = context.attribute( "show-cpi-effort", "1" ).toInt();
debugPlan<<"Cost:"<<m_chartinfo.showCost<<"bcws="<<m_chartinfo.showBCWSCost<<"bcwp="<<m_chartinfo.showBCWPCost<<"acwp="<<m_chartinfo.showACWPCost;
debugPlan<<"Effort:"<<m_chartinfo.showCost<<"bcws="<<m_chartinfo.showBCWSCost<<"bcwp="<<m_chartinfo.showBCWPCost<<"acwp="<<m_chartinfo.showACWPCost;
setupChart();
return true;
}
void PerformanceStatusBase::saveContext( QDomElement &context ) const
{
context.setAttribute( "show-bar-chart", QString::number(m_chartinfo.showBarChart) );
context.setAttribute( "show-line-chart", QString::number(m_chartinfo.showLineChart) );
context.setAttribute( "show-table-view", QString::number(m_chartinfo.showTableView ));
context.setAttribute( "show-base-values", QString::number(m_chartinfo.showBaseValues) );
context.setAttribute( "show-indeces", QString::number(m_chartinfo.showIndices) );
context.setAttribute( "show-cost", QString::number(m_chartinfo.showCost) );
context.setAttribute( "show-bcws-cost", QString::number(m_chartinfo.showBCWSCost) );
context.setAttribute( "show-bcwp-cost", QString::number(m_chartinfo.showBCWPCost) );
context.setAttribute( "show-acwp-cost", QString::number(m_chartinfo.showACWPCost) );
context.setAttribute( "show-effort", QString::number(m_chartinfo.showEffort) );
context.setAttribute( "show-bcws-effort", QString::number(m_chartinfo.showBCWSEffort) );
context.setAttribute( "show-bcwp-effort", QString::number(m_chartinfo.showBCWPEffort) );
context.setAttribute( "show-acwp-effort", QString::number(m_chartinfo.showACWPEffort) );
context.setAttribute( "show-spi-cost", QString::number(m_chartinfo.showSpiCost) );
context.setAttribute( "show-cpi-cost", QString::number(m_chartinfo.showCpiCost) );
context.setAttribute( "show-spi-effort", QString::number(m_chartinfo.showSpiEffort) );
context.setAttribute( "show-cpi-effort", QString::number(m_chartinfo.showCpiEffort) );
}
KoPrintJob *PerformanceStatusBase::createPrintJob( ViewBase *parent )
{
PerformanceStatusPrintingDialog *dia = new PerformanceStatusPrintingDialog( parent, this, parent->project() );
dia->printer().setCreator("Plan");
return dia;
}
void PerformanceStatusBase::setNodes( const QList<Node *> &nodes )
{
m_chartmodel.setNodes( nodes );
static_cast<PerformanceDataCurrentDateModel*>( ui_performancetable->model() )->setNodes( nodes );
}
//-----------------------------------
PerformanceStatusTreeView::PerformanceStatusTreeView( QWidget *parent )
: QSplitter( parent )
, m_manager(0)
{
m_tree = new TreeViewBase( this );
NodeItemModel *m = new NodeItemModel( m_tree );
m_tree->setModel( m );
QList<int> lst1; lst1 << 1 << -1; // only display column 0 (NodeName) in tree view
m_tree->setDefaultColumns( QList<int>() << 0 );
m_tree->setColumnsHidden( lst1 );
m_tree->setSelectionMode( QAbstractItemView::ExtendedSelection );
addWidget( m_tree );
m_tree->setTreePosition(-1);
m_chart = new PerformanceStatusBase( this );
addWidget( m_chart );
connect( m_tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PerformanceStatusTreeView::slotSelectionChanged );
QTimer::singleShot( 0, this, &PerformanceStatusTreeView::resizeSplitters );
}
void PerformanceStatusTreeView::slotSelectionChanged( const QItemSelection&, const QItemSelection& )
{
//debugPlan;
QList<Node*> nodes;
foreach ( const QModelIndex &i, m_tree->selectionModel()->selectedIndexes() ) {
Node *n = nodeModel()->node( i );
if ( ! nodes.contains( n ) ) {
nodes.append( n );
}
}
m_chart->setNodes( nodes );
}
NodeItemModel *PerformanceStatusTreeView::nodeModel() const
{
return static_cast<NodeItemModel*>( m_tree->model() );
}
void PerformanceStatusTreeView::setScheduleManager( ScheduleManager *sm )
{
if (!sm && m_manager) {
// we should only get here if the only schedule manager is scheduled,
// or when last schedule manager is deleted
m_domdoc.clear();
QDomElement element = m_domdoc.createElement("expanded");
m_domdoc.appendChild(element);
treeView()->saveExpanded(element);
}
bool tryexpand = sm && !m_manager;
bool expand = sm && m_manager && sm != m_manager;
QDomDocument doc;
if (expand) {
QDomElement element = doc.createElement("expanded");
doc.appendChild(element);
treeView()->saveExpanded(element);
}
m_manager = sm;
nodeModel()->setScheduleManager( sm );
m_chart->setScheduleManager( sm );
if (expand) {
treeView()->doExpand(doc);
} else if (tryexpand) {
treeView()->doExpand(m_domdoc);
}
}
Project *PerformanceStatusTreeView::project() const
{
return nodeModel()->project();
}
void PerformanceStatusTreeView::setProject( Project *project )
{
nodeModel()->setProject( project );
m_chart->setProject( project );
}
bool PerformanceStatusTreeView::loadContext( const KoXmlElement &context )
{
debugPlan;
bool res = false;
res = m_chart->loadContext( context.namedItem( "chart" ).toElement() );
res &= m_tree->loadContext( nodeModel()->columnMap(), context.namedItem( "tree" ).toElement() );
return res;
}
void PerformanceStatusTreeView::saveContext( QDomElement &context ) const
{
QDomElement c = context.ownerDocument().createElement( "chart" );
context.appendChild( c );
m_chart->saveContext( c );
QDomElement t = context.ownerDocument().createElement( "tree" );
context.appendChild( t );
m_tree->saveContext( nodeModel()->columnMap(), t );
}
KoPrintJob *PerformanceStatusTreeView::createPrintJob( ViewBase *view )
{
return m_chart->createPrintJob( view );
}
// hackish way to get reasonable initial splitter sizes
void PerformanceStatusTreeView::resizeSplitters()
{
int x1 = sizes().value( 0 );
int x2 = sizes().value( 1 );
if ( x1 == 0 && x2 == 0 ) {
// not shown yet, try later
QTimer::singleShot( 100, this, &PerformanceStatusTreeView::resizeSplitters );
return;
}
if ( x1 == 0 || x2 == 0 ) {
// one is hidden, do nothing
return;
}
int tot = x1 + x2;
x1 = qMax( x1, qMin( ( tot ) / 2, 150 ) );
setSizes( QList<int>() << x1 << ( tot - x1 ) );
}
//-----------------------------------
PerformanceStatusView::PerformanceStatusView(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent )
{
debugPlan<<"-------------------- creating PerformanceStatusView -------------------";
QVBoxLayout * l = new QVBoxLayout( this );
l->setMargin( 0 );
m_view = new PerformanceStatusTreeView( this );
connect(this, &ViewBase::expandAll, m_view->treeView(), &TreeViewBase::slotExpand);
connect(this, &ViewBase::collapseAll, m_view->treeView(), &TreeViewBase::slotCollapse);
l->addWidget( m_view );
setupGui();
connect( m_view->treeView(), &TreeViewBase::headerContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
connect( m_view->chartView(), &QWidget::customContextMenuRequested, this, &ViewBase::slotHeaderContextMenuRequested );
connect( m_view->treeView(), SIGNAL(contextMenuRequested(QModelIndex,QPoint,QModelIndexList)), SLOT(slotContextMenuRequested(QModelIndex,QPoint)) );
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Task Performance View</title>"
"<para>"
"Displays performance data aggregated to the selected task."
"</para><para>"
"This view supports configuration and printing using the context menu."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Task_Performance_View")));
}
void PerformanceStatusView::slotContextMenuRequested( const QModelIndex &index, const QPoint& pos )
{
debugPlan<<index<<pos;
m_view->treeView()->setContextMenuIndex(index);
if ( ! index.isValid() ) {
slotHeaderContextMenuRequested( pos );
return;
}
Node *node = m_view->nodeModel()->node( index );
if ( node == 0 ) {
slotHeaderContextMenuRequested( pos );
m_view->treeView()->setContextMenuIndex(QModelIndex());
return;
}
slotContextMenuRequested( node, pos );
m_view->treeView()->setContextMenuIndex(QModelIndex());
}
Node *PerformanceStatusView::currentNode() const
{
return m_view->nodeModel()->node( m_view->treeView()->selectionModel()->currentIndex() );
}
void PerformanceStatusView::slotContextMenuRequested( Node *node, const QPoint& pos )
{
debugPlan<<node->name()<<" :"<<pos;
QString name;
switch ( node->type() ) {
case Node::Type_Task:
name = "taskview_popup";
break;
case Node::Type_Milestone:
name = "taskview_milestone_popup";
break;
case Node::Type_Summarytask:
name = "taskview_summary_popup";
break;
default:
break;
}
//debugPlan<<name;
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
emit requestPopupMenu( name, pos );
}
void PerformanceStatusView::setScheduleManager( ScheduleManager *sm )
{
//debugPlan;
ViewBase::setScheduleManager(sm);
m_view->setScheduleManager( sm );
}
void PerformanceStatusView::setProject( Project *project )
{
m_view->setProject( project );
}
void PerformanceStatusView::setGuiActive( bool activate )
{
debugPlan<<activate;
// updateActionsEnabled( true );
ViewBase::setGuiActive( activate );
}
void PerformanceStatusView::setupGui()
{
// Add the context menu actions for the view options
createOptionActions(ViewBase::OptionAll);
}
void PerformanceStatusView::slotOptions()
{
debugPlan;
PerformanceStatusViewSettingsDialog *dlg = new PerformanceStatusViewSettingsDialog( this, m_view, this, sender()->objectName() == "print options" );
connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
bool PerformanceStatusView::loadContext( const KoXmlElement &context )
{
debugPlan;
ViewBase::loadContext( context );
return m_view->loadContext( context );
}
void PerformanceStatusView::saveContext( QDomElement &context ) const
{
ViewBase::saveContext( context );
m_view->saveContext( context );
}
KoPrintJob *PerformanceStatusView::createPrintJob()
{
return m_view->createPrintJob( this );
}
//------------------------------------------------
PerformanceStatusViewSettingsPanel::PerformanceStatusViewSettingsPanel( PerformanceStatusBase *view, QWidget *parent )
: QWidget( parent ),
m_view( view )
{
setupUi( this );
#ifndef PLAN_CHART_DEBUG
ui_table->hide();
#endif
PerformanceChartInfo info = m_view->chartInfo();
ui_linechart->setChecked( info.showLineChart );
ui_barchart->setChecked( info.showBarChart );
#ifdef PLAN_CHART_DEBUG
ui_table->setChecked( info.showTableView );
#endif
ui_bcwsCost->setCheckState( info.showBCWSCost ? Qt::Checked : Qt::Unchecked );
ui_bcwpCost->setCheckState( info.showBCWPCost ? Qt::Checked : Qt::Unchecked );
ui_acwpCost->setCheckState( info.showACWPCost ? Qt::Checked : Qt::Unchecked );
ui_cost->setChecked( info.showCost );
ui_bcwsEffort->setCheckState( info.showBCWSEffort ? Qt::Checked : Qt::Unchecked );
ui_bcwpEffort->setCheckState( info.showBCWPEffort ? Qt::Checked : Qt::Unchecked );
ui_acwpEffort->setCheckState( info.showACWPEffort ? Qt::Checked : Qt::Unchecked );
ui_effort->setChecked( info.showEffort );
ui_showbasevalues->setChecked( info.showBaseValues );
ui_showindices->setChecked( info.showIndices );
ui_spicost->setCheckState( info.showSpiCost ? Qt::Checked : Qt::Unchecked );
ui_cpicost->setCheckState( info.showCpiCost ? Qt::Checked : Qt::Unchecked );
ui_spieffort->setCheckState( info.showSpiEffort ? Qt::Checked : Qt::Unchecked );
ui_cpieffort->setCheckState( info.showCpiEffort ? Qt::Checked : Qt::Unchecked );
connect(ui_showbasevalues, &QAbstractButton::toggled, this, &PerformanceStatusViewSettingsPanel::switchStackWidget);
connect(ui_showindices, &QAbstractButton::toggled, this, &PerformanceStatusViewSettingsPanel::switchStackWidget);
switchStackWidget();
}
void PerformanceStatusViewSettingsPanel::slotOk()
{
PerformanceChartInfo info;
info.showLineChart = ui_linechart->isChecked();
info.showBarChart = ui_barchart->isChecked();
info.showTableView = ui_table->isChecked();
info.showBaseValues = ui_showbasevalues->isChecked();
info.showIndices = ui_showindices->isChecked();
info.showBCWSCost = ui_bcwsCost->checkState() == Qt::Unchecked ? false : true;
info.showBCWPCost = ui_bcwpCost->checkState() == Qt::Unchecked ? false : true;
info.showACWPCost = ui_acwpCost->checkState() == Qt::Unchecked ? false : true;
info.showCost = ui_cost->isChecked();
info.showBCWSEffort = ui_bcwsEffort->checkState() == Qt::Unchecked ? false : true;
info.showBCWPEffort = ui_bcwpEffort->checkState() == Qt::Unchecked ? false : true;
info.showACWPEffort = ui_acwpEffort->checkState() == Qt::Unchecked ? false : true;
info.showEffort = ui_effort->isChecked();
info.showSpiCost = ui_spicost->isChecked();
info.showCpiCost = ui_cpicost->isChecked();
info.showSpiEffort = ui_spieffort->isChecked();
info.showCpiEffort = ui_cpieffort->isChecked();
m_view->setChartInfo( info );
}
void PerformanceStatusViewSettingsPanel::setDefault()
{
ui_linechart->setChecked( true );
ui_bcwsCost->setCheckState( Qt::Checked );
ui_bcwpCost->setCheckState( Qt::Checked );
ui_acwpCost->setCheckState( Qt::Checked );
ui_cost->setChecked( true );
ui_bcwsEffort->setCheckState( Qt::Checked );
ui_bcwpEffort->setCheckState( Qt::Checked );
ui_acwpEffort->setCheckState( Qt::Checked );
ui_effort->setChecked( Qt::Unchecked );
ui_showbasevalues->setChecked( true );
ui_showindices->setChecked( false );
ui_spicost->setCheckState( Qt::Checked );
ui_cpicost->setCheckState( Qt::Checked );
ui_spieffort->setCheckState( Qt::Checked );
ui_cpieffort->setCheckState( Qt::Checked );
}
void PerformanceStatusViewSettingsPanel::switchStackWidget()
{
if ( ui_showbasevalues->isChecked() ) {
ui_stackWidget->setCurrentIndex( 0 );
} else if ( ui_showindices->isChecked() ) {
ui_stackWidget->setCurrentIndex( 1 );
}
//debugPlan<<sender()<<ui_stackWidget->currentIndex();
}
//-----------------
PerformanceStatusViewSettingsDialog::PerformanceStatusViewSettingsDialog( PerformanceStatusView *view, PerformanceStatusTreeView *treeview, QWidget *parent, bool selectPrint )
: ItemViewSettupDialog( view, treeview->treeView(), true, parent )
{
PerformanceStatusViewSettingsPanel *panel = new PerformanceStatusViewSettingsPanel( treeview->chartView(), this );
KPageWidgetItem *page = insertWidget( 0, panel, i18n( "Chart" ), i18n( "Chart Settings" ) );
setCurrentPage( page );
addPrintingOptions(selectPrint);
//connect( panel, SIGNAL(changed(bool)), this, SLOT(enableButtonOk(bool)) );
connect( this, &QDialog::accepted, panel, &PerformanceStatusViewSettingsPanel::slotOk );
connect( button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, panel, &PerformanceStatusViewSettingsPanel::setDefault );
}
//-----------------
ProjectStatusViewSettingsDialog::ProjectStatusViewSettingsDialog( ViewBase *base, PerformanceStatusBase *view, QWidget *parent, bool selectPrint )
: KPageDialog( parent ),
m_base( base )
{
PerformanceStatusViewSettingsPanel *panel = new PerformanceStatusViewSettingsPanel( view, this );
KPageWidgetItem *page = new KPageWidgetItem( panel, i18n( "Chart" ) );
page->setHeader( i18n( "Chart Settings" ) );
addPage( page );
QTabWidget *tab = new QTabWidget();
QWidget *w = ViewBase::createPageLayoutWidget( base );
tab->addTab( w, w->windowTitle() );
m_pagelayout = w->findChild<KoPageLayoutWidget*>();
Q_ASSERT( m_pagelayout );
m_headerfooter = ViewBase::createHeaderFooterWidget( base );
m_headerfooter->setOptions( base->printingOptions() );
tab->addTab( m_headerfooter, m_headerfooter->windowTitle() );
page = addPage( tab, i18n( "Printing" ) );
page->setHeader( i18n( "Printing Options" ) );
if (selectPrint) {
setCurrentPage(page);
}
connect( this, &QDialog::accepted, panel, &PerformanceStatusViewSettingsPanel::slotOk );
//TODO: there was no default button configured, should there?
// connect( button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked(bool)), panel, SLOT(setDefault()) );
connect( this, &QDialog::accepted, this, &ProjectStatusViewSettingsDialog::slotOk);
}
void ProjectStatusViewSettingsDialog::slotOk()
{
debugPlan;
m_base->setPageLayout( m_pagelayout->pageLayout() );
m_base->setPrintingOptions( m_headerfooter->options() );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptusedefforteditor.cpp b/src/libs/ui/kptusedefforteditor.cpp
index 6316e7ae..19184a3b 100644
--- a/src/libs/ui/kptusedefforteditor.cpp
+++ b/src/libs/ui/kptusedefforteditor.cpp
@@ -1,876 +1,877 @@
/* This file is part of the KDE project
Copyright (C) 2007, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kptusedefforteditor.h"
#include "kptitemmodelbase.h"
#include <QDate>
#include <QHeaderView>
#include <QLocale>
#include <KLocalizedString>
#include "kptproject.h"
#include "kpttask.h"
#include "kptresource.h"
#include "kptdebug.h"
namespace KPlato
{
UsedEffortItemModel::UsedEffortItemModel ( QWidget *parent )
: QAbstractItemModel( parent ),
m_completion( 0 ),
m_readonly( false )
{
m_headers << i18n( "Resource" );
QLocale locale;
for ( int i = 1; i <= 7; ++i ) {
m_headers << locale.dayName( i, QLocale::ShortFormat );
}
m_headers << i18n( "This Week" );
}
Qt::ItemFlags UsedEffortItemModel::flags ( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( m_readonly || ! index.isValid() || index.column() == 8 ) {
return flags;
}
if ( index.column() == 0 ) {
const Resource *r = resource( index );
if ( r ) {
if ( m_resourcelist.contains( r ) && ! m_completion->usedEffortMap().contains( r ) ) {
return flags | Qt::ItemIsEditable;
}
}
return flags;
}
return flags | Qt::ItemIsEditable;
}
QVariant UsedEffortItemModel::data ( const QModelIndex &index, int role ) const
{
if ( ! index.isValid() ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole: {
if ( index.column() == 0 ) {
const Resource *r = resource( index );
//debugPlan<<index.row()<<","<<index.column()<<""<<r;
if ( r ) {
return r->name();
}
break;
}
Completion::UsedEffort *ue = usedEffort( index );
if ( ue == 0 ) {
return QVariant();
}
if ( index.column() == 8 ) {
// Total
//debugPlan<<index.row()<<","<<index.column()<<" total"<<endl;
double res = 0.0;
foreach ( const QDate &d, m_dates ) {
Completion::UsedEffort::ActualEffort e = ue->effort( d );
res += e.normalEffort().toDouble( Duration::Unit_h );
}
return QLocale().toString(res, 'f', 1 );
}
Completion::UsedEffort::ActualEffort e = ue->effort( m_dates.value( index.column() - 1 ) );
double res = e.normalEffort().toDouble( Duration::Unit_h );
return QLocale().toString(res, 'f', 1 );
}
case Qt::EditRole: {
if ( index.column() == 8 ) {
return QVariant();
}
if ( index.column() == 0 ) {
const Resource *r = resource( index );
//debugPlan<<index.row()<<","<<index.column()<<" "<<r<<endl;
if ( r ) {
return r->name();
}
} else {
Completion::UsedEffort *ue = usedEffort( index );
if ( ue == 0 ) {
return QVariant();
}
Completion::UsedEffort::ActualEffort e = ue->effort( m_dates.value( index.column() - 1 ) );
double res = e.normalEffort().toDouble( Duration::Unit_h );
return QLocale().toString(res, 'f', 1 );
}
break;
}
case Role::EnumList: {
if ( index.column() == 0 ) {
QStringList lst = m_editlist.keys();
return lst;
}
break;
}
case Role::EnumListValue: {
if ( index.column() == 0 ) {
return m_editlist.values().indexOf( resource( index ) ); // clazy:exclude=container-anti-pattern
}
break;
}
default: break;
}
return QVariant();
}
bool UsedEffortItemModel::setData ( const QModelIndex &idx, const QVariant &value, int role )
{
debugPlan;
switch ( role ) {
case Qt::EditRole: {
if ( idx.column() == 8 ) {
return false;
}
if ( idx.column() == 0 ) {
const Resource *er = resource( idx );
Q_ASSERT( er != 0 );
Q_ASSERT ( m_editlist.count() > value.toInt() );
const Resource *v = m_editlist.values().value( value.toInt() ); // clazy:exclude=container-anti-pattern
Q_ASSERT( v != 0 );
int x = m_resourcelist.indexOf( er );
Q_ASSERT( x != -1 );
m_resourcelist.replace( x, v );
m_completion->addUsedEffort( v );
emit dataChanged( createIndex( idx.row(), 1 ), createIndex( idx.row(), columnCount() - 1 ) );
emit rowInserted( createIndex( idx.row(), 0 ) );
return true;
}
Completion::UsedEffort *ue = usedEffort( idx );
if ( ue == 0 ) {
return false;
}
QDate d = m_dates.value( idx.column() - 1 );
Completion::UsedEffort::ActualEffort e = ue->effort( d );
e.setNormalEffort( Duration( value.toDouble(), Duration::Unit_h ) );
ue->setEffort( d, e );
emit dataChanged( idx, idx );
return true;
}
default: break;
}
return false;
}
bool UsedEffortItemModel::submit()
{
debugPlan;
return QAbstractItemModel::submit();
}
void UsedEffortItemModel::revert()
{
debugPlan;
QList<const Resource*> lst = m_resourcelist;
foreach ( const Resource *r, lst ) {
if ( ! m_completion->usedEffortMap().contains( r ) ) {
int row = m_resourcelist.indexOf( r );
if ( row != -1 ) {
beginRemoveRows( QModelIndex(), row, row );
m_resourcelist.removeAt( row );
endRemoveRows();
}
}
}
}
QVariant UsedEffortItemModel::headerData ( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Vertical ) {
return QVariant();
}
if ( section < 0 || section >= m_headers.count() ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
return m_headers.at( section );
case Qt::ToolTipRole: {
if ( section >= 1 && section <= 7 ) {
return QLocale().toString(m_dates.at( section - 1 ), QLocale::ShortFormat);
}
if ( section == 8 ) {
return i18n( "Total effort this week" );
}
break;
}
default: break;
}
return QVariant();
}
int UsedEffortItemModel::columnCount(const QModelIndex & parent ) const
{
int c = 0;
if ( m_completion && ! parent.isValid() ) {
c = 9;
}
return c;
}
int UsedEffortItemModel::rowCount(const QModelIndex & ) const
{
int rows = 0;
if ( m_completion ) {
rows = m_resourcelist.count();
}
return rows;
}
QModelIndex UsedEffortItemModel::index ( int row, int column, const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return QModelIndex();
}
return createIndex( row, column );
}
void UsedEffortItemModel::setCompletion( Completion *completion )
{
beginResetModel();
m_completion = completion;
m_resourcelist.clear();
QMap<QString, const Resource*> lst;
const Completion::ResourceUsedEffortMap &map = completion->usedEffortMap();
Completion::ResourceUsedEffortMap::const_iterator it;
for (it = map.constBegin(); it != map.constEnd(); ++it) {
lst.insertMulti(it.key()->name(), it.key());
}
m_resourcelist = lst.values();
endResetModel();
}
const Resource *UsedEffortItemModel::resource(const QModelIndex &index ) const
{
int row = index.row();
if ( m_completion == 0 || row < 0 || row >= m_resourcelist.count() ) {
return 0;
}
return m_resourcelist.value( row );
}
Completion::UsedEffort *UsedEffortItemModel::usedEffort(const QModelIndex &index ) const
{
const Resource *r = resource( index );
if ( r == 0 ) {
return 0;
}
return m_completion->usedEffort( r );
}
void UsedEffortItemModel::setCurrentMonday( const QDate &date )
{
beginResetModel();
m_dates.clear();
for ( int i = 0; i < 7; ++i ) {
m_dates << date.addDays( i );
}
endResetModel();
emit headerDataChanged ( Qt::Horizontal, 1, 7 );
}
QModelIndex UsedEffortItemModel::addRow()
{
if ( m_project == 0 ) {
return QModelIndex();
}
m_editlist.clear();
m_editlist = freeResources();
if ( m_editlist.isEmpty() ) {
return QModelIndex();
}
int row = rowCount();
beginInsertRows( QModelIndex(), row, row );
m_resourcelist.append(m_editlist.first());
endInsertRows();
return createIndex(row, 0, const_cast<Resource*>(m_editlist.first()));
}
QMap<QString, const Resource*> UsedEffortItemModel::freeResources() const
{
QMap<QString, const Resource*> map;
foreach ( Resource *r, m_project->resourceList() ) {
if ( ! m_resourcelist.contains( r ) ) {
map.insertMulti( r->name(), r );
}
}
return map;
}
//-----------
UsedEffortEditor::UsedEffortEditor( QWidget *parent )
: QTableView( parent )
{
UsedEffortItemModel *m = new UsedEffortItemModel(this );
setModel( m );
setItemDelegateForColumn ( 0, new EnumDelegate( this ) );
setItemDelegateForColumn ( 1, new DoubleSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 2, new DoubleSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 3, new DoubleSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 4, new DoubleSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 5, new DoubleSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 6, new DoubleSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 7, new DoubleSpinBoxDelegate( this ) );
connect ( model(), &QAbstractItemModel::dataChanged, this, &UsedEffortEditor::changed );
connect ( m, &UsedEffortItemModel::rowInserted, this, &UsedEffortEditor::resourceAdded );
}
bool UsedEffortEditor::hasFreeResources() const
{
return ! static_cast<UsedEffortItemModel*>( model() )->freeResources().isEmpty();
}
void UsedEffortEditor::setProject( Project *p )
{
static_cast<UsedEffortItemModel*>( model() )->setProject( p );
}
void UsedEffortEditor::setCompletion( Completion *completion )
{
static_cast<UsedEffortItemModel*>( model() )->setCompletion( completion );
}
void UsedEffortEditor::setCurrentMonday( const QDate &date )
{
static_cast<UsedEffortItemModel*>( model() )->setCurrentMonday( date );
}
void UsedEffortEditor::addResource()
{
UsedEffortItemModel *m = static_cast<UsedEffortItemModel*>( model() );
QModelIndex i = m->addRow();
if ( i.isValid() ) {
setCurrentIndex( i );
edit( i );
}
}
//----------------------------------------
CompletionEntryItemModel::CompletionEntryItemModel ( QObject *parent )
: QAbstractItemModel( parent ),
m_node( 0 ),
m_project( 0 ),
m_manager( 0 ),
m_completion( 0 )
{
m_headers << i18n( "Date" )
// xgettext: no-c-format
<< i18n( "% Completed" )
<< i18n( "Used Effort" )
<< i18n( "Remaining Effort" )
<< i18n( "Planned Effort" );
m_flags.insert( Property_Date, Qt::NoItemFlags );
m_flags.insert( Property_Completion, Qt::ItemIsEditable );
m_flags.insert( Property_UsedEffort, Qt::NoItemFlags );
m_flags.insert( Property_RemainingEffort, Qt::ItemIsEditable );
m_flags.insert( Property_PlannedEffort, Qt::NoItemFlags );
}
void CompletionEntryItemModel::setTask( Task *t )
{
m_node = t;
m_project = 0;
if ( m_node && m_node->projectNode() ) {
m_project = static_cast<Project*>( m_node->projectNode() );
}
}
void CompletionEntryItemModel::slotDataChanged()
{
refresh();
}
void CompletionEntryItemModel::setManager( ScheduleManager *sm )
{
m_manager = sm;
refresh();
}
Qt::ItemFlags CompletionEntryItemModel::flags ( const QModelIndex &index ) const
{
if ( index.isValid() && index.column() < m_flags.count() ) {
return QAbstractItemModel::flags( index ) | m_flags[ index.column() ];
}
return QAbstractItemModel::flags( index );
}
QVariant CompletionEntryItemModel::date ( int row, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return m_datelist.value( row );
default: break;
}
return QVariant();
}
QVariant CompletionEntryItemModel::percentFinished ( int row, int role ) const
{
Completion::Entry *e = m_completion->entry( date( row ).toDate() );
if ( e == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
return e->percentFinished;
default: break;
}
return QVariant();
}
QVariant CompletionEntryItemModel::remainingEffort ( int row, int role ) const
{
Completion::Entry *e = m_completion->entry( date( row ).toDate() );
if ( e == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
return e->remainingEffort.format();
}
case Qt::EditRole:
return e->remainingEffort.toDouble( Duration::Unit_h );
case Role::DurationScales: {
QVariantList lst; // TODO: week
if ( m_node && m_project ) {
if ( m_node->estimate()->type() == Estimate::Type_Effort ) {
lst.append( m_project->standardWorktime()->day() );
}
}
if ( lst.isEmpty() ) {
lst.append( 24.0 );
}
lst << 60.0 << 60.0 << 1000.0;
return lst;
}
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Role::Minimum:
return m_project->config().minimumDurationUnit();
case Role::Maximum:
return m_project->config().maximumDurationUnit();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CompletionEntryItemModel::actualEffort ( int row, int role ) const
{
Completion::Entry *e = m_completion->entry( date( row ).toDate() );
if ( e == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
Duration v;
if ( m_completion->entrymode() == Completion::EnterEffortPerResource ) {
v = m_completion->actualEffortTo( date( row ).toDate() );
} else {
v = e->totalPerformed;
}
//debugPlan<<m_node->name()<<": "<<v<<" "<<unit<<" : "<<scales<<endl;
return v.format();
}
case Qt::EditRole:
return e->totalPerformed.toDouble( Duration::Unit_h );
case Role::DurationScales: {
QVariantList lst; // TODO: week
if ( m_node && m_project ) {
if ( m_node->estimate()->type() == Estimate::Type_Effort ) {
lst.append( m_project->standardWorktime()->day() );
}
}
if ( lst.isEmpty() ) {
lst.append( 24 );
}
lst << 60 << 60 << 1000;
return lst;
}
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Role::Minimum:
return m_project->config().minimumDurationUnit();
case Role::Maximum:
return m_project->config().maximumDurationUnit();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CompletionEntryItemModel::plannedEffort ( int /*row*/, int role ) const
{
if ( m_node == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
Duration v = m_node->plannedEffort( id(), ECCT_EffortWork );
//debugPlan<<m_node->name()<<": "<<v<<" "<<unit;
return v.format();
}
case Qt::EditRole:
return QVariant();
case Role::DurationScales: {
QVariantList lst; // TODO: week
if ( m_node && m_project ) {
if ( m_node->estimate()->type() == Estimate::Type_Effort ) {
lst.append( m_project->standardWorktime()->day() );
}
}
if ( lst.isEmpty() ) {
lst.append( 24.0 );
}
lst << 60.0 << 60.0 << 1000.0;
return lst;
}
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Role::Minimum:
return m_project->config().minimumDurationUnit();
case Role::Maximum:
return m_project->config().maximumDurationUnit();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant CompletionEntryItemModel::data ( const QModelIndex &index, int role ) const
{
if ( ! index.isValid() ) {
return QVariant();
}
switch ( index.column() ) {
case Property_Date: return date( index.row(), role );
case Property_Completion: return percentFinished( index.row(), role );
case Property_UsedEffort: return actualEffort( index.row(), role );
case Property_RemainingEffort: return remainingEffort( index.row(), role );
case Property_PlannedEffort: return plannedEffort( index.row(), role );
default: break;
}
return QVariant();
}
QList<qint64> CompletionEntryItemModel::scales() const
{
QList<qint64> lst;
if ( m_node && m_project ) {
if ( m_node->estimate()->type() == Estimate::Type_Effort ) {
lst = m_project->standardWorktime()->scales();
}
}
if ( lst.isEmpty() ) {
lst = Estimate::defaultScales();
}
//debugPlan<<lst;
return lst;
}
bool CompletionEntryItemModel::setData ( const QModelIndex &idx, const QVariant &value, int role )
{
//debugPlan;
switch ( role ) {
case Qt::EditRole: {
if ( idx.column() == Property_Date ) {
QDate od = date( idx.row() ).toDate();
removeEntry( od );
addEntry( value.toDate() );
// emit dataChanged( idx, idx );
return true;
}
if ( idx.column() == Property_Completion ) {
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e == 0 ) {
return false;
}
e->percentFinished = value.toInt();
if ( m_completion->entrymode() == Completion::EnterCompleted && m_node ) {
// calculate used/remaining
Duration est = m_node->plannedEffort( id(), ECCT_EffortWork );
e->totalPerformed = est * e->percentFinished / 100;
e->remainingEffort = est - e->totalPerformed;
}
emit dataChanged( idx, createIndex( idx.row(), 3 ) );
return true;
}
if ( idx.column() == Property_UsedEffort ) {
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e == 0 ) {
return false;
}
double v( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration d = Estimate::scale( v, unit, scales() );
if ( d == e->totalPerformed ) {
return false;
}
e->totalPerformed = d;
emit dataChanged( idx, idx );
return true;
}
if ( idx.column() == Property_RemainingEffort ) {
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e == 0 ) {
return false;
}
double v( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration d = Estimate::scale( v, unit, scales() );
if ( d == e->remainingEffort ) {
return false;
}
e->remainingEffort = d;
emit dataChanged( idx, idx );
return true;
}
}
default: break;
}
return false;
}
bool CompletionEntryItemModel::submit()
{
debugPlan<<endl;
return QAbstractItemModel::submit();
}
void CompletionEntryItemModel::revert()
{
debugPlan<<endl;
refresh();
}
QVariant CompletionEntryItemModel::headerData ( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Vertical ) {
return QVariant();
}
if ( section < 0 || section >= m_headers.count() ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
return m_headers.at( section );
default: break;
}
return QVariant();
}
int CompletionEntryItemModel::columnCount(const QModelIndex & /*parent */) const
{
return 5;
}
int CompletionEntryItemModel::rowCount(const QModelIndex &idx ) const
{
if ( m_completion == 0 || idx.isValid() ) {
return 0;
}
return m_datelist.count();
}
QModelIndex CompletionEntryItemModel::index ( int row, int column, const QModelIndex &parent ) const
{
if ( parent.isValid() ) {
return QModelIndex();
}
return createIndex( row, column );
}
void CompletionEntryItemModel::setCompletion( Completion *completion )
{
m_completion = completion;
refresh();
}
void CompletionEntryItemModel::refresh()
{
beginResetModel();
m_datelist.clear();
m_flags[ Property_UsedEffort ] = Qt::NoItemFlags;
if ( m_completion ) {
m_datelist = m_completion->entries().keys();
if ( m_completion->entrymode() == Completion::EnterEffortPerTask ) {
m_flags[ Property_UsedEffort ] = Qt::ItemIsEditable;
}
}
debugPlan<<m_datelist<<endl;
endResetModel();
}
QModelIndex CompletionEntryItemModel::addRow()
{
if ( m_completion == 0 ) {
return QModelIndex();
}
int row = rowCount();
QDate d = QDate::currentDate();
if ( row > 0 && d <= m_datelist.last() ) {
d = m_datelist.last().addDays( 1 );
}
beginInsertRows( QModelIndex(), row, row );
m_datelist.append( d );
endInsertRows();
return createIndex( row, 0 );
}
void CompletionEntryItemModel::removeEntry( const QDate& date )
{
removeRow( m_datelist.indexOf( date ) );
}
void CompletionEntryItemModel::removeRow( int row )
{
debugPlan<<row;
if ( row < 0 && row >= rowCount() ) {
return;
}
QDate date = m_datelist.value( row );
beginRemoveRows( QModelIndex(), row, row );
m_datelist.removeAt( row );
endRemoveRows();
debugPlan<<date<<" removed row"<<row;
m_completion->takeEntry( date );
emit rowRemoved( date );
emit changed();
}
void CompletionEntryItemModel::addEntry( const QDate& date )
{
debugPlan<<date<<endl;
Completion::Entry *e = new Completion::Entry();
if ( m_completion->entries().isEmpty() ) {
if ( m_node ) {
e->remainingEffort = m_node->plannedEffort( id(), ECCT_EffortWork );
}
} else {
e->percentFinished = m_completion->percentFinished();
e->totalPerformed = m_completion->actualEffort();
e->remainingEffort = m_completion->remainingEffort();
}
m_completion->addEntry( date, e );
refresh();
int i = m_datelist.indexOf( date );
if ( i != -1 ) {
emit rowInserted( date );
emit dataChanged( createIndex( i, 1 ), createIndex( i, rowCount() - 1 ) );
} else errorPlan<<"Failed to find added entry: "<<date<<endl;
}
//-----------
CompletionEntryEditor::CompletionEntryEditor( QWidget *parent )
: QTableView( parent )
{
verticalHeader()->hide();
CompletionEntryItemModel *m = new CompletionEntryItemModel(this );
setItemDelegateForColumn ( 1, new ProgressBarDelegate( this ) );
setItemDelegateForColumn ( 2, new DurationSpinBoxDelegate( this ) );
setItemDelegateForColumn ( 3, new DurationSpinBoxDelegate( this ) );
setCompletionModel( m );
}
void CompletionEntryEditor::setCompletionModel( CompletionEntryItemModel *m )
{
if ( model() ) {
disconnect(model(), &CompletionEntryItemModel::rowInserted, this, &CompletionEntryEditor::rowInserted);
disconnect(model(), &CompletionEntryItemModel::rowRemoved, this, &CompletionEntryEditor::rowRemoved);
disconnect(model(), &QAbstractItemModel::dataChanged, this, &CompletionEntryEditor::changed);
disconnect(model(), &CompletionEntryItemModel::changed, this, &CompletionEntryEditor::changed);
disconnect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &CompletionEntryEditor::selectedItemsChanged);
}
setModel( m );
if ( model() ) {
connect(model(), &CompletionEntryItemModel::rowInserted, this, &CompletionEntryEditor::rowInserted);
connect(model(), &CompletionEntryItemModel::rowRemoved, this, &CompletionEntryEditor::rowRemoved);
connect(model(), &QAbstractItemModel::dataChanged, this, &CompletionEntryEditor::changed);
connect(model(), &CompletionEntryItemModel::changed, this, &CompletionEntryEditor::changed);
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &CompletionEntryEditor::selectedItemsChanged);
}
}
void CompletionEntryEditor::setCompletion( Completion *completion )
{
model()->setCompletion( completion );
}
void CompletionEntryEditor::addEntry()
{
debugPlan<<endl;
QModelIndex i = model()->addRow();
if ( i.isValid() ) {
model()->setFlags( i.column(), Qt::ItemIsEditable );
setCurrentIndex( i );
emit selectedItemsChanged(QItemSelection(), QItemSelection()); //hmmm, control removeEntryBtn
scrollTo( i );
edit( i );
model()->setFlags( i.column(), Qt::NoItemFlags );
}
}
void CompletionEntryEditor::removeEntry()
{
//debugPlan;
QModelIndexList lst = selectedIndexes();
debugPlan<<lst;
QMap<int, int> rows;
while ( ! lst.isEmpty() ) {
QModelIndex idx = lst.takeFirst();
rows[ idx.row() ] = 0;
}
QList<int> r = rows.uniqueKeys();
debugPlan<<rows<<r;
for ( int i = r.count() - 1; i >= 0; --i ) {
model()->removeRow( r.at( i ) );
}
}
} //KPlato namespace
diff --git a/src/libs/ui/kptviewbase.cpp b/src/libs/ui/kptviewbase.cpp
index 99e4f5d3..945906d6 100644
--- a/src/libs/ui/kptviewbase.cpp
+++ b/src/libs/ui/kptviewbase.cpp
@@ -1,2524 +1,2525 @@
/* This file is part of the KDE project
Copyright (C) 2006 -2010, 2012 Dag Andersen <danders@get2net.dk>
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 "kptviewbase.h"
#include "kptitemmodelbase.h"
#include "kptproject.h"
#include "kptdebug.h"
#include "config.h"
#include <kmessagebox.h>
#include <kactioncollection.h>
#include <kactionmenu.h>
#include <kzip.h>
#include <KStandardAction>
#include <KoIcon.h>
#include <KoDocument.h>
#include <KoPart.h>
#include <KoMainWindow.h>
#include <KoPageLayoutWidget.h>
#include <KoPagePreviewWidget.h>
#include <KoUnit.h>
#include <KoXmlReader.h>
#include <QAbstractItemModel>
#include <QAbstractProxyModel>
#include <QSortFilterProxyModel>
#include <QHeaderView>
#include <QPoint>
#include <QScrollBar>
#include <QAbstractScrollArea>
#include <QMetaEnum>
#include <QPainter>
#include <QAction>
#include <QMenu>
#include <QFocusEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QContextMenuEvent>
#include <QDragMoveEvent>
#include <QTimer>
namespace KPlato
{
DockWidget::DockWidget( ViewBase *v, const QString &identity, const QString &title )
: QDockWidget( v ),
view( v ),
id( identity ),
location( Qt::RightDockWidgetArea ),
editor( false ),
m_shown( true )
{
setWindowTitle( title );
setObjectName( v->objectName() + '-' + identity );
toggleViewAction()->setObjectName( objectName() );
connect(this, &QDockWidget::dockLocationChanged, this, &DockWidget::setLocation);
}
void DockWidget::activate( KoMainWindow *mainWindow )
{
connect(this, &QDockWidget::visibilityChanged, this, &DockWidget::setShown);
setVisible( m_shown );
mainWindow->addDockWidget( location, this );
foreach(const KActionCollection *c, KActionCollection::allCollections()) {
KActionMenu *a = qobject_cast<KActionMenu*>(c->action("settings_dockers_menu"));
if ( a ) {
a->addAction( toggleViewAction() );
break;
}
}
}
void DockWidget::deactivate( KoMainWindow *mainWindow )
{
disconnect(this, &QDockWidget::visibilityChanged, this, &DockWidget::setShown);
mainWindow->removeDockWidget( this );
// activation re-parents to QMainWindow, so re-parent back to view
setParent( const_cast<ViewBase*>( view ) );
foreach(const KActionCollection *c, KActionCollection::allCollections()) {
KActionMenu *a = qobject_cast<KActionMenu*>(c->action("settings_dockers_menu"));
if ( a ) {
a->removeAction( toggleViewAction() );
break;
}
}
}
void DockWidget::setShown( bool show )
{
m_shown = show;
setVisible( show );
}
bool KPlato::DockWidget::shown() const
{
return m_shown;
}
void DockWidget::setLocation( Qt::DockWidgetArea area )
{
location = area;
}
bool DockWidget::saveXml( QDomElement &context ) const
{
QDomElement e = context.ownerDocument().createElement( "docker" );
context.appendChild( e );
e.setAttribute( "id", id );
e.setAttribute( "location", QString::number(location) );
e.setAttribute( "floating", QString::number(isFloating()) );
e.setAttribute( "visible", QString::number(m_shown) );
return true;
}
void DockWidget::loadXml(const KoXmlElement& context)
{
location = static_cast<Qt::DockWidgetArea>( context.attribute( "location", "0" ).toInt() );
setFloating( (bool) context.attribute( "floating", "0" ).toInt() );
m_shown = context.attribute( "visible", "1" ).toInt();
}
//------------------------
bool PrintingOptions::loadXml( KoXmlElement &element )
{
KoXmlElement e;
forEachElement( e, element ) {
if ( e.tagName() == "header" ) {
headerOptions.group = e.attribute( "group", "0" ).toInt();
headerOptions.project = static_cast<Qt::CheckState>( e.attribute( "project", "0" ).toInt() );
headerOptions.date = static_cast<Qt::CheckState>( e.attribute( "date", "0" ).toInt() );
headerOptions.manager = static_cast<Qt::CheckState>( e.attribute( "manager", "0" ).toInt() );
headerOptions.page = static_cast<Qt::CheckState>( e.attribute( "page", "0" ).toInt() );
} else if ( e.tagName() == "footer" ) {
footerOptions.group = e.attribute( "group", "0" ).toInt();
footerOptions.project = static_cast<Qt::CheckState>( e.attribute( "project", "0" ).toInt() );
footerOptions.date = static_cast<Qt::CheckState>( e.attribute( "date", "0" ).toInt() );
footerOptions.manager = static_cast<Qt::CheckState>( e.attribute( "manager", "0" ).toInt() );
footerOptions.page = static_cast<Qt::CheckState>( e.attribute( "page", "0" ).toInt() );
}
}
return true;
}
void PrintingOptions::saveXml( QDomElement &element ) const
{
QDomElement me = element.ownerDocument().createElement( "printing-options" );
element.appendChild( me );
QDomElement h = me.ownerDocument().createElement( "header" );
me.appendChild( h );
h.setAttribute( "group", QString::number(headerOptions.group) );
h.setAttribute( "project", QString::number(headerOptions.project) );
h.setAttribute( "date", QString::number(headerOptions.date) );
h.setAttribute( "manager", QString::number(headerOptions.manager) );
h.setAttribute( "page", QString::number(headerOptions.page) );
QDomElement f = me.ownerDocument().createElement( "footer" );
me.appendChild( f );
f.setAttribute( "group", QString::number(footerOptions.group) );
f.setAttribute( "project", QString::number(footerOptions.project) );
f.setAttribute( "date", QString::number(footerOptions.date) );
f.setAttribute( "manager", QString::number(footerOptions.manager) );
f.setAttribute( "page", QString::number(footerOptions.page) );
}
//----------------------
PrintingHeaderFooter::PrintingHeaderFooter( const PrintingOptions &opt, QWidget *parent )
: QWidget( parent )
{
setupUi( this );
setWindowTitle( i18n("Header and Footer" ));
setOptions( opt );
connect(ui_header, &QGroupBox::toggled, this, &PrintingHeaderFooter::slotChanged);
connect(ui_headerProject, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_headerPage, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_headerManager, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_headerDate, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_footer, &QGroupBox::toggled, this, &PrintingHeaderFooter::slotChanged);
connect(ui_footerProject, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_footerPage, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_footerManager, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
connect(ui_footerDate, &QCheckBox::stateChanged, this, &PrintingHeaderFooter::slotChanged);
}
PrintingHeaderFooter::~PrintingHeaderFooter()
{
//debugPlan;
}
void PrintingHeaderFooter::slotChanged()
{
debugPlan;
emit changed( options() );
}
void PrintingHeaderFooter::setOptions( const PrintingOptions &options )
{
m_options = options;
ui_header->setChecked( m_options.headerOptions.group );
ui_headerProject->setCheckState( m_options.headerOptions.project );
ui_headerDate->setCheckState( m_options.headerOptions.date );
ui_headerManager->setCheckState( m_options.headerOptions.manager );
ui_headerPage->setCheckState( m_options.headerOptions.page );
ui_footer->setChecked( m_options.footerOptions.group );
ui_footerProject->setCheckState( m_options.footerOptions.project );
ui_footerDate->setCheckState( m_options.footerOptions.date );
ui_footerManager->setCheckState( m_options.footerOptions.manager );
ui_footerPage->setCheckState( m_options.footerOptions.page );
}
PrintingOptions PrintingHeaderFooter::options() const
{
//debugPlan;
PrintingOptions opt;
opt.headerOptions.group = ui_header->isChecked();
opt.headerOptions.project = ui_headerProject->checkState();
opt.headerOptions.date = ui_headerDate->checkState();
opt.headerOptions.manager = ui_headerManager->checkState();
opt.headerOptions.page = ui_headerPage->checkState();
opt.footerOptions.group = ui_footer->isChecked();
opt.footerOptions.project = ui_footerProject->checkState();
opt.footerOptions.date = ui_footerDate->checkState( );
opt.footerOptions.manager = ui_footerManager->checkState();
opt.footerOptions.page = ui_footerPage->checkState();
return opt;
}
PrintingDialog::PrintingDialog( ViewBase *view )
: KoPrintingDialog( view ),
m_view( view ),
m_widget( 0 )
{
setPrinterPageLayout( view->pageLayout() );
QImage px( 100, 600, QImage::Format_Mono );
int dpm = printer().resolution() * 40;
px.setDotsPerMeterX( dpm );
px.setDotsPerMeterY( dpm );
QPainter p( &px );
m_textheight = p.boundingRect( QRectF(), Qt::AlignTop, "Aj" ).height();
debugPlan<<"textheight:"<<m_textheight;
}
PrintingDialog::~PrintingDialog()
{
}
QAbstractPrintDialog::PrintDialogOptions PrintingDialog::printDialogOptions() const
{
return QAbstractPrintDialog::PrintToFile |
QAbstractPrintDialog::PrintPageRange |
QAbstractPrintDialog::PrintCollateCopies |
QAbstractPrintDialog::DontUseSheet;
}
PrintingOptions PrintingDialog::printingOptions() const
{
return m_view->printingOptions();
}
void PrintingDialog::setPrintingOptions( const PrintingOptions &opt )
{
debugPlan;
m_view->setPrintingOptions( opt );
emit changed( opt );
emit changed();
}
void PrintingDialog::setPrinterPageLayout( const KoPageLayout &pagelayout )
{
QPrinter &p = printer();
QPrinter::Orientation o;
switch ( pagelayout.orientation ) {
case KoPageFormat::Portrait: o = QPrinter::Portrait; break;
case KoPageFormat::Landscape: o = QPrinter::Landscape; break;
default: o = QPrinter::Portrait; break;
}
p.setOrientation( o );
p.setPaperSize( KoPageFormat::printerPageSize( pagelayout.format ) );
p.setPageMargins( pagelayout.leftMargin, pagelayout.topMargin, pagelayout.rightMargin, pagelayout.bottomMargin, QPrinter::Point );
}
void PrintingDialog::startPrinting(RemovePolicy removePolicy )
{
setPrinterPageLayout( m_view->pageLayout() ); // FIXME: Something resets printer().paperSize() to A4 !
KoPrintingDialog::startPrinting( removePolicy );
}
QWidget *PrintingDialog::createPageLayoutWidget() const
{
QWidget *w = ViewBase::createPageLayoutWidget( m_view );
KoPageLayoutWidget *pw = w->findChild<KoPageLayoutWidget*>();
connect(pw, SIGNAL(layoutChanged(KoPageLayout)), m_view, SLOT(setPageLayout(KoPageLayout)));
connect(pw, &KoPageLayoutWidget::layoutChanged, this, &PrintingDialog::setPrinterPageLayout);
connect(pw, SIGNAL(layoutChanged(KoPageLayout)), this, SIGNAL(changed()));
return w;
}
QList<QWidget*> PrintingDialog::createOptionWidgets() const
{
//debugPlan;
PrintingHeaderFooter *w = new PrintingHeaderFooter( printingOptions() );
connect(w, &PrintingHeaderFooter::changed, this, &PrintingDialog::setPrintingOptions);
const_cast<PrintingDialog*>( this )->m_widget = w;
return QList<QWidget*>() << w;
}
/*QList<KoShape*> PrintingDialog::shapesOnPage(int)
{
return QList<KoShape*>();
}*/
void PrintingDialog::drawRect( QPainter &p, const QRect &r, Qt::Edges edges )
{
p.save();
QPen pen = p.pen();
pen.setColor(Qt::gray);
p.setPen(pen);
if (edges & Qt::LeftEdge) {
p.drawLine( r.topLeft(), r.bottomLeft() );
}
if (edges & Qt::BottomEdge) {
p.drawLine( r.bottomLeft(), r.bottomRight() );
}
if (edges & Qt::TopEdge) {
p.drawLine( r.topRight(), r.bottomRight() );
}
if (edges & Qt::RightEdge) {
p.drawLine( r.topRight(), r.bottomRight() );
}
p.restore();
}
QRect PrintingDialog::headerRect() const
{
PrintingOptions options = m_view->printingOptions();
if ( options.headerOptions.group == false ) {
return QRect();
}
int height = headerFooterHeight( options.headerOptions );
return QRect( 0, 0, const_cast<PrintingDialog*>( this )->printer().pageRect().width(), height );
}
QRect PrintingDialog::footerRect() const
{
PrintingOptions options = m_view->printingOptions();
if ( options.footerOptions.group == false ) {
return QRect();
}
int height = headerFooterHeight( options.footerOptions );
QRect r = const_cast<PrintingDialog*>( this )->printer().pageRect();
return QRect( 0, r.height() - height, r.width(), height );
}
int PrintingDialog::headerFooterHeight( const PrintingOptions::Data &options ) const
{
int height = 0.0;
if ( options.page == Qt::Checked || options.project == Qt::Checked || options.manager == Qt::Checked || options.date == Qt::Checked ) {
height += m_textheight * 1.5;
}
if ( options.project == Qt::Checked && options.manager == Qt::Checked && ( options.date == Qt::Checked || options.page == Qt::Checked ) ) {
height *= 2.0;
}
debugPlan<<height;
return height;
}
void PrintingDialog::paintHeaderFooter( QPainter &p, const PrintingOptions &options, int pageNumber, const Project &project )
{
if ( options.headerOptions.group == true ) {
paint( p, options.headerOptions, headerRect(), pageNumber, project );
}
if ( options.footerOptions.group == true ) {
paint( p, options.footerOptions, footerRect(), pageNumber, project );
}
}
void PrintingDialog::paint( QPainter &p, const PrintingOptions::Data &options, const QRect &rect, int pageNumber, const Project &project )
{
p.save();
p.setPen( Qt::black );
p.drawRect( rect );
QRect projectRect;
QString projectName = project.name();
QRect pageRect;
QString page = i18nc( "1=page number, 2=last page number", "%1 (%2)", pageNumber, documentLastPage() );
QRect managerRect;
QString manager = project.leader();
QRect dateRect;
QString date = QLocale().toString(QDate::currentDate(), QLocale::ShortFormat);
QRect rect_1 = rect;
QRect rect_2 = rect;
if ( options.project == Qt::Checked ) {
rect_2.setHeight( rect.height() / 2 );
rect_2.translate( 0, rect_2.height() );
}
if ( ( options.project == Qt::Checked && options.manager == Qt::Checked ) && ( options.date == Qt::Checked || options.page == Qt::Checked ) ) {
rect_1.setHeight( rect.height() / 2 );
p.drawLine( rect_1.bottomLeft(), rect_1.bottomRight() );
}
if ( options.page == Qt::Checked ) {
pageRect = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, page );
pageRect.setHeight( rect_1.height() );
rect_1.setRight( pageRect.left() - 2 );
p.save();
QFont f = p.font();
f.setPointSize( f.pointSize() * 0.5 );
p.setFont( f );
QRect r = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, i18n("Page:") );
if (r.left() > pageRect.left()) {
pageRect.setLeft(r.left());
}
p.restore();
if ( options.project == Qt::Checked || options.manager == Qt::Checked || options.date == Qt::Checked ) {
p.drawLine( rect_1.topRight(), rect_1.bottomRight() );
}
}
if ( options.date == Qt::Checked ) {
p.save();
QFont f = p.font();
f.setPointSize( f.pointSize() * 0.5 );
p.setFont( f );
QRect rct = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, i18n("Date:") );
p.restore();
if ( ( options.project == Qt::Checked && options.manager != Qt::Checked ) ||
( options.project != Qt::Checked && options.manager == Qt::Checked ) )
{
dateRect = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, date );
dateRect.setHeight( rect_1.height() );
rect_1.setRight( dateRect.left() - 2 );
if (rct.right() > dateRect.right()) {
dateRect.setRight(rct.right());
}
p.drawLine( rect_1.topRight(), rect_1.bottomRight() );
} else if ( options.project == Qt::Checked && options.manager == Qt::Checked ) {
dateRect = p.boundingRect( rect_2, Qt::AlignRight|Qt::AlignTop, date );
dateRect.setHeight( rect_2.height() );
rect_2.setRight( dateRect.left() - 2 );
if (rct.right() > dateRect.right()) {
dateRect.setRight(rct.right());
}
p.drawLine( rect_2.topRight(), rect_2.bottomRight() );
} else {
dateRect = p.boundingRect( rect_2, Qt::AlignLeft|Qt::AlignTop, date );
if (rct.right() > dateRect.right()) {
dateRect.setRight(rct.right());
}
dateRect.setHeight( rect_2.height() );
rect_2.setRight( dateRect.left() - 2 );
if ( rect_2.left() != rect.left() ) {
p.drawLine( rect_2.topRight(), rect_2.bottomRight() );
}
}
}
if ( options.manager == Qt::Checked ) {
p.save();
QFont f = p.font();
f.setPointSize( f.pointSize() * 0.5 );
p.setFont( f );
QRect rct = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, i18n("Manager:") );
p.restore();
if ( options.project != Qt::Checked ) {
managerRect = p.boundingRect( rect_1, Qt::AlignLeft|Qt::AlignTop, manager );
managerRect.setHeight( rect_1.height() );
if (rct.right() > managerRect.right()) {
managerRect.setRight(rct.right());
}
} else if ( options.date != Qt::Checked && options.page != Qt::Checked ) {
managerRect = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, manager );
managerRect.setHeight( rect_1.height() );
if (rct.right() > managerRect.right()) {
managerRect.setRight(rct.right());
}
rect_1.setRight( managerRect.left() - 2 );
p.drawLine( rect_1.topRight(), rect_1.bottomRight() );
} else {
managerRect = p.boundingRect( rect_2, Qt::AlignLeft|Qt::AlignTop, manager );
managerRect.setHeight( rect_2.height() );
if (rct.right() > managerRect.right()) {
managerRect.setRight(rct.right());
}
}
}
if ( options.project == Qt::Checked ) {
projectRect = p.boundingRect( rect_1, Qt::AlignLeft|Qt::AlignTop, projectName );
projectRect.setHeight( rect_1.height() );
p.save();
QFont f = p.font();
f.setPointSize( f.pointSize() * 0.5 );
p.setFont( f );
QRect rct = p.boundingRect( rect_1, Qt::AlignRight|Qt::AlignTop, i18n("Project:") );
if (rct.right() > projectRect.right()) {
projectRect.setRight(rct.right());
}
p.restore();
}
if ( options.page == Qt::Checked ) {
p.drawText( pageRect, Qt::AlignHCenter|Qt::AlignBottom, page );
}
if ( options.project == Qt::Checked ) {
p.drawText( projectRect.adjusted(3, 0, 3, 0), Qt::AlignLeft|Qt::AlignBottom, projectName );
}
if ( options.date == Qt::Checked ) {
p.drawText( dateRect, Qt::AlignHCenter|Qt::AlignBottom, date );
}
if ( options.manager == Qt::Checked ) {
p.drawText( managerRect.adjusted(3, 0, 3, 0), Qt::AlignLeft|Qt::AlignBottom, manager );
}
QFont f = p.font();
f.setPointSize( f.pointSize() * 0.5 );
p.setFont( f );
if ( options.page == Qt::Checked ) {
p.drawText( pageRect, Qt::AlignTop|Qt::AlignLeft, i18n( "Page:" ) );
}
if ( options.project == Qt::Checked ) {
p.drawText( projectRect, Qt::AlignTop|Qt::AlignLeft, i18n( "Project:" ) );
}
if ( options.date == Qt::Checked ) {
p.drawText( dateRect, Qt::AlignTop|Qt::AlignLeft, i18n( "Date:" ) );
}
if ( options.manager == Qt::Checked ) {
p.drawText( managerRect, Qt::AlignTop|Qt::AlignLeft, i18n( "Manager:" ) );
}
p.restore();
}
//--------------
ViewBase::ViewBase(KoPart *part, KoDocument *doc, QWidget *parent)
: KoView(part, doc, parent),
m_readWrite( false ),
m_proj( 0 ),
m_schedulemanager( 0 )
{
}
ViewBase::~ViewBase()
{
if ( koDocument() ) {
//HACK to avoid ~View to access koDocument()
setDocumentDeleted();
}
}
void ViewBase::setProject( Project *project )
{
m_proj = project;
emit projectChanged( project );
}
KoDocument *ViewBase::part() const
{
return koDocument();
}
KoPageLayout ViewBase::pageLayout() const
{
return m_pagelayout;
}
void ViewBase::setPageLayout( const KoPageLayout &layout )
{
m_pagelayout = layout;
}
bool ViewBase::isActive() const
{
if ( hasFocus() ) {
return true;
}
foreach ( QWidget *v, findChildren<QWidget*>() ) {
if ( v->hasFocus() ) {
return true;
}
}
return false;
}
void ViewBase::updateReadWrite( bool readwrite )
{
m_readWrite = readwrite;
emit readWriteChanged( readwrite );
}
void ViewBase::setGuiActive( bool active ) // virtual slot
{
//debugPlan<<active;
emit guiActivated( this, active );
}
void ViewBase::slotUpdateReadWrite( bool rw )
{
updateReadWrite( rw );
}
KoPrintJob *ViewBase::createPrintJob()
{
KMessageBox::sorry(this, i18n("This view does not support printing."));
return 0;
}
/*static*/
QWidget *ViewBase::createPageLayoutWidget( ViewBase *view )
{
QWidget *widget = new QWidget();
widget->setWindowTitle( xi18nc( "@title:tab", "Page Layout" ) );
QHBoxLayout *lay = new QHBoxLayout(widget);
KoPageLayoutWidget *w = new KoPageLayoutWidget( widget, view->pageLayout() );
w->showPageSpread( false );
lay->addWidget( w, 1 );
KoPagePreviewWidget *prev = new KoPagePreviewWidget( widget );
prev->setPageLayout( view->pageLayout() );
lay->addWidget( prev, 1 );
connect (w, &KoPageLayoutWidget::layoutChanged, prev, &KoPagePreviewWidget::setPageLayout);
return widget;
}
/*static*/
PrintingHeaderFooter *ViewBase::createHeaderFooterWidget( ViewBase *view )
{
PrintingHeaderFooter *widget = new PrintingHeaderFooter( view->printingOptions() );
widget->setWindowTitle( xi18nc( "@title:tab", "Header and Footer" ) );
widget->setOptions( view->printingOptions() );
return widget;
}
void ViewBase::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlan;
QList<QAction*> lst = contextActionList();
if ( ! lst.isEmpty() ) {
QMenu::exec( lst, pos, lst.first() );
}
}
void ViewBase::createOptionActions(int actions)
{
QAction *action;
action = new QAction(this);
action->setSeparator(true);
addContextAction(action);
if (actions & OptionExpand) {
action = new QAction(koIcon("arrow-down"), i18n("Expand All"), this);
connect(action, &QAction::triggered, this, &ViewBase::expandAll);
addContextAction(action);
}
if (actions & OptionExpand) {
action = new QAction(koIcon("arrow-up"), i18n("Collapse All"), this);
connect(action, &QAction::triggered, this, &ViewBase::collapseAll);
addContextAction(action);
}
action = new QAction(this);
action->setSeparator(true);
addContextAction(action);
if (actions & OptionPrint) {
action = KStandardAction::create(KStandardAction::Print, mainWindow(), SLOT(slotFilePrint()), this);
action->setObjectName("print");
addContextAction(action);
}
if (actions & OptionPrintPreview) {
action = KStandardAction::create(KStandardAction::PrintPreview, mainWindow(), SLOT(slotFilePrintPreview()), this);
action->setObjectName("print preview");
addContextAction(action);
}
if (actions & OptionPrintPdf) {
action = new QAction(koIcon("application-pdf"), i18n("Print to PDF..."), this);
action->setObjectName("print pdf");
connect(action, SIGNAL(triggered()), mainWindow(), SLOT(exportToPdf()));
addContextAction(action);
}
if (actions & OptionPrintConfig) {
action = new QAction(koIcon("configure"), i18n("Print Options..."), this);
action->setObjectName("print options");
connect(action, &QAction::triggered, this, &ViewBase::slotOptions);
addContextAction(action);
}
action = new QAction(this);
action->setSeparator(true);
addContextAction(action);
if (actions & OptionViewConfig) {
action = new QAction(koIcon("configure"), i18n("Configure View..."), this);
action->setObjectName("configure view");
connect(action, &QAction::triggered, this, &ViewBase::slotOptions);
addContextAction(action);
}
}
void ViewBase::slotOptionsFinished( int result )
{
if ( result == QDialog::Accepted ) {
emit optionsModified();
}
if ( sender() ) {
sender()->deleteLater();
}
}
bool ViewBase::loadContext( const KoXmlElement &context )
{
KoXmlElement me;
forEachElement( me, context ) {
if ( me.tagName() == "page-layout" ) {
m_pagelayout.format = KoPageFormat::formatFromString( me.attribute( "format" ) );
m_pagelayout.orientation = me.attribute( "orientation" ) == "landscape" ? KoPageFormat::Landscape : KoPageFormat::Portrait;
m_pagelayout.width = me.attribute( "width", "0.0" ).toDouble();
m_pagelayout.height = me.attribute( "height", "0.0" ).toDouble();
m_pagelayout.leftMargin = me.attribute( "left-margin", QString::number( MM_TO_POINT( 20.0 ) ) ).toDouble();
m_pagelayout.rightMargin = me.attribute( "right-margin", QString::number( MM_TO_POINT( 20.0 ) ) ).toDouble();
m_pagelayout.topMargin = me.attribute( "top-margin", QString::number( MM_TO_POINT( 20.0 ) ) ).toDouble();
m_pagelayout.bottomMargin = me.attribute( "bottom-margin", QString::number( MM_TO_POINT( 20.0 ) ) ).toDouble();
} else if ( me.tagName() == "printing-options" ) {
m_printingOptions.loadXml( me );
} else if ( me.tagName() == "dockers" ) {
KoXmlElement e;
forEachElement ( e, me ) {
DockWidget *ds = findDocker( e.attribute( "id" ) );
if ( ds ) {
ds->loadXml( e );
}
}
}
}
return true;
}
void ViewBase::saveContext( QDomElement &context ) const
{
QDomElement me = context.ownerDocument().createElement( "page-layout" );
context.appendChild( me );
me.setAttribute( "format", KoPageFormat::formatString( m_pagelayout.format ) );
me.setAttribute( "orientation", m_pagelayout.orientation == KoPageFormat::Portrait ? "portrait" : "landscape" );
me.setAttribute( "width", QString::number(m_pagelayout.width) );
me.setAttribute( "height",QString::number(m_pagelayout. height) );
me.setAttribute( "left-margin", QString::number(m_pagelayout.leftMargin) );
me.setAttribute( "right-margin", QString::number(m_pagelayout.rightMargin) );
me.setAttribute( "top-margin", QString::number(m_pagelayout.topMargin) );
me.setAttribute( "bottom-margin",QString::number( m_pagelayout.bottomMargin) );
m_printingOptions.saveXml( context );
if ( ! m_dockers.isEmpty() ) {
QDomElement e = context.ownerDocument().createElement( "dockers" );
context.appendChild( e );
foreach ( const DockWidget *ds, m_dockers ) {
ds->saveXml( e );
}
}
}
void ViewBase::addDocker( DockWidget *ds )
{
//addAction( "view_docker_list", ds->toggleViewAction() );
m_dockers << ds;
}
QList<DockWidget*> ViewBase::dockers() const
{
return m_dockers;
}
DockWidget* ViewBase::findDocker( const QString &id ) const
{
foreach ( DockWidget *ds, m_dockers ) {
if ( ds->id == id ) {
return ds;
}
}
return 0;
}
//----------------------
TreeViewPrintingDialog::TreeViewPrintingDialog( ViewBase *view, TreeViewBase *treeview, Project *project )
: PrintingDialog( view ),
m_tree( treeview ),
m_project( project ),
m_firstRow( -1 )
{
printer().setFromTo( documentFirstPage(), documentLastPage() );
}
int TreeViewPrintingDialog::documentLastPage() const
{
int page = documentFirstPage();
while ( firstRow( page ) != -1 ) {
++page;
}
if ( page > documentFirstPage() ) {
--page;
}
return page;
}
int TreeViewPrintingDialog::firstRow( int page ) const
{
debugPlan<<page;
int pageNumber = page - documentFirstPage();
QHeaderView *mh = m_tree->header();
int height = mh->height();
int hHeight = headerRect().height();
int fHeight = footerRect().height();
QRect pageRect = const_cast<TreeViewPrintingDialog*>( this )->printer().pageRect();
int gap = 8;
int pageHeight = pageRect.height() - height;
if ( hHeight > 0 ) {
pageHeight -= ( hHeight + gap );
}
if ( fHeight > 0 ) {
pageHeight -= ( fHeight + gap );
}
int rowsPrPage = pageHeight / height;
int rows = m_tree->model()->rowCount();
int row = -1;
for ( int i = 0; i < rows; ++i ) {
if ( ! m_tree->isRowHidden( i, QModelIndex() ) ) {
row = i;
break;
}
}
if ( row != -1 ) {
QModelIndex idx = m_tree->model()->index( row, 0, QModelIndex() );
row = 0;
while ( idx.isValid() ) {
if ( row >= rowsPrPage * pageNumber ) {
debugPlan<<page<<pageNumber;
break;
}
++row;
idx = m_tree->indexBelow( idx );
}
if ( ! idx.isValid() ) {
row = -1;
}
}
debugPlan<<"Page"<<page<<":"<<(row==-1?"empty":"first row=")<<row<<"("<<rowsPrPage<<")";
return row;
}
QList<QWidget*> TreeViewPrintingDialog::createOptionWidgets() const
{
QList<QWidget*> lst;
lst << createPageLayoutWidget();
lst += PrintingDialog::createOptionWidgets();
return lst;
}
void TreeViewPrintingDialog::printPage( int page, QPainter &painter )
{
m_firstRow = firstRow( page );
QHeaderView *mh = m_tree->header();
int length = mh->length();
int height = mh->height();
QRect hRect = headerRect();
QRect fRect = footerRect();
QRect pageRect = printer().pageRect();
pageRect.moveTo( 0, 0 );
QRect paperRect = printer().paperRect();
QAbstractItemModel *model = m_tree->model();
debugPlan<<pageRect<<paperRect;
painter.translate( pageRect.topLeft() );
painter.setClipping( true );
if ( m_project ) {
paintHeaderFooter( painter, m_view->printingOptions(), page, *(m_project) );
}
int gap = 8;
int pageHeight = pageRect.height() - height;
if ( hRect.isValid() ) {
pageHeight -= ( hRect.height() + gap );
}
if ( fRect.isValid() ) {
pageHeight -= ( fRect.height() + gap );
}
int rowsPrPage = pageHeight / height;
double sx = pageRect.width() > length ? 1.0 : (double)pageRect.width() / (double)length;
double sy = 1.0;
painter.scale( sx, sy );
int h = 0;
painter.translate( 0, hRect.height() + gap );
h = hRect.height() + gap;
painter.setPen(Qt::black);
painter.setBrush( Qt::lightGray );
int higestIndex = 0;
int rightpos = 0;
for ( int i = 0; i < mh->count(); ++i ) {
QString text = model->headerData( i, Qt::Horizontal ).toString();
QVariant a = model->headerData( i, Qt::Horizontal, Qt::TextAlignmentRole );
int align = a.isValid() ? a.toInt() : (int)(Qt::AlignLeft|Qt::AlignVCenter);
if ( ! mh->isSectionHidden( i ) ) {
QRect r = QRect( mh->sectionPosition( i ), 0, mh->sectionSize( i ), height ).adjusted(0, 0, 0, -painter.pen().width());
if (rightpos < r.right()) {
higestIndex = i;
rightpos = r.right();
}
painter.drawRect( r );
// FIXME There is a bug somewhere, the text somehow overwites the rect outline for the first column!
painter.save();
painter.setBrush(QBrush());
painter.drawText( r.adjusted(3, 1, -3, -1), align, text );
painter.drawRect( r );
painter.restore();
}
//debugPlan<<text<<"hidden="<<h->isSectionHidden( i )<<h->sectionPosition( i );
}
if ( m_firstRow == -1 ) {
debugPlan<<"No data";
return;
}
painter.setBrush( QBrush() );
QModelIndex idx = model->index( m_firstRow, 0, QModelIndex() );
int numRows = 0;
//debugPlan<<page<<rowsPrPage;
while ( idx.isValid() && numRows < rowsPrPage ) {
painter.translate( 0, height );
h += height;
for ( int i = 0; i < mh->count(); ++i ) {
if ( mh->isSectionHidden( i ) ) {
continue;
}
Qt::Edges edges = Qt::BottomEdge | Qt::LeftEdge;
QModelIndex index = model->index( idx.row(), i, idx.parent() );
QString text = model->data( index ).toString();
QVariant a = model->data( index, Qt::TextAlignmentRole );
int align = a.isValid() ? a.toInt() : (int)(Qt::AlignLeft|Qt::AlignVCenter);
QRect r( mh->sectionPosition( i ), 0, mh->sectionSize( i ), height );
if (higestIndex == i) {
edges |= Qt::RightEdge;
r.adjust(0, 0, 1, 0);
}
drawRect( painter, r, edges );
painter.drawText( r.adjusted(3, 1, -3, -1) , align, text );
}
++numRows;
idx = m_tree->indexBelow( idx );
}
}
/**
* TreeViewBase is a QTreeView adapted for operation by keyboard only and as components in DoubleTreeViewBase.
* Note that keyboard navigation and selection behavior may not be fully compliant with QTreeView.
* If you use other settings than QAbstractItemView::ExtendedSelection and QAbstractItemView::SelectRows,
* you should have a look at the implementation keyPressEvent() and updateSelection().
*/
TreeViewBase::TreeViewBase( QWidget *parent )
: QTreeView( parent ),
m_arrowKeyNavigation( true ),
m_acceptDropsOnView( false ),
m_readWrite( false )
{
setDefaultDropAction( Qt::MoveAction );
setItemDelegate( new ItemDelegate( this ) );
setAlternatingRowColors ( true );
header()->setContextMenuPolicy( Qt::CustomContextMenu );
connect( header(), &QWidget::customContextMenuRequested, this, &TreeViewBase::slotHeaderContextMenuRequested );
}
void TreeViewBase::dropEvent( QDropEvent *e )
{
debugPlan;
QTreeView::dropEvent( e );
}
KoPrintJob * TreeViewBase::createPrintJob( ViewBase *parent )
{
TreeViewPrintingDialog *dia = new TreeViewPrintingDialog( parent, this, parent->project() );
dia->printer().setCreator( QString( "Plan %1" ).arg( PLAN_VERSION_STRING ) );
// dia->printer().setFullPage(true); // ignore printer margins
return dia;
}
void TreeViewBase::setReadWrite( bool rw )
{
m_readWrite = rw;
if ( model() ) {
model()->setData( QModelIndex(), rw, Role::ReadWrite );
}
}
void TreeViewBase::createItemDelegates( ItemModelBase *model )
{
for ( int c = 0; c < model->columnCount(); ++c ) {
QAbstractItemDelegate *delegate = model->createDelegate( c, this );
if ( delegate ) {
setItemDelegateForColumn( c, delegate );
}
}
}
void TreeViewBase::slotHeaderContextMenuRequested( const QPoint& pos )
{
//debugPlan;
emit headerContextMenuRequested( header()->mapToGlobal( pos ) );
}
void TreeViewBase::setColumnsHidden( const QList<int> &lst )
{
//debugPlan<<m_hideList;
int prev = -1;
QList<int> xlst;
foreach ( int c, lst ) {
if ( c == -1 ) {
// hide rest
for ( int i = prev+1; i < model()->columnCount(); ++i ) {
if ( ! lst.contains( i ) ) {
xlst << i;
}
}
break;
}
xlst << c;
prev = c;
}
for ( int c = 0; c < model()->columnCount(); ++c ) {
setColumnHidden( c, xlst.contains( c ) );
}
}
QModelIndex TreeViewBase::firstColumn( int row, const QModelIndex &parent )
{
int s;
for ( s = 0; s < header()->count(); ++s ) {
if ( ! header()->isSectionHidden( header()->logicalIndex( s ) ) ) {
break;
}
}
if ( s == -1 ) {
return QModelIndex();
}
return model()->index( row, header()->logicalIndex( s ), parent );
}
QModelIndex TreeViewBase::lastColumn( int row, const QModelIndex &parent )
{
int s;
for ( s = header()->count() - 1; s >= 0; --s ) {
if ( ! header()->isSectionHidden( header()->logicalIndex( s ) ) ) {
break;
}
}
if ( s == -1 ) {
return QModelIndex();
}
return model()->index( row, header()->logicalIndex( s ), parent );
}
QModelIndex TreeViewBase::nextColumn( const QModelIndex &curr )
{
return moveCursor( curr, QAbstractItemView::MoveRight );
}
QModelIndex TreeViewBase::previousColumn( const QModelIndex &curr )
{
return moveCursor( curr, QAbstractItemView::MoveLeft );
}
QModelIndex TreeViewBase::firstEditable( int row, const QModelIndex &parent )
{
QModelIndex index = firstColumn( row, parent );
if ( model()->flags( index ) & Qt::ItemIsEditable ) {
return index;
}
return moveToEditable( index, QAbstractItemView::MoveRight );
}
QModelIndex TreeViewBase::lastEditable( int row, const QModelIndex &parent )
{
QModelIndex index = lastColumn( row, parent );
if ( model()->flags( index ) & Qt::ItemIsEditable ) {
return index;
}
return moveToEditable( index, QAbstractItemView::MoveLeft );
}
// Reimplemented to fix qt bug 160083: Doesn't scroll horizontally.
void TreeViewBase::scrollTo(const QModelIndex &index, ScrollHint hint)
{
//debugPlan<<objectName()<<index<<hint;
if ( ! hasFocus() ) {
return;
}
QTreeView::scrollTo( index, hint ); // scrolls vertically
if ( ! index.isValid() ) {
return;
}
// horizontal
int viewportWidth = viewport()->width();
int horizontalOffset = header()->offset();
int horizontalPosition = header()->sectionPosition(index.column());
int cellWidth = header()->sectionSize(index.column());
if (hint == PositionAtCenter) {
horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
} else {
if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
horizontalScrollBar()->setValue(horizontalPosition);
else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
}
}
void TreeViewBase::focusInEvent(QFocusEvent *event)
{
//debugPlan<<event->reason();
QAbstractScrollArea::focusInEvent(event); //NOTE: not QTreeView
if ( event->reason() == Qt::MouseFocusReason ) {
return;
}
QModelIndex curr = currentIndex();
if ( ! curr.isValid() || ! isIndexHidden( curr ) ) {
return;
}
QModelIndex idx = curr;
for ( int s = 0; s < header()->count(); ++s) {
idx = model()->index( curr.row(), header()->logicalIndex( s ), curr.parent() );
if ( ! isIndexHidden( idx ) ) {
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
scrollTo( idx );
break;
}
}
}
/*!
\reimp
*/
void TreeViewBase::keyPressEvent(QKeyEvent *event)
{
//debugPlan<<objectName()<<event->key()<<","<<m_arrowKeyNavigation;
if ( !m_arrowKeyNavigation ) {
QTreeView::keyPressEvent( event );
return;
}
QModelIndex current = currentIndex();
if ( current.isValid() ) {
switch (event->key()) {
case Qt::Key_Right: {
QModelIndex nxt = moveCursor( MoveRight, Qt::NoModifier );
if ( nxt.isValid() ) {
selectionModel()->setCurrentIndex( nxt, QItemSelectionModel::NoUpdate );
} else {
emit moveAfterLastColumn( current );
}
event->accept();
return;
break;
}
case Qt::Key_Left: {
QModelIndex prv = moveCursor( MoveLeft, Qt::NoModifier );
if ( prv.isValid() ) {
selectionModel()->setCurrentIndex( prv, QItemSelectionModel::NoUpdate );
} else {
emit moveBeforeFirstColumn( current );
}
event->accept();
return;
break;
}
case Qt::Key_Down: {
QModelIndex i = moveCursor( MoveDown, Qt::NoModifier );
updateSelection( current, i, event );
event->accept();
return;
break;
}
case Qt::Key_Up: {
QModelIndex i = moveCursor( MoveUp, Qt::NoModifier );
updateSelection( current, i, event );
event->accept();
return;
break;
}
default: break;
}
}
QTreeView::keyPressEvent(event);
}
void TreeViewBase::updateSelection( const QModelIndex &oldidx, const QModelIndex &newidx, QKeyEvent *event )
{
if ( newidx == oldidx || ! newidx.isValid() ) {
return;
}
if ( !hasFocus() && QApplication::focusWidget() == indexWidget(oldidx) ) {
setFocus();
}
QItemSelectionModel::SelectionFlags command;
// NoUpdate on Key movement and Ctrl
Qt::KeyboardModifiers modifiers = static_cast<const QKeyEvent*>(event)->modifiers();
switch (static_cast<const QKeyEvent*>(event)->key()) {
case Qt::Key_Backtab:
modifiers = modifiers & ~Qt::ShiftModifier; // special case for backtab
Q_FALLTHROUGH();
case Qt::Key_Down:
case Qt::Key_Up:
case Qt::Key_Left:
case Qt::Key_Right:
if (modifiers & Qt::ControlModifier)
command = QItemSelectionModel::NoUpdate;
else if (modifiers & Qt::ShiftModifier)
command = QItemSelectionModel::Select | selectionBehaviorFlags();
else
command = QItemSelectionModel::ClearAndSelect | selectionBehaviorFlags();
break;
default:
break;
}
selectionModel()->setCurrentIndex( newidx, command );
}
void TreeViewBase::mousePressEvent(QMouseEvent *event)
{
// If the mouse is pressed outside any item, the current item should be/remain selected
QPoint pos = event->pos();
QModelIndex index = indexAt(pos);
debugPlan<<index<<event->pos();
if ( ! index.isValid() ) {
index = selectionModel()->currentIndex();
if ( index.isValid() && ! selectionModel()->isSelected( index ) ) {
pos = visualRect( index ).center();
QMouseEvent e( event->type(), pos, mapToGlobal( pos ), event->button(), event->buttons(), event->modifiers() );
QTreeView::mousePressEvent( &e );
event->setAccepted( e.isAccepted() );
debugPlan<<index<<e.pos();
}
return;
}
QTreeView::mousePressEvent( event );
}
void TreeViewBase::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
{
//debugPlan<<editor<<hint;
ItemDelegate *delegate = ::qobject_cast<ItemDelegate*>( sender() );
if ( delegate == 0 ) {
warnPlan<<"Not a KPlato::ItemDelegate, try standard treatment"<<editor<<hint;
return QTreeView::closeEditor( editor, hint );
}
// Hacky, if only hint was an int!
Delegate::EndEditHint endHint = delegate->endEditHint();
// Close editor, do nothing else
QTreeView::closeEditor( editor, QAbstractItemDelegate::NoHint );
QModelIndex index;
switch ( endHint ) {
case Delegate::EditLeftItem:
index = moveToEditable( currentIndex(), MoveLeft );
break;
case Delegate::EditRightItem:
index = moveToEditable( currentIndex(), MoveRight );
break;
case Delegate::EditDownItem:
index = moveToEditable( currentIndex(), MoveDown );
break;
case Delegate::EditUpItem:
index = moveToEditable( currentIndex(), MoveUp );
break;
default:
//debugPlan<<"Standard treatment"<<editor<<hint;
return QTreeView::closeEditor( editor, hint ); // standard treatment
}
if (index.isValid()) {
QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::ClearAndSelect | selectionBehaviorFlags();
//debugPlan<<flags;
QPersistentModelIndex persistent(index);
selectionModel()->setCurrentIndex(persistent, flags);
// currentChanged signal would have already started editing
if (!(editTriggers() & QAbstractItemView::CurrentChanged)) {
edit(persistent);
}
}
}
QModelIndex TreeViewBase::moveToEditable( const QModelIndex &index, CursorAction cursorAction )
{
QModelIndex ix = index;
do {
ix = moveCursor( ix, cursorAction );
} while ( ix.isValid() && ! ( model()->flags( ix ) & Qt::ItemIsEditable ) );
//debugPlan<<ix;
if ( ! ix.isValid() ) {
switch ( cursorAction ) {
case MovePrevious:
case MoveLeft: emit editBeforeFirstColumn( index ); break;
case MoveNext:
case MoveRight: emit editAfterLastColumn( index ); break;
default: break;
}
}
return ix;
}
/*
Reimplemented from QTreeView to make tab/backtab in editor work reasonably well.
Move the cursor in the way described by \a cursorAction, *not* using the
information provided by the button \a modifiers.
*/
QModelIndex TreeViewBase::moveCursor( CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
{
QModelIndex current = currentIndex();
//debugPlan<<cursorAction<<current;
if (!current.isValid()) {
return QTreeView::moveCursor( cursorAction, modifiers );
}
return moveCursor( current, cursorAction, modifiers );
}
QModelIndex TreeViewBase::moveCursor( const QModelIndex &index, CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
{
executeDelayedItemsLayout();
QModelIndex current = index;
int col = current.column();
QModelIndex ix;
switch (cursorAction) {
case MoveDown: {
// TODO: span
// Fetch the index below current.
// This should be the next non-hidden row, same column as current,
// that has a column in current.column()
ix = indexBelow( current );
while ( ix.isValid() && col >= model()->columnCount(ix.parent()) ) {
//debugPlan<<col<<model()->columnCount(ix.parent())<<ix;
ix = indexBelow( ix );
}
if ( ix.isValid() ) {
ix = model()->index( ix.row(), col, ix.parent() );
} // else Here we could go to the top
return ix;
}
case MoveUp: {
// TODO: span
// Fetch the index above current.
// This should be the previous non-hidden row, same column as current,
// that has a column in current.column()
ix = indexAbove( current );
while ( ix.isValid() && col >= model()->columnCount(ix.parent()) ) {
ix = indexAbove( ix );
}
if ( ix.isValid() ) {
ix = model()->index( ix.row(), col, ix.parent() );
} // else Here we could go to the bottom
return ix;
}
case MovePrevious:
case MoveLeft: {
for ( int s = header()->visualIndex( col ) - 1; s >= 0; --s ) {
if ( ! header()->isSectionHidden( header()->logicalIndex( s ) ) ) {
ix = model()->index( current.row(), header()->logicalIndex( s ), current.parent() );
break;
}
}
return ix;
}
case MoveNext:
case MoveRight: {
for ( int s = header()->visualIndex( col ) + 1; s < header()->count(); ++s ) {
if ( ! header()->isSectionHidden( header()->logicalIndex( s ) ) ) {
ix = model()->index( current.row(), header()->logicalIndex( s ), current.parent() );
break;
}
}
return ix;
}
case MovePageUp:
case MovePageDown: {
ix = QTreeView::moveCursor( cursorAction, modifiers );
// Now we are at the correct row, so move to correct column
if ( ix.isValid() ) {
ix = model()->index( ix.row(), col, ix.parent() );
} // else Here we could go to the bottom
return ix;
}
case MoveHome: {
if ( ( modifiers & Qt::ControlModifier ) == 0 ) {
ix = QTreeView::moveCursor( cursorAction, modifiers ); // move to first row
} else { //stay at this row
ix = current;
}
for ( int s = 0; s < header()->count(); ++s ) {
int logicalIndex = header()->logicalIndex( s );
if ( ! isColumnHidden( logicalIndex ) ) {
ix = model()->index( ix.row(), header()->logicalIndex( s ), ix.parent() );
break;
}
}
return ix;
}
case MoveEnd: {
if ( ( modifiers & Qt::ControlModifier ) == 0 ) {
ix = QTreeView::moveCursor( cursorAction, modifiers ); // move to last row
} else { //stay at this row
ix = current;
}
for ( int s = header()->count() - 1; s >= 0; --s ) {
int logicalIndex = header()->logicalIndex( s );
if ( ! isColumnHidden( logicalIndex ) ) {
ix = model()->index( ix.row(), logicalIndex, ix.parent() );
break;
}
}
return ix;
}
default: break;
}
return ix;
}
void TreeViewBase::contextMenuEvent ( QContextMenuEvent *event )
{
debugPlan<<selectionModel()->selectedRows();
emit contextMenuRequested( indexAt(event->pos()), event->globalPos(), selectionModel()->selectedRows() );
}
void TreeViewBase::slotCurrentChanged( const QModelIndex &current, const QModelIndex & )
{
if ( current.isValid() ) {
scrollTo( current );
}
}
void TreeViewBase::setModel( QAbstractItemModel *model )
{
if ( selectionModel() ) {
disconnect( selectionModel(), &QItemSelectionModel::currentChanged, this, &TreeViewBase::slotCurrentChanged );
}
QTreeView::setModel( model );
if ( selectionModel() ) {
connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &TreeViewBase::slotCurrentChanged );
}
setReadWrite( m_readWrite );
}
void TreeViewBase::setSelectionModel( QItemSelectionModel *model )
{
if ( selectionModel() ) {
disconnect( selectionModel(), &QItemSelectionModel::currentChanged, this, &TreeViewBase::slotCurrentChanged );
}
QTreeView::setSelectionModel( model );
if ( selectionModel() ) {
connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &TreeViewBase::slotCurrentChanged );
}
}
void TreeViewBase::setStretchLastSection( bool mode )
{
header()->setStretchLastSection( mode );
}
void TreeViewBase::mapToSection( int col, int section )
{
header()->moveSection( header()->visualIndex( col ), section );
}
int TreeViewBase::section( int col ) const
{
return header()->visualIndex( col );
}
void TreeViewBase::dragMoveEvent(QDragMoveEvent *event)
{
//debugPlan;
if (dragDropMode() == InternalMove
&& (event->source() != this || !(event->possibleActions() & Qt::MoveAction))) {
//debugPlan<<"Internal:"<<event->isAccepted();
return;
}
QTreeView::dragMoveEvent( event );
if ( dropIndicatorPosition() == QAbstractItemView::OnViewport ) {
if ( ! m_acceptDropsOnView ) {
event->ignore();
}
debugPlan<<"On viewport:"<<event->isAccepted();
} else {
QModelIndex index = indexAt( event->pos() );
if ( index.isValid() ) {
emit dropAllowed( index, dropIndicatorPosition(), event );
} else {
event->ignore();
debugPlan<<"Invalid index:"<<event->isAccepted();
}
}
if ( event->isAccepted() ) {
if ( viewport()->cursor().shape() == Qt::ForbiddenCursor ) {
viewport()->unsetCursor();
}
} else if ( viewport()->cursor().shape() != Qt::ForbiddenCursor ) {
viewport()->setCursor( Qt::ForbiddenCursor );
}
debugPlan<<event->isAccepted()<<viewport()->cursor().shape();
}
QModelIndex TreeViewBase::firstVisibleIndex( const QModelIndex &idx ) const
{
int count = model()->columnCount();
for ( int c = 0; c < count; ++c ) {
if ( ! isColumnHidden( c ) ) {
return model()->index( idx.row(), c, model()->parent( idx ) );
}
}
return QModelIndex();
}
bool TreeViewBase::loadContext( const QMetaEnum &map, const KoXmlElement &element, bool expand )
{
//debugPlan<<objectName();
header()->setStretchLastSection( (bool)( element.attribute( "stretch-last-column", "1" ).toInt() ) );
KoXmlElement e = element.namedItem( "columns" ).toElement();
if ( ! e.isNull() ) {
if ( ! map.isValid() ) {
// try numbers
debugPlan<<"invalid map";
for ( int i = model()->columnCount() - 1; i >= 0; --i ) {
QString s = e.attribute( QString( "column-%1" ).arg( i ), "" );
if ( s == "hidden" ) {
hideColumn( i );
} else if ( s == "shown" ) {
showColumn( i );
} else debugPlan<<objectName()<<"Unknown column:"<<s;
}
} else {
for ( int i = model()->columnCount() - 1; i >= 0; --i ) {
QString n = map.key( i );
//debugPlan<<i<<"="<<n;
if ( ! n.isEmpty() ) {
QString s = e.attribute( n, "" );
if ( s == "hidden" ) {
hideColumn( i );
} else if ( s == "shown" ) {
showColumn( i );
} else debugPlan<<objectName()<<"Unknown column:"<<s;
} else debugPlan<<"Column not in enum:"<<i;
}
}
}
e = element.namedItem( "sections" ).toElement();
if ( ! e.isNull() ) {
QHeaderView *h = header();
QString s( "section-%1" );
if ( ! map.isValid() ) {
// try numbers
for ( int i = 0; i < h->count(); ++i ) {
if ( e.hasAttribute( s.arg( i ) ) ) {
int index = e.attribute( s.arg( i ), "-1" ).toInt();
if ( index >= 0 && index < h->count() ) {
header()->moveSection( h->visualIndex( index ), i );
}
}
}
} else {
QMap<int, int > m; // QMap<destination, column>
for ( int i = 0; i < h->count(); ++i ) {
QString n = e.attribute( s.arg( i ) );
if ( n.isEmpty() ) {
continue;
}
int col = map.keyToValue( n.toUtf8() );
if ( col >= 0 && col < h->count() ) {
m.insert( i, col );
}
}
for ( QMap<int, int>::const_iterator it = m.constBegin(); it != m.constEnd(); ++it ) {
QString n = e.attribute( s.arg( it.key() ) );
int current = h->visualIndex( it.value() );
header()->moveSection( current, it.key() );
}
}
}
if (expand) {
loadExpanded(element);
}
return true;
}
void TreeViewBase::saveContext( const QMetaEnum &map, QDomElement &element, bool expand ) const
{
//debugPlan<<objectName();
element.setAttribute( "stretch-last-column", QString::number(header()->stretchLastSection()) );
QDomElement e = element.ownerDocument().createElement( "columns" );
element.appendChild( e );
for ( int i = 0; i < model()->columnCount(); ++i ) {
bool h = isColumnHidden( i );
if ( ! map.isValid() ) {
debugPlan<<"invalid map";
e.setAttribute( QString( "column-%1" ).arg( i ), h ? "hidden" : "shown" );
} else {
QString n = map.key( i );
//debugPlan<<i<<"="<<n;
if ( ! n.isEmpty() ) {
e.setAttribute( n, h ? "hidden" : "shown" );
}
}
}
e = element.ownerDocument().createElement( "sections" );
element.appendChild( e );
QHeaderView *h = header();
for ( int i = 0; i < h->count(); ++i ) {
if ( ! isColumnHidden( h->logicalIndex( i ) ) ) {
if ( ! map.isValid() ) {
e.setAttribute( QString( "section-%1" ).arg( i ), h->logicalIndex( i ) );
} else {
QString n = map.key( h->logicalIndex( i ) );
if ( ! n.isEmpty() ) {
e.setAttribute( QString( "section-%1" ).arg( i ), n );
}
}
}
}
if (expand) {
QDomElement expanded = element.ownerDocument().createElement("expanded");
element.appendChild(expanded);
saveExpanded(expanded);
}
}
ItemModelBase *TreeViewBase::itemModel() const
{
QAbstractItemModel *m = model();
QAbstractProxyModel *p = qobject_cast<QAbstractProxyModel*>( m );
while ( p ) {
m = p->sourceModel();
p = qobject_cast<QAbstractProxyModel*>( m );
}
return qobject_cast<ItemModelBase*>( m );
}
void TreeViewBase::expandRecursive(const QModelIndex &idx, bool xpand)
{
int rowCount = model()->rowCount(idx);
if (rowCount == 0) {
return;
}
xpand ? expand(idx) : collapse(idx);
for (int r = 0; r < rowCount; ++r) {
QModelIndex i = model()->index(r, 0, idx);
Q_ASSERT(i.isValid());
expandRecursive(i, xpand);
}
}
void TreeViewBase::slotExpand()
{
// NOTE: Do not use this, KGantt does not like it
// if (!m_contextMenuIndex.isValid()) {
// expandAll();
// return;
// }
QModelIndex idx = m_contextMenuIndex;
if (idx.column() > 0) {
idx = idx.model()->index(idx.row(), idx.column(), idx.parent());
}
expandRecursive(idx, true);
}
void TreeViewBase::slotCollapse()
{
// NOTE: Do not use this, KGantt does not like it
// if (!m_contextMenuIndex.isValid()) {
// collapseAll();
// return;
// }
QModelIndex idx = m_contextMenuIndex;
if (idx.column() > 0) {
idx = idx.model()->index(idx.row(), 0, idx.parent());
}
expandRecursive(idx, false);
}
void TreeViewBase::setContextMenuIndex(const QModelIndex &idx)
{
m_contextMenuIndex = idx;
}
void TreeViewBase::loadExpanded(const KoXmlElement &element)
{
// we get here on loadContext()
m_loadContextDoc.clear();
KoXmlElement expanded = element.namedItem("expanded").toElement();
if (expanded.isNull()) {
return;
}
KoXml::asQDomElement(m_loadContextDoc, expanded);
// FIXME:
// if data is dependent on schedule manger
// we cannot do anything until schedulemanger is set,
// so we wait a bit and hope everything is ok
QTimer::singleShot(500, this, &TreeViewBase::doContextExpanded);
}
void TreeViewBase::expandRecursivly(QDomElement element, const QModelIndex &parent)
{
if (element.isNull()) {
return;
}
for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
QDomElement e = n.toElement();
if (e.tagName() != "item") {
continue;
}
int childRow = e.attribute("row", "-1").toInt();
if (childRow > -1) {
QModelIndex idx = model()->index(childRow, 0, parent);
if (idx.isValid()) {
setExpanded(idx, true);
expandRecursivly(e, idx);
}
}
}
}
void TreeViewBase::doExpand(QDomDocument &doc)
{
// we get here on setScheduleManager()
m_expandDoc = doc;
QTimer::singleShot(0, this, &TreeViewBase::doExpanded);
}
void TreeViewBase::doContextExpanded()
{
expandRecursivly(m_loadContextDoc.documentElement());
}
void TreeViewBase::doExpanded()
{
expandRecursivly(m_expandDoc.documentElement());
}
void TreeViewBase::saveExpanded(QDomElement &element, const QModelIndex &parent) const
{
for (int r = 0; r < model()->rowCount(parent); ++r) {
QModelIndex idx = model()->index(r, 0, parent);
if (isExpanded(idx)) {
QDomElement e = element.ownerDocument().createElement("item");
e.setAttribute("row", r);
element.appendChild(e);
saveExpanded(e, idx);
}
}
}
//----------------------
DoubleTreeViewPrintingDialog::DoubleTreeViewPrintingDialog( ViewBase *view, DoubleTreeViewBase *treeview, Project *project )
: PrintingDialog( view ),
m_tree( treeview ),
m_project( project ),
m_firstRow( -1 )
{
printer().setFromTo( documentFirstPage(), documentLastPage() );
}
int DoubleTreeViewPrintingDialog::documentLastPage() const
{
debugPlan<<KoPageFormat::formatString( m_view->pageLayout().format );
int page = documentFirstPage();
while ( firstRow( page ) != -1 ) {
++page;
}
if ( page > documentFirstPage() ) {
--page;
}
return page;
}
int DoubleTreeViewPrintingDialog::firstRow( int page ) const
{
debugPlan<<page;
int pageNumber = page - documentFirstPage();
QHeaderView *mh = m_tree->masterView()->header();
QHeaderView *sh = m_tree->slaveView()->header();
int height = mh->height() > sh->height() ? mh->height() : sh->height();
int hHeight = headerRect().height();
int fHeight = footerRect().height();
QRect pageRect = const_cast<DoubleTreeViewPrintingDialog*>( this )->printer().pageRect();
int gap = 8;
int pageHeight = pageRect.height() - height;
if ( hHeight > 0 ) {
pageHeight -= ( hHeight + gap );
}
if ( fHeight > 0 ) {
pageHeight -= ( fHeight + gap );
}
int rowsPrPage = pageHeight / height;
debugPlan<<"rowsPrPage"<<rowsPrPage;
Q_ASSERT( rowsPrPage > 0 );
int rows = m_tree->model()->rowCount();
int row = -1;
for ( int i = 0; i < rows; ++i ) {
if ( ! m_tree->masterView()->isRowHidden( i, QModelIndex() ) ) {
row = i;
break;
}
}
if ( row != -1 ) {
QModelIndex idx = m_tree->model()->index( row, 0, QModelIndex() );
row = 0;
while ( idx.isValid() ) {
if ( row >= rowsPrPage * pageNumber ) {
debugPlan<<page<<pageNumber;
break;
}
++row;
idx = m_tree->masterView()->indexBelow( idx );
}
if ( ! idx.isValid() ) {
row = -1;
}
}
debugPlan<<"Page"<<page<<":"<<(row==-1?"empty":"first row=")<<row<<"("<<rowsPrPage<<")";
return row;
}
QList<QWidget*> DoubleTreeViewPrintingDialog::createOptionWidgets() const
{
QList<QWidget*> lst;
lst << createPageLayoutWidget();
lst += PrintingDialog::createOptionWidgets();
return lst;
}
void DoubleTreeViewPrintingDialog::printPage( int page, QPainter &painter )
{
debugPlan<<page<<"paper size:"<<printer().paperSize()<<"---------------------------";
setPrinterPageLayout( m_view->pageLayout() );
qreal t, l, b, r; printer().getPageMargins( &l, &t, &r, &b, QPrinter::Point );
debugPlan<<page<<"paper size:"<<printer().paperSize()<<printer().pageRect()<<l<<t<<r<<b;
painter.save();
m_firstRow = firstRow( page );
QHeaderView *mh = m_tree->masterView()->header();
QHeaderView *sh = m_tree->slaveView()->header();
int length = mh->length() + sh->length();
int height = mh->height() > sh->height() ? mh->height() : sh->height();
QRect hRect = headerRect();
QRect fRect = footerRect();
QRect pageRect = printer().pageRect();
pageRect.moveTo( 0, 0 );
QRect paperRect = printer().paperRect();
QAbstractItemModel *model = m_tree->model();
Q_ASSERT( model != 0 );
debugPlan<<pageRect<<paperRect;
painter.translate( pageRect.topLeft() );
painter.setClipping( true );
if ( m_project ) {
paintHeaderFooter( painter, printingOptions(), page, *(m_project) );
}
int gap = 8;
int pageHeight = pageRect.height() - height;
if ( hRect.isValid() ) {
pageHeight -= ( hRect.height() + gap );
}
if ( fRect.isValid() ) {
pageHeight -= ( fRect.height() + gap );
}
int rowsPrPage = pageHeight / height;
double sx = pageRect.width() > length ? 1.0 : (double)pageRect.width() / (double)length;
double sy = 1.0;
painter.scale( sx, sy );
int h = 0;
painter.translate( 0, hRect.height() + gap );
h = hRect.height() + gap;
painter.setPen(Qt::black);
painter.setBrush( Qt::lightGray );
int higestIndex = 0;
int rightpos = 0;
for ( int i = 0; i < mh->count(); ++i ) {
QString text = model->headerData( i, Qt::Horizontal ).toString();
QVariant a = model->headerData( i, Qt::Horizontal, Qt::TextAlignmentRole );
int align = a.isValid() ? a.toInt() : (int)(Qt::AlignLeft|Qt::AlignVCenter);
if ( ! mh->isSectionHidden( i ) ) {
QRect r = QRect( mh->sectionPosition( i ), 0, mh->sectionSize( i ), height ).adjusted(0, 0, 0, -painter.pen().width());
if (rightpos < r.right()) {
higestIndex = i;
rightpos = r.right();
}
painter.drawRect( r );
// FIXME There is a bug somewhere, the text somehow overwites the rect outline for the first column!
painter.save();
painter.setBrush(QBrush());
painter.drawText( r.adjusted(3, 1, -3, -1), align, text );
painter.drawRect( r );
painter.restore();
}
if ( ! sh->isSectionHidden( i ) ) {
QRect r = QRect( sh->sectionPosition( i ) + mh->length(), 0, sh->sectionSize( i ), height ).adjusted(0, 0, 0, -painter.pen().width());
if (rightpos < r.right()) {
higestIndex = i;
rightpos = r.right();
}
painter.drawRect( r );
painter.drawText( r.adjusted(3, 1, -3, -1), align, text );
}
//debugPlan<<text<<"hidden="<<h->isSectionHidden( i )<<h->sectionPosition( i );
}
if ( m_firstRow == -1 || model->rowCount() == 0 ) {
debugPlan<<"No data";
painter.restore();
return;
}
painter.setBrush( QBrush() );
QModelIndex idx = model->index( 0, 0 );
for ( int r = 0; r < m_firstRow && idx.isValid(); ++r ) {
idx = m_tree->masterView()->indexBelow( idx );
}
int numRows = 0;
//debugPlan<<page<<rowsPrPage;
while ( idx.isValid() && numRows < rowsPrPage ) {
debugPlan<<"print:"<<idx;
painter.translate( 0, height );
h += height;
for ( int i = 0; i < mh->count(); ++i ) {
if ( mh->isSectionHidden( i ) && sh->isSectionHidden( i ) ) {
continue;
}
Qt::Edges edges = Qt::BottomEdge | Qt::LeftEdge;
QModelIndex index = model->index( idx.row(), i, idx.parent() );
QString text = model->data( index ).toString();
QVariant a = model->data( index, Qt::TextAlignmentRole );
int align = a.isValid() ? a.toInt() : (int)(Qt::AlignLeft|Qt::AlignVCenter);
if ( ! mh->isSectionHidden( i ) ) {
QRect r( mh->sectionPosition( i ), 0, mh->sectionSize( i ), height );
if (higestIndex == i) {
edges |= Qt::RightEdge;
r.adjust(0, 0, 1, 0);
}
drawRect( painter, r, edges );
painter.drawText( r.adjusted(3, 1, -3, -1) , align, text );
}
if ( ! sh->isSectionHidden( i ) ) {
QRect r( sh->sectionPosition( i ) + mh->length(), 0, sh->sectionSize( i ), height );
if (higestIndex == i) {
edges |= Qt::RightEdge;
r.adjust(0, 0, 1, 0);
}
drawRect( painter, r, edges );
painter.drawText( r.adjusted(3, 1, -3, -1), align, text );
}
}
++numRows;
idx = m_tree->masterView()->indexBelow( idx );
}
painter.restore();
}
/**
* DoubleTreeViewBase is a QSplitter containing two treeviews.
* This makes it possible to keep columns visible in one view when scrolling the other view horizontally.
*/
DoubleTreeViewBase::DoubleTreeViewBase( bool /*mode*/, QWidget *parent )
: QSplitter( parent ),
m_rightview( 0 ),
m_selectionmodel( 0 ),
m_readWrite( false ),
m_mode( false )
{
init();
}
DoubleTreeViewBase::DoubleTreeViewBase( QWidget *parent )
: QSplitter( parent ),
m_rightview( 0 ),
m_selectionmodel( 0 ),
m_mode( false )
{
init();
}
DoubleTreeViewBase::~DoubleTreeViewBase()
{
}
KoPrintJob *DoubleTreeViewBase::createPrintJob( ViewBase *parent )
{
DoubleTreeViewPrintingDialog *dia = new DoubleTreeViewPrintingDialog( parent, this, parent->project() );
dia->printer().setCreator( QString( "Plan %1" ).arg( PLAN_VERSION_STRING ) );
// dia->printer().setFullPage(true); // ignore printer margins
return dia;
}
void DoubleTreeViewBase::slotExpand()
{
m_leftview->slotExpand();
}
void DoubleTreeViewBase::slotCollapse()
{
m_leftview->slotCollapse();
}
void DoubleTreeViewBase::setParentsExpanded( const QModelIndex &idx, bool expanded )
{
//debugPlan<<idx<<m_leftview->isExpanded( idx )<<m_rightview->isExpanded( idx );
QModelIndex p = model()->parent( idx );
QList<QModelIndex> lst;
while ( p.isValid() ) {
lst << p;
p = model()->parent( p );
}
while ( ! lst.isEmpty() ) {
p = lst.takeLast();
m_leftview->setExpanded( p, expanded );
m_rightview->setExpanded( m_rightview->firstVisibleIndex( p ), expanded ); //HACK: qt can't handle that column 0 is hidden!
//debugPlan<<p<<m_leftview->isExpanded( p )<<m_rightview->isExpanded( p );
}
}
void DoubleTreeViewBase::init()
{
setOrientation( Qt::Horizontal );
setHandleWidth( 3 );
m_leftview = new TreeViewBase(this);
m_leftview->setObjectName("Left view");
addWidget( m_leftview );
setStretchFactor( 0, 1 );
m_rightview = new TreeViewBase(this);
m_rightview->setObjectName("Right view");
addWidget( m_rightview );
setStretchFactor( 1, 1 );
m_leftview->setTreePosition(-1); // always visual index 0
connect( m_leftview, &TreeViewBase::contextMenuRequested, this, &DoubleTreeViewBase::contextMenuRequested );
connect( m_leftview, &TreeViewBase::headerContextMenuRequested, this, &DoubleTreeViewBase::slotLeftHeaderContextMenuRequested );
connect( m_rightview, &TreeViewBase::contextMenuRequested, this, &DoubleTreeViewBase::contextMenuRequested );
connect( m_rightview, &TreeViewBase::headerContextMenuRequested, this, &DoubleTreeViewBase::slotRightHeaderContextMenuRequested );
connect( m_leftview->verticalScrollBar(), &QAbstractSlider::valueChanged, m_rightview->verticalScrollBar(), &QAbstractSlider::setValue );
connect( m_rightview->verticalScrollBar(), &QAbstractSlider::valueChanged, m_leftview->verticalScrollBar(), &QAbstractSlider::setValue );
connect( m_leftview, &TreeViewBase::moveAfterLastColumn, this, &DoubleTreeViewBase::slotToRightView );
connect( m_rightview, &TreeViewBase::moveBeforeFirstColumn, this, &DoubleTreeViewBase::slotToLeftView );
connect( m_leftview, &TreeViewBase::editAfterLastColumn, this, &DoubleTreeViewBase::slotEditToRightView );
connect( m_rightview, &TreeViewBase::editBeforeFirstColumn, this, &DoubleTreeViewBase::slotEditToLeftView );
connect( m_leftview, &QTreeView::expanded, m_rightview, &QTreeView::expand );
connect( m_leftview, &QTreeView::collapsed, m_rightview, &QTreeView::collapse );
connect( m_rightview, &QTreeView::expanded, m_leftview, &QTreeView::expand );
connect( m_rightview, &QTreeView::collapsed, m_leftview, &QTreeView::collapse );
connect( m_leftview, &TreeViewBase::dropAllowed, this, &DoubleTreeViewBase::dropAllowed );
connect( m_rightview, &TreeViewBase::dropAllowed, this, &DoubleTreeViewBase::dropAllowed );
m_actionSplitView = new QAction(koIcon("view-split-left-right"), QString(), this);
setViewSplitMode( true );
connect( m_leftview->header(), &QHeaderView::sortIndicatorChanged, this, &DoubleTreeViewBase::slotLeftSortIndicatorChanged );
connect( m_rightview->header(), &QHeaderView::sortIndicatorChanged, this, &DoubleTreeViewBase::slotRightSortIndicatorChanged );
}
void DoubleTreeViewBase::slotLeftSortIndicatorChanged( int logicalIndex, Qt::SortOrder /*order*/ )
{
QSortFilterProxyModel *sf = qobject_cast<QSortFilterProxyModel*>( model() );
if ( sf ) {
ItemModelBase *m = m_rightview->itemModel();
if ( m ) {
sf->setSortRole( m->sortRole( logicalIndex ) );
}
}
m_leftview->header()->setSortIndicatorShown( true );
// sorting controlled by left treeview, turn right off
m_rightview->header()->setSortIndicatorShown( false );
}
void DoubleTreeViewBase::slotRightSortIndicatorChanged( int logicalIndex, Qt::SortOrder /*order*/ )
{
QSortFilterProxyModel *sf = qobject_cast<QSortFilterProxyModel*>( model() );
if ( sf ) {
ItemModelBase *m = m_rightview->itemModel();
if ( m ) {
sf->setSortRole( m->sortRole( logicalIndex ) );
}
}
m_rightview->header()->setSortIndicatorShown( true );
// sorting controlled by right treeview, turn left off
m_leftview->header()->setSortIndicatorShown( false );
}
QList<int> DoubleTreeViewBase::expandColumnList( const QList<int> &lst ) const
{
QList<int> mlst = lst;
if ( ! mlst.isEmpty() ) {
int v = 0;
if ( mlst.last() == -1 && mlst.count() > 1 ) {
v = mlst[ mlst.count() - 2 ] + 1;
mlst.removeLast();
}
for ( int c = v; c < model()->columnCount(); ++c ) {
mlst << c;
}
}
return mlst;
}
void DoubleTreeViewBase::hideColumns( TreeViewBase *view, const QList<int> &list )
{
view->setColumnsHidden( list );
}
void DoubleTreeViewBase::hideColumns( const QList<int> &masterList, const QList<int> &slaveList )
{
m_leftview->setColumnsHidden( masterList );
m_rightview->setColumnsHidden( slaveList );
if ( m_rightview->isHidden() ) {
QList<int> mlst = expandColumnList( masterList );
QList<int> slst = expandColumnList( slaveList );
QList<int> lst;
for ( int c = 0; c < model()->columnCount(); ++c ) {
// only hide columns hidden in *both* views
//debugPlan<<c<<(mlst.indexOf( c ))<<(slst.indexOf( c ));
if ( (mlst.indexOf( c ) >= 0) && (slst.indexOf( c ) >= 0) ) {
lst << c;
}
}
//debugPlan<<lst;
m_leftview->setColumnsHidden( lst );
} else {
setStretchFactors();
}
}
void DoubleTreeViewBase::slotToRightView( const QModelIndex &index )
{
//debugPlan<<index.column();
QModelIndex nxt = m_rightview->firstColumn( index.row(), model()->parent( index ) );
m_rightview->setFocus();
if ( nxt.isValid() ) {
m_selectionmodel->setCurrentIndex( nxt, QItemSelectionModel::NoUpdate );
}
}
void DoubleTreeViewBase::slotToLeftView( const QModelIndex &index )
{
//debugPlan<<index.column();
QModelIndex prv = m_leftview->lastColumn( index.row(), model()->parent( index ) );
m_leftview->setFocus();
if ( prv.isValid() ) {
m_selectionmodel->setCurrentIndex( prv, QItemSelectionModel::NoUpdate );
}
}
void DoubleTreeViewBase::slotEditToRightView( const QModelIndex &index )
{
//debugPlan<<index.column()<<endl;
if ( m_rightview->isHidden() ) {
return;
}
m_rightview->setFocus();
QModelIndex nxt = m_rightview->firstEditable( index.row(), model()->parent ( index ) );
if ( nxt.isValid() && ( model()->flags( nxt ) & Qt::ItemIsEditable ) ) {
m_selectionmodel->setCurrentIndex( nxt, QItemSelectionModel::NoUpdate );
m_rightview->edit( nxt );
} else {
slotToRightView( index );
}
}
void DoubleTreeViewBase::slotEditToLeftView( const QModelIndex &index )
{
//debugPlan<<index.column()<<endl;
if ( m_leftview->isHidden() ) {
return;
}
m_leftview->setFocus();
QModelIndex nxt = m_leftview->lastEditable( index.row(), model()->parent ( index ) );
if ( nxt.isValid() && ( model()->flags( nxt ) & Qt::ItemIsEditable ) ) {
m_selectionmodel->setCurrentIndex( nxt, QItemSelectionModel::NoUpdate );
m_leftview->edit( nxt );
} else {
slotToLeftView( index );
}
}
void DoubleTreeViewBase::setReadWrite( bool rw )
{
m_readWrite = rw;
m_leftview->setReadWrite( rw );
m_rightview->setReadWrite( rw );
}
void DoubleTreeViewBase::closePersistentEditor( const QModelIndex &index )
{
m_leftview->closePersistentEditor( index );
m_rightview->closePersistentEditor( index );
}
void DoubleTreeViewBase::setModel( QAbstractItemModel *model )
{
m_leftview->setModel( model );
m_rightview->setModel( model );
if ( m_selectionmodel ) {
disconnect( m_selectionmodel, &QItemSelectionModel::selectionChanged, this, &DoubleTreeViewBase::slotSelectionChanged );
disconnect( m_selectionmodel, &QItemSelectionModel::currentChanged, this, &DoubleTreeViewBase::currentChanged );
}
m_selectionmodel = m_leftview->selectionModel();
m_rightview->setSelectionModel( m_selectionmodel );
connect( m_selectionmodel, &QItemSelectionModel::selectionChanged, this, &DoubleTreeViewBase::slotSelectionChanged );
connect( m_selectionmodel, &QItemSelectionModel::currentChanged, this, &DoubleTreeViewBase::currentChanged );
setReadWrite( m_readWrite );
}
QAbstractItemModel *DoubleTreeViewBase::model() const
{
return m_leftview->model();
}
void DoubleTreeViewBase::slotSelectionChanged( const QItemSelection &sel, const QItemSelection & )
{
emit selectionChanged( sel.indexes() );
}
void DoubleTreeViewBase::setSelectionModel( QItemSelectionModel *model )
{
m_leftview->setSelectionModel( model );
m_rightview->setSelectionModel( model );
}
void DoubleTreeViewBase::setSelectionMode( QAbstractItemView::SelectionMode mode )
{
m_leftview->setSelectionMode( mode );
m_rightview->setSelectionMode( mode );
}
void DoubleTreeViewBase::setSelectionBehavior( QAbstractItemView::SelectionBehavior mode )
{
m_leftview->setSelectionBehavior( mode );
m_rightview->setSelectionBehavior( mode );
}
void DoubleTreeViewBase::setItemDelegateForColumn( int col, QAbstractItemDelegate * delegate )
{
m_leftview->setItemDelegateForColumn( col, delegate );
m_rightview->setItemDelegateForColumn( col, delegate );
}
void DoubleTreeViewBase::createItemDelegates( ItemModelBase *model )
{
m_leftview->createItemDelegates( model );
m_rightview->createItemDelegates( model );
}
void DoubleTreeViewBase::setEditTriggers( QAbstractItemView::EditTriggers mode )
{
m_leftview->setEditTriggers( mode );
m_rightview->setEditTriggers( mode );
}
QAbstractItemView::EditTriggers DoubleTreeViewBase::editTriggers() const
{
return m_leftview->editTriggers();
}
void DoubleTreeViewBase::setStretchLastSection( bool mode )
{
m_rightview->header()->setStretchLastSection( mode );
if ( m_rightview->isHidden() ) {
m_leftview->header()->setStretchLastSection( mode );
}
}
void DoubleTreeViewBase::edit( const QModelIndex &index )
{
if ( ! m_leftview->isColumnHidden( index.column() ) ) {
m_leftview->edit( index );
} else if ( ! m_rightview->isHidden() && ! m_rightview->isColumnHidden( index.column() ) ) {
m_rightview->edit( index );
}
}
void DoubleTreeViewBase::setDragDropMode( QAbstractItemView::DragDropMode mode )
{
m_leftview->setDragDropMode( mode );
m_rightview->setDragDropMode( mode );
}
void DoubleTreeViewBase::setDragDropOverwriteMode( bool mode )
{
m_leftview->setDragDropOverwriteMode( mode );
m_rightview->setDragDropOverwriteMode( mode );
}
void DoubleTreeViewBase::setDropIndicatorShown( bool mode )
{
m_leftview->setDropIndicatorShown( mode );
m_rightview->setDropIndicatorShown( mode );
}
void DoubleTreeViewBase::setDragEnabled ( bool mode )
{
m_leftview->setDragEnabled( mode );
m_rightview->setDragEnabled( mode );
}
void DoubleTreeViewBase::setAcceptDrops( bool mode )
{
m_leftview->setAcceptDrops( mode );
m_rightview->setAcceptDrops( mode );
}
void DoubleTreeViewBase::setAcceptDropsOnView( bool mode )
{
m_leftview->setAcceptDropsOnView( mode );
m_rightview->setAcceptDropsOnView( mode );
}
void DoubleTreeViewBase::setDefaultDropAction( Qt::DropAction action )
{
m_leftview->setDefaultDropAction( action );
m_rightview->setDefaultDropAction( action );
}
void DoubleTreeViewBase::slotRightHeaderContextMenuRequested( const QPoint &pos )
{
//debugPlan;
emit slaveHeaderContextMenuRequested( pos );
emit headerContextMenuRequested( pos );
}
void DoubleTreeViewBase::slotLeftHeaderContextMenuRequested( const QPoint &pos )
{
//debugPlan;
emit masterHeaderContextMenuRequested( pos );
emit headerContextMenuRequested( pos );
}
void DoubleTreeViewBase::setStretchFactors()
{
int lc = m_leftview->header()->count() - m_leftview->header()->hiddenSectionCount();
int rc = m_rightview->header()->count() - m_rightview->header()->hiddenSectionCount();
setStretchFactor( indexOf( m_rightview ), qMax( 1, qMin( 4, rc / qMax( 1, lc ) ) ) );
//debugPlan<<this<<"set stretch factor="<<qMax( 1, qMin( 4, rc / lc ) );
}
bool DoubleTreeViewBase::loadContext( const QMetaEnum &map, const KoXmlElement &element )
{
//debugPlan;
QList<int> lst1;
QList<int> lst2;
KoXmlElement slave = element.namedItem( "slave" ).toElement();
if (!slave.isNull()) {
if (slave.attribute("hidden", "false") == "true") {
setViewSplitMode(false);
} else {
setStretchFactors();
}
m_rightview->loadContext(map, slave, false);
}
KoXmlElement master = element.namedItem("master").toElement();
if (!master.isNull()) {
m_leftview->loadContext(map, master);
}
return true;
}
void DoubleTreeViewBase::saveContext( const QMetaEnum &map, QDomElement &element ) const
{
QDomElement master = element.ownerDocument().createElement( "master" );
element.appendChild(master);
m_leftview->saveContext(map, master);
QDomElement slave = element.ownerDocument().createElement( "slave" );
element.appendChild(slave);
if (m_rightview->isHidden()) {
slave.setAttribute("hidden", "true");
}
m_rightview->saveContext(map, slave, false);
}
void DoubleTreeViewBase::setViewSplitMode( bool split )
{
if ( split ) {
m_actionSplitView->setText( i18n( "Unsplit View" ) );
m_actionSplitView->setIcon(koIcon("view-close"));
} else {
m_actionSplitView->setText( i18n( "Split View" ) );
m_actionSplitView->setIcon(koIcon("view-split-left-right"));
}
if ( m_mode == split ) {
return;
}
m_mode = split;
if ( split ) {
m_leftview->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
m_leftview->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
if ( model() ) {
m_rightview->setColumnHidden( 0, true );
m_leftview->resizeColumnToContents( 0 );
for ( int c = 1; c < m_rightview->model()->columnCount(); ++c ) {
if ( m_leftview->isColumnHidden( c ) ) {
m_rightview->setColumnHidden( c, true );
} else {
m_rightview->setColumnHidden( c, false );
m_rightview->mapToSection( c, m_leftview->section( c ) );
m_leftview->setColumnHidden( c, true );
m_rightview->resizeColumnToContents( c );
}
}
}
m_rightview->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
m_rightview->show();
} else {
m_rightview->hide();
if ( model() ) {
int offset = m_rightview->isColumnHidden( 0 ) ? 1 : 0;
for ( int c = 0; c < model()->columnCount(); ++c ) {
if ( ! m_rightview->isColumnHidden( c ) ) {
m_leftview->setColumnHidden( c, false );
m_leftview->mapToSection( c, m_rightview->section( c ) + offset );
m_leftview->resizeColumnToContents( c );
}
}
}
m_leftview->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
m_leftview->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
}
}
void DoubleTreeViewBase::setRootIsDecorated ( bool show )
{
m_leftview->setRootIsDecorated( show );
m_rightview->setRootIsDecorated( show );
}
QModelIndex DoubleTreeViewBase::indexAt( const QPoint &pos ) const
{
QModelIndex idx = m_leftview->indexAt( pos );
if ( ! idx.isValid() ) {
idx = m_rightview->indexAt( pos );
}
return idx;
}
void DoubleTreeViewBase::setContextMenuIndex(const QModelIndex &idx)
{
m_leftview->setContextMenuIndex(idx);
m_rightview->setContextMenuIndex(idx);
}
} // namespace KPlato
diff --git a/src/libs/ui/kptwbsdefinitiondialog.cpp b/src/libs/ui/kptwbsdefinitiondialog.cpp
index 88210f89..5c4873f0 100644
--- a/src/libs/ui/kptwbsdefinitiondialog.cpp
+++ b/src/libs/ui/kptwbsdefinitiondialog.cpp
@@ -1,58 +1,59 @@
/* This file is part of the KDE project
Copyright (C) 2005 Dag Andersen <danders@get2net.dk>
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 "kptwbsdefinitiondialog.h"
#include "kptwbsdefinitionpanel.h"
#include "kptwbsdefinition.h"
#include <kptcommand.h>
#include <KLocalizedString>
namespace KPlato
{
WBSDefinitionDialog::WBSDefinitionDialog(Project &project, WBSDefinition &def, QWidget *p)
: KoDialog(p)
{
setCaption( i18n("WBS Definition") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
m_panel = new WBSDefinitionPanel(project, def, this);
setMainWidget(m_panel);
enableButtonOk(false);
connect(m_panel, &WBSDefinitionPanel::changed, this, &KoDialog::enableButtonOk);
connect(this, &KoDialog::okClicked, this, &WBSDefinitionDialog::slotOk);
}
KUndo2Command *WBSDefinitionDialog::buildCommand() {
return m_panel->buildCommand();
}
void WBSDefinitionDialog::slotOk() {
if (!m_panel->ok())
return;
accept();
}
} //KPlato namespace
diff --git a/src/libs/ui/kptwbsdefinitionpanel.cpp b/src/libs/ui/kptwbsdefinitionpanel.cpp
index ac1e836d..df53e00f 100644
--- a/src/libs/ui/kptwbsdefinitionpanel.cpp
+++ b/src/libs/ui/kptwbsdefinitionpanel.cpp
@@ -1,225 +1,226 @@
/* This file is part of the KDE project
Copyright (C) 2005-2007, 2012 Dag Andersen <danders@get2net.dk>
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 "kptwbsdefinitionpanel.h"
#include "kptwbsdefinition.h"
#include "kptcommand.h"
#include "kptproject.h"
#include "kptdebug.h"
#include <kcombobox.h>
#include <QComboBox>
#include <QTableWidget>
#include <QStringList>
#include <kundo2command.h>
namespace KPlato
{
ComboBoxDelegate::ComboBoxDelegate(QStringList &list, QObject *parent)
: QStyledItemDelegate(parent)
{
debugPlan;
setObjectName("ComboBoxDelegate");
m_list = list;
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
debugPlan;
QComboBox *editor = new KComboBox(parent);
editor->installEventFilter(const_cast<ComboBoxDelegate*>(this));
return editor;
}
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString value = index.model()->data(index, Qt::DisplayRole).toString();
debugPlan<<value<<":"<<m_list;
QComboBox *comboBox = static_cast<QComboBox*>(editor);
comboBox->insertItems(0, m_list);
comboBox->setCurrentIndex(comboBox->findText(value));
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QComboBox *comboBox = static_cast<QComboBox*>(editor);
debugPlan<<comboBox->currentText();
model->setData(index, comboBox->currentText());
}
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
//----------------------
WBSDefinitionPanel::WBSDefinitionPanel( Project &project, WBSDefinition &def, QWidget *p, const char *n)
: QWidget(p),
m_project( project ),
m_def(def),
selectedRow(-1)
{
setObjectName(n);
setupUi(this);
projectCode->setText( m_def.projectCode() );
projectSeparator->setText( m_def.projectSeparator() );
QStringList codeList = m_def.codeList();
debugPlan<<codeList;
defaultSeparator->setText(m_def.defaultSeparator());
defaultCode->addItems(codeList);
defaultCode->setCurrentIndex(m_def.defaultCodeIndex());
defaultCode->setFocus();
levelsTable->setItemDelegateForColumn(0, new ComboBoxDelegate(codeList, this));
levelsGroup->setChecked(m_def.isLevelsDefEnabled());
int i = 0;
const QMap<int, WBSDefinition::CodeDef> &lev = m_def.levelsDef();
levelsTable->setRowCount(lev.count());
QStringList sl;
debugPlan<<"Map size="<<lev.count();
QMap<int, WBSDefinition::CodeDef>::const_iterator it;
for (it = lev.begin(); it != lev.end(); ++it) {
sl << QString("%1").arg(it.key());
QTableWidgetItem *item = new QTableWidgetItem();
item->setData(Qt::DisplayRole, it.value().code);
levelsTable->setItem(i, 0, item);
item = new QTableWidgetItem();
item->setText(it.value().separator);
levelsTable->setItem(i, 1, item);
i++;
}
levelsTable->setVerticalHeaderLabels(sl);
//levelsTable->setColumnStretchable(0, true);
slotLevelChanged(level->value());
connect(projectCode, &QLineEdit::textChanged, this, &WBSDefinitionPanel::slotChanged);
connect(projectSeparator, &QLineEdit::textChanged, this, &WBSDefinitionPanel::slotChanged);
connect(defaultCode, SIGNAL(activated(int)), SLOT(slotChanged()));
connect(defaultSeparator, &QLineEdit::textChanged, this, &WBSDefinitionPanel::slotChanged);
connect(levelsGroup, &QGroupBox::toggled, this, &WBSDefinitionPanel::slotLevelsGroupToggled);
connect(levelsTable, &QTableWidget::cellChanged, this, &WBSDefinitionPanel::slotChanged);
connect(levelsTable, &QTableWidget::itemSelectionChanged, this, &WBSDefinitionPanel::slotSelectionChanged);
connect(level, SIGNAL(valueChanged(int)), SLOT(slotLevelChanged(int)));
connect(removeBtn, &QAbstractButton::clicked, this, &WBSDefinitionPanel::slotRemoveBtnClicked);
connect(addBtn, &QAbstractButton::clicked, this, &WBSDefinitionPanel::slotAddBtnClicked);
removeBtn->setEnabled(false);
}
void WBSDefinitionPanel::setStartValues() {
}
KUndo2Command *WBSDefinitionPanel::buildCommand() {
WBSDefinition def = m_def;
def.setProjectCode( projectCode->text() );
def.setProjectSeparator( projectSeparator->text() );
def.setDefaultCode(defaultCode->currentIndex());
def.setDefaultSeparator(defaultSeparator->text());
def.setLevelsDefEnabled(levelsGroup->isChecked());
def.clearLevelsDef();
for (int i = 0; i < levelsTable->rowCount(); ++i) {
def.setLevelsDef(levelsTable->verticalHeaderItem(i)->text().toInt(), levelsTable->item(i, 0)->text(), levelsTable->item(i, 1)->text());
}
WBSDefinitionModifyCmd *cmd = new WBSDefinitionModifyCmd( m_project, def, kundo2_i18n("Modify WBS Code Definition"));
return cmd;
}
bool WBSDefinitionPanel::ok() {
debugPlan;
return true;
}
void WBSDefinitionPanel::slotChanged() {
emit changed(true);
}
void WBSDefinitionPanel::slotSelectionChanged() {
QString s;
selectedRow = -1;
QList<QTableWidgetItem *> items = levelsTable->selectedItems();
if (items.count() == 2 && items[0]->row() == items[1]->row()) {
selectedRow = items[0]->row();
s = QString("Row[%1]=selected ").arg(selectedRow);
} else {
s = "None selected";
}
removeBtn->setEnabled(selectedRow != -1);
debugPlan<<s;
}
void WBSDefinitionPanel::slotRemoveBtnClicked() {
debugPlan<<selectedRow;
if (selectedRow == -1) {
return;
}
levelsTable->removeRow(selectedRow);
removeBtn->setEnabled(false);
slotLevelChanged(level->value());
}
void WBSDefinitionPanel::slotAddBtnClicked() {
debugPlan;
int i=levelsTable->rowCount()-1;
for (; i >= 0; --i) {
debugPlan<<"Checking row["<<i<<"]="<<levelsTable->verticalHeaderItem(i)->text()<<" with"<<level->value();
if (level->value() > levelsTable->verticalHeaderItem(i)->text().toInt()) {
break;
}
}
i++;
levelsTable->insertRow(i);
levelsTable->setVerticalHeaderItem(i, new QTableWidgetItem(QString("%1").arg(level->value())));
QTableWidgetItem *item = new QTableWidgetItem();
item->setData(Qt::DisplayRole, (m_def.codeList().value(m_def.defaultCodeIndex())));
levelsTable->setItem(i, 0, item);
item = new QTableWidgetItem();
item->setText(m_def.defaultSeparator());
levelsTable->setItem(i, 1, item);
addBtn->setEnabled(false);
slotChanged();
debugPlan<<"Added row="<<i<<" level="<<level->value();
}
void WBSDefinitionPanel::slotLevelChanged(int value) {
for (int i=0; i < levelsTable->rowCount(); ++i) {
if (value == levelsTable->verticalHeaderItem(i)->text().toInt()) {
addBtn->setEnabled(false);
return;
}
}
addBtn->setEnabled(levelsGroup->isChecked());
slotChanged();
}
void WBSDefinitionPanel::slotLevelsGroupToggled(bool /*on*/) {
debugPlan;
slotLevelChanged(level->value());
}
} //KPlato namespace
diff --git a/src/libs/ui/kptworkpackagemergedialog.cpp b/src/libs/ui/kptworkpackagemergedialog.cpp
index 797ace3f..a499e129 100644
--- a/src/libs/ui/kptworkpackagemergedialog.cpp
+++ b/src/libs/ui/kptworkpackagemergedialog.cpp
@@ -1,189 +1,190 @@
/* This file is part of the KDE project
Copyright (C) 2011 Dag Andersen <danders@get2net.dk>
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 "kptworkpackagemergedialog.h"
#include "kptpackage.h"
#include "kptproject.h"
#include "kpttask.h"
#include <kmessagebox.h>
#include <kextendableitemdelegate.h>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QList>
#include <QStyle>
#include <QStyleOption>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QCheckBox>
#include "kmessagebox_copy.cpp" // themedMessageBoxIcon()
namespace KPlato
{
PackageInfoWidget::PackageInfoWidget( Package *package, QWidget* parent)
: QFrame( parent ),
m_package( package )
{
setFrameStyle( QFrame::Sunken | QFrame::StyledPanel );
QHBoxLayout *l = new QHBoxLayout( this );
l->addSpacing( 20 );
QCheckBox *w = new QCheckBox( this );
w->setText( i18n( "Used Effort" ) );
w->setCheckState( package->settings.usedEffort ? Qt::Checked : Qt::Unchecked );
l->addWidget( w );
connect(w, &QCheckBox::stateChanged, this, &PackageInfoWidget::slotUsedEffortChanged);
w = new QCheckBox( this );
w->setText( i18n( "Task Progress" ) );
w->setCheckState( package->settings.progress ? Qt::Checked : Qt::Unchecked );
l->addWidget( w );
connect(w, &QCheckBox::stateChanged, this, &PackageInfoWidget::slotProgressChanged);
w = new QCheckBox( this );
w->setText( i18n("&Documents") );
w->setCheckState( package->settings.documents ? Qt::Checked : Qt::Unchecked );
l->addWidget( w );
connect(w, &QCheckBox::stateChanged, this, &PackageInfoWidget::slotDocumentsChanged);
}
void PackageInfoWidget::slotUsedEffortChanged( int s )
{
m_package->settings.usedEffort = (bool)s;
}
void PackageInfoWidget::slotProgressChanged( int s )
{
m_package->settings.progress = (bool)s;
}
void PackageInfoWidget::slotDocumentsChanged( int s )
{
m_package->settings.documents = (bool)s;
}
WorkPackageMergePanel::WorkPackageMergePanel( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
}
WorkPackageMergeDialog::WorkPackageMergeDialog( const QString &text, const QMap<QDateTime, Package*> &list, QWidget *parent )
: KoDialog( parent ),
m_packages( list.values() )
{
panel.ui_text->setText( text );
QIcon icon = themedMessageBoxIcon( QMessageBox::Information );
if ( ! icon.isNull() ) {
QStyleOption option;
option.initFrom( this );
panel.ui_icon->setPixmap( icon.pixmap( style()->pixelMetric( QStyle::PM_MessageBoxIconSize, &option, this ) ) );
}
setButtons( KoDialog::Yes | KoDialog::No );
panel.ui_view->setHeaderHidden( true );
panel.ui_view->setRootIsDecorated( false );
m_delegate = new KExtendableItemDelegate( panel.ui_view );
panel.ui_view->setItemDelegate( m_delegate );
m_model = new QStandardItemModel( panel.ui_view );
foreach( Package *p, m_packages ) {
QList<QStandardItem*> items;
items << new QStandardItem();
items << new QStandardItem( p->project->childNode( 0 )->name() );
items << new QStandardItem( static_cast<Task*>( p->project->childNode( 0 ) )->workPackage().ownerName() );
items << new QStandardItem( QLocale().toString(p->timeTag, QLocale::ShortFormat) );
if ( p->toTask ) {
items[ CheckColumn ]->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable );
items[ CheckColumn ]->setCheckState( Qt::Checked );
items[ TaskNameColumn ]->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
items[ OwnerNameColumn ]->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
items[DateTimeColumn]->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
items[ DateTimeColumn ]->setTextAlignment( Qt::AlignCenter );
}
m_model->appendRow( items );
}
// sort on datetime first
QSortFilterProxyModel *dtsort = new QSortFilterProxyModel( panel.ui_view );
dtsort->setSourceModel( m_model );
dtsort->sort( DateTimeColumn, Qt::DescendingOrder );
// then on task name
QSortFilterProxyModel *tasksort = new QSortFilterProxyModel( panel.ui_view );
tasksort->setSourceModel( dtsort );
tasksort->sort( TaskNameColumn, Qt::AscendingOrder );
panel.ui_view->setModel( tasksort );
for ( int c = 0; c < m_model->columnCount(); ++c ) {
panel.ui_view->resizeColumnToContents( c );
}
setMainWidget( &panel );
slotChanged();
connect(panel.ui_view->model(), &QAbstractItemModel::dataChanged, this, &WorkPackageMergeDialog::slotChanged);
connect(panel.ui_view, &QAbstractItemView::activated, this, &WorkPackageMergeDialog::slotActivated);
}
WorkPackageMergeDialog::~WorkPackageMergeDialog()
{
m_delegate->contractAll();
}
void WorkPackageMergeDialog::slotActivated( const QModelIndex &idx )
{
QModelIndex i = idx;
if ( i.column() >= CheckColumn ) {
i = i.model()->index( i.row(), TaskNameColumn, i.parent() );
}
if ( i.column() != TaskNameColumn ) {
return;
}
if ( m_delegate->isExtended( i ) ) {
m_delegate->contractItem( i );
} else {
m_delegate->extendItem( new PackageInfoWidget( m_packages.at( idx.row() ) ), i );
}
}
QList<int> WorkPackageMergeDialog::checkedList() const
{
QList<int> lst;
int count = m_model->rowCount();
for ( int i = 0; i < count; ++i ) {
if ( m_model->index( i, 0 ).data( Qt::CheckStateRole ).toInt() == Qt::Checked ) {
lst << i;
}
}
return lst;
}
void WorkPackageMergeDialog::slotChanged()
{
enableButton( KoDialog::Yes, m_model->rowCount() > 0 );
}
} // namespace KPlato
diff --git a/src/libs/ui/kptworkpackagesenddialog.cpp b/src/libs/ui/kptworkpackagesenddialog.cpp
index 19503fb1..c2c3108b 100644
--- a/src/libs/ui/kptworkpackagesenddialog.cpp
+++ b/src/libs/ui/kptworkpackagesenddialog.cpp
@@ -1,44 +1,45 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
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 "kptworkpackagesenddialog.h"
#include "kptworkpackagesendpanel.h"
#include "kptdocumentspanel.h"
#include "kpttask.h"
#include <KLocalizedString>
namespace KPlato
{
WorkPackageSendDialog::WorkPackageSendDialog( const QList<Node*> &tasks, ScheduleManager *sm, QWidget *p)
: KoDialog(p)
{
setCaption( xi18nc( "@title:window", "Send Work Packages") );
setButtons( Close );
setDefaultButton( Close );
showButtonSeparator( true );
m_wp = new WorkPackageSendPanel( tasks, sm, this);
setMainWidget( m_wp );
}
} //KPlato namespace
diff --git a/src/libs/ui/kptworkpackagesendpanel.cpp b/src/libs/ui/kptworkpackagesendpanel.cpp
index 6e2e3a63..cf556018 100644
--- a/src/libs/ui/kptworkpackagesendpanel.cpp
+++ b/src/libs/ui/kptworkpackagesendpanel.cpp
@@ -1,73 +1,74 @@
/* This file is part of the KDE project
Copyright (C) 2007 Dag Andersen <danders@get2net.dk>
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 "kptworkpackagesendpanel.h"
#include "kpttaskeditor.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptschedule.h"
#include <KoIcon.h>
#include <KLocalizedString>
#include <QPushButton>
#include <QLineEdit>
#include <QList>
namespace KPlato
{
WorkPackageSendPanel::WorkPackageSendPanel( const QList<Node*> &tasks, ScheduleManager *sm, QWidget *p )
: QWidget(p)
{
setupUi( this );
long id = sm ? sm->scheduleId() : NOTSCHEDULED;
foreach ( Node *n, tasks ) {
Task *t = qobject_cast<Task*>( n );
if ( t == 0 ) {
continue;
}
foreach ( Resource *r, t->workPackage().fetchResources( id ) ) {
m_resMap[ r ] << n;
}
}
QMap<Resource*, QList<Node*> >::const_iterator it;
for ( it = m_resMap.constBegin(); it != m_resMap.constEnd(); ++it ) {
QPushButton *pb = new QPushButton(koIcon("mail-send"), i18n("Send To..."), scrollArea);
QLineEdit *le = new QLineEdit( it.key()->name(), scrollArea );
le->setReadOnly( true );
formLayout->addRow( pb, le );
connect( pb, &QAbstractButton::clicked, this, &WorkPackageSendPanel::slotSendClicked );
m_pbMap[ pb ] = it.key();
}
}
void WorkPackageSendPanel::slotSendClicked()
{
Resource *r = m_pbMap[ qobject_cast<QPushButton*>( sender() ) ];
emit sendWorkpackages( m_resMap[ r ], r );
}
} //KPlato namespace
diff --git a/src/libs/ui/locale/localemon.cpp b/src/libs/ui/locale/localemon.cpp
index b900f4a7..ee44dde0 100644
--- a/src/libs/ui/locale/localemon.cpp
+++ b/src/libs/ui/locale/localemon.cpp
@@ -1,133 +1,134 @@
/*
* localemon.cpp
*
* Copyright (c) 1999-2003 Hans Petter Bieker <bieker@kde.org>
* Copyright (c) 2009, 2012 Dag Andersen <danders@get2net.dk>
* Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
*
* Requires the Qt widget libraries, available at no cost at
* http://www.troll.no/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
//#include "toplevel.h"
#include "localemon.h"
#include "kptlocale.h"
#include "kptcommand.h"
#include "kptdebug.h"
#include <QCheckBox>
#include <QComboBox>
#include <QStandardPaths>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <KSharedConfig>
namespace KPlato {
LocaleConfigMoney::LocaleConfigMoney(Locale *locale,
QWidget *parent)
: QWidget(parent),
m_locale(locale)
{
setupUi(this);
// Money
m_labMonCurSym->setObjectName( I18N_NOOP("Currency symbol:") );
m_labMonFraDig->setObjectName( I18N_NOOP("Fract digits:") );
connect(m_edMonCurSym,&QLineEdit::textChanged,this, &LocaleConfigMoney::slotMonCurSymChanged);
connect(m_inMonFraDig,SIGNAL(valueChanged(int)),SLOT(slotMonFraDigChanged(int)));
m_inMonFraDig->setRange(0, 10);
m_inMonFraDig->setSingleStep(1);
slotTranslate();
slotLocaleChanged();
}
LocaleConfigMoney::~LocaleConfigMoney()
{
}
void LocaleConfigMoney::slotLocaleChanged()
{
m_edMonCurSym->setText( m_locale->currencySymbolExplicit());
m_inMonFraDig->setValue(m_locale->monetaryDecimalPlaces());
}
void LocaleConfigMoney::slotMonCurSymChanged(const QString &/*t*/)
{
emit localeChanged();
}
void LocaleConfigMoney::slotMonFraDigChanged(int /*value*/)
{
emit localeChanged();
}
void LocaleConfigMoney::slotMonPosPreCurSymChanged()
{
emit localeChanged();
}
void LocaleConfigMoney::slotMonNegPreCurSymChanged()
{
emit localeChanged();
}
void LocaleConfigMoney::slotMonPosMonSignPosChanged(int /*i*/)
{
emit localeChanged();
}
void LocaleConfigMoney::slotMonNegMonSignPosChanged(int /*i*/)
{
emit localeChanged();
}
void LocaleConfigMoney::slotTranslate()
{
QString str;
str = i18n( "Here you can enter your usual currency "
"symbol, e.g. $ or €." );
m_labMonCurSym->setWhatsThis( str );
m_edMonCurSym->setWhatsThis( str );
}
MacroCommand *LocaleConfigMoney::buildCommand()
{
MacroCommand *m = new MacroCommand();
if ( m_locale->currencySymbolExplicit() != m_edMonCurSym->text() ) {
m->addCommand( new ModifyCurrencySymolCmd( m_locale, m_edMonCurSym->text() ) );
}
if (m_locale->monetaryDecimalPlaces() != m_inMonFraDig->value()) {
m->addCommand( new ModifyCurrencyFractionalDigitsCmd( m_locale, m_inMonFraDig->value() ) );
}
debugPlan<<"buildCommand:"<<m->isEmpty();
if ( m->isEmpty() ) {
delete m;
return 0;
}
return m;
}
} // namespace KPlato
diff --git a/src/libs/ui/projectsettings/ProjectSettingsView.cpp b/src/libs/ui/projectsettings/ProjectSettingsView.cpp
index fe8fd59e..a0912956 100644
--- a/src/libs/ui/projectsettings/ProjectSettingsView.cpp
+++ b/src/libs/ui/projectsettings/ProjectSettingsView.cpp
@@ -1,90 +1,91 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "ProjectSettingsView.h"
#include "kptdebug.h"
#include <KoDocument.h>
#include <QFileDialog>
namespace KPlato
{
//-----------------------------------
ProjectSettingsView::ProjectSettingsView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
widget.setupUi(this);
setupGui();
connect(widget.resourcesBrowseBtn, SIGNAL(clicked()), this, SLOT(slotOpenResourcesFile()));
connect(widget.resourcesConnectBtn, SIGNAL(clicked()), this, SLOT(slotResourcesConnect()));
}
void ProjectSettingsView::updateReadWrite( bool /*readwrite */)
{
}
void ProjectSettingsView::setGuiActive( bool activate )
{
debugPlan<<activate;
}
void ProjectSettingsView::slotContextMenuRequested( const QModelIndex &/*index*/, const QPoint& /*pos */)
{
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
}
void ProjectSettingsView::slotEnableActions( bool on )
{
updateActionsEnabled( on );
}
void ProjectSettingsView::updateActionsEnabled( bool /*on */)
{
}
void ProjectSettingsView::setupGui()
{
// Add the context menu actions for the view options
}
KoPrintJob *ProjectSettingsView::createPrintJob()
{
return 0;//m_view->createPrintJob( this );
}
void ProjectSettingsView::slotOpenResourcesFile()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Resources"), "", tr("Resources files (*.plan)"));
widget.resourcesFile->setText(fileName);
}
void ProjectSettingsView::slotResourcesConnect()
{
QString fn = widget.resourcesFile->text();
if (fn.startsWith('/')) {
fn.prepend("file:/");
}
emit connectResources(fn);
}
} // namespace KPlato
diff --git a/src/libs/ui/projectview/ProjectView.cpp b/src/libs/ui/projectview/ProjectView.cpp
index 02a159dc..7740afb1 100644
--- a/src/libs/ui/projectview/ProjectView.cpp
+++ b/src/libs/ui/projectview/ProjectView.cpp
@@ -1,197 +1,198 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "ProjectView.h"
#include "kptdebug.h"
#include <QTableView>
#include <QHeaderView>
#include <QSqlTableModel>
#include <QSqlQuery>
#include <QSqlError>
#include <QMessageBox>
#include <QDebug>
namespace KPlato
{
ProjectView::ProjectView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
debugPlan<<"----------------- Create ProjectView ----------------------";
QVBoxLayout * l = new QVBoxLayout(this);
l->setMargin(0);
m_view = new QTableView(this);
l->addWidget(m_view);
if (createConnection()) {
QSqlTableModel *model = new QSqlTableModel(m_view, QSqlDatabase::database("projects"));
model->setTable("projects");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
model->setHeaderData(0, Qt::Horizontal, i18n("ID"));
model->setHeaderData(1, Qt::Horizontal, i18n("Name"));
model->setHeaderData(2, Qt::Horizontal, i18n("Manager"));
model->setHeaderData(3, Qt::Horizontal, i18n("File"));
m_view->setModel(model);
}
m_view->horizontalHeader()->setSectionHidden(0, true);
}
bool ProjectView::createConnection()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "projects");
db.setDatabaseName("projects.sqlite");
if (!db.open()) {
QMessageBox::critical(0, qApp->tr("Cannot open database"),
qApp->tr("Unable to establish a database connection.\n"
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information how "
"to build it.\n\n"
"Click Cancel to exit."), QMessageBox::Cancel);
return false;
}
QSqlQuery query(db);
QString q = "create table if not exists projects (id integer primary key, "
"name text, manager text, file text)";
if (!query.exec(q)) {
debugPlan<<"Table creation failed:"<<query.lastError();
return false;
}
q = "SELECT tbl_name FROM sqlite_master";
if (query.exec(q)) {
debugPlan<<"tables:";
while (query.next()) {
qDebug() << "\t"<<query.value(0).toString();
}
} else {
debugPlan<<"Table query failed:"<<query.lastError();
}
q = "insert into projects (name, manager, file) values ('P1', 'Meg', 'file://jens.plan)";
query.exec(q);
return true;
}
void ProjectView::setGuiActive(bool activate)
{
debugPlan<<activate;
updateActionsEnabled(true);
ViewBase::setGuiActive(activate);
}
void ProjectView::slotCurrentChanged(const QModelIndex &, const QModelIndex &)
{
slotEnableActions();
}
void ProjectView::slotSelectionChanged()
{
slotEnableActions();
}
QModelIndexList ProjectView::selectedRows() const
{
return m_view->selectionModel()->selectedRows();
}
int ProjectView::selectedRowCount() const
{
return selectedRows().count();
}
void ProjectView::slotContextMenuRequested(const QPoint& pos)
{
debugPlan;
emit requestPopupMenu("reportsgeneratorview_popup", m_view->mapToGlobal(pos));
}
void ProjectView::slotEnableActions()
{
updateActionsEnabled(isReadWrite());
}
void ProjectView::updateActionsEnabled(bool on)
{
}
void ProjectView::setupGui()
{
// Umpff, adding a specific list name for this view in calligraplan.rc does not work!
// But reusing an already existing name works, so...
// Not important atm, the whole setup should be refactored anyway.
QString name = "workpackage_list";
// KActionCollection *coll = actionCollection();
//
// actionAddReport = new QAction(koIcon("list-add"), i18n("Add Report"), this);
// coll->addAction("add_report", actionAddReport);
// coll->setDefaultShortcut(actionAddReport, Qt::CTRL + Qt::Key_I);
// connect(actionAddReport, SIGNAL(triggered(bool)), SLOT(slotAddReport()));
// addAction(name, actionAddReport);
// addContextAction(actionAddReport);
//
// actionRemoveReport = new QAction(koIcon("list-remove"), i18n("Remove Report"), this);
// coll->addAction("remove_report", actionRemoveReport);
// coll->setDefaultShortcut(actionRemoveReport, Qt::CTRL + Qt::Key_D);
// connect(actionRemoveReport, SIGNAL(triggered(bool)), SLOT(slotRemoveReport()));
// addAction(name, actionRemoveReport);
// addContextAction(actionRemoveReport);
//
// actionGenerateReport = new QAction(koIcon("document-export"), i18n("Generate Report"), this);
// coll->addAction("generate_report", actionGenerateReport);
// coll->setDefaultShortcut(actionGenerateReport, Qt::CTRL + Qt::Key_G);
// connect(actionGenerateReport, SIGNAL(triggered(bool)), SLOT(slotGenerateReport()));
// addAction(name, actionGenerateReport);
// addContextAction(actionGenerateReport);
// createOptionAction();
}
void ProjectView::slotOptions()
{
debugPlan;
// SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog(this, m_view, this);
// dlg->addPrintingOptions();
// connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
// dlg->show();
// dlg->raise();
// dlg->activateWindow();
}
bool ProjectView::loadContext(const KoXmlElement &context)
{
debugPlan;
return true;
}
void ProjectView::saveContext(QDomElement &context) const
{
debugPlan;
ViewBase::saveContext(context);
}
} // namespace KPlato
diff --git a/src/libs/ui/reports/items/text/PlanReportDesignerItemText.cpp b/src/libs/ui/reports/items/text/PlanReportDesignerItemText.cpp
index cdfbbe4d..0f73e2e1 100644
--- a/src/libs/ui/reports/items/text/PlanReportDesignerItemText.cpp
+++ b/src/libs/ui/reports/items/text/PlanReportDesignerItemText.cpp
@@ -1,179 +1,180 @@
/* This file is part of the KDE project
* Copyright (C) 2001-2007 by OpenMFG, LLC (info@openmfg.com)
* Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk)
* Copyright (C) 2016 by Dag Andersen <danders@get2net.dk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
+// clazy:excludeall=qstring-arg
#include "PlanReportDesignerItemText.h"
#include "KReportDesignerItemBase.h"
#include "KReportDesigner.h"
#include "KReportLineStyle.h"
#include <KPropertySet>
#include <QDomDocument>
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
void PlanReportDesignerItemText::init(QGraphicsScene *scene, KReportDesigner *d)
{
//setFlags(ItemIsSelectable | ItemIsMovable);
if (scene)
scene->addItem(this);
connect(propertySet(), SIGNAL(propertyChanged(KPropertySet&,KProperty&)),
this, SLOT(slotPropertyChanged(KPropertySet&,KProperty&)));
KReportDesignerItemRectBase::init(&m_pos, &m_size, m_set, d);
m_controlSource->setListData(m_reportDesigner->fieldKeys(), m_reportDesigner->fieldNames());
setZValue(Z);
updateRenderText(m_controlSource->value().toString(), m_itemValue->value().toString(),
QLatin1String("textarea"));
}
PlanReportDesignerItemText::PlanReportDesignerItemText(KReportDesigner * rw, QGraphicsScene * scene, const QPointF &pos)
: KReportDesignerItemRectBase(rw)
{
Q_UNUSED(pos);
init(scene, rw);
setSceneRect(properRect(*rw, getTextRect().width(), getTextRect().height()));
m_name->setValue(m_reportDesigner->suggestEntityName(typeName()));
}
PlanReportDesignerItemText::PlanReportDesignerItemText(const QDomNode & element, KReportDesigner * d, QGraphicsScene * s)
: PlanReportItemText(element), KReportDesignerItemRectBase(d)
{
init(s, d);
setSceneRect(m_pos.toScene(), m_size.toScene());
}
PlanReportDesignerItemText* PlanReportDesignerItemText::clone()
{
QDomDocument d;
QDomElement e = d.createElement(QLatin1String("clone"));
QDomNode n;
buildXML(&d, &e);
n = e.firstChild();
return new PlanReportDesignerItemText(n, designer(), 0);
}
PlanReportDesignerItemText::~PlanReportDesignerItemText()
{}
QRect PlanReportDesignerItemText::getTextRect() const
{
return QFontMetrics(font()).boundingRect(int (x()), int (y()), 0, 0, textFlags(), m_renderText);
}
void PlanReportDesignerItemText::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(option);
Q_UNUSED(widget)
// store any values we plan on changing so we can restore them
QFont f = painter->font();
QPen p = painter->pen();
painter->setFont(font());
painter->setBackgroundMode(Qt::TransparentMode);
QColor bg = m_backgroundColor->value().value<QColor>();
bg.setAlphaF(m_backgroundOpacity->value().toReal()*0.01);
painter->setPen(m_foregroundColor->value().value<QColor>());
painter->fillRect(rect(), bg);
painter->drawText(rect(), textFlags(), m_renderText);
if ((Qt::PenStyle)m_lineStyle->value().toInt() == Qt::NoPen || m_lineWeight->value().toInt() <= 0) {
painter->setPen(QPen(Qt::lightGray));
} else {
painter->setPen(QPen(m_lineColor->value().value<QColor>(), m_lineWeight->value().toInt(), (Qt::PenStyle)m_lineStyle->value().toInt()));
}
painter->drawRect(rect());
painter->setPen(m_foregroundColor->value().value<QColor>());
drawHandles(painter);
// restore an values before we started just in case
painter->setFont(f);
painter->setPen(p);
}
void PlanReportDesignerItemText::buildXML(QDomDocument *doc, QDomElement *parent)
{
//kreportpluginDebug();
QDomElement entity = doc->createElement(QLatin1String("report:") + typeName());
// properties
addPropertyAsAttribute(&entity, m_name);
addPropertyAsAttribute(&entity, m_controlSource);
addPropertyAsAttribute(&entity, m_verticalAlignment);
addPropertyAsAttribute(&entity, m_horizontalAlignment);
entity.setAttribute(QLatin1String("report:bottom-padding"), m_bottomPadding);
entity.setAttribute(QLatin1String("report:z-index"), zValue());
addPropertyAsAttribute(&entity, m_itemValue);
// bounding rect
buildXMLRect(doc, &entity, &m_pos, &m_size);
//text style info
buildXMLTextStyle(doc, &entity, textStyle());
//Line Style
buildXMLLineStyle(doc, &entity, lineStyle());
parent->appendChild(entity);
}
void PlanReportDesignerItemText::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
m_controlSource->setListData(m_reportDesigner->fieldKeys(), m_reportDesigner->fieldNames());
KReportDesignerItemRectBase::mousePressEvent(event);
}
void PlanReportDesignerItemText::slotPropertyChanged(KPropertySet &s, KProperty &p)
{
Q_UNUSED(s);
if (p.name() == "position") {
m_pos.setUnitPos(p.value().toPointF(), KReportPosition::DontUpdateProperty);
} else if (p.name() == "size") {
m_size.setUnitSize(p.value().toSizeF(), KReportSize::DontUpdateProperty);
} else if (p.name() == "name") {
//For some reason p.oldValue returns an empty string
if (!m_reportDesigner->isEntityNameUnique(p.value().toString(), this)) {
p.setValue(m_oldName);
} else {
m_oldName = p.value().toString();
}
}
setSceneRect(m_pos.toScene(), m_size.toScene(), DontUpdateProperty);
if (m_reportDesigner)
m_reportDesigner->setModified(true);
if (scene())
scene()->update();
updateRenderText(m_controlSource->value().toString(), m_itemValue->value().toString(),
QLatin1String("textarea"));
}
diff --git a/src/libs/ui/reports/items/text/PlanReportItemText.cpp b/src/libs/ui/reports/items/text/PlanReportItemText.cpp
index 56413a84..fe6e0e10 100644
--- a/src/libs/ui/reports/items/text/PlanReportItemText.cpp
+++ b/src/libs/ui/reports/items/text/PlanReportItemText.cpp
@@ -1,331 +1,332 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk)
* Copyright (C) 2016 by Dag Andersen <danders@get2net.dk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
+// clazy:excludeall=qstring-arg
#include "PlanReportItemText.h"
#include "KReportRenderObjects.h"
#include <KPropertySet>
#include <QPrinter>
#include <QApplication>
#include <QPalette>
#include <QFontMetrics>
#include <QDomNodeList>
#include <QTextEdit>
#include <QDebug>
#include <QLoggingCategory>
const QLoggingCategory &REPORTTEXT_LOG()
{
static const QLoggingCategory category("calligra.plan.report.text");
return category;
}
#define debugText qCDebug(REPORTTEXT_LOG)<<QString("%1:").arg(__func__)
PlanReportItemText::PlanReportItemText()
{
createProperties();
}
PlanReportItemText::PlanReportItemText(const QDomNode & element) : m_bottomPadding(0.0)
{
QDomNodeList nl = element.childNodes();
QString n;
QDomNode node;
createProperties();
m_name->setValue(element.toElement().attribute(QLatin1String("report:name")));
m_controlSource->setValue(element.toElement().attribute(QLatin1String("report:item-data-source")));
m_controlSource->setOption("extraValueAllowed", QLatin1String("true"));
m_itemValue->setValue(element.toElement().attribute(QLatin1String("report:value")));
Z = element.toElement().attribute(QLatin1String("report:z-index")).toDouble();
m_horizontalAlignment->setValue(element.toElement().attribute(QLatin1String("report:horizontal-align")));
m_verticalAlignment->setValue(element.toElement().attribute(QLatin1String("report:vertical-align")));
m_bottomPadding = element.toElement().attribute(QLatin1String("report:bottom-padding")).toDouble();
parseReportRect(element.toElement(), &m_pos, &m_size);
for (int i = 0; i < nl.count(); i++) {
node = nl.item(i);
n = node.nodeName();
if (n == QLatin1String("report:text-style")) {
KRTextStyleData ts;
if (parseReportTextStyleData(node.toElement(), &ts)) {
m_backgroundColor->setValue(ts.backgroundColor);
m_foregroundColor->setValue(ts.foregroundColor);
m_backgroundOpacity->setValue(ts.backgroundOpacity);
m_font->setValue(ts.font);
}
} else if (n == QLatin1String("report:line-style")) {
KReportLineStyle ls;
if (parseReportLineStyleData(node.toElement(), &ls)) {
m_lineWeight->setValue(ls.width());
m_lineColor->setValue(ls.color());
m_lineStyle->setValue(QPen(ls.penStyle()));
}
} else {
qDebug() << "while parsing field element encountered unknown element: " << n;
}
}
}
PlanReportItemText::~PlanReportItemText()
{
delete m_set;
}
Qt::Alignment PlanReportItemText::textFlags() const
{
Qt::Alignment align;
QString t;
t = m_horizontalAlignment->value().toString();
if (t == QLatin1String("center"))
align = Qt::AlignHCenter;
else if (t == QLatin1String("right"))
align = Qt::AlignRight;
else
align = Qt::AlignLeft;
t = m_verticalAlignment->value().toString();
if (t == QLatin1String("center"))
align |= Qt::AlignVCenter;
else if (t == QLatin1String("bottom"))
align |= Qt::AlignBottom;
else
align |= Qt::AlignTop;
return align;
}
void PlanReportItemText::createProperties()
{
m_set = new KPropertySet;
//connect ( set, SIGNAL ( propertyChanged ( KPropertySet &, KProperty & ) ), this, SLOT ( propertyChanged ( KPropertySet &, KProperty & ) ) );
QStringList keys, strings;
//_query = new KProperty ( "Query", QStringList(), QStringList(), "Data Source", "Query" );
m_controlSource = new KProperty("item-data-source", QStringList(), QStringList(), QString(), tr("Data Source"));
m_itemValue = new KProperty("value", QString(), tr("Value"), tr("Value used if not bound to a field"));
keys << QLatin1String("left") << QLatin1String("center") << QLatin1String("right");
strings << tr("Left") << tr("Center") << tr("Right");
m_horizontalAlignment = new KProperty("horizontal-align", keys, strings, QLatin1String("left"), tr("Horizontal Alignment"));
keys.clear();
strings.clear();
keys << QLatin1String("top") << QLatin1String("center") << QLatin1String("bottom");
strings << tr("Top") << tr("Center") << tr("Bottom");
m_verticalAlignment = new KProperty("vertical-align", keys, strings, QLatin1String("center"), tr("Vertical Alignment"));
m_font = new KProperty("font", QApplication::font(), tr("Font"));
m_backgroundColor = new KProperty("background-color", QColor(Qt::white), tr("Background Color"));
m_foregroundColor = new KProperty("foreground-color", QPalette().color(QPalette::Foreground), tr("Foreground Color"));
m_lineWeight = new KProperty("line-weight", 1, tr("Line Weight"));
m_lineColor = new KProperty("line-color", QColor(Qt::black), tr("Line Color"));
m_lineStyle = new KProperty("line-style", QPen(Qt::NoPen), tr("Line Style"), tr("Line Style"), KProperty::LineStyle);
m_backgroundOpacity = new KProperty("background-opacity", QVariant(0), tr("Background Opacity"));
m_backgroundOpacity->setOption("max", 100);
m_backgroundOpacity->setOption("min", 0);
m_backgroundOpacity->setOption("unit", QLatin1String("%"));
addDefaultProperties();
m_set->addProperty(m_controlSource);
m_set->addProperty(m_itemValue);
m_set->addProperty(m_horizontalAlignment);
m_set->addProperty(m_verticalAlignment);
m_set->addProperty(m_font);
m_set->addProperty(m_backgroundColor);
m_set->addProperty(m_foregroundColor);
m_set->addProperty(m_backgroundOpacity);
m_set->addProperty(m_lineWeight);
m_set->addProperty(m_lineColor);
m_set->addProperty(m_lineStyle);
}
QString PlanReportItemText::itemDataSource() const
{
return m_controlSource->value().toString();
}
qreal PlanReportItemText::bottomPadding() const
{
return m_bottomPadding;
}
void PlanReportItemText::setBottomPadding(qreal bp)
{
if (m_bottomPadding != bp) {
m_bottomPadding = bp;
}
}
KRTextStyleData PlanReportItemText::textStyle() const
{
KRTextStyleData d;
d.backgroundColor = m_backgroundColor->value().value<QColor>();
d.foregroundColor = m_foregroundColor->value().value<QColor>();
d.font = m_font->value().value<QFont>();
d.backgroundOpacity = m_backgroundOpacity->value().toInt();
return d;
}
KReportLineStyle PlanReportItemText::lineStyle() const
{
KReportLineStyle ls;
ls.setWidth(m_lineWeight->value().toInt());
ls.setColor(m_lineColor->value().value<QColor>());
ls.setPenStyle((Qt::PenStyle)m_lineStyle->value().toInt());
return ls;
}
// RTTI
QString PlanReportItemText::typeName() const
{
return QLatin1String("plan.text");
}
int PlanReportItemText::renderSimpleData(OROPage *page, OROSection *section, const QPointF &offset,
const QVariant &data, KReportScriptHandler *script)
{
Q_UNUSED(script);
QString qstrValue;
QString cs = itemDataSource();
if (!cs.isEmpty()) {
// just convert rich text to plain text for now
QTextEdit te;
te.setText(data.toString());
qstrValue = te.toPlainText();
debugText<<qstrValue;
} else {
qstrValue = m_itemValue->value().toString();
debugText<<"No data source:"<<qstrValue;
}
QPointF pos = m_pos.toScene();
QSizeF size = m_size.toScene();
pos += offset;
QRectF trf(pos, size);
qreal intStretch = trf.top() - offset.y();
if (qstrValue.length()) {
QRectF rect = trf;
int pos = 0;
QChar separator;
QRegExp re(QLatin1String("\\s"));
QPrinter prnt(QPrinter::HighResolution);
QFontMetrics fm(font(), &prnt);
// int intRectWidth = (int)(trf.width() * prnt.resolution()) - 10;
int intRectWidth = (int)((m_size.toPoint().width() / 72) * prnt.resolution());
int intLineCounter = 0;
qreal intBaseTop = trf.top();
qreal intRectHeight = trf.height();
while (qstrValue.length()) {
int idx = re.indexIn(qstrValue, pos);
if (idx == -1) {
idx = qstrValue.length();
separator = QLatin1Char('\n');
} else
separator = qstrValue.at(idx);
if (fm.boundingRect(qstrValue.left(idx)).width() < intRectWidth || pos == 0) {
pos = idx + 1;
if (separator == QLatin1Char('\n')) {
QString line = qstrValue.left(idx);
qstrValue.remove(0, idx + 1);
pos = 0;
rect.setTop(intBaseTop + (intLineCounter * intRectHeight));
rect.setBottom(rect.top() + intRectHeight);
OROTextBox * tb = new OROTextBox();
tb->setPosition(rect.topLeft());
tb->setSize(rect.size());
tb->setFont(font());
tb->setText(line);
tb->setFlags(textFlags());
tb->setTextStyle(textStyle());
tb->setLineStyle(lineStyle());
if (page) {
page->addPrimitive(tb);
}
if (section) {
OROTextBox *tb2 = dynamic_cast<OROTextBox*>(tb->clone());
tb2->setPosition(m_pos.toPoint());
section->addPrimitive(tb2);
}
if (!page) {
delete tb;
}
intStretch += intRectHeight;
intLineCounter++;
}
} else {
QString line = qstrValue.left(pos - 1);
qstrValue.remove(0, pos);
pos = 0;
rect.setTop(intBaseTop + (intLineCounter * intRectHeight));
rect.setBottom(rect.top() + intRectHeight);
OROTextBox * tb = new OROTextBox();
tb->setPosition(rect.topLeft());
tb->setSize(rect.size());
tb->setFont(font());
tb->setText(line);
tb->setFlags(textFlags());
tb->setTextStyle(textStyle());
tb->setLineStyle(lineStyle());
if (page) {
page->addPrimitive(tb);
} else {
delete tb;
}
intStretch += intRectHeight;
intLineCounter++;
}
}
intStretch += (m_bottomPadding / 100.0);
}
return intStretch; //Item returns its required section height
}
diff --git a/src/libs/ui/reports/items/text/PlanReportScriptText.cpp b/src/libs/ui/reports/items/text/PlanReportScriptText.cpp
index 4125eca4..cea40f8a 100644
--- a/src/libs/ui/reports/items/text/PlanReportScriptText.cpp
+++ b/src/libs/ui/reports/items/text/PlanReportScriptText.cpp
@@ -1,205 +1,206 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk)
* Copyright (C) 2016 by Dag Andersen <danders@get2net.dk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
+// clazy:excludeall=qstring-arg
#include "PlanReportScriptText.h"
#include <QFile>
#include <QTextStream>
namespace Scripting
{
Text::Text(PlanReportItemText* t)
{
m_text = t;
}
Text::~Text()
{
}
QString Text::source() const
{
return m_text->itemDataSource();
}
void Text::setSource(const QString& s)
{
m_text->m_controlSource->setValue(s);
}
int Text::horizontalAlignment() const
{
const QString a = m_text->m_horizontalAlignment->value().toString().toLower();
if (a == QLatin1String("left")) {
return -1;
}
if (a == QLatin1String("center")) {
return 0;
}
if (a == QLatin1String("right")) {
return 1;
}
return -1;
}
void Text::setHorizonalAlignment(int a)
{
switch (a) {
case -1:
m_text->m_horizontalAlignment->setValue(QLatin1String("left"));
break;
case 0:
m_text->m_horizontalAlignment->setValue(QLatin1String("center"));
break;
case 1:
m_text->m_horizontalAlignment->setValue(QLatin1String("right"));
break;
default:
m_text->m_horizontalAlignment->setValue(QLatin1String("left"));
break;
}
}
int Text::verticalAlignment() const
{
const QString a = m_text->m_horizontalAlignment->value().toString().toLower();
if (a == QLatin1String("top")) {
return -1;
}
if (a == QLatin1String("middle")) {
return 0;
}
if (a == QLatin1String("bottom")) {
return 1;
}
return -1;
}
void Text::setVerticalAlignment(int a)
{
switch (a) {
case -1:
m_text->m_verticalAlignment->setValue(QLatin1String("top"));
break;
case 0:
m_text->m_verticalAlignment->setValue(QLatin1String("middle"));
break;
case 1:
m_text->m_verticalAlignment->setValue(QLatin1String("bottom"));
break;
default:
m_text->m_verticalAlignment->setValue(QLatin1String("middle"));
break;
}
}
QColor Text::backgroundColor() const
{
return m_text->m_backgroundColor->value().value<QColor>();
}
void Text::setBackgroundColor(const QColor& c)
{
m_text->m_backgroundColor->setValue(QColor(c));
}
QColor Text::foregroundColor() const
{
return m_text->m_foregroundColor->value().value<QColor>();
}
void Text::setForegroundColor(const QColor& c)
{
m_text->m_foregroundColor->setValue(QColor(c));
}
int Text::backgroundOpacity() const
{
return m_text->m_backgroundOpacity->value().toInt();
}
void Text::setBackgroundOpacity(int o)
{
m_text->m_backgroundOpacity->setValue(o);
}
QColor Text::lineColor() const
{
return m_text->m_lineColor->value().value<QColor>();
}
void Text::setLineColor(const QColor& c)
{
m_text->m_lineColor->setValue(QColor(c));
}
int Text::lineWeight() const
{
return m_text->m_lineWeight->value().toInt();
}
void Text::setLineWeight(int w)
{
m_text->m_lineWeight->setValue(w);
}
int Text::lineStyle() const
{
return m_text->m_lineStyle->value().toInt();
}
void Text::setLineStyle(int s)
{
if (s < 0 || s > 5) {
s = 1;
}
m_text->m_lineStyle->setValue(s);
}
QPointF Text::position() const
{
return m_text->m_pos.toPoint();
}
void Text::setPosition(const QPointF& p)
{
m_text->m_pos.setPointPos(p);
}
QSizeF Text::size() const
{
return m_text->m_size.toPoint();
}
void Text::setSize(const QSizeF& s)
{
m_text->m_size.setPointSize(s);
}
void Text::loadFromFile(const QString &fn)
{
QFile file(fn);
//kreportpluginDebug() << "Loading from" << fn;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
m_text->m_controlSource->setValue(tr("$Unable to read %1").arg(fn));
return;
}
QTextStream in(&file);
QString data = in.readAll();
/*
while (!in.atEnd()) {
QString line = in.readLine();
process_line(line);
}*/
m_text->m_controlSource->setValue(QVariant(QLatin1String("$") + data));
}
}
diff --git a/src/libs/ui/reports/items/text/PlanReportTextPlugin.cpp b/src/libs/ui/reports/items/text/PlanReportTextPlugin.cpp
index 83a27e68..42dd5c3d 100644
--- a/src/libs/ui/reports/items/text/PlanReportTextPlugin.cpp
+++ b/src/libs/ui/reports/items/text/PlanReportTextPlugin.cpp
@@ -1,71 +1,72 @@
/* This file is part of the KDE project
* Copyright (C) 2010 by Adam Pigg (adam@piggz.co.uk)
* Copyright (C) 2016 by Dag Andersen <danders@get2net.dk>
*
* 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.1 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 "PlanReportTextPlugin.h"
#include "PlanReportItemText.h"
#include "PlanReportDesignerItemText.h"
#ifdef KREPORT_SCRIPTING
#include "PlanReportScriptText.h"
#endif
#include "KReportPluginMetaData.h"
#include <QDebug>
KREPORT_PLUGIN_FACTORY(PlanReportTextPlugin, "text.json")
PlanReportTextPlugin::PlanReportTextPlugin(QObject *parent, const QVariantList &args)
: KReportPluginInterface(parent, args)
{
}
PlanReportTextPlugin::~PlanReportTextPlugin()
{
}
QObject* PlanReportTextPlugin::createRendererInstance(const QDomNode& element)
{
return new PlanReportItemText(element);
}
QObject* PlanReportTextPlugin::createDesignerInstance(const QDomNode& element, KReportDesigner* designer, QGraphicsScene* scene)
{
return new PlanReportDesignerItemText(element, designer, scene);
}
QObject* PlanReportTextPlugin::createDesignerInstance(KReportDesigner* designer, QGraphicsScene* scene, const QPointF& pos)
{
return new PlanReportDesignerItemText(designer, scene, pos);
}
#ifdef KREPORT_SCRIPTING
QObject* PlanReportTextPlugin::createScriptInstance(KReportItemBase* item)
{
PlanReportItemText *text = dynamic_cast<PlanReportItemText*>(item);
if (text) {
return new Scripting::Text(text);
}
return 0;
}
#endif
#include "PlanReportTextPlugin.moc"
diff --git a/src/libs/ui/reports/reportdata.cpp b/src/libs/ui/reports/reportdata.cpp
index 6e093b45..8d9f9b15 100644
--- a/src/libs/ui/reports/reportdata.cpp
+++ b/src/libs/ui/reports/reportdata.cpp
@@ -1,1061 +1,1062 @@
/*
* KPlato Report Plugin
* Copyright (C) 2007-2009 by Adam Pigg (adam@piggz.co.uk)
* Copyright (C) 2010 by Dag Andersen <danders@get2net.dk>
* Copyright (C) 2016 by Dag Andersen <danders@get2net.dk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "reportdata.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptnodeitemmodel.h"
#include "kptflatproxymodel.h"
#include "kpttaskstatusmodel.h"
#include "kptnodechartmodel.h"
#include "kptaccountsmodel.h"
#include "kptresourcemodel.h"
#include "kptresourceallocationmodel.h"
#include "kptresourceappointmentsmodel.h"
#include <KLocalizedString>
#include <QSortFilterProxyModel>
#include <QString>
#include <QStringList>
extern int planDbg();
namespace KPlato
{
PLANUI_EXPORT QList<ReportData*> Report::createBaseReportDataModels( QObject *parent )
{
QList<ReportData*> lst;
ReportData *data = new TaskReportData( parent );
lst << data;
data = new TaskStatusReportData( parent );
lst << data;
data = new ResourceAssignmentReportData( parent );
lst << data;
data = new ResourceReportData( parent );
lst << data;
data = new CostPerformanceReportData( parent );
lst << data;
data = new EffortPerformanceReportData( parent );
lst << data;
data = new CostBreakdownReportData( parent );
lst << data;
data = new ProjectReportData( parent );
lst << data;
foreach ( ReportData *r, lst ) {
QList<ReportData*> sub;
foreach ( ReportData *d, lst ) {
if ( d->isSubDataSource() ) {
sub << d;
}
}
r->setSubDataSources( sub );
}
return lst;
}
PLANUI_EXPORT ReportData *Report::findReportData( const QList<ReportData*> &lst, const QString &type )
{
foreach( ReportData *r, lst ) {
if ( r->objectName() == type ) {
return r;
}
}
return 0;
}
//------------------
ReportData::ReportData( QObject *parent )
: QObject( parent ),
m_row( 0 ),
m_project( 0 ),
m_schedulemanager( 0 ),
m_maindatasource( false ),
m_subdatasource( false )
{
}
ReportData::ReportData( const ReportData &other )
: QObject(),
m_project( 0 ),
m_schedulemanager( 0 )
{
setObjectName( other.objectName() );
m_name = other.m_name;
m_columnroles = other.m_columnroles;
m_sortlist = other.m_sortlist;
m_maindatasource = other.m_maindatasource;
m_subdatasource = other.m_subdatasource;
m_subdatasources = other.m_subdatasources;
}
ReportData::~ReportData()
{
}
void ReportData::setColumnRole( int column, int role )
{
m_columnroles[ column ] = role;
}
bool ReportData::open()
{
close();
ItemModelBase *basemodel = itemModel();
if ( basemodel ) {
basemodel->setProject( m_project );
basemodel->setScheduleManager( m_schedulemanager );
} else errorPlan<<"No item model";
if ( ! m_sortlist.isEmpty() ) {
QAbstractItemModel *sourcemodel = m_model.sourceModel();
foreach ( const SortedField &sort, m_sortlist ) {
int col = fieldNumber( sort.field );
QSortFilterProxyModel *sf = new QSortFilterProxyModel( &m_model );
sf->setSourceModel( sourcemodel );
if ( basemodel ) {
sf->setSortRole( basemodel->sortRole( col ) );
}
sf->sort( col, sort.order );
sourcemodel = sf;
m_sortmodels << sf;
}
m_model.setSourceModel( sourcemodel );
}
return true;
}
bool ReportData::close()
{
while ( ! m_sortmodels.isEmpty() ) {
QAbstractProxyModel *m = qobject_cast<QAbstractProxyModel*>( m_sortmodels.takeLast() );
for ( QAbstractProxyModel *p = &m_model; p != 0; p = qobject_cast<QAbstractProxyModel*>( p->sourceModel() ) ) {
if ( p->sourceModel() == m ) {
p->setSourceModel( m->sourceModel() );
delete m;
break;
}
}
}
ItemModelBase *basemodel = itemModel();
if ( basemodel ) {
basemodel->setScheduleManager( 0 );
basemodel->setProject( 0 );
}
return true;
}
QString ReportData::sourceName() const {
return m_name;
}
int ReportData::fieldNumber ( const QString &fld ) const
{
QStringList names = fieldKeys();
int idx = names.indexOf( fld );
return idx;
}
QStringList ReportData::fieldNames() const
{
QStringList names;
int count = m_model.columnCount();
for ( int i = 0; i < count; ++i ) {
names << m_model.headerData( i, Qt::Horizontal ).toString();
}
return names;
}
QStringList ReportData::fieldKeys() const
{
QStringList keys;
int count = m_model.columnCount();
for ( int i = 0; i < count; ++i ) {
keys << m_model.headerData( i, Qt::Horizontal, Role::ColumnTag ).toString();
}
return keys;
}
QVariant ReportData::value ( unsigned int i ) const {
debugPlan<<i<<m_model.rowCount();
if ( m_model.rowCount() == 0 ) {
return QVariant();
}
int role = m_columnroles.contains( i ) ? m_columnroles[ i ] : Qt::DisplayRole;
QVariant value = m_model.index( at(), i ).data( role );
return value;
}
QVariant ReportData::value ( const QString &fld ) const
{
debugPlan<<fld;
if (fld.startsWith('#') && fld.indexOf(objectName()) != 1) {
// Not this data source
if (!fld.contains('.')) {
return QVariant();
}
QString source = fld.mid(1).toLower();
ReportData *rd = getReportData(source.left(source.indexOf('.')));
if (!rd) {
return QVariant();
}
return rd->value(fld);
}
if ( m_model.rowCount() == 0 ) {
return QVariant();
}
int i = fieldNumber ( fld );
return value( i );
}
bool ReportData::moveNext()
{
if ( m_model.rowCount() <= m_row + 1 ) {
return false;
}
++m_row;
return true;
}
bool ReportData::movePrevious()
{
if ( m_row <= 0 ) {
return false;
}
--m_row;
return true;
}
bool ReportData::moveFirst()
{
if ( m_model.rowCount() == 0 ) {
return false;
}
m_row = 0;
return true;
}
bool ReportData::moveLast()
{
if ( m_model.rowCount() == 0 ) {
return false;
}
m_row = m_model.rowCount() - 1;
return true;
}
qint64 ReportData::at() const
{
return m_row;
}
qint64 ReportData::recordCount() const {
return m_model.rowCount();
}
QStringList ReportData::dataSources() const
{
QStringList lst;
foreach ( ReportData *r, m_subdatasources ) {
if ( r->isSubDataSource() ) {
lst << r->objectName();
}
}
return lst;
}
QStringList ReportData::dataSourceNames() const
{
QStringList lst;
foreach ( ReportData *r, m_subdatasources ) {
if ( r->isSubDataSource() ) {
lst << r->sourceName();
}
}
return lst;
}
void ReportData::setSorting(const QList<SortedField>& lst )
{
m_sortlist = lst;
}
KReportData* ReportData::data(const QString &source)
{
ReportData *r = Report::findReportData( m_subdatasources, source );
if ( r ) {
r = r->clone();
r->setParent( this );
r->setProject( m_project );
r->setScheduleManager( m_schedulemanager );
}
debugPlan<<this<<m_subdatasources<<r;
return r;
}
void ReportData::setModel( QAbstractItemModel *model )
{
m_model.setSourceModel( model );
}
QAbstractItemModel *ReportData::model() const
{
return const_cast<QSortFilterProxyModel*>( &m_model );
}
ItemModelBase *ReportData::itemModel() const
{
QAbstractItemModel *m = m_model.sourceModel();
QAbstractProxyModel *p = 0;
do {
p = qobject_cast<QAbstractProxyModel*>( m );
if ( p ) {
m = p->sourceModel();
}
} while ( p );
return qobject_cast<ItemModelBase*>( m );
}
void ReportData::setProject( Project *project )
{
m_project = project;
}
void ReportData::setScheduleManager( ScheduleManager *sm )
{
m_schedulemanager = sm;
}
ReportData *ReportData::getReportData(const QString &tag) const
{
if (tag == "project") {
if (!m_datasources.contains(tag)) {
ReportData *r = new ProjectReportData();
r->setParent( const_cast<ReportData*>(this) );
r->setProject( m_project );
r->setScheduleManager( m_schedulemanager );
m_datasources[tag] = r;
}
debugPlan<<tag<<m_datasources[tag];
return m_datasources[tag];
}
return 0;
}
//---------------------------
TaskReportData::TaskReportData( QObject *parent )
: ReportData( parent )
{
m_maindatasource = true;
m_subdatasource = false;
setObjectName( "tasks" );
m_name = i18n( "Tasks" );
setColumnRole( NodeModel::NodeDescription, Qt::EditRole );
createModels();
}
TaskReportData::TaskReportData( const TaskReportData &other )
: ReportData( other )
{
createModels();
}
bool TaskReportData::loadXml( const KoXmlElement &element )
{
Q_UNUSED(element);
return true;
}
void TaskReportData::saveXml( QDomElement &element ) const
{
Q_UNUSED(element);
}
ReportData *TaskReportData::clone() const
{
return new TaskReportData( *this );
}
void TaskReportData::createModels()
{
QRegExp rex( QString( "^(%1|%2)$" ).arg( (int)Node::Type_Task ).arg( (int)Node::Type_Milestone ) );
QSortFilterProxyModel *sf = new QSortFilterProxyModel( &m_model );
m_model.setSourceModel( sf );
sf->setFilterKeyColumn( NodeModel::NodeType );
sf->setFilterRole( Qt::EditRole );
sf->setFilterRegExp( rex );
sf->setDynamicSortFilter( true );
FlatProxyModel *fm = new FlatProxyModel( sf );
sf->setSourceModel( fm );
NodeItemModel *m = new NodeItemModel( fm );
fm->setSourceModel( m );
}
//---------------------------
TaskStatusReportData::TaskStatusReportData( QObject *parent )
: ReportData( parent )
{
m_maindatasource = true;
m_subdatasource = false;
setObjectName( "taskstatus" );
m_name = i18n( "Task status" );
setColumnRole( NodeModel::NodeDescription, Qt::EditRole );
createModels();
}
TaskStatusReportData::TaskStatusReportData( const TaskStatusReportData &other )
: ReportData( other )
{
createModels();
}
bool TaskStatusReportData::loadXml( const KoXmlElement &element )
{
Q_UNUSED(element);
return true;
}
void TaskStatusReportData::saveXml( QDomElement &element ) const
{
Q_UNUSED(element);
}
ReportData *TaskStatusReportData::clone() const
{
return new TaskStatusReportData( *this );
}
void TaskStatusReportData::createModels()
{
QRegExp rex( QString( "^(%1|%2)$" ).arg( (int)Node::Type_Task ).arg( (int)Node::Type_Milestone ) );
QSortFilterProxyModel *sf = new QSortFilterProxyModel( &m_model );
m_model.setSourceModel( sf );
sf->setFilterKeyColumn( NodeModel::NodeType );
sf->setFilterRole( Qt::EditRole );
sf->setFilterRegExp( rex );
sf->setDynamicSortFilter( true );
FlatProxyModel *fm = new FlatProxyModel( sf );
sf->setSourceModel( fm );
TaskStatusItemModel *m = new TaskStatusItemModel( fm );
fm->setSourceModel( m );
}
//---------------------------
ResourceReportData::ResourceReportData( QObject *parent )
: ReportData( parent )
{
m_maindatasource = true;
m_subdatasource = false;
setObjectName( "resources" );
m_name = i18n( "Resources" );
createModels();
}
ResourceReportData::ResourceReportData( const ResourceReportData &other )
: ReportData( other )
{
createModels();
}
bool ResourceReportData::loadXml( const KoXmlElement &element )
{
Q_UNUSED(element);
return true;
}
void ResourceReportData::saveXml( QDomElement &element ) const
{
Q_UNUSED(element);
}
ReportData *ResourceReportData::clone() const
{
return new ResourceReportData( *this );
}
void ResourceReportData::createModels()
{
ItemModelBase *m = 0;
QRegExp rex( QString( "^(%1)$" ).arg( (int)OT_Resource ) );
QSortFilterProxyModel *sf = new QSortFilterProxyModel( &m_model );
m_model.setSourceModel( sf );
sf->setFilterKeyColumn( 0 );
sf->setFilterRole( Role::ObjectType );
sf->setFilterRegExp( rex );
sf->setDynamicSortFilter( true );
FlatProxyModel *fm = new FlatProxyModel( sf );
sf->setSourceModel( fm );
m = new ResourceItemModel( fm );
fm->setSourceModel( m );
}
//---------------------------
ResourceAssignmentReportData::ResourceAssignmentReportData( QObject *parent )
: ReportData( parent )
{
m_maindatasource = true;
m_subdatasource = false;
setObjectName( "resourceassignments" );
m_name = i18n( "Resource assignments" );
createModels();
}
ResourceAssignmentReportData::ResourceAssignmentReportData( const ResourceAssignmentReportData &other )
: ReportData( other )
{
createModels();
}
bool ResourceAssignmentReportData::loadXml( const KoXmlElement &element )
{
Q_UNUSED(element);
return true;
}
void ResourceAssignmentReportData::saveXml( QDomElement &element ) const
{
Q_UNUSED(element);
}
ReportData *ResourceAssignmentReportData::clone() const
{
return new ResourceAssignmentReportData( *this );
}
void ResourceAssignmentReportData::createModels()
{
QSortFilterProxyModel *sf = 0;
ItemModelBase *m = 0;
QRegExp rex( QString( "^(%1)$" ).arg( (int)OT_Appointment ) );
sf = new QSortFilterProxyModel( &m_model );
sf->setFilterKeyColumn( 0 );
sf->setFilterRole( Role::ObjectType );
sf->setFilterRegExp( rex );
sf->setDynamicSortFilter( true );
FlatProxyModel *fm = new FlatProxyModel( sf );
sf->setSourceModel( fm );
m = new ResourceAppointmentsRowModel( fm );
fm->setSourceModel( m );
m_model.setSourceModel( sf );
}
//---------------------------
ChartReportData::ChartReportData( QObject *parent )
: ReportData( parent ),
cbs( false ),
m_firstrow( 0 ),
m_lastrow( -1 )
{
// these controls the amount of data (days) to include in a chart
m_keywords << "start"
<< "end"
<< "first"
<< "days";
}
ChartReportData::ChartReportData( const ChartReportData &other )
: ReportData( other ),
m_fakedata( true )
{
}
bool ChartReportData::open()
{
return ReportData::open();
}
int ChartReportData::firstRow()
{
if ( m_fakedata ) {
return 0;
}
int row = 0;
QDate s;
if ( m_expressions.contains( "start" ) ) {
s = m_expressions[ "start" ].toDate();
} else if ( m_expressions.contains( "first" ) ) {
s = QDate::currentDate().addDays( m_expressions[ "first" ].toInt() );
}
if ( s.isValid() ) {
if ( m_startdate.isValid() && s > m_startdate ) {
row = m_startdate.daysTo( s );
m_startdate = s;
}
debugPlan<<s<<row;
}
return row;
}
int ChartReportData::lastRow() const
{
if ( m_fakedata ) {
return 3;
}
int row = cbs
? m_model.columnCount() - 5 // cbs has data as columns + name, description, total (0-2) and parent (last)
: m_model.rowCount() - 1;
if ( row < 0 ) {
return -1;
}
QDate e;
if ( m_expressions.contains( "end" ) ) {
e = m_expressions[ "end" ].toDate();
} else if ( m_expressions.contains( "days" ) ) {
e = m_startdate.addDays( m_expressions[ "days" ].toInt() - 1 );
}
if ( e.isValid() ) {
QDate last;
if ( cbs ) {
last = m_model.headerData( row + 3, Qt::Horizontal, Qt::EditRole ).toDate();
} else {
last = m_model.headerData( row, Qt::Vertical, Qt::EditRole ).toDate();
}
if ( last.isValid() && e < last ) {
row -= ( e.daysTo( last ) );
}
debugPlan<<last<<e<<row;
}
return row > m_firstrow ? row : m_firstrow;
}
bool ChartReportData::moveNext()
{
if ( m_row >= recordCount() - 1 ) {
return false;
}
++m_row;
return true;
}
bool ChartReportData::movePrevious()
{
if ( m_row <= 0 ) {
return false;
}
--m_row;
return true;
}
bool ChartReportData::moveFirst()
{
m_row = 0;
return true;
}
bool ChartReportData::moveLast()
{
m_row = recordCount() - 1;
return true;
}
qint64 ChartReportData::recordCount() const
{
return m_lastrow < 0 ? 0 : m_lastrow - m_firstrow + 1;
}
QVariant ChartReportData::value ( unsigned int i ) const
{
if ( m_fakedata ) {
debugPlan<<m_row<<i;
return QVariant( ( int )( m_row * i ) );
}
QVariant value;
int row = m_row + m_firstrow;
if ( cbs ) {
if ( i == 0 ) {
// x-axis labels
value = m_model.headerData( row + 3, Qt::Horizontal );
} else {
// data
value = m_model.index( i - 1, row + 2 ).data( Role::Planned );
}
} else {
if ( i == 0 ) {
// x-axis labels
value = m_model.headerData( row, Qt::Vertical );
} else {
// data
value = m_model.index( row, i - 1 ).data();
debugPlan<<this<<row<<m_model.headerData( row, Qt::Vertical, Qt::EditRole )<<i<<"="<<value;
}
}
return value;
}
QVariant ChartReportData::value( const QString &name ) const
{
debugPlan<<name;
if ( m_expressions.contains( name ) ) {
return m_expressions[ name ];
}
return ReportData::value( name );
}
QStringList ChartReportData::fieldNames() const
{
// Legends
QStringList names;
names << ""; // first row/column not used
if ( cbs ) {
int count = m_model.rowCount();
for ( int i = 0; i < count; ++i ) {
names << m_model.index( i, 0 ).data().toString();
}
} else {
int count = m_model.columnCount();
for ( int i = 0; i < count; ++i ) {
// debugPlan<<this<<i<<"("<<count<<"):"<<m_model.headerData( i, Qt::Horizontal ).toString();
names << m_model.headerData( i, Qt::Horizontal ).toString();
}
}
// debugPlan<<this<<names;
return names;
}
void ChartReportData::addExpression( const QString &field, const QVariant &/*value*/, char /*relation*/ )
{
// debugPlan<<field<<value<<relation;
QStringList lst = field.split( '=', QString::SkipEmptyParts );
if ( lst.count() == 2 ) {
QString key = lst[ 0 ].trimmed().toLower();
if ( m_keywords.contains( key ) ) {
m_expressions.insert( key, lst[ 1 ].trimmed() );
} else {
warnPlan<<"unknown key:"<<key;
}
} else {
warnPlan<<"Invalid key or data:"<<field;
}
}
bool ChartReportData::loadXml( const KoXmlElement &element )
{
Q_UNUSED(element);
return true;
}
void ChartReportData::saveXml( QDomElement &element ) const
{
Q_UNUSED(element);
}
//-----------------
CostPerformanceReportData::CostPerformanceReportData( QObject *parent )
: ChartReportData( parent ),
m_chartmodel( 0 )
{
m_maindatasource = false;
m_subdatasource = true;
setObjectName( "costperformance" );
m_name = i18n( "Cost Performance" );
cbs = false;
createModels();
}
CostPerformanceReportData::CostPerformanceReportData( const CostPerformanceReportData &other )
: ChartReportData( other ),
m_chartmodel( 0 )
{
m_fakedata = false;
cbs = other.cbs;
createModels();
}
bool CostPerformanceReportData::open()
{
if ( ! ChartReportData::open() ) {
return false;
}
if ( m_chartmodel ) {
m_chartmodel->setNodes( m_project ? QList<Node*>() << m_project : QList<Node*>() );
}
m_startdate = m_model.headerData( 0, Qt::Vertical, Qt::EditRole ).toDate();
m_firstrow = firstRow();
m_lastrow = lastRow();
return true;
}
ReportData *CostPerformanceReportData::clone() const
{
return new CostPerformanceReportData( *this );
}
void CostPerformanceReportData::createModels()
{
ChartProxyModel *cpm = new ChartProxyModel( &m_model );
m_model.setSourceModel( cpm );
// hide effort
cpm->setRejectColumns( QList<int>() << 3 << 4 << 5 );
cpm->setZeroColumns( QList<int>() << 3 << 4 << 5 );
m_chartmodel = new ChartItemModel( cpm );
cpm->setSourceModel( m_chartmodel );
}
//-----------------
EffortPerformanceReportData::EffortPerformanceReportData( QObject *parent )
: ChartReportData( parent ),
m_chartmodel( 0 )
{
m_maindatasource = false;
m_subdatasource = true;
setObjectName( "effortperformance" );
m_name = i18n( "Effort Performance" );
cbs = false;
createModels();
}
EffortPerformanceReportData::EffortPerformanceReportData( const EffortPerformanceReportData &other )
: ChartReportData( other ),
m_chartmodel( 0 )
{
m_fakedata = false;
cbs = other.cbs;
createModels();
}
bool EffortPerformanceReportData::open()
{
if ( ! ChartReportData::open() ) {
return false;
}
if ( m_chartmodel ) {
m_chartmodel->setNodes( m_project ? QList<Node*>() << m_project : QList<Node*>() );
}
m_startdate = m_model.headerData( 0, Qt::Vertical, Qt::EditRole ).toDate();
m_firstrow = firstRow();
m_lastrow = lastRow();
return true;
}
ReportData *EffortPerformanceReportData::clone() const
{
return new EffortPerformanceReportData( *this );
}
void EffortPerformanceReportData::createModels()
{
ChartProxyModel *cpm = new ChartProxyModel( &m_model );
// hide cost
cpm->setRejectColumns( QList<int>() << 0 << 1 << 2 );
cpm->setZeroColumns( QList<int>() << 0 << 1 << 2 );
m_chartmodel = new ChartItemModel( cpm );
cpm->setSourceModel( m_chartmodel );
m_model.setSourceModel( cpm );
}
//-----------------
CostBreakdownReportData::CostBreakdownReportData( QObject *parent )
: ChartReportData( parent )
{
m_maindatasource = false;
m_subdatasource = true;
setObjectName( "costbreakdown" );
m_name = i18n( "Cost Breakdown" );
cbs = true;
createModels();
}
CostBreakdownReportData::CostBreakdownReportData( const CostBreakdownReportData &other )
: ChartReportData( other )
{
m_fakedata = false;
cbs = other.cbs;
createModels();
}
bool CostBreakdownReportData::open()
{
if ( ! ChartReportData::open() ) {
return false;
}
m_startdate = m_model.headerData( 3, Qt::Horizontal, Qt::EditRole ).toDate();
m_firstrow = firstRow();
m_lastrow = lastRow();
return true;
}
ReportData *CostBreakdownReportData::clone() const
{
return new CostBreakdownReportData( *this );
}
void CostBreakdownReportData::createModels()
{
FlatProxyModel *fm = new FlatProxyModel( &m_model );
ItemModelBase *m = new CostBreakdownItemModel( fm );
fm->setSourceModel( m );
m_model.setSourceModel( fm );
}
//-----------------
ProjectReportData::ProjectReportData( QObject *parent )
: ReportData( parent )
{
m_maindatasource = true;
m_subdatasource = false;
setObjectName( "project" );
m_name = i18n( "Project" );
createModels();
m_keys[NodeModel::NodeName] = "#project.name";
m_keys[NodeModel::NodeResponsible] = "#project.manager";
m_keys[NodeModel::NodeDescription] = "#project.description";
m_keys[NodeModel::NodeBCWS] = "#project.bcws-cost";
m_keys[NodeModel::NodeBCWP] = "#project.bcwp-cost";
m_keys[NodeModel::NodeACWP] = "#project.acwp-cost";
m_keys[NodeModel::NodePerformanceIndex] = "#project.spi-cost";
//TODO: not in nodemodel atm
// m_keys[NodeModel::NodePerformanceIndex] = "#project.cpi-cost";
// m_keys[NodeModel::NodeBCWS] = "#project.bcws-effort";
// m_keys[NodeModel::NodeBCWP] = "#project.bcwp-effort";
// m_keys[NodeModel::NodeACWP] = "#project.acwp-effort";
// m_keys[NodeModel::NodePerformanceIndex] = "#project.spi-effort";
m_names[NodeModel::NodeName] = m_data.headerData(NodeModel::NodeName).toString();
m_names[NodeModel::NodeResponsible] = m_data.headerData(NodeModel::NodeResponsible).toString();
m_names[NodeModel::NodeDescription] = m_data.headerData(NodeModel::NodeDescription).toString();
m_names[NodeModel::NodeBCWS] = m_data.headerData(NodeModel::NodeBCWS).toString();
m_names[NodeModel::NodeBCWP] = m_data.headerData(NodeModel::NodeBCWP).toString();
m_names[NodeModel::NodeACWP] = m_data.headerData(NodeModel::NodeACWP).toString();
m_names[NodeModel::NodePerformanceIndex] = m_data.headerData(NodeModel::NodePerformanceIndex).toString();
setColumnRole(NodeModel::NodeDescription, Qt::EditRole);
}
ProjectReportData::ProjectReportData( const ProjectReportData &other )
: ReportData( other )
{
m_keys = other.m_keys;
m_names = other.m_names;
m_project = other.m_project;
m_schedulemanager = other.m_schedulemanager;
createModels();
}
bool ProjectReportData::moveFirst()
{
m_row = 0;
return true;
}
bool ProjectReportData::moveNext()
{
m_row = 0;
return false; // only one row
}
bool ProjectReportData::moveLast()
{
m_row = 0;
return true; // always at last
}
QStringList ProjectReportData::fieldNames() const
{
return m_names.values();
}
QStringList ProjectReportData::fieldKeys() const
{
return m_keys.values();
}
QVariant ProjectReportData::value(int column) const
{
QVariant v;
if (!m_project) {
return v;
}
if (!m_project->locale()) {
debugPlan<<"No locale:"<<m_project;
return v;
}
int role = m_columnroles.value( column, Qt::DisplayRole );
v = m_data.data(m_project, column, role);
return v;
}
QVariant ProjectReportData::value(const QString &fld) const
{
QVariant v;
int column = m_keys.key(fld.toLower());
if (column >= 0) {
v = value(column);
}
debugPlan<<fld<<column<<v;
return v;
}
ReportData *ProjectReportData::clone() const
{
ReportData *r = new ProjectReportData( *this );
return r;
}
qint64 ProjectReportData::recordCount() const {
return m_keys.count();
}
void ProjectReportData::createModels()
{
m_data.setProject(m_project);
m_data.setManager(m_schedulemanager);
}
void ProjectReportData::setProject( Project *project )
{
m_data.setProject(project);
ReportData::setProject(project);
}
void ProjectReportData::setScheduleManager( ScheduleManager *sm )
{
m_data.setManager(sm);
ReportData::setScheduleManager(sm);
}
} //namespace KPlato
diff --git a/src/libs/ui/reports/reportscripts.cpp b/src/libs/ui/reports/reportscripts.cpp
index 3548e2fd..fdb40bdc 100644
--- a/src/libs/ui/reports/reportscripts.cpp
+++ b/src/libs/ui/reports/reportscripts.cpp
@@ -1,117 +1,118 @@
/* This file is part of the KDE project
Copyright (C) 2010, 2012 Dag Andersen <danders@get2net.dk>
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 "reportscripts.h"
#include "reportdata.h"
#include "kptdebug.h"
namespace KPlato
{
ProjectAccess::ProjectAccess( ReportData *rd )
: m_reportdata( rd )
{
}
QVariant ProjectAccess::Name() const
{
if ( m_reportdata && m_reportdata->project() ) {
return m_reportdata->project()->name().toUtf8();
}
return QVariant();
}
QVariant ProjectAccess::Manager() const
{
if ( m_reportdata && m_reportdata->project() ) {
return m_reportdata->project()->leader().toUtf8();
}
return QVariant();
}
QVariant ProjectAccess::Plan() const
{
if ( m_reportdata && m_reportdata->scheduleManager() ) {
return m_reportdata->scheduleManager()->name().toUtf8();
}
return QVariant();
}
QVariant ProjectAccess::BCWS() const
{
if ( m_reportdata && m_reportdata->project() ) {
long id = m_reportdata->scheduleManager() ? m_reportdata->scheduleManager()->scheduleId() : BASELINESCHEDULE;
double r = m_reportdata->project()->bcws( QDate::currentDate(), id );
return QLocale().toString(r, 'f', 2 ).toUtf8();
}
return QVariant();
}
QVariant ProjectAccess::BCWP() const
{
if ( m_reportdata && m_reportdata->project() ) {
long id = m_reportdata->scheduleManager() ? m_reportdata->scheduleManager()->scheduleId() : BASELINESCHEDULE;
double r = m_reportdata->project()->bcwp( QDate::currentDate(), id );
return QLocale().toString(r, 'f', 2 ).toUtf8();
}
warnPlan<<"No report data or project"<<m_reportdata;
return QVariant();
}
QVariant ProjectAccess::ACWP() const
{
if ( m_reportdata && m_reportdata->project() ) {
long id = m_reportdata->scheduleManager() ? m_reportdata->scheduleManager()->scheduleId() : BASELINESCHEDULE;
double r = m_reportdata->project()->acwp( QDate::currentDate(), id ).cost();
return QLocale().toString(r, 'f', 2 ).toUtf8();
}
return QVariant();
}
QVariant ProjectAccess::CPI() const
{
if ( m_reportdata && m_reportdata->project() ) {
double r = 0.0;
long id = m_reportdata->scheduleManager() ? m_reportdata->scheduleManager()->scheduleId() : BASELINESCHEDULE;
double b = m_reportdata->project()->bcwp( QDate::currentDate(), id );
double a = m_reportdata->project()->acwp( QDate::currentDate(), id ).cost();
if ( a > 0 ) {
r = b / a;
}
return QLocale().toString(r, 'f', 2 ).toUtf8();
}
return QVariant();
}
QVariant ProjectAccess::SPI() const
{
debugPlan<<"ProjectAccess::SPI:";
if ( m_reportdata && m_reportdata->project() ) {
int id = m_reportdata->scheduleManager() ? m_reportdata->scheduleManager()->scheduleId() : BASELINESCHEDULE;
double r = m_reportdata->project()->schedulePerformanceIndex( QDate::currentDate(), id );
return QLocale().toString(r, 'f', 2 ).toUtf8();
}
return QVariant();
}
} // namespace KPlato
diff --git a/src/libs/ui/reports/reportsourceeditor.cpp b/src/libs/ui/reports/reportsourceeditor.cpp
index a97f7853..c237da3c 100644
--- a/src/libs/ui/reports/reportsourceeditor.cpp
+++ b/src/libs/ui/reports/reportsourceeditor.cpp
@@ -1,92 +1,93 @@
/* This file is part of the KDE project
Copyright (C) 2010 Dag Andersen <danders@get2net.dk>
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 "reportsourceeditor.h"
#include "report.h"
#include "kptnodeitemmodel.h"
#include <QDomElement>
#include <QTimer>
#include "kptdebug.h"
namespace KPlato
{
ReportSourceEditor::ReportSourceEditor( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
connect(ui_source, SIGNAL(currentIndexChanged(int)), SLOT(slotCurrentIndexChanged()));
}
void ReportSourceEditor::setModel( QAbstractItemModel *model )
{
ui_source->setModel( model );
ui_source->setCurrentIndex( 0 );
}
void ReportSourceEditor::slotCurrentIndexChanged()
{
emit selectFromChanged( selectFromTag() );
}
QString ReportSourceEditor::selectFromTag() const
{
QString tag;
if ( ui_source->currentIndex() >= 0 ) {
QAbstractItemModel *m = ui_source->model();
tag = m->index( ui_source->currentIndex(), 0 ).data( Reports::TagRole ).toString();
}
return tag;
}
void ReportSourceEditor::setSourceData( const QDomElement &element )
{
if ( element.tagName() != "data-source" ) {
debugPlan<<"no source element";
ui_source->setCurrentIndex( 0 );
return;
}
QString selectfrom = element.attribute( "select-from" );
QAbstractItemModel *m = ui_source->model();
for ( int row = 0; row < m->rowCount(); ++row ) {
QString name = m->index( row, 0 ).data( Reports::TagRole ).toString();
if ( ! name.isEmpty() && name == selectfrom ) {
ui_source->setCurrentIndex( row );
return;
}
}
debugPlan<<"no source";
ui_source->setCurrentIndex( 0 );
}
void ReportSourceEditor::sourceData( QDomElement &element ) const
{
QDomElement e = element.ownerDocument().createElement( "data-source" );
element.appendChild( e );
int row = ui_source->currentIndex();
QAbstractItemModel *m = ui_source->model();
e.setAttribute( "select-from", m->index( row, 0 ).data( Reports::TagRole ).toString() );
}
} //namespace KPlato
diff --git a/src/libs/ui/reports/reportview.cpp b/src/libs/ui/reports/reportview.cpp
index 34c6f3bb..4bf0d460 100644
--- a/src/libs/ui/reports/reportview.cpp
+++ b/src/libs/ui/reports/reportview.cpp
@@ -1,1367 +1,1368 @@
/*
* KPlato Report Plugin
* Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk)
* Copyright (C) 2010, 2011, 2012 by Dag Andersen <danders@get2net.dk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser 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 "reportview.h"
#include "reportview_p.h"
#include "report.h"
#include "reportdata.h"
#include "reportsourceeditor.h"
#include "reportscripts.h"
#include "ui_reportsectionswidget.h"
#include "ui_reporttoolswidget.h"
#include <KReportPage>
#include <KReportPreRenderer>
#include <KReportRenderObjects>
#include <KReportDesigner>
#include <KReportDesignerSection>
#include <KReportDesignerSectionDetail>
#include <KReportDesignerSectionDetailGroup>
#include <KPropertyEditorView>
#include "kptglobal.h"
#include "kptaccountsmodel.h"
#include "kptflatproxymodel.h"
#include "kptnodeitemmodel.h"
#include "kpttaskstatusmodel.h"
#include "kptresourcemodel.h"
#include "kptresourceappointmentsmodel.h"
#include "kptschedule.h"
#include "kptnodechartmodel.h"
#include "kptdebug.h"
#include "KoPageLayout.h"
#include "KoDocument.h"
#include "KoIcon.h"
#include <KoXmlReader.h>
#include <kactionmenu.h>
#include <kactioncollection.h>
#include <kstandardaction.h>
#include <kstandardguiitem.h>
#include <kguiitem.h>
#include <kmessagebox.h>
#include <ktoolbar.h>
#include <QCloseEvent>
#include <QPainter>
#include <QPrintDialog>
#include <QPrinter>
#include <QLabel>
#include <QVBoxLayout>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLayout>
#include <QDockWidget>
#include <QModelIndex>
#include <QModelIndexList>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QActionGroup>
#include <QStackedWidget>
#include <QAction>
#include <qpushbutton.h>
#include <QMimeDatabase>
#include <QFileDialog>
namespace KPlato
{
//----------------
ReportPrintingDialog::ReportPrintingDialog( ViewBase *view, ORODocument *reportDocument )
: KoPrintingDialog( view ),
m_reportDocument( reportDocument )
{
printer().setFromTo( documentFirstPage(), documentLastPage() );
m_context.printer = &printer();
m_context.painter = 0;
KReportRendererFactory factory;
m_renderer = factory.createInstance( "print" );
//FIXME: This should be done by KReportPrintRender but setupPrinter() is private
QPrinter *pPrinter = &printer();
pPrinter->setCreator("Plan");
pPrinter->setDocName(reportDocument->title());
pPrinter->setFullPage(true);
pPrinter->setOrientation((reportDocument->pageOptions().isPortrait() ? QPrinter::Portrait : QPrinter::Landscape));
pPrinter->setPageOrder(QPrinter::FirstPageFirst);
if (reportDocument->pageOptions().getPageSize().isEmpty())
pPrinter->setPageSize(QPrinter::Custom);
else
pPrinter->setPageSize(KoPageFormat::printerPageSize(KoPageFormat::formatFromString(reportDocument->pageOptions().getPageSize())));
//FIXME: There is something wrong with KReport margins
qreal left = reportDocument->pageOptions().getMarginLeft();
qreal top = reportDocument->pageOptions().getMarginTop();
qreal right = reportDocument->pageOptions().getMarginRight();
qreal bottom = reportDocument->pageOptions().getMarginBottom();
pPrinter->setPageMargins( left, top, right, bottom, QPrinter::Point );
}
ReportPrintingDialog::~ReportPrintingDialog()
{
delete m_renderer;
}
void ReportPrintingDialog::startPrinting( RemovePolicy removePolicy )
{
debugPlan;
QPainter p( &printer() );
printPage( 1, p );
if ( removePolicy == DeleteWhenDone ) {
deleteLater();
}
}
int ReportPrintingDialog::documentLastPage() const
{
return m_reportDocument->pages();
}
void ReportPrintingDialog::printPage( int page, QPainter &painter )
{
m_context.painter = &painter;
m_renderer->render( m_context, m_reportDocument, page );
}
QAbstractPrintDialog::PrintDialogOptions ReportPrintingDialog::printDialogOptions() const
{
return QAbstractPrintDialog::PrintToFile |
QAbstractPrintDialog::PrintPageRange |
QAbstractPrintDialog::PrintCollateCopies |
QAbstractPrintDialog::DontUseSheet;
}
//---------------------
ReportView::ReportView(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent )
{
// debugPlan<<"--------------- ReportView ------------------";
setObjectName("ReportView");
QLayout *l = new QHBoxLayout( this );
l->setMargin(0);
m_stack = new QStackedWidget( this );
l->addWidget( m_stack );
ReportWidget *v = new ReportWidget(part, doc, m_stack);
m_stack->addWidget( v );
connect(v, SIGNAL(editReportDesign()),SLOT(slotEditReport()));
connect(v, SIGNAL(guiActivated(ViewBase*,bool)), SIGNAL(guiActivated(ViewBase*,bool)));
ReportDesigner *d = new ReportDesigner(part, doc, m_stack);
m_stack->addWidget( d );
connect(d, SIGNAL(viewReport()), SLOT(slotViewReport()));
connect(d, SIGNAL(guiActivated(ViewBase*,bool)), SIGNAL(guiActivated(ViewBase*,bool)));
connect(d, SIGNAL(optionsModified()), SIGNAL(optionsModified()));
m_stack->setCurrentIndex( 0 );
}
void ReportView::slotEditReport()
{
reportWidget()->setGuiActive( false );
m_stack->setCurrentIndex( 1 );
reportDesigner()->setGuiActive( true );
}
void ReportView::slotViewReport()
{
reportDesigner()->setGuiActive( false );
if ( reportWidget()->documentIsNull() || reportDesigner()->isModified() ) {
reportWidget()->loadXML( reportDesigner()->document() );
}
if ( reportDesigner()->isModified() ) {
emit optionsModified();
reportDesigner()->setModified( false );
}
m_stack->setCurrentIndex( 0 );
reportWidget()->setGuiActive( true );
}
void ReportView::setProject( Project *project )
{
reportWidget()->setProject( project );
reportDesigner()->setProject( project );
}
void ReportView::setScheduleManager( ScheduleManager *sm )
{
reportWidget()->setScheduleManager( sm );
reportDesigner()->setScheduleManager( sm );
}
void ReportView::slotRefreshView()
{
reportWidget()->slotRefreshView();
}
KoPrintJob *ReportView::createPrintJob()
{
return static_cast<ViewBase*>( m_stack->currentWidget() )->createPrintJob();
}
void ReportView::setGuiActive( bool active )
{
return static_cast<ViewBase*>( m_stack->currentWidget() )->setGuiActive( active );
}
bool ReportView::loadXML( const QDomDocument &doc )
{
reportDesigner()->setData( doc );
return reportWidget()->loadXML( doc );
}
bool ReportView::loadContext( const KoXmlElement &context )
{
bool res = true;
// designer first, widget uses it's data
res = reportDesigner()->loadContext( context );
res &= reportWidget()->loadContext( context );
reportWidget()->loadXML( reportDesigner()->document() );
return res;
}
void ReportView::saveContext( QDomElement &context ) const
{
QDomElement e = context.ownerDocument().createElement( "view" );
context.appendChild( e );
e.setAttribute( "current-view", QString::number(m_stack->currentIndex()) );
reportDesigner()->saveContext( context );
reportWidget()->saveContext( context );
}
ReportWidget *ReportView::reportWidget() const
{
return static_cast<ReportWidget*>( m_stack->widget( 0 ) );
}
ReportDesigner *ReportView::reportDesigner() const
{
return static_cast<ReportDesigner*>( m_stack->widget( 1 ) );
}
QDomDocument ReportView::document() const
{
return reportDesigner()->document();
}
QList< ReportData* > ReportView::reportDataModels() const
{
return reportWidget()->reportDataModels();
}
//---------------------
ReportWidget::ReportWidget(KoPart *part, KoDocument *doc, QWidget *parent )
: ViewBase(part, doc, parent ),
m_reportdatamodels( Report::createBaseReportDataModels() )
{
// debugPlan<<"--------------- ReportWidget ------------------";
m_preRenderer = 0;
setObjectName("ReportWidget");
m_reportView = new QGraphicsView(this);
m_reportScene = new QGraphicsScene(this);
m_reportScene->setSceneRect(0,0,1000,2000);
m_reportView->setScene(m_reportScene);
m_reportScene->setBackgroundBrush(palette().brush(QPalette::Dark));
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin(0);
l->addWidget( m_reportView );
m_pageSelector = new ReportNavigator( this );
l->addWidget( m_pageSelector );
setupGui();
connect(m_pageSelector->ui_next, SIGNAL(clicked()), this, SLOT(nextPage()));
connect(m_pageSelector->ui_prev, SIGNAL(clicked()), this, SLOT(prevPage()));
connect(m_pageSelector->ui_first, SIGNAL(clicked()), this, SLOT(firstPage()));
connect(m_pageSelector->ui_last, SIGNAL(clicked()), this, SLOT(lastPage()));
connect(m_pageSelector->ui_selector, SIGNAL(valueChanged(int)), SLOT(renderPage(int)));
slotRefreshView();
}
//-----------------
void ReportWidget::renderPage( int page )
{
m_reportPage->renderPage( page );
}
void ReportWidget::nextPage()
{
m_pageSelector->ui_selector->setValue( m_pageSelector->ui_selector->value() + 1 );
}
void ReportWidget::prevPage()
{
m_pageSelector->ui_selector->setValue( m_pageSelector->ui_selector->value() - 1 );
}
void ReportWidget::firstPage()
{
m_pageSelector->ui_selector->setValue( 1 );
}
void ReportWidget::lastPage()
{
m_pageSelector->ui_selector->setValue( m_pageSelector->ui_max->value() );
}
KoPrintJob *ReportWidget::createPrintJob()
{
return new ReportPrintingDialog( this, m_reportDocument );
}
KoPageLayout ReportWidget::pageLayout() const
{
KoPageLayout p = ViewBase::pageLayout();
KReportPageOptions opt = m_reportDocument->pageOptions();
p.orientation = opt.isPortrait() ? KoPageFormat::Portrait : KoPageFormat::Landscape;
if (opt.getPageSize().isEmpty()) {
p.format = KoPageFormat::CustomSize;
p.width = opt.getCustomWidth();
p.height = opt.getCustomHeight();
} else {
p.format = KoPageFormat::formatFromString(opt.getPageSize());
}
p.topMargin = opt.getMarginTop();
p.bottomMargin = opt.getMarginBottom();
p.leftMargin = opt.getMarginLeft();
p.rightMargin = opt.getMarginRight();
p.pageEdge = 0.0;
p.bindingSide = 0.0;
return p;
}
QUrl ReportWidget::getExportFileName(const QString &mimetype)
{
const QString filterString = QMimeDatabase().mimeTypeForName(mimetype).filterString();
const QString result = QFileDialog::getSaveFileName(this, xi18nc("@title:window", "Export Report"), QString(), filterString);
return result.isEmpty() ? QUrl() : QUrl::fromLocalFile(result);
}
void ReportWidget::exportAsTextDocument()
{
KReportRendererBase *renderer = m_factory.createInstance("odtframes");
if ( renderer == 0 ) {
KMessageBox::sorry(this, i18n("Export"), i18n("Export to text document is not supported"));
return;
}
KReportRendererContext context;
context.destinationUrl = getExportFileName(QStringLiteral("application/vnd.oasis.opendocument.text"));
debugPlan<<"Export to odt:"<<context.destinationUrl;
if (!context.destinationUrl.isValid()) {
return;
}
if (!renderer->render(context, m_reportDocument)) {
KMessageBox::error(this, xi18nc( "@info", "Failed to export to <filename>%1</filename>", context.destinationUrl.toDisplayString()) , i18n("Export to text document failed"));
}
}
void ReportWidget::exportAsSpreadsheet()
{
KReportRendererBase *renderer;
renderer = m_factory.createInstance("ods");
if ( renderer == 0 ) {
KMessageBox::sorry(this, i18n("Export"), i18n("Export to spreadsheet document is not supported"));
return;
}
KReportRendererContext context;
context.destinationUrl = getExportFileName(QStringLiteral("application/vnd.oasis.opendocument.spreadsheet"));
if (!context.destinationUrl.isValid()) {
return;
}
debugPlan<<"Export to ods:"<<context.destinationUrl;
if (!renderer->render(context, m_reportDocument)) {
KMessageBox::error(this, xi18nc( "@info", "Failed to export to <filename>%1</filename>", context.destinationUrl.toDisplayString()) , i18n("Export to spreadsheet failed"));
}
}
void ReportWidget::exportAsWebPage()
{
//QT5TODO: perhaps also support "htmltable", with similar question switch like kexi
//Though plugins should rather not be hardcoded at all in the future.
KReportRendererBase *renderer;
renderer = m_factory.createInstance("htmlcss");
if ( renderer == 0 ) {
KMessageBox::sorry(this, i18n("Export"), i18n("Export to HTML document is not supported"));
return;
}
KReportRendererContext context;
context.destinationUrl = getExportFileName(QStringLiteral("text/html"));
if (!context.destinationUrl.isValid()) {
return;
}
debugPlan<<"Export to html:"<<context.destinationUrl;
if (!renderer->render(context, m_reportDocument)) {
KMessageBox::error(this, xi18nc( "@info", "Failed to export to <filename>%1</filename>", context.destinationUrl.toDisplayString()) , i18n("Export to HTML failed"));
}
}
void ReportWidget::setupGui()
{
/*KActionCollection *coll = actionCollection();*/
QAction *a = 0;
QString name = "reportview_list";
a = new QAction(koIcon("go-next-view"), i18n("Edit Report"), this);
a->setToolTip( xi18nc( "@info:tooltip", "Edit the report definition" ) );
a->setWhatsThis( xi18nc( "@info:whatsthis", "Opens the report design in the report design dialog." ) );
connect(a, SIGNAL(triggered()), this, SIGNAL(editReportDesign()));
addAction( name, a );
/* TODO activate when KReport is fixed
KActionMenu *exportMenu = new KActionMenu(koIcon("document-export"), xi18nc("@title:menu","E&xport As"), this);
exportMenu->setToolTip( xi18nc( "@info:tooltip", "Export to file" ) );
exportMenu->setDelayed(false);
a = new QAction(koIcon("application-vnd.oasis.opendocument.text"), i18n("Text Document..."), this);
a->setToolTip(i18n("Export the report as a text document (in OpenDocument Text format)"));
connect(a, SIGNAL(triggered()), this, SLOT(exportAsTextDocument()));
exportMenu->addAction(a);
a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), i18n("Spreadsheet..."), this);
a->setToolTip(i18n("Export the report as a spreadsheet (in OpenDocument Spreadsheet format)"));
connect(a, SIGNAL(triggered()), this, SLOT(exportAsSpreadsheet()));
exportMenu->addAction(a);
a = new QAction(koIcon("text-html"), i18n("Web Page..."), this);
a->setObjectName("export_as_web_page");
a->setToolTip(i18n("Export the report as a web page (in HTML format)"));
connect(a, SIGNAL(triggered()), this, SLOT(exportAsWebPage()));
exportMenu->addAction(a);
addAction( name, exportMenu );*/
}
void ReportWidget::setGuiActive( bool active ) // virtual slot
{
if ( active ) {
slotRefreshView();
}
ViewBase::setGuiActive( active );
}
void ReportWidget::slotRefreshView()
{
if ( ! isVisible() ) {
debugPlan<<"Not visible";
return;
}
delete m_preRenderer;
QDomElement e = m_design.documentElement();
m_preRenderer = new KReportPreRenderer( e.firstChildElement( "report:content" ) );
if ( ! m_preRenderer->isValid()) {
debugPlan<<"Invalid design document";
return;
}
ReportData *rd = createReportData( e );
m_preRenderer->setSourceData( rd );
m_preRenderer->registerScriptObject(new ProjectAccess( rd ), "project");
if (! m_preRenderer->generateDocument()) {
debugPlan << "Could not generate report document";
return;
}
m_reportDocument = m_preRenderer->document();
m_pageSelector->setMaximum( m_reportDocument ? m_reportDocument->pages() : 1 );
m_pageSelector->setCurrentPage( 1 );
m_reportPage = new KReportPage(this, m_reportDocument);
m_reportPage->setObjectName("ReportPage");
m_reportScene->setSceneRect(0,0,m_reportPage->rect().width() + 40, m_reportPage->rect().height() + 40);
m_reportScene->addItem(m_reportPage);
m_reportPage->setPos(20,20);
m_reportView->centerOn(0,0);
return;
}
void ReportWidget::setReportDataModels( const QList<ReportData*> &models )
{
m_reportdatamodels = models;
}
ReportData *ReportWidget::createReportData( const QDomElement &element )
{
// get the data source
QDomElement e = element.firstChildElement( "data-source" );
QString modelname = e.attribute( "select-from" );
return createReportData( modelname );
}
ReportData *ReportWidget::createReportData( const QString &type )
{
ReportData *r = Report::findReportData( m_reportdatamodels, type );
Q_ASSERT( r );
if ( r ) {
r = r->clone();
r->setParent( this );
r->setProject( project() );
r->setScheduleManager( m_schedulemanager );
}
return r;
}
bool ReportWidget::loadXML( const QDomDocument &doc )
{
m_design = doc;
slotRefreshView();
return true;
}
bool ReportWidget::loadContext( const KoXmlElement &/*context*/ )
{
return true;
}
void ReportWidget::saveContext( QDomElement &/*context*/ ) const
{
}
bool ReportWidget::documentIsNull() const
{
return m_design.isNull();
}
//------------------
ReportNavigator::ReportNavigator( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
ui_first->setIcon(koIcon("go-first-view"));
ui_last->setIcon(koIcon("go-last-view"));
ui_prev->setIcon(koIcon("go-previous-view"));
ui_next->setIcon(koIcon("go-next-view"));
connect( ui_max, SIGNAL(valueChanged(int)), SLOT(slotMaxChanged(int)));
connect( ui_selector, SIGNAL(valueChanged(int)), SLOT(setButtonsEnabled()) );
ui_max->setValue( 1 );
}
void ReportNavigator::setMaximum( int value )
{
ui_max->setMaximum( value );
ui_max->setValue( value );
}
void ReportNavigator::setCurrentPage( int page )
{
ui_selector->setValue( page );
}
void ReportNavigator::slotMaxChanged( int value )
{
ui_selector->setMaximum( value );
setButtonsEnabled();
}
void ReportNavigator::setButtonsEnabled()
{
bool backw = ui_selector->value() > ui_selector->minimum();
ui_first->setEnabled( backw );
ui_prev->setEnabled( backw );
bool forw = ui_selector->value() < ui_selector->maximum();
ui_last->setEnabled( forw );
ui_next->setEnabled( forw );
}
//-------------------
ModifyReportDefinitionCmd ::ModifyReportDefinitionCmd( ReportView *view, const QDomDocument &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_view( view ),
m_newvalue( value.cloneNode().toDocument() ),
m_oldvalue( m_view->document().cloneNode().toDocument() )
{
}
void ModifyReportDefinitionCmd ::execute()
{
m_view->loadXML( m_newvalue );
}
void ModifyReportDefinitionCmd ::unexecute()
{
m_view->loadXML( m_oldvalue );
}
//--------------------------
ReportDesigner::ReportDesigner(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent),
m_designer( 0 ),
m_reportdatamodels( Report::createBaseReportDataModels() ),
m_groupsectioneditor( new GroupSectionEditor( this ) )
{
QVBoxLayout *l = new QVBoxLayout( this );
l->setMargin(0);
m_scrollarea = new QScrollArea( this );
l->addWidget( m_scrollarea );
setupGui();
QDomDocument domdoc;
domdoc.setContent( QString( "<planreportdefinition version=\"1.0\" mime=\"application/x-vnd.kde.plan.report.definition\" editor=\"Plan<\">"
"<data-source select-from=\"tasks\"/>"
"<report:content xmlns:report=\"http://kexi-project.org/report/2.0\" "
"xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" "
"xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\">"
"<report:title>Report</report:title>"
"</report:content>"
"</planreportdefinition>" ) );
setData( domdoc );
}
void ReportDesigner::setupGui()
{
/*KActionCollection *coll = actionCollection();*/
QAction *a = 0;
QString name = "edit_copypaste";
a = KStandardAction::cut( this );
connect(a, SIGNAL(triggered(bool)), this, SIGNAL(cutActivated()));
addAction( name, a );
a = KStandardAction::copy( this );
connect(a, SIGNAL(triggered(bool)), this, SIGNAL(copyActivated()));
addAction( name, a );
a = KStandardAction::paste( this );
connect(a, SIGNAL(triggered(bool)), this, SIGNAL(pasteActivated()));
addAction( name, a );
const KGuiItem del = KStandardGuiItem::del();
a = new QAction(del.icon(), del.text(), this);
a->setObjectName( "edit_delete" );
a->setToolTip(del.toolTip());
a->setShortcut( QKeySequence::Delete );
connect(a, SIGNAL(triggered(bool)), this, SIGNAL(deleteActivated()));
addAction( name, a );
name = "reportdesigner_list";
a = new QAction(koIcon("go-previous-view"), i18n("View report"), this);
a->setObjectName( "view_report" );
connect(a, SIGNAL(triggered(bool)), SIGNAL(viewReport()));
addAction( name, a );
m_undoaction = new QAction(koIcon("edit-undo"), i18n("Undo all changes"), this);
m_undoaction->setObjectName( "undo_all_changes" );
m_undoaction->setEnabled( false );
connect(m_undoaction, SIGNAL(triggered(bool)), SLOT(undoAllChanges()));
addAction( name, m_undoaction );
a = new QAction(koIcon("document-export"), i18n("Export report definition"), this);
a->setObjectName( "save_report_definition" );
connect(a, SIGNAL(triggered(bool)), SLOT(slotSaveReportDefinition()));
addAction( name, a );
createDockers();
}
void ReportDesigner::slotSaveReportDefinition()
{
const QString fileName = QFileDialog::getSaveFileName(this);
if (fileName.isEmpty()) {
debugPlan<<"No file name given";
return;
}
QFile file( fileName );
if ( ! file.open( QIODevice::WriteOnly ) ) {
KMessageBox::sorry( this, xi18nc( "@info", "Cannot open file:<nl/><filename>%1</filename>", file.fileName() ) );
return;
}
QTextStream out( &file );
out << document().toString();
file.close();
}
void ReportDesigner::undoAllChanges()
{
if ( isModified() ) {
setData();
}
}
void ReportDesigner::slotModified()
{
m_undoaction->setEnabled( isModified() );
}
bool ReportDesigner::isModified() const
{
return m_designer->isModified();
}
void ReportDesigner::setModified( bool on )
{
m_designer->setModified( on );
m_undoaction->setEnabled( on );
}
void ReportDesigner::setData( const QDomDocument &doc )
{
m_original = doc.cloneNode().toDocument();
setData();
}
void ReportDesigner::setData()
{
delete m_designer;
QDomElement e = m_original.documentElement().firstChildElement( "report:content" );
if ( e.isNull() ) {
m_designer = new KReportDesigner( m_scrollarea );
} else {
m_designer = new KReportDesigner( m_scrollarea, e );
}
m_scrollarea->setWidget( m_designer );
m_sourceeditor->setSourceData( m_original.documentElement().firstChildElement( "data-source" ) );
blockSignals( true );
setReportData( m_sourceeditor->selectFromTag() );
blockSignals( false );
slotPropertySetChanged();
connect(m_designer, SIGNAL(dirty()), SLOT(slotModified()));
connect(m_designer, SIGNAL(propertySetChanged()), SLOT(slotPropertySetChanged()));
connect(m_designer, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString)));
connect(this, SIGNAL(cutActivated()), m_designer, SLOT(slotEditCut()));
connect(this, SIGNAL(copyActivated()), m_designer, SLOT(slotEditCopy()));
connect(this, SIGNAL(pasteActivated()), m_designer, SLOT(slotEditPaste()));
connect(this, SIGNAL(deleteActivated()), m_designer, SLOT(slotEditDelete()));
emit reportheaderShown(m_designer->section(KReportSectionData::ReportHeader));
emit reportfooterShown(m_designer->section(KReportSectionData::ReportFooter));
emit headerFirstpageShown(m_designer->section(KReportSectionData::PageHeaderFirst));
emit headerLastpageShown(m_designer->section(KReportSectionData::PageHeaderLast));
emit headerOddpagesShown(m_designer->section(KReportSectionData::PageHeaderOdd));
emit headerEvenpagesShown(m_designer->section(KReportSectionData::PageHeaderEven));
emit headerAllpagesShown(m_designer->section(KReportSectionData::PageHeaderAny));
emit footerFirstpageShown(m_designer->section(KReportSectionData::PageFooterFirst));
emit footerLastpageShown(m_designer->section(KReportSectionData::PageFooterLast));
emit footerOddpagesShown(m_designer->section(KReportSectionData::PageFooterOdd));
emit footerEvenpagesShown(m_designer->section(KReportSectionData::PageFooterEven));
emit footerAllpagesShown(m_designer->section(KReportSectionData::PageFooterAny));
m_designer->setModified( false );
slotModified();
}
QDomDocument ReportDesigner::document() const
{
QDomDocument document( "planreportdefinition" );
document.appendChild( document.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement e = document.createElement( "planreportdefinition" );
e.setAttribute( "editor", "Plan" );
e.setAttribute( "mime", "application/x-vnd.kde.plan.report.definition" );
e.setAttribute( "version", "1.0" );
document.appendChild( e );
if ( m_sourceeditor ) {
m_sourceeditor->sourceData( e );
}
e.appendChild( m_designer->document() );
/* debugPlan<<"ReportDesignerView::document:";
* debugPlan<<document.toString();*/
return document;
}
void ReportDesigner::createDockers()
{
// Add dockers
DockWidget *dw;
QWidget *w;
dw = new DockWidget( this, "DataElements", xi18nc( "@title:window report data elements", "Data Elements" ) );
dw->setLocation( Qt::LeftDockWidgetArea );
w = new QWidget( dw );
Ui::ReportToolsWidget tw;
tw.setupUi( w );
// allow only the following item types, there is not appropriate data for others
const QStringList itemtypes = QStringList()
<< QLatin1String("org.kde.kreport.label")
<< QLatin1String("org.kde.kreport.plan.text") // replaces text
<< QLatin1String("org.kde.kreport.field")
<< QLatin1String("org.kde.kreport.line")
<< QLatin1String("org.kde.kreport.checkbox")
<< QLatin1String("org.kde.kreport.chart")
<< QLatin1String("org.kde.kreport.web"); // can be used for fixed sized rich text
// TODO: proper tooltips
// const QStringList itemTooltips = QStringList()
// << xi18nc( "@into:tooltip", "Label" )
// << xi18nc( "@into:tooltip", "Text element with variable height" )
// << xi18nc( "@into:tooltip", "Text element" )
// << xi18nc( "@into:tooltip", "Line" )
// << xi18nc( "@into:tooltip", "Checkbox" )
// << xi18nc( "@into:tooltip", "Chart" )
// << xi18nc( "@into:tooltip", "Text element with fixed size" );
QActionGroup *ag = new QActionGroup( this );
QMap<int, QToolButton*> tblst;
for( const QAction *a : m_designer->itemActions( ag ) ) {
int pos = itemtypes.indexOf( a->objectName() );
if ( pos >= 0 ) {
QToolButton *tb = new QToolButton( w );
tb->setObjectName( a->objectName() );
tb->setIcon( a->icon() );
tb->setText( a->text() );
tb->setToolTip( a->text() );
tb->setCheckable( true );
tblst[pos] = tb;
connect(tb, SIGNAL(clicked(bool)), SLOT(slotInsertAction()));
connect(this, SIGNAL(resetButtonState(bool)), tb, SLOT(setChecked(bool)));
}
}
int i = 0;
for(QToolButton *tb : tblst) {
tw.horizontalLayout->insertWidget( i++, tb );
}
m_sourceeditor = tw.sourceEditor;
m_sourceeditor->setModel( createSourceModel( m_sourceeditor ) );
connect(m_sourceeditor, SIGNAL(selectFromChanged(QString)), SLOT(setReportData(QString)));
m_propertyeditor = tw.propertyEditor;
dw->setWidget( w );
addDocker( dw );
dw = new DockWidget( this, "Sections", xi18nc( "@title:window report section docker", "Headers && Footers" ) );
dw->setLocation( Qt::RightDockWidgetArea );
w = new QScrollArea( dw );
Ui::ReportSectionsWidget sw;
sw.setupUi( w );
dw->setWidget( w );
connect(sw.reportheader, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.reportfooter, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.headerFirstpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.headerLastpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.headerOddpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.headerEvenpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.headerAllpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.footerFirstpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.footerLastpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.footerOddpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.footerEvenpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(sw.footerAllpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool)));
connect(this, SIGNAL(reportheaderShown(bool)), sw.reportheader, SLOT(setChecked(bool)));
connect(this, SIGNAL(reportfooterShown(bool)), sw.reportfooter, SLOT(setChecked(bool)));
connect(this, SIGNAL(headerFirstpageShown(bool)), sw.headerFirstpage, SLOT(setChecked(bool)));
connect(this, SIGNAL(headerLastpageShown(bool)), sw.headerLastpage, SLOT(setChecked(bool)));
connect(this, SIGNAL(headerOddpagesShown(bool)), sw.headerOddpages, SLOT(setChecked(bool)));
connect(this, SIGNAL(headerEvenpagesShown(bool)), sw.headerEvenpages, SLOT(setChecked(bool)));
connect(this, SIGNAL(headerAllpagesShown(bool)), sw.headerAllpages, SLOT(setChecked(bool)));
connect(this, SIGNAL(footerFirstpageShown(bool)), sw.footerFirstpage, SLOT(setChecked(bool)));
connect(this, SIGNAL(footerLastpageShown(bool)), sw.footerLastpage, SLOT(setChecked(bool)));
connect(this, SIGNAL(footerOddpagesShown(bool)), sw.footerOddpages, SLOT(setChecked(bool)));
connect(this, SIGNAL(footerEvenpagesShown(bool)), sw.footerEvenpages, SLOT(setChecked(bool)));
connect(this, SIGNAL(footerAllpagesShown(bool)), sw.footerAllpages, SLOT(setChecked(bool)));
addDocker( dw );
dw = new DockWidget( this, "Groups", xi18nc( "@title:window report group section docker", "Groups" ) );
dw->setLocation( Qt::RightDockWidgetArea );
w = new QWidget( dw );
m_groupsectioneditor->setupUi( w );
dw->setWidget( w );
addDocker( dw );
}
void ReportDesigner::setReportData( const QString &tag )
{
emit optionsModified();
ReportData *rd = Report::findReportData( m_reportdatamodels, tag );
if ( rd ) {
rd = rd->clone(); // KReportDesigner takes ownership, so give it a clone it can delete
}
m_designer->setReportData( rd );
m_groupsectioneditor->setData( m_designer, rd );
}
QStandardItemModel *ReportDesigner::createSourceModel( QObject *parent ) const
{
QStandardItemModel *m = new QStandardItemModel( parent );
foreach ( ReportData *r, m_reportdatamodels ) {
if ( r->isMainDataSource() ) {
QStandardItem *item = new QStandardItem( r->sourceName() );
item->setData( r->objectName(), Reports::TagRole );
item->setEditable( false );
m->appendRow( item );
}
}
return m;
}
void ReportDesigner::slotPropertySetChanged()
{
if ( m_propertyeditor ) {
m_propertyeditor->changeSet( m_designer->itemPropertySet() );
}
}
void ReportDesigner::slotInsertAction()
{
m_designer->slotItem( sender()->objectName() );
}
void ReportDesigner::slotItemInserted(const QString &)
{
emit resetButtonState( false );
}
void ReportDesigner::slotSectionToggled( bool on )
{
QString n = sender()->objectName();
if ( n == "reportheader" ) {
on ? m_designer->insertSection( KReportSectionData::ReportHeader )
: m_designer->removeSection( KReportSectionData::ReportHeader );
} else if ( n == "reportfooter" ) {
on ? m_designer->insertSection( KReportSectionData::ReportFooter )
: m_designer->removeSection( KReportSectionData::ReportFooter );
} else if ( n == "headerFirstpage" ) {
on ? m_designer->insertSection( KReportSectionData::PageHeaderFirst )
: m_designer->removeSection( KReportSectionData::PageHeaderFirst );
} else if ( n == "headerLastpage" ) {
on ? m_designer->insertSection( KReportSectionData::PageHeaderLast )
: m_designer->removeSection( KReportSectionData::PageHeaderLast );
} else if ( n == "headerOddpages" ) {
on ? m_designer->insertSection( KReportSectionData::PageHeaderOdd )
: m_designer->removeSection( KReportSectionData::PageHeaderOdd );
} else if ( n == "headerEvenpages" ) {
on ? m_designer->insertSection( KReportSectionData::PageHeaderEven )
: m_designer->removeSection( KReportSectionData::PageHeaderEven );
} else if ( n == "headerAllpages" ) {
on ? m_designer->insertSection( KReportSectionData::PageHeaderAny )
: m_designer->removeSection( KReportSectionData::PageHeaderAny );
} else if ( n == "footerFirstpage" ) {
on ? m_designer->insertSection( KReportSectionData::PageFooterFirst )
: m_designer->removeSection( KReportSectionData::PageFooterFirst );
} else if ( n == "footerLastpage" ) {
on ? m_designer->insertSection( KReportSectionData::PageFooterLast )
: m_designer->removeSection( KReportSectionData::PageFooterLast );
} else if ( n == "footerOddpages" ) {
on ? m_designer->insertSection( KReportSectionData::PageFooterOdd )
: m_designer->removeSection( KReportSectionData::PageFooterOdd );
} else if ( n == "footerEvenpages" ) {
on ? m_designer->insertSection( KReportSectionData::PageFooterEven )
: m_designer->removeSection( KReportSectionData::PageFooterEven );
} else if ( n == "footerAllpages" ) {
on ? m_designer->insertSection( KReportSectionData::PageFooterAny )
: m_designer->removeSection( KReportSectionData::PageFooterAny );
} else {
debugPlan<<"unknown section";
}
}
bool ReportDesigner::loadContext(const KoXmlElement& context)
{
KoXmlElement e = context.namedItem( "planreportdefinition" ).toElement();
if ( e.isNull() ) {
e = context.namedItem( "kplatoreportdefinition" ).toElement();
}
if ( ! e.isNull() ) {
QDomDocument doc( "context" );
KoXml::asQDomElement( doc, e );
setData( doc );
} else {
debugPlan<<"Invalid context xml";
setData( QDomDocument() ); // create an empty designer
}
return true;
}
void ReportDesigner::saveContext(QDomElement& context) const
{
context.appendChild( document().documentElement().cloneNode() );
}
//---------------------
GroupSectionEditor::GroupSectionEditor( QObject *parent )
: QObject( parent ),
designer( 0 ),
reportdata( 0 )
{
clear();
}
void GroupSectionEditor::setupUi( QWidget *widget )
{
gsw.setupUi( widget );
gsw.view->setModel( &model );
gsw.view->setItemDelegateForColumn( 0, new EnumDelegate( gsw.view ) );
gsw.view->setItemDelegateForColumn( 1, new CheckStateItemDelegate( gsw.view ) );
gsw.view->setItemDelegateForColumn( 2, new EnumDelegate( gsw.view ) );
gsw.view->setItemDelegateForColumn( 3, new EnumDelegate( gsw.view ) );
gsw.view->setItemDelegateForColumn( 4, new EnumDelegate( gsw.view ) );
gsw.btnAdd->setIcon(koIcon("list-add"));
gsw.btnRemove->setIcon(koIcon("list-remove"));
gsw.btnMoveUp->setIcon(koIcon("arrow-up"));
gsw.btnMoveDown->setIcon(koIcon("arrow-down"));
gsw.btnRemove->setEnabled( false );
gsw.btnMoveUp->setEnabled( false );
gsw.btnMoveDown->setEnabled( false );
connect(gsw.view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(slotSelectionChanged(QItemSelection)));
connect(gsw.btnAdd, SIGNAL(clicked(bool)), SLOT(slotAddRow()));
connect(gsw.btnRemove, SIGNAL(clicked(bool)), SLOT(slotRemoveRows()));
connect(gsw.btnMoveUp, SIGNAL(clicked(bool)), SLOT(slotMoveRowUp()));
connect(gsw.btnMoveDown, SIGNAL(clicked(bool)), SLOT(slotMoveRowDown()));
}
void GroupSectionEditor::slotSelectionChanged( const QItemSelection &sel )
{
QItemSelectionModel *m = gsw.view->selectionModel();
gsw.btnRemove->setEnabled( ! sel.isEmpty() );
gsw.btnMoveUp->setEnabled( ! sel.isEmpty() && ! m->isRowSelected( 0, QModelIndex() ) );
gsw.btnMoveDown->setEnabled( ! sel.isEmpty() && ! m->isRowSelected( model.rowCount() - 1, QModelIndex() ) );
}
void GroupSectionEditor::clear()
{
model.clear();
QStringList n;
n << xi18nc( "@title:column", "Column" )
<< xi18nc( "@title:column", "Sort" )
<< xi18nc( "@title:column", "Header" )
<< xi18nc( "@title:column", "Footer" )
<< xi18nc( "@title:column", "Page Break" );
model.setHorizontalHeaderLabels( n );
model.setHeaderData( 0, Qt::Horizontal, xi18nc( "@info:tooltip", "Groups data by the selected column" ), Qt::ToolTipRole );
model.setHeaderData( 1, Qt::Horizontal, xi18nc( "@info:tooltip", "Sorts data" ), Qt::ToolTipRole );
model.setHeaderData( 2, Qt::Horizontal, xi18nc( "@info:tooltip", "Show header section" ), Qt::ToolTipRole );
model.setHeaderData( 3, Qt::Horizontal, xi18nc( "@info:tooltip", "Show footer section" ), Qt::ToolTipRole );
model.setHeaderData( 4, Qt::Horizontal, xi18nc( "@info:tooltip", "Insert page break" ), Qt::ToolTipRole );
}
void GroupSectionEditor::setData( KReportDesigner *d, ReportData *rd )
{
clear();
designer = d;
reportdata = rd;
KReportDesignerSectionDetail *sd = designer->detailSection();
if ( ! sd ) {
return;
}
for (int i = 0; i < sd->groupSectionCount(); i++) {
KReportDesignerSectionDetailGroup *g = sd->groupSection( i );
ColumnItem *ci = new ColumnItem( g );
ci->names = rd->fieldNames();
ci->keys = rd->fieldKeys();
SortItem *si = new SortItem( g );
HeaderItem *hi = new HeaderItem( g );
FooterItem *fi = new FooterItem( g );
PageBreakItem *pi = new PageBreakItem( g );
model.appendRow( QList<QStandardItem*>() << ci << si << hi << fi << pi );
}
}
void GroupSectionEditor::slotAddRow()
{
KReportDesignerSectionDetail *sd = designer->detailSection();
if ( ! sd ) {
return;
}
KReportDesignerSectionDetailGroup * g = new KReportDesignerSectionDetailGroup( reportdata->fieldKeys().value( 0 ), sd, sd );
sd->insertGroupSection( sd->groupSectionCount(), g );
ColumnItem *ci = new ColumnItem( g );
ci->names = reportdata->fieldNames();
ci->keys = reportdata->fieldKeys();
SortItem *si = new SortItem( g );
HeaderItem *hi = new HeaderItem( g );
FooterItem *fi = new FooterItem( g );
PageBreakItem *pi = new PageBreakItem( g );
model.appendRow( QList<QStandardItem*>() << ci << si << hi << fi << pi );
}
void GroupSectionEditor::slotRemoveRows()
{
KReportDesignerSectionDetail *sd = designer->detailSection();
if ( ! sd ) {
return;
}
QList<int> rows;
foreach ( const QModelIndex &idx, gsw.view->selectionModel()->selectedRows() ) {
rows <<idx.row();
}
qSort( rows );
for (int i = rows.count() - 1; i >= 0; --i ) {
int row = rows.at( i );
QList<QStandardItem*> items = model.takeRow( row );
sd->removeGroupSection( row, true );
qDeleteAll( items );
}
}
void GroupSectionEditor::slotMoveRowUp()
{
KReportDesignerSectionDetail *sd = designer->detailSection();
if ( ! sd ) {
return;
}
QList<int> rows;
foreach ( const QModelIndex &idx, gsw.view->selectionModel()->selectedRows() ) {
rows <<idx.row();
}
qSort( rows );
if ( rows.isEmpty() || rows.first() == 0 ) {
return;
}
foreach ( int row, rows ) {
QList<QStandardItem*> items = model.takeRow( row );
KReportDesignerSectionDetailGroup *g = sd->groupSection( row );
bool showgh = g->groupHeaderVisible();
bool showgf = g->groupFooterVisible();
sd->removeGroupSection( row );
sd->insertGroupSection( row - 1, g );
g->setGroupHeaderVisible( showgh );
g->setGroupFooterVisible( showgf );
model.insertRow( row - 1, items );
}
QModelIndex idx1 = model.index( rows.first()-1, 0 );
QModelIndex idx2 = model.index( rows.last()-1, 0 );
QItemSelection s = QItemSelection ( idx1, idx2 );
gsw.view->selectionModel()->select( s, QItemSelectionModel::Rows | QItemSelectionModel::Clear | QItemSelectionModel::Select );
}
void GroupSectionEditor::slotMoveRowDown()
{
KReportDesignerSectionDetail *sd = designer->detailSection();
if ( ! sd ) {
return;
}
QList<int> rows;
foreach ( const QModelIndex &idx, gsw.view->selectionModel()->selectedRows() ) {
rows <<idx.row();
}
qSort( rows );
if ( rows.isEmpty() || rows.last() >= model.rowCount() - 1 ) {
return;
}
for ( int i = rows.count() - 1; i >= 0; --i ) {
int row = rows.at( i );
QList<QStandardItem*> items = model.takeRow( row );
KReportDesignerSectionDetailGroup *g = sd->groupSection( row );
bool showgh = g->groupHeaderVisible();
bool showgf = g->groupFooterVisible();
sd->removeGroupSection( row );
sd->insertGroupSection( row + 1, g );
g->setGroupHeaderVisible( showgh );
g->setGroupFooterVisible( showgf );
model.insertRow( row + 1, items );
}
QModelIndex idx1 = model.index( rows.first()+1, 0 );
QModelIndex idx2 = model.index( rows.last()+1, 0 );
QItemSelection s = QItemSelection ( idx1, idx2 );
gsw.view->selectionModel()->select( s, QItemSelectionModel::Rows | QItemSelectionModel::Clear | QItemSelectionModel::Select );
}
//----------------
GroupSectionEditor::ColumnItem::ColumnItem( KReportDesignerSectionDetailGroup *g )
: Item( g )
{
}
QVariant GroupSectionEditor::ColumnItem::data( int role ) const
{
switch ( role ) {
case Qt::DisplayRole: return names.value( keys.indexOf( group->column() ) );
case Role::EnumList: return names;
case Role::EnumListValue: return keys.indexOf( group->column() );
default: break;
}
return Item::data( role );
}
void GroupSectionEditor::ColumnItem::setData( const QVariant &value, int role )
{
if ( role == Qt::EditRole ) {
group->setColumn( keys.value( value.toInt() ) );
return;
}
return Item::setData( value, role );
}
//---------------------
GroupSectionEditor::SortItem::SortItem( KReportDesignerSectionDetailGroup *g )
: Item( g )
{
names << i18n( "Ascending" ) << i18n( "Descending" );
}
QVariant GroupSectionEditor::SortItem::data( int role ) const
{
switch ( role ) {
case Qt::DisplayRole: return QVariant();
case Qt::ToolTipRole: return group->sort() ? names.value( 1 ) : names.value( 0 );
case Qt::DecorationRole: return group->sort() ? koIcon("arrow-down") : koIcon("arrow-up");
case Qt::EditRole: return group->sort() ? Qt::Unchecked : Qt::Checked;
case Role::EnumList: return names;
case Role::EnumListValue: return group->sort() ? 1 : 0;
default: break;
}
return Item::data( role );
}
void GroupSectionEditor::SortItem::setData( const QVariant &value, int role )
{
if ( role == Qt::EditRole ) {
group->setSort( value.toInt() == 0 ? Qt::AscendingOrder : Qt::DescendingOrder );
return;
} else if ( role == Qt::CheckStateRole ) {
group->setSort( value.toInt() == 0 ? Qt::DescendingOrder : Qt::AscendingOrder );
return;
}
return Item::setData( value, role );
}
//---------------------
GroupSectionEditor::HeaderItem::HeaderItem( KReportDesignerSectionDetailGroup *g )
: Item( g )
{
names << i18n( "No" ) << i18n( "Yes" );
setCheckable( true );
}
QVariant GroupSectionEditor::HeaderItem::data( int role ) const
{
switch ( role ) {
case Qt::DisplayRole: return QVariant();
case Qt::CheckStateRole: return group->groupHeaderVisible() ? Qt::Checked : Qt::Unchecked;
case Role::EnumList: return names;
case Role::EnumListValue: return group->groupHeaderVisible() ? 1 : 0;
default: break;
}
return Item::data( role );
}
void GroupSectionEditor::HeaderItem::setData( const QVariant &value, int role )
{
debugPlan<<value<<role;
if ( role == Qt::EditRole ) {
group->setGroupHeaderVisible( value.toInt() == 1 );
return;
} else if ( role == Qt::CheckStateRole ) {
group->setGroupHeaderVisible( value.toInt() > 0 );
return;
}
return Item::setData( value, role );
}
//---------------------
GroupSectionEditor::FooterItem::FooterItem( KReportDesignerSectionDetailGroup *g )
: Item( g )
{
names << i18n( "No" ) << i18n( "Yes" );
setCheckable( true );
}
QVariant GroupSectionEditor::FooterItem::data( int role ) const
{
switch ( role ) {
case Qt::DisplayRole: return QVariant();
case Qt::CheckStateRole: return group->groupFooterVisible() ? Qt::Checked : Qt::Unchecked;
case Role::EnumList: return names;
case Role::EnumListValue: return group->groupFooterVisible() ? 1 : 0;
default: break;
}
return Item::data( role );
}
void GroupSectionEditor::FooterItem::setData( const QVariant &value, int role )
{
if ( role == Qt::EditRole ) {
group->setGroupFooterVisible( value.toInt() == 1 );
return;
} else if ( role == Qt::CheckStateRole ) {
group->setGroupFooterVisible( value.toInt() > 0 );
return;
}
return Item::setData( value, role );
}
//---------------------
GroupSectionEditor::PageBreakItem::PageBreakItem( KReportDesignerSectionDetailGroup *g )
: Item( g )
{
names << i18n( "None" ) << i18n( "After footer" ) << i18n( "Before header" );
}
QVariant GroupSectionEditor::PageBreakItem::data( int role ) const
{
switch ( role ) {
case Qt::DisplayRole: return names.value( (int)group->pageBreak() );
case Qt::ToolTipRole: return names.value( (int)group->pageBreak() );
case Role::EnumList: return names;
case Role::EnumListValue: return (int)group->pageBreak();
default: break;
}
return Item::data( role );
}
void GroupSectionEditor::PageBreakItem::setData( const QVariant &value, int role )
{
if ( role == Qt::EditRole ) {
group->setPageBreak( (KReportDesignerSectionDetailGroup::PageBreak)( value.toInt() ) );
return;
}
return Item::setData( value, role );
}
} // namespace KPlato
diff --git a/src/libs/ui/reportsgenerator/ReportsGeneratorView.cpp b/src/libs/ui/reportsgenerator/ReportsGeneratorView.cpp
index 8e18d4df..b6084c35 100644
--- a/src/libs/ui/reportsgenerator/ReportsGeneratorView.cpp
+++ b/src/libs/ui/reportsgenerator/ReportsGeneratorView.cpp
@@ -1,539 +1,540 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 "ReportsGeneratorView.h"
#include "reportgenerator/ReportGenerator.h"
#include "Help.h"
#include "kptdebug.h"
#include <KoIcon.h>
#include <KoXmlReader.h>
#include <KActionCollection>
#include <KUrlRequester>
#include <KFile>
#include <QAction>
#include <QHeaderView>
#include <QTreeView>
#include <QStandardItemModel>
#include <QModelIndex>
#include <QModelIndexList>
#include <QStyledItemDelegate>
#include <QString>
#include <QStringList>
#include <QStandardPaths>
#include <QDir>
#include <QUrl>
#include <QMap>
#include <QMessageBox>
#include <QDebug>
namespace KPlato
{
#define FULLPATHROLE Qt::UserRole + 123
class TemplateFileDelegate : public QStyledItemDelegate
{
public:
TemplateFileDelegate(QObject *parent);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
QMap<QString, QUrl> files;
};
TemplateFileDelegate::TemplateFileDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
QWidget *TemplateFileDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option)
Q_UNUSED(index);
qDebug()<<Q_FUNC_INFO;
return new QComboBox(parent);
}
void TemplateFileDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QComboBox *cb = qobject_cast<QComboBox*>(editor);
qDebug()<<Q_FUNC_INFO<<cb;
if (!cb) {
return;
}
cb->setEditable(true);
cb->addItems(files.keys());
QString file = index.data().toString();
cb->setCurrentText(file);
}
void TemplateFileDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QComboBox *cb = qobject_cast<QComboBox*>(editor);
qDebug()<<Q_FUNC_INFO<<cb;
if (cb) {
QString cfile = index.data().toString();
QString nfile = cb->currentText();
qDebug()<<"template file:"<<nfile<<files;
if (cfile != nfile) {
model->setData(index, nfile);
if (files.contains(nfile)) {
nfile = files[nfile].url();
}
model->setData(index, nfile, FULLPATHROLE);
}
} else qDebug()<<" No combo box editor!!";
}
class FileItemDelegate : public QStyledItemDelegate
{
public:
FileItemDelegate(QObject *parent);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
QMap<QString, QUrl> files;
};
FileItemDelegate::FileItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
QWidget *FileItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
KUrlRequester *u = new KUrlRequester(parent);
u->setMode(KFile::File);
return u;
}
void FileItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
KUrlRequester *u = qobject_cast<KUrlRequester*>(editor);
QString file = index.data().toString();
if (!file.isEmpty()) {
u->setUrl(QUrl(file));
}
}
void FileItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
KUrlRequester *u = qobject_cast<KUrlRequester*>(editor);
if (u && index.isValid()) {
model->setData(index, u->url().url());
}
}
class FileNameExtensionDelegate : public QStyledItemDelegate
{
public:
FileNameExtensionDelegate(QObject *parent);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
};
FileNameExtensionDelegate::FileNameExtensionDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
QWidget *FileNameExtensionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
QComboBox *cb = new QComboBox(parent);
for (int i = 0; i < ReportsGeneratorView::addOptions().count(); ++i) {
cb->addItem(ReportsGeneratorView::addOptions().at(i), ReportsGeneratorView::addTags().value(i));
}
return cb;
}
void FileNameExtensionDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QComboBox *cb = qobject_cast<QComboBox*>(editor);
if (cb) {
int idx = ReportsGeneratorView::addTags().indexOf(index.data(Qt::UserRole).toString());
cb->setCurrentIndex(idx < 0 ? 0 : idx);
}
}
void FileNameExtensionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QComboBox *cb = qobject_cast<QComboBox*>(editor);
if (cb && index.isValid()) {
model->setData(index, cb->currentData(), Qt::UserRole);
model->setData(index, cb->currentText());
}
}
QStringList ReportsGeneratorView::addOptions()
{
return QStringList() << i18n("Nothing") << i18n("Date") << i18n("Number");
}
QStringList ReportsGeneratorView::addTags()
{
return QStringList() << "Nothing" << "Date" << "Number";
}
ReportsGeneratorView::ReportsGeneratorView(KoPart *part, KoDocument *doc, QWidget *parent)
: ViewBase(part, doc, parent)
{
debugPlan<<"----------------- Create ReportsGeneratorView ----------------------";
QVBoxLayout * l = new QVBoxLayout(this);
l->setMargin(0);
m_view = new QTreeView(this);
QStandardItemModel *m = new QStandardItemModel(m_view);
m->setHorizontalHeaderLabels(QStringList() << i18n("Name") << i18n("Report Template") << i18n("Report File") << i18n("Add"));
m->setHeaderData(0, Qt::Horizontal, xi18nc("@info:tooltip", "Report name"), Qt::ToolTipRole);
m->setHeaderData(1, Qt::Horizontal, xi18nc("@info:tooltip", "Report template file name"), Qt::ToolTipRole);
m->setHeaderData(2, Qt::Horizontal, xi18nc("@info:tooltip", "Name of the generated report file"), Qt::ToolTipRole);
m->setHeaderData(3, Qt::Horizontal, xi18nc("@info:tooltip", "Information added to filename"), Qt::ToolTipRole);
m_view->setModel(m);
m_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_view->setRootIsDecorated(false);
m_view->setAlternatingRowColors(true);
connect(m_view, &QWidget::customContextMenuRequested, this, &ReportsGeneratorView::slotContextMenuRequested);
l->addWidget(m_view);
TemplateFileDelegate *del = new TemplateFileDelegate(m_view);
QString path = QStandardPaths::locate(QStandardPaths::AppDataLocation, "reports", QStandardPaths::LocateDirectory);
qDebug()<<"standardpath:"<<path;
if (!path.isEmpty()) {
QDir dir(path);
qDebug()<<dir.entryList(QDir::Files|QDir::QDir::NoDotAndDotDot);
foreach(const QString &file, dir.entryList(QDir::Files|QDir::QDir::NoDotAndDotDot)) {
QUrl url;
url.setUrl(path + '/' + file);
qDebug()<<"templates:"<<url<<path<<file;
del->files.insert(url.fileName(), url);
}
}
m_view->setItemDelegateForColumn(1, del);
m_view->setItemDelegateForColumn(2, new FileItemDelegate(m_view));
m_view->setItemDelegateForColumn(3, new FileNameExtensionDelegate(m_view));
m_view->header()->setSectionResizeMode(3, QHeaderView::Fixed);
m_view->header()->resizeSection(3, 12);
connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ReportsGeneratorView::slotSelectionChanged);
setupGui();
Help::add(this,
xi18nc("@info:whatsthis",
"<title>Add and generate reports</title>"
"<para>"
"Enables you to add and generate reports based on Open Document (.odf) files."
"</para><para>"
"You can create a report template using any Open Document text editor."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Reports_Generator_View")));
}
void ReportsGeneratorView::setGuiActive(bool activate)
{
debugPlan<<activate;
updateActionsEnabled(true);
ViewBase::setGuiActive(activate);
}
void ReportsGeneratorView::slotCurrentChanged(const QModelIndex &, const QModelIndex &)
{
slotEnableActions();
}
void ReportsGeneratorView::slotSelectionChanged()
{
slotEnableActions();
}
QModelIndexList ReportsGeneratorView::selectedRows() const
{
return m_view->selectionModel()->selectedRows();
}
int ReportsGeneratorView::selectedRowCount() const
{
return selectedRows().count();
}
void ReportsGeneratorView::slotContextMenuRequested(const QPoint& pos)
{
debugPlan;
emit requestPopupMenu("reportsgeneratorview_popup", m_view->mapToGlobal(pos));
}
void ReportsGeneratorView::slotEnableActions()
{
updateActionsEnabled(isReadWrite());
}
void ReportsGeneratorView::updateActionsEnabled(bool on)
{
actionAddReport->setEnabled(on);
actionRemoveReport->setEnabled(on && selectedRowCount() > 0);
actionGenerateReport->setEnabled(on && selectedRowCount() > 0);
}
void ReportsGeneratorView::setupGui()
{
// Umpff, adding a specific list name for this view in calligraplan.rc does not work!
// But reusing an already existing name works, so...
// Not important atm, the whole setup should be refactored anyway.
//QString name = "reportsgeneratorview_list";
QString name = "workpackage_list";
KActionCollection *coll = actionCollection();
actionAddReport = new QAction(koIcon("list-add"), i18n("Add Report"), this);
coll->addAction("add_report", actionAddReport);
coll->setDefaultShortcut(actionAddReport, Qt::CTRL + Qt::Key_I);
connect(actionAddReport, &QAction::triggered, this, &ReportsGeneratorView::slotAddReport);
addAction(name, actionAddReport);
addContextAction(actionAddReport);
actionRemoveReport = new QAction(koIcon("list-remove"), i18n("Remove Report"), this);
coll->addAction("remove_report", actionRemoveReport);
coll->setDefaultShortcut(actionRemoveReport, Qt::CTRL + Qt::Key_D);
connect(actionRemoveReport, &QAction::triggered, this, &ReportsGeneratorView::slotRemoveReport);
addAction(name, actionRemoveReport);
addContextAction(actionRemoveReport);
actionGenerateReport = new QAction(koIcon("document-export"), i18n("Generate Report"), this);
coll->addAction("generate_report", actionGenerateReport);
coll->setDefaultShortcut(actionGenerateReport, Qt::CTRL + Qt::Key_G);
connect(actionGenerateReport, &QAction::triggered, this, &ReportsGeneratorView::slotGenerateReport);
addAction(name, actionGenerateReport);
addContextAction(actionGenerateReport);
// createOptionAction();
}
void ReportsGeneratorView::slotOptions()
{
debugPlan;
// SplitItemViewSettupDialog *dlg = new SplitItemViewSettupDialog(this, m_view, this);
// dlg->addPrintingOptions();
// connect(dlg, SIGNAL(finished(int)), SLOT(slotOptionsFinished(int)));
// dlg->show();
// dlg->raise();
// dlg->activateWindow();
}
void ReportsGeneratorView::slotAddReport()
{
debugPlan;
QAbstractItemModel *m = m_view->model();
int row = m->rowCount();
m->insertRow(row);
QModelIndex idx = m->index(row, 0);
m->setData(idx, i18n("New report"));
QModelIndex add = m->index(row, 3);
m->setData(add, ReportsGeneratorView::addOptions().at(0));
m->setData(add, ReportsGeneratorView::addTags().at(0), Qt::UserRole);
m_view->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
m_view->edit(idx);
emit optionsModified();
}
void ReportsGeneratorView::slotRemoveReport()
{
debugPlan<<selectedRows();
QAbstractItemModel *m = m_view->model();
QModelIndexList lst = selectedRows();
if (lst.isEmpty()) {
return;
}
// Assumption: model is flat
// We must do this in descending row order
QMap<int, QModelIndex> map;
for (int i = 0; i < lst.count(); ++i) {
map.insert(-lst.at(i).row(), lst.at(i)); // sort descending
}
for (const QModelIndex &idx : map) {
Q_ASSERT(!idx.parent().isValid()); // must be flat
m->removeRow(idx.row(), idx.parent());
}
emit optionsModified();
}
void ReportsGeneratorView::slotGenerateReport()
{
debugPlan;
QAbstractItemModel *model = m_view->model();
foreach(const QModelIndex &idx, selectedRows()) {
QString name = model->index(idx.row(), 0).data().toString();
QString tmp = model->index(idx.row(), 1).data(FULLPATHROLE).toString();
QString file = model->index(idx.row(), 2).data().toString();
if (tmp.isEmpty()) {
QMessageBox::information(this, xi18nc("@title:window", "Generate Report"),
i18n("Failed to generate %1."
"\nTemplate file name is empty.", name));
continue;
}
if (file.isEmpty()) {
debugPlan<<"No files for report:"<<name<<tmp<<file;
QMessageBox::information(this, xi18nc("@title:window", "Generate Report"),
i18n("Failed to generate %1."
"\nReport file name is empty.", name));
continue;
}
QString addition = model->index(idx.row(), 3).data(Qt::UserRole).toString();
if (addition == "Date") {
int dotpos = file.lastIndexOf('.');
QString date = QDate::currentDate().toString();
file = file.insert(dotpos, date.prepend('-'));
} else if (addition == "Number") {
int dotpos = file.lastIndexOf('.');
QString fn = file;
for (int i = 1; QFile::exists(fn); ++i) {
fn = file.insert(dotpos, QString::number(i).prepend('-'));
}
file = fn;
}
// warn if file exists
if (QFile::exists(QUrl(file).path())) {
if (QMessageBox::question(this, i18n("Report Generation"), i18n("File exists. Continue?")) == QMessageBox::No) {
return;
}
}
generateReport(tmp, file);
}
}
bool ReportsGeneratorView::generateReport(const QString &templateFile, const QString &file)
{
ReportGenerator rg;
rg.setReportType("odt"); // TODO: handle different report types
rg.setTemplateFile(templateFile);
rg.setReportFile(file);
rg.setProject(project());
rg.setScheduleManager(scheduleManager());
if (!rg.open()) {
debugPlan<<"Failed to open report generator";
QMessageBox::warning(this, i18n("Failed to open report generator"), rg.lastError());
return false;
}
if (!rg.createReport()) {
QMessageBox::warning(this, i18n("Failed to create report"), rg.lastError());
return false;
}
QMessageBox::information(this, i18n("Report Generation"), i18n("Report file generated: %1", file));
return true;
}
bool ReportsGeneratorView::loadContext(const KoXmlElement &context)
{
debugPlan;
m_view->header()->setStretchLastSection((bool)(context.attribute("stretch-last-column", "1").toInt()));
KoXmlElement e = context.namedItem("sections").toElement();
if (!e.isNull()) {
QHeaderView *h = m_view->header();
QString s("section-%1");
for (int i = 0; i < h->count(); ++i) {
if (e.hasAttribute(s.arg(i))) {
int index = e.attribute(s.arg(i), "-1").toInt();
if (index >= 0 && index < h->count()) {
h->moveSection(h->visualIndex(index), i);
}
}
}
}
KoXmlElement parent = context.namedItem("data").toElement();
if (!parent.isNull()) {
debugPlan<<"Load data";
int row = 0;
QAbstractItemModel *model = m_view->model();
forEachElement(e, parent) {
if (e.tagName() != "row") {
continue;
}
model->insertRow(row);
QString name = e.attribute("name");
QString tmp = e.attribute("template");
QString file = e.attribute("file");
QString add = e.attribute("add");
QModelIndex idx = model->index(row, 0);
model->setData(idx, name);
idx = model->index(row, 1);
model->setData(idx, tmp, FULLPATHROLE);
model->setData(idx, QUrl(tmp).fileName());
idx = model->index(row, 2);
model->setData(idx, file);
idx = model->index(row, 3);
model->setData(idx, add, Qt::UserRole);
model->setData(idx, ReportsGeneratorView::addOptions().value(ReportsGeneratorView::addTags().indexOf(add)));
++row;
}
}
ViewBase::loadContext(context);
for (int c = 0; c < m_view->header()->count(); ++c) {
m_view->resizeColumnToContents(c);
}
return true;
}
void ReportsGeneratorView::saveContext(QDomElement &context) const
{
debugPlan;
context.setAttribute( "stretch-last-column", QString::number(m_view->header()->stretchLastSection()) );
QDomElement e = context.ownerDocument().createElement("sections");
context.appendChild(e);
QHeaderView *h = m_view->header();
for (int i = 0; i < h->count(); ++i) {
e.setAttribute(QString("section-%1").arg(i), h->logicalIndex(i));
}
QDomElement data = context.ownerDocument().createElement("data");
context.appendChild(data);
const QAbstractItemModel *model = m_view->model();
for (int row = 0; row < model->rowCount(); ++row) {
e = data.ownerDocument().createElement("row");
data.appendChild(e);
QModelIndex idx = model->index(row, 0);
e.setAttribute("name", idx.data().toString());
idx = model->index(row, 1);
e.setAttribute("template", idx.data(FULLPATHROLE).toString());
idx = model->index(row, 2);
e.setAttribute("file", idx.data().toString());
idx = model->index(row, 3);
e.setAttribute("add", idx.data(Qt::UserRole).toString());
}
ViewBase::saveContext(context);
}
} // namespace KPlato
diff --git a/src/libs/ui/welcome/WelcomeView.cpp b/src/libs/ui/welcome/WelcomeView.cpp
index 38fbd330..cf384a1b 100644
--- a/src/libs/ui/welcome/WelcomeView.cpp
+++ b/src/libs/ui/welcome/WelcomeView.cpp
@@ -1,321 +1,322 @@
/* This file is part of the KDE project
Copyright (C) 2017 Dag Andersen <danders@get2net.dk>
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 <KoApplication.h>
#include <KoMainWindow.h>
#include <KoDocument.h>
#include <KoFileDialog.h>
#include <QStringList>
#include <QStringListModel>
#include <QItemSelectionModel>
#include <QItemSelection>
#include <QUrl>
#include <QIcon>
#include <QStandardPaths>
#include <QEvent>
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",
"<title>Create a new project</title>"
"<para>"
"Creates a new project with default values defined in"
" <emphasis>Settings</emphasis>."
"<nl/>Opens the <emphasis>project dialog</emphasis>"
" so you can define project specific properties like"
" <resource>Project Name</resource>,"
" <resource>Target Start</resource>"
" and <resource>- End</resource> times."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Creating_a_Project")));
Help::add(widget.createResourceFileBtn,
xi18nc("@info:whatsthis",
"<title>Shared resources</title>"
"<para>"
"Create a shared resources file."
"<nl/>This enables you to only create your resources once,"
" you just refer to your resources file when you create a new project."
"<nl/>These resources can then be shared between projects"
" to avoid overbooking resources across projects."
"<nl/>Shared resources must be defined in a separate file."
"<nl/><link url='%1'>More...</link>"
"</para>", Help::page("Manual/Managing_Resources")));
Help::add(widget.recentProjects,
xi18nc("@info:whatsthis",
"<title>Recent Projects</title>"
"<para>"
"A list of the 10 most recent project files opened."
"</para><para>"
"<nl/>This enables you to quickly open projects you have worked on recently."
"</para>"));
Help::add(widget.introductionBtn,
xi18nc("@info:whatsthis",
"<title>Introduction to <application>Plan</application></title>"
"<para>"
"These introductory pages gives you hints and tips on what"
" you can use <application>Plan</application> for, and how to use it."
"</para>"));
Help::add(widget.contextHelp,
xi18nc("@info:whatsthis",
"<title>Context help</title>"
"<para>"
"Help is available many places using <emphasis>What's This</emphasis>."
"<nl/>It is activated using the menu entry <interface>Help->What's this?</interface>"
" or the keyboard shortcut <shortcut>Shift+F1</shortcut>."
"</para><para>"
"In dialogs it is available via the <interface>?</interface> in the dialog title bar."
"</para><para>"
"If you see <link url='%1'>More...</link> in the text,"
" pressing it will display more information from online resources in your browser."
"</para>", Help::page("Manual/Context_Help")));
m_model = new RecentFilesModel(this);
widget.recentProjects->setModel(m_model);
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->selectionModel(), &QItemSelectionModel::selectionChanged, 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);
widget.recentProjects->resizeColumnToContents(0);
}
void WelcomeView::updateReadWrite(bool /*readwrite */)
{
}
void WelcomeView::setGuiActive(bool activate)
{
debugPlan<<activate;
}
void WelcomeView::slotRecentFileSelected(const QItemSelection &selected)
{
QModelIndex idx = selected.indexes().value(0);
if (idx.isValid()) {
QString file = idx.data().toString();
int start = file.indexOf('[');
int end = file.indexOf(']');
file = file.left(end).mid(start+1);
QUrl url = QUrl::fromUserInput(file);
debugWelcome<<file<<url;
if (url.isValid()) {
emit recentProject(url);
emit finished();
}
}
}
void WelcomeView::slotContextMenuRequested(const QModelIndex &/*index*/, const QPoint& /*pos */)
{
//debugPlan<<index.row()<<","<<index.column()<<":"<<pos;
}
void WelcomeView::slotEnableActions(bool on)
{
updateActionsEnabled(on);
}
void WelcomeView::updateActionsEnabled(bool /*on */)
{
}
void WelcomeView::setupGui()
{
// Add the context menu actions for the view options
}
KoPrintJob *WelcomeView::createPrintJob()
{
return 0;
}
void WelcomeView::slotNewProject()
{
if (m_filedialog) {
return;
}
Project *p = project();
if (p) {
if (!m_projectdialog) {
m_projectdialog = new MainProjectDialog(*p, this, false /*edit*/);
connect(m_projectdialog.data(), &MainProjectDialog::dialogFinished, this, &WelcomeView::slotProjectEditFinished);
connect(m_projectdialog.data(), &MainProjectDialog::sigLoadSharedResources, this, &WelcomeView::slotLoadSharedResources);
}
m_projectdialog->show();
m_projectdialog->raise();
m_projectdialog->activateWindow();
}
}
void WelcomeView::slotProjectEditFinished(int result)
{
qDebug()<<Q_FUNC_INFO;
MainProjectDialog *dia = qobject_cast<MainProjectDialog*>(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()<<Q_FUNC_INFO<<"Project dialog is open";
return;
}
Project *p = project();
if (p) {
KoFileDialog filedialog(this, KoFileDialog::OpenFile, "OpenDocument");
filedialog.setCaption(i18n("Open Document"));
filedialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
filedialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import));
filedialog.setHideNameFilterDetailsOption();
QUrl url = QUrl::fromUserInput(filedialog.filename());
if (!url.isEmpty() && mainWindow()->openDocument(url)) {
emit finished();
}
}
}
void WelcomeView::slotOpenFileFinished(int result)
{
KoFileDialog *dia = qobject_cast<KoFileDialog*>(sender());
if (dia == 0) {
return;
}
if (result == QDialog::Accepted) {
QUrl url = QUrl::fromUserInput(dia->filename());
if (!url.isEmpty() && mainWindow()->openDocument(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/widgets/KoDialog.cpp b/src/libs/widgets/KoDialog.cpp
index 2beba5bf..93c1881c 100644
--- a/src/libs/widgets/KoDialog.cpp
+++ b/src/libs/widgets/KoDialog.cpp
@@ -1,1054 +1,1055 @@
/* This file is part of the KDE Libraries
* Copyright (C) 1998 Thomas Tanghus (tanghus@earthling.net)
* Additions 1999-2000 by Espen Sand (espen@kde.org)
* by Holger Freyther <freyther@kde.org>
* 2005-2009 by Olivier Goffart (ogoffart at kde.org)
*
* 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 "KoDialog.h"
#include "KoDialog_p.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QHideEvent>
#include <QPointer>
#include <QStyle>
#include <QTimer>
#include <QVBoxLayout>
#include <QWhatsThis>
#include <QDebug>
#include <QPushButton>
#include <kconfig.h>
#include <klocalizedstring.h>
#include <kseparator.h>
#include <kstandardguiitem.h>
#include <khelpclient.h>
#include <kurllabel.h>
#include <kwindowconfig.h>
void KoDialogPrivate::setupLayout()
{
Q_Q(KoDialog);
if (!dirty) {
QMetaObject::invokeMethod(q, "queuedLayoutUpdate", Qt::QueuedConnection);
dirty = true;
}
}
void KoDialogPrivate::queuedLayoutUpdate()
{
if (!dirty) {
return;
}
dirty = false;
Q_Q(KoDialog);
// Don't lose the focus widget when re-creating the layout.
// Testcase: KOrganizer's "Select Categories" dialog
QPointer<QWidget> focusWidget = mMainWidget ? mMainWidget->focusWidget() : 0;
if (q->layout() && q->layout() != mTopLayout) {
qWarning() << q->metaObject()->className() << "created with a layout; don't do that, KoDialog takes care of it, use mainWidget or setMainWidget instead";
delete q->layout();
}
delete mTopLayout;
if (mButtonOrientation == Qt::Horizontal) {
mTopLayout = new QVBoxLayout(q);
} else {
mTopLayout = new QHBoxLayout(q);
}
if (mUrlHelp) {
mTopLayout->addWidget(mUrlHelp, 0, Qt::AlignRight);
}
if (mMainWidget) {
mTopLayout->addWidget(mMainWidget, 10);
}
if (mDetailsWidget) {
mTopLayout->addWidget(mDetailsWidget);
}
if (mActionSeparator) {
mTopLayout->addWidget(mActionSeparator);
}
if (mButtonBox) {
mButtonBox->setOrientation(mButtonOrientation);
mTopLayout->addWidget(mButtonBox);
}
if (focusWidget) {
focusWidget->setFocus();
}
}
void KoDialogPrivate::appendButton(KoDialog::ButtonCode key, const KGuiItem &item)
{
Q_Q(KoDialog);
QDialogButtonBox::ButtonRole role = QDialogButtonBox::InvalidRole;
switch (key) {
case KoDialog::Help:
case KoDialog::Details:
role = QDialogButtonBox::HelpRole;
break;
case KoDialog::Default:
case KoDialog::Reset:
role = QDialogButtonBox::ResetRole;
break;
case KoDialog::Ok:
role = QDialogButtonBox::AcceptRole;
break;
case KoDialog::Apply:
role = QDialogButtonBox::ApplyRole;
break;
case KoDialog::Try:
case KoDialog::Yes:
role = QDialogButtonBox::YesRole;
break;
case KoDialog::Close:
case KoDialog::Cancel:
role = QDialogButtonBox::RejectRole;
break;
case KoDialog::No:
role = QDialogButtonBox::NoRole;
break;
case KoDialog::User1:
case KoDialog::User2:
case KoDialog::User3:
role = QDialogButtonBox::ActionRole;
break;
default:
role = QDialogButtonBox::InvalidRole;
break;
}
if (role == QDialogButtonBox::InvalidRole) {
return;
}
QPushButton *button = new QPushButton;
KGuiItem::assign(button, item);
mButtonBox->addButton(button, role);
mButtonList.insert(key, button);
mButtonSignalMapper.setMapping(button, key);
QObject::connect(button, SIGNAL(clicked()),
&mButtonSignalMapper, SLOT(map()));
if (key == mDefaultButton) {
// Now that it exists, set it as default
q->setDefaultButton(mDefaultButton);
}
}
void KoDialogPrivate::init(KoDialog *q)
{
q_ptr = q;
dirty = false;
q->setButtons(KoDialog::Ok | KoDialog::Cancel);
q->setDefaultButton(KoDialog::Ok);
q->connect(&mButtonSignalMapper, SIGNAL(mapped(int)), q, SLOT(slotButtonClicked(int)));
q->setPlainCaption(qApp->applicationDisplayName()); // set appropriate initial window title for case it gets not set later
}
void KoDialogPrivate::helpLinkClicked()
{
q_ptr->slotButtonClicked(KoDialog::Help);
}
KoDialog::KoDialog(QWidget *parent, Qt::WindowFlags flags)
: QDialog(parent, flags)
, d_ptr(new KoDialogPrivate)
{
d_ptr->init(this);
}
KoDialog::KoDialog(KoDialogPrivate &dd, QWidget *parent, Qt::WindowFlags flags)
: QDialog(parent, flags)
, d_ptr(&dd)
{
d_ptr->init(this);
}
KoDialog::~KoDialog()
{
delete d_ptr;
}
void KoDialog::setButtons(ButtonCodes buttonMask)
{
Q_D(KoDialog);
if (d->mButtonBox) {
d->mButtonList.clear();
delete d->mButtonBox;
d->mButtonBox = 0;
}
if (buttonMask & Cancel) {
buttonMask &= ~Close;
}
if (buttonMask & Apply) {
buttonMask &= ~Try;
}
if (buttonMask & Details) {
buttonMask &= ~Default;
}
if (buttonMask == None) {
d->setupLayout();
return; // When we want no button box
}
d->mEscapeButton = (buttonMask & Cancel) ? Cancel : Close;
d->mButtonBox = new QDialogButtonBox(this);
if (buttonMask & Help) {
d->appendButton(Help, KStandardGuiItem::help());
}
if (buttonMask & Default) {
d->appendButton(Default, KStandardGuiItem::defaults());
}
if (buttonMask & Reset) {
d->appendButton(Reset, KStandardGuiItem::reset());
}
if (buttonMask & User3) {
d->appendButton(User3, KGuiItem());
}
if (buttonMask & User2) {
d->appendButton(User2, KGuiItem());
}
if (buttonMask & User1) {
d->appendButton(User1, KGuiItem());
}
if (buttonMask & Ok) {
d->appendButton(Ok, KStandardGuiItem::ok());
}
if (buttonMask & Apply) {
d->appendButton(Apply, KStandardGuiItem::apply());
}
if (buttonMask & Try) {
d->appendButton(Try, KGuiItem(i18n("&Try")));
}
if (buttonMask & Cancel) {
d->appendButton(Cancel, KStandardGuiItem::cancel());
}
if (buttonMask & Close) {
d->appendButton(Close, KStandardGuiItem::close());
}
if (buttonMask & Yes) {
d->appendButton(Yes, KStandardGuiItem::yes());
}
if (buttonMask & No) {
d->appendButton(No, KStandardGuiItem::no());
}
if (buttonMask & Details) {
d->appendButton(Details, KGuiItem(QString(), "help-about"));
setDetailsWidgetVisible(false);
}
d->setupLayout();
}
void KoDialog::setButtonsOrientation(Qt::Orientation orientation)
{
Q_D(KoDialog);
if (d->mButtonOrientation != orientation) {
d->mButtonOrientation = orientation;
if (d->mActionSeparator) {
d->mActionSeparator->setOrientation(d->mButtonOrientation);
}
if (d->mButtonOrientation == Qt::Vertical) {
enableLinkedHelp(false); // 2000-06-18 Espen: No support for this yet.
}
}
}
void KoDialog::setEscapeButton(ButtonCode id)
{
d_func()->mEscapeButton = id;
}
void KoDialog::setDefaultButton(ButtonCode newDefaultButton)
{
Q_D(KoDialog);
if (newDefaultButton == None) {
newDefaultButton = NoDefault; // #148969
}
const KoDialog::ButtonCode oldDefault = defaultButton();
bool oldDefaultHadFocus = false;
if (oldDefault != NoDefault) {
QPushButton *old = button(oldDefault);
if (old) {
oldDefaultHadFocus = (focusWidget() == old);
old->setDefault(false);
}
}
if (newDefaultButton != NoDefault) {
QPushButton *b = button(newDefaultButton);
if (b) {
b->setDefault(true);
if (focusWidget() == 0 || oldDefaultHadFocus) {
// No widget had focus yet, or the old default button had
// -> ok to give focus to the new default button, so that it's
// really default (Enter triggers it).
// But we don't do this if the kdialog user gave focus to a
// specific widget in the dialog.
b->setFocus();
}
}
}
d->mDefaultButton = newDefaultButton;
Q_ASSERT(defaultButton() == newDefaultButton);
}
KoDialog::ButtonCode KoDialog::defaultButton() const
{
Q_D(const KoDialog);
QHashIterator<int, QPushButton *> it(d->mButtonList);
while (it.hasNext()) {
it.next();
if (it.value()->isDefault()) {
return (ButtonCode)it.key();
}
}
return d->mDefaultButton;
}
void KoDialog::setMainWidget(QWidget *widget)
{
Q_D(KoDialog);
if (d->mMainWidget == widget) {
return;
}
d->mMainWidget = widget;
if (d->mMainWidget && d->mMainWidget->layout()) {
// Avoid double-margin problem
d->mMainWidget->layout()->setMargin(0);
}
d->setupLayout();
}
QWidget *KoDialog::mainWidget()
{
Q_D(KoDialog);
if (!d->mMainWidget) {
setMainWidget(new QWidget(this));
}
return d->mMainWidget;
}
QSize KoDialog::sizeHint() const
{
Q_D(const KoDialog);
if (!d->mMinSize.isEmpty()) {
return d->mMinSize.expandedTo(minimumSizeHint()) + d->mIncSize;
} else {
if (d->dirty) {
const_cast<KoDialogPrivate *>(d)->queuedLayoutUpdate();
}
return QDialog::sizeHint() + d->mIncSize;
}
}
QSize KoDialog::minimumSizeHint() const
{
Q_D(const KoDialog);
if (d->dirty) {
const_cast<KoDialogPrivate *>(d)->queuedLayoutUpdate();
}
return QDialog::minimumSizeHint() + d->mIncSize;
}
//
// Grab QDialogs keypresses if non-modal.
//
void KoDialog::keyPressEvent(QKeyEvent *event)
{
Q_D(KoDialog);
if (event->modifiers() == 0) {
if (event->key() == Qt::Key_F1) {
QPushButton *button = this->button(Help);
if (button) {
button->animateClick();
event->accept();
return;
}
}
if (event->key() == Qt::Key_Escape) {
QPushButton *button = this->button(d->mEscapeButton);
if (button) {
button->animateClick();
event->accept();
return;
}
}
} else if (event->key() == Qt::Key_F1 && event->modifiers() == Qt::ShiftModifier) {
QWhatsThis::enterWhatsThisMode();
event->accept();
return;
} else if (event->modifiers() == Qt::ControlModifier &&
(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)) {
// accept the dialog when Ctrl-Return is pressed
QPushButton *button = this->button(Ok);
if (button) {
button->animateClick();
event->accept();
return;
}
}
QDialog::keyPressEvent(event);
}
int KoDialog::marginHint()
{
return QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin);
}
int KoDialog::spacingHint()
{
return QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
}
int KoDialog::groupSpacingHint()
{
return QApplication::fontMetrics().lineSpacing();
}
QString KoDialog::makeStandardCaption(const QString &userCaption,
QWidget *window,
CaptionFlags flags)
{
Q_UNUSED(window);
QString caption = qApp->applicationDisplayName();
QString captionString = userCaption.isEmpty() ? caption : userCaption;
// If the document is modified, add '[modified]'.
if (flags & ModifiedCaption) {
captionString += QString::fromUtf8(" [") + i18n("modified") + QString::fromUtf8("]");
}
if (!userCaption.isEmpty()) {
// Add the application name if:
// User asked for it, it's not a duplication and the app name (caption()) is not empty
if (flags & AppNameCaption &&
!caption.isEmpty() &&
!userCaption.endsWith(caption)) {
// TODO: check to see if this is a transient/secondary window before trying to add the app name
// on platforms that need this
captionString += i18nc("Document/application separator in titlebar", " – ") + caption;
}
}
return captionString;
}
void KoDialog::setCaption(const QString &_caption)
{
const QString caption = makeStandardCaption(_caption, this);
setPlainCaption(caption);
}
void KoDialog::setCaption(const QString &caption, bool modified)
{
CaptionFlags flags = HIGCompliantCaption;
// ### Qt5 TODO: port to [*], see QWidget::setWindowFilePath
if (modified) {
flags |= ModifiedCaption;
}
setPlainCaption(makeStandardCaption(caption, this, flags));
}
void KoDialog::setPlainCaption(const QString &caption)
{
if (QWidget *win = window()) {
win->setWindowTitle(caption);
}
}
void KoDialog::resizeLayout(QWidget *widget, int margin, int spacing) //static
{
if (widget->layout()) {
resizeLayout(widget->layout(), margin, spacing);
}
if (widget->children().count() > 0) {
const QList<QObject *> list = widget->children();
foreach (QObject *object, list) {
if (object->isWidgetType()) {
resizeLayout((QWidget *)object, margin, spacing);
}
}
}
}
void KoDialog::resizeLayout(QLayout *layout, int margin, int spacing) //static
{
QLayoutItem *child;
int pos = 0;
while ((child = layout->itemAt(pos))) {
if (child->layout()) {
resizeLayout(child->layout(), margin, spacing);
}
++pos;
}
if (layout->layout()) {
layout->layout()->setMargin(margin);
layout->layout()->setSpacing(spacing);
}
}
static QRect screenRect(QWidget *widget, int screen)
{
QDesktopWidget *desktop = QApplication::desktop();
KConfig gc("kdeglobals", KConfig::NoGlobals);
KConfigGroup cg(&gc, "Windows");
if (desktop->isVirtualDesktop() &&
cg.readEntry("XineramaEnabled", true) &&
cg.readEntry("XineramaPlacementEnabled", true)) {
if (screen < 0 || screen >= desktop->numScreens()) {
if (screen == -1) {
screen = desktop->primaryScreen();
} else if (screen == -3) {
screen = desktop->screenNumber(QCursor::pos());
} else {
screen = desktop->screenNumber(widget);
}
}
return desktop->availableGeometry(screen);
} else {
return desktop->geometry();
}
}
void KoDialog::centerOnScreen(QWidget *widget, int screen)
{
if (!widget) {
return;
}
QRect rect = screenRect(widget, screen);
widget->move(rect.center().x() - widget->width() / 2,
rect.center().y() - widget->height() / 2);
}
bool KoDialog::avoidArea(QWidget *widget, const QRect &area, int screen)
{
if (!widget) {
return false;
}
QRect fg = widget->frameGeometry();
if (!fg.intersects(area)) {
return true; // nothing to do.
}
const QRect scr = screenRect(widget, screen);
QRect avoid(area); // let's add some margin
avoid.translate(-5, -5);
avoid.setRight(avoid.right() + 10);
avoid.setBottom(avoid.bottom() + 10);
if (qMax(fg.top(), avoid.top()) <= qMin(fg.bottom(), avoid.bottom())) {
// We need to move the widget up or down
int spaceAbove = qMax(0, avoid.top() - scr.top());
int spaceBelow = qMax(0, scr.bottom() - avoid.bottom());
if (spaceAbove > spaceBelow) // where's the biggest side?
if (fg.height() <= spaceAbove) { // big enough?
fg.setY(avoid.top() - fg.height());
} else {
return false;
}
else if (fg.height() <= spaceBelow) { // big enough?
fg.setY(avoid.bottom());
} else {
return false;
}
}
if (qMax(fg.left(), avoid.left()) <= qMin(fg.right(), avoid.right())) {
// We need to move the widget left or right
const int spaceLeft = qMax(0, avoid.left() - scr.left());
const int spaceRight = qMax(0, scr.right() - avoid.right());
if (spaceLeft > spaceRight) // where's the biggest side?
if (fg.width() <= spaceLeft) { // big enough?
fg.setX(avoid.left() - fg.width());
} else {
return false;
}
else if (fg.width() <= spaceRight) { // big enough?
fg.setX(avoid.right());
} else {
return false;
}
}
widget->move(fg.x(), fg.y());
return true;
}
void KoDialog::showButtonSeparator(bool state)
{
Q_D(KoDialog);
if ((d->mActionSeparator != 0) == state) {
return;
}
if (state) {
if (d->mActionSeparator) {
return;
}
d->mActionSeparator = new KSeparator(this);
d->mActionSeparator->setOrientation(d->mButtonOrientation);
} else {
delete d->mActionSeparator;
d->mActionSeparator = 0;
}
d->setupLayout();
}
void KoDialog::setInitialSize(const QSize &size)
{
d_func()->mMinSize = size;
adjustSize();
}
void KoDialog::incrementInitialSize(const QSize &size)
{
d_func()->mIncSize = size;
adjustSize();
}
QPushButton *KoDialog::button(ButtonCode id) const
{
Q_D(const KoDialog);
return d->mButtonList.value(id, 0);
}
void KoDialog::enableButton(ButtonCode id, bool state)
{
QPushButton *button = this->button(id);
if (button) {
button->setEnabled(state);
}
}
bool KoDialog::isButtonEnabled(ButtonCode id) const
{
QPushButton *button = this->button(id);
if (button) {
return button->isEnabled();
}
return false;
}
void KoDialog::enableButtonOk(bool state)
{
enableButton(Ok, state);
}
void KoDialog::enableButtonApply(bool state)
{
enableButton(Apply, state);
}
void KoDialog::enableButtonCancel(bool state)
{
enableButton(Cancel, state);
}
void KoDialog::showButton(ButtonCode id, bool state)
{
QPushButton *button = this->button(id);
if (button) {
state ? button->show() : button->hide();
}
}
void KoDialog::setButtonGuiItem(ButtonCode id, const KGuiItem &item)
{
QPushButton *button = this->button(id);
if (!button) {
return;
}
KGuiItem::assign(button, item);
}
void KoDialog::setButtonText(ButtonCode id, const QString &text)
{
Q_D(KoDialog);
if (!d->mSettingDetails && (id == Details)) {
d->mDetailsButtonText = text;
setDetailsWidgetVisible(d->mDetailsVisible);
return;
}
QPushButton *button = this->button(id);
if (button) {
button->setText(text);
}
}
QString KoDialog::buttonText(ButtonCode id) const
{
QPushButton *button = this->button(id);
if (button) {
return button->text();
} else {
return QString();
}
}
void KoDialog::setButtonIcon(ButtonCode id, const QIcon &icon)
{
QPushButton *button = this->button(id);
if (button) {
button->setIcon(icon);
}
}
QIcon KoDialog::buttonIcon(ButtonCode id) const
{
QPushButton *button = this->button(id);
if (button) {
return button->icon();
} else {
return QIcon();
}
}
void KoDialog::setButtonToolTip(ButtonCode id, const QString &text)
{
QPushButton *button = this->button(id);
if (button) {
if (text.isEmpty()) {
button->setToolTip(QString());
} else {
button->setToolTip(text);
}
}
}
QString KoDialog::buttonToolTip(ButtonCode id) const
{
QPushButton *button = this->button(id);
if (button) {
return button->toolTip();
} else {
return QString();
}
}
void KoDialog::setButtonWhatsThis(ButtonCode id, const QString &text)
{
QPushButton *button = this->button(id);
if (button) {
if (text.isEmpty()) {
button->setWhatsThis(QString());
} else {
button->setWhatsThis(text);
}
}
}
QString KoDialog::buttonWhatsThis(ButtonCode id) const
{
QPushButton *button = this->button(id);
if (button) {
return button->whatsThis();
} else {
return QString();
}
}
void KoDialog::setButtonFocus(ButtonCode id)
{
QPushButton *button = this->button(id);
if (button) {
button->setFocus();
}
}
void KoDialog::setDetailsWidget(QWidget *detailsWidget)
{
Q_D(KoDialog);
if (d->mDetailsWidget == detailsWidget) {
return;
}
delete d->mDetailsWidget;
d->mDetailsWidget = detailsWidget;
if (d->mDetailsWidget->parentWidget() != this) {
d->mDetailsWidget->setParent(this);
}
d->mDetailsWidget->hide();
d->setupLayout();
if (!d->mSettingDetails) {
setDetailsWidgetVisible(d->mDetailsVisible);
}
}
bool KoDialog::isDetailsWidgetVisible() const
{
return d_func()->mDetailsVisible;
}
void KoDialog::setDetailsWidgetVisible(bool visible)
{
Q_D(KoDialog);
if (d->mDetailsButtonText.isEmpty()) {
d->mDetailsButtonText = i18n("&Details");
}
d->mSettingDetails = true;
d->mDetailsVisible = visible;
if (d->mDetailsVisible) {
emit aboutToShowDetails();
setButtonText(Details, d->mDetailsButtonText + " <<");
if (d->mDetailsWidget) {
if (layout()) {
layout()->setEnabled(false);
}
d->mDetailsWidget->show();
adjustSize();
if (layout()) {
layout()->activate();
layout()->setEnabled(true);
}
}
} else {
setButtonText(Details, d->mDetailsButtonText + " >>");
if (d->mDetailsWidget) {
d->mDetailsWidget->hide();
}
if (layout()) {
layout()->activate();
adjustSize();
}
}
d->mSettingDetails = false;
}
void KoDialog::delayedDestruct()
{
if (isVisible()) {
hide();
}
deleteLater();
}
void KoDialog::slotButtonClicked(int button)
{
Q_D(KoDialog);
emit buttonClicked(static_cast<KoDialog::ButtonCode>(button));
switch (button) {
case Ok:
emit okClicked();
accept();
break;
case Apply:
emit applyClicked();
break;
case Try:
emit tryClicked();
break;
case User3:
emit user3Clicked();
break;
case User2:
emit user2Clicked();
break;
case User1:
emit user1Clicked();
break;
case Yes:
emit yesClicked();
done(Yes);
break;
case No:
emit noClicked();
done(No);
break;
case Cancel:
emit cancelClicked();
reject();
break;
case Close:
emit closeClicked();
done(Close); // KDE5: call reject() instead; more QDialog-like.
break;
case Help:
emit helpClicked();
if (!d->mAnchor.isEmpty() || !d->mHelpApp.isEmpty()) {
KHelpClient::invokeHelp(d->mAnchor, d->mHelpApp);
}
break;
case Default:
emit defaultClicked();
break;
case Reset:
emit resetClicked();
break;
case Details:
setDetailsWidgetVisible(!d->mDetailsVisible);
break;
}
// If we're here from the closeEvent, and auto-delete is on, well, auto-delete now.
if (d->mDeferredDelete) {
d->mDeferredDelete = false;
delayedDestruct();
}
}
void KoDialog::enableLinkedHelp(bool state)
{
Q_D(KoDialog);
if ((d->mUrlHelp != 0) == state) {
return;
}
if (state) {
if (d->mUrlHelp) {
return;
}
d->mUrlHelp = new KUrlLabel(this);
d->mUrlHelp->setText(helpLinkText());
d->mUrlHelp->setFloatEnabled(true);
d->mUrlHelp->setUnderline(true);
d->mUrlHelp->setMinimumHeight(fontMetrics().height() + marginHint());
connect(d->mUrlHelp, SIGNAL(leftClickedUrl()), SLOT(helpLinkClicked()));
d->mUrlHelp->show();
} else {
delete d->mUrlHelp;
d->mUrlHelp = 0;
}
d->setupLayout();
}
void KoDialog::setHelp(const QString &anchor, const QString &appname)
{
Q_D(KoDialog);
d->mAnchor = anchor;
d->mHelpApp = appname;
}
void KoDialog::setHelpLinkText(const QString &text)
{
Q_D(KoDialog);
d->mHelpLinkText = text;
if (d->mUrlHelp) {
d->mUrlHelp->setText(helpLinkText());
}
}
QString KoDialog::helpLinkText() const
{
Q_D(const KoDialog);
return (d->mHelpLinkText.isEmpty() ? i18n("Get help...") : d->mHelpLinkText);
}
void KoDialog::updateGeometry()
{
}
void KoDialog::hideEvent(QHideEvent *event)
{
emit hidden();
if (!event->spontaneous()) {
emit finished();
}
}
void KoDialog::closeEvent(QCloseEvent *event)
{
Q_D(KoDialog);
QPushButton *button = this->button(d->mEscapeButton);
if (button && !isHidden()) {
button->animateClick();
if (testAttribute(Qt::WA_DeleteOnClose)) {
// Don't let QWidget::close do a deferred delete just yet, wait for the click first
d->mDeferredDelete = true;
setAttribute(Qt::WA_DeleteOnClose, false);
}
} else {
QDialog::closeEvent(event);
}
}
#include "moc_KoDialog.cpp"
diff --git a/src/libs/widgets/KoDockWidgetTitleBar.cpp b/src/libs/widgets/KoDockWidgetTitleBar.cpp
index 75e8123b..acea2e6b 100644
--- a/src/libs/widgets/KoDockWidgetTitleBar.cpp
+++ b/src/libs/widgets/KoDockWidgetTitleBar.cpp
@@ -1,381 +1,382 @@
/* This file is part of the KDE project
Copyright (c) 2007 Marijn Kruisselbrink <mkruisselbrink@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
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 "KoDockWidgetTitleBar.h"
#include "KoDockWidgetTitleBar_p.h"
#include "KoDockWidgetTitleBarButton.h"
#include <KoIcon.h>
#include <WidgetsDebug.h>
#include <klocalizedstring.h>
#include <QAbstractButton>
#include <QAction>
#include <QLabel>
#include <QLayout>
#include <QStyle>
#include <QStylePainter>
#include <QStyleOptionFrame>
static inline bool hasFeature(const QDockWidget *dockwidget, QDockWidget::DockWidgetFeature feature)
{
return (dockwidget->features() & feature) == feature;
}
static QIcon openIcon(QDockWidget *q)
{
QIcon icon = q->style()->standardIcon(QStyle::SP_TitleBarShadeButton);
return icon.isNull() ? koIcon("arrow-down") : icon;
}
static QIcon closeIcon(QDockWidget *q)
{
QIcon icon = q->style()->standardIcon(QStyle::SP_TitleBarUnshadeButton);
return icon.isNull() ? koIcon("arrow-right") : icon;
}
KoDockWidgetTitleBar::KoDockWidgetTitleBar(QDockWidget* dockWidget)
: QWidget(dockWidget), d(new Private(this))
{
QDockWidget *q = dockWidget;
d->floatButton = new KoDockWidgetTitleBarButton(this);
d->floatButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarNormalButton, 0, q));
connect(d->floatButton, SIGNAL(clicked()), SLOT(toggleFloating())); // clazy:exclude=old-style-connect
d->floatButton->setVisible(true);
d->floatButton->setToolTip(i18nc("@info:tooltip", "Float Docker"));
d->floatButton->setStyleSheet("border: 0");
d->closeButton = new KoDockWidgetTitleBarButton(this);
d->closeButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarCloseButton, 0, q));
connect(d->closeButton, &QAbstractButton::clicked, q, &QWidget::close);
d->closeButton->setVisible(true);
d->closeButton->setToolTip(i18nc("@info:tooltip", "Close Docker"));
d->closeButton->setStyleSheet("border: 0"); // border makes the header busy looking (appears on some OSs)
d->collapseButton = new KoDockWidgetTitleBarButton(this);
d->collapseButton->setIcon(openIcon(q));
connect(d->collapseButton, SIGNAL(clicked()), SLOT(toggleCollapsed())); // clazy:exclude=old-style-connect
d->collapseButton->setVisible(true);
d->collapsable = true;
d->collapseButton->setToolTip(i18nc("@info:tooltip", "Collapse Docker"));
d->collapseButton->setStyleSheet("border: 0");
d->lockButton = new KoDockWidgetTitleBarButton(this);
d->lockButton->setCheckable(true);
d->lockButton->setIcon(koIcon("object-unlocked"));
connect(d->lockButton, &QAbstractButton::toggled, this, &KoDockWidgetTitleBar::setLocked);
d->lockButton->setVisible(true);
d->lockable = true;
d->lockButton->setToolTip(i18nc("@info:tooltip", "Lock Docker"));
d->lockButton->setStyleSheet("border: 0");
connect(dockWidget, SIGNAL(featuresChanged(QDockWidget::DockWidgetFeatures)), SLOT(featuresChanged(QDockWidget::DockWidgetFeatures))); // clazy:exclude=old-style-connect
connect(dockWidget, SIGNAL(topLevelChanged(bool)), SLOT(topLevelChanged(bool))); // clazy:exclude=old-style-connect
d->featuresChanged(0);
}
KoDockWidgetTitleBar::~KoDockWidgetTitleBar()
{
delete d;
}
QSize KoDockWidgetTitleBar::minimumSizeHint() const
{
return sizeHint();
}
QSize KoDockWidgetTitleBar::sizeHint() const
{
if (isHidden()) {
return QSize(0, 0);
}
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
int mw = q->style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, q);
int fw = q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q);
// get size of buttons...
QSize closeSize(0, 0);
if (d->closeButton && hasFeature(q, QDockWidget::DockWidgetClosable)) {
closeSize = d->closeButton->sizeHint();
}
QSize floatSize(0, 0);
if (d->floatButton && hasFeature(q, QDockWidget::DockWidgetFloatable)) {
floatSize = d->floatButton->sizeHint();
}
QSize hideSize(0, 0);
if (d->collapseButton && d->collapsable) {
hideSize = d->collapseButton->sizeHint();
}
QSize lockSize(0, 0);
if (d->lockButton && d->lockable) {
lockSize = d->lockButton->sizeHint();
}
int buttonHeight = qMax(qMax(qMax(closeSize.height(), floatSize.height()), hideSize.height()), lockSize.height()) + 2;
int buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + lockSize.width();
int height = buttonHeight;
if (d->textVisibilityMode == FullTextAlwaysVisible) {
// get font size
QFontMetrics titleFontMetrics = q->fontMetrics();
int fontHeight = titleFontMetrics.lineSpacing() + 2 * mw;
height = qMax(height, fontHeight);
}
/*
* Calculate the width of title and add to the total width of the docker window when collapsed.
*/
const int titleWidth =
(d->textVisibilityMode == FullTextAlwaysVisible) ? (q->fontMetrics().width(q->windowTitle()) + 2*mw) :
0;
if (d->preCollapsedWidth > 0) {
return QSize(d->preCollapsedWidth, height);
}
else {
if (d->textVisibilityMode == FullTextAlwaysVisible) {
return QSize(buttonWidth /*+ height*/ + 2*mw + 2*fw + titleWidth, height);
}
else {
if (q->widget()) {
return QSize(qMin(q->widget()->sizeHint().width(), buttonWidth), height);
}
else {
return QSize(buttonWidth, height);
}
}
}
}
void KoDockWidgetTitleBar::paintEvent(QPaintEvent*)
{
QStylePainter p(this);
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
int fw = q->isFloating() ? q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q) : 0;
int mw = q->style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, q);
QStyleOptionDockWidget titleOpt;
titleOpt.initFrom(q);
QSize collapseButtonSize(0,0);
if (d->collapsable) {
collapseButtonSize = d->collapseButton->size();
}
QSize lockButtonSize(0,0);
if (d->lockable) {
lockButtonSize = d->lockButton->size();
}
titleOpt.rect = QRect(QPoint(fw + mw + collapseButtonSize.width() + lockButtonSize.width(), 0),
QSize(geometry().width() - (fw * 2) - mw - collapseButtonSize.width() - lockButtonSize.width(), geometry().height()));
titleOpt.title = q->windowTitle();
titleOpt.closable = hasFeature(q, QDockWidget::DockWidgetClosable);
titleOpt.floatable = hasFeature(q, QDockWidget::DockWidgetFloatable);
p.drawControl(QStyle::CE_DockWidgetTitle, titleOpt);
}
void KoDockWidgetTitleBar::resizeEvent(QResizeEvent*)
{
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
int fw = q->isFloating() ? q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q) : 0;
QStyleOptionDockWidget opt;
opt.initFrom(q);
opt.rect = QRect(QPoint(fw, fw), QSize(geometry().width() - (fw * 2), geometry().height() - (fw * 2)));
opt.title = q->windowTitle();
opt.closable = hasFeature(q, QDockWidget::DockWidgetClosable);
opt.floatable = hasFeature(q, QDockWidget::DockWidgetFloatable);
QRect floatRect = q->style()->subElementRect(QStyle::SE_DockWidgetFloatButton, &opt, q);
if (!floatRect.isNull())
d->floatButton->setGeometry(floatRect);
QRect closeRect = q->style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &opt, q);
if (!closeRect.isNull())
d->closeButton->setGeometry(closeRect);
int top = fw;
if (!floatRect.isNull())
top = floatRect.y();
else if (!closeRect.isNull())
top = closeRect.y();
QSize size = d->collapseButton->size();
if (!closeRect.isNull()) {
size = d->closeButton->size();
} else if (!floatRect.isNull()) {
size = d->floatButton->size();
}
QRect collapseRect = QRect(QPoint(fw, top), size);
d->collapseButton->setGeometry(collapseRect);
size = d->lockButton->size();
if (!closeRect.isNull()) {
size = d->closeButton->size();
} else if (!floatRect.isNull()) {
size = d->floatButton->size();
}
int offset = 0;
if (d->collapsable) {
offset = collapseRect.width();
}
QRect lockRect = QRect(QPoint(fw + 2 + offset, top), size);
d->lockButton->setGeometry(lockRect);
if (width() < (closeRect.width() + lockRect.width()) + 50) {
d->collapsable = false;
d->collapseButton->setVisible(false);
d->lockButton->setVisible(false);
d->lockable = false;
} else {
d->collapsable = d->collapsableSet;
d->collapseButton->setVisible(d->collapsableSet);
d->lockButton->setVisible(true);
d->lockable = true;
}
}
void KoDockWidgetTitleBar::setCollapsed(bool collapsed)
{
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
if (q && q->widget() && q->widget()->isHidden() != collapsed)
d->toggleCollapsed();
}
void KoDockWidgetTitleBar::setLocked(bool locked)
{
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
d->locked = locked;
d->lockButton->blockSignals(true);
d->lockButton->setChecked(locked);
d->lockButton->blockSignals(false);
//qDebug() << "setlocked" << q << d->locked << locked;
if (locked) {
d->features = q->features();
q->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
else {
q->setFeatures(d->features);
}
q->toggleViewAction()->setEnabled(!locked);
d->closeButton->setEnabled(!locked);
d->floatButton->setEnabled(!locked);
d->collapseButton->setEnabled(!locked);
d->updateIcons();
q->setProperty("Locked", locked);
resizeEvent(0);
}
void KoDockWidgetTitleBar::setCollapsable(bool collapsable)
{
d->collapsableSet = collapsable;
d->collapsable = collapsable;
d->collapseButton->setVisible(collapsable);
}
void KoDockWidgetTitleBar::setTextVisibilityMode(TextVisibilityMode textVisibilityMode)
{
d->textVisibilityMode = textVisibilityMode;
}
void KoDockWidgetTitleBar::updateIcons()
{
d->updateIcons();
}
void KoDockWidgetTitleBar::Private::toggleFloating()
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
q->setFloating(!q->isFloating());
}
void KoDockWidgetTitleBar::Private::topLevelChanged(bool topLevel)
{
lockButton->setEnabled(!topLevel);
}
void KoDockWidgetTitleBar::Private::toggleCollapsed()
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
if (q == 0) // there does not *have* to be anything on the dockwidget.
return;
preCollapsedWidth = q->widget()->isHidden() ? -1 : thePublic->width();
q->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); // will be overwritten again next
if (q->widget()) {
q->widget()->setVisible(q->widget()->isHidden());
collapseButton->setIcon(q->widget()->isHidden() ? closeIcon(q) : openIcon(q));
}
}
void KoDockWidgetTitleBar::Private::featuresChanged(QDockWidget::DockWidgetFeatures)
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
closeButton->setVisible(hasFeature(q, QDockWidget::DockWidgetClosable));
floatButton->setVisible(hasFeature(q, QDockWidget::DockWidgetFloatable));
thePublic->resizeEvent(0);
}
// QT5TODO: this is not yet triggered by theme changes it seems
void KoDockWidgetTitleBar::Private::updateIcons()
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
lockButton->setIcon((!locked) ? koIcon("object-unlocked") : koIcon("object-locked"));
// this method gets called when switching themes, so update all of the themed icons now
floatButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarNormalButton, 0, q));
closeButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarCloseButton, 0, q));
if (q->widget()) {
collapseButton->setIcon(q->widget()->isHidden() ? closeIcon(q) : openIcon(q));
}
thePublic->resizeEvent(0);
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoDockWidgetTitleBar.cpp"
diff --git a/src/libs/widgets/KoDockWidgetTitleBarButton.cpp b/src/libs/widgets/KoDockWidgetTitleBarButton.cpp
index 476da466..e83daa9d 100644
--- a/src/libs/widgets/KoDockWidgetTitleBarButton.cpp
+++ b/src/libs/widgets/KoDockWidgetTitleBarButton.cpp
@@ -1,114 +1,115 @@
/* This file is part of the KDE project
Copyright (c) 2007 Marijn Kruisselbrink <mkruisselbrink@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
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 "KoDockWidgetTitleBarButton.h"
#include <WidgetsDebug.h>
#include <QAbstractButton>
#include <QAction>
#include <QLabel>
#include <QLayout>
#include <QStyle>
#include <QStylePainter>
#include <QStyleOptionFrame>
class Q_DECL_HIDDEN KoDockWidgetTitleBarButton::Private
{
public:
Private() : styleSize(0, 0), iconSize(0) {}
QSize styleSize;
int iconSize;
};
KoDockWidgetTitleBarButton::KoDockWidgetTitleBarButton(QWidget *parent)
: QAbstractButton(parent), d(new Private())
{
setFocusPolicy(Qt::NoFocus);
}
KoDockWidgetTitleBarButton::~KoDockWidgetTitleBarButton()
{
delete d;
}
QSize KoDockWidgetTitleBarButton::sizeHint() const
{
ensurePolished();
const int margin = style()->pixelMetric(QStyle::PM_DockWidgetTitleBarButtonMargin, 0, this);
if (icon().isNull()) {
return QSize(18, 18); // TODO: was QSize(margin, margin), hardcoded values seem bad
}
int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
if (iconSize != d->iconSize) {
const_cast<KoDockWidgetTitleBarButton*>(this)->d->iconSize = iconSize;
const QPixmap pm = icon().pixmap(iconSize);
const_cast<KoDockWidgetTitleBarButton*>(this)->d->styleSize = QSize(pm.width() + margin, pm.height() + margin);
}
return d->styleSize;
}
QSize KoDockWidgetTitleBarButton::minimumSizeHint() const
{
return sizeHint();
}
// redraw the button when the mouse enters or leaves it
void KoDockWidgetTitleBarButton::enterEvent(QEvent *event)
{
if (isEnabled())
update();
QAbstractButton::enterEvent(event);
}
void KoDockWidgetTitleBarButton::leaveEvent(QEvent *event)
{
if (isEnabled())
update();
QAbstractButton::leaveEvent(event);
}
void KoDockWidgetTitleBarButton::paintEvent(QPaintEvent *)
{
QPainter p(this);
QStyleOptionToolButton opt;
opt.initFrom(this);
opt.state |= QStyle::State_AutoRaise;
if (isEnabled() && underMouse() && !isChecked() && !isDown())
opt.state |= QStyle::State_Raised;
if (isChecked())
opt.state |= QStyle::State_On;
if (isDown())
opt.state |= QStyle::State_Sunken;
style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, &p, this);
opt.icon = icon();
opt.subControls = 0;
opt.activeSubControls = 0;
opt.features = QStyleOptionToolButton::None;
opt.arrowType = Qt::NoArrow;
int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
opt.iconSize = QSize(size, size);
style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &p, this);
}
diff --git a/src/libs/widgets/KoDocumentInfoDlg.cpp b/src/libs/widgets/KoDocumentInfoDlg.cpp
index 16dbf6dd..0f4d03ee 100644
--- a/src/libs/widgets/KoDocumentInfoDlg.cpp
+++ b/src/libs/widgets/KoDocumentInfoDlg.cpp
@@ -1,478 +1,479 @@
/* This file is part of the KDE project
Copyright (c) 2000 Simon Hausmann <hausmann@kde.org>
2006 Martin Pfeiffer <hubipete@gmx.net>
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 "KoDocumentInfoDlg.h"
#include "ui_koDocumentInfoAboutWidget.h"
#include "ui_koDocumentInfoAuthorWidget.h"
#include "KoDocumentInfo.h"
#include "KoDocumentBase.h"
#include "KoGlobal.h"
#ifdef QCA2
#include <KoEncryptionChecker.h>
#endif
#include "KoPageWidgetItem.h"
//#include <KoDocumentRdfBase.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <kiconloader.h>
#include <kmessagebox.h>
#include <kmainwindow.h>
#include <KoDialog.h>
#include <QUrl>
#include <QLineEdit>
#include <QDateTime>
#include <QMimeDatabase>
#include <QMimeType>
// see KoIcon.h
#define koSmallIcon(name) (SmallIcon(QStringLiteral(name)))
class KoPageWidgetItemAdapter : public KPageWidgetItem
{
Q_OBJECT
public:
KoPageWidgetItemAdapter(KoPageWidgetItem *item)
: KPageWidgetItem(item->widget(), item->name())
, m_item(item)
{
setHeader(item->name());
setIcon(QIcon::fromTheme(item->iconName()));
}
~KoPageWidgetItemAdapter() { delete m_item; }
bool shouldDialogCloseBeVetoed() { return m_item->shouldDialogCloseBeVetoed(); }
void apply() { m_item->apply(); }
private:
KoPageWidgetItem * const m_item;
};
class KoDocumentInfoDlg::KoDocumentInfoDlgPrivate
{
public:
KoDocumentInfoDlgPrivate() :
toggleEncryption(false),
applyToggleEncryption(false),
documentSaved(false) {}
~KoDocumentInfoDlgPrivate() {}
KoDocumentInfo* info;
QList<KPageWidgetItem*> pages;
Ui::KoDocumentInfoAboutWidget* aboutUi;
Ui::KoDocumentInfoAuthorWidget* authorUi;
bool toggleEncryption;
bool applyToggleEncryption;
bool documentSaved;
};
KoDocumentInfoDlg::KoDocumentInfoDlg(QWidget* parent, KoDocumentInfo* docInfo)
: KPageDialog(parent)
, d(new KoDocumentInfoDlgPrivate)
{
d->info = docInfo;
setWindowTitle(i18n("Document Information"));
// setInitialSize(QSize(500, 500));
setFaceType(KPageDialog::List);
setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
button(QDialogButtonBox::Ok)->setDefault(true);
d->aboutUi = new Ui::KoDocumentInfoAboutWidget();
QWidget *infodlg = new QWidget();
d->aboutUi->setupUi(infodlg);
#ifdef QCA2
if (!KoEncryptionChecker::isEncryptionSupported()) {
#endif
d->aboutUi->lblEncryptedDesc->setVisible(false);
d->aboutUi->lblEncrypted->setVisible(false);
d->aboutUi->pbEncrypt->setVisible(false);
d->aboutUi->lblEncryptedPic->setVisible(false);
#ifdef QCA2
}
#endif
d->aboutUi->cbLanguage->addItems(KoGlobal::listOfLanguages());
d->aboutUi->cbLanguage->setCurrentIndex(-1);
KPageWidgetItem *page = new KPageWidgetItem(infodlg, i18n("General"));
page->setHeader(i18n("General"));
// Ugly hack, the mimetype should be a parameter, instead
KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent());
if (doc) {
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName(doc->mimeType());
if (mime.isValid()) {
page->setIcon(QIcon::fromTheme(mime.iconName()));
}
} else {
// hide all entries not used in pages for KoDocumentInfoPropsPage
d->aboutUi->filePathInfoLabel->setVisible(false);
d->aboutUi->filePathLabel->setVisible(false);
d->aboutUi->filePathSeparatorLine->setVisible(false);
d->aboutUi->lblTypeDesc->setVisible(false);
d->aboutUi->lblType->setVisible(false);
}
addPage(page);
d->pages.append(page);
initAboutTab();
d->authorUi = new Ui::KoDocumentInfoAuthorWidget();
QWidget *authordlg = new QWidget();
d->authorUi->setupUi(authordlg);
page = new KPageWidgetItem(authordlg, i18n("Author"));
page->setHeader(i18n("Last saved by"));
page->setIcon(koIcon("user-identity"));
addPage(page);
d->pages.append(page);
initAuthorTab();
}
KoDocumentInfoDlg::~KoDocumentInfoDlg()
{
delete d->authorUi;
delete d->aboutUi;
delete d;
}
void KoDocumentInfoDlg::accept()
{
// check if any pages veto the close
foreach(KPageWidgetItem* item, d->pages) {
KoPageWidgetItemAdapter *page = dynamic_cast<KoPageWidgetItemAdapter*>(item);
if (page) {
if (page->shouldDialogCloseBeVetoed()) {
return;
}
}
}
// all fine, go and apply
saveAboutData();
foreach(KPageWidgetItem* item, d->pages) {
KoPageWidgetItemAdapter *page = dynamic_cast<KoPageWidgetItemAdapter*>(item);
if (page) {
page->apply();
}
}
KPageDialog::accept();
}
bool KoDocumentInfoDlg::isDocumentSaved()
{
return d->documentSaved;
}
void KoDocumentInfoDlg::initAboutTab()
{
KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent());
if (doc) {
d->aboutUi->filePathLabel->setText(doc->localFilePath());
}
d->aboutUi->leTitle->setText(d->info->aboutInfo("title"));
d->aboutUi->leSubject->setText(d->info->aboutInfo("subject"));
QString language = KoGlobal::languageFromTag(d->info->aboutInfo("language"));
d->aboutUi->cbLanguage->setCurrentIndex(d->aboutUi->cbLanguage->findText(language));
d->aboutUi->leKeywords->setToolTip(i18n("Use ';' (Example: Office;KDE;Calligra)"));
if (!d->info->aboutInfo("keyword").isEmpty())
d->aboutUi->leKeywords->setText(d->info->aboutInfo("keyword"));
d->aboutUi->meComments->setPlainText(d->info->aboutInfo("description"));
if (doc && !doc->mimeType().isEmpty()) {
QMimeDatabase db;
QMimeType docmime = db.mimeTypeForName(doc->mimeType());
if (docmime.isValid())
d->aboutUi->lblType->setText(docmime.comment());
}
if (!d->info->aboutInfo("creation-date").isEmpty()) {
QDateTime t = QDateTime::fromString(d->info->aboutInfo("creation-date"),
Qt::ISODate);
QString s = QLocale().toString(t);
d->aboutUi->lblCreated->setText(s + ", " +
d->info->aboutInfo("initial-creator"));
}
if (!d->info->aboutInfo("date").isEmpty()) {
QDateTime t = QDateTime::fromString(d->info->aboutInfo("date"), Qt::ISODate);
QString s = QLocale().toString(t);
d->aboutUi->lblModified->setText(s + ", " + d->info->authorInfo("creator"));
}
d->aboutUi->lblRevision->setText(d->info->aboutInfo("editing-cycles"));
if (doc && (doc->supportedSpecialFormats() & KoDocumentBase::SaveEncrypted)) {
if (doc->specialOutputFlag() == KoDocumentBase::SaveEncrypted) {
if (d->toggleEncryption) {
d->aboutUi->lblEncrypted->setText(i18n("This document will be decrypted"));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked"));
d->aboutUi->pbEncrypt->setText(i18n("Do not decrypt"));
} else {
d->aboutUi->lblEncrypted->setText(i18n("This document is encrypted"));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked"));
d->aboutUi->pbEncrypt->setText(i18n("D&ecrypt"));
}
} else {
if (d->toggleEncryption) {
d->aboutUi->lblEncrypted->setText(i18n("This document will be encrypted."));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked"));
d->aboutUi->pbEncrypt->setText(i18n("Do not encrypt"));
} else {
d->aboutUi->lblEncrypted->setText(i18n("This document is not encrypted"));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked"));
d->aboutUi->pbEncrypt->setText(i18n("&Encrypt"));
}
}
} else {
d->aboutUi->lblEncrypted->setText(i18n("This document does not support encryption"));
d->aboutUi->pbEncrypt->setEnabled( false );
}
connect(d->aboutUi->pbReset, &QAbstractButton::clicked,
this, &KoDocumentInfoDlg::slotResetMetaData);
connect(d->aboutUi->pbEncrypt, &QAbstractButton::clicked,
this, &KoDocumentInfoDlg::slotToggleEncryption);
}
void KoDocumentInfoDlg::initAuthorTab()
{
d->authorUi->fullName->setText(d->info->authorInfo("creator"));
d->authorUi->initials->setText(d->info->authorInfo("initial"));
d->authorUi->title->setText(d->info->authorInfo("author-title"));
d->authorUi->company->setText(d->info->authorInfo("company"));
d->authorUi->email->setText(d->info->authorInfo("email"));
d->authorUi->phoneWork->setText(d->info->authorInfo("telephone-work"));
d->authorUi->phoneHome->setText(d->info->authorInfo("telephone"));
d->authorUi->fax->setText(d->info->authorInfo("fax"));
d->authorUi->country->setText(d->info->authorInfo("country"));
d->authorUi->postal->setText(d->info->authorInfo("postal-code"));
d->authorUi->city->setText(d->info->authorInfo("city"));
d->authorUi->street->setText(d->info->authorInfo("street"));
d->authorUi->position->setText(d->info->authorInfo("position"));
}
void KoDocumentInfoDlg::saveAboutData()
{
d->info->setAboutInfo("keyword", d->aboutUi->leKeywords->text());
d->info->setAboutInfo("title", d->aboutUi->leTitle->text());
d->info->setAboutInfo("subject", d->aboutUi->leSubject->text());
d->info->setAboutInfo("description", d->aboutUi->meComments->toPlainText());
d->info->setAboutInfo("language", KoGlobal::tagOfLanguage(d->aboutUi->cbLanguage->currentText()));
d->applyToggleEncryption = d->toggleEncryption;
}
void KoDocumentInfoDlg::hideEvent( QHideEvent *event )
{
Q_UNUSED(event);
// Saving encryption implies saving the document, this is done after closing the dialog
// TODO: shouldn't this be skipped if cancel is pressed?
saveEncryption();
}
void KoDocumentInfoDlg::slotResetMetaData()
{
d->info->resetMetaData();
if (!d->info->aboutInfo("creation-date").isEmpty()) {
QDateTime t = QDateTime::fromString(d->info->aboutInfo("creation-date"),
Qt::ISODate);
QString s = QLocale().toString(t);
d->aboutUi->lblCreated->setText(s + ", " +
d->info->aboutInfo("initial-creator"));
}
if (!d->info->aboutInfo("date").isEmpty()) {
QDateTime t = QDateTime::fromString(d->info->aboutInfo("date"), Qt::ISODate);
QString s = QLocale().toString(t);
d->aboutUi->lblModified->setText(s + ", " + d->info->authorInfo("creator"));
}
d->aboutUi->lblRevision->setText(d->info->aboutInfo("editing-cycles"));
}
void KoDocumentInfoDlg::slotToggleEncryption()
{
KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent());
if (!doc)
return;
d->toggleEncryption = !d->toggleEncryption;
if (doc->specialOutputFlag() == KoDocumentBase::SaveEncrypted) {
if (d->toggleEncryption) {
d->aboutUi->lblEncrypted->setText(i18n("This document will be decrypted"));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked"));
d->aboutUi->pbEncrypt->setText(i18n("Do not decrypt"));
} else {
d->aboutUi->lblEncrypted->setText(i18n("This document is encrypted"));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked"));
d->aboutUi->pbEncrypt->setText(i18n("D&ecrypt"));
}
} else {
if (d->toggleEncryption) {
d->aboutUi->lblEncrypted->setText(i18n("This document will be encrypted."));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked"));
d->aboutUi->pbEncrypt->setText(i18n("Do not encrypt"));
} else {
d->aboutUi->lblEncrypted->setText(i18n("This document is not encrypted"));
d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked"));
d->aboutUi->pbEncrypt->setText(i18n("&Encrypt"));
}
}
}
void KoDocumentInfoDlg::saveEncryption()
{
if (!d->applyToggleEncryption)
return;
KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent());
if (!doc)
return;
KMainWindow* mainWindow = dynamic_cast< KMainWindow* >(parent());
if (doc->specialOutputFlag() == KoDocumentBase::SaveEncrypted) {
// Decrypt
if (KMessageBox::warningContinueCancel(
this,
i18n("<qt>Decrypting the document will remove the password protection from it."
"<p>Do you still want to decrypt the file?</qt>"),
i18n("Confirm Decrypt"),
KGuiItem(i18n("Decrypt")),
KStandardGuiItem::cancel(),
"DecryptConfirmation"
) != KMessageBox::Continue) {
return;
}
bool modified = doc->isModified();
doc->setOutputMimeType(doc->outputMimeType(), doc->specialOutputFlag() & ~KoDocumentBase::SaveEncrypted);
if (!mainWindow) {
KMessageBox::information(
this,
i18n("<qt>Your document could not be saved automatically."
"<p>To complete the decryption, please save the document.</qt>"),
i18n("Save Document"),
"DecryptSaveMessage");
return;
}
if (modified && KMessageBox::questionYesNo(
this,
i18n("<qt>The document has been changed since it was opened. To complete the decryption the document needs to be saved."
"<p>Do you want to save the document now?</qt>"),
i18n("Save Document"),
KStandardGuiItem::save(),
KStandardGuiItem::dontSave(),
"DecryptSaveConfirmation"
) != KMessageBox::Yes) {
return;
}
} else {
// Encrypt
bool modified = doc->isModified();
if (!doc->url().isEmpty() && !(doc->mimeType().startsWith("application/vnd.oasis.opendocument.") && doc->specialOutputFlag() == 0)) {
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName(doc->mimeType());
QString comment = mime.isValid() ? mime.comment() : i18n("%1 (unknown file type)", QString::fromLatin1(doc->mimeType()));
if (KMessageBox::warningContinueCancel(
this,
i18n("<qt>The document is currently saved as %1. The document needs to be changed to <b>OASIS OpenDocument</b> to be encrypted."
"<p>Do you want to change the file to OASIS OpenDocument?</qt>", QString("<b>%1</b>").arg(comment)),
i18n("Change Filetype"),
KGuiItem(i18n("Change")),
KStandardGuiItem::cancel(),
"EncryptChangeFiletypeConfirmation"
) != KMessageBox::Continue) {
return;
}
doc->resetURL();
}
doc->setMimeType(doc->nativeOasisMimeType());
doc->setOutputMimeType(doc->nativeOasisMimeType(), KoDocumentBase::SaveEncrypted);
if (!mainWindow) {
KMessageBox::information(
this,
i18n("<qt>Your document could not be saved automatically."
"<p>To complete the encryption, please save the document.</qt>"),
i18n("Save Document"),
"EncryptSaveMessage");
return;
}
if (modified && KMessageBox::questionYesNo(
this,
i18n("<qt>The document has been changed since it was opened. To complete the encryption the document needs to be saved."
"<p>Do you want to save the document now?</qt>"),
i18n("Save Document"),
KStandardGuiItem::save(),
KStandardGuiItem::dontSave(),
"EncryptSaveConfirmation"
) != KMessageBox::Yes) {
return;
}
}
// Why do the dirty work ourselves?
emit saveRequested();
d->toggleEncryption = false;
d->applyToggleEncryption = false;
// Detects when the user cancelled saving
d->documentSaved = !doc->url().isEmpty();
}
QList<KPageWidgetItem*> KoDocumentInfoDlg::pages() const
{
return d->pages;
}
void KoDocumentInfoDlg::setReadOnly(bool ro)
{
d->aboutUi->meComments->setReadOnly(ro);
Q_FOREACH(KPageWidgetItem* page, d->pages) {
Q_FOREACH(QLineEdit* le, page->widget()->findChildren<QLineEdit *>()) {
le->setReadOnly(ro);
}
Q_FOREACH(QPushButton* le, page->widget()->findChildren<QPushButton *>()) {
le->setDisabled(ro);
}
}
}
void KoDocumentInfoDlg::addPageItem(KoPageWidgetItem *item)
{
KPageWidgetItem * page = new KoPageWidgetItemAdapter(item);
addPage(page);
d->pages.append(page);
}
#include "KoDocumentInfoDlg.moc"
diff --git a/src/libs/widgets/KoDpi.cpp b/src/libs/widgets/KoDpi.cpp
index 57700f52..b55035d6 100644
--- a/src/libs/widgets/KoDpi.cpp
+++ b/src/libs/widgets/KoDpi.cpp
@@ -1,72 +1,73 @@
/* This file is part of the KDE project
Copyright (C) 2001 David Faure <faure@kde.org>
Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
Copyright 2009 Thomas Zander <zander@kde.org>
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 "KoDpi.h"
#include <QFontInfo>
#ifdef HAVE_X11
#include <QX11Info>
#else
#include <QApplication>
#include <QDesktopWidget>
#endif
#include <QGlobalStatic>
Q_GLOBAL_STATIC(KoDpi, s_instance)
KoDpi* KoDpi::self()
{
return s_instance;
}
KoDpi::KoDpi()
{
// Another way to get the DPI of the display would be QPaintDeviceMetrics,
// but we have no widget here (and moving this to KoView wouldn't allow
// using this from the document easily).
#ifdef HAVE_X11
m_dpiX = QX11Info::appDpiX();
m_dpiY = QX11Info::appDpiY();
#else
QDesktopWidget *w = QApplication::desktop();
if (w) {
m_dpiX = w->logicalDpiX();
m_dpiY = w->logicalDpiY();
} else {
m_dpiX = 75;
m_dpiY = 75;
}
#endif
}
KoDpi::~KoDpi()
{
}
void KoDpi::setDPI(int x, int y)
{
//debugWidgets << x <<"," << y;
KoDpi* s = self();
s->m_dpiX = x;
s->m_dpiY = y;
}
diff --git a/src/libs/widgets/KoGlobal.cpp b/src/libs/widgets/KoGlobal.cpp
index 9c8bca70..a1426487 100644
--- a/src/libs/widgets/KoGlobal.cpp
+++ b/src/libs/widgets/KoGlobal.cpp
@@ -1,170 +1,171 @@
/* This file is part of the KDE project
Copyright (C) 2001 David Faure <faure@kde.org>
Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
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 "KoGlobal.h"
#include <config.h>
#include <KoResourcePaths.h>
#include <QPaintDevice>
#include <QFont>
#include <QFontInfo>
#include <QFontDatabase>
#include <QGlobalStatic>
#include <WidgetsDebug.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfig.h>
Q_GLOBAL_STATIC(KoGlobal, s_instance)
KoGlobal* KoGlobal::self()
{
return s_instance;
}
KoGlobal::KoGlobal()
: m_pointSize(-1)
, m_planConfig(0)
{
// Fixes a bug where values from some config files are not picked up
// due to KSharedConfig::openConfig() being initialized before paths have been set up above.
// NOTE: Values set without a sync() call before KoGlobal has been initialized will not stick
KSharedConfig::openConfig()->reparseConfiguration();
}
KoGlobal::~KoGlobal()
{
delete m_planConfig;
}
QFont KoGlobal::_defaultFont()
{
QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
// we have to use QFontInfo, in case the font was specified with a pixel size
if (font.pointSize() == -1) {
// cache size into m_pointSize, since QFontInfo loads the font -> slow
if (m_pointSize == -1)
m_pointSize = QFontInfo(font).pointSize();
Q_ASSERT(m_pointSize != -1);
font.setPointSize(m_pointSize);
}
//debugWidgets<<"QFontInfo(font).pointSize() :"<<QFontInfo(font).pointSize();
//debugWidgets<<"font.name() :"<<font.family ();
return font;
}
QStringList KoGlobal::_listOfLanguageTags()
{
if (m_langMap.isEmpty())
createListOfLanguages();
return m_langMap.values();
}
QStringList KoGlobal::_listOfLanguages()
{
if (m_langMap.empty())
createListOfLanguages();
return m_langMap.keys();
}
void KoGlobal::createListOfLanguages()
{
KConfig config("all_languages", KConfig::NoGlobals);
QMap<QString, bool> seenLanguages;
const QStringList langlist = config.groupList();
for (QStringList::ConstIterator itall = langlist.begin();
itall != langlist.end(); ++itall) {
const QString tag = *itall;
const QString name = config.group(tag).readEntry("Name", tag);
// e.g. name is "French" and tag is "fr"
// The QMap does the sorting on the display-name, so that
// comboboxes are sorted.
m_langMap.insert(name, tag);
seenLanguages.insert(tag, true);
}
// Also take a look at the installed translations.
// Many of them are already in all_languages but all_languages doesn't
// currently have en_US (etc?).
const QStringList translationList = KoResourcePaths::findAllResources("locale",
QString::fromLatin1("*/kf5_entry.desktop"));
for (QStringList::ConstIterator it = translationList.begin();
it != translationList.end(); ++it) {
// Extract the language tag from the directory name
QString tag = *it;
int index = tag.lastIndexOf('/');
tag = tag.left(index);
index = tag.lastIndexOf('/');
tag = tag.mid(index + 1);
if (seenLanguages.find(tag) == seenLanguages.end()) {
KConfig entry(*it, KConfig::SimpleConfig);
const QString name = entry.group("KCM Locale").readEntry("Name", tag);
// e.g. name is "US English" and tag is "en_US"
m_langMap.insert(name, tag);
// enable this if writing a third way of finding languages below
//seenLanguages.insert( tag, true );
}
}
// #### We also might not have an entry for a language where spellchecking is supported,
// but no KDE translation is available, like fr_CA.
// How to add them?
}
QString KoGlobal::tagOfLanguage(const QString & _lang)
{
const LanguageMap& map = self()->m_langMap;
QMap<QString, QString>::ConstIterator it = map.find(_lang);
if (it != map.end())
return *it;
return QString();
}
QString KoGlobal::languageFromTag(const QString &langTag)
{
const LanguageMap& map = self()->m_langMap;
QMap<QString, QString>::ConstIterator it = map.begin();
const QMap<QString, QString>::ConstIterator end = map.end();
for (; it != end; ++it)
if (it.value() == langTag)
return it.key();
// Language code not found. Better return the code (tag) than nothing.
return langTag;
}
KConfig* KoGlobal::_planConfig()
{
if (!m_planConfig) {
m_planConfig = new KConfig("calligraplanrc");
}
return m_planConfig;
}
diff --git a/src/libs/widgets/KoPageLayoutDialog.cpp b/src/libs/widgets/KoPageLayoutDialog.cpp
index 7aa1bad0..9f53a9ea 100644
--- a/src/libs/widgets/KoPageLayoutDialog.cpp
+++ b/src/libs/widgets/KoPageLayoutDialog.cpp
@@ -1,157 +1,158 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2010 Thomas Zander <zander@kde.org>
*
* 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 "KoPageLayoutDialog.h"
#include "KoPageLayoutWidget.h"
#include "KoPagePreviewWidget.h"
#include <klocalizedstring.h>
#include <WidgetsDebug.h>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QTimer>
class Q_DECL_HIDDEN KoPageLayoutDialog::Private
{
public:
Private() : pageLayoutWidget(0), documentCheckBox(0) {}
KoPageLayoutWidget *pageLayoutWidget;
QCheckBox *documentCheckBox;
};
KoPageLayoutDialog::KoPageLayoutDialog(QWidget *parent, const KoPageLayout &layout)
: KPageDialog(parent)
, d(new Private)
{
setWindowTitle(i18n("Page Layout"));
setFaceType(KPageDialog::Tabbed);
QWidget *widget = new QWidget(this);
addPage(widget, i18n("Page"));
QHBoxLayout *lay = new QHBoxLayout(widget);
d->pageLayoutWidget = new KoPageLayoutWidget(widget, layout);
d->pageLayoutWidget->showUnitchooser(false);
lay->addWidget(d->pageLayoutWidget,1);
KoPagePreviewWidget *prev = new KoPagePreviewWidget(widget);
// use not original layout, but "fixed" one (e.g. with 0 values) as now hold by pageLayoutWidget
prev->setPageLayout(d->pageLayoutWidget->pageLayout());
lay->addWidget(prev, 1);
connect (d->pageLayoutWidget, &KoPageLayoutWidget::layoutChanged,
prev, &KoPagePreviewWidget::setPageLayout);
connect (d->pageLayoutWidget, &KoPageLayoutWidget::layoutChanged,
this, &KoPageLayoutDialog::setPageLayout);
connect (d->pageLayoutWidget, &KoPageLayoutWidget::unitChanged,
this, &KoPageLayoutDialog::unitChanged);
}
KoPageLayoutDialog::~KoPageLayoutDialog()
{
delete d;
}
KoPageLayout KoPageLayoutDialog::pageLayout() const
{
return d->pageLayoutWidget->pageLayout();
}
void KoPageLayoutDialog::setPageLayout(const KoPageLayout &layout)
{
d->pageLayoutWidget->setPageLayout(layout);
}
void KoPageLayoutDialog::accept()
{
KPageDialog::accept();
deleteLater();
}
void KoPageLayoutDialog::reject()
{
KPageDialog::reject();
deleteLater();
}
bool KoPageLayoutDialog::applyToDocument() const
{
return d->documentCheckBox && d->documentCheckBox->isChecked();
}
void KoPageLayoutDialog::showApplyToDocument(bool on)
{
if (on && d->documentCheckBox == 0) {
for (int i = 0; i < children().count(); ++i) {
if (QDialogButtonBox *buttonBox = qobject_cast<QDialogButtonBox*>(children()[i])) {
d->documentCheckBox = new QCheckBox(i18n("Apply to document"), buttonBox);
d->documentCheckBox->setChecked(true);
buttonBox->addButton(d->documentCheckBox, QDialogButtonBox::ResetRole);
break;
}
}
Q_ASSERT(d->pageLayoutWidget);
connect (d->documentCheckBox, &QAbstractButton::toggled,
d->pageLayoutWidget, &KoPageLayoutWidget::setApplyToDocument);
} else if (d->documentCheckBox) {
d->documentCheckBox->setVisible(on);
}
}
/*
void KoPageLayoutDialog::showTextDirection(bool on)
{
d->pageLayoutWidget->showTextDirection(on);
}
KoText::Direction KoPageLayoutDialog::textDirection() const
{
return d->pageLayoutWidget->textDirection();
}
void KoPageLayoutDialog::setTextDirection(KoText::Direction direction)
{
d->pageLayoutWidget->setTextDirection(direction);
}
*/
void KoPageLayoutDialog::showPageSpread(bool on)
{
d->pageLayoutWidget->showPageSpread(on);
}
void KoPageLayoutDialog::setPageSpread(bool pageSpread)
{
d->pageLayoutWidget->setPageSpread(pageSpread);
}
void KoPageLayoutDialog::showUnitchooser(bool on)
{
d->pageLayoutWidget->showUnitchooser(on);
}
/*
void KoPageLayoutDialog::setUnit(const KoUnit &unit)
{
d->pageLayoutWidget->setUnit(unit);
}
*/
diff --git a/src/libs/widgets/KoPageLayoutWidget.cpp b/src/libs/widgets/KoPageLayoutWidget.cpp
index 2d8790c1..3e003171 100644
--- a/src/libs/widgets/KoPageLayoutWidget.cpp
+++ b/src/libs/widgets/KoPageLayoutWidget.cpp
@@ -1,350 +1,351 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2010 Thomas Zander <zander@kde.org>
*
* 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 "KoPageLayoutWidget.h"
#include <ui_KoPageLayoutWidget.h>
#include <KoUnit.h>
#include <QButtonGroup>
class Q_DECL_HIDDEN KoPageLayoutWidget::Private
{
public:
Ui::KoPageLayoutWidget widget;
KoPageLayout pageLayout;
KoUnit unit;
QButtonGroup *orientationGroup;
bool marginsEnabled;
bool allowSignals;
};
KoPageLayoutWidget::KoPageLayoutWidget(QWidget *parent, const KoPageLayout &layout)
: QWidget(parent)
, d(new Private)
{
d->widget.setupUi(this);
d->pageLayout = layout;
d->marginsEnabled = true;
d->allowSignals = true;
d->orientationGroup = new QButtonGroup(this);
d->orientationGroup->addButton(d->widget.portrait, KoPageFormat::Portrait);
d->orientationGroup->addButton(d->widget.landscape, KoPageFormat::Landscape);
QButtonGroup *group2 = new QButtonGroup(this);
group2->addButton(d->widget.singleSided);
group2->addButton(d->widget.facingPages);
// the two sets of labels we use might have different lengths; make sure this does not create a 'jumping' ui
d->widget.facingPages->setChecked(true);
facingPagesChanged();
int width = qMax(d->widget.leftLabel->width(), d->widget.rightLabel->width());
d->widget.singleSided->setChecked(true);
facingPagesChanged();
width = qMax(width, qMax(d->widget.leftLabel->width(), d->widget.rightLabel->width()));
d->widget.leftLabel->setMinimumSize(QSize(width, 5));
d->widget.units->addItems(KoUnit::listOfUnitNameForUi(KoUnit::HidePixel));
d->widget.sizes->addItems(KoPageFormat::localizedPageFormatNames());
setPageSpread(false);
connect(d->widget.sizes, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KoPageLayoutWidget::sizeChanged);
connect(d->widget.units, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KoPageLayoutWidget::slotUnitChanged);
connect(group2, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &KoPageLayoutWidget::facingPagesChanged);
connect(d->orientationGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &KoPageLayoutWidget::orientationChanged);
connect(d->widget.width, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::optionsChanged);
connect(d->widget.height, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::optionsChanged);
connect(d->widget.topMargin, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::marginsChanged);
connect(d->widget.bottomMargin, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::marginsChanged);
connect(d->widget.bindingEdgeMargin, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::marginsChanged);
connect(d->widget.pageEdgeMargin, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::marginsChanged);
connect(d->widget.width, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::optionsChanged);
connect(d->widget.height, &KoUnitDoubleSpinBox::valueChangedPt, this, &KoPageLayoutWidget::optionsChanged);
setUnit(KoUnit(KoUnit::Millimeter));
setPageLayout(layout);
if (layout.format == 0) // make sure we always call this during startup, even if the A3 (index=0) was chosen
sizeChanged(layout.format);
showTextDirection(false);
/* disable advanced page layout features by default */
d->widget.facingPageLabel->setVisible(false);
d->widget.facingPages->setVisible(false);
d->widget.singleSided->setVisible(false);
d->widget.stylesLabel->setVisible(false);
d->widget.pageStyle->setVisible(false);
}
KoPageLayoutWidget::~KoPageLayoutWidget()
{
delete d;
}
KoPageLayout KoPageLayoutWidget::pageLayout() const
{
return d->pageLayout;
}
void KoPageLayoutWidget::sizeChanged(int row)
{
if (row < 0) return;
if (! d->allowSignals) return;
d->allowSignals = false;
d->pageLayout.format = static_cast<KoPageFormat::Format> (row);
bool custom = d->pageLayout.format == KoPageFormat::CustomSize;
d->widget.width->setEnabled( custom );
d->widget.height->setEnabled( custom );
if ( !custom ) {
d->pageLayout.width = MM_TO_POINT( KoPageFormat::width( d->pageLayout.format, d->pageLayout.orientation ) );
d->pageLayout.height = MM_TO_POINT( KoPageFormat::height( d->pageLayout.format, d->pageLayout.orientation ) );
if (d->widget.facingPages->isChecked()) // is pagespread
d->pageLayout.width *= 2;
}
d->widget.width->changeValue( d->pageLayout.width );
d->widget.height->changeValue( d->pageLayout.height );
emit layoutChanged(d->pageLayout);
d->allowSignals = true;
}
void KoPageLayoutWidget::slotUnitChanged(int row)
{
setUnit(KoUnit::fromListForUi(row, KoUnit::HidePixel));
}
void KoPageLayoutWidget::setUnit(const KoUnit &unit)
{
if (d->unit == unit)
return;
d->unit = unit;
d->widget.width->setUnit(unit);
d->widget.height->setUnit(unit);
d->widget.topMargin->setUnit(unit);
d->widget.bottomMargin->setUnit(unit);
d->widget.bindingEdgeMargin->setUnit(unit);
d->widget.pageEdgeMargin->setUnit(unit);
d->widget.units->setCurrentIndex(unit.indexInListForUi(KoUnit::HidePixel));
emit unitChanged(d->unit);
}
void KoPageLayoutWidget::setPageLayout(const KoPageLayout &layout)
{
if (! d->allowSignals) return;
d->allowSignals = false;
d->pageLayout = layout;
Q_ASSERT(d->orientationGroup->button( layout.orientation ));
d->orientationGroup->button( layout.orientation )->setChecked( true );
if (layout.bindingSide >= 0 && layout.pageEdge >= 0) {
d->widget.facingPages->setChecked(true);
d->widget.bindingEdgeMargin->changeValue(layout.bindingSide);
d->widget.pageEdgeMargin->changeValue(layout.pageEdge);
d->pageLayout.leftMargin = -1;
d->pageLayout.rightMargin = -1;
}
else {
d->widget.singleSided->setChecked(true);
d->widget.bindingEdgeMargin->changeValue(layout.leftMargin);
d->widget.pageEdgeMargin->changeValue(layout.rightMargin);
d->pageLayout.pageEdge = -1;
d->pageLayout.bindingSide = -1;
}
facingPagesChanged();
d->widget.topMargin->changeValue(layout.topMargin);
d->widget.bottomMargin->changeValue(layout.bottomMargin);
d->allowSignals = true;
d->widget.sizes->setCurrentIndex(layout.format); // calls sizeChanged()
}
void KoPageLayoutWidget::facingPagesChanged()
{
if (! d->allowSignals) return;
d->allowSignals = false;
if (d->widget.singleSided->isChecked()) {
d->widget.leftLabel->setText(i18n("Left Edge:"));
d->widget.rightLabel->setText(i18n("Right Edge:"));
}
else {
d->widget.leftLabel->setText(i18n("Binding Edge:"));
d->widget.rightLabel->setText(i18n("Page Edge:"));
}
d->allowSignals = true;
marginsChanged();
sizeChanged(d->widget.sizes->currentIndex());
}
void KoPageLayoutWidget::marginsChanged()
{
if (! d->allowSignals) return;
d->allowSignals = false;
d->pageLayout.leftMargin = -1;
d->pageLayout.rightMargin = -1;
d->pageLayout.bindingSide = -1;
d->pageLayout.pageEdge = -1;
d->pageLayout.topMargin = d->marginsEnabled?d->widget.topMargin->value():0;
d->pageLayout.bottomMargin = d->marginsEnabled?d->widget.bottomMargin->value():0;
qreal left = d->marginsEnabled?d->widget.bindingEdgeMargin->value():0;
qreal right = d->marginsEnabled?d->widget.pageEdgeMargin->value():0;
if (left + right > d->pageLayout.width - 10) {
// make sure the actual text area is never smaller than 10 points.
qreal diff = d->pageLayout.width - 10 - left - right;
left = qMin(d->pageLayout.width - 10, qMax(qreal(0.0), left - diff / qreal(2.0)));
right = qMax(qreal(0.0), right - d->pageLayout.width - 10 - left);
}
if (d->widget.singleSided->isChecked()) {
d->pageLayout.leftMargin = left;
d->pageLayout.rightMargin = right;
}
else {
d->pageLayout.bindingSide = left;
d->pageLayout.pageEdge = right;
}
// debugWidgets << " " << d->pageLayout.left <<"|"<< d->pageLayout.bindingSide << "," <<
// d->pageLayout.right << "|"<< d->pageLayout.pageEdge;
emit layoutChanged(d->pageLayout);
d->allowSignals = true;
}
void KoPageLayoutWidget::setTextAreaAvailable(bool available)
{
d->marginsEnabled = available;
d->widget.margins->setEnabled(available);
marginsChanged();
}
void KoPageLayoutWidget::optionsChanged()
{
if (! d->allowSignals) return;
if (d->widget.sizes->currentIndex() == KoPageFormat::CustomSize) {
d->pageLayout.width = d->widget.width->value();
d->pageLayout.height = d->widget.height->value();
} else
sizeChanged(d->widget.sizes->currentIndex());
marginsChanged();
}
void KoPageLayoutWidget::orientationChanged()
{
if (! d->allowSignals) return;
d->allowSignals = false;
d->pageLayout.orientation = d->widget.landscape->isChecked() ? KoPageFormat::Landscape : KoPageFormat::Portrait;
qreal x = d->widget.height->value();
d->widget.height->changeValue( d->widget.width->value() );
d->widget.width->changeValue( x );
d->allowSignals = true;
optionsChanged();
}
void KoPageLayoutWidget::showUnitchooser(bool on) {
d->widget.units->setVisible(on);
d->widget.unitsLabel->setVisible(on);
}
void KoPageLayoutWidget::showPageSpread(bool on)
{
d->widget.facingPageLabel->setVisible(on);
d->widget.singleSided->setVisible(on);
d->widget.facingPages->setVisible(on);
}
void KoPageLayoutWidget::setPageSpread(bool pageSpread)
{
if (pageSpread)
d->widget.facingPages->setChecked(true);
else
d->widget.singleSided->setChecked(true);
}
void KoPageLayoutWidget::setApplyToDocument(bool apply)
{
if (apply) {
d->widget.facingPageLabel->setText(i18n("Facing Pages:"));
d->widget.facingPages->setText(i18n("Facing pages"));
}
else {
d->widget.facingPageLabel->setText(i18n("Page Layout:"));
d->widget.facingPages->setText(i18n("Page spread"));
}
}
void KoPageLayoutWidget::showTextDirection(bool on)
{
d->widget.directionLabel->setVisible(on);
d->widget.textDirection->setVisible(on);
}
/*
void KoPageLayoutWidget::setTextDirection(KoText::Direction direction )
{
int index = 0;
switch(direction) {
case KoText::LeftRightTopBottom:
index = 1;
break;
case KoText::RightLeftTopBottom:
index = 2;
break;
case KoText::TopBottomRightLeft: // unused for now.
case KoText::InheritDirection:
case KoText::AutoDirection:
index = 0;
case KoText::TopBottomLeftRight:
; // unhandled, because it actually doesn't exist in real-world writing systems.
// Boustrophedon would be interesting to implement, though
}
d->widget.textDirection->setCurrentIndex(index);
}
KoText::Direction KoPageLayoutWidget::textDirection() const
{
switch(d->widget.textDirection->currentIndex()) {
case 1: return KoText::LeftRightTopBottom;
case 2: return KoText::RightLeftTopBottom;
default:
case 0: return KoText::AutoDirection;
}
}
*/
void KoPageLayoutWidget::showPageStyles(bool on)
{
d->widget.stylesLabel->setVisible(on);
d->widget.pageStyle->setVisible(on);
}
void KoPageLayoutWidget::setPageStyles(const QStringList &styles)
{
d->widget.pageStyle->clear();
d->widget.pageStyle->addItems(styles);
}
QString KoPageLayoutWidget::currentPageStyle() const
{
return d->widget.pageStyle->currentText();
}
diff --git a/src/libs/widgets/KoPagePreviewWidget.cpp b/src/libs/widgets/KoPagePreviewWidget.cpp
index f9fab188..23716846 100644
--- a/src/libs/widgets/KoPagePreviewWidget.cpp
+++ b/src/libs/widgets/KoPagePreviewWidget.cpp
@@ -1,164 +1,165 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Gary Cramblitt <garycramblitt@comcast.net>
*
* 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 "KoPagePreviewWidget.h"
#include <KoDpi.h>
#include <KoUnit.h>
#include <KoPageLayout.h>
#include <KoColumns.h>
#include <QPainter>
#include <WidgetsDebug.h>
class Q_DECL_HIDDEN KoPagePreviewWidget::Private
{
public:
KoPageLayout pageLayout;
KoColumns columns;
};
KoPagePreviewWidget::KoPagePreviewWidget(QWidget *parent)
: QWidget(parent)
, d(new Private)
{
setMinimumSize( 100, 100 );
}
KoPagePreviewWidget::~KoPagePreviewWidget()
{
delete d;
}
void KoPagePreviewWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
// resolution[XY] is in pixel per pt
qreal resolutionX = POINT_TO_INCH( static_cast<qreal>(KoDpi::dpiX()) );
qreal resolutionY = POINT_TO_INCH( static_cast<qreal>(KoDpi::dpiY()) );
qreal pageWidth = d->pageLayout.width * resolutionX;
qreal pageHeight = d->pageLayout.height * resolutionY;
const bool pageSpread = (d->pageLayout.bindingSide >= 0 && d->pageLayout.pageEdge >= 0);
qreal sheetWidth = pageWidth / (pageSpread?2:1);
qreal zoomH = (height() * 90 / 100) / pageHeight;
qreal zoomW = (width() * 90 / 100) / pageWidth;
qreal zoom = qMin( zoomW, zoomH );
pageWidth *= zoom;
sheetWidth *= zoom;
pageHeight *= zoom;
QPainter painter( this );
QRect page = QRectF((width() - pageWidth) / 2.0,
(height() - pageHeight) / 2.0, sheetWidth, pageHeight).toRect();
painter.save();
drawPage(painter, zoom, page, true);
painter.restore();
if(pageSpread) {
page.moveLeft(page.left() + (int) (sheetWidth));
painter.save();
drawPage(painter, zoom, page, false);
painter.restore();
}
painter.end();
// paint scale
}
void KoPagePreviewWidget::drawPage(QPainter &painter, qreal zoom, const QRect &dimensions, bool left)
{
painter.fillRect(dimensions, QBrush(palette().base()));
painter.setPen(QPen(palette().color(QPalette::Dark), 0));
painter.drawRect(dimensions);
// draw text areas
QRect textArea = dimensions;
if ((d->pageLayout.topMargin == 0 && d->pageLayout.bottomMargin == 0 &&
d->pageLayout.leftMargin == 0 && d->pageLayout.rightMargin == 0) ||
( d->pageLayout.pageEdge == 0 && d->pageLayout.bindingSide == 0)) {
// no margin
return;
}
else {
textArea.setTop(textArea.top() + qRound(zoom * d->pageLayout.topMargin));
textArea.setBottom(textArea.bottom() - qRound(zoom * d->pageLayout.bottomMargin));
qreal leftMargin, rightMargin;
if(d->pageLayout.bindingSide < 0) { // normal margins.
leftMargin = d->pageLayout.leftMargin;
rightMargin = d->pageLayout.rightMargin;
}
else { // margins mirrored for left/right pages
leftMargin = d->pageLayout.bindingSide;
rightMargin = d->pageLayout.pageEdge;
if(left)
qSwap(leftMargin, rightMargin);
}
textArea.setLeft(textArea.left() + qRound(zoom * leftMargin));
textArea.setRight(textArea.right() - qRound(zoom * rightMargin));
}
painter.setBrush( QBrush( palette().color(QPalette::ButtonText), Qt::HorPattern ) );
painter.setPen(QPen(palette().color(QPalette::Dark), 0));
// uniform columns?
if (d->columns.columnData.isEmpty()) {
qreal columnWidth = (textArea.width() + (d->columns.gapWidth * zoom)) / d->columns.count;
int width = qRound(columnWidth - d->columns.gapWidth * zoom);
for ( int i = 0; i < d->columns.count; ++i )
painter.drawRect( qRound(textArea.x() + i * columnWidth), textArea.y(), width, textArea.height());
} else {
qreal totalRelativeWidth = 0.0;
foreach(const KoColumns::ColumnDatum &cd, d->columns.columnData) {
totalRelativeWidth += cd.relativeWidth;
}
int relativeColumnXOffset = 0;
for (int i = 0; i < d->columns.count; i++) {
const KoColumns::ColumnDatum &columnDatum = d->columns.columnData.at(i);
const qreal columnWidth = textArea.width() * columnDatum.relativeWidth / totalRelativeWidth;
const qreal columnXOffset = textArea.width() * relativeColumnXOffset / totalRelativeWidth;
painter.drawRect( qRound(textArea.x() + columnXOffset + columnDatum.leftMargin * zoom),
qRound(textArea.y() + columnDatum.topMargin * zoom),
qRound(columnWidth - (columnDatum.leftMargin + columnDatum.rightMargin) * zoom),
qRound(textArea.height() - (columnDatum.topMargin + columnDatum.bottomMargin) * zoom));
relativeColumnXOffset += columnDatum.relativeWidth;
}
}
}
void KoPagePreviewWidget::setPageLayout(const KoPageLayout &layout)
{
d->pageLayout = layout;
update();
}
void KoPagePreviewWidget::setColumns(const KoColumns &columns)
{
d->columns = columns;
update();
}
diff --git a/src/libs/widgets/KoResourcePaths.cpp b/src/libs/widgets/KoResourcePaths.cpp
index 7df9dc60..81371bda 100644
--- a/src/libs/widgets/KoResourcePaths.cpp
+++ b/src/libs/widgets/KoResourcePaths.cpp
@@ -1,363 +1,364 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+// clazy:excludeall=qstring-arg
#include "KoResourcePaths.h"
#include <QGlobalStatic>
#include <QStringList>
#include <QHash>
#include <QStandardPaths>
#include <QDir>
#include <QFileInfo>
#include <QDebug>
#include <QSet>
#ifdef Q_OS_WIN
static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
#else
static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
#endif
class KoResourcePathsImpl
{
public:
static QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type)
{
return
type == QLatin1String("data") ? QStandardPaths::GenericDataLocation :
type == QLatin1String("config") ? QStandardPaths::GenericConfigLocation :
type == QLatin1String("cache") ? QStandardPaths::CacheLocation :
type == QLatin1String("tmp") ? QStandardPaths::TempLocation :
type == QLatin1String("appdata") ? QStandardPaths::DataLocation :
type == QLatin1String("locale") ? QStandardPaths::GenericDataLocation :
/* default */ QStandardPaths::GenericDataLocation;
}
KoResourcePathsImpl();
~KoResourcePathsImpl();
void addResourceTypeInternal(const QString &type, const QString &basetype,
const QString &relativeName, bool priority);
void addResourceDirInternal(const QString &type, const QString &absdir, bool priority);
QString findResourceInternal(const QString &type, const QString &fileName);
QStringList findDirsInternal(const QString &type, const QString &relDir);
QStringList findAllResourcesInternal(const QString &type,
const QString &filter = QString(),
KoResourcePaths::SearchOptions options = KoResourcePaths::NoSearchOptions) const;
QStringList resourceDirsInternal(const QString &type);
QString saveLocationInternal(const QString &type, const QString &suffix = QString(), bool create = true);
QString locateLocalInternal(const QString &type, const QString &filename, bool createDir = false);
private:
QHash<QString, QStringList> m_absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global
QHash<QString, QStringList> m_relatives; // Same with relative paths
};
KoResourcePathsImpl::KoResourcePathsImpl()
{
}
KoResourcePathsImpl::~KoResourcePathsImpl()
{
}
void KoResourcePathsImpl::addResourceTypeInternal(const QString &type, const QString &basetype,
const QString &relativename,
bool priority)
{
if (relativename.isEmpty()) return;
QString copy = relativename;
Q_ASSERT(basetype == "data");
if (!copy.endsWith(QLatin1Char('/'))) {
copy += QLatin1Char('/');
}
QStringList &rels = m_relatives[type]; // find or insert
if (!rels.contains(copy, cs)) {
if (priority) {
rels.prepend(copy);
} else {
rels.append(copy);
}
}
//qDebug() << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << m_relatives[type];
}
void KoResourcePathsImpl::addResourceDirInternal(const QString &type, const QString &absdir, bool priority)
{
if (absdir.isEmpty() || type.isEmpty()) return;
// find or insert entry in the map
QString copy = absdir;
if (!copy.endsWith(QLatin1Char('/'))) {
copy += QLatin1Char('/');
}
QStringList &paths = m_absolutes[type];
if (!paths.contains(copy, cs)) {
if (priority) {
paths.prepend(copy);
} else {
paths.append(copy);
}
}
//qDebug() << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << m_absolutes[type];
}
QString KoResourcePathsImpl::findResourceInternal(const QString &type, const QString &fileName)
{
const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
QString resource = QStandardPaths::locate(location, fileName, QStandardPaths::LocateFile);
if (resource.isEmpty()) {
foreach(const QString &relative, m_relatives.value(type)) {
resource = QStandardPaths::locate(location, relative + fileName, QStandardPaths::LocateFile);
if (!resource.isEmpty()) {
break;
}
}
}
if (resource.isEmpty()) {
foreach(const QString &absolute, m_absolutes.value(type)) {
const QString filePath = absolute + fileName;
if (QFileInfo::exists(filePath)) {
resource = filePath;
break;
}
}
}
//Q_ASSERT(!resource.isEmpty());
//qDebug() << "findResource: type" << type << "filename" << fileName << "resource" << resource;
return resource;
}
QStringList KoResourcePathsImpl::findDirsInternal(const QString &type, const QString &relDir)
{
const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
QStringList dirs = QStandardPaths::locateAll(location, relDir, QStandardPaths::LocateDirectory);
foreach(const QString &relative, m_relatives.value(type)) {
dirs << QStandardPaths::locateAll(location, relative + relDir, QStandardPaths::LocateDirectory);
}
foreach(const QString &absolute, m_absolutes.value(type)) {
const QString dirPath = absolute + relDir;
if (QDir(dirPath).exists()) {
dirs << dirPath;
}
}
//Q_ASSERT(!dirs.isEmpty());
//qDebug() << "findDirs: type" << type << "relDir" << relDir<< "resource" << dirs;
return dirs;
}
QStringList filesInDir(const QString &startdir, const QString & filter, bool noduplicates, bool recursive)
{
//qDebug() << "filesInDir: startdir" << startdir << "filter" << filter << "noduplicates" << noduplicates << "recursive" << recursive;
QStringList result;
// First the entries in this path
QStringList nameFilters;
nameFilters << filter;
const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name);
//qDebug() << "\tFound:" << fileNames.size() << ":" << fileNames;
Q_FOREACH (const QString &fileName, fileNames) {
QString file = startdir + '/' + fileName;
result << file;
}
// And then everything underneath, if recursive is specified
if (recursive) {
const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
Q_FOREACH (const QString &subdir, entries) {
//qDebug() << "\tGoing to look in subdir" << subdir << "of" << startdir;
result << filesInDir(startdir + '/' + subdir, filter, noduplicates, recursive);
}
}
return result;
}
QStringList KoResourcePathsImpl::findAllResourcesInternal(const QString &type,
const QString &_filter,
KoResourcePaths::SearchOptions options) const
{
//qDebug() << "=====================================================";
bool noDuplicates = options & KoResourcePaths::NoDuplicates;
bool recursive = options & KoResourcePaths::Recursive;
//qDebug() << "findAllResources: type" << type << "filter" << _filter << "no dups" << noDuplicates << "recursive" << recursive;
const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
const QStringList relatives = m_relatives.value(type);
QString filter = _filter;
QString prefix;
// In cases where the filter is like "color-schemes/*.colors" instead of "*.kpp", used with unregistgered resource types
if (filter.indexOf('*') > 0) {
prefix = filter.split('*').first();
filter = '*' + filter.split('*')[1];
//qDebug() << "Split up alias" << relatives << "filter" << filter;
}
QStringList resources;
if (relatives.isEmpty()) {
resources << QStandardPaths::locateAll(location, prefix + filter, QStandardPaths::LocateFile);
}
////qDebug() << "\tresources from qstandardpaths:" << resources.size();
foreach(const QString &relative, relatives) {
//qDebug() << "\t\relative:" << relative;
const QStringList dirs = QStandardPaths::locateAll(location, relative + prefix, QStandardPaths::LocateDirectory);
QSet<QString> s = QSet<QString>::fromList(dirs);
//qDebug() << "\t\tdirs:" << dirs;
Q_FOREACH (const QString &dir, s) {
resources << filesInDir(dir, filter, noDuplicates, recursive);
}
}
foreach(const QString &absolute, m_absolutes.value(type)) {
const QString dir = absolute + prefix;
if (QDir(dir).exists()) {
resources << filesInDir(dir, filter, noDuplicates, recursive);
}
}
if (noDuplicates) {
QSet<QString> s = QSet<QString>::fromList(resources);
resources = s.toList();
}
//qDebug() << "\tresources also from aliases:" << resources.size();
//qDebug() << "=====================================================";
//Q_ASSERT(!resources.isEmpty());
return resources;
}
QStringList KoResourcePathsImpl::resourceDirsInternal(const QString &type)
{
//return KGlobal::dirs()->resourceDirs(type.toLatin1());
QStringList resourceDirs;
const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
foreach(const QString &relative, m_relatives.value(type)) {
resourceDirs << QStandardPaths::locateAll(location, relative, QStandardPaths::LocateDirectory);
}
foreach(const QString &absolute, m_absolutes.value(type)) {
if (QDir(absolute).exists()) {
resourceDirs << absolute;
}
}
//qDebug() << "resourceDirs: type" << type << resourceDirs;
return resourceDirs;
}
QString KoResourcePathsImpl::saveLocationInternal(const QString &type, const QString &suffix, bool create)
{
QString path = QStandardPaths::writableLocation(mapTypeToQStandardPaths(type)) + '/' + suffix;
QDir d(path);
if (!d.exists() && create) {
d.mkpath(path);
}
//qDebug() << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path;
return path;
}
QString KoResourcePathsImpl::locateLocalInternal(const QString &type, const QString &filename, bool createDir)
{
QString path = saveLocationInternal(type, "", createDir);
//qDebug() << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path;
return path + '/' + filename;
}
Q_GLOBAL_STATIC(KoResourcePathsImpl, s_instance);
void KoResourcePaths::addResourceType(const char *type, const char *basetype,
const QString &relativeName, bool priority)
{
s_instance->addResourceTypeInternal(QString::fromLatin1(type), QString::fromLatin1(basetype), relativeName, priority);
}
void KoResourcePaths::addResourceDir(const char *type, const QString &dir, bool priority)
{
s_instance->addResourceDirInternal(QString::fromLatin1(type), dir, priority);
}
QString KoResourcePaths::findResource(const char *type, const QString &fileName)
{
return s_instance->findResourceInternal(QString::fromLatin1(type), fileName);
}
QStringList KoResourcePaths::findDirs(const char *type, const QString &reldir)
{
return s_instance->findDirsInternal(QString::fromLatin1(type), reldir);
}
QStringList KoResourcePaths::findAllResources(const char *type,
const QString &filter,
SearchOptions options)
{
return s_instance->findAllResourcesInternal(QString::fromLatin1(type), filter, options);
}
QStringList KoResourcePaths::resourceDirs(const char *type)
{
return s_instance->resourceDirsInternal(QString::fromLatin1(type));
}
QString KoResourcePaths::saveLocation(const char *type, const QString &suffix, bool create)
{
return s_instance->saveLocationInternal(QString::fromLatin1(type), suffix, create);
}
QString KoResourcePaths::locate(const char *type, const QString &filename)
{
return s_instance->findResourceInternal(QString::fromLatin1(type), filename);
}
QString KoResourcePaths::locateLocal(const char *type, const QString &filename, bool createDir)
{
return s_instance->locateLocalInternal(QString::fromLatin1(type), filename, createDir);
}
diff --git a/src/libs/widgets/KoUnitDoubleSpinBox.cpp b/src/libs/widgets/KoUnitDoubleSpinBox.cpp
index b7357da5..7c0e6061 100644
--- a/src/libs/widgets/KoUnitDoubleSpinBox.cpp
+++ b/src/libs/widgets/KoUnitDoubleSpinBox.cpp
@@ -1,220 +1,221 @@
/* This file is part of the KDE project
Copyright (C) 2002, Rob Buis(buis@kde.org)
Copyright (C) 2004, Nicolas GOUTTE <goutte@kde.org>
Copyright (C) 2007, Thomas Zander <zander@kde.org>
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 "KoUnitDoubleSpinBox.h"
#include <KoUnit.h>
#include <WidgetsDebug.h>
#include <klocalizedstring.h>
// #define DEBUG_VALIDATOR
// #define DEBUG_VALUEFROMTEXT
class Q_DECL_HIDDEN KoUnitDoubleSpinBox::Private
{
public:
Private(double low, double up, double step)
: lowerInPoints(low),
upperInPoints(up),
stepInPoints(step),
unit(KoUnit(KoUnit::Point))
{
}
double lowerInPoints; ///< lowest value in points
double upperInPoints; ///< highest value in points
double stepInPoints; ///< step in points
KoUnit unit;
};
KoUnitDoubleSpinBox::KoUnitDoubleSpinBox( QWidget *parent)
: QDoubleSpinBox( parent ),
d( new Private(-9999, 9999, 1))
{
QDoubleSpinBox::setDecimals( 2 );
//setAcceptLocalizedNumbers( true );
setUnit( KoUnit(KoUnit::Point) );
setAlignment( Qt::AlignRight );
connect(this, SIGNAL(valueChanged(double)), SLOT(privateValueChanged()));
}
KoUnitDoubleSpinBox::~KoUnitDoubleSpinBox()
{
delete d;
}
QValidator::State KoUnitDoubleSpinBox::validate(QString &input, int &pos) const
{
#ifdef DEBUG_VALIDATOR
debugWidgets <<"KoUnitDoubleSpinBox::validate :" << input <<" at" << pos;
#else
Q_UNUSED(pos);
#endif
QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end
const int res = input.indexOf( regexp );
if ( res == -1 )
{
// Nothing like an unit? The user is probably editing the unit
#ifdef DEBUG_VALIDATOR
debugWidgets <<"Intermediate (no unit)";
#endif
return QValidator::Intermediate;
}
// ### TODO: are all the QString::trimmed really necessary?
const QString number ( input.left( res ).trimmed() );
const QString unitName ( regexp.cap( 1 ).trimmed().toLower() );
#ifdef DEBUG_VALIDATOR
debugWidgets <<"Split:" << number <<":" << unitName <<":";
#endif
const double value = valueFromText( number );
double newVal = 0.0;
if (!qIsNaN(value)) {
bool ok;
const KoUnit unit = KoUnit::fromSymbol(unitName, &ok);
if ( ok )
newVal = unit.fromUserValue( value );
else
{
// Probably the user is trying to edit the unit
#ifdef DEBUG_VALIDATOR
debugWidgets <<"Intermediate (unknown unit)";
#endif
return QValidator::Intermediate;
}
}
else
{
warnWidgets << "Not a number: " << number;
return QValidator::Invalid;
}
newVal = KoUnit::ptToUnit( newVal, d->unit );
//input = textFromValue( newVal ); // don't overwrite for now; the effect is not exactly what I expect...
return QValidator::Acceptable;
}
void KoUnitDoubleSpinBox::changeValue( double val )
{
QDoubleSpinBox::setValue( d->unit.toUserValue( val ) );
// TODO: emit valueChanged ONLY if the value was out-of-bounds
// This will allow the 'user' dialog to set a dirty bool and ensure
// a proper value is getting saved.
}
void KoUnitDoubleSpinBox::privateValueChanged() {
emit valueChangedPt( value () );
}
void KoUnitDoubleSpinBox::setUnit( const KoUnit &unit )
{
if (unit == d->unit) return;
double oldvalue = d->unit.fromUserValue( QDoubleSpinBox::value() );
QDoubleSpinBox::setMinimum( unit.toUserValue( d->lowerInPoints ) );
QDoubleSpinBox::setMaximum( unit.toUserValue( d->upperInPoints ) );
qreal step = unit.toUserValue( d->stepInPoints );
if (unit.type() == KoUnit::Pixel) {
// limit the pixel step by 1.0
step = qMax(qreal(1.0), step);
}
QDoubleSpinBox::setSingleStep( step );
d->unit = unit;
QDoubleSpinBox::setValue( KoUnit::ptToUnit( oldvalue, unit ) );
setSuffix(unit.symbol().prepend(QLatin1Char(' ')));
}
double KoUnitDoubleSpinBox::value( ) const
{
return d->unit.fromUserValue( QDoubleSpinBox::value() );
}
void KoUnitDoubleSpinBox::setMinimum( double min )
{
d->lowerInPoints = min;
QDoubleSpinBox::setMinimum( d->unit.toUserValue( min ) );
}
void KoUnitDoubleSpinBox::setMaximum( double max )
{
d->upperInPoints = max;
QDoubleSpinBox::setMaximum( d->unit.toUserValue( max ) );
}
void KoUnitDoubleSpinBox::setLineStep( double step )
{
d->stepInPoints = KoUnit(KoUnit::Point).toUserValue(step);
QDoubleSpinBox::setSingleStep( step );
}
void KoUnitDoubleSpinBox::setLineStepPt( double step )
{
d->stepInPoints = step;
QDoubleSpinBox::setSingleStep( d->unit.toUserValue( step ) );
}
void KoUnitDoubleSpinBox::setMinMaxStep( double min, double max, double step )
{
setMinimum( min );
setMaximum( max );
setLineStepPt( step );
}
QString KoUnitDoubleSpinBox::textFromValue( double value ) const
{
//debugWidgets <<"textFromValue:" << QString::number( value, 'f', 12 ) <<" =>" << num;
//const QString num(QString("%1%2").arg(QLocale().toString(value, d->precision ), m_unit.symbol()));
//const QString num ( QString( "%1").arg( QLocale().toString( value, d->precision )) );
return QLocale().toString( value, 'f', decimals() );
}
double KoUnitDoubleSpinBox::valueFromText( const QString& str ) const
{
QString str2( str );
str2.remove(d->unit.symbol());
return QLocale().toDouble(str2);
// QString str2( str );
// /* KLocale::readNumber wants the thousand separator exactly at 1000.
// But when editing, it might be anywhere. So we need to remove it. */
// const QString sep( KGlobal::locale()->thousandsSeparator() );
// if ( !sep.isEmpty() )
// str2.remove( sep );
// str2.remove(d->unit.symbol());
// bool ok;
// const double dbl = KGlobal::locale()->readNumber( str2, &ok );
//#ifdef DEBUG_VALUEFROMTEXT
// if ( ok )
// debugWidgets <<"valueFromText:" << str <<": => :" << str2 <<": =>" << QString::number( dbl, 'f', 12 );
// else
// warnWidgets << "valueFromText error:" << str << ": => :" << str2 << ":";
//#endif
// return dbl;
}
diff --git a/src/libs/widgets/KoVBox.cpp b/src/libs/widgets/KoVBox.cpp
index 58adeade..b17c6aa7 100644
--- a/src/libs/widgets/KoVBox.cpp
+++ b/src/libs/widgets/KoVBox.cpp
@@ -1,97 +1,98 @@
/* This file is part of the KDE libraries
Copyright (C) 2005 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 "KoVBox.h"
#include <QEvent>
#include <QApplication>
#include <QVBoxLayout>
KoVBox::KoVBox(QWidget *parent)
: QFrame(parent),
d(0)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
setLayout(layout);
}
KoVBox::~KoVBox()
{
}
void KoVBox::childEvent(QChildEvent *event)
{
switch (event->type()) {
case QEvent::ChildAdded: {
QChildEvent *childEvent = static_cast<QChildEvent *>(event);
if (childEvent->child()->isWidgetType()) {
QWidget *widget = static_cast<QWidget *>(childEvent->child());
static_cast<QBoxLayout *>(layout())->addWidget(widget);
}
break;
}
case QEvent::ChildRemoved: {
QChildEvent *childEvent = static_cast<QChildEvent *>(event);
if (childEvent->child()->isWidgetType()) {
QWidget *widget = static_cast<QWidget *>(childEvent->child());
static_cast<QBoxLayout *>(layout())->removeWidget(widget);
}
break;
}
default:
break;
}
QFrame::childEvent(event);
}
QSize KoVBox::sizeHint() const
{
KoVBox *that = const_cast<KoVBox *>(this);
QApplication::sendPostedEvents(that, QEvent::ChildAdded);
return QFrame::sizeHint();
}
QSize KoVBox::minimumSizeHint() const
{
KoVBox *that = const_cast<KoVBox *>(this);
QApplication::sendPostedEvents(that, QEvent::ChildAdded);
return QFrame::minimumSizeHint();
}
void KoVBox::setSpacing(int spacing)
{
layout()->setSpacing(spacing);
}
void KoVBox::setStretchFactor(QWidget *widget, int stretch)
{
static_cast<QBoxLayout *>(layout())->setStretchFactor(widget, stretch);
}
void KoVBox::setMargin(int margin)
{
layout()->setMargin(margin);
}
diff --git a/src/libs/widgets/WidgetsDebug.cpp b/src/libs/widgets/WidgetsDebug.cpp
index f9fb36b0..01d58647 100644
--- a/src/libs/widgets/WidgetsDebug.cpp
+++ b/src/libs/widgets/WidgetsDebug.cpp
@@ -1,27 +1,28 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "WidgetsDebug.h"
const QLoggingCategory &WIDGETS_LOG() \
{
static const QLoggingCategory category("calligra.plan.lib.widgets");
return category;
}
diff --git a/src/libs/widgets/tests/KoProgressUpdater_test.cpp b/src/libs/widgets/tests/KoProgressUpdater_test.cpp
index 5195e2c7..ff859cf6 100644
--- a/src/libs/widgets/tests/KoProgressUpdater_test.cpp
+++ b/src/libs/widgets/tests/KoProgressUpdater_test.cpp
@@ -1,291 +1,292 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* 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 "KoProgressUpdater_test.h"
#include "KoProgressUpdater.h"
#include "KoUpdater.h"
#include <QThread>
#include <ThreadWeaver/ThreadWeaver>
#include <QTest>
// KoProgressUpdater signal timer has interval of 250 ms, see PROGRESSUPDATER_GUITIMERINTERVAL
// Also is the timer of type Qt::CoarseTimer, which "tr[ies] to keep accuracy within 5% of the desired interval",
// so wait a little longer
#define WAIT_FOR_PROGRESSUPDATER_UI_UPDATES 275
class TestWeaverJob : public ThreadWeaver::Job
{
public:
TestWeaverJob( QPointer<KoUpdater> updater, int steps = 10 )
: ThreadWeaver::Job()
, m_updater(updater)
, m_steps(steps)
{
}
void run(ThreadWeaver::JobPointer, ThreadWeaver::Thread *)
{
for (int i = 1; i < m_steps + 1; ++i) {
for (int j = 1; j < 10000; ++j){}
m_updater->setProgress((100 / m_steps) * i);
if ( m_updater->interrupted() ) {
m_updater->setProgress(100);
return;
}
}
m_updater->setProgress(100);
}
protected:
QPointer<KoUpdater> m_updater;
int m_steps;
};
class TestJob : public QThread {
Q_OBJECT
public:
TestJob( QPointer<KoUpdater> updater, int steps = 10 )
: QThread()
, m_updater( updater )
, m_steps( steps )
{
}
virtual void run()
{
for (int i = 1; i < m_steps + 1; ++i) {
sleep(1);
m_updater->setProgress((100 / m_steps) * i);
if ( m_updater->interrupted() ) {
m_updater->setProgress(100);
return;
}
}
m_updater->setProgress(100);
}
private:
QPointer<KoUpdater> m_updater;
int m_steps;
};
class TestProgressBar : public KoProgressProxy
{
public:
int min;
int max;
int value;
QString formatString;
TestProgressBar()
: min(0)
, max(0)
, value(0)
{
}
int maximum() const
{
return max;
}
void setValue( int v )
{
value = v;
}
void setRange( int minimum, int maximum )
{
min = minimum;
max = maximum;
}
void setFormat( const QString & format )
{
formatString = format;
}
};
void KoProgressUpdaterTest::testCreation()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
QPointer<KoUpdater> updater = pu.startSubtask();
QCOMPARE(bar.min, 0);
QCOMPARE(bar.max, 0);
QCOMPARE(bar.value, 0);
QVERIFY(bar.formatString.isNull());
pu.start();
QCOMPARE(bar.min, 0);
QCOMPARE(bar.max, 99);
QCOMPARE(bar.value, 0);
}
void KoProgressUpdaterTest::testSimpleProgress()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start();
QPointer<KoUpdater> updater = pu.startSubtask();
updater->setProgress(50);
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
QCOMPARE(bar.value, 50);
updater->setProgress(100);
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
QCOMPARE(bar.value, 100);
}
void KoProgressUpdaterTest::testSimpleThreadedProgress()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start();
QPointer<KoUpdater> u = pu.startSubtask();
TestJob t(u);
t.start();
while (!t.isFinished()) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
for (int i = 0; i < 10 && bar.value < 100; ++i) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run.
QCOMPARE(bar.value, 100);
}
void KoProgressUpdaterTest::testSubUpdaters()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start();
QPointer<KoUpdater> u1 = pu.startSubtask(4);
QPointer<KoUpdater> u2 = pu.startSubtask(6);
u1->setProgress(100);
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run.
QCOMPARE(bar.value, 40);
u2->setProgress(100);
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run.
QCOMPARE(bar.value, 100);
}
void KoProgressUpdaterTest::testThreadedSubUpdaters()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start();
QPointer<KoUpdater> u1 = pu.startSubtask(4);
QPointer<KoUpdater> u2= pu.startSubtask(6);
TestJob t1(u1, 4);
TestJob t2(u2, 6);
t1.start();
t2.start();
while ( t1.isRunning() || t2.isRunning() ) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
for (int i = 0; i < 10 && bar.value < 100; ++i) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
QCOMPARE(bar.value, 100);
}
void KoProgressUpdaterTest::testRecursiveProgress()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start();
QPointer<KoUpdater> u1 = pu.startSubtask();
KoProgressUpdater pu2(u1);
pu2.start();
QPointer<KoUpdater> u2 = pu2.startSubtask();
u2->setProgress(50);
u2->setProgress(100);
for (int i = 0; i < 10 && bar.value < 100; ++i) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
QCOMPARE(bar.value, 100);
}
void KoProgressUpdaterTest::testThreadedRecursiveProgress()
{
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start();
QPointer<KoUpdater> u1 = pu.startSubtask();
KoProgressUpdater pu2(u1);
pu2.start();
QPointer<KoUpdater> u2 = pu2.startSubtask();
TestJob t1(u2);
t1.start();
while (t1.isRunning() ) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
for (int i = 0; i < 10 && bar.value < 100; ++i) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
QCOMPARE(bar.value, 100);
}
void KoProgressUpdaterTest::testFromWeaver()
{
jobsdone = 0;
TestProgressBar bar;
KoProgressUpdater pu(&bar);
pu.start(10);
ThreadWeaver::Queue::instance()->setMaximumNumberOfThreads(4);
for (int i = 0; i < 10; ++i) {
QPointer<KoUpdater> up = pu.startSubtask();
ThreadWeaver::QObjectDecorator * job = new ThreadWeaver::QObjectDecorator(new TestWeaverJob(up, 10));
connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(jobDone(ThreadWeaver::JobPointer)) );
ThreadWeaver::Queue::instance()->enqueue(ThreadWeaver::make_job_raw(job));
}
while (!ThreadWeaver::Queue::instance()->isIdle()) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES);
}
ThreadWeaver::Queue::instance()->finish();
for (int i = 0; i < 10 && jobsdone < 10; ++i) {
QTest::qWait(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job.
}
QCOMPARE(jobsdone, 10);
}
void KoProgressUpdaterTest::jobDone(ThreadWeaver::JobPointer job)
{
Q_UNUSED(job);
++jobsdone;
}
QTEST_GUILESS_MAIN(KoProgressUpdaterTest)
#include "KoProgressUpdater_test.moc"
diff --git a/src/libs/widgets/tests/zoomcontroller_test.cpp b/src/libs/widgets/tests/zoomcontroller_test.cpp
index 23fe2e33..f36c9956 100644
--- a/src/libs/widgets/tests/zoomcontroller_test.cpp
+++ b/src/libs/widgets/tests/zoomcontroller_test.cpp
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "zoomcontroller_test.h"
#include <QTest>
#include <QCoreApplication>
#include <kactioncollection.h>
#include <WidgetsDebug.h>
#include "KoCanvasControllerWidget.h"
#include "KoZoomHandler.h"
#include "KoZoomController.h"
void zoomcontroller_test::testApi()
{
KoZoomHandler zoomHandler;
KoZoomController zoomController(new KoCanvasControllerWidget(0), &zoomHandler, new KActionCollection(this), KoZoomAction::AspectMode);
Q_UNUSED(zoomController);
}
QTEST_MAIN(zoomcontroller_test)
diff --git a/src/libs/widgets/tests/zoomhandler_test.cpp b/src/libs/widgets/tests/zoomhandler_test.cpp
index 7bdcf489..9cfe56ed 100644
--- a/src/libs/widgets/tests/zoomhandler_test.cpp
+++ b/src/libs/widgets/tests/zoomhandler_test.cpp
@@ -1,115 +1,116 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "zoomhandler_test.h"
#include <QTest>
#include <QCoreApplication>
#include <WidgetsDebug.h>
#include "KoZoomHandler.h"
#include "KoDpi.h"
#include "KoUnit.h"
void zoomhandler_test::testConstruction()
{
KoZoomHandler * zoomHandler = new KoZoomHandler();
QCOMPARE( zoomHandler->zoomFactorX(), 1. );
QCOMPARE( zoomHandler->zoomFactorY(), 1. );
QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->resolutionX() ), ( int )KoDpi::dpiX() );
QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->resolutionY() ), ( int )KoDpi::dpiY() );
QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->zoomedResolutionX() ), ( int )KoDpi::dpiX() );
QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->zoomedResolutionY() ), ( int )KoDpi::dpiY() );
QCOMPARE( zoomHandler->zoomMode(), KoZoomMode::ZOOM_CONSTANT );
QCOMPARE( zoomHandler->zoomInPercent(), 100 );
delete zoomHandler;
}
void zoomhandler_test::testApi()
{
KoZoomHandler zoomHandler;
qreal x, y;
zoomHandler.setResolution( 128, 129 );
QCOMPARE( zoomHandler.resolutionX(), 128. );
QCOMPARE( zoomHandler.resolutionY(), 129. );
zoomHandler.setZoomedResolution( 50, 60 );
QCOMPARE( zoomHandler.zoomedResolutionX(), 50.);
QCOMPARE( zoomHandler.zoomedResolutionY(), 60.);
zoomHandler.setZoom( 0.2 ); // is 20%
QCOMPARE( zoomHandler.zoomInPercent(), 20);
QCOMPARE( zoomHandler.resolutionX(), 128. );
QCOMPARE( zoomHandler.resolutionY(), 129. );
QCOMPARE( zoomHandler.zoomedResolutionX(), 25.6 );
QCOMPARE( zoomHandler.zoomedResolutionY(), 25.8 );
zoomHandler.zoom( &x, &y );
QVERIFY( x == 25.6 && y == 25.8 );
zoomHandler.setZoom( 1. );
zoomHandler.setZoom( 0.2 );
QCOMPARE( zoomHandler.zoomInPercent(), 20 );
QCOMPARE( zoomHandler.resolutionX(), 128. );
QCOMPARE( zoomHandler.resolutionY(), 129. );
QCOMPARE( zoomHandler.zoomedResolutionX(), 25.6 );
QCOMPARE( zoomHandler.zoomedResolutionY(), 25.8 );
zoomHandler.zoom( &x, &y );
QVERIFY( x == 25.6 && y == 25.8 );
zoomHandler.setZoomMode( KoZoomMode::ZOOM_CONSTANT );
QCOMPARE( zoomHandler.zoomMode(), KoZoomMode::ZOOM_CONSTANT );
zoomHandler.setZoomMode( KoZoomMode::ZOOM_WIDTH );
QCOMPARE( zoomHandler.zoomMode(), KoZoomMode::ZOOM_WIDTH );
zoomHandler.setZoomMode( KoZoomMode::ZOOM_PAGE );
QCOMPARE( zoomHandler.zoomMode(), KoZoomMode::ZOOM_PAGE );
}
void zoomhandler_test::testViewToDocument()
{
KoZoomHandler zoomHandler;
zoomHandler.setZoom( 1.0 );
zoomHandler.setDpi( 100, 100 );
QCOMPARE( zoomHandler.viewToDocument( QPointF( 0, 0 ) ), QPointF( 0, 0 ) );
// 100 view pixels are 72 postscript points at 100% zoom, 100ppi.
QCOMPARE( zoomHandler.viewToDocument( QRectF( 0, 0, 100, 100 ) ), QRectF( 0, 0, 72, 72 ) );
QCOMPARE( zoomHandler.viewToDocumentX( 0 ), 0. );
QCOMPARE( zoomHandler.viewToDocumentY( 0 ), 0. );
}
void zoomhandler_test::testDocumentToView()
{
KoZoomHandler zoomHandler;
zoomHandler.setZoom( 1.0 );
zoomHandler.setDpi( 100, 100 );
QCOMPARE( zoomHandler.documentToView( QPointF( 0,0 ) ), QPointF( 0, 0 ) );
QCOMPARE( zoomHandler.documentToView( QRectF( 0, 0, 72, 72 ) ), QRectF( 0, 0, 100, 100) );
QCOMPARE( zoomHandler.documentToViewX( 72 ), 100. );
QCOMPARE( zoomHandler.documentToViewY( 72 ), 100. );
}
QTEST_APPLESS_MAIN(zoomhandler_test)
diff --git a/src/libs/widgetutils/KoFileDialog.cpp b/src/libs/widgetutils/KoFileDialog.cpp
index dcf11ead..56b5c901 100644
--- a/src/libs/widgetutils/KoFileDialog.cpp
+++ b/src/libs/widgetutils/KoFileDialog.cpp
@@ -1,525 +1,526 @@
/* This file is part of the KDE project
Copyright (C) 2013 - 2014 Yue Liu <yue.liu@mail.com>
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 "KoFileDialog.h"
#include <QDebug>
#include <QFileDialog>
#include <QApplication>
#include <QImageReader>
#include <QClipboard>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <klocalizedstring.h>
#include <QUrl>
#include <QMimeDatabase>
#include <QMimeType>
class Q_DECL_HIDDEN KoFileDialog::Private
{
public:
Private(QWidget *parent_,
KoFileDialog::DialogType dialogType_,
const QString caption_,
const QString defaultDir_,
const QString dialogName_)
: parent(parent_)
, type(dialogType_)
, dialogName(dialogName_)
, caption(caption_)
, defaultDirectory(defaultDir_)
, filterList(QStringList())
, defaultFilter(QString())
, useStaticForNative(false)
, hideDetails(false)
, swapExtensionOrder(false)
{
// Force the native file dialogs on Windows. Except for KDE, the native file dialogs are only possible
// using the static methods. The Qt documentation is wrong here, if it means what it says " By default,
// the native file dialog is used unless you use a subclass of QFileDialog that contains the Q_OBJECT
// macro."
#ifdef Q_OS_WIN
useStaticForNative = true;
#endif
// Non-static KDE file is broken when called with QFileDialog::AcceptSave:
// then the directory above defaultdir is opened, and defaultdir is given as the default file name...
//
// So: in X11, use static methods inside KDE, which give working native dialogs, but non-static outside
// KDE, which gives working Qt dialogs.
//
// Only show the GTK dialog in Gnome, where people deserve it
#ifdef HAVE_X11
if (qgetenv("KDE_FULL_SESSION").size() > 0) {
useStaticForNative = true;
}
if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") {
useStaticForNative = true;
QClipboard *cb = QApplication::clipboard();
cb->blockSignals(true);
swapExtensionOrder = true;
}
#endif
}
~Private()
{
if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") {
useStaticForNative = true;
QClipboard *cb = QApplication::clipboard();
cb->blockSignals(false);
}
}
QWidget *parent;
KoFileDialog::DialogType type;
QString dialogName;
QString caption;
QString defaultDirectory;
QStringList filterList;
QString defaultFilter;
QScopedPointer<QFileDialog> fileDialog;
QMimeType mimeType;
bool useStaticForNative;
bool hideDetails;
bool swapExtensionOrder;
};
KoFileDialog::KoFileDialog(QWidget *parent,
KoFileDialog::DialogType type,
const QString &dialogName)
: d(new Private(parent, type, "", getUsedDir(dialogName), dialogName))
{
}
KoFileDialog::~KoFileDialog()
{
delete d;
}
void KoFileDialog::setCaption(const QString &caption)
{
d->caption = caption;
}
void KoFileDialog::setDefaultDir(const QString &defaultDir, bool override)
{
if (override || d->defaultDirectory.isEmpty()) {
QFileInfo f(defaultDir);
d->defaultDirectory = f.absoluteFilePath();
} else {
QFileInfo df(d->defaultDirectory);
if (!df.isFile()) {
QFileInfo f(defaultDir);
if (df.exists()) {
df.setFile(df.filePath(), f.fileName());
d->defaultDirectory = df.absoluteFilePath();
} else {
QFileInfo f(defaultDir);
d->defaultDirectory = f.absoluteFilePath();
}
}
}
}
void KoFileDialog::setOverrideDir(const QString &overrideDir)
{
d->defaultDirectory = overrideDir;
}
void KoFileDialog::setImageFilters()
{
QStringList imageMimeTypes;
foreach(const QByteArray &mimeType, QImageReader::supportedMimeTypes()) {
imageMimeTypes << QLatin1String(mimeType);
}
setMimeTypeFilters(imageMimeTypes);
}
void KoFileDialog::setNameFilter(const QString &filter)
{
d->filterList.clear();
if (d->type == KoFileDialog::SaveFile) {
QStringList mimeList;
d->filterList << splitNameFilter(filter, &mimeList);
d->defaultFilter = d->filterList.first();
}
else {
d->filterList << filter;
}
}
void KoFileDialog::setNameFilters(const QStringList &filterList,
QString defaultFilter)
{
d->filterList.clear();
if (d->type == KoFileDialog::SaveFile) {
QStringList mimeList;
foreach(const QString &filter, filterList) {
d->filterList << splitNameFilter(filter, &mimeList);
}
if (!defaultFilter.isEmpty()) {
mimeList.clear();
QStringList defaultFilters = splitNameFilter(defaultFilter, &mimeList);
if (defaultFilters.size() > 0) {
defaultFilter = defaultFilters.first();
}
}
}
else {
d->filterList = filterList;
}
d->defaultFilter = defaultFilter;
}
void KoFileDialog::setMimeTypeFilters(const QStringList &filterList,
QString defaultFilter)
{
d->filterList = getFilterStringListFromMime(filterList, true);
if (!defaultFilter.isEmpty()) {
QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultFilter, false);
if (defaultFilters.size() > 0) {
defaultFilter = defaultFilters.first();
}
}
d->defaultFilter = defaultFilter;
}
void KoFileDialog::setHideNameFilterDetailsOption()
{
d->hideDetails = true;
}
QStringList KoFileDialog::nameFilters() const
{
return d->filterList;
}
QString KoFileDialog::selectedNameFilter() const
{
if (!d->useStaticForNative) {
return d->fileDialog->selectedNameFilter();
}
else {
return d->defaultFilter;
}
}
QString KoFileDialog::selectedMimeType() const
{
if (d->mimeType.isValid()) {
return d->mimeType.name();
}
else {
return "";
}
}
void KoFileDialog::createFileDialog()
{
qInfo()<<Q_FUNC_INFO<<d->type;
d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory));
if (d->type == SaveFile) {
d->fileDialog->setAcceptMode(QFileDialog::AcceptSave);
d->fileDialog->setFileMode(QFileDialog::AnyFile);
}
else { // open / import
d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen);
if (d->type == ImportDirectory
|| d->type == OpenDirectory)
{
d->fileDialog->setFileMode(QFileDialog::Directory);
d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true);
}
else { // open / import file(s)
if (d->type == OpenFile
|| d->type == ImportFile)
{
d->fileDialog->setFileMode(QFileDialog::ExistingFile);
}
else { // files
d->fileDialog->setFileMode(QFileDialog::ExistingFiles);
}
}
}
d->fileDialog->setNameFilters(d->filterList);
if (!d->defaultFilter.isEmpty()) {
d->fileDialog->selectNameFilter(d->defaultFilter);
}
if (d->type == ImportDirectory ||
d->type == ImportFile || d->type == ImportFiles ||
d->type == SaveFile) {
d->fileDialog->setWindowModality(Qt::WindowModal);
}
if (d->hideDetails) {
d->fileDialog->setOption(QFileDialog::HideNameFilterDetails);
}
connect(d->fileDialog.data(), &QFileDialog::filterSelected, this, &KoFileDialog::filterSelected);
connect(d->fileDialog.data(), &QDialog::finished, this, &KoFileDialog::finished);
}
QString KoFileDialog::filename()
{
qInfo()<<Q_FUNC_INFO<<d->useStaticForNative;
QString url;
if (!d->useStaticForNative) {
if (!d->fileDialog) {
createFileDialog();
}
if (d->fileDialog->exec() == QDialog::Accepted) {
url = d->fileDialog->selectedFiles().first();
}
}
else {
switch (d->type) {
case OpenFile:
{
url = QFileDialog::getOpenFileName(d->parent,
d->caption,
d->defaultDirectory,
d->filterList.join(";;"),
&d->defaultFilter);
break;
}
case OpenDirectory:
{
url = QFileDialog::getExistingDirectory(d->parent,
d->caption,
d->defaultDirectory,
QFileDialog::ShowDirsOnly);
break;
}
case ImportFile:
{
url = QFileDialog::getOpenFileName(d->parent,
d->caption,
d->defaultDirectory,
d->filterList.join(";;"),
&d->defaultFilter);
break;
}
case ImportDirectory:
{
url = QFileDialog::getExistingDirectory(d->parent,
d->caption,
d->defaultDirectory,
QFileDialog::ShowDirsOnly);
break;
}
case SaveFile:
{
url = QFileDialog::getSaveFileName(d->parent,
d->caption,
d->defaultDirectory,
d->filterList.join(";;"),
&d->defaultFilter);
break;
}
default:
;
}
}
if (!url.isEmpty()) {
if (d->type == SaveFile && QFileInfo(url).suffix().isEmpty()) {
int start = d->defaultFilter.lastIndexOf("*.") + 1;
int end = d->defaultFilter.lastIndexOf(" )");
int n = end - start;
QString extension = d->defaultFilter.mid(start, n);
url.append(extension);
}
QMimeDatabase db;
d->mimeType = db.mimeTypeForFile(url);
saveUsedDir(url, d->dialogName);
}
return url;
}
QStringList KoFileDialog::filenames()
{
QStringList urls;
if (!d->useStaticForNative) {
if (!d->fileDialog) {
createFileDialog();
}
if (d->fileDialog->exec() == QDialog::Accepted) {
urls = d->fileDialog->selectedFiles();
}
}
else {
switch (d->type) {
case OpenFiles:
case ImportFiles:
{
urls = QFileDialog::getOpenFileNames(d->parent,
d->caption,
d->defaultDirectory,
d->filterList.join(";;"),
&d->defaultFilter);
break;
}
default:
;
}
}
if (urls.size() > 0) {
saveUsedDir(urls.first(), d->dialogName);
}
return urls;
}
void KoFileDialog::filterSelected(const QString &filter)
{
// "Windows BMP image ( *.bmp )";
int start = filter.lastIndexOf("*.") + 2;
int end = filter.lastIndexOf(" )");
int n = end - start;
QString extension = filter.mid(start, n);
d->defaultFilter = filter;
d->fileDialog->setDefaultSuffix(extension);
}
QStringList KoFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList)
{
Q_ASSERT(mimeList);
QStringList filters;
QString description;
if (nameFilter.contains("(")) {
description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed();
}
QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts );
foreach(QString entry, entries) {
entry = entry.remove("*");
entry = entry.remove(")");
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName("bla" + entry);
if (mime.name() != "application/octet-stream") {
if (!mimeList->contains(mime.name())) {
mimeList->append(mime.name());
filters.append(mime.comment() + " ( *" + entry + " )");
}
}
else {
filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )");
}
}
return filters;
}
const QStringList KoFileDialog::getFilterStringListFromMime(const QStringList &mimeList,
bool withAllSupportedEntry)
{
QStringList mimeSeen;
QStringList ret;
if (withAllSupportedEntry) {
ret << QString();
}
for (QStringList::ConstIterator
it = mimeList.begin(); it != mimeList.end(); ++it) {
QMimeDatabase db;
QMimeType mimeType = db.mimeTypeForName(*it);
if (!mimeType.isValid()) {
continue;
}
if (!mimeSeen.contains(mimeType.name())) {
QString oneFilter;
QStringList patterns = mimeType.globPatterns();
QStringList::ConstIterator jt;
for (jt = patterns.constBegin(); jt != patterns.constEnd(); ++jt) {
if (d->swapExtensionOrder) {
oneFilter.prepend(*jt + " ");
if (withAllSupportedEntry) {
ret[0].prepend(*jt + " ");
}
}
else {
oneFilter.append(*jt + " ");
if (withAllSupportedEntry) {
ret[0].append(*jt + " ");
}
}
}
oneFilter = mimeType.comment() + " ( " + oneFilter + ")";
ret << oneFilter;
mimeSeen << mimeType.name();
}
}
if (withAllSupportedEntry) {
ret[0] = i18n("All supported formats") + " ( " + ret[0] + (")");
}
return ret;
}
QString KoFileDialog::getUsedDir(const QString &dialogName)
{
if (dialogName.isEmpty()) return "";
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString dir = group.readEntry(dialogName);
return dir;
}
void KoFileDialog::saveUsedDir(const QString &fileName,
const QString &dialogName)
{
if (dialogName.isEmpty()) return;
QFileInfo fileInfo(fileName);
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
group.writeEntry(dialogName, fileInfo.absolutePath());
}
void KoFileDialog::setVisible(bool value)
{
d->fileDialog.data()->setVisible(value);
if (value) {
d->fileDialog.data()->raise();
d->fileDialog.data()->activateWindow();
}
}
diff --git a/src/libs/widgetutils/KoProgressBar.cpp b/src/libs/widgetutils/KoProgressBar.cpp
index 8f14da2a..2a685ca3 100644
--- a/src/libs/widgetutils/KoProgressBar.cpp
+++ b/src/libs/widgetutils/KoProgressBar.cpp
@@ -1,56 +1,57 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2007
*
* 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 "KoProgressBar.h"
KoProgressBar::KoProgressBar(QWidget *parent)
: QProgressBar(parent)
{
}
KoProgressBar::~KoProgressBar()
{
}
int KoProgressBar::maximum() const
{
return QProgressBar::maximum();
}
void KoProgressBar::setValue(int value)
{
QProgressBar::setValue(value);
if (value >= minimum() && value < maximum()) {
setVisible( true );
} else {
emit done();
setVisible( false );
}
}
void KoProgressBar::setRange(int minimum, int maximum)
{
QProgressBar::setRange(minimum, maximum);
}
void KoProgressBar::setFormat(const QString &format)
{
QProgressBar::setFormat(format);
}
diff --git a/src/libs/widgetutils/KoProgressUpdater.cpp b/src/libs/widgetutils/KoProgressUpdater.cpp
index 25e1d25b..13324c9b 100644
--- a/src/libs/widgetutils/KoProgressUpdater.cpp
+++ b/src/libs/widgetutils/KoProgressUpdater.cpp
@@ -1,234 +1,235 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoProgressUpdater.h"
#include <QApplication>
#include <QString>
#include <QTextStream>
#include <QTimer>
#include "KoUpdaterPrivate_p.h"
#include "KoUpdater.h"
#include "KoProgressProxy.h"
// 4 updates per second should be enough
#define PROGRESSUPDATER_GUITIMERINTERVAL 250
class Q_DECL_HIDDEN KoProgressUpdater::Private
{
public:
Private(KoProgressUpdater *_parent, KoProgressProxy *p, Mode _mode,
QTextStream *output_ = 0)
: parent(_parent)
, progressBar(p)
, mode(_mode)
, totalWeight(0)
, currentProgress(0)
, updated(false)
, output(output_)
, updateGuiTimer(_parent)
, canceled(false)
{
}
KoProgressUpdater *parent;
KoProgressProxy *progressBar;
Mode mode;
int totalWeight;
int currentProgress;
bool updated; // is true whenever the progress needs to be recomputed
QTextStream *output;
QTimer updateGuiTimer; // fires regularly to update the progress bar widget
QList<QPointer<KoUpdaterPrivate> > subtasks;
QList<QPointer<KoUpdater> > subTaskWrappers; // We delete these
QTime referenceTime;
static void logEvents(QTextStream& out, KoProgressUpdater::Private *updater,
const QTime& startTime, const QString& prefix);
bool canceled;
};
// NOTE: do not make the KoProgressUpdater object part of the QObject
// hierarchy. Do not make KoProgressProxy its parent (note that KoProgressProxy
// is not necessarily castable to QObject ). This prevents proper functioning
// of progress reporting in multi-threaded environments.
KoProgressUpdater::KoProgressUpdater(KoProgressProxy *progressBar,
Mode mode, QTextStream *output)
: d (new Private(this, progressBar, mode, output))
{
Q_ASSERT(d->progressBar);
connect(&d->updateGuiTimer, &QTimer::timeout, this, &KoProgressUpdater::updateUi, Qt::QueuedConnection);
}
KoProgressUpdater::~KoProgressUpdater()
{
if (d->output) {
Private::logEvents(*d->output, d, referenceTime(), "");
}
d->progressBar->setValue(d->progressBar->maximum());
// make sure to stop the timer to avoid accessing
// the data we are going to delete right now
d->updateGuiTimer.stop();
qDeleteAll(d->subtasks);
d->subtasks.clear();
qDeleteAll(d->subTaskWrappers);
d->subTaskWrappers.clear();
delete d;
}
void KoProgressUpdater::setReferenceTime(const QTime &referenceTime)
{
d->referenceTime = referenceTime;
}
QTime KoProgressUpdater::referenceTime() const
{
return d->referenceTime;
}
void KoProgressUpdater::start(int range, const QString &text)
{
d->updateGuiTimer.start(PROGRESSUPDATER_GUITIMERINTERVAL);
qDeleteAll(d->subtasks);
d->subtasks.clear();
qDeleteAll(d->subTaskWrappers);
d->subTaskWrappers.clear();
d->progressBar->setRange(0, range-1);
d->progressBar->setValue(0);
if(!text.isEmpty()) {
d->progressBar->setFormat(text);
}
d->totalWeight = 0;
d->canceled = false;
}
QPointer<KoUpdater> KoProgressUpdater::startSubtask(int weight,
const QString &name)
{
KoUpdaterPrivate *p = new KoUpdaterPrivate(this, weight, name);
d->totalWeight += weight;
d->subtasks.append(p);
connect(p, &KoUpdaterPrivate::sigUpdated, this, &KoProgressUpdater::update);
QPointer<KoUpdater> updater = new KoUpdater(p);
d->subTaskWrappers.append(updater);
if (!d->updateGuiTimer.isActive()) {
// we maybe need to restart the timer if it was stopped in updateUi() cause
// other sub-tasks created before this one finished already.
d->updateGuiTimer.start(PROGRESSUPDATER_GUITIMERINTERVAL);
}
return updater;
}
void KoProgressUpdater::cancel()
{
foreach(KoUpdaterPrivate *updater, d->subtasks) {
updater->setProgress(100);
updater->interrupt();
}
d->canceled = true;
updateUi();
}
void KoProgressUpdater::update()
{
d->updated = true;
if (d->mode == Unthreaded) {
qApp->processEvents();
}
}
void KoProgressUpdater::updateUi()
{
// This function runs in the app main thread. All the progress
// updates arrive at the KoUpdaterPrivate instances through
// queued connections, so until we relinquish control to the
// event loop, the progress values cannot change, and that
// won't happen until we return from this function (which is
// triggered by a timer)
if (d->updated) {
int totalProgress = 0;
foreach(QPointer<KoUpdaterPrivate> updater, d->subtasks) {
if (updater->interrupted()) {
d->currentProgress = -1;
return;
}
int progress = updater->progress();
if (progress > 100 || progress < 0) {
progress = updater->progress();
}
totalProgress += progress *updater->weight();
}
d->currentProgress = totalProgress / d->totalWeight;
d->updated = false;
}
if (d->currentProgress == -1) {
d->progressBar->setValue( d->progressBar->maximum() );
// should we hide the progressbar after a little while?
return;
}
if (d->currentProgress >= d->progressBar->maximum()) {
// we're done
d->updateGuiTimer.stop(); // 10 updates/second should be enough?
}
d->progressBar->setValue(d->currentProgress);
}
bool KoProgressUpdater::interrupted() const
{
return d->canceled;
}
bool KoProgressUpdater::hasOutput() const
{
return d->output != 0;
}
void KoProgressUpdater::Private::logEvents(QTextStream& out,
KoProgressUpdater::Private *updater,
const QTime& startTime,
const QString& prefix) {
// initial implementation: write out the names of all events
foreach (QPointer<KoUpdaterPrivate> p, updater->subtasks) {
if (!p) continue;
foreach (const KoUpdaterPrivate::TimePoint &tp, p->getPoints()) {
out << prefix+p->objectName() << '\t'
<< startTime.msecsTo(tp.time) << '\t' << tp.value << endl;
}
}
}
diff --git a/src/libs/widgetutils/KoUpdater.cpp b/src/libs/widgetutils/KoUpdater.cpp
index c3aa4640..92676d77 100644
--- a/src/libs/widgetutils/KoUpdater.cpp
+++ b/src/libs/widgetutils/KoUpdater.cpp
@@ -1,101 +1,102 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoUpdater.h"
#include "KoUpdaterPrivate_p.h"
KoUpdater::KoUpdater(KoUpdaterPrivate *p)
: QObject(p),
m_progressPercent(0)
{
d = p;
Q_ASSERT(p);
Q_ASSERT(!d.isNull());
connect( this, &KoUpdater::sigCancel, d.data(), &KoUpdaterPrivate::cancel );
connect( this, &KoUpdater::sigProgress, d.data(), &KoUpdaterPrivate::setProgress );
connect( d.data(), &KoUpdaterPrivate::sigInterrupted, this, &KoUpdater::interrupt );
setRange(0, 100);
m_interrupted = false;
}
void KoUpdater::cancel()
{
emit sigCancel();
}
void KoUpdater::setProgress(int percent)
{
if (m_progressPercent >= percent) {
return;
}
d->addPoint(percent);
m_progressPercent = percent;
emit sigProgress( percent );
}
int KoUpdater::progress() const
{
return m_progressPercent;
}
bool KoUpdater::interrupted() const
{
return m_interrupted;
}
int KoUpdater::maximum() const
{
return 100;
}
void KoUpdater::setValue( int value )
{
if ( value < min ) value = min;
if ( value > max ) value = max;
// Go from range to percent
if (range == 0) return;
setProgress( ((100 * value ) / range) + 1 );
}
void KoUpdater::setRange( int minimum, int maximum )
{
min = minimum - 1;
max = maximum;
range = max - min;
}
void KoUpdater::setFormat( const QString & format )
{
Q_UNUSED(format);
// XXX: Do nothing
}
void KoUpdater::interrupt()
{
m_interrupted = true;
}
diff --git a/src/libs/widgetutils/KoUpdaterPrivate_p.cpp b/src/libs/widgetutils/KoUpdaterPrivate_p.cpp
index ca499cb0..971c665f 100644
--- a/src/libs/widgetutils/KoUpdaterPrivate_p.cpp
+++ b/src/libs/widgetutils/KoUpdaterPrivate_p.cpp
@@ -1,46 +1,47 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoUpdaterPrivate_p.h"
KoUpdaterPrivate::~KoUpdaterPrivate()
{
interrupt();
}
void KoUpdaterPrivate::cancel()
{
m_parent->cancel();
}
void KoUpdaterPrivate::interrupt()
{
m_interrupted = true;
emit sigInterrupted();
}
void KoUpdaterPrivate::setProgress(int percent)
{
if(m_progress >= percent) {
return;
}
m_progress = percent;
emit sigUpdated();
}
diff --git a/src/libs/widgetutils/tests/KoFileDialogTester.cpp b/src/libs/widgetutils/tests/KoFileDialogTester.cpp
index cd449813..5aa27782 100644
--- a/src/libs/widgetutils/tests/KoFileDialogTester.cpp
+++ b/src/libs/widgetutils/tests/KoFileDialogTester.cpp
@@ -1,338 +1,339 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2014
*
* 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 "KoFileDialogTester.h"
#include "ui_KoFileDialogTester.h"
#include <QApplication>
#include <QStandardPaths>
#include <QListWidget>
#include <QCheckBox>
#include <QRadioButton>
#include <KoFileDialog.h>
KoFileDialogTester::KoFileDialogTester(QWidget *parent) :
QWidget(parent),
ui(new Ui::KoFileDialogTester)
{
ui->setupUi(this);
connect(ui->bnOpenFile, SIGNAL(clicked()), SLOT(testOpenFile()));
connect(ui->bnOpenFiles, SIGNAL(clicked()), SLOT(testOpenFiles()));
connect(ui->bnOpenDirectory, SIGNAL(clicked()), SLOT(testOpenDirectory()));
connect(ui->bnImportFile, SIGNAL(clicked()), SLOT(testImportFile()));
connect(ui->bnImportFiles, SIGNAL(clicked()), SLOT(testImportFiles()));
connect(ui->bnImportDirectory, SIGNAL(clicked()), SLOT(testImportDirectory()));
connect(ui->bnSaveFile, SIGNAL(clicked()), SLOT(testSaveFile()));
m_nameFilters << "Documents (*.odt *.doc *.txt)"
<< "Images (*.png *.jpg *.jpeg)"
<< "Presentations (*.ppt *.odp)"
<< "Patterns (*.pat *.jpg *.gif *.png *.tif *.xpm *.bmp)"
<< "Palettes (*.gpl *.pal *.act *.aco *.colors)";
m_mimeFilter = QStringList()
<< "image/x-exr" << "image/openraster" << "image/x-tga" << "image/vnd.adobe.photoshop"
<< "image/x-xcf" << "image/x-portable-pixmap" << "image/x-portable-graymap"
<< "image/x-portable-bitmap" << "image/png" << "image/jp2"
<< "image/tiff" << "application/vnd.oasis.opendocument.graphics"
<< "application/pdf" << "image/jpeg" << "image/bmp" << "image/x-xpixmap"
<< "image/gif" << "image/x-xbitmap"
<< "image/x-adobe-dng" << "image/x-xfig" << "image/svg+xml" << "image/svg+xml-compressed"
<< "image/x-eps" << "image/eps" << "application/eps" << "application/x-eps" << "application/postscript"
<< "image/x-wmf" << "application/x-karbon"
<< "image/tiff" << "application/vnd.oasis.opendocument.graphics"
;
}
KoFileDialogTester::~KoFileDialogTester()
{
delete ui;
}
void KoFileDialogTester::testOpenFile()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::OpenFile, ui->txtUniqueKey->text());
dlg.setCaption("Testing: OpenFile");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QString url = dlg.filename();
ui->listResults->addItem(url);
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
void KoFileDialogTester::testOpenFiles()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::OpenFiles, ui->txtUniqueKey->text());
dlg.setCaption("Testing: OpenFile");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QStringList urls = dlg.filenames();
foreach(const QString &url, urls) {
ui->listResults->addItem(url);
}
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
void KoFileDialogTester::testOpenDirectory()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::OpenDirectory, ui->txtUniqueKey->text());
dlg.setCaption("Testing: OpenDirectory");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QString url = dlg.filename();
ui->listResults->addItem(url);
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
void KoFileDialogTester::testImportFile()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::ImportFile, ui->txtUniqueKey->text());
dlg.setCaption("Testing: ImportFile");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QString url = dlg.filename();
ui->listResults->addItem(url);
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
void KoFileDialogTester::testImportFiles()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::ImportFiles, ui->txtUniqueKey->text());
dlg.setCaption("Testing: ImportFiles");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QStringList urls = dlg.filenames(); foreach(const QString &url, urls) {
ui->listResults->addItem(url);
}
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
void KoFileDialogTester::testImportDirectory()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::ImportDirectory, ui->txtUniqueKey->text());
dlg.setCaption("Testing: Import Directory");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QString url = dlg.filename();
ui->listResults->addItem(url);
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
void KoFileDialogTester::testSaveFile()
{
ui->listResults->clear();
KoFileDialog dlg(this, KoFileDialog::SaveFile, ui->txtUniqueKey->text());
dlg.setCaption("Testing: SaveFile");
dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
if (ui->radioName->isChecked()) {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setNameFilters(m_nameFilters, m_nameFilters.last());
}
else {
dlg.setNameFilters(m_nameFilters);
}
}
else {
if (ui->chkSetDefaultFilter->isChecked()) {
dlg.setMimeTypeFilters(m_mimeFilter, m_mimeFilter[4]);
}
else {
dlg.setMimeTypeFilters(m_mimeFilter);
}
}
if (ui->chkHideNameFilterDetailsOption->isChecked()) {
dlg.setHideNameFilterDetailsOption();
}
QString url = dlg.filename();
ui->listResults->addItem(url);
ui->lblMime->setText(dlg.selectedMimeType());
ui->txtFilter->setText(dlg.selectedNameFilter());
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
KoFileDialogTester w;
w.show();
return a.exec();
}
diff --git a/src/libs/widgetutils/tests/KoPropertiesTest.cpp b/src/libs/widgetutils/tests/KoPropertiesTest.cpp
index 5a595204..0d3ac9ce 100644
--- a/src/libs/widgetutils/tests/KoPropertiesTest.cpp
+++ b/src/libs/widgetutils/tests/KoPropertiesTest.cpp
@@ -1,144 +1,145 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "KoPropertiesTest.h"
#include <QTest>
#include <KoProperties.h>
void KoPropertiesTest::testDeserialization()
{
QString test;
KoProperties props;
props.setProperty("bla", "bla");
QVERIFY(!props.load(test));
QVERIFY(!props.isEmpty());
QVERIFY(props.stringProperty("bla") == "bla");
test = "<bla>asdsadasjk</bla>";
QVERIFY(props.load(test));
QVERIFY(props.isEmpty());
props.setProperty("bla", "bla");
test = "<bla>asdsadasjk</";
QVERIFY(!props.load(test));
QVERIFY(!props.isEmpty());
QVERIFY(props.stringProperty("bla") == "bla");
}
void KoPropertiesTest::testRoundTrip()
{
KoProperties props;
props.setProperty("string", "string");
props.setProperty("xmlstring", "<xml>bla</xml>");
props.setProperty("xmlstring2", "<xml>&adsa</xml>");
props.setProperty("cdata", "<![CDATA[blabla]]>");
props.setProperty("int", 10);
props.setProperty("bool", false);
props.setProperty("qreal", 1.38);
QString stored = props.store("KoPropertiesTest");
KoProperties restored;
restored.load(stored);
QVERIFY(restored.stringProperty("string") == "string");
QVERIFY(restored.stringProperty("xmlstring") == "<xml>bla</xml>");
QVERIFY(restored.stringProperty("xmlstring2") == "<xml>&adsa</xml>");
QVERIFY(restored.stringProperty("cdata") == "<![CDATA[blabla]]>");
QVERIFY(restored.intProperty("int") == 10);
QVERIFY(restored.boolProperty("bool") == false);
QVERIFY(restored.doubleProperty("qreal") == 1.38);
}
void KoPropertiesTest::testProperties()
{
KoProperties props;
QVERIFY(props.isEmpty());
QString visible = "visible";
QVERIFY(!props.value(visible).isValid());
props.setProperty("visible", "bla");
QVERIFY(props.value("visible") == "bla");
QVERIFY(props.stringProperty("visible", "blabla") == "bla");
props.setProperty("bool", true);
QVERIFY(props.boolProperty("bool", false) == true);
props.setProperty("bool", false);
QVERIFY(props.boolProperty("bool", true) == false);
props.setProperty("qreal", 1.0);
QVERIFY(props.doubleProperty("qreal", 2.0) == 1.0);
props.setProperty("qreal", 2.0);
QVERIFY(props.doubleProperty("qreal", 1.0) == 2.0);
props.setProperty("int", 1);
QVERIFY(props.intProperty("int", 2) == 1);
props.setProperty("int", 2);
QVERIFY(props.intProperty("int", 1) == 2);
QVariant v;
QVERIFY(props.property("sdsadsakldjsajd", v) == false);
QVERIFY(!v.isValid());
QVERIFY(props.property("visible", v) == true);
QVERIFY(v.isValid());
QVERIFY(v == "bla");
QVERIFY(!props.isEmpty());
QVERIFY(props.contains("visible"));
QVERIFY(!props.contains("adsajkdsakj dsaieqwewqoie"));
QVERIFY(props.contains(visible));
int count = 0;
QMapIterator<QString, QVariant> iter = props.propertyIterator();
while (iter.hasNext()) {
iter.next();
count++;
}
QVERIFY(count == 4);
}
bool checkProps(const KoProperties & props)
{
return (props.value("bla") == 1);
}
void KoPropertiesTest::testPassAround()
{
KoProperties props;
props.setProperty("bla", 1);
QVERIFY(checkProps(props));
KoProperties props2 = props;
QVERIFY(checkProps(props2));
KoProperties props3(props);
checkProps(props3);
props3.setProperty("bla", 3);
QVERIFY(props3.value("bla") == 3);
QVERIFY(checkProps(props));
QVERIFY(checkProps(props2));
}
QTEST_GUILESS_MAIN(KoPropertiesTest)
diff --git a/src/main.cpp b/src/main.cpp
index 275cb6a3..b5f0c204 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,57 +1,58 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
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 "kptaboutdata.h"
#include "kptmaindocument.h"
#include <KoApplication.h>
#include <Calligra2Migration.h>
#include <QApplication>
#include <QLoggingCategory>
extern "C" Q_DECL_EXPORT int kdemain( int argc, char **argv )
{
/**
* Disable debug output by default, only log warnings.
* Debug logs can be controlled by the environment variable QT_LOGGING_RULES.
*
* For example, to get full debug output, run the following:
* QT_LOGGING_RULES="calligra.*=true" calligraplan
*
* See: http://doc.qt.io/qt-5/qloggingcategory.html
*/
QLoggingCategory::setFilterRules("calligra.*.debug=false\n"
"calligra.*.warning=true");
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
KoApplication app(PLAN_MIME_TYPE, QStringLiteral("calligraplan"), KPlato::newAboutData, argc, argv);
// Migrate data from kde4 to kf5 locations
Calligra2Migration m("calligraplan", "plan");
m.setConfigFiles(QStringList() << QStringLiteral("planrc"));
m.setUiFiles(QStringList() << QStringLiteral("plan.rc") << QStringLiteral("plan_readonly.rc"));
m.migrate();
if (!app.start()) {
return 1;
}
return app.exec();
}
diff --git a/src/plugins/filters/icalendar/export/icalendarexport.cpp b/src/plugins/filters/icalendar/export/icalendarexport.cpp
index 757d693a..cd755982 100644
--- a/src/plugins/filters/icalendar/export/icalendarexport.cpp
+++ b/src/plugins/filters/icalendar/export/icalendarexport.cpp
@@ -1,192 +1,193 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "icalendarexport.h"
#include "config.h"
#include <kptmaindocument.h>
#include <kpttask.h>
#include <kptnode.h>
#include <kptresource.h>
#include <kptdocuments.h>
#include "kptdebug.h"
#include <kcalcore/attendee.h>
#include <kcalcore/attachment.h>
#include <kcalcore/icalformat.h>
#include <kcalcore/memorycalendar.h>
#include <QTextCodec>
#include <QByteArray>
#include <QString>
#include <QTextStream>
#include <QFile>
#include <kpluginfactory.h>
#include <KoFilterChain.h>
#include <KoFilterManager.h>
#include <KoDocument.h>
using namespace KPlato;
K_PLUGIN_FACTORY_WITH_JSON(ICalendarExportFactory, "plan_icalendar_export.json",
registerPlugin<ICalendarExport>();)
#ifdef HAVE_QDATETIME_KCALCORE
#define KQDT QDateTime
#else
#define KQDT KDateTime
#endif
ICalendarExport::ICalendarExport(QObject* parent, const QVariantList &)
: KoFilter(parent)
{
}
KoFilter::ConversionStatus ICalendarExport::convert(const QByteArray& from, const QByteArray& to)
{
debugPlan << from << to;
if ( ( from != "application/x-vnd.kde.plan" ) || ( to != "text/calendar" ) ) {
return KoFilter::NotImplemented;
}
bool batch = false;
if ( m_chain->manager() ) {
batch = m_chain->manager()->getBatchMode();
}
if ( batch ) {
//TODO
debugPlan << "batch";
return KoFilter::UsageError;
}
debugPlan<<"online:"<<m_chain->inputDocument();
MainDocument *doc = dynamic_cast<MainDocument*>( m_chain->inputDocument() );
if (doc == 0) {
errorPlan << "Cannot open Plan document";
return KoFilter::InternalError;
}
if (m_chain->outputFile().isEmpty()) {
errorPlan << "Output filename is empty";
return KoFilter::InternalError;
}
QFile file(m_chain->outputFile());
if (! file.open(QIODevice::WriteOnly)) {
errorPlan << "Failed to open output file:" << file.fileName();
return KoFilter::StorageCreationError;
}
KoFilter::ConversionStatus status = convert(doc->getProject(), file);
file.close();
//debugPlan << "Finished with status:"<<status;
return status;
}
KoFilter::ConversionStatus ICalendarExport::convert(const Project &project, QFile &file)
{
KCalCore::Calendar::Ptr cal(new KCalCore::MemoryCalendar("UTC"));
//TODO: schedule selection dialog
long id = ANYSCHEDULED;
bool baselined = project.isBaselined(id);
QList<ScheduleManager*> lst = project.allScheduleManagers();
foreach(const ScheduleManager *m, lst) {
if (! baselined) {
id = lst.last()->scheduleId();
//debugPlan<<"last:"<<id;
break;
}
if (m->isBaselined()) {
id = m->scheduleId();
//debugPlan<<"baselined:"<<id;
break;
}
}
//debugPlan<<id;
createTodos(cal, &project, id);
KCalCore::ICalFormat format;
qint64 n = file.write(format.toString(cal).toUtf8());
if (n < 0) {
return KoFilter::InternalError;
}
return KoFilter::OK;
}
void ICalendarExport::createTodos(KCalCore::Calendar::Ptr cal, const Node *node, long id, KCalCore::Todo::Ptr parent)
{
KCalCore::Todo::Ptr todo(new KCalCore::Todo());
todo->setUid( node->id() );
todo->setSummary(node->name());
todo->setDescription(node->description());
todo->setCategories(QLatin1String("Plan"));
if (! node->projectNode()->leader().isEmpty()) {
todo->setOrganizer(node->projectNode()->leader());
}
if ( node->type() != Node::Type_Project && ! node->leader().isEmpty()) {
KCalCore::Person::Ptr p = KCalCore::Person::fromFullName(node->leader());
KCalCore::Attendee::Ptr a(new KCalCore::Attendee(p->name(), p->email()));
a->setRole(KCalCore::Attendee::NonParticipant);
todo->addAttendee(a);
}
DateTime st = node->startTime(id);
DateTime et = node->endTime(id);
if (st.isValid()) {
todo->setDtStart( KQDT( st ) );
}
if (et.isValid()) {
todo->setDtDue( KQDT( et ) );
}
if (node->type() == Node::Type_Task) {
const Task *task = qobject_cast<Task*>(const_cast<Node*>(node));
Schedule *s = task->schedule(id);
if (id < 0 || s == 0) {
// Not scheduled, use requests
const QList<Resource*> lst = task->requestedResources();
foreach(const Resource *r, lst) {
if (r->type() == Resource::Type_Work) {
todo->addAttendee(KCalCore::Attendee::Ptr(new KCalCore::Attendee(r->name(), r->email())));
}
}
} else {
foreach(const Resource *r, s->resources()) {
if (r->type() == Resource::Type_Work) {
todo->addAttendee(KCalCore::Attendee::Ptr(new KCalCore::Attendee(r->name(), r->email())));
}
}
}
} else if (node->type() == Node::Type_Milestone) {
const Task *task = qobject_cast<Task*>(const_cast<Node*>(node));
todo->setDtStart(KQDT());
todo->setPercentComplete(task->completion().percentFinished());
}
foreach(const Document *doc, node->documents().documents()) {
todo->addAttachment(KCalCore::Attachment::Ptr(new KCalCore::Attachment(doc->url().url())));
}
if (! parent.isNull()) {
todo->setRelatedTo(parent->uid(), KCalCore::Incidence::RelTypeParent);
}
cal->addTodo(todo);
foreach(const Node *n, node->childNodeIterator()) {
createTodos(cal, n, id, todo);
}
}
#include "icalendarexport.moc"
diff --git a/src/plugins/filters/kplato/import/kplatoimport.cpp b/src/plugins/filters/kplato/import/kplatoimport.cpp
index adab2f6d..ee1ff5b3 100644
--- a/src/plugins/filters/kplato/import/kplatoimport.cpp
+++ b/src/plugins/filters/kplato/import/kplatoimport.cpp
@@ -1,82 +1,83 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "kplatoimport.h"
#include <kptmaindocument.h>
#include <kpttask.h>
#include <kptnode.h>
#include <kptresource.h>
#include <kptdocuments.h>
#include "kptdebug.h"
#include <QTextCodec>
#include <QByteArray>
#include <QString>
#include <QTextStream>
#include <QFile>
#include <kpluginfactory.h>
#include <KoFilterChain.h>
#include <KoFilterManager.h>
#include <KoDocument.h>
using namespace KPlato;
K_PLUGIN_FACTORY_WITH_JSON(KPlatoImportFactory, "plan_kplato_import.json",
registerPlugin<KPlatoImport>();)
KPlatoImport::KPlatoImport(QObject* parent, const QVariantList &)
: KoFilter(parent)
{
}
KoFilter::ConversionStatus KPlatoImport::convert(const QByteArray& from, const QByteArray& to)
{
debugPlan << from << to;
if ( ( from != "application/x-vnd.kde.kplato" ) || ( to != "application/x-vnd.kde.plan" ) ) {
return KoFilter::NotImplemented;
}
KoDocument *part = 0;
bool batch = false;
if ( m_chain->manager() ) {
batch = m_chain->manager()->getBatchMode();
}
if (batch) {
//TODO
debugPlan << "batch";
} else {
//debugPlan<<"online";
part = m_chain->outputDocument();
}
if (part == 0) {
errorPlan << "Cannot open document";
return KoFilter::InternalError;
}
if ( ! part->loadNativeFormat( m_chain->inputFile() ) ) {
return KoFilter::ParsingError;
}
return KoFilter::OK;
}
#include "kplatoimport.moc"
diff --git a/src/plugins/schedulers/rcps/KPlatoRCPSPlugin.cpp b/src/plugins/schedulers/rcps/KPlatoRCPSPlugin.cpp
index dec70831..1baf7993 100644
--- a/src/plugins/schedulers/rcps/KPlatoRCPSPlugin.cpp
+++ b/src/plugins/schedulers/rcps/KPlatoRCPSPlugin.cpp
@@ -1,166 +1,167 @@
/* This file is part of the KDE project
* Copyright (C) 2009, 2012 Dag Andersen <danders@get2net.dk>
*
* 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 "KPlatoRCPSPlugin.h"
#include "kptschedulerplugin_macros.h"
#include "kptdebug.h"
#include "KPlatoRCPSScheduler.h"
#include "kptproject.h"
#include "kptschedule.h"
#include <kptschedulerplugin.h>
#include <librcps.h>
#include <KLocalizedString>
#include <QApplication>
#ifndef PLAN_NOPLUGIN
PLAN_SCHEDULERPLUGIN_EXPORT(KPlatoRCPSPlugin, "planrcpsscheduler.json")
#endif
using namespace KPlato;
KPlatoRCPSPlugin::KPlatoRCPSPlugin( QObject * parent, const QVariantList & )
: KPlato::SchedulerPlugin(parent)
{
debugPlan<<rcps_version();
m_granularities << (long unsigned int) 1 * 60 * 1000
<< (long unsigned int) 15 * 60 * 1000
<< (long unsigned int) 30 * 60 * 1000
<< (long unsigned int) 60 * 60 * 1000;
}
KPlatoRCPSPlugin::~KPlatoRCPSPlugin()
{
}
QString KPlatoRCPSPlugin::description() const
{
return xi18nc( "@info:whatsthis", "<title>RCPS Scheduler</title>"
"<para>The Resource Constrained Project Scheduler (RCPS) focuses on scheduling"
" the project to avoid overbooking resources."
" It still respects task dependencies and also tries to fulfill time constraints."
" However, time constraints can make it very difficult to find a good solution,"
" so it may be preferable to use a different scheduler in these cases.</para>"
);
}
int KPlatoRCPSPlugin::capabilities() const
{
return SchedulerPlugin::AvoidOverbooking | SchedulerPlugin::ScheduleForward | SchedulerPlugin::ScheduleBackward;
}
ulong KPlatoRCPSPlugin::currentGranularity() const
{
ulong v = m_granularities.value( m_granularity );
return qMax( v, (ulong)60000 ); // minimum 1 min
}
void KPlatoRCPSPlugin::calculate( KPlato::Project &project, KPlato::ScheduleManager *sm, bool nothread )
{
foreach ( SchedulerThread *j, m_jobs ) {
if ( j->manager() == sm ) {
return;
}
}
sm->setScheduling( true );
KPlatoRCPSScheduler *job = new KPlatoRCPSScheduler( &project, sm, currentGranularity() );
m_jobs << job;
connect(job, SIGNAL(jobFinished(SchedulerThread*)), SLOT(slotFinished(SchedulerThread*)));
project.changed( sm );
// connect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), &project, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
// connect(this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), &project, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)));
connect(job, SIGNAL(maxProgressChanged(int)), sm, SLOT(setMaxProgress(int)));
connect(job, SIGNAL(progressChanged(int)), sm, SLOT(setProgress(int)));
if ( nothread ) {
job->doRun();
} else {
job->start();
}
}
void KPlatoRCPSPlugin::stopAllCalculations()
{
foreach ( SchedulerThread *s, m_jobs ) {
stopCalculation( s );
}
}
void KPlatoRCPSPlugin::stopCalculation( SchedulerThread *sch )
{
if ( sch ) {
//FIXME: this should just call stopScheduling() and let the job finish "normally"
disconnect( sch, SIGNAL(jobFinished(KPlatoRCPSScheduler*)), this, SLOT(slotFinished(KPlatoRCPSScheduler*)) );
sch->stopScheduling();
// wait max 20 seconds.
sch->mainManager()->setCalculationResult( ScheduleManager::CalculationStopped );
if ( ! sch->wait( 20000 ) ) {
sch->deleteLater();
m_jobs.removeAt( m_jobs.indexOf( sch ) );
} else {
slotFinished( sch );
}
}
}
void KPlatoRCPSPlugin::slotStarted( SchedulerThread */*job*/ )
{
// debugPlan<<"KPlatoRCPSPlugin::slotStarted:";
}
void KPlatoRCPSPlugin::slotFinished( SchedulerThread *j )
{
KPlatoRCPSScheduler *job = static_cast<KPlatoRCPSScheduler*>( j );
Project *mp = job->mainProject();
ScheduleManager *sm = job->mainManager();
//debugPlan<<"KPlatoRCPSPlugin::slotFinished:"<<mp<<sm<<job->isStopped();
if ( job->isStopped() ) {
sm->setCalculationResult( ScheduleManager::CalculationCanceled );
} else {
updateLog( job );
Project *tp = job->project();
ScheduleManager *tm = job->manager();
updateProject( tp, tm, mp, sm );
sm->setCalculationResult( ScheduleManager::CalculationDone );
}
sm->setScheduling( false );
m_jobs.removeAt( m_jobs.indexOf( job ) );
if ( m_jobs.isEmpty() ) {
m_synctimer.stop();
}
emit sigCalculationFinished( mp, sm );
disconnect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), mp, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
disconnect(this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), mp, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)));
job->deleteLater();
}
#include "KPlatoRCPSPlugin.moc"
diff --git a/src/plugins/schedulers/rcps/KPlatoRCPSScheduler.cpp b/src/plugins/schedulers/rcps/KPlatoRCPSScheduler.cpp
index d378091c..0417ff03 100644
--- a/src/plugins/schedulers/rcps/KPlatoRCPSScheduler.cpp
+++ b/src/plugins/schedulers/rcps/KPlatoRCPSScheduler.cpp
@@ -1,1376 +1,1377 @@
/* This file is part of the KDE project
* Copyright (C) 2009, 2010, 2012 Dag Andersen <danders@get2net.dk>
*
* 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 "KPlatoRCPSScheduler.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptresource.h"
#include "kpttask.h"
#include "kptrelation.h"
#include "kptdebug.h"
#include <librcps.h>
#include <QString>
#include <QTimer>
#include <QMutexLocker>
#include <QLocale>
#include <KLocalizedString>
#include <KFormat>
#include <iostream>
#define GENERATION_MIN_LIMIT 5000
#define PROGRESS_CALLBACK_FREQUENCY 100
#define PROGRESS_MAX_VALUE 120000
#define PROGRESS_INIT_VALUE 12000
#define PROGRESS_INIT_STEP 2000
/* low weight == late, high weight == early */
#define WEIGHT_ASAP 50
#define WEIGHT_ALAP 1
#define WEIGHT_CONSTRAINT 1000
#define WEIGHT_FINISH 1000
#define GROUP_TARGETTIME 1
#define GROUP_CONSTRAINT 2
class ProgressInfo
{
public:
explicit ProgressInfo() : init( true ), base( 0 ), progress( 0 )
{
fitness.group = 0;
fitness.weight = 0;
}
bool init;
int base;
int progress;
struct rcps_fitness fitness;
};
KPlatoRCPSScheduler::KPlatoRCPSScheduler( Project *project, ScheduleManager *sm, ulong granularity, QObject *parent )
: SchedulerThread( project, sm, parent ),
result( -1 ),
m_schedule( 0 ),
m_recalculate( false ),
m_usePert( false ),
m_backward( false ),
m_problem( 0 ),
m_timeunit( granularity / 1000 ),
m_offsetFromTime_t( 0 ),
m_progressinfo( new ProgressInfo() )
{
connect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), project, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
emit sigCalculationStarted( project, sm );
connect( this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), project, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)) );
}
KPlatoRCPSScheduler::~KPlatoRCPSScheduler()
{
delete m_progressinfo;
qDeleteAll( m_duration_info_list );
qDeleteAll( m_weight_info_list );
rcps_problem_free( m_problem );
}
int KPlatoRCPSScheduler::progress_callback( int generations, struct rcps_fitness fitness, void *arg )
{
if ( arg == 0 ) {
return -1;
}
KPlatoRCPSScheduler *self = static_cast<KPlatoRCPSScheduler*>( arg );
//debugPlan<<"KPlatoRCPSScheduler::progress_callback"<<generations<<fitness<<arg;
return self->progress( generations, fitness );
}
int KPlatoRCPSScheduler::progress( int generations, struct rcps_fitness fitness )
{
if ( m_haltScheduling ) {
debugPlan<<"KPlatoRCPSScheduler::progress:"<<"halt";
return -1;
}
if ( m_stopScheduling ) {
m_schedule->logWarning( i18n( "Scheduling halted after %1 generations", generations ), 1 );
debugPlan<<"KPlatoRCPSScheduler::progress:"<<"stop";
return -1;
}
// std::cout << "Progress after: " << generations << " generations\n";
if ( m_progressinfo->init ) {
if ( generations == 0 ) {
m_progressinfo->progress += PROGRESS_INIT_STEP;
} else {
m_progressinfo->progress = PROGRESS_INIT_VALUE;
m_progressinfo->init = false;
// std::cout << "Population generated: "<< generations << "\n";
}
} else {
m_progressinfo->progress = PROGRESS_INIT_VALUE + generations;
}
// detect change in fitness
if ( rcps_fitness_cmp( &m_progressinfo->fitness, &fitness ) != 0 ) {
// std::cout << "Fitness changed in generation: " << generations << " group=["<<m_progressinfo->fitness.group<<"->"<<fitness.group<<"]"<<" weight=["<<m_progressinfo->fitness.weight<<"->"<<fitness.weight<<"]\n";
m_progressinfo->fitness = fitness;
m_progressinfo->base = generations;
}
m_manager->setProgress( m_progressinfo->progress );
setProgress( m_progressinfo->progress );
// stop if fitness does not change in GENERATION_MIN_LIMIT generations
/* int result = ( generations >= m_progressinfo->base + GENERATION_MIN_LIMIT ? 1 : 0 );
if ( result ) {
//debugPlan<<"KPlatoRCPSScheduler::progress, stop after"<<generations<<"generations, progress:"<<m_progressinfo->progress;
m_schedule->logDebug( QString( "Acceptable solution found after %1 generations" ).arg( generations ), 1 );
std::cout << "Acceptable solution found after " << generations << " generations\n";
}*/
return 0;
}
int KPlatoRCPSScheduler::duration_callback( int direction, int time, int nominal_duration, void *arg )
{
//debugPlan<<"plan_duration:"<<direction<<time<<nominal_duration<<arg;
if ( arg == 0 ) {
return nominal_duration;
}
KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>( arg );
return info->self->duration( direction, time, nominal_duration, info );
}
int KPlatoRCPSScheduler::duration( int direction, int time, int nominal_duration, KPlatoRCPSScheduler::duration_info *info )
{
if ( m_haltScheduling || m_manager == 0 ) {
return nominal_duration;
}
++(info->calls);
if ( info->cache.contains( QPair<int, int>( time, direction ) ) ) {
return info->cache[ QPair<int, int>( time, direction ) ];
}
if ( m_manager->recalculate() && info->task->completion().isFinished() ) {
return 0;
}
int dur = 0;
if ( info->task->constraint() == Node::FixedInterval ) {
// duration may depend on daylight saving so we need to calculate
// NOTE: dur may not be correct if time != info->task->constraintStartTime, let's see what happens...
dur = ( info->task->constraintEndTime() - info->task->constraintStartTime() ).seconds() / m_timeunit;
info->task->schedule()->logDebug( QString( "Fixed interval: Time=%1, duration=%2 ( %3, %4 )" ).arg( time ).arg( dur ).arg( fromRcpsTime( time ).toString() ).arg( Duration( (qint64)(dur) * m_timeunit * 1000 ).toDouble( Duration::Unit_h ) ) );
} else if ( info->estimatetype == Estimate::Type_Effort ) {
if ( info->requests.isEmpty() ) {
dur = info->estimate.seconds() / m_timeunit;
} else {
dur = info->task->requests().duration(
info->requests,
fromRcpsTime( time ),
info->estimate,
0, /*no schedule*/
m_backward ? ! direction : direction
).seconds() / m_timeunit;
//debugPlan<<info->task->name()<< QString( "duration_callback effort: backward=%5, direction=%6 (direction=%7); Time=%1, duration=%2 ( %3, %4 )" ).arg( time ).arg( dur ).arg( fromRcpsTime( time ).toString() ).arg( Duration( (qint64)(dur) * m_timeunit * 1000 ).toDouble( Duration::Unit_h ) ).arg( m_backward ).arg( direction ).arg( m_backward ? !direction : direction );
}
} else {
dur = info->task->length(
fromRcpsTime( time ),
info->estimate,
0, /*no schedule*/
m_backward ? ! direction : direction
).seconds() / m_timeunit;
}
info->cache[ QPair<int, int>( time, direction ) ] = dur;
info->task->schedule()->logDebug( QString( "duration_callback: Time=%1, duration=%2 ( %3, %4 )" ).arg( time ).arg( dur ).arg( fromRcpsTime( time ).toString() ).arg( Duration( (qint64)(dur) * m_timeunit * 1000 ).toDouble( Duration::Unit_h ) ) );
return dur;
}
int KPlatoRCPSScheduler::weight_callback( int time, int duration, struct rcps_fitness *nominal_weight, void* weight_arg, void* fitness_arg )
{
//debugPlan<<"plan_weight:"<<time<<nominal_weight<<arg;
if ( weight_arg == 0 ) {
nominal_weight->weight *= time;
return 0;
}
KPlatoRCPSScheduler::weight_info *winfo = static_cast<KPlatoRCPSScheduler::weight_info*>( weight_arg );
KPlatoRCPSScheduler::fitness_info *finfo = static_cast<KPlatoRCPSScheduler::fitness_info*>( fitness_arg );
return winfo->self->weight( time, duration, nominal_weight, winfo, finfo );
}
void *KPlatoRCPSScheduler::fitness_callback_init( void *arg )
{
Q_ASSERT( arg );
KPlatoRCPSScheduler::fitness_info *info = static_cast<KPlatoRCPSScheduler::fitness_info*>( arg );
Q_ASSERT( info );
fitness_info *finfo = new fitness_info;
finfo->self = info->self;
// debugPlan<<info->self;
return finfo;
}
int KPlatoRCPSScheduler::fitness_callback_result( struct rcps_fitness *fit, void *arg )
{
KPlatoRCPSScheduler::fitness_info *info = static_cast<KPlatoRCPSScheduler::fitness_info*>( arg );
info->self->fitness( fit, info );
delete info;
return 0;
}
int KPlatoRCPSScheduler::fitness( struct rcps_fitness *fit, KPlatoRCPSScheduler::fitness_info *info )
{
/* std::cout << ">-------------------------------------------\n";
std::cout << "Sequence: ";
foreach ( Task *t, info->jobs ) { std::cout << (t ? t->name().toLocal8Bit().data() : "End") << ", "; }
std::cout << "\n";
debugPlan<<info->map;*/
QMultiMap<int, QPair<int, Task*> >::const_iterator it = info->map.constFind( GROUP_CONSTRAINT );
if ( it != info->map.constEnd() ) {
// constraint
fit->group = GROUP_CONSTRAINT;
for ( ; it.key() == GROUP_CONSTRAINT && it != info->map.constEnd(); ++it ) {
fit->weight += it.value().first;
QString s = it.value().second ? it.value().second->name() : "End node";
// std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
// m_schedule->logDebug( QString( "%3: %1 %2" ).arg( it.key() ).arg( it.value().first ).arg( it.value().second->name() ) );
}
// std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
return 0;
}
it = info->map.constFind( GROUP_TARGETTIME );
if ( it != info->map.constEnd() ) {
// missed target time
fit->group = GROUP_TARGETTIME;
for ( ; it.key() == GROUP_TARGETTIME && it != info->map.constEnd(); ++it ) {
fit->weight += it.value().first;
QString s = it.value().second ? it.value().second->name() : "End node";
// std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
// m_schedule->logDebug( QString( "%3: %1 %2" ).arg( it.key() ).arg( it.value().first ).arg( it.value().second->name() ) );
}
// std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
return 0;
}
fit->group = 0;
for ( it = info->map.constBegin(); it != info->map.constEnd(); ++it ) {
fit->weight += it.value().first;
QString s = it.value().second ? it.value().second->name() : "End node";
// std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
// m_schedule->logDebug( QString( "%3: %1 %2" ).arg( it.key() ).arg( it.value().first ).arg( it.value().second->name() ) );
}
// std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
return 0;
}
int KPlatoRCPSScheduler::weight( int time, int duration, struct rcps_fitness *nominal_weight, KPlatoRCPSScheduler::weight_info* info, KPlatoRCPSScheduler::fitness_info* finfo )
{
if ( m_haltScheduling || m_manager == 0 ) {
return 0;
}
if ( m_manager->recalculate() && info->task->completion().isFinished() ) {
return 0;
}
struct rcps_fitness &f = *nominal_weight;
f.group = 0;
f.weight = time;
if ( info->isEndJob ) {
if ( info->finish == 0 ) {
info->finish = time;
/* const char *s = QString( "First : %1 %2 %3 End job" ).arg( time, 10 ).arg( duration, 10 ).arg( w, 10 ).toLatin1();
std::cout<<s<<"\n";*/
}
/* w = WEIGHT_FINISH * info->finish / ( time > 0 ? time : 1 );
if ( time > info->targettime ) {
w = w + ( WEIGHT_CONSTRAINT * ( time - info->targettime ) );
}*/
if ( time > info->targettime ) {
f.group = GROUP_TARGETTIME;
f.weight = time - info->targettime;
}
/* const char *s = QString( "End job: %1 %2 %3 End job target: %4" ).arg( time, 10 ).arg( duration, 10 ).arg( w, 10 ).arg( info->targettime ).toLatin1();
std::cout<<s<<"\n";*/
} else {
if ( m_backward ) {
switch ( info->task->constraint() ) {
case Node::FinishNotLater:
if ( info->targettime > time ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * ( info->targettime - time );
}
break;
case Node::MustFinishOn:
if ( info->targettime != time ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * abs( info->targettime - time );
}
break;
case Node::StartNotEarlier:
if ( info->targettime < time ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * ( time - info->targettime );
}
break;
case Node::MustStartOn:
case Node::FixedInterval:
if ( info->targettime != time ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * abs( info->targettime - time );
}
break;
default:
break;
}
/* const char *s = QString( "Backward: %1 %2 %3 %4 (target: %5)" ).arg( time, 10 ).arg( duration, 10 ).arg( w, 10 ).arg( info->task->name() ).arg( info->targettime ).toLatin1();
std::cout<<s<<"\n";*/
} else {
switch ( info->task->constraint() ) {
case Node::StartNotEarlier:
if ( time < info->targettime ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * ( info->targettime - time );
}
break;
case Node::MustStartOn:
case Node::FixedInterval:
if ( info->targettime != time ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * ( abs( info->targettime - time ) );
}
break;
case Node::FinishNotLater:
// std::cout << "FNL " << info->task->name().toLocal8Bit().data() << ": end="<<time+duration<<" target="<<info->targettime<<"\n";
if ( time + duration > info->targettime ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * ( time - info->targettime );
}
// std::cout << info->task->name().toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";
break;
case Node::MustFinishOn:
// std::cout << "MSO " << info->task->name().toLocal8Bit().data() << ": end="<<time+duration<<" target="<<info->targettime<<"\n";
if ( info->targettime != time + duration ) {
f.group = GROUP_CONSTRAINT;
f.weight = WEIGHT_CONSTRAINT * abs( info->targettime - time );
}
// std::cout << info->task->name().toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";
break;
default:
break;
}
/* const char *s = QString( "Forward: %1 %2 %3 %4 (target: %5)" ).arg( time, 10 ).arg( duration, 10 ).arg( w, 10 ).arg( info->task->name() ).arg( info->targettime ).toLatin1();
std::cout<<s<<"\n";*/
}
}
// QString s = info->task ? info->task->name() : "End node";
if ( finfo ) {
finfo->map.insert( f.group, QPair<int, Task*>( f.weight, info->task ) );
finfo->jobs << info->task;
// debugPlan<<s<<":"<<finfo->map;
}// else debugPlan<<s<<":"<<"No finfo!";
/* std::cout << "Weight: " << s.toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";*/
return 0;
}
void KPlatoRCPSScheduler::run()
{
if ( m_haltScheduling ) {
deleteLater();
return;
}
if ( m_stopScheduling ) {
return;
}
{ // mutex -->
m_projectMutex.lock();
m_managerMutex.lock();
m_project = new Project();
loadProject( m_project, m_pdoc );
m_project->setName( "Schedule: " + m_project->name() ); //Debug
m_project->stopcalculation = false;
m_manager = m_project->scheduleManager( m_mainmanagerId );
Q_CHECK_PTR( m_manager );
Q_ASSERT( m_manager->expected() );
Q_ASSERT( m_manager != m_mainmanager );
Q_ASSERT( m_manager->scheduleId() == m_mainmanager->scheduleId() );
Q_ASSERT( m_manager->expected() != m_mainmanager->expected() );
m_manager->setName( "Schedule: " + m_manager->name() ); //Debug
m_schedule = m_manager->expected();
connect(m_manager, SIGNAL(sigLogAdded(Schedule::Log)), this, SLOT(slotAddLog(Schedule::Log)));
m_project->initiateCalculation( *m_schedule );
m_project->initiateCalculationLists( *m_schedule );
m_problem = rcps_problem_new();
rcps_problem_setfitness_mode( m_problem, FITNESS_WEIGHT );
m_usePert = m_manager->usePert();
m_recalculate = m_manager->recalculate();
if ( m_recalculate ) {
m_starttime = m_manager->recalculateFrom();
m_backward = false;
} else {
m_backward = m_manager->schedulingDirection();
m_starttime = m_backward ? m_project->constraintEndTime() : m_project->constraintStartTime();
}
m_targettime = m_backward ? m_project->constraintStartTime() : m_project->constraintEndTime();
m_project->setCurrentSchedule( m_manager->expected()->id() );
m_schedule->setPhaseName( 0, i18n( "Init" ) );
QLocale locale;
if ( ! m_backward ) {
m_schedule->logDebug( QString( "Schedule project using RCPS Scheduler, starting at %1, granularity %2 sec" ).arg( locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat) ).arg( m_timeunit ), 0 );
if ( m_recalculate ) {
m_schedule->logInfo( i18n( "Re-calculate project from start time: %1", locale.toString(m_starttime, QLocale::ShortFormat) ), 0 );
} else {
m_schedule->logInfo( i18n( "Schedule project from start time: %1", locale.toString(m_starttime, QLocale::ShortFormat) ), 0 );
}
} else {
m_schedule->logDebug( QString( "Schedule project backward using RCPS Scheduler, starting at %1, granularity %2 sec" ).arg( locale.toString( QDateTime::currentDateTime(), QLocale::ShortFormat) ).arg( m_timeunit ), 0 );
m_schedule->logInfo( i18n( "Schedule project from end time: %1", locale.toString(m_starttime, QLocale::ShortFormat) ), 0 );
}
m_managerMutex.unlock();
m_projectMutex.unlock();
} // <--- mutex
m_progressinfo->progress += PROGRESS_INIT_STEP / 5;
setProgress( m_progressinfo->progress );
result = kplatoToRCPS();
if ( result != 0 ) {
m_schedule->logError( i18n( "Failed to build a valid RCPS project" ) );
setProgress( PROGRESS_MAX_VALUE );
return;
}
m_schedule->setPhaseName( 1, i18n( "Schedule" ) );
setMaxProgress( PROGRESS_MAX_VALUE );
solve();
if ( m_haltScheduling ) {
deleteLater();
return;
}
if ( result != 0 ) {
m_schedule->logError( i18n( "Invalid scheduling solution. Result: %1", result ), 1 );
}
kplatoFromRCPS();
setProgress( PROGRESS_MAX_VALUE );
}
int KPlatoRCPSScheduler::check()
{
return rcps_check( m_problem );
}
void KPlatoRCPSScheduler::solve()
{
debugPlan<<"KPlatoRCPSScheduler::solve()";
struct rcps_solver *s = rcps_solver_new();
rcps_solver_set_progress_callback(s, PROGRESS_CALLBACK_FREQUENCY, this, &KPlatoRCPSScheduler::progress_callback);
rcps_solver_set_duration_callback(s, &KPlatoRCPSScheduler::duration_callback );
rcps_problem_set_weight_callback( m_problem, &KPlatoRCPSScheduler::weight_callback );
fitness_init_arg.self = this;
rcps_problem_set_fitness_callback( m_problem, &KPlatoRCPSScheduler::fitness_callback_init, &fitness_init_arg, &KPlatoRCPSScheduler::fitness_callback_result );
Q_ASSERT( check() == 0 );
rcps_solver_setparam( s, SOLVER_PARAM_POPSIZE, 1000 );
rcps_solver_solve( s, m_problem );
result = rcps_solver_getwarnings( s );
rcps_solver_free( s );
}
int KPlatoRCPSScheduler::kplatoToRCPS()
{
addResources();
addTasks();
addDependencies();
addRequests();
setWeights();
setConstraints();
int r = check();
return r;
}
void KPlatoRCPSScheduler::taskFromRCPSForward( struct rcps_job *job, Task *task, QMap<Node*, QList<ResourceRequest*> > &resourcemap )
{
if ( m_haltScheduling || m_manager == 0 ) {
return;
}
QLocale locale;
Schedule *cs = task->currentSchedule();
Q_ASSERT( cs );
struct rcps_mode *mode = rcps_mode_get(job, rcps_job_getmode_res(job));
/* get the duration of this mode */
KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>( rcps_mode_get_cbarg(mode) );
qint64 dur = 0;
qint64 st = rcps_job_getstart_res(job);
if ( info == 0 ) {
dur = rcps_mode_getduration(mode);
} else {
cs->logDebug( QString( "Task '%1' estimate: %2" ).arg( task->name() ).arg( task->estimate()->value( Estimate::Use_Expected, false ).toString() ), 1 );
cs->logDebug( QString( "Task '%1' duration called %2 times, cached values: %3" ).arg( rcps_job_getname(job) ).arg( info->calls ).arg( info->cache.count() ) );
dur = duration_callback( 0, st, rcps_mode_getduration(mode), info );
for ( QMap<QPair<int, int>, int>::ConstIterator it = info->cache.constBegin(); it != info->cache.constEnd(); ++it ) {
cs->logDebug( QString( "Task '%1' start: %2, duration: %3 (%4, %5 hours)" ).arg( rcps_job_getname(job) ).arg( it.key().first ).arg( it.value() ).arg( fromRcpsTime( it.key().first ).toString() ).arg( (double)(it.value())/60.0 ), 1 );
}
}
DateTime start = m_starttime.addSecs(st * m_timeunit);
DateTime end = start + Duration( dur * m_timeunit * 1000 );
cs->logDebug( QString( "Task '%1' start=%2, duration=%3: %4 - %5" ).arg( rcps_job_getname(job) ).arg( st ).arg( dur ).arg( locale.toString(start, QLocale::ShortFormat) ).arg( locale.toString(end, QLocale::ShortFormat ) ), 1 );
task->setStartTime( start );
task->setEndTime( end );
for ( int reqs = 0; reqs < rcps_request_count(mode); ++reqs ) {
struct rcps_request *req = rcps_request_get(mode, reqs);
struct rcps_alternative *alt = rcps_alternative_get(req, rcps_request_getalternative_res(req));
int amount = rcps_alternative_getamount(alt);
struct rcps_resource *res = rcps_alternative_getresource(alt);
cs->logDebug( QString( "Job %1: resource %2 is %3 available" ).arg( rcps_job_getname(job) ).arg( rcps_resource_getname(res) ).arg( amount ), 1 );
// do actual appointments etc
ResourceRequest *r = m_requestmap.value( req );
if ( r == 0 ) {
cs->logWarning( i18n( "No resource request is registered" ), 1 );
continue;
}
resourcemap[ task ] << r;
cs->logDebug( QString( "Make appointments to resource %1" ).arg( r->resource()->name() ), 1 );
r->makeAppointment( cs, amount );
}
if ( m_recalculate ) {
if ( task->completion().isFinished() ) {
task->copySchedule();
if ( m_manager ) {
cs->logDebug( QString( "Task is completed, copied schedule: %2 to %3" ).arg( task->name() ).arg( locale.toString(task->startTime(), QLocale::ShortFormat) ).arg( locale.toString(task->endTime(), QLocale::ShortFormat) ), 1 );
}
} else if ( task->completion().isStarted() ) {
task->copyAppointments( DateTime(), start );
if ( m_manager ) {
cs->logDebug( QString( "Task is %4% completed, copied appointments from %2 to %3" ).arg( task->name() ).arg( locale.toString(task->startTime(), QLocale::ShortFormat) ).arg( locale.toString(start, QLocale::ShortFormat) ).arg( task->completion().percentFinished() ), 1 );
}
}
}
cs->setScheduled( true );
if ( task->estimate()->type() == Estimate::Type_Effort ) {
if ( task->appointmentStartTime().isValid() ) {
task->setStartTime( task->appointmentStartTime() );
}
if ( task->appointmentEndTime().isValid() ) {
task->setEndTime( task->appointmentEndTime() );
}
if ( info && info->requests.isEmpty() ) {
cs->setResourceError( true );
cs->logError( i18n( "No resource has been allocated" ), 1 );
}
} else if ( task->estimate()->calendar() ) {
DateTime t = task->estimate()->calendar()->firstAvailableAfter( task->startTime(), task->endTime() );
if ( t.isValid() ) {
task->setStartTime( t );
}
t = task->estimate()->calendar()->firstAvailableBefore( task->endTime(), task->startTime() );
if ( t.isValid() ) {
task->setEndTime( t );
}
} //else Fixed duration
task->setDuration( task->endTime() - task->startTime() );
cs->logInfo( i18n( "Scheduled task to start at %1 and finish at %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat ) ), 1 );
}
void KPlatoRCPSScheduler::kplatoFromRCPS()
{
if ( m_backward ) {
kplatoFromRCPSBackward();
} else {
kplatoFromRCPSForward();
}
}
void KPlatoRCPSScheduler::kplatoFromRCPSForward()
{
//debugPlan<<"KPlatoRCPSScheduler::kplatoFromRCPSForward:";
MainSchedule *cs = static_cast<MainSchedule*>( m_project->currentSchedule() );
QMap<Node*, QList<ResourceRequest*> > resourcemap;
int count = rcps_job_count(m_problem);
int step = ( PROGRESS_MAX_VALUE - m_progressinfo->progress ) / count;
DateTime projectstart = m_starttime.addSecs( rcps_job_getstart_res(m_jobstart) * m_timeunit );
for ( int i = 0; i < count; ++i ) {
m_progressinfo->progress += step;
m_manager->setProgress( m_progressinfo->progress );
setProgress( m_progressinfo->progress );
struct rcps_job *job = rcps_job_get( m_problem, i );
Task *task = m_taskmap.value( job );
if ( task == 0 ) {
continue; //might be dummy task for lag, ...
}
taskFromRCPSForward( job, task, resourcemap );
if ( projectstart > task->startTime() ) {
projectstart = task->startTime();
}
}
qint64 st = rcps_job_getstart_res(m_jobstart) * m_timeunit;
DateTime start = m_starttime.addSecs( st );
qint64 et = rcps_job_getstart_res(m_jobend) * m_timeunit;
DateTime end = m_starttime.addSecs( et );
m_project->setStartTime( projectstart );
m_project->setEndTime( end );
adjustSummaryTasks( m_schedule->summaryTasks() );
calculatePertValues( resourcemap );
QLocale locale;
cs->logInfo( i18n( "Project scheduled to start at %1 and finish at %2", locale.toString(projectstart, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat) ), 1 );
if ( m_manager ) {
cs->logDebug( QString( "Project scheduling finished at %1" ).arg( QDateTime::currentDateTime().toString() ), 1 );
m_project->finishCalculation( *m_manager );
m_manager->scheduleChanged( cs );
}
}
void KPlatoRCPSScheduler::taskFromRCPSBackward( struct rcps_job *job, Task *task, QMap<Node*, QList<ResourceRequest*> > &resourcemap )
{
if ( m_haltScheduling || m_manager == 0 ) {
return;
}
QLocale locale;
Schedule *cs = task->currentSchedule();
Q_ASSERT( cs );
struct rcps_mode *mode = rcps_mode_get( job, rcps_job_getmode_res( job ) );
/* get the duration of this mode */
KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>( rcps_mode_get_cbarg( mode ) );
qint64 dur = 0;
qint64 st = rcps_job_getstart_res( job );
if ( info == 0 ) {
dur = rcps_mode_getduration( mode );
} else {
cs->logDebug( QString( "Task '%1' estimate: %2" ).arg( task->name() ).arg( task->estimate()->value( Estimate::Use_Expected, false ).toString() ), 1 );
cs->logDebug( QString( "Task '%1' duration called %2 times, cached values: %3" ).arg( rcps_job_getname( job ) ).arg( info->calls ).arg( info->cache.count() ) );
dur = duration_callback( 0, st, rcps_mode_getduration( mode ), info );
for ( QMap<QPair<int, int>, int>::ConstIterator it = info->cache.constBegin(); it != info->cache.constEnd(); ++it ) {
cs->logDebug( QString( "Task '%1' start: %2, duration: %3 (%4, %5 hours)" ).arg( rcps_job_getname(job) ).arg( it.key().first ).arg( it.value() ).arg( fromRcpsTime( it.key().first ).toString() ).arg( (double)(it.value())/60.0 ), 1 );
}
}
DateTime end = fromRcpsTime( st );
DateTime start = fromRcpsTime( st + dur );
cs->logDebug( QString( "Task '%1' start=%2, duration=%3: %4 - %5" ).arg( rcps_job_getname(job) ).arg( st ).arg( dur ).arg( locale.toString(start, QLocale::ShortFormat) ).arg( locale.toString(end, QLocale::ShortFormat) ), 1 );
task->setStartTime( start );
task->setEndTime( end );
for ( int reqs = 0; reqs < rcps_request_count( mode ); ++reqs ) {
struct rcps_request *req = rcps_request_get( mode, reqs );
struct rcps_alternative *alt = rcps_alternative_get( req, rcps_request_getalternative_res( req ) );
int amount = rcps_alternative_getamount( alt );
struct rcps_resource *res = rcps_alternative_getresource( alt );
cs->logDebug( QString( "Job %1: resource %2 is %3 available" ).arg( rcps_job_getname( job ) ).arg( rcps_resource_getname( res ) ).arg( amount ), 1 );
// do actual appointments etc
ResourceRequest *r = m_requestmap.value( req );
if ( r == 0 ) {
cs->logWarning( i18n( "No resource request is registered" ), 1 );
continue;
}
resourcemap[ task ] << r;
cs->logDebug( QString( "Make appointments to resource %1" ).arg( r->resource()->name() ), 1 );
r->makeAppointment( cs, amount );
}
if ( m_recalculate ) {
if ( task->completion().isFinished() ) {
task->copySchedule();
if ( m_manager ) {
cs->logDebug( QString( "Task is completed, copied schedule: %2 to %3" ).arg( task->name() ).arg( locale.toString(task->startTime(), QLocale::ShortFormat) ).arg( locale.toString(task->endTime(), QLocale::ShortFormat) ), 1 );
}
} else if ( task->completion().isStarted() ) {
task->copyAppointments( DateTime(), start );
if ( m_manager ) {
cs->logDebug( QString( "Task is %4% completed, copied appointments from %2 to %3" ).arg( task->name() ).arg( locale.toString(task->startTime(), QLocale::ShortFormat) ).arg( locale.toString(start, QLocale::ShortFormat) ).arg( task->completion().percentFinished() ), 1 );
}
}
}
cs->setScheduled( true );
if ( task->estimate()->type() == Estimate::Type_Effort ) {
if ( task->appointmentStartTime().isValid() ) {
task->setStartTime( task->appointmentStartTime() );
}
if ( task->appointmentEndTime().isValid() ) {
task->setEndTime( task->appointmentEndTime() );
}
if ( info && info->requests.isEmpty() ) {
cs->setResourceError( true );
cs->logError( i18n( "No resource has been allocated" ), 1 );
}
} else if ( task->estimate()->calendar() ) {
DateTime t = task->estimate()->calendar()->firstAvailableAfter( task->startTime(), task->endTime() );
if ( t.isValid() ) {
task->setStartTime( t );
}
t = task->estimate()->calendar()->firstAvailableBefore( task->endTime(), task->startTime() );
if ( t.isValid() ) {
task->setEndTime( t );
}
} //else Fixed duration
task->setDuration( task->endTime() - task->startTime() );
cs->logInfo( i18n( "Scheduled task to start at %1 and finish at %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat) ), 1 );
}
void KPlatoRCPSScheduler::kplatoFromRCPSBackward()
{
QLocale locale;
//debugPlan<<"KPlatoRCPSScheduler::kplatoFromRCPSBackward:";
MainSchedule *cs = static_cast<MainSchedule*>( m_project->currentSchedule() );
QMap<Node*, QList<ResourceRequest*> > resourcemap;
int count = rcps_job_count( m_problem );
int step = ( PROGRESS_MAX_VALUE - m_progressinfo->progress ) / count;
DateTime projectstart = fromRcpsTime( rcps_job_getstart_res( m_jobend ) );
for ( int i = 0; i < count; ++i ) {
m_progressinfo->progress += step;
m_manager->setProgress( m_progressinfo->progress );
setProgress( m_progressinfo->progress );
struct rcps_job *job = rcps_job_get( m_problem, i );
Task *task = m_taskmap.value( job );
if ( task == 0 ) {
continue; //might be dummy task for lag, ...
}
taskFromRCPSBackward( job, task, resourcemap );
if ( projectstart > task->startTime() ) {
projectstart = task->startTime();
}
}
DateTime end = fromRcpsTime( rcps_job_getstart_res( m_jobstart ) );
m_project->setStartTime( projectstart );
m_project->setEndTime( end );
cs->logInfo( i18n( "Project scheduled to start at %1 and finish at %2", locale.toString(projectstart, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat) ), 1 );
if ( projectstart < m_project->constraintStartTime() ) {
cs->setConstraintError( true );
cs->logError( i18n( "Must start project early in order to finish in time: %1", locale.toString(m_project->constraintStartTime(), QLocale::ShortFormat) ), 1 );
}
adjustSummaryTasks( m_schedule->summaryTasks() );
calculatePertValues( resourcemap );
if ( m_manager ) {
cs->logDebug( QString( "Project scheduling finished at %1" ).arg( QDateTime::currentDateTime().toString() ), 1 );
m_project->finishCalculation( *m_manager );
m_manager->scheduleChanged( cs );
}
}
void KPlatoRCPSScheduler::adjustSummaryTasks( const QList<Node*> &nodes )
{
foreach ( Node *n, nodes ) {
adjustSummaryTasks( n->childNodeIterator() );
if ( n->parentNode()->type() == Node::Type_Summarytask ) {
DateTime pt = n->parentNode()->startTime();
DateTime nt = n->startTime();
if ( ! pt.isValid() || pt > nt ) {
n->parentNode()->setStartTime( nt );
}
pt = n->parentNode()->endTime();
nt = n->endTime();
if ( ! pt.isValid() || pt < nt ) {
n->parentNode()->setEndTime( nt );
}
}
}
}
void KPlatoRCPSScheduler::calculatePertValues( const QMap<Node*, QList<ResourceRequest*> > &map )
{
if ( m_manager ) {
m_schedule->setPhaseName( 2, i18nc( "Project Evaluation and Review Technique", "PERT" ) );
}
foreach ( Node *n, m_project->allNodes() ) {
if ( n->type() != Node::Type_Task && n->type() != Node::Type_Milestone ) {
continue;
}
Task *t = static_cast<Task*>( n );
if ( n->isStartNode() ) {
(void)calculateLateStuff( map, static_cast<Task*>( n ) );
}
if ( n->isEndNode() ) {
(void)calculateEarlyStuff( map, static_cast<Task*>( n ) );
}
switch ( n->constraint() ) {
case Node::StartNotEarlier:
n->schedule()->setNegativeFloat( n->startTime() < n->constraintStartTime()
? n->startTime() - n->constraintStartTime()
: Duration::zeroDuration );
break;
case Node::MustStartOn:
case Node::FixedInterval:
n->schedule()->setNegativeFloat( n->startTime() > n->constraintStartTime()
? n->startTime() - n->constraintStartTime()
: n->constraintStartTime() - n->startTime() );
break;
case Node::FinishNotLater:
n->schedule()->setNegativeFloat( n->endTime() > n->constraintEndTime()
? n->endTime() - n->constraintEndTime()
: Duration::zeroDuration );
break;
case Node::MustFinishOn:
n->schedule()->setNegativeFloat( n->endTime() > n->constraintEndTime()
? n->endTime() - n->constraintEndTime()
: n->constraintEndTime() - n->endTime() );
break;
default:
break;
}
if ( t->negativeFloat() != 0 ) {
n->schedule()->setConstraintError( true );
n->schedule()->logError( i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", n->constraintToString( true ), KFormat().formatDuration( t->negativeFloat().milliseconds() ) ) );
}
}
}
Duration KPlatoRCPSScheduler::calculateLateStuff( const QMap<Node*, QList<ResourceRequest*> > &map, Task *task )
{
Schedule *cs = task->currentSchedule();
Duration pf = m_project->endTime() - m_project->startTime();
QList<Relation*> lst = task->dependChildNodes() + task->childProxyRelations();
if ( lst.isEmpty() ) {
// end node
DateTime end = task->endTime();
if ( task->estimate()->type() == Estimate::Type_Effort ) {
foreach ( ResourceRequest *r, map.value( static_cast<Node*>( task ) ) ) {
DateTime x = r->resource()->availableBefore( m_project->endTime(), task->endTime(), 0 );
cs->logDebug( QString( "Resource '%1' available before %2: %3" ).arg( r->resource()->name() ).arg( m_project->endTime().toString() ).arg( x.toString() ), 2 );
if ( x.isValid() && x > end ) {
end = x;
}
}
} else if ( task->estimate()->calendar() ) {
end = task->estimate()->calendar()->firstAvailableBefore( m_project->endTime(), task->endTime() );
cs->logDebug( QString( "Calendar work time before %1: %2" ).arg( m_project->endTime().toString() ).arg( end.toString() ), 2 );
}
// TODO must calculate backwards to get late *start* of task
pf = end.isValid() ? end - task->endTime() : m_project->endTime() - task->endTime();
task->setFreeFloat( pf );
} else {
Duration prev = pf;
Duration x = pf;
foreach ( Relation *r, lst ) {
prev = qMin( prev, calculateLateStuff( map, static_cast<Task*>( r->child() ) ) );
DateTime end = task->endTime();
if ( task->estimate()->type() == Estimate::Type_Effort ) {
foreach ( ResourceRequest *req, map.value( static_cast<Node*>( task ) ) ) {
DateTime y = req->resource()->availableBefore( r->child()->startTime(), task->endTime(), 0 );
cs->logDebug( QString( "Resource '%1' available before %2: %3" ).arg( req->resource()->name() ).arg( r->child()->startTime().toString() ).arg( y.toString() ), 2 );
if ( y.isValid() && y > end ) {
end = y;
}
}
} else if ( task->estimate()->calendar() ) {
end = task->estimate()->calendar()->firstAvailableBefore( r->child()->startTime(), task->endTime() );
cs->logDebug( QString( "Calendar work time before %1: %2" ).arg( r->child()->startTime().toString() ).arg( end.toString() ), 2 );
}
x = qMin( x, end.isValid() ? end - task->endTime() : r->child()->startTime() - task->endTime() );
}
task->setFreeFloat( x );
// TODO must calculate backwards to get late *start* of task
pf = prev + x;
}
task->setPositiveFloat( pf );
task->setLateFinish( task->endTime() + pf );
task->setLateStart( task->lateFinish() - ( task->endTime() - task->startTime() ) );
QLocale locale;
cs->logDebug( QString( "Late start %1, late finish %2, positive float %3" ).arg( locale.toString(task->lateStart(), QLocale::ShortFormat ) ).arg( locale.toString(task->lateFinish(), QLocale::ShortFormat) ).arg( pf.toString() ), 2 );
return pf;
}
Duration KPlatoRCPSScheduler::calculateEarlyStuff( const QMap<Node*, QList<ResourceRequest*> > &map, Task *task )
{
Schedule *cs = task->currentSchedule();
Duration tot = m_project->endTime() - m_project->startTime();
QList<Relation*> lst = task->dependParentNodes() + task->parentProxyRelations();
if ( lst.isEmpty() ) {
// start node
DateTime earlystart = task->startTime();
if ( task->estimate()->type() == Estimate::Type_Effort ) {
foreach ( ResourceRequest *r, map.value( static_cast<Node*>( task ) ) ) {
DateTime x = r->resource()->availableAfter( m_project->startTime(), task->startTime(), 0 );
cs->logDebug( QString( "Resource '%1' available after %2 (%4): %3" ).arg( r->resource()->name() ).arg( m_project->startTime().toString() ).arg( x.toString() ).arg( task->startTime().toString() ), 2 );
if ( x.isValid() && x < earlystart ) {
earlystart = x;
}
}
} else if ( task->estimate()->calendar() ) {
earlystart = task->estimate()->calendar()->firstAvailableAfter( m_project->startTime(), task->startTime() );
cs->logDebug( QString( "Calendar work time after %1: %2" ).arg( m_project->startTime().toString() ).arg( earlystart.toString() ), 2 );
}
// TODO must calculate forward to get early *????* of task
tot = earlystart.isValid() ? task->startTime() - earlystart : task->startTime() - m_project->startTime();
} else {
Duration prev = tot;
Duration x = tot;
foreach ( Relation *r, lst ) {
prev = qMin( prev, calculateEarlyStuff( map, static_cast<Task*>( r->parent() ) ) );
DateTime earlystart = task->startTime();
if ( task->estimate()->type() == Estimate::Type_Effort ) {
foreach ( ResourceRequest *req, map.value( static_cast<Node*>( task ) ) ) {
DateTime y = req->resource()->availableAfter( r->parent()->endTime(), task->startTime(), 0 );
cs->logDebug( QString( "Resource '%1' available after %2: %3" ).arg( req->resource()->name() ).arg( r->parent()->endTime().toString() ).arg( y.toString() ), 2 );
if ( y.isValid() && y < earlystart ) {
earlystart = y;
}
}
} else if ( task->estimate()->calendar() ) {
earlystart = task->estimate()->calendar()->firstAvailableAfter( r->parent()->endTime(), task->startTime() );
cs->logDebug( QString( "Calendar work time after %1: %2" ).arg( r->parent()->endTime().toString() ).arg( earlystart.toString() ), 2 );
}
x = qMin( x, earlystart.isValid() ? task->startTime() - earlystart : task->startTime() - r->parent()->startTime() );
}
// TODO must calculate backwards to get late *start* of task
tot = prev + x;
}
task->setEarlyStart( task->startTime() - tot );
task->setEarlyFinish( task->earlyStart() + ( task->endTime() - task->startTime() ) );
QLocale locale;
cs->logDebug( QString( "Early start %1, early finish %2" ).arg( locale.toString(task->earlyStart(), QLocale::ShortFormat) ).arg( locale.toString(task->earlyFinish(), QLocale::ShortFormat) ), 2 );
return tot;
}
struct rcps_resource *KPlatoRCPSScheduler::addResource( KPlato::Resource *r)
{
if ( m_resourcemap.values().contains( r ) ) {
warnPlan<<r->name()<<"already exist";
return 0;
}
struct rcps_resource *res = rcps_resource_new();
rcps_resource_setname( res, r->name().toLocal8Bit().data() );
rcps_resource_setavail( res, r->units() );
rcps_resource_add( m_problem, res );
m_resourcemap[res] = r;
return res;
}
void KPlatoRCPSScheduler::addResources()
{
debugPlan;
QList<Resource*> list = m_project->resourceList();
for (int i = 0; i < list.count(); ++i) {
addResource( list.at(i) );
}
}
int KPlatoRCPSScheduler::toRcpsTime( const DateTime &time ) const
{
return ( m_backward ? time.secsTo( m_starttime ) : m_starttime.secsTo( time ) ) / m_timeunit;
}
DateTime KPlatoRCPSScheduler::fromRcpsTime( int time ) const
{
return m_starttime.addSecs( ( m_backward ? -time : time ) * m_timeunit );
}
struct rcps_job *KPlatoRCPSScheduler::addTask( KPlato::Task *task )
{
struct rcps_job *job = rcps_job_new();
rcps_job_setname( job, task->name().toLocal8Bit().data() );
rcps_job_add( m_problem, job );
m_taskmap[job] = task;
return job;
}
void KPlatoRCPSScheduler::addTasks()
{
debugPlan;
// Add a start job
m_jobstart = rcps_job_new();
rcps_job_setname( m_jobstart, "RCPS start job" );
rcps_job_add( m_problem, m_jobstart );
struct rcps_mode *mode = rcps_mode_new();
rcps_mode_add( m_jobstart, mode );
QList<Node*> list = m_project->allNodes();
for (int i = 0; i < list.count(); ++i) {
Node *n = list.at(i);
switch ( n->type() ) {
case Node::Type_Summarytask:
m_schedule->insertSummaryTask( n );
break;
case Node::Type_Task:
case Node::Type_Milestone:
addTask( static_cast<Task*>( n ) );
break;
default:
break;
}
}
// Add an end job
m_jobend = rcps_job_new();
rcps_job_setname( m_jobend, "RCPS end job" );
rcps_job_add( m_problem, m_jobend );
mode = rcps_mode_new();
rcps_mode_add( m_jobend, mode );
// add a weight callback argument
struct KPlatoRCPSScheduler::weight_info *info = new KPlatoRCPSScheduler::weight_info;
info->self = this;
info->task = 0;
info->targettime = toRcpsTime( m_targettime );
info->isEndJob = true;
info->finish = 0;
rcps_mode_set_weight_cbarg( mode, info );
m_weight_info_list[ m_jobend ] = info;
for( int i = 0; i < rcps_job_count( m_problem ); ++i ) {
debugPlan<<"Task:"<<rcps_job_getname( rcps_job_get(m_problem, i) );
}
}
struct rcps_job *KPlatoRCPSScheduler::addJob( const QString &name, int duration )
{
struct rcps_job *job = rcps_job_new();
rcps_job_setname( job, name.toLocal8Bit().data() );
rcps_job_add( m_problem, job );
struct rcps_mode *mode = rcps_mode_new();
rcps_mode_setduration( mode, duration );
rcps_mode_add( job, mode );
return job;
}
void KPlatoRCPSScheduler::addDependenciesForward( struct rcps_job *job, KPlato::Task *task )
{
if ( task->dependParentNodes().isEmpty() && task->parentProxyRelations().isEmpty() ) {
rcps_job_successor_add( m_jobstart, job, SUCCESSOR_FINISH_START );
}
if ( task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty() ) {
rcps_job_successor_add( job, m_jobend, SUCCESSOR_FINISH_START );
}
foreach ( Relation *r, task->dependChildNodes() ) {
Node *n = r->child();
if ( n == 0 || n->type() == Node::Type_Summarytask ) {
continue;
}
int type = SUCCESSOR_FINISH_START;
switch ( r->type() ) {
case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
case Relation::StartStart: type = SUCCESSOR_START_START; break;
}
if ( r->lag() == Duration::zeroDuration ) {
rcps_job_successor_add( job, m_taskmap.key( static_cast<Task*>( n ) ), type );
} else {
// Add a dummy job to represent the lag
struct rcps_job *dummy = addJob( r->lag().toString(), r->lag().seconds() / m_timeunit );
rcps_job_successor_add( job, dummy, type );
int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
rcps_job_successor_add( dummy, m_taskmap.key( static_cast<Task*>( n ) ), t );
}
}
foreach ( Relation *r, task->childProxyRelations() ) {
Node *n = r->child();
if ( n == 0 || n->type() == Node::Type_Summarytask ) {
continue;
}
int type = SUCCESSOR_FINISH_START;
switch ( r->type() ) {
case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
case Relation::StartStart: type = SUCCESSOR_START_START; break;
}
if ( r->lag() == Duration::zeroDuration ) {
rcps_job_successor_add( job, m_taskmap.key( static_cast<Task*>( n ) ), type );
} else {
// Add a dummy job to represent the lag
struct rcps_job *dummy = addJob( r->lag().toString(), r->lag().seconds() / m_timeunit );
rcps_job_successor_add( job, dummy, type );
int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
rcps_job_successor_add( dummy, m_taskmap.key( static_cast<Task*>( n ) ), t );
}
}
}
void KPlatoRCPSScheduler::addDependenciesBackward( struct rcps_job *job, KPlato::Task *task )
{
if ( task->dependParentNodes().isEmpty() && task->parentProxyRelations().isEmpty() ) {
rcps_job_successor_add( job, m_jobend, SUCCESSOR_FINISH_START );
//debugPlan<<rcps_job_getname( job )<<"->"<<rcps_job_getname( m_jobend );
}
if ( task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty() ) {
rcps_job_successor_add( m_jobstart, job, SUCCESSOR_FINISH_START );
//debugPlan<<rcps_job_getname( m_jobstart )<<"->"<<rcps_job_getname( job );
}
foreach ( Relation *r, task->dependParentNodes() ) {
Node *n = r->parent();
if ( n == 0 || n->type() == Node::Type_Summarytask ) {
continue;
}
int type = SUCCESSOR_FINISH_START;
switch ( r->type() ) {
case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
case Relation::StartStart: type = SUCCESSOR_START_START; break;
}
if ( r->lag() == Duration::zeroDuration ) {
rcps_job_successor_add( job, m_taskmap.key( static_cast<Task*>( n ) ), type );
//debugPlan<<rcps_job_getname( job )<<"->"<<rcps_job_getname( m_taskmap.key( static_cast<Task*>( n ) ) )<<type;
} else {
// Add a dummy job to represent the lag
struct rcps_job *dummy = addJob( r->lag().toString(), r->lag().seconds() / m_timeunit );
rcps_job_successor_add( job, dummy, type );
debugPlan<<rcps_job_getname( job )<<"->"<<"dummy lag"<<type;
int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
rcps_job_successor_add( dummy, m_taskmap.key( static_cast<Task*>( n ) ), t );
//debugPlan<<"dummy lag"<<"->"<<rcps_job_getname( m_taskmap.key( static_cast<Task*>( n ) ) )<<t;
}
}
foreach ( Relation *r, task->parentProxyRelations() ) {
Node *n = r->parent();
if ( n == 0 || n->type() == Node::Type_Summarytask ) {
continue;
}
int type = SUCCESSOR_FINISH_START;
switch ( r->type() ) {
case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
case Relation::StartStart: type = SUCCESSOR_START_START; break;
}
if ( r->lag() == Duration::zeroDuration ) {
rcps_job_successor_add( job, m_taskmap.key( static_cast<Task*>( n ) ), type );
//debugPlan<<rcps_job_getname( job )<<"->"<<rcps_job_getname( m_taskmap.key( static_cast<Task*>( n ) ) )<<type;
} else {
// Add a dummy job to represent the lag
struct rcps_job *dummy = addJob( r->lag().toString(), r->lag().seconds() / m_timeunit );
rcps_job_successor_add( job, dummy, type );
debugPlan<<rcps_job_getname( job )<<"->"<<"dummy lag"<<type;
int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
rcps_job_successor_add( dummy, m_taskmap.key( static_cast<Task*>( n ) ), t );
//debugPlan<<"dummy lag"<<"->"<<rcps_job_getname( m_taskmap.key( static_cast<Task*>( n ) ) )<<t;
}
}
}
void KPlatoRCPSScheduler::addDependencies()
{
debugPlan;
QMap<struct rcps_job*, Task*> ::const_iterator it = m_taskmap.constBegin();
for ( ; it != m_taskmap.constEnd(); ++it ) {
if ( m_backward ) {
addDependenciesBackward( it.key(), it.value() );
} else {
addDependenciesForward( it.key(), it.value() );
}
}
}
void KPlatoRCPSScheduler::addRequests()
{
debugPlan;
QMap<struct rcps_job*, Task*> ::const_iterator it = m_taskmap.constBegin();
for ( ; it != m_taskmap.constEnd(); ++it ) {
addRequest( it.key(), it.value() );
}
}
void KPlatoRCPSScheduler::addRequest( rcps_job *job, Task *task )
{
debugPlan;
struct rcps_mode *mode = rcps_mode_new();
rcps_mode_add( job, mode );
// add a weight callback argument
struct KPlatoRCPSScheduler::weight_info *wi = new KPlatoRCPSScheduler::weight_info;
wi->self = this;
wi->task = task;
wi->targettime = 0;
wi->isEndJob = false;
wi->finish = 0;
rcps_mode_set_weight_cbarg( mode, wi );
m_weight_info_list[ job ] = wi;
if ( task->constraint() != Node::FixedInterval ) {
if ( task->type() == Node::Type_Milestone || task->estimate() == 0 || ( m_recalculate && task->completion().isFinished() ) ) {
rcps_mode_setduration(mode, 0);
return;
}
if ( task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() == 0 ) {
// Fixed duration, no duration callback needed
rcps_mode_setduration(mode, task->estimate()->value( Estimate::Use_Expected, m_usePert ).seconds() / m_timeunit );
return;
}
}
/* set the argument for the duration callback */
struct KPlatoRCPSScheduler::duration_info *info = new KPlatoRCPSScheduler::duration_info;
info->self = this;
info->calls = 0;
info->task = task;
if ( m_recalculate && task->completion().isStarted() ) {
info->estimate = task->completion().remainingEffort();
} else {
info->estimate = task->estimate()->value( Estimate::Use_Expected, m_usePert );
}
info->requests = task->requests().resourceRequests(); // returns team members (not team resource itself)
info->estimatetype = task->estimate()->type();
rcps_mode_set_cbarg( mode, info );
m_duration_info_list[ job ] = info;
foreach ( ResourceRequest *rr, info->requests ) {
Resource *r = rr->resource();
if ( r->type() == Resource::Type_Team ) {
warnPlan<<"There should not be any request to a team resource:"<<r->name();
continue;
}
struct rcps_request *req = rcps_request_new();
rcps_request_add( mode, req );
m_requestmap[ req ] = rr;
struct rcps_alternative *alt = rcps_alternative_new();
rcps_alternative_setresource( alt, m_resourcemap.key( r ) );
rcps_alternative_setamount( alt, (double)rr->units() * 100 / r->units() );
rcps_alternative_add( req, alt );
}
}
void KPlatoRCPSScheduler::setConstraints()
{
for ( QMap<struct rcps_job*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it ) {
Task *task = it.value();
struct rcps_job *job = it.key();
struct weight_info *wi = m_weight_info_list.value( job );
struct duration_info *di = m_duration_info_list.value( job );
switch ( task->constraint() ) {
case Node::MustStartOn:
case Node::StartNotEarlier:
wi->targettime = toRcpsTime( task->constraintStartTime() );
if ( m_backward ) {
int d = 0;
if ( di ) {
// as m_backward == true, DURATION_BACKWARD in rcps means forward in plan
d = duration( DURATION_BACKWARD, wi->targettime, 0, di );
}
wi->targettime -= d;
}
rcps_job_setearliest_start( job, wi->targettime );
task->currentSchedule()->logDebug( QString( "%2 %3 %4: %5 (rcps=%6)")
.arg( task->constraintToString() )
.arg( m_backward?"backward":"forward")
.arg( task->constraintStartTime().toString() )
.arg( fromRcpsTime( wi->targettime ).toString() )
.arg( wi->targettime ) );
break;
case Node::MustFinishOn:
wi->targettime = toRcpsTime( task->constraintEndTime() );
if ( ! m_backward ) {
int d = 0;
if ( di ) {
d = duration( DURATION_BACKWARD, wi->targettime, 0, di );
}
rcps_job_setearliest_start( job, wi->targettime - d );
}
break;
case Node::FinishNotLater:
wi->targettime = toRcpsTime( task->constraintEndTime() );
if ( m_backward ) {
rcps_job_setearliest_start( job, wi->targettime );
}
break;
case Node::FixedInterval:
wi->targettime = m_backward ? toRcpsTime( task->constraintEndTime() ) : toRcpsTime( task->constraintStartTime() );
rcps_job_setearliest_start( job, wi->targettime );
break;
default:
break;
}
}
}
void KPlatoRCPSScheduler::setWeights()
{
for ( QMap<struct rcps_job*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it ) {
Task *task = it.value();
struct rcps_job *job = it.key();
if ( m_backward ) {
switch ( task->constraint() ) {
case Node::ASAP:
rcps_job_setweight( job, WEIGHT_ALAP );
break;
case Node::ALAP:
rcps_job_setweight( job, WEIGHT_ASAP );
break;
case Node::StartNotEarlier:
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::MustStartOn:
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::FinishNotLater:
rcps_job_setearliest_start( job, toRcpsTime( task->constraintEndTime() ) );
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::MustFinishOn:
rcps_job_setearliest_start( job, toRcpsTime( task->constraintEndTime() ) );
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::FixedInterval:
rcps_job_setearliest_start( job, toRcpsTime( task->constraintEndTime() ) );
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
default:
rcps_job_setweight( job, WEIGHT_ASAP );
break;
}
} else {
switch ( task->constraint() ) {
case Node::ASAP:
rcps_job_setweight( job, WEIGHT_ASAP );
break;
case Node::ALAP:
rcps_job_setweight( job, WEIGHT_ALAP );
break;
case Node::StartNotEarlier:
rcps_job_setearliest_start( job, toRcpsTime( task->constraintStartTime() ) );
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::MustStartOn:
rcps_job_setearliest_start( job, toRcpsTime( task->constraintStartTime() ) );
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::FinishNotLater:
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::MustFinishOn:
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
case Node::FixedInterval:
rcps_job_setearliest_start( job, toRcpsTime( task->constraintStartTime() ) );
rcps_job_setweight( job, WEIGHT_CONSTRAINT );
break;
default:
rcps_job_setweight( job, WEIGHT_ASAP );
break;
}
}
}
}
diff --git a/src/plugins/schedulers/rcps/tests/ProjectTester.cpp b/src/plugins/schedulers/rcps/tests/ProjectTester.cpp
index 61bec6dc..143e4bab 100644
--- a/src/plugins/schedulers/rcps/tests/ProjectTester.cpp
+++ b/src/plugins/schedulers/rcps/tests/ProjectTester.cpp
@@ -1,669 +1,670 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2011 Dag Andersen <danders@get2net.dk>
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 "ProjectTester.h"
#include "KPlatoRCPSPlugin.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include <QTest>
#include "tests/DateTimeTester.h"
#include "tests/debug.cpp"
namespace KPlato
{
static ResourceGroup *createWorkResources( Project &p, int count )
{
ResourceGroup *g = new ResourceGroup();
g->setName( "G1" );
p.addResourceGroup( g );
for ( int i = 0; i < count; ++i ) {
Resource *r = new Resource();
r->setName( QString( "R%1" ).arg( i + 1 ) );
p.addResource( g, r );
}
return g;
}
static void createRequest( Task *t, Resource *r )
{
ResourceGroupRequest *gr = t->requests().find( r->parentGroup() );
if ( gr == 0 ) {
gr = new ResourceGroupRequest( r->parentGroup() );
}
t->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
}
static Calendar *createCalendar( Project &p )
{
Calendar *c = new Calendar();
c->setDefault( true );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
for ( int i=1; i <= 7; ++i ) {
CalendarDay *d = c->weekday( i );
d->setState( CalendarDay::Working );
d->addInterval( t1, length );
}
p.addCalendar( c );
return c;
}
void ProjectTester::initTestCase()
{
m_project = new Project();
m_project->setName( "P1" );
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId( m_project );
m_project->setConstraintStartTime( DateTime::fromString( "2012-02-01 00:00" ) );
m_project->setConstraintEndTime( m_project->constraintStartTime().addDays( 7 ) );
// standard worktime defines 8 hour day as default
QVERIFY( m_project->standardWorktime() );
QCOMPARE( m_project->standardWorktime()->day(), 8.0 );
m_calendar = createCalendar( *m_project );
m_task = 0;
qDebug()<<"Project:"<<m_project->constraintStartTime()<<m_project->constraintEndTime();
Debug::print( m_project, "Initiated to:" );
}
void ProjectTester::cleanupTestCase()
{
qDebug()<<this<<m_project;
delete m_project;
}
void ProjectTester::oneTask()
{
QDate today = QDate::fromString( "2012-02-01", Qt::ISODate );
QDate tomorrow = today.addDays( 1 );
QDate yesterday = today.addDays( -1 );
QTime t1( 9, 0, 0 );
QTime t2 ( 17, 0, 0 );
int length = t1.msecsTo( t2 );
Task *t = m_project->createTask();
t->setName( "T1" );
m_project->addTask( t, m_project );
t->estimate()->setUnit( Duration::Unit_d );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Duration );
ScheduleManager *sm = m_project->createScheduleManager( "Test Plan" );
m_project->addScheduleManager( sm );
QString s = "Calculate forward, Task: Fixed duration ------------------------------";
qDebug()<<s;
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::printSchedulingLog( *sm, s );
Debug::print( m_project, s );
Debug::print( t, s );
QCOMPARE( t->startTime(), m_project->startTime() );
QCOMPARE( t->endTime(), DateTime(t->startTime().addDays( 1 )) );
s = "Calculate forward, Task: Length --------------------------------------";
qDebug()<<s;
t->estimate()->setCalendar( m_calendar );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( m_calendar, s );
Debug::print( m_project, s, true );
QCOMPARE( t->startTime(), m_calendar->firstAvailableAfter( m_project->startTime(), m_project->endTime() ) );
QCOMPARE( t->endTime(), DateTime( t->startTime().addMSecs( length ) ) );
s = "Calculate forward, Task: Effort --------------------------------------";
qDebug()<<s;
ResourceGroup *g = new ResourceGroup();
m_project->addResourceGroup( g );
Resource *r = new Resource();
r->setAvailableFrom( QDateTime( yesterday, QTime(), Qt::LocalTime ) );
r->setCalendar( m_calendar );
m_project->addResource( g, r );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
t->addRequest( gr );
ResourceRequest *rr = new ResourceRequest( r, 100 );
gr->addResourceRequest( rr );
t->estimate()->setType( Estimate::Type_Effort );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->startTime(), m_calendar->firstAvailableAfter( m_project->startTime(), m_project->endTime() ) );
QCOMPARE( t->endTime(), DateTime( t->startTime().addMSecs( length ) ) );
s = "Calculate forward, Task: MustStartOn --------------------------------------";
qDebug()<<s;
t->setConstraint( Node::MustStartOn );
t->setConstraintStartTime( DateTime( tomorrow, t1 ) );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate backward, Task: MustStartOn --------------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( true );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
Debug::printSchedulingLog( *sm, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate backward, Task: StartNotEarlier --------------------------------------";
qDebug()<<s;
t->setConstraint( Node::StartNotEarlier );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate forward, Task: StartNotEarlier --------------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( false );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate forward, Task: MustFinishOn --------------------------------------";
qDebug()<<s;
t->setConstraint( Node::MustFinishOn );
t->setConstraintEndTime( DateTime( tomorrow, t2 ) );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate backward, Task: MustFinishOn --------------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( true );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate backward, Task: FinishNotLater --------------------------------------";
qDebug()<<s;
t->setConstraint( Node::FinishNotLater );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->endTime(), t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate forward, Task: FinishNotLater --------------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( false );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( m_project, s, true );
QVERIFY( t->endTime() <= t->constraintEndTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 8, 0 ) );
s = "Calculate forward, Task: FixedInterval --------------------------------------";
qDebug()<<s;
t->setConstraint( Node ::FixedInterval );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->constraintEndTime() );
s = "Calculate backward, Task: FixedInterval --------------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( true );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( *m_project, sm, true/*nothread*/ );
}
Debug::print( t, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
QCOMPARE( t->endTime(), t->constraintEndTime() );
}
void ProjectTester::team()
{
Project project;
project.setName( "P1" );
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
DateTime targetstart = DateTime( QDate( 2010, 5, 1 ), QTime(0,0,0) );
DateTime targetend = DateTime( targetstart.addDays( 7 ) );
project.setConstraintStartTime( targetstart );
project.setConstraintEndTime( targetend);
Calendar *c = new Calendar("Test");
QTime t1(8,0,0);
int length = 8*60*60*1000; // 8 hours
for ( int i = 1; i <= 7; ++i ) {
CalendarDay *wd1 = c->weekday(i);
wd1->setState(CalendarDay::Working);
wd1->addInterval(TimeInterval(t1, length));
}
project.addCalendar( c );
Task *task1 = project.createTask();
task1->setName( "T1" );
project.addTask( task1, &project );
task1->estimate()->setUnit( Duration::Unit_d );
task1->estimate()->setExpectedEstimate( 2.0 );
task1->estimate()->setType( Estimate::Type_Effort );
QString s = "One team with one resource --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceGroup *g = new ResourceGroup();
project.addResourceGroup( g );
Resource *r1 = new Resource();
r1->setName( "R1" );
r1->setCalendar( c );
project.addResource( g, r1 );
Resource *r2 = new Resource();
r2->setName( "Team member" );
r2->setCalendar( c );
project.addResource( g, r2 );
Resource *team = new Resource();
team->setType( Resource::Type_Team );
team->setName( "Team" );
team->addTeamMemberId( r2->id() );
project.addResource( g, team );
ResourceGroupRequest *gr = new ResourceGroupRequest( g );
task1->addRequest( gr );
ResourceRequest *tr = new ResourceRequest( team, 100 );
gr->addResourceRequest( tr );
ScheduleManager *sm = project.createScheduleManager( "Team" );
project.addScheduleManager( sm );
sm->createSchedules();
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
DateTime expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with one resource + one resource --------";
qDebug()<<endl<<"Testing:"<<s;
ResourceRequest *rr1 = new ResourceRequest( r1, 100 );
gr->addResourceRequest( rr1 );
sm = project.createScheduleManager( "Team + Resource" );
project.addScheduleManager( sm );
sm->createSchedules();
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
// Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 0, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with one resource + one resource, resource available too late --------";
qDebug()<<endl<<"Testing:"<<s;
r1->setAvailableFrom( targetend );
r1->setAvailableUntil( targetend.addDays( 7 ) );
sm = project.createScheduleManager( "Team + Resource not available" );
project.addScheduleManager( sm );
sm->createSchedules();
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with two resources --------";
qDebug()<<endl<<"Testing:"<<s;
r1->removeRequests();
team->addTeamMemberId( r1->id() );
r1->setAvailableFrom( targetstart );
r1->setAvailableUntil( targetend );
sm = project.createScheduleManager( "Team with 2 resources" );
project.addScheduleManager( sm );
sm->createSchedules();
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
// Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 0, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
s = "One team with two resources, one resource unavailable --------";
qDebug()<<endl<<"Testing:"<<s;
r1->setAvailableFrom( targetend );
r1->setAvailableUntil( targetend.addDays( 2 ) );
sm = project.createScheduleManager( "Team, one unavailable resource" );
project.addScheduleManager( sm );
sm->createSchedules();
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( r1, s);
// Debug::print( r2, s);
Debug::print( team, s, false);
Debug::print( &project, task1, s);
Debug::printSchedulingLog( *sm, s );
expectedEndTime = targetstart + Duration( 1, 16, 0 );
QCOMPARE( task1->endTime(), expectedEndTime );
}
void ProjectTester::mustStartOn()
{
return;
Project project;
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
project.setConstraintStartTime( DateTime::fromString( "2011-01-01T00:00:00" ) );
project.setConstraintEndTime( DateTime::fromString( "2011-01-12T00:00:00" ) );
createCalendar( project );
ResourceGroup *g = createWorkResources( project, 1 );
Task *t = project.createTask();
t->setName( "T1" );
project.addTask( t, &project );
t->estimate()->setUnit( Duration::Unit_h );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
createRequest( t, g->resourceAt( 0 ) );
t->setConstraint( Node::MustStartOn );
t->setConstraintStartTime( DateTime::fromString( "2011-01-01T11:00:00" ) );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
QString s = "Calculate forward, Task: MustStartOn ------------------------------";
qDebug()<<s;
Debug::print( t, s );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( &project, s );
Debug::print( t, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
s = "Calculate forward, 2 Tasks ------------------------------";
qDebug()<<s;
Task *t2 = project.createTask();
t2->setName( "T2" );
project.addTask( t2, &project );
t2->estimate()->setUnit( Duration::Unit_d );
t2->estimate()->setExpectedEstimate( 10.0 );
t2->estimate()->setType( Estimate::Type_Effort );
createRequest( t2, g->resourceAt( 0 ) );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( &project, s );
Debug::print( t, s );
Debug::print( t2, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
s = "Calculate backward, 2 Tasks ------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( true );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( &project, s );
Debug::print( t, s );
Debug::print( t2, s );
QCOMPARE( t->startTime(), t->constraintStartTime() );
}
void ProjectTester::startNotEarlier()
{
Project project;
project.setId( project.uniqueNodeId() );
project.registerNodeId( &project );
project.setConstraintStartTime( DateTime::fromString( "2011-01-01T00:00:00" ) );
project.setConstraintEndTime( DateTime::fromString( "2011-01-12T00:00:00" ) );
createCalendar( project );
ResourceGroup *g = createWorkResources( project, 1 );
Task *t = project.createTask();
t->setName( "T1" );
project.addTask( t, &project );
t->estimate()->setUnit( Duration::Unit_h );
t->estimate()->setExpectedEstimate( 1.0 );
t->estimate()->setType( Estimate::Type_Effort );
createRequest( t, g->resourceAt( 0 ) );
t->setConstraint( Node::StartNotEarlier );
t->setConstraintStartTime( DateTime::fromString( "2011-01-02T11:00:00" ) );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
QString s = "Calculate forward, Task: StartNotEarlier ------------------------------";
qDebug()<<s;
Debug::print( t, s );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( &project, s );
Debug::print( t, s );
QVERIFY( t->startTime() >= t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 1, 0 ) );
s = "Calculate forward, 2 Tasks ------------------------------";
qDebug()<<s;
Task *t2 = project.createTask();
t2->setName( "T2" );
project.addTask( t2, &project );
t2->estimate()->setUnit( Duration::Unit_d );
t2->estimate()->setExpectedEstimate( 7.0 );
t2->estimate()->setType( Estimate::Type_Effort );
createRequest( t2, g->resourceAt( 0 ) );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( &project, s );
Debug::print( t, s );
Debug::print( t2, s );
QVERIFY( t->startTime() >= t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 1, 0 ) );
s = "Calculate backward, 2 Tasks ------------------------------";
qDebug()<<s;
sm->setSchedulingDirection( true );
{
KPlatoRCPSPlugin rcps( 0, QVariantList() );
rcps.calculate( project, sm, true/*nothread*/ );
}
Debug::print( &project, s );
Debug::print( t, s );
Debug::print( t2, s );
Debug::printSchedulingLog( *sm, s );
QVERIFY( t->startTime() >= t->constraintStartTime() );
QCOMPARE( t->endTime(), t->startTime() + Duration( 0, 1, 0 ) );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::ProjectTester )
diff --git a/src/plugins/schedulers/tj/PlanTJPlugin.cpp b/src/plugins/schedulers/tj/PlanTJPlugin.cpp
index 99d8df20..478e05af 100644
--- a/src/plugins/schedulers/tj/PlanTJPlugin.cpp
+++ b/src/plugins/schedulers/tj/PlanTJPlugin.cpp
@@ -1,166 +1,167 @@
/* This file is part of the KDE project
* Copyright (C) 2009, 2011 Dag Andersen <danders@get2net.dk>
*
* 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 "PlanTJPlugin.h"
#include "kptschedulerplugin_macros.h"
#include "PlanTJScheduler.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptdebug.h"
#include <KLocalizedString>
#include <QApplication>
#ifndef PLAN_NOPLUGIN
PLAN_SCHEDULERPLUGIN_EXPORT(PlanTJPlugin, "plantjscheduler.json")
#endif
using namespace KPlato;
PlanTJPlugin::PlanTJPlugin( QObject * parent, const QVariantList & )
: KPlato::SchedulerPlugin(parent)
{
m_granularities << (long unsigned int) 5 * 60 * 1000
<< (long unsigned int) 15 * 60 * 1000
<< (long unsigned int) 30 * 60 * 1000
<< (long unsigned int) 60 * 60 * 1000;
}
PlanTJPlugin::~PlanTJPlugin()
{
}
QString PlanTJPlugin::description() const
{
return i18nc( "@info:whatsthis", "<title>TaskJuggler Scheduler</title>"
"<para>This is a slightly modified version of the scheduler used in TaskJuggler."
" It has been enhanced to handle resource units.</para>"
"<para>Scheduling backwards is simulated by scheduling all tasks as late as possible.</para>"
"<para><note>Plan does not utilize all of its functionality.</note></para>"
);
}
int PlanTJPlugin::capabilities() const
{
return SchedulerPlugin::AvoidOverbooking | SchedulerPlugin::ScheduleForward | SchedulerPlugin::ScheduleBackward;
}
ulong PlanTJPlugin::currentGranularity() const
{
ulong v = m_granularities.value( m_granularity );
return qMax( v, (ulong)300000 ); // minimum 5 min
}
void PlanTJPlugin::calculate( KPlato::Project &project, KPlato::ScheduleManager *sm, bool nothread )
{
foreach ( SchedulerThread *j, m_jobs ) {
if ( j->manager() == sm ) {
return;
}
}
sm->setScheduling( true );
PlanTJScheduler *job = new PlanTJScheduler( &project, sm, currentGranularity() );
m_jobs << job;
connect(job, &KPlato::SchedulerThread::jobFinished, this, &PlanTJPlugin::slotFinished);
project.changed( sm );
// connect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), &project, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
// connect(this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), &project, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)));
connect(job, &KPlato::SchedulerThread::maxProgressChanged, sm, &KPlato::ScheduleManager::setMaxProgress);
connect(job, &KPlato::SchedulerThread::progressChanged, sm, &KPlato::ScheduleManager::setProgress);
if ( nothread ) {
job->doRun();
} else {
job->start();
}
}
void PlanTJPlugin::stopAllCalculations()
{
foreach ( SchedulerThread *s, m_jobs ) {
stopCalculation( s );
}
}
void PlanTJPlugin::stopCalculation( SchedulerThread *sch )
{
if ( sch ) {
//FIXME: this should just call stopScheduling() and let the job finish "normally"
disconnect( sch, &KPlato::SchedulerThread::jobFinished, this, &PlanTJPlugin::slotFinished );
sch->stopScheduling();
// wait max 20 seconds.
sch->mainManager()->setCalculationResult( ScheduleManager::CalculationStopped );
if ( ! sch->wait( 20000 ) ) {
sch->deleteLater();
m_jobs.removeAt( m_jobs.indexOf( sch ) );
} else {
slotFinished( sch );
}
}
}
void PlanTJPlugin::slotStarted( SchedulerThread */*job*/ )
{
// debugPlan<<"PlanTJPlugin::slotStarted:";
}
void PlanTJPlugin::slotFinished( SchedulerThread *j )
{
PlanTJScheduler *job = static_cast<PlanTJScheduler*>( j );
Project *mp = job->mainProject();
ScheduleManager *sm = job->mainManager();
//debugPlan<<"PlanTJPlugin::slotFinished:"<<mp<<sm<<job->isStopped();
if ( job->isStopped() ) {
sm->setCalculationResult( ScheduleManager::CalculationCanceled );
} else {
updateLog( job );
if ( job->result > 0 ) {
sm->setCalculationResult( ScheduleManager::CalculationError );
} else {
Project *tp = job->project();
ScheduleManager *tm = job->manager();
updateProject( tp, tm, mp, sm );
sm->setCalculationResult( ScheduleManager::CalculationDone );
}
}
sm->setScheduling( false );
m_jobs.removeAt( m_jobs.indexOf( job ) );
if ( m_jobs.isEmpty() ) {
m_synctimer.stop();
}
emit sigCalculationFinished( mp, sm );
disconnect(this, &PlanTJPlugin::sigCalculationStarted, mp, &KPlato::Project::sigCalculationStarted);
disconnect(this, &PlanTJPlugin::sigCalculationFinished, mp, &KPlato::Project::sigCalculationFinished);
job->deleteLater();
}
#include "PlanTJPlugin.moc"
diff --git a/src/plugins/schedulers/tj/PlanTJScheduler.cpp b/src/plugins/schedulers/tj/PlanTJScheduler.cpp
index 8ee472bf..64432311 100644
--- a/src/plugins/schedulers/tj/PlanTJScheduler.cpp
+++ b/src/plugins/schedulers/tj/PlanTJScheduler.cpp
@@ -1,917 +1,918 @@
/* This file is part of the KDE project
* Copyright (C) 2009, 2010, 2011, 2012 Dag Andersen <danders@get2net.dk>
* Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
*
* 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 "PlanTJScheduler.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptresource.h"
#include "kpttask.h"
#include "kptrelation.h"
#include "kptduration.h"
#include "kptcalendar.h"
#include "kptdebug.h"
#include "taskjuggler/taskjuggler.h"
#include "taskjuggler/Project.h"
#include "taskjuggler/Scenario.h"
#include "taskjuggler/Resource.h"
#include "taskjuggler/Task.h"
#include "taskjuggler/Interval.h"
#include "taskjuggler/Allocation.h"
#include "taskjuggler/Utility.h"
#include "taskjuggler/UsageLimits.h"
#include "taskjuggler/CoreAttributes.h"
#include "taskjuggler/TjMessageHandler.h"
#include "taskjuggler/debug.h"
#include <QString>
#include <QTime>
#include <QMutexLocker>
#include <QMap>
#include <QLocale>
#include <KLocalizedString>
#include <KFormat>
#include <iostream>
#define PROGRESS_MAX_VALUE 100
PlanTJScheduler::PlanTJScheduler( Project *project, ScheduleManager *sm, ulong granularity, QObject *parent )
: SchedulerThread( project, sm, parent ),
result( -1 ),
m_schedule( 0 ),
m_recalculate( false ),
m_usePert( false ),
m_backward( false ),
m_granularity( granularity )
{
TJ::TJMH.reset();
connect(&TJ::TJMH, &TJ::TjMessageHandler::message, this, &PlanTJScheduler::slotMessage);
connect(this, &PlanTJScheduler::sigCalculationStarted, project, &KPlato::Project::sigCalculationStarted);
emit sigCalculationStarted( project, sm );
connect( this, &PlanTJScheduler::sigCalculationFinished, project, &KPlato::Project::sigCalculationFinished );
}
PlanTJScheduler::~PlanTJScheduler()
{
delete m_tjProject;
}
void PlanTJScheduler::slotMessage( int type, const QString &msg, TJ::CoreAttributes *object )
{
// debugPlan<<"PlanTJScheduler::slotMessage:"<<msg;
Schedule::Log log;
if ( object && object->getType() == CA_Task && m_taskmap.contains( static_cast<TJ::Task*>( object ) ) ) {
log = Schedule::Log( static_cast<Node*>( m_taskmap[ static_cast<TJ::Task*>( object ) ] ), type, msg );
} else if ( object && object->getType() == CA_Resource && m_resourcemap.contains( static_cast<TJ::Resource*>( object ) ) ) {
log = Schedule::Log( 0, m_resourcemap[ static_cast<TJ::Resource*>( object ) ], type, msg );
} else if ( object && ! object->getName().isEmpty() ) {
log = Schedule::Log( static_cast<Node*>( m_project ), type, QString( "%1: %2" ).arg(object->getName() ).arg( msg ) );
} else {
log = Schedule::Log( static_cast<Node*>( m_project ), type, msg );
}
slotAddLog( log );
}
void PlanTJScheduler::run()
{
if ( m_haltScheduling ) {
deleteLater();
return;
}
if ( m_stopScheduling ) {
return;
}
setMaxProgress( PROGRESS_MAX_VALUE );
{ // mutex -->
m_projectMutex.lock();
m_managerMutex.lock();
m_project = new Project();
loadProject( m_project, m_pdoc );
m_project->setName( "Schedule: " + m_project->name() ); //Debug
m_project->stopcalculation = false;
m_manager = m_project->scheduleManager( m_mainmanagerId );
Q_CHECK_PTR( m_manager );
Q_ASSERT( m_manager->expected() );
Q_ASSERT( m_manager != m_mainmanager );
Q_ASSERT( m_manager->scheduleId() == m_mainmanager->scheduleId() );
Q_ASSERT( m_manager->expected() != m_mainmanager->expected() );
m_manager->setName( "Schedule: " + m_manager->name() ); //Debug
m_schedule = m_manager->expected();
bool x = connect(m_manager, SIGNAL(sigLogAdded(KPlato::Schedule::Log)), this, SLOT(slotAddLog(KPlato::Schedule::Log)));
Q_ASSERT( x );
Q_UNUSED( x );
m_project->initiateCalculation( *m_schedule );
m_project->initiateCalculationLists( *m_schedule );
m_usePert = m_manager->usePert();
m_recalculate = m_manager->recalculate();
if ( m_recalculate ) {
m_backward = false;
} else {
m_backward = m_manager->schedulingDirection();
}
m_project->setCurrentSchedule( m_manager->expected()->id() );
m_schedule->setPhaseName( 0, xi18nc( "@info/plain" , "Init" ) );
QLocale locale;
KFormat format(locale);
if ( ! m_backward ) {
logDebug( m_project, 0, QString( "Schedule project using TJ Scheduler, starting at %1, granularity %2" ).arg( QDateTime::currentDateTime().toString() ).arg( format.formatDuration( m_granularity ) ), 0 );
if ( m_recalculate ) {
logInfo( m_project, 0, xi18nc( "@info/plain" , "Re-calculate project from start time: %1", locale.toString(m_project->constraintStartTime(), QLocale::ShortFormat) ), 0 );
} else {
logInfo( m_project, 0, xi18nc( "@info/plain" , "Schedule project from start time: %1", locale.toString(m_project->constraintStartTime(), QLocale::ShortFormat) ), 0 );
}
logInfo( m_project, 0, xi18nc( "@info/plain" , "Project target finish time: %1", locale.toString(m_project->constraintEndTime(), QLocale::ShortFormat) ), 0 );
} else {
logDebug( m_project, 0, QString( "Schedule project backward using TJ Scheduler, starting at %1, granularity %2" ).arg( locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat) ).arg( format.formatDuration( m_granularity ) ), 0 );
logInfo( m_project, 0, xi18nc( "@info/plain" , "Schedule project from end time: %1", locale.toString(m_project->constraintEndTime(), QLocale::ShortFormat) ), 0 );
}
m_managerMutex.unlock();
m_projectMutex.unlock();
} // <--- mutex
setProgress( 2 );
if ( ! kplatoToTJ() ) {
result = 1;
setProgress( PROGRESS_MAX_VALUE );
return;
}
setMaxProgress( PROGRESS_MAX_VALUE );
connect(m_tjProject, SIGNAL(updateProgressBar(int,int)), this, SLOT(setProgress(int)));
m_schedule->setPhaseName( 1, xi18nc( "@info/plain" , "Schedule" ) );
logInfo( m_project, 0, "Start scheduling", 1 );
bool r = solve();
if ( ! r ) {
debugPlan<<"Scheduling failed";
result = 2;
logError( m_project, 0, xi18nc( "@info/plain" , "Failed to schedule project" ) );
setProgress( PROGRESS_MAX_VALUE );
return;
}
if ( m_haltScheduling ) {
debugPlan<<"Scheduling halted";
logInfo( m_project, 0, "Scheduling halted" );
deleteLater();
return;
}
m_schedule->setPhaseName( 2, xi18nc( "@info/plain" , "Update" ) );
logInfo( m_project, 0, "Scheduling finished, update project", 2 );
if ( ! kplatoFromTJ() ) {
logError( m_project, 0, "Project update failed" );
}
setProgress( PROGRESS_MAX_VALUE );
m_schedule->setPhaseName( 3, xi18nc( "@info/plain" , "Finish" ) );
}
bool PlanTJScheduler::check()
{
DebugCtrl.setDebugMode( 0 );
DebugCtrl.setDebugLevel( 1000 );
return m_tjProject->pass2( true );
}
bool PlanTJScheduler::solve()
{
debugPlan<<"PlanTJScheduler::solve()";
TJ::Scenario *sc = m_tjProject->getScenario( 0 );
if ( ! sc ) {
logError( m_project, 0, xi18nc( "@info/plain" , "Failed to find scenario to schedule" ) );
return false;
}
DebugCtrl.setDebugLevel(0);
DebugCtrl.setDebugMode(PSDEBUG+TSDEBUG+RSDEBUG+PADEBUG);
return m_tjProject->scheduleScenario( sc );
}
bool PlanTJScheduler::kplatoToTJ()
{
m_tjProject = new TJ::Project();
m_tjProject->setScheduleGranularity( m_granularity / 1000 );
m_tjProject->getScenario( 0 )->setMinSlackRate( 0.0 ); // Do not calculate critical path
m_tjProject->setNow( m_project->constraintStartTime().toTime_t() );
m_tjProject->setStart( m_project->constraintStartTime().toTime_t() );
m_tjProject->setEnd( m_project->constraintEndTime().toTime_t() );
m_tjProject->setDailyWorkingHours( m_project->standardWorktime()->day() );
// Set working days for the project, it is used for tasks with a length specification
// FIXME: Plan has task specific calendars for this estimate type
KPlato::Calendar *cal = m_project->defaultCalendar();
if ( ! cal ) {
m_project->calendars().value( 0 );
}
if ( cal ) {
int days[ 7 ] = { Qt::Sunday, Qt::Monday, Qt::Tuesday, Qt::Wednesday, Qt::Thursday, Qt::Friday, Qt::Saturday };
for ( int i = 0; i < 7; ++i ) {
CalendarDay *d = 0;
for ( Calendar *c = cal; c; c = c->parentCal() ) {
QTime t; t.start();
d = c->weekday( days[ i ] );
Q_ASSERT( d );
if ( d == 0 || d->state() != CalendarDay::Undefined ) {
break;
}
}
if ( d && d->state() == CalendarDay::Working ) {
QList<TJ::Interval*> lst;
foreach ( const TimeInterval *ti, d->timeIntervals() ) {
TJ::Interval *tji = new TJ::Interval( toTJInterval( ti->startTime(), ti->endTime(),tjGranularity() ) );
lst << tji;
}
m_tjProject->setWorkingHours( i, lst );
qDeleteAll( lst );
}
}
}
addTasks();
setConstraints();
addDependencies();
addRequests();
addStartEndJob();
if (result != -1) {
return false;
}
return check();
}
void PlanTJScheduler::addStartEndJob()
{
TJ::Task *start = new TJ::Task( m_tjProject, "TJ::StartJob", "TJ::StartJob", 0, QString(), 0);
start->setMilestone( true );
if ( ! m_backward ) {
start->setSpecifiedStart( 0, m_tjProject->getStart() );
start->setPriority( 999 );
} else {
// backwards: insert a new ms before start and make start an ALAP to push all other jobs ALAP
TJ::Task *bs = new TJ::Task( m_tjProject, "TJ::StartJob-B", "TJ::StartJob-B", 0, QString(), 0);
bs->setMilestone( true );
bs->setSpecifiedStart( 0, m_tjProject->getStart() );
bs->setPriority( 999 );
bs->addPrecedes( start->getId() );
start->addDepends( bs->getId() );
start->setScheduling( TJ::Task::ALAP );
}
TJ::Task *end = new TJ::Task( m_tjProject, "TJ::EndJob", "TJ::EndJob", 0, QString(), 0);
end->setMilestone( true );
if ( m_backward ) {
end->setSpecifiedEnd( 0, m_tjProject->getEnd() - 1 );
end->setScheduling( TJ::Task::ALAP );
}
for ( QMap<TJ::Task*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it ) {
if ( it.value()->isStartNode() ) {
it.key()->addDepends( start->getId() );
// logDebug( m_project, 0, QString( "'%1' depends on: '%2'" ).arg( it.key()->getName() ).arg( start->getName() ) );
if ( start->getScheduling() == TJ::Task::ALAP ) {
start->addPrecedes( it.key()->getId() );
// logDebug( m_project, 0, QString( "'%1' precedes: '%2'" ).arg( start->getName() ).arg( it.key()->getName() ) );
}
}
if ( it.value()->isEndNode() ) {
end->addDepends( it.key()->getId() );
if ( it.key()->getScheduling() == TJ::Task::ALAP ) {
it.key()->addPrecedes( end->getId() );
}
}
}
}
// static
int PlanTJScheduler::toTJDayOfWeek( int day )
{
return day == 7 ? 0 : day;
}
// static
DateTime PlanTJScheduler::fromTime_t( time_t t, const QTimeZone &tz ) {
return DateTime ( QDateTime::fromTime_t( t ).toTimeZone( tz ) );
}
time_t PlanTJScheduler::toTJTime_t( const QDateTime &dt, ulong granularity )
{
int secs = QTime( 0, 0, 0 ).secsTo( dt.time() );
secs -= secs % granularity;
return QDateTime( dt.date(), QTime( 0, 0, 0 ).addSecs( secs ), dt.timeZone() ).toTime_t();
}
// static
AppointmentInterval PlanTJScheduler::fromTJInterval( const TJ::Interval &tji, const QTimeZone &tz ) {
AppointmentInterval a( fromTime_t( tji.getStart(), tz ), fromTime_t( tji.getEnd(), tz ).addSecs( 1 ) );
return a;
}
// static
TJ::Interval PlanTJScheduler::toTJInterval( const QDateTime &start, const QDateTime &end, ulong granularity ) {
int secs = QTime( 0, 0, 0 ).secsTo( start.time() );
secs -= secs % granularity;
QDateTime s( start.date(), QTime( 0, 0, 0 ).addSecs( secs ), start.timeZone() );
secs = QTime( 0, 0, 0 ).secsTo( end.time() );
secs -= secs % granularity;
QDateTime e( end.date(), QTime( 0, 0, 0 ).addSecs( secs ), end.timeZone() );
TJ::Interval ti( s.toTime_t(), e.addSecs( -1 ).toTime_t() );
return ti;
}
// static
TJ::Interval PlanTJScheduler::toTJInterval( const QTime &start, const QTime &end, ulong granularity ) {
int secs = QTime( 0, 0, 0 ).secsTo( start );
time_t s = secs - ( secs % granularity );
secs = ( end == QTime( 0, 0, 0 ) ) ? 86399 : QTime( 0, 0, 0 ).secsTo( end );
time_t e = secs - ( secs % granularity ) - 1;
TJ::Interval ti( s, e );
return ti;
}
ulong PlanTJScheduler::tjGranularity() const
{
return m_tjProject->getScheduleGranularity();
}
bool PlanTJScheduler::kplatoFromTJ()
{
MainSchedule *cs = static_cast<MainSchedule*>( m_project->currentSchedule() );
QDateTime start;
QDateTime end;
for ( QMap<TJ::Task*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it ) {
if ( ! taskFromTJ( it.key(), it.value() ) ) {
return false;
}
if ( ! start.isValid() || it.value()->startTime() < start ) {
start = it.value()->startTime();
}
if ( ! end.isValid() || it.value()->endTime() > end ) {
end = it.value()->endTime();
}
}
m_project->setStartTime( start.isValid() ? start : m_project->constraintStartTime() );
m_project->setEndTime( end.isValid() ? end : m_project->constraintEndTime() );
adjustSummaryTasks( m_schedule->summaryTasks() );
foreach ( Task *task, m_taskmap ) {
calcPertValues( task );
}
m_project->calcCriticalPathList( m_schedule );
// calculate positive float
foreach ( Task* t, m_taskmap ) {
if ( ! t->inCriticalPath() && t->isStartNode() ) {
calcPositiveFloat( t );
}
}
QLocale locale;
logInfo( m_project, 0, xi18nc( "@info/plain" , "Project scheduled to start at %1 and finish at %2", locale.toString(m_project->startTime(), QLocale::ShortFormat), locale.toString(m_project->endTime(), QLocale::ShortFormat) ) );
if ( m_manager ) {
logDebug( m_project, 0, QString( "Project scheduling finished at %1" ).arg( locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat) ) );
m_project->finishCalculation( *m_manager );
m_manager->scheduleChanged( cs );
}
return true;
}
bool PlanTJScheduler::taskFromTJ( TJ::Task *job, Task *task )
{
if ( m_haltScheduling || m_manager == 0 ) {
return true;
}
Schedule *cs = task->currentSchedule();
Q_ASSERT( cs );
QTimeZone tz = m_project->timeZone();
debugPlan<<"taskFromTJ:"<<task<<task->name()<<cs->id()<<tz;
time_t s = job->getStart( 0 );
if ( s < m_tjProject->getStart() || s > m_tjProject->getEnd() ) {
m_project->currentSchedule()->setSchedulingError( true );
cs->setSchedulingError( true );
s = m_tjProject->getStart();
}
time_t e = job->getEnd( 0 );
if ( job->isMilestone() ) {
Q_ASSERT( s = (e + 1));
e = s - 1;
} else if ( e <= s || e > m_tjProject->getEnd() ) {
m_project->currentSchedule()->setSchedulingError( true );
cs->setSchedulingError( true );
e = s + (8*60*60);
}
task->setStartTime( fromTime_t( s, tz ) );
task->setEndTime( fromTime_t( e + 1, tz ) );
task->setDuration( task->endTime() - task->startTime() );
debugPlan<<TJ::time2ISO(s)<<task->startTime()<<"-- "<<TJ::time2ISO(e+1)<<task->endTime();
if ( ! task->startTime().isValid() ) {
logError( task, 0, xi18nc( "@info/plain", "Invalid start time" ) );
return false;
}
if ( ! task->endTime().isValid() ) {
logError( task, 0, xi18nc( "@info/plain", "Invalid end time" ) );
return false;
}
if ( m_project->startTime() > task->startTime() ) {
m_project->setStartTime( task->startTime() );
}
if ( task->endTime() > m_project->endTime() ) {
m_project->setEndTime( task->endTime() );
}
foreach ( TJ::CoreAttributes *a, job->getBookedResources( 0 ) ) {
TJ::Resource *r = static_cast<TJ::Resource*>( a );
Resource *res = m_resourcemap[ r ];
const QVector<TJ::Interval> lst = r->getBookedIntervals( 0, job );
foreach ( const TJ::Interval &tji, lst ) {
AppointmentInterval ai = fromTJInterval( tji, tz );
double load = res->type() == Resource::Type_Material ? res->units() : ai.load() * r->getEfficiency();
res->addAppointment( cs, ai.startTime(), ai.endTime(), load );
logDebug( task, 0, '\'' + res->name() + "' added appointment: " + ai.startTime().toString( Qt::ISODate ) + " - " + ai.endTime().toString( Qt::ISODate ) );
}
}
cs->setScheduled( true );
QLocale locale;
if ( task->type() == Node::Type_Milestone ) {
logInfo( task, 0, xi18nc( "@info/plain" , "Scheduled milestone: %1", locale.toString(task->startTime(), QLocale::ShortFormat) ) );
} else {
logInfo( task, 0, xi18nc( "@info/plain" , "Scheduled task: %1 - %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat) ) );
}
return true;
}
void PlanTJScheduler::adjustSummaryTasks( const QList<Node*> &nodes )
{
foreach ( Node *n, nodes ) {
adjustSummaryTasks( n->childNodeIterator() );
if ( n->parentNode()->type() == Node::Type_Summarytask ) {
DateTime pt = n->parentNode()->startTime();
DateTime nt = n->startTime();
if ( ! pt.isValid() || pt > nt ) {
n->parentNode()->setStartTime( nt );
}
pt = n->parentNode()->endTime();
nt = n->endTime();
if ( ! pt.isValid() || pt < nt ) {
n->parentNode()->setEndTime( nt );
}
}
}
}
Duration PlanTJScheduler::calcPositiveFloat( Task *task )
{
if ( task->positiveFloat() != 0 ) {
return task->positiveFloat();
}
Duration x;
if ( task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty() ) {
x = m_project->endTime() - task->endTime();
} else {
foreach ( const Relation *r, task->dependChildNodes() + task->childProxyRelations() ) {
if ( ! r->child()->inCriticalPath() ) {
Duration f = calcPositiveFloat( static_cast<Task*>( r->child() ) );
if ( x == 0 || f < x ) {
x = f;
}
}
}
}
Duration totfloat = task->freeFloat() + x;
task->setPositiveFloat( totfloat );
return totfloat;
}
void PlanTJScheduler::calcPertValues( Task *t )
{
switch ( t->constraint() ) {
case Node::MustStartOn:
if ( t->constraintStartTime() != t->startTime() ) {
t->setNegativeFloat( t->startTime() - t->constraintStartTime() );
}
break;
case Node::StartNotEarlier:
if ( t->startTime() < t->constraintStartTime() ) {
t->setNegativeFloat( t->constraintStartTime() - t->startTime() );
}
break;
case Node::MustFinishOn:
if ( t->constraintEndTime() != t->endTime() ) {
t->setNegativeFloat( t->endTime() - t->constraintEndTime() );
}
break;
case Node::FinishNotLater:
if ( t->endTime() > t->constraintEndTime() ) {
t->setNegativeFloat( t->endTime() - t->constraintEndTime() );
}
break;
case Node::FixedInterval:
if ( t->constraintStartTime() != t->startTime() ) {
t->setNegativeFloat( t->startTime() - t->constraintStartTime() );
} else if ( t->endTime() != t->constraintEndTime() ) {
t->setNegativeFloat( t->endTime() - t->constraintEndTime() );
}
break;
default:
break;
}
if ( t->negativeFloat() != 0 ) {
t->currentSchedule()->setConstraintError( true );
m_project->currentSchedule()->setSchedulingError( true );
logError( t, 0, i18nc( "1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", t->constraintToString( true ), t->negativeFloat().toString( Duration::Format_i18nHour ) ) );
}
debugPlan<<t->name()<<t->startTime()<<t->endTime();
Duration negativefloat;
foreach ( const Relation *r, t->dependParentNodes() + t->parentProxyRelations() ) {
if ( r->parent()->endTime() + r->lag() > t->startTime() ) {
Duration f = r->parent()->endTime() + r->lag() - t->startTime();
if ( f > negativefloat ) {
negativefloat = f;
}
}
}
if ( negativefloat > 0 ) {
t->currentSchedule()->setSchedulingError( true );
m_project->currentSchedule()->setSchedulingError( true );
logError( t, 0, xi18nc( "@info/plain", "Failed to meet dependency. Negative float=%1", negativefloat.toString( Duration::Format_i18nHour ) ) );
if ( t->negativeFloat() < negativefloat ) {
t->setNegativeFloat( negativefloat );
}
}
Duration freefloat;
foreach ( const Relation *r, t->dependChildNodes() + t->childProxyRelations() ) {
if ( t->endTime() + r->lag() < r->child()->startTime() ) {
Duration f = r->child()->startTime() - r->lag() - t->endTime();
if ( f > 0 && ( freefloat == 0 || freefloat > f ) ) {
freefloat = f;
}
}
}
t->setFreeFloat( freefloat );
}
bool PlanTJScheduler::exists( QList<CalendarDay*> &lst, CalendarDay *day )
{
foreach ( CalendarDay *d, lst ) {
if ( d->date() == day->date() && day->state() != CalendarDay::Undefined && d->state() != CalendarDay::Undefined ) {
return true;
}
}
return false;
}
TJ::Resource *PlanTJScheduler::addResource( KPlato::Resource *r)
{
if ( m_resourcemap.values().contains( r ) ) {
debugPlan<<r->name()<<"already exist";
return m_resourcemap.key( r );
}
TJ::Resource *res = new TJ::Resource( m_tjProject, r->id(), r->name(), 0 );
if ( r->type() == Resource::Type_Material ) {
res->setEfficiency( 0.0 );
} else {
res->setEfficiency( (double)(r->units()) / 100. );
}
Calendar *cal = r->calendar();
Q_ASSERT(cal);
DateTime start = qMax( r->availableFrom(), m_project->constraintStartTime() );
DateTime end = m_project->constraintEndTime();
if ( r->availableUntil().isValid() && end > r->availableUntil() ) {
end = r->availableUntil();
}
AppointmentIntervalList lst = cal->workIntervals( start, end, 1.0 );
// qDebug()<<r->name()<<lst;
const QMultiMap<QDate, AppointmentInterval> &map = lst.map();
QMultiMap<QDate, AppointmentInterval>::const_iterator mapend = map.constEnd();
QMultiMap<QDate, AppointmentInterval>::const_iterator it = map.constBegin();
TJ::Shift *shift = new TJ::Shift( m_tjProject, r->id(), r->name(), 0, QString(), 0 );
for ( ; it != mapend; ++it ) {
shift->addWorkingInterval( toTJInterval( it.value().startTime(), it.value().endTime(), m_granularity/1000 ) );
}
res->addShift( toTJInterval( start, end, m_granularity/1000 ), shift );
m_resourcemap[res] = r;
logDebug( m_project, 0, "Added resource: " + r->name() );
/* QListIterator<TJ::Interval*> it = res->getVacationListIterator();
while ( it.hasNext() ) {
TJ::Interval *i = it.next();
logDebug( m_project, 0, "Vacation: " + TJ::time2ISO( i->getStart() ) + " - " + TJ::time2ISO( i->getEnd() ) );
}*/
return res;
}
TJ::Task *PlanTJScheduler::addTask( KPlato::Task *task, TJ::Task *parent )
{
/* if ( m_backward && task->isStartNode() ) {
Relation *r = new Relation( m_backwardTask, task );
m_project->addRelation( r );
}*/
TJ::Task *t = new TJ::Task(m_tjProject, task->id(), task->name(), parent, QString(), 0);
m_taskmap[ t ] = task;
// logDebug( m_project, 0, "Added task: " + task->name() );
addWorkingTime( task, t );
return t;
}
void PlanTJScheduler::addWorkingTime( KPlato::Task *task, TJ::Task *job )
{
if ( task->type() != Node::Type_Task || task->estimate()->type() != Estimate::Type_Duration || ! task->estimate()->calendar() ) {
return;
}
int id = 0;
Calendar *cal = task->estimate()->calendar();
DateTime start = m_project->constraintStartTime();
DateTime end = m_project->constraintEndTime();
AppointmentIntervalList lst = cal->workIntervals( start, end, 1.0 );
const QMultiMap<QDate, AppointmentInterval> &map = lst.map();
QMultiMap<QDate, AppointmentInterval>::const_iterator mapend = map.constEnd();
QMultiMap<QDate, AppointmentInterval>::const_iterator it = map.constBegin();
TJ::Shift *shift = new TJ::Shift( m_tjProject, task->id() + QString( "-%1" ).arg( ++id ), task->name(), 0, QString(), 0 );
for ( ; it != mapend; ++it ) {
shift->addWorkingInterval(toTJInterval(it.value().startTime(), it.value().endTime(), m_granularity/1000));
}
job->addShift(toTJInterval(start, end, m_granularity/1000), shift);
}
void PlanTJScheduler::addTasks()
{
debugPlan;
QList<Node*> list = m_project->allNodes();
for (int i = 0; i < list.count(); ++i) {
Node *n = list.at(i);
TJ::Task *parent = 0;
switch ( n->type() ) {
case Node::Type_Summarytask:
m_schedule->insertSummaryTask( n );
break;
case Node::Type_Task:
case Node::Type_Milestone:
switch ( n->constraint() ) {
case Node::StartNotEarlier:
parent = addStartNotEarlier( n );
break;
case Node::FinishNotLater:
parent = addFinishNotLater( n );
break;
}
addTask( static_cast<Task*>( n ), parent );
break;
default:
break;
}
}
}
void PlanTJScheduler::addDepends( const Relation *rel )
{
TJ::Task *child = m_tjProject->getTask( rel->child()->id() );
TJ::TaskDependency *d = child->addDepends( rel->parent()->id() );
d->setGapDuration( 0, rel->lag().seconds() );
}
void PlanTJScheduler::addPrecedes( const Relation *rel )
{
TJ::Task *parent = m_tjProject->getTask( rel->parent()->id() );
TJ::TaskDependency *d = parent->addPrecedes( rel->child()->id() );
d->setGapDuration( 0, rel->lag().seconds() );
}
void PlanTJScheduler::addDependencies( KPlato::Task *task )
{
foreach ( Relation *r, task->dependParentNodes() + task->parentProxyRelations() ) {
Node *n = r->parent();
if ( n == 0 || n->type() == Node::Type_Summarytask ) {
continue;
}
switch ( r->type() ) {
case Relation::FinishStart:
break;
case Relation::FinishFinish:
case Relation::StartStart:
warnPlan<<"Dependency type not handled. Using FinishStart.";
logWarning( task, 0, xi18nc( "@info/plain" , "Dependency type '%1' not handled. Using FinishStart.", r->typeToString( true ) ) );
break;
}
switch ( task->constraint() ) {
case Node::ASAP:
case Node::ALAP:
addPrecedes( r );
addDepends( r );
break;
case Node::MustStartOn:
case Node::StartNotEarlier:
addPrecedes( r );
if ( task->constraintStartTime() < m_project->constraintStartTime() ) {
addDepends( r );
}
break;
case Node::MustFinishOn:
case Node::FinishNotLater:
addDepends( r );
if ( task->constraintEndTime() < m_project->constraintEndTime() ) {
addPrecedes( r );
}
break;
case Node::FixedInterval:
break;
}
}
}
void PlanTJScheduler::addDependencies()
{
foreach ( Task *t, m_taskmap ) {
addDependencies( t );
}
}
void PlanTJScheduler::setConstraints()
{
QMap<TJ::Task*, Task*> ::const_iterator it = m_taskmap.constBegin();
for ( ; it != m_taskmap.constEnd(); ++it ) {
setConstraint( it.key(), it.value() );
}
}
void PlanTJScheduler::setConstraint( TJ::Task *job, KPlato::Task *task )
{
switch ( task->constraint() ) {
case Node::ASAP:
if ( ! job->isMilestone() ) {
job->setScheduling( m_backward ? TJ::Task::ALAP : TJ::Task::ASAP );
}
break;
case Node::ALAP:
job->setScheduling( TJ::Task::ALAP);
break;
case Node::MustStartOn:
if ( task->constraintStartTime() >= m_project->constraintStartTime() ) {
job->setPriority( 600 );
job->setSpecifiedStart( 0, task->constraintStartTime().toTime_t() );
logDebug( task, 0, QString( "MSO: set specified start: %1").arg( TJ::time2ISO( task->constraintStartTime().toTime_t() ) ) );
} else {
logWarning( task, 0, xi18nc( "@info/plain", "%1: Invalid start constraint", task->constraintToString( true ) ) );
}
break;
case Node::StartNotEarlier: {
break;
}
case Node::MustFinishOn:
if ( task->constraintEndTime() <= m_project->constraintEndTime() ) {
job->setPriority( 600 );
job->setScheduling( TJ::Task::ALAP );
job->setSpecifiedEnd( 0, task->constraintEndTime().toTime_t() - 1 );
logDebug( task, 0, QString( "MFO: set specified end: %1").arg( TJ::time2ISO( task->constraintEndTime().toTime_t() ) ) );
} else {
logWarning( task, 0, xi18nc( "@info/plain", "%1: Invalid end constraint", task->constraintToString( true ) ) );
}
break;
case Node::FinishNotLater: {
break;
}
case Node::FixedInterval: {
job->setPriority( 700 );
TJ::Interval i( toTJInterval( task->constraintStartTime(), task->constraintEndTime(), tjGranularity() ) );
job->setSpecifiedPeriod( 0, i );
// estimate not allowed
job->setDuration( 0, 0.0 );
job->setLength( 0, 0.0 );
job->setEffort( 0, 0.0 );
logDebug( task, 0, QString( "FI: set specified: %1 - %2 -> %3 - %4 (%5)")
.arg( TJ::time2ISO( task->constraintStartTime().toTime_t() ) )
.arg( TJ::time2ISO( task->constraintEndTime().toTime_t() ) )
.arg( TJ::time2ISO( i.getStart() ) )
.arg( TJ::time2ISO( i.getEnd() ) )
.arg( tjGranularity() ) );
break;
}
default:
logWarning( task, 0, xi18nc( "@info/plain", "Unhandled time constraint type" ) );
break;
}
}
TJ::Task *PlanTJScheduler::addStartNotEarlier( Node *task )
{
DateTime time = task->constraintStartTime();
if ( task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() != 0 ) {
Calendar *cal = task->estimate()->calendar();
if ( cal != m_project->defaultCalendar() && cal != m_project->calendars().value( 0 ) ) {
logWarning( task, 0, xi18nc( "@info/plain", "Could not use the correct calendar for calculation of task duration" ) );
} else {
time = cal->firstAvailableAfter( time, m_project->constraintEndTime() );
}
}
TJ::Task *p = new TJ::Task( m_tjProject, QString("%1-sne").arg( m_tjProject->taskCount() + 1 ), task->name() + "-sne", 0, QString(), 0 );
p->setSpecifiedStart( 0, toTJTime_t( time, tjGranularity() ) );
p->setSpecifiedEnd( 0, m_tjProject->getEnd() - 1 );
qDebug()<<"PlanTJScheduler::addStartNotEarlier:"<<time<<TJ::time2ISO(toTJTime_t( time, tjGranularity() ));
return p;
}
TJ::Task *PlanTJScheduler::addFinishNotLater( Node *task )
{
DateTime time = task->constraintEndTime();
if ( task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() != 0 ) {
Calendar *cal = task->estimate()->calendar();
if ( cal != m_project->defaultCalendar() && cal != m_project->calendars().value( 0 ) ) {
logWarning( task, 0, xi18nc( "@info/plain", "Could not use the correct calendar for calculation of task duration" ) );
} else {
time = cal->firstAvailableBefore( time, m_project->constraintStartTime() );
}
}
TJ::Task *p = new TJ::Task( m_tjProject, QString("%1-fnl").arg( m_tjProject->taskCount() + 1 ), task->name() + "-fnl", 0, QString(), 0 );
p->setSpecifiedEnd( 0, toTJTime_t( time, tjGranularity() ) - 1 );
p->setSpecifiedStart( 0, m_tjProject->getStart() );
return p;
}
void PlanTJScheduler::addRequests()
{
debugPlan;
QMap<TJ::Task*, Task*> ::const_iterator it = m_taskmap.constBegin();
for ( ; it != m_taskmap.constEnd(); ++it ) {
addRequest( it.key(), it.value() );
}
}
void PlanTJScheduler::addRequest( TJ::Task *job, Task *task )
{
debugPlan;
if ( task->type() == Node::Type_Milestone || task->estimate() == 0 || ( m_recalculate && task->completion().isFinished() ) ) {
job->setMilestone( true );
job->setDuration( 0, 0.0 );
return;
}
// Note: FI tasks can never have an estimate set (duration, length or effort)
if ( task->constraint() != Node::FixedInterval ) {
if ( task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() == 0 ) {
job->setDuration( 0, task->estimate()->value( Estimate::Use_Expected, m_usePert ).toDouble( Duration::Unit_d ) );
return;
}
if ( task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() != 0 ) {
job->setLength( 0, task->estimate()->value( Estimate::Use_Expected, m_usePert ).toDouble( Duration::Unit_d ) * 24.0 / m_tjProject->getDailyWorkingHours() );
return;
}
if ( m_recalculate && task->completion().isStarted() ) {
job->setEffort( 0, task->completion().remainingEffort().toDouble( Duration::Unit_d ) );
} else {
Estimate *estimate = task->estimate();
double e = estimate->scale( estimate->value( Estimate::Use_Expected, m_usePert ), Duration::Unit_d, estimate->scales() );
job->setEffort( 0, e );
}
}
if ( task->requests().isEmpty() ) {
return;
}
foreach ( ResourceRequest *rr, task->requests().resourceRequests( true /*resolveTeam*/ ) ) {
if (!rr->resource()->calendar()) {
result = 1; // stops scheduling
logError(task, 0, i18n("No working hours defined for resource: %1",rr->resource()->name()));
continue; // may happen if no calendar is set, and no default calendar
}
TJ::Resource *tjr = addResource( rr->resource() );
TJ::Allocation *a = new TJ::Allocation();
a->setSelectionMode( TJ::Allocation::order );
if ( rr->units() != 100 ) {
TJ::UsageLimits *l = new TJ::UsageLimits();
l->setDailyUnits( rr->units() );
a->setLimits( l );
}
a->addCandidate( tjr );
job->addAllocation( a );
logDebug( task, 0, "Added resource candidate: " + rr->resource()->name() );
foreach ( Resource *r, rr->requiredResources() ) {
TJ::Resource *tr = addResource( r );
a->addRequiredResource( tjr, tr );
logDebug( task, 0, "Added required resource: " + r->name() );
}
}
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/Allocation.cpp b/src/plugins/schedulers/tj/taskjuggler/Allocation.cpp
index ec98c9ff..162f9b93 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Allocation.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Allocation.cpp
@@ -1,120 +1,121 @@
/*
* Allocation.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Allocation.h"
#include "tjlib-internal.h"
#include "Resource.h"
#include "ResourceTreeIterator.h"
// #include "ReportXML.h"
#include "UsageLimits.h"
/* -- DTD --
<!ELEMENT Allocation (Load, Persistent)>
<!ELEMENT Load (#PCDATA)>
<!ELEMENT Persistent (#PCDATA)>
<!ATTLIST Allocation ResourceID CDATA #REQUIRED>
/-- DTD --/
*/
namespace TJ
{
Allocation::Allocation() :
limits(0),
shifts(),
persistent(false),
mandatory(false),
lockedResource(0),
conflictStart(0),
candidates(),
selectionMode(minAllocationProbability)
{
// shifts.setAutoDelete(true);
}
Allocation::~Allocation()
{
while(!shifts.isEmpty()) delete shifts.takeFirst();
delete limits;
}
Allocation::Allocation(const Allocation& a) :
limits(a.limits ? new UsageLimits(*a.limits) : 0),
shifts(),
persistent(a.persistent),
mandatory(a.mandatory),
lockedResource(a.lockedResource),
conflictStart(0),
candidates(a.candidates),
selectionMode(a.selectionMode)
{
// shifts.setAutoDelete(true);
for (QListIterator<ShiftSelection*> sli(a.shifts); sli.hasNext();)
shifts.append(new ShiftSelection(*(sli.next())));
}
void
Allocation::setLimits(UsageLimits* l)
{
delete limits;
limits = l;
}
bool
Allocation::isWorker() const
{
/* For an allocation to be a worker, all allocated resources must have an
* non zero efficiency. */
for (QListIterator<Resource*> cli(candidates); cli.hasNext();)
if (!cli.next()->isWorker())
return false;
return true;
}
/* Creation of the XML Reprsentation of the Allocation */
QDomElement Allocation::xmlElement( QDomDocument& doc )
{
Q_UNUSED(doc);
QDomElement elem;/* = doc.createElement( "Allocation" );
elem.appendChild(ReportXML::createXMLElem( doc, "Persistent", isPersistent() ? "Yes":"No" ));
elem.setAttribute( "ResourceID", candidates.first()->getId());*/
/* candidates are missing TODO */
return elem;
}
bool
Allocation::setSelectionMode(const QString& smt)
{
if (smt == KW("order"))
selectionMode = order;
else if (smt == KW("minallocated"))
selectionMode = minAllocationProbability;
else if (smt == KW("minloaded"))
selectionMode = minLoaded;
else if (smt == KW("maxloaded"))
selectionMode = maxLoaded;
else if (smt == KW("random"))
selectionMode = random;
else
return false;
return true;
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/BookingList.cpp b/src/plugins/schedulers/tj/taskjuggler/BookingList.cpp
index d135962b..e8b845e1 100644
--- a/src/plugins/schedulers/tj/taskjuggler/BookingList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/BookingList.cpp
@@ -1,22 +1,23 @@
/*
* BookingList.h - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "BookingList.h"
int
BookingList::compareItems(QCollection::Item i1, QCollection::Item i2)
{
Booking* b1 = static_cast<Booking*>(i1);
Booking* b2 = static_cast<Booking*>(i2);
return b1->getInterval().compare(b2->getInterval());
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/CoreAttributes.cpp b/src/plugins/schedulers/tj/taskjuggler/CoreAttributes.cpp
index ceab35fc..f70c1da7 100644
--- a/src/plugins/schedulers/tj/taskjuggler/CoreAttributes.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/CoreAttributes.cpp
@@ -1,289 +1,290 @@
/*
* CoreAttributes.h - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "CoreAttributes.h"
#include "CoreAttributesList.h"
#include "CustomAttributeDefinition.h"
// #include "TextAttribute.h"
// #include "ReferenceAttribute.h"
#include "Task.h"
namespace TJ
{
CoreAttributes::CoreAttributes(Project* p, const QString& i,
const QString& n,
CoreAttributes* parent_, const QString& df,
uint dl) :
project(p),
id(i),
name(n),
parent(parent_),
definitionFile(df),
definitionLine(dl),
sequenceNo(0),
hierarchNo(0),
index(-1),
hierarchIndex(0),
sub(new CoreAttributesList()),
flags(),
customAttributes()
{
// customAttributes.setAutoDelete(true);
if (parent_)
parent_->sub->append(this);
// qDebug()<<"CoreAttributes:"<<this;
}
CoreAttributes::~CoreAttributes()
{
qDebug()<<"~CoreAttributes:"<<this;
while (!sub->isEmpty())
delete sub->takeFirst();
if (parent && parent->sub->contains(this))
parent->sub->removeAt(parent->sub->indexOf(this));
delete sub;
while (!customAttributes.isEmpty()) delete customAttributes.values().takeFirst();
}
uint
CoreAttributes::treeLevel() const
{
uint tl = 0;
for (CoreAttributes* c = parent; c; c = c->parent)
tl++;
return tl;
}
CoreAttributesList
CoreAttributes::getSubList() const
{
return *sub;
}
CoreAttributesListIterator
CoreAttributes::getSubListIterator() const
{
return CoreAttributesListIterator(*sub);
}
bool
CoreAttributes::hasSubs() const
{
return !sub->isEmpty();
}
void
CoreAttributes::setHierarchNo(uint no)
{
hierarchNo = no;
uint hNo = 1;
foreach (CoreAttributes *a, *sub) {
a->setHierarchNo(hNo++);
}
}
QString
CoreAttributes::getHierarchNo() const
{
QString text;
const CoreAttributes* ca = this;
do
{
if (!text.isEmpty())
text.prepend(QLatin1Char('.'));
text = QString("%1").arg(ca->hierarchNo) + text;
ca = ca->getParent();
}
while (ca);
return text;
}
void
CoreAttributes::setHierarchIndex(uint no)
{
if (no == 0)
{
hierarchIndex = 0;
return;
}
/* If there is no parent, we take the passed number. */
if (!parent)
{
hierarchIndex = no;
return;
}
/* Find the highest hierarchIndex of all children of this CAs parent. */
uint max = 0;
foreach (CoreAttributes *a, (*parent->sub)) {
if (a->hierarchIndex > max)
max = a->hierarchIndex;
}
/* The index is then the highest found + 1. */
hierarchIndex = max + 1;
}
QString
CoreAttributes::getHierarchIndex() const
{
QString text;
const CoreAttributes* ca = this;
while (ca)
{
if (!text.isEmpty())
text.prepend(QLatin1Char('.'));
text = QString("%1").arg(ca->hierarchIndex) + text;
ca = ca->getParent();
}
return text;
}
QString
CoreAttributes::getHierarchLevel() const
{
return QString("%1").arg(treeLevel());
}
void
CoreAttributes::getFullName(QString& fullName) const
{
fullName.clear();
for (const CoreAttributes* c = this; c != 0; c = c->parent)
fullName = c->name + QLatin1Char('.') + fullName;
// Remove trailing dot.
fullName.remove(fullName.length() - 1, 1);
}
QString
CoreAttributes::getFullId() const
{
QString fullID = id;
for (const CoreAttributes* c = parent; c != 0; c = c->parent)
fullID = c->id + QLatin1Char('.') + fullID;
return fullID;
}
bool
CoreAttributes::hasSameAncestor(const CoreAttributes* c) const
{
if (c == 0)
return false;
CoreAttributes const* p1;
for (p1 = this; p1->parent; p1 = p1->parent)
;
CoreAttributes const* p2;
for (p2 = c; p2->parent; p2 = p2->parent)
;
return p1 == p2;
}
bool
CoreAttributes::isDescendantOf(const CoreAttributes* c) const
{
if (c == 0)
return false;
for (CoreAttributes const* p = this->parent; p; p = p->parent)
if (p == c)
return true;
return false;
}
bool
CoreAttributes::isParentOf(const CoreAttributes* c) const
{
if (!c)
return false;
for (CoreAttributes const* p = c->parent; p; p = p->parent)
if (p == this)
return true;
return false;
}
bool
CoreAttributes::isLeaf() const
{
return sub->isEmpty();
}
void
CoreAttributes::addCustomAttribute(const QString& id, CustomAttribute* ca)
{
customAttributes.insert(id, ca);
}
const CustomAttribute*
CoreAttributes::getCustomAttribute(const QString& id) const
{
return customAttributes[id];
}
void
CoreAttributes::inheritCustomAttributes
(const QMap<QString, CustomAttributeDefinition*>& dict)
{
QMap<QString, CustomAttributeDefinition*>::const_iterator cadi = dict.constBegin();
for ( ; cadi != dict.constEnd(); ++cadi)
{
const CustomAttribute* custAttr;
if (cadi.value()->getInherit() &&
(custAttr = parent->getCustomAttribute(cadi.key())))
{
switch (custAttr->getType())
{
case CAT_Text:
/* addCustomAttribute(cadi.key(), new TextAttribute
(*(static_cast<const TextAttribute*>(custAttr))));
break;*/
case CAT_Reference:
/* addCustomAttribute(cadi.key(), new ReferenceAttribute
(*(static_cast<const ReferenceAttribute*>(custAttr))));
break;*/
default:
qFatal("CoreAttributes::inheritCustomAttributes: "
"Unknown CAT %d", custAttr->getType());
break;
}
}
}
}
} // namespace TJ
QDebug operator<<( QDebug dbg, const TJ::CoreAttributes* t )
{
if ( t == 0 ) {
return dbg << (void*)t;
}
return operator<<( dbg, *t );
}
QDebug operator<<( QDebug dbg, const TJ::CoreAttributes& t )
{
switch ( t.getType() ) {
case CA_Task: dbg << "Task[" << t.getName() << "]"; break;
case CA_Resource: dbg << "Resource[" << t.getName() << "]"; break;
case CA_Account: dbg << "Account[" << t.getName() << "]"; break;
case CA_Shift: dbg << "Shift[" << t.getName() << "]"; break;
case CA_Scenario: dbg << "Scenario[" << t.getName() << "]"; break;
default: dbg << "CoreAttribute[" << t.getName() << "]"; break;
}
return dbg;
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/CoreAttributesList.cpp b/src/plugins/schedulers/tj/taskjuggler/CoreAttributesList.cpp
index 66ecc1f1..54cd0849 100644
--- a/src/plugins/schedulers/tj/taskjuggler/CoreAttributesList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/CoreAttributesList.cpp
@@ -1,300 +1,301 @@
/*
* CoreAttributesList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "CoreAttributesList.h"
#include <QDebug>
namespace TJ
{
CoreAttributesList::~CoreAttributesList()
{
if (autoDelete()) {
setAutoDelete(false);
while (!isEmpty()) {
delete takeFirst();
}
setAutoDelete(true);
}
// if (autoDelete())
// {
// /* We need to make sure that the CoreAttributes are first removed from
// * the list and then deleted. */
// setAutoDelete(false);
// while (!isEmpty())
// {
// CoreAttributes* tp = getFirst();
// removeRef(tp);
// delete tp;
// }
// setAutoDelete(true);
// }
}
void
CoreAttributesList::setAutoDelete(bool on)
{
m_autodelete = on;
}
bool
CoreAttributesList::autoDelete() const
{
return m_autodelete;
}
void
CoreAttributesList::deleteContents()
{
// Don't understand this code (danders)
while (!isEmpty()) {
for (CoreAttributesListIterator li(*this); *li; ++li)
if ((*li)->getParent() == 0)
{
delete *li;
break;
}
}
}
void
CoreAttributesList::setSorting(int s, int level)
{
if (level >=0 && level < maxSortingLevel)
sorting[level] = s;
else
qFatal("CoreAttributesList::setSorting: level out of range: %d",
level);
}
void
CoreAttributesList::sort()
{
QList<CoreAttributes*> lst = *this;
clear();
QStringList s; for(int i = 0; i < lst.count(); ++i) s << lst.at(i)->getId();
qDebug()<<"CoreAttributesList::sort:"<<s;
while (!lst.isEmpty()) {
inSort(lst.takeLast());
}
s.clear(); for(int i = 0; i < lst.count(); ++i) s << lst.at(i)->getId();
qDebug()<<"CoreAttributesList::sort: sorted"<<s;
}
void
CoreAttributesList::createIndex(bool initial)
{
/* In "initial" mode the sequenceNo is set. This should only be done once
* for each list. In the other mode the index is set. This is most likely
* called after the sorting criteria have been changed. */
int i = 1;
if (initial)
{
uint hNo = 1;
for (int pos = 0; pos < count(); ++pos)
{
CoreAttributes *a = at(pos);
a->setSequenceNo(i);
if (a->getParent() == 0)
a->setHierarchNo(hNo++);
}
}
else
{
sort();
for (int pos = 0; pos < count(); ++pos)
{
CoreAttributes *a = at(pos);
a->setIndex(i);
// Reset all hierarchIndices to 0.
a->setHierarchIndex(0);
}
// Then number them again.
uint hNo = 1;
for (int pos = 0; pos < count(); ++pos)
{
CoreAttributes *a = at(pos);
a->setHierarchIndex(hNo);
if (a->getParent() == 0)
hNo++;
}
}
}
int
CoreAttributesList::getIndex(const QString& id) const
{
for (int pos = 0; pos < count(); ++pos) {
CoreAttributes *a = at(pos);
if (a->getId() == id)
return a->getIndex();
}
return -1;
}
uint
CoreAttributesList::maxDepth() const
{
uint md = 0;
for (int pos = 0; pos < count(); ++pos) {
CoreAttributes *a = at(pos);
if (a->treeLevel() + 1 > md)
md = a->treeLevel() + 1;
}
return md;
}
bool
CoreAttributesList::isSupportedSortingCriteria(int sc)
{
switch (sc)
{
case SequenceUp:
case SequenceDown:
case TreeMode:
case FullNameDown:
case FullNameUp:
case IndexUp:
case IndexDown:
case IdUp:
case NameUp:
case NameDown:
return true;
default:
return false;
}
}
int CoreAttributesList::inSort(CoreAttributes* attr)
{
int i = 0;
for (; i < count(); ++i) {
int r = compareItems(attr, at(i));
if (r < 0) {
break;
}
}
insert(i, attr);
return i;
}
int
CoreAttributesList::compareItemsLevel(CoreAttributes* c1, CoreAttributes* c2,
int level)
{
if (level < 0 || level >= maxSortingLevel)
return -1;
switch (sorting[level])
{
case SequenceUp:
return c1->getSequenceNo() == c2->getSequenceNo() ? 0 :
c1->getSequenceNo() < c2->getSequenceNo() ? -1 : 1;
case SequenceDown:
return c1->getSequenceNo() == c2->getSequenceNo() ? 0 :
c1->getSequenceNo() > c2->getSequenceNo() ? -1 : 1;
case TreeMode:
{
if (level == 0)
return compareTreeItemsT(this, c1, c2);
else
return c1->getSequenceNo() < c2->getSequenceNo() ? -1 : 1;
}
case FullNameDown:
{
QString fn1;
c1->getFullName(fn1);
QString fn2;
c2->getFullName(fn2);
return fn1.compare(fn2);
}
case FullNameUp:
{
QString fn1;
c1->getFullName(fn1);
QString fn2;
c2->getFullName(fn2);
return fn2.compare(fn1);
}
case IndexUp:
return c2->getIndex() == c1->getIndex() ? 0 :
c2->getIndex() < c1->getIndex() ? -1 : 1;
case IndexDown:
return c1->getIndex() == c2->getIndex() ? 0 :
c1->getIndex() > c2->getIndex() ? -1 : 1;
case IdUp:
return QString::compare(c1->getId(), c2->getId());
case IdDown:
return QString::compare(c2->getId(), c1->getId());
case NameUp:
return c1->getName().compare(c2->getName());
case NameDown:
return c2->getName().compare(c1->getName());
default:
qFatal("CoreAttributesList:compareItemsLevel: "
"Please implement sorting for mode (%d/%d) in sub class!",
sorting[level], level);
}
return 0;
}
int
CoreAttributesList::compareItems(CoreAttributes* c1, CoreAttributes* c2)
{
int res;
for (int i = 0; i < CoreAttributesList::maxSortingLevel; ++i)
if ((res = compareItemsLevel(c1, c2, i)) != 0)
return res;
return res;
}
//static
QStringList CoreAttributesList::getSortCriteria()
{
QStringList lst;
lst << "SequenceUp" << "SequenceDown"
<< "TreeMode" << "NameUp" << "NameDown" << "FullNameUp"
<< "FullNameDown" << "IdUp" << "IdDown" << "IndexUp" << "IndexDown"
<< "StatusUp" << "StatusDown" << "CompletedUp" << "CompletedDown"
<< "PrioUp" << "PrioDown"
<< "ResponsibleUp" << "ResponsibleDown"
<< "MinEffortUp" << "MinEffortDown"
<< "MaxEffortUp" << "MaxEffortDown"
<< "RateUp" << "RateDown"
<< "StartUp" << "StartDown" << "EndUp" << "EndDown"
<< "CriticalnessUp" << "CriticalnessDown"
<< "PathCriticalnessUp" << "PathCriticalnessDown"
;
return lst;
}
} // namespace TJ
QDebug operator<<( QDebug dbg, const TJ::CoreAttributesList& lst )
{
QStringList s;
for ( int i = 0; i < TJ::CoreAttributesList::maxSortingLevel; ++i ) {
s << TJ::CoreAttributesList::getSortCriteria().at( lst.getSorting( i ) );
}
dbg.nospace() << "CoreAttributeList{sort: " << s.join("|") << " (";
for( int i = 0; i < lst.count(); ++i ) {
dbg << lst.at( i );
if ( i < lst.count() - 1 ) {
dbg.nospace() << ',';
}
}
dbg.nospace() << ")}";
return dbg;
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/Interval.cpp b/src/plugins/schedulers/tj/taskjuggler/Interval.cpp
index d4cdd9de..be5430a9 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Interval.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Interval.cpp
@@ -1,28 +1,29 @@
/*
* Interval.h - TaskJuggler
*
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Interval.h"
QDebug operator<<( QDebug dbg, const TJ::Interval *i )
{
return i == 0 ? (dbg << (void*)i) : operator<<( dbg, *i );
}
QDebug operator<<( QDebug dbg, const TJ::Interval &i )
{
dbg << "Interval[";
if ( i.isNull() ) dbg << "Null";
else dbg << TJ::time2ISO(i.getStart())<<"-"<<TJ::time2ISO(i.getEnd());
dbg << "]";
return dbg;
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/Project.cpp b/src/plugins/schedulers/tj/taskjuggler/Project.cpp
index 33514680..e3156851 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Project.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Project.cpp
@@ -1,1178 +1,1179 @@
/*
* Project.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
* by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
#define tjDebug qDebug
+// clazy:excludeall=qstring-arg
#include "Project.h"
#include <stdlib.h>
#include <QList>
#include <QString>
#include <QStringList>
#include <QDebug>
#include <KLocalizedString>
#include "TjMessageHandler.h"
#include "Scenario.h"
#include "Shift.h"
// #include "Account.h"
#include "Resource.h"
/*#include "HTMLTaskReport.h"
#include "HTMLResourceReport.h"
#include "HTMLAccountReport.h"
#include "HTMLWeeklyCalendar.h"
#include "HTMLStatusReport.h"
#include "CSVTaskReport.h"
#include "CSVResourceReport.h"
#include "CSVAccountReport.h"
#include "ExportReport.h"
#include "ReportXML.h"*/
#include "UsageLimits.h"
#include "CustomAttributeDefinition.h"
DebugController DebugCtrl;
namespace TJ
{
Project::Project() :
QObject(),
start(0),
end(0),
now(0),
allowRedefinitions(false),
weekStartsMonday(true),
name(),
version(),
copyright(),
customer(),
timeZone(),
timeFormat("%Y-%m-%d %H:%M"),
shortTimeFormat("%H:%M"),
// currency(),
// currencyDigits(3),
// numberFormat("-", "", ",", ".", 1),
// currencyFormat("(", ")", ",", ".", 0),
priority(500),
minEffort(0.0),
resourceLimits(0),
rate(0.0),
dailyWorkingHours(8.0),
yearlyWorkingDays(260.714),
workingHours(),
scheduleGranularity(ONEHOUR),
allowedFlags(),
projectIDs(),
currentId(),
maxErrors(0),
// journal(),
vacationList(),
scenarioList(),
taskList(),
resourceList(),
// accountList(),
shiftList(),
originalTaskList(),
originalResourceList(),
// originalAccountList(),
taskAttributes(),
resourceAttributes(),
// accountAttributes(),
// xmlreport(0),
// reports(),
// interactiveReports(),
sourceFiles(),
breakFlag(false)
{
qDebug()<<"Project:"<<this;
/* Pick some reasonable initial number since we don't know the
* project time frame yet. */
initUtility(20000);
// vacationList.setAutoDelete(true);
// accountAttributes.setAutoDelete(true);
// taskAttributes.setAutoDelete(true);
// resourceAttributes.setAutoDelete(true);
// reports.setAutoDelete(true);
new Scenario(this, "plan", "Plan", 0);
scenarioList.createIndex(true);
scenarioList.createIndex(false);
foreach(CoreAttributes *s, scenarioList) {
qDebug()<<"Project:"<<static_cast<CoreAttributes*>(s)<<static_cast<CoreAttributes*>(s)->getName()<<static_cast<CoreAttributes*>(s)->getSequenceNo();
}
setNow(time(0));
/* Initialize working hours with default values that match the Monday -
* Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
* countries. */
// Sunday
workingHours[0] = new QList<Interval*>();
// workingHours[0]->setAutoDelete(true);
for (int i = 1; i < 6; ++i)
{
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
workingHours[i]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
workingHours[i]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
}
// Saturday
workingHours[6] = new QList<Interval*>();
// workingHours[6]->setAutoDelete(true);
}
Project::~Project()
{
qDebug()<<"~Project:"<<this;
taskList.deleteContents();
resourceList.deleteContents();
Resource::deleteStaticData();
// accountList.deleteContents();
shiftList.deleteContents();
scenarioList.deleteContents();
delete resourceLimits;
// Remove support for 1.0 XML reports for next major release. */
// delete xmlreport;
for (int i = 0; i < 7; ++i) {
while ( ! workingHours[i]->isEmpty() ) {
delete workingHours[i]->takeFirst();
}
delete workingHours[i];
}
exitUtility();
qDebug()<<"~Project:"<<this;
}
// void
// Project::addSourceFile(const QString& f)
// {
// if (sourceFiles.find(f) == sourceFiles.end())
// sourceFiles.append(f);
// }
//
// QStringList
// Project::getSourceFiles() const
// {
// return sourceFiles;
// }
void
Project::setProgressInfo(const QString& i)
{
emit updateProgressInfo(i);
}
void
Project::setProgressBar(int i, int of)
{
emit updateProgressBar(i, of);
}
bool
Project::setTimeZone(const QString& tz)
{
if (!setTimezone(tz.toLocal8Bit()))
return false;
timeZone = tz;
return true;
}
Scenario*
Project::getScenario(int sc) const
{
return static_cast<Scenario*>(scenarioList.value(sc));
}
QString
Project::getScenarioName(int sc) const
{
Scenario *s = getScenario(sc);
return s ? s->getName() : QString();
}
QString
Project::getScenarioId(int sc) const
{
Scenario *s = getScenario(sc);
return s ? s->getId() : QString();
}
int
Project::getScenarioIndex(const QString& id) const
{
return scenarioList.getIndex(id);
}
void
Project::setNow(time_t n)
{
/* Align 'now' time to timing resolution. If the resolution is
* changed later, this has to be done again. */
now = (n / scheduleGranularity) * scheduleGranularity;
}
void
Project::setWorkingHours(int day, const QList< Interval* >& l)
{
if (day < 0 || day > 6)
qFatal("day out of range");
delete workingHours[day];
// Create a deep copy of the interval list.
workingHours[day] = new QList<Interval*>;
// workingHours[day]->setAutoDelete(true);
foreach( Interval *i, l) {
workingHours[day]->append(new Interval(*i));
}
}
bool
Project::addId(const QString& id, bool changeCurrentId)
{
if (projectIDs.indexOf(id) != -1)
return false;
else
projectIDs.append(id);
if (changeCurrentId)
currentId = id;
return true;
}
QString
Project::getIdIndex(const QString& i) const
{
int idx;
if ((idx = projectIDs.indexOf(i)) == -1)
return QString("?");
QString idxStr;
do
{
idxStr = QChar('A' + idx % ('Z' - 'A')) + idxStr;
idx /= 'Z' - 'A';
} while (idx > 'Z' - 'A');
return idxStr;
}
void
Project::addScenario(Scenario* s)
{
scenarioList.append(s);
/* This is not too efficient, but since there are usually only a few
* scenarios in a project, this doesn't hurt too much. */
scenarioList.createIndex(true);
scenarioList.createIndex(false);
}
void
Project::deleteScenario(Scenario* s)
{
if (scenarioList.contains(s)) {
scenarioList.removeAt(scenarioList.indexOf(s));
}
}
void
Project::setResourceLimits(UsageLimits* l)
{
if (resourceLimits)
delete resourceLimits;
resourceLimits = l;
}
void
Project::addTask(Task* t)
{
taskList.append(t);
}
void
Project::deleteTask(Task* t)
{
if (taskList.contains(t)) {
taskList.removeAt(taskList.indexOf(t));
}
}
bool
Project::addTaskAttribute(const QString& id, CustomAttributeDefinition* cad)
{
if (taskAttributes.contains(id))
return false;
taskAttributes.insert(id, cad);
return true;
}
const CustomAttributeDefinition*
Project::getTaskAttribute(const QString& id) const
{
return taskAttributes[id];
}
void
Project::addShift(Shift* s)
{
shiftList.append(s);
}
void
Project::deleteShift(Shift* s)
{
if (shiftList.contains(s)) {
shiftList.removeAt(shiftList.indexOf(s));
}
}
void
Project::addResource(Resource* r)
{
qDebug()<<"Project::addResource:"<<r<<resourceList;
resourceList.append(r);
}
void
Project::deleteResource(Resource* r)
{
if (resourceList.contains(r)) {
resourceList.removeAt(resourceList.indexOf(r));
}
}
bool
Project::addResourceAttribute(const QString& id,
CustomAttributeDefinition* cad)
{
if (resourceAttributes.contains(id))
return false;
resourceAttributes.insert(id, cad);
return true;
}
const CustomAttributeDefinition*
Project::getResourceAttribute(const QString& id) const
{
return resourceAttributes[id];
}
// void
// Project::addAccount(Account* a)
// {
// accountList.append(a);
// }
//
// void
// Project::deleteAccount(Account* a)
// {
// if (accountList.contains(a)) {
// accountList.removeAt(accountList.indexOf(a);
// }
//
// bool
// Project::addAccountAttribute(const QString& id,
// CustomAttributeDefinition* cad)
// {
// if (accountAttributes.find(id))
// return false;
//
// accountAttributes.insert(id, cad);
// return true;
// }
// const CustomAttributeDefinition*
// Project::getAccountAttribute(const QString& id) const
// {
// return accountAttributes[id];
// }
bool
Project::isWorkingDay(time_t wd) const
{
return !(workingHours[dayOfWeek(wd, false)]->isEmpty() ||
isVacation(wd));
}
bool
Project::isWorkingTime(time_t wd) const
{
if (isVacation(wd))
return false;
int dow = dayOfWeek(wd, false);
foreach (Interval *i, *getWorkingHours(dow)) {
if (i->contains(secondsOfDay(wd)))
return true;
}
return false;
}
bool
Project::isWorkingTime(const Interval& iv) const
{
if (isVacation(iv.getStart()))
return false;
int dow = dayOfWeek(iv.getStart(), false);
foreach (Interval *i, *(workingHours[dow]))
{
if (i->contains(Interval(secondsOfDay(iv.getStart()),
secondsOfDay(iv.getEnd()))))
return true;
}
return false;
}
int
Project::calcWorkingDays(const Interval& iv) const
{
int workingDays = 0;
for (time_t s = midnight(iv.getStart()); s <= iv.getEnd();
s = sameTimeNextDay(s))
if (isWorkingDay(s))
workingDays++;
return workingDays;
}
double
Project::convertToDailyLoad(long secs) const
{
return ((double) secs / (dailyWorkingHours * ONEHOUR));
}
// void
// Project::addJournalEntry(JournalEntry* entry)
// {
// journal.inSort(entry);
// }
// Journal::Iterator
// Project::getJournalIterator() const
// {
// return Journal::Iterator(journal);
// }
bool
Project::pass2(bool noDepCheck)
{
int oldErrors = TJMH.getErrors();
if (taskList.isEmpty())
{
TJMH.errorMessage(xi18nc("@info/plain", "The project does not contain any tasks."));
return false;
}
qDebug()<<"pass2 task info:";
foreach ( CoreAttributes *a, taskList ) {
Task *t = static_cast<Task*>( a );
qDebug()<<t->getName()<<t->getDuration( 0 )<<t->getPrecedes()<<t->getDepends();
}
QMap<QString, Task*> idHash;
/* The optimum size for the localtime hash is twice the number of time
* slots times 2 (because of timeslot and timeslot - 1s). */
initUtility(4 * ((end - start) / scheduleGranularity));
// Generate sequence numbers for all lists.
taskList.createIndex(true);
resourceList.createIndex(true);
// accountList.createIndex(true);
shiftList.createIndex(true);
// Initialize random generator.
srand((int) start);
// Create hash to map task IDs to pointers.
foreach (CoreAttributes *t, taskList)
{
idHash.insert(static_cast<Task*>(t)->getId(), static_cast<Task*>(t));
}
// Create cross links from dependency lists.
foreach (CoreAttributes *t, taskList)
static_cast<Task*>(t)->xRef(idHash);
foreach (CoreAttributes *t, taskList)
{
// Set dates according to implicit dependencies
static_cast<Task*>(t)->implicitXRef();
// Sort allocations properly
static_cast<Task*>(t)->sortAllocations();
// Save so far booked resources as specified resources
static_cast<Task*>(t)->saveSpecifiedBookedResources();
}
// Save a copy of all manually booked resources.
foreach (CoreAttributes *r, resourceList)
static_cast<Resource*>(r)->saveSpecifiedBookings();
/* Now we can copy the missing values from the plan scenario to the other
* scenarios. */
if (scenarioList.count() > 1)
{
qWarning()<<"TODO";
/* for (ScenarioListIterator sli(scenarioList[0]->getSubListIterator());
*sli; ++sli)
overlayScenario(0, (*sli)->getSequenceNo() - 1);*/
}
// Now check that all tasks have sufficient data to be scheduled.
setProgressInfo(QString("Checking scheduling data..."));
bool error = false;
foreach (CoreAttributes *s, scenarioList) {
foreach (CoreAttributes *t, taskList) {
if (!static_cast<Task*>(t)->preScheduleOk(static_cast<Scenario*>(s)->getSequenceNo() - 1))
{
error = true;
}
}
}
if (error)
return false;
if (!noDepCheck)
{
setProgressInfo(QString("Searching for dependency loops ..."));
if (DEBUGPS(1))
tjDebug("Searching for dependency loops ...");
// Check all tasks for dependency loops.
LDIList chkedTaskList;
foreach (CoreAttributes *t, taskList) {
if (static_cast<Task*>(t)->loopDetector(chkedTaskList))
return false;
}
setProgressInfo(QString("Searching for underspecified tasks ..."));
if (DEBUGPS(1))
tjDebug("Searching for underspecified tasks ...");
foreach (CoreAttributes *s, scenarioList) {
foreach (CoreAttributes *t, taskList) {
if (!static_cast<Task*>(t)->checkDetermination(static_cast<Scenario*>(s)->getSequenceNo() - 1))
error = true;
}
}
if (error)
return false;
}
TJ::TaskList starts;
TJ::TaskList ends;
QStringList tl;
foreach ( TJ::CoreAttributes *t, taskList ) {
tl << t->getName();
if ( ! static_cast<TJ::Task*>(t)->hasPrevious() ) {
starts << static_cast<TJ::Task*>(t);
tl << "(s)";
}
if ( ! static_cast<TJ::Task*>(t)->hasFollowers() ) {
ends << static_cast<TJ::Task*>(t);
tl << "(e)";
}
}
tl.clear();
foreach ( TJ::CoreAttributes *t, taskList ) {
tl << t->getName();
if ( ! static_cast<TJ::Task*>(t)->hasPrevious() ) {
starts << static_cast<TJ::Task*>(t);
tl << "(s)";
}
if ( ! static_cast<TJ::Task*>(t)->hasFollowers() ) {
ends << static_cast<TJ::Task*>(t);
tl << "(e)";
}
}
if (DEBUGPS(2)) {
qDebug()<<"Tasks:"<<tl;
qDebug()<<"Depends/precedes: -------------------";
tl.clear();
foreach ( TJ::CoreAttributes *t, taskList ) {
tl << t->getName() + ( static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" ) + " depends: ";
for ( QListIterator<TJ::TaskDependency*> it = static_cast<TJ::Task*>(t)->getDependsIterator(); it.hasNext(); ) {
const TJ::Task *a = it.next()->getTaskRef();
QString s = a->getName() + ( a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" );
tl << s;
}
qDebug()<<tl; tl.clear();
tl << t->getName() + ( static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" ) + " precedes: ";
for ( QListIterator<TJ::TaskDependency*> it = static_cast<TJ::Task*>(t)->getPrecedesIterator(); it.hasNext(); ) {
const TJ::Task *a = it.next()->getTaskRef();
QString s = a->getName() + ( a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" );
tl << s;
}
qDebug()<<tl; tl.clear();
}
qDebug()<<"Followers/previous: -------------------";
tl.clear();
foreach ( TJ::CoreAttributes *t, taskList ) {
tl << t->getName() + ( static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" ) + " followers: ";
for ( TJ::TaskListIterator it = static_cast<TJ::Task*>(t)->getFollowersIterator(); it.hasNext(); ) {
const TJ::Task *a = static_cast<TJ::Task*>( it.next() );
QString s = a->getName() + ( a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" );
tl << s;
}
qDebug()<<tl; tl.clear();
tl << t->getName() + ( static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" ) + " previous: ";
for ( TJ::TaskListIterator it = static_cast<TJ::Task*>(t)->getPreviousIterator(); it.hasNext(); ) {
const TJ::Task *a = static_cast<TJ::Task*>( it.next() );
QString s = a->getName() + ( a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" );
tl << s;
}
qDebug()<<tl; tl.clear();
}
qDebug()<<"Successors/predecessors: -------------------";
tl.clear();
foreach ( TJ::CoreAttributes *c, taskList ) {
tl << c->getName() + ( static_cast<TJ::Task*>(c)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" ) + " successors: ";
foreach ( TJ::CoreAttributes *t, static_cast<TJ::Task*>(c)->getSuccessors() ) {
TJ::Task *a = static_cast<TJ::Task*>(t);
QString s = a->getName() + ( a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" );
tl << s;
}
qDebug()<<tl; tl.clear();
tl << c->getName() + ( static_cast<TJ::Task*>(c)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" ) + " predecessors: ";
foreach ( TJ::CoreAttributes *t, static_cast<TJ::Task*>(c)->getPredecessors() ) {
TJ::Task *a = static_cast<TJ::Task*>(t);
QString s = a->getName() + ( a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)" );
tl << s;
}
qDebug()<<tl; tl.clear();
}
}
return TJMH.getErrors() == oldErrors;
}
bool
Project::scheduleScenario(Scenario* sc)
{
int oldErrors = TJMH.getErrors();
// setProgressInfo(QString("Scheduling scenario %1...").arg(sc->getName()));
int scIdx = sc->getSequenceNo() - 1;
prepareScenario(scIdx);
if (!schedule(scIdx))
{
if (DEBUGPS(2))
tjDebug()<<"Scheduling errors in scenario: "<<(sc->getId());
if (breakFlag)
return false;
}
finishScenario(scIdx);
foreach (CoreAttributes *r, resourceList)
{
if (!static_cast<Resource*>(r)->bookingsOk(scIdx))
break;
}
return TJMH.getErrors() == oldErrors;
}
void
Project::completeBuffersAndIndices()
{
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->computeBuffers();
}
/* Create indices for all lists according to their default sorting
* criteria. */
taskList.createIndex();
resourceList.createIndex();
// accountList.createIndex();
shiftList.createIndex();
}
bool
Project::scheduleAllScenarios()
{
bool schedulingOk = true;
foreach (CoreAttributes *s, scenarioList) {
if (static_cast<Scenario*>(s)->getEnabled())
{
if (DEBUGPS(1))
tjDebug()<<"Scheduling scenario:"<<static_cast<Scenario*>(s)->getId();
if (!scheduleScenario(static_cast<Scenario*>(s)))
schedulingOk = false;
if (breakFlag)
return false;
}
}
completeBuffersAndIndices();
return schedulingOk;
}
void
Project::overlayScenario(int base, int sc)
{
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->overlayScenario(base, sc);
}
foreach (CoreAttributes *s, scenarioList[sc]->getSubList()) {
overlayScenario(sc, static_cast<Scenario*>(s)->getSequenceNo() - 1);
}
}
void
Project::prepareScenario(int sc)
{
foreach (CoreAttributes *r, resourceList) {
static_cast<Resource*>(r)->prepareScenario(sc);
}
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->prepareScenario(sc);
}
/* First we compute the criticalness of the individual task without their
* dependency context. */
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->computeCriticalness(sc);
}
/* Then we compute the path criticalness that represents the criticalness
* of a task taking their dependency context into account. */
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->computePathCriticalness(sc);
}
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->propagateInitialValues(sc);
}
if (DEBUGTS(4))
{
tjDebug("Allocation probabilities for the resources:");
foreach (CoreAttributes *r, resourceList) {
qDebug()<<QString("Resource %1: %2%")
.arg(static_cast<Resource*>(r)->getName())
.arg(static_cast<Resource*>(r)->getAllocationProbability(sc));
}
tjDebug("Criticalnesses of the tasks with respect to resource "
"availability:");
foreach (CoreAttributes *t, taskList) {
qDebug()<<QString("Task %1: %2 %3").arg(static_cast<Task*>(t)->getName())
.arg(static_cast<Task*>(t)->getCriticalness(sc))
.arg(static_cast<Task*>(t)->getPathCriticalness(sc));
}
}
}
void
Project::finishScenario(int sc)
{
foreach (CoreAttributes *r, resourceList) {
static_cast<Resource*>(r)->finishScenario(sc);
}
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->finishScenario(sc);
}
#if 0
/* We need to have finished the scenario for all tasks before we can
* calculate the completion degree. */
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->calcCompletionDegree(sc);
}
#endif
/* If the user has not set the minSlackRate to 0 we look for critical
* paths. */
if (getScenario(sc)->getMinSlackRate() > 0.0)
{
setProgressInfo(QString("Computing critical paths..."));
/* The critical path detector needs to know the end of the last task.
* So we have to find this out first. */
time_t maxEnd = 0;
foreach (CoreAttributes *t, taskList) {
if (maxEnd < static_cast<Task*>(t)->getEnd(sc))
maxEnd = static_cast<Task*>(t)->getEnd(sc);
}
foreach (CoreAttributes *t, taskList) {
static_cast<Task*>(t)->checkAndMarkCriticalPath
(sc, getScenario(sc)->getMinSlackRate(), maxEnd);
}
}
}
TaskList Project::tasksReadyToBeScheduled(int sc, const TaskList& allLeafTasks)
{
TaskList workItems;
foreach (CoreAttributes *t, allLeafTasks) {
if (static_cast<Task*>(t)->isReadyForScheduling())
workItems.append(static_cast<Task*>(t));
}
if ( workItems.isEmpty() ) {
foreach (CoreAttributes *t, allLeafTasks) {
if (!static_cast<Task*>(t)->isSchedulingDone() && !static_cast<Task*>(t)->isReadyForScheduling()) {
TJMH.debugMessage("Not ready to be scheduled", t);
}
}
foreach (CoreAttributes *c, allLeafTasks) {
Task *t = static_cast<Task*>(c);
if (!t->isSchedulingDone() /*&& !t->isRunaway()*/) {
if (t->getScheduling() == Task::ASAP) {
time_t es = t->earliestStart(sc);
//qDebug()<<"schedule rest: earliest start"<<time2ISO(es)<<time2ISO(time_t(1));
if (es > 1) { // NOTE: es is 1 if predecessor is not scheduled!
t->propagateStart(sc, es);
} else if (t->hasAlapPredecessor()) {
time_t le = t->latestEnd(sc);
if (le > time_t(0) ) {
t->setScheduling(Task::ALAP);
t->propagateEnd(sc, le );
}// else qDebug()<<"schedule rest: no end time"<<t;
} //else qDebug()<<"schedule rest: no start time"<<t;
} else {
time_t le = t->latestEnd(sc);
if (le > time_t(0) ) {
t->propagateEnd(sc, le );
} else qDebug()<<"ALAP schedule rest: no end time"<<t;
}
if (t->isReadyForScheduling()) {
workItems.append(t);
} else qDebug()<<"Schedule rest: not ready"<<t;
}
if (workItems.isEmpty()) {
TaskList lst;
foreach (CoreAttributes *c, allLeafTasks) {
Task *t = static_cast<Task*>(c);
if (!t->isSchedulingDone()) {
lst << t;
}
}
if (lst.isEmpty()) {
return workItems; // finished
}
if (DEBUGPS(5)) {
if (!lst.isEmpty()) {
qDebug()<<"These tasks are still not ready to be scheduled:"<<allLeafTasks;
}
}
}
}
}
/* if ( workItems.isEmpty() && getTask("TJ::StartJob")->getScheduling() == Task::ASAP) {
foreach (CoreAttributes *c, allLeafTasks) {
}
} */
if ( workItems.isEmpty() && getTask("TJ::StartJob")->getScheduling() == Task::ALAP) {
qDebug()<<"tasksReadyToSchedule:"<<"backward, try really hard";
foreach (CoreAttributes *c, allLeafTasks) {
Task *task = static_cast<Task*>(c);
if (!task->isSchedulingDone()) {
continue;
}
qDebug()<<"tasksReadyToSchedule:"<<"scheduled task:"<<task<<time2ISO(task->start)<<time2ISO(task->end);
Task *predecessor = 0;
long gapLength = 0;
long gapDuration = 0;
foreach (CoreAttributes *c, task->previous) {
Task *t = static_cast<Task*>(c);
if (t->isSchedulingDone()) {
continue;
}
// get the dependency/longest gap
foreach (TaskDependency *d, t->precedes) {
if (d->getTaskRef() == task) {
predecessor = t;
gapLength = qMax(gapLength, d->getGapLength(sc));
gapDuration = qMax(gapDuration, d->getGapDuration(sc));
}
}
}
if ( predecessor == 0 ) {
continue;
}
time_t potentialDate = task->start - 1;
time_t dateBeforeLengthGap;
for (dateBeforeLengthGap = potentialDate; gapLength > 0 && dateBeforeLengthGap >= start; dateBeforeLengthGap -= getScheduleGranularity()) {
if (isWorkingTime(dateBeforeLengthGap)) {
gapLength -= getScheduleGranularity();
}
if (dateBeforeLengthGap < potentialDate - gapDuration) {
potentialDate = dateBeforeLengthGap;
} else {
potentialDate -= gapDuration;
}
}
qDebug()<<"tasksReadyToSchedule:"<<"schedule predecessor:"<<predecessor<<time2ISO(potentialDate);
predecessor->propagateEnd(sc, potentialDate);
workItems << predecessor;
break;
}
}
return workItems;
}
bool
Project::schedule(int sc)
{
int oldErrors = TJMH.getErrors();
int maxProgress = 0;
// The scheduling function only cares about leaf tasks. Container tasks
// are scheduled automatically when all their children are scheduled. So
// we create a task list that only contains leaf tasks.
TaskList allLeafTasks;
foreach (CoreAttributes *t, taskList) {
if (!static_cast<Task*>(t)->hasSubs()) {
allLeafTasks.append(static_cast<Task*>(t));
// TJMH.debugMessage("Leaf task", t);
}
}
allLeafTasks.setSorting(CoreAttributesList::PrioDown, 0);
allLeafTasks.setSorting(CoreAttributesList::PathCriticalnessDown, 1);
allLeafTasks.setSorting(CoreAttributesList::SequenceUp, 2);
allLeafTasks.sort();
maxProgress = allLeafTasks.count();
int sortedTasks = 0;
foreach (CoreAttributes *t, allLeafTasks) {
if (static_cast<Task*>(t)->isSchedulingDone())
sortedTasks++;
}
/* The workItems list contains all tasks that are ready to be scheduled at
* any given iteration. When a tasks has been scheduled completely, this
* list needs to be updated again as some tasks may now have become ready
* to be scheduled. */
TaskList workItems = tasksReadyToBeScheduled(sc, allLeafTasks);
bool done;
/* While the scheduling process progresses, the list contains more and
* more scheduled tasks. We use the cleanupTimer to remove those in
* certain intervals. As we potentially have already completed tasks in
* the list when we start, we initialize the timer with a very large
* number so the first round of cleanup is done right after the first
* scheduling pass. */
breakFlag = false;
// bool runAwayFound = false;
do
{
done = true;
time_t slot = 0;
int priority = 0;
double pathCriticalness = 0.0;
Task::SchedulingInfo schedulingInfo = Task::ASAP;
/* The task list is sorted by priority. The priority decreases towards
* the end of the list. We iterate through the list and look for a
* task that can be scheduled. It the determines the time slot that
* will be scheduled during this run for all subsequent tasks as well.
*/
foreach (CoreAttributes *t, workItems) {
// TJMH.debugMessage(QString("'%1' schedule for slot: %2, (%3 -%4)").arg(static_cast<Task*>(t)->getName()).arg(time2ISO(slot)).arg(time2ISO(start)).arg(time2ISO(end)));
if (slot == 0)
{
/* No time slot has been set yet. Check if this task can be
* scheduled and provides a suggestion. */
slot = static_cast<Task*>(t)->nextSlot(scheduleGranularity);
// TJMH.debugMessage(QString("'%1' first slot: %2, (%3 -%4)").arg(static_cast<Task*>(t)->getName()).arg(time2ISO(slot)).arg(time2ISO(start)).arg(time2ISO(end)), t);
/* If not, try the next task. */
if (slot == 0)
continue;
priority = static_cast<Task*>(t)->getPriority();
pathCriticalness = static_cast<Task*>(t)->getPathCriticalness(sc);
schedulingInfo = static_cast<Task*>(t)->getScheduling();
if (DEBUGPS(4))
qDebug()<<QString("Task '%1' (Prio %2, Direction: %3) requests slot %4")
.arg(static_cast<Task*>(t)->getName()).arg(static_cast<Task*>(t)->getPriority())
.arg(static_cast<Task*>(t)->getScheduling())
.arg(time2ISO(slot));
/* If the task wants a time slot outside of the project time
* frame, we flag this task as a runaway and go to the next
* task. */
if (slot < start ||
slot > (end - (time_t) scheduleGranularity + 1))
{
if (!static_cast<Task*>(t)->isRunaway()) {
if (slot < start) {
static_cast<Task*>(t)->warningMessage(i18n("Attempt to schedule task to start before project target time"));
} else {
static_cast<Task*>(t)->warningMessage(i18n("Attempt to schedule task to end after project target time"));
}
static_cast<Task*>(t)->setRunaway();
}
// runAwayFound = true;
slot = 0;
continue;
}
}
done = false;
/* Each task has a scheduling direction (forward or backward)
* depending on it's constrains. The task with the highest
* priority/pathCriticalness determines the time slot and hence the
* scheduling direction. Since tasks that have the other direction
* cannot the scheduled then, we have to stop this run as soon as
* we hit a task that runs in the other direction. If we would not
* do this, tasks with lower priority/pathCriticalness would grab
* resources form tasks with higher priority. */
if (static_cast<Task*>(t)->getScheduling() != schedulingInfo &&
!static_cast<Task*>(t)->isMilestone())
{
if (DEBUGPS(4))
qDebug()<<QString("Changing scheduling direction to %1 due to task '%2'")
.arg(static_cast<Task*>(t)->getScheduling())
.arg(static_cast<Task*>(t)->getName());
break;
}
/* We must avoid that lower priority tasks get resources even
* though there are higher priority tasks that are ready to be
* scheduled but have a non-adjacent last slot. If two tasks have
* the same priority the pathCriticalness is being used. */
if (static_cast<Task*>(t)->getPriority() < priority ||
(static_cast<Task*>(t)->getPriority() == priority &&
static_cast<Task*>(t)->getPathCriticalness(sc) < pathCriticalness))
break;
// Schedule this task for the current time slot.
if (static_cast<Task*>(t)->schedule(sc, slot, scheduleGranularity))
{
workItems = tasksReadyToBeScheduled(sc, allLeafTasks);
int oldSortedTasks = sortedTasks;
sortedTasks = 0;
foreach (CoreAttributes *t, allLeafTasks) {
if (static_cast<Task*>(t)->isSchedulingDone())
sortedTasks++;
}
// Update the progress bar after every 10th completed tasks.
if (oldSortedTasks / 10 != sortedTasks / 10)
{
setProgressBar(100 * ( (double)sortedTasks / maxProgress ), sortedTasks);
setProgressInfo(QString("Scheduling scenario %1 at %2")
.arg(getScenarioId(sc)).arg(time2tjp(slot)));
}
}
}
} while (!done && !breakFlag);
if (breakFlag)
{
setProgressInfo("");
setProgressBar(0, 0);
TJMH.infoMessage(xi18nc("@info/plain", "Scheduling aborted on user request"));
return false;
}
// if (runAwayFound) {
// foreach (CoreAttributes *t, taskList) {
// if (static_cast<Task*>(t)->isRunaway()) {
// if (static_cast<Task*>(t)->getScheduling() == Task::ASAP) {
// TJMH.errorMessage(xi18nc("@info/plain", "Cannot meet the projects target finish time. Try using a later project end date.", t->getName()), t);
// } else {
// TJMH.errorMessage(xi18nc("@info/plain", "Cannot meet the projects target start time. Try using an earlier project start date.", t->getName()), t);
// }
// }
// }
// }
if (TJMH.getErrors() == oldErrors)
setProgressBar(100, 100);
/* Check that the resulting schedule meets all the requirements that the
* user has specified. */
setProgressInfo(QString("Checking schedule of scenario %1")
.arg(getScenarioId(sc)));
checkSchedule(sc);
return TJMH.getErrors() == oldErrors;
}
void
Project::breakScheduling()
{
breakFlag = true;
}
bool
Project::checkSchedule(int sc) const
{
int oldErrors = TJMH.getErrors();
foreach (CoreAttributes *t, taskList) {
/* Only check top-level tasks, since they recursively check their sub
* tasks. */
if (static_cast<Task*>(t)->getParent() == 0)
static_cast<Task*>(t)->scheduleOk(sc);
if (maxErrors > 0 && TJMH.getErrors() >= maxErrors)
{
TJMH.errorMessage(xi18nc("@info/plain", "Too many errors. Giving up."));
return false;
}
}
return TJMH.getErrors() == oldErrors;
}
// Report*
// Project::getReport(uint idx) const
// {
// QPtrListIterator<Report> it(reports);
// for (uint i = 0; *it && i < idx; ++it, ++i)
// ;
// return *it;
// }
//
// /*QPtr*/ListIterator<Report>
// Project::getReportListIterator() const
// {
// return QPtrListIterator<Report>(reports);
// }
//
// bool
// Project::generateReports() const
// {
// // Generate reports
// int errors = 0;
// for (QPtrListIterator<Report> ri(reports); *ri != 0; ++ri)
// {
// // We generate all but Qt*Reports. Those are for the GUI version.
// if (strncmp((*ri)->getType(), "Qt", 2) != 0)
// {
// if (DEBUGPS(1))
// tjDebug(QString("Generating report '%1' ...")
// .arg((*ri)->getFileName()));
//
// if (!(*ri)->generate())
// errors++;
// }
// }
//
// generateXMLReport();
//
// return errors == 0;
// }
//
// bool Project::generateXMLReport() const
// {
// if ( xmlreport )
// return xmlreport->generate();
// else
// return false;
// }
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/Resource.cpp b/src/plugins/schedulers/tj/taskjuggler/Resource.cpp
index 8120944e..a3214f46 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Resource.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Resource.cpp
@@ -1,1395 +1,1396 @@
/*
* Resource.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Resource.h"
#include <KLocalizedString>
#include <assert.h>
#include "ResourceTreeIterator.h"
#include "Project.h"
#include "ShiftSelection.h"
#include "BookingList.h"
// #include "Account.h"
#include "UsageLimits.h"
#include "TjMessageHandler.h"
#include "tjlib-internal.h"
// #include "ReportXML.h"
#include "CustomAttributeDefinition.h"
namespace TJ
{
/*
* Calls to sbIndex are fairly expensive due to the floating point
* division. We therefor use buffers that stores the index of the
* first/last slot of a day/week/month for each slot.
*/
static uint* DayStartIndex = 0;
static uint* WeekStartIndex = 0;
static uint* MonthStartIndex = 0;
static uint* DayEndIndex = 0;
static uint* WeekEndIndex = 0;
static uint* MonthEndIndex = 0;
Resource::Resource(Project* p, const QString& i, const QString& n,
Resource* pr, const QString& df, uint dl) :
CoreAttributes(p, i, n, pr, df, dl),
minEffort(0.0),
// journal(),
limits(0),
efficiency(0.0),
rate(0.0),
workingHours(),
shifts(),
vacations(),
scoreboard(0),
sbSize((p->getEnd() + 1 - p->getStart()) / p->getScheduleGranularity() + 1),
specifiedBookings(new SbBooking**[p->getMaxScenarios()]),
scoreboards(new SbBooking**[p->getMaxScenarios()]),
scenarios(new ResourceScenario[p->getMaxScenarios()]),
allocationProbability(new double[p->getMaxScenarios()])
{
// vacations.setAutoDelete(true);
// shifts.setAutoDelete(true);
p->addResource(this);
for (int sc = 0; sc < p->getMaxScenarios(); sc++)
{
scoreboards[sc] = 0;
specifiedBookings[sc] = 0;
}
for (int i = 0; i < p->getMaxScenarios(); ++i)
allocationProbability[i] = 0;
if (!DayStartIndex)
{
DayStartIndex = new uint[sbSize];
WeekStartIndex = new uint[sbSize];
MonthStartIndex = new uint[sbSize];
long i = 0;
uint dayStart = 0;
uint weekStart = 0;
uint monthStart = 0;
bool weekStartsMonday = project->getWeekStartsMonday();
for (time_t ts = p->getStart(); i < (long) sbSize; ts +=
p->getScheduleGranularity(), ++i)
{
if (ts == midnight(ts))
dayStart = i;
DayStartIndex[i] = dayStart;
if (ts == beginOfWeek(ts, weekStartsMonday))
weekStart = i;
WeekStartIndex[i] = weekStart;
if (ts == beginOfMonth(ts))
monthStart = i;
MonthStartIndex[i] = monthStart;
}
DayEndIndex = new uint[sbSize];
WeekEndIndex = new uint[sbSize];
MonthEndIndex = new uint[sbSize];
i = sbSize - 1;
uint dayEnd = i;
uint weekEnd = i;
uint monthEnd = i;
// WTF does p->getEnd not return the 1st sec after the time frame!!!
for (time_t ts = p->getEnd() + 1; i >= 0;
ts -= p->getScheduleGranularity(), --i)
{
DayEndIndex[i] = dayEnd;
if (ts - midnight(ts) < (int) p->getScheduleGranularity())
dayEnd = i > 0 ? i - 1 : 0;
WeekEndIndex[i] = weekEnd;
if (ts - beginOfWeek(ts, weekStartsMonday) <
(int) p->getScheduleGranularity())
weekEnd = i > 0 ? i - 1 : 0;
MonthEndIndex[i] = monthEnd;
if (ts - beginOfMonth(ts) < (int) p->getScheduleGranularity())
monthEnd = i > 0 ? i - 1 : 0;
}
}
for (int i = 0; i < 7; i++)
{
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
}
}
Resource::~Resource()
{
int i;
for (i = 0; i < 7; i++) {
while (!workingHours[i]->isEmpty()) delete workingHours[i]->takeFirst();
delete workingHours[i];
}
for (int sc = 0; sc < project->getMaxScenarios(); sc++)
{
if (scoreboards[sc])
{
for (uint i = 0; i < sbSize; i++)
if (scoreboards[sc][i] >= (SbBooking*) 4)
{
uint j;
for (j = i + 1; j < sbSize &&
scoreboards[sc][i] == scoreboards[sc][j]; j++)
;
delete scoreboards[sc][i];
i = j - 1;
}
delete [] scoreboards[sc];
scoreboards[sc] = 0;
}
if (specifiedBookings[sc])
{
for (uint i = 0; i < sbSize; i++)
if (specifiedBookings[sc][i] >= (SbBooking*) 4)
{
uint j;
for (j = i + 1; j < sbSize &&
specifiedBookings[sc][i] == specifiedBookings[sc][j];
j++)
;
delete specifiedBookings[sc][i];
i = j - 1;
}
delete [] specifiedBookings[sc];
specifiedBookings[sc] = 0;
}
}
delete [] allocationProbability;
delete [] specifiedBookings;
delete [] scoreboards;
delete [] scenarios;
delete limits;
project->deleteResource(this);
}
void
Resource::deleteStaticData()
{
delete [] DayStartIndex;
delete [] WeekStartIndex;
delete [] MonthStartIndex;
delete [] DayEndIndex;
delete [] WeekEndIndex;
delete [] MonthEndIndex;
DayStartIndex = 0;
WeekStartIndex = 0;
MonthStartIndex = 0;
DayEndIndex = 0;
WeekEndIndex = 0;
MonthEndIndex = 0;
}
void
Resource::inheritValues()
{
Resource* pr = (Resource*) parent;
if (pr)
{
// Inherit flags from parent resource.
for (QStringList::ConstIterator it = pr->flags.constBegin();
it != pr->flags.constEnd(); ++it)
addFlag(*it);
// Inherit default working hours from parent resource.
for (int i = 0; i < 7; i++)
{
while (!workingHours[i]->isEmpty()) delete workingHours[i]->takeFirst();
delete workingHours[i];
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
for (QListIterator<Interval*> ivi(*pr->workingHours[i]); ivi.hasNext();)
workingHours[i]->append(new Interval(*(ivi.next())));
}
// Inherit vacation intervals from parent resource.
for (QListIterator<Interval*> vli(pr->vacations); vli.hasNext();)
vacations.append(new Interval(*(vli.next())));
minEffort = pr->minEffort;
if (pr->limits)
limits = new UsageLimits(*pr->limits);
else
limits = 0;
rate = pr->rate;
efficiency = pr->efficiency;
// Inherit inheritable custom attributes
inheritCustomAttributes(project->getResourceAttributeDict());
}
else
{
// Inherit from default working hours project defaults.
for (int i = 0; i < 7; i++)
{
while (!workingHours[i]->isEmpty()) delete workingHours[i]->takeFirst();
delete workingHours[i];
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
for (QListIterator<Interval*>ivi(project->getWorkingHoursIterator(i)); ivi.hasNext();) {
workingHours[i]->append(new Interval(*(ivi.next())));
}
}
minEffort = project->getMinEffort();
if (project->getResourceLimits())
limits = new UsageLimits(*project->getResourceLimits());
else
limits = 0;
// rate = project->getRate();
efficiency = 1.0;
}
}
void
Resource::setLimits(UsageLimits* l)
{
if (limits)
delete limits;
limits = l;
}
void
Resource::initScoreboard()
{
scoreboard = new SbBooking*[sbSize];
// First mark all scoreboard slots as unavailable (1).
for (uint i = 0; i < sbSize; i++)
scoreboard[i] = (SbBooking*) 1;
// Then change all worktime slots to 0 (available) again.
for (time_t t = project->getStart(); t < project->getEnd() + 1;
t += project->getScheduleGranularity())
{
if (isOnShift(Interval(t, t + project->getScheduleGranularity() - 1))) {
scoreboard[sbIndex(t)] = (SbBooking*) 0;
}
}
// Then mark all resource specific vacation slots as such (2).
for (QListIterator<Interval*> ivi(vacations); ivi.hasNext();) {
Interval *i = ivi.next();
for (time_t date = i->getStart() > project->getStart() ?
i->getStart() : project->getStart();
date < i->getEnd() && date < project->getEnd() + 1;
date += project->getScheduleGranularity()) {
scoreboard[sbIndex(date)] = (SbBooking*) 2;
}
}
// Mark all global vacation slots as such (2)
for (VacationList::Iterator ivi(project->getVacationListIterator()); ivi.hasNext();)
{
Interval *i = ivi.next();
if (i->getStart() > project->getEnd() ||
i->getEnd() < project->getStart())
continue;
uint startIdx = sbIndex(i->getStart() >= project->getStart() ?
i->getStart() : project->getStart());
uint endIdx = sbIndex(i->getEnd() >= project->getStart() ?
i->getEnd() : project->getEnd());
for (uint idx = startIdx; idx <= endIdx; ++idx)
scoreboard[idx] = (SbBooking*) 2;
}
}
uint
Resource::sbIndex(time_t date) const
{
if (date < project->getStart()) qDebug()<<"Resource::sbIndex:"<<time2ISO(date)<<time2ISO(project->getStart());
assert(date >= project->getStart());
if (date > project->getEnd()) qDebug()<<"Resource::sbIndex:"<<time2ISO(date)<<time2ISO(project->getEnd());
assert(date <= project->getEnd());
// Convert date to corresponding scoreboard index.
uint sbIdx = (date - project->getStart()) /
project->getScheduleGranularity();
assert(sbIdx < sbSize);
return sbIdx;
}
time_t
Resource::index2start(uint idx) const
{
return project->getStart() + idx *
project->getScheduleGranularity();
}
time_t
Resource::index2end(uint idx) const
{
return project->getStart() + (idx + 1) *
project->getScheduleGranularity() - 1;
}
/**
* \retval 0 { resource is available }
* \retval 1 { resource is unavailable }
* \retval 2 { resource is on vacation }
* \retval 3 { undefined }
* \retval 4 { resource is allocated to a task }
*/
int
Resource::isAvailable(time_t date)
{
/* The scoreboard of a resource is only generated on demand, so that large
* resource lists that are only scarcely used for the project do not slow
* TJ down too much. */
if (!scoreboard)
initScoreboard();
// Check if the interval is booked or blocked already.
uint sbIdx = sbIndex(date);
if (scoreboard[sbIdx])
{
QString reason;
if (scoreboard[sbIdx] == ((SbBooking*) 1)) {
reason = "off-hour";
} else if (scoreboard[sbIdx] == ((SbBooking*) 2)) {
reason = "vacation";
} else if (scoreboard[sbIdx] == ((SbBooking*) 3)) {
reason = "UNDEFINED";
} else {
reason = "allocated to " + scoreboard[sbIdx]->getTask()->getName();
}
if (DEBUGRS(6)) {
qDebug()<<QString(" Resource %1 is busy (%2) at: %3").arg(name).arg(reason).arg(time2ISO(date));
}
return scoreboard[sbIdx] < ((SbBooking*) 4) ? 1 : 4;
}
if (!limits) {
// TJMH.debugMessage(QString("Resource is available today (%1) ").arg(time2ISO(date)), this);
return 0;
}
if (limits && limits->getDailyUnits() > 0) {
int bookedSlots = 1;
int workSlots = 0;
for (uint i = DayStartIndex[sbIdx]; i <= DayEndIndex[sbIdx]; i++) {
SbBooking* b = scoreboard[i];
if (b == (SbBooking*) 0) {
++workSlots;
} else if (b >= (SbBooking*) 4) {
++workSlots;
++bookedSlots;
}
}
if ( workSlots > 0 ) {
workSlots = (workSlots * limits->getDailyUnits()) / 100;
if (workSlots == 0) {
workSlots = 1;
}
}
if (bookedSlots > workSlots) {
if (DEBUGRS(2)) {
qDebug()<<"Resource is overloaded:"<<name<<"units="<<limits->getDailyUnits()<<"work="<<workSlots<<"booked="<<bookedSlots;
}
// TJMH.debugMessage(QString("Resource is overloaded today: %1 (%2 slots)").arg(time2ISO(date)).arg(bookedSlots), this);
return 2; //TODO review
}
}
else if ((limits && limits->getDailyMax() > 0))
{
// Now check that the resource is not overloaded on this day.
uint bookedSlots = 1;
for (uint i = DayStartIndex[sbIdx]; i <= DayEndIndex[sbIdx]; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
bookedSlots++;
}
if (limits && limits->getDailyMax() > 0 &&
bookedSlots > limits->getDailyMax())
{
if (DEBUGRS(6))
qDebug()<<QString(" Resource %1 overloaded today (%2)").arg(name).arg(bookedSlots);
// TJMH.debugMessage(QString("Resource is overloaded today: %1 (%2 slots)").arg(time2ISO(date)).arg(bookedSlots), this);
return 2;
}
}
if ((limits && limits->getWeeklyMax() > 0))
{
// Now check that the resource is not overloaded on this week.
uint bookedSlots = 1;
for (uint i = WeekStartIndex[sbIdx]; i <= WeekEndIndex[sbIdx]; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
bookedSlots++;
}
if (limits && limits->getWeeklyMax() > 0 &&
bookedSlots > limits->getWeeklyMax())
{
if (DEBUGRS(6))
qDebug()<<QString(" Resource %1 overloaded this week (%2)").arg(name).arg(bookedSlots);
return 2;
}
}
if ((limits && limits->getMonthlyMax() > 0))
{
// Now check that the resource is not overloaded on this month.
uint bookedSlots = 1;
for (uint i = MonthStartIndex[sbIdx]; i <= MonthEndIndex[sbIdx]; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
bookedSlots++;
}
if (limits && limits->getMonthlyMax() > 0 &&
bookedSlots > limits->getMonthlyMax())
{
if (DEBUGRS(6))
qDebug()<<QString(" Resource %1 overloaded this month (%2)").arg(name).arg(bookedSlots);
return 2;
}
}
return 0;
}
bool
Resource::book(Booking* nb)
{
uint idx = sbIndex(nb->getStart());
return bookSlot(idx, nb);
}
bool
Resource::bookSlot(uint idx, SbBooking* nb)
{
// Make sure that the time slot is still available.
if (scoreboard[idx] > (SbBooking*) 0)
{
delete nb;
return false;
}
SbBooking* b;
// Try to merge the booking with the booking in the previous slot.
if (idx > 0 && (b = scoreboard[idx - 1]) >= (SbBooking*) 4 &&
b->getTask() == nb->getTask())
{
scoreboard[idx] = b;
delete nb;
return true;
}
// Try to merge the booking with the booking in the following slot.
if (idx < sbSize - 1 && (b = scoreboard[idx + 1]) >= (SbBooking*) 4 &&
b->getTask() == nb->getTask())
{
scoreboard[idx] = b;
delete nb;
return true;
}
scoreboard[idx] = nb;
return true;
}
//bool
//Resource::bookInterval(Booking* nb, int sc, int sloppy, int overtime)
//{
// uint sIdx = sbIndex(nb->getStart());
// uint eIdx = sbIndex(nb->getEnd());
// bool conflict = false;
// for (uint i = sIdx; i <= eIdx; i++)
// if (scoreboard[i] > (SbBooking*) overtime)
// {
// uint j;
// for (j = i + 1 ; j <= eIdx &&
// scoreboard[i] == scoreboard[j]; j++)
// ;
// if (scoreboard[i] == (SbBooking*) 1)
// {
// if (sloppy > 0)
// {
// i = j;
// continue;
// }
// TJMH.errorMessage(xi18nc("@info/plain 1=datetime 2=task name", "Resource is unavailable at %1. It cannot be assigned to task %2.", formatTime(index2start(i)), nb->getTask()->getName()), this);
// }
// else if (scoreboard[i] == (SbBooking*) 2)
// {
// if (sloppy > 1)
// {
// i = j;
// continue;
// }
// TJMH.errorMessage(xi18nc("@info/plain 1=datetime 2=task name", "Resource is on vacation at %1. It cannot be assigned to task %2.", formatTime(index2start(i)), nb->getTask()->getName()), this);
// }
// else
// {
// if (sloppy > 2)
// {
// i = j;
// continue;
// }
// TJMH.errorMessage(xi18nc("@info/plain 1=datetime 2=task name 3=task name", "Allocation conflict at %1. Conflicting tasks are %2 and %3.", formatTime(index2start(i)), scoreboard[i]->getTask()->getName(), nb->getTask()->getName()), this);
// }
// conflict = true;
// i = j;
// }
// if (conflict)
// return false;
// for (uint i = sIdx; i <= eIdx; i++)
// if (scoreboard[i] <= static_cast<SbBooking*>(1))
// bookSlot(i, new SbBooking(*nb), overtime);
// return true;
//}
//bool
//Resource::addBooking(int sc, Booking* nb, int sloppy, int overtime)
//{
// SbBooking** tmp = scoreboard;
// if (scoreboards[sc])
// scoreboard = scoreboards[sc];
// else
// initScoreboard();
// bool retVal = bookInterval(nb, sc, sloppy, overtime);
// // Cross register booking with task.
// if (retVal && nb->getTask())
// nb->getTask()->addBookedResource(sc, this);
// delete nb;
// scoreboards[sc] = scoreboard;
// scoreboard = tmp;
// return retVal;
//}
bool
Resource::addShift(const Interval& i, Shift* s)
{
return shifts.insert(new ShiftSelection(i, s));
}
bool
Resource::addShift(ShiftSelection* s)
{
return shifts.insert(s);
}
void
Resource::addVacation(Interval* i)
{
vacations.append(i);
}
bool
Resource::isWorker() const
{
for (ConstResourceTreeIterator rti(this); *rti; ++rti)
if ((*rti)->efficiency == 0.0)
return false;
return true;
}
double
Resource::getCurrentLoad(const Interval& period, const Task* task) const
{
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return 0.0;
return efficiency * project->convertToDailyLoad
(getCurrentLoadSub(sbIndex(iv.getStart()), sbIndex(iv.getEnd()), task) *
project->getScheduleGranularity());
}
long
Resource::getCurrentLoadSub(uint startIdx, uint endIdx, const Task* task) const
{
long bookings = 0;
for (ResourceListIterator rli(*sub); rli.hasNext();) {
Resource *r = static_cast<Resource*>(rli.next());
bookings += r->getCurrentLoadSub(startIdx, endIdx, task);
}
if (!scoreboard)
return bookings;
for (uint i = startIdx; i <= endIdx && i < sbSize; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
if (!task || task == b->getTask() || b->getTask()->isDescendantOf(task))
bookings++;
}
return bookings;
}
uint
Resource::getWorkSlots(time_t date) const
{
if (!scoreboard) {
return 0;
}
uint workSlots = 0;
uint sbIdx = sbIndex(date);
for (uint i = DayStartIndex[sbIdx]; i <= DayEndIndex[sbIdx]; i++) {
SbBooking* b = scoreboard[i];
if (b == (SbBooking*) 0 || b >= (SbBooking*) 4) {
++workSlots;
}
}
return workSlots;
}
uint
Resource::getCurrentDaySlots(time_t date, const Task* t)
{
/* Return the number of slots this resource is allocated to in the current
* scenario. If a task is given, only the slots allocated to this task
* will be counted. */
if (hasSubs())
{
uint timeSlots = 0;
for (ResourceListIterator rli(getSubListIterator()); rli.hasNext();) {
Resource *r = static_cast<Resource*>(rli.next());
timeSlots += r->getCurrentDaySlots(date, t);
}
return timeSlots;
}
if (!scoreboard)
return 0;
uint sbIdx = sbIndex(date);
uint bookedSlots = 0;
for (uint i = DayStartIndex[sbIdx]; i <= DayEndIndex[sbIdx]; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
if (!t || b->getTask() == t || b->getTask()->isDescendantOf(t))
bookedSlots++;
}
return bookedSlots;
}
uint
Resource::getCurrentWeekSlots(time_t date, const Task* t)
{
/* Return the number of slots this resource is allocated to in the current
* scenario. If a task is given, only the slots allocated to this task
* will be counted. */
if (hasSubs())
{
uint timeSlots = 0;
for (ResourceListIterator rli(getSubListIterator()); rli.hasNext();) {
timeSlots += (*rli)->getCurrentWeekSlots(date, t);
}
return timeSlots;
}
if (!scoreboard)
return 0;
uint sbIdx = sbIndex(date);
uint bookedSlots = 0;
for (uint i = WeekStartIndex[sbIdx]; i <= WeekEndIndex[sbIdx]; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
if (!t || b->getTask() == t || b->getTask()->isDescendantOf(t))
bookedSlots++;
}
return bookedSlots;
}
uint
Resource::getCurrentMonthSlots(time_t date, const Task* t)
{
/* Return the number of slots this resource is allocated to in the current
* scenario. If a task is given, only the slots allocated to this task
* will be counted. */
if (hasSubs())
{
uint timeSlots = 0;
for (ResourceListIterator rli(getSubListIterator()); rli.hasNext();) {
timeSlots += (*rli)->getCurrentMonthSlots(date, t);
}
return timeSlots;
}
if (!scoreboard)
return 0;
uint sbIdx = sbIndex(date);
uint bookedSlots = 0;
for (uint i = MonthStartIndex[sbIdx]; i <= MonthEndIndex[sbIdx]; i++)
{
SbBooking* b = scoreboard[i];
if (b < (SbBooking*) 4)
continue;
if (!t || b->getTask() == t || b->getTask()->isDescendantOf(t))
bookedSlots++;
}
return bookedSlots;
}
double
Resource::getEffectiveLoad(int sc, const Interval& period, AccountType acctType,
const Task* task) const
{
double load = 0.0;
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return 0.0;
if (hasSubs())
{
for (ResourceListIterator rli(*sub); rli.hasNext();) {
Resource *r = static_cast<Resource*>(rli.next());
load += r->getEffectiveLoad(sc, iv, acctType, task);
}
}
else
{
uint startIdx = sbIndex(iv.getStart());
uint endIdx = sbIndex(iv.getEnd());
load = project->convertToDailyLoad
(getAllocatedSlots(sc, startIdx, endIdx, acctType, task) *
project->getScheduleGranularity()) * efficiency;
}
return load;
}
double
Resource::getAllocatedTimeLoad(int sc, const Interval& period,
AccountType acctType, const Task* task) const
{
return project->convertToDailyLoad
(getAllocatedTime(sc, period, acctType, task));
}
long
Resource::getAllocatedTime(int sc, const Interval& period, AccountType acctType,
const Task* task) const
{
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return 0;
uint startIdx = sbIndex(iv.getStart());
uint endIdx = sbIndex(iv.getEnd());
if (scenarios[sc].firstSlot > 0 && scenarios[sc].lastSlot > 0)
{
if (startIdx < (uint) scenarios[sc].firstSlot)
startIdx = scenarios[sc].firstSlot;
if (endIdx > (uint) scenarios[sc].lastSlot)
endIdx = scenarios[sc].lastSlot;
}
return getAllocatedSlots(sc, startIdx, endIdx, acctType, task) *
project->getScheduleGranularity();
}
long
Resource::getAllocatedSlots(int sc, uint startIdx, uint endIdx,
AccountType acctType, const Task* task) const
{
long bookings = 0;
if (isGroup())
{
for (ResourceListIterator rli(*sub); rli.hasNext();) {
Resource *r = static_cast<Resource*>(rli.next());
bookings += r->getAllocatedSlots(sc, startIdx, endIdx,
acctType, task);
}
return bookings;
}
// If the scoreboard has not been initialized there is no load.
if (!scoreboards[sc])
return bookings;
if (scenarios[sc].firstSlot > 0 && scenarios[sc].lastSlot > 0)
{
if (task)
{
/* If the load is to be calculated for a certain task, we check
* whether this task is in the resource allocation list. Only then
* we calculate the real number of allocated slots. */
bool isAllocated = false;
for (TaskListIterator tli(scenarios[sc].allocatedTasks); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (task == t || t->isDescendantOf(task))
{
isAllocated = true;
break;
}
}
if (!isAllocated)
return bookings;
}
if (startIdx < (uint) scenarios[sc].firstSlot)
startIdx = scenarios[sc].firstSlot;
if (endIdx > (uint) scenarios[sc].lastSlot)
endIdx = scenarios[sc].lastSlot;
}
for (uint i = startIdx; i <= endIdx && i < sbSize; i++)
{
SbBooking* b = scoreboards[sc][i];
if (b < (SbBooking*) 4)
continue;
if ((task == 0 ||
(task != 0 && (task == b->getTask() ||
b->getTask()->isDescendantOf(task))))/* &&
(acctType == AllAccounts ||
(b->getTask()->getAccount() && b->getTask()->getAccount()->getAcctType() == acctType))*/)
bookings++;
}
return bookings;
}
double
Resource::getEffectiveFreeLoad(int sc, const Interval& period)
{
double load = 0.0;
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return 0.0;
if (hasSubs())
{
for (ResourceListIterator rli(*sub); *rli != 0; ++rli)
load += (*rli)->getEffectiveFreeLoad(sc, iv);
}
else
{
uint startIdx = sbIndex(iv.getStart());
uint endIdx = sbIndex(iv.getEnd());
load = project->convertToDailyLoad
(getAvailableSlots(sc, startIdx, endIdx) *
project->getScheduleGranularity()) * efficiency;
}
return load;
}
double
Resource::getAvailableTimeLoad(int sc, const Interval& period)
{
return project->convertToDailyLoad(getAvailableTime(sc, period));
}
long
Resource::getAvailableTime(int sc, const Interval& period)
{
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return 0;
return getAvailableSlots(sc, sbIndex(iv.getStart()),
sbIndex(iv.getEnd())) *
project->getScheduleGranularity();
}
long
Resource::getAvailableSlots(int sc, uint startIdx, uint endIdx)
{
long availSlots = 0;
if (!sub->isEmpty())
{
for (ResourceListIterator rli(*sub); *rli != 0; ++rli)
availSlots += (*rli)->getAvailableSlots(sc, startIdx, endIdx);
}
else
{
if (!scoreboards[sc])
{
scoreboard = scoreboards[sc];
initScoreboard();
scoreboards[sc] = scoreboard;
}
for (uint i = startIdx; i <= endIdx; i++)
if (scoreboards[sc][i] == 0)
availSlots++;
}
return availSlots;
}
double
Resource::getCredits(int sc, const Interval& period, AccountType acctType,
const Task* task) const
{
return project->convertToDailyLoad
(getAllocatedTime(sc, period, acctType, task)) * rate;
}
bool
Resource::isAllocated(int sc, const Interval& period, const QString& prjId)
const
{
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return false;
uint startIdx = sbIndex(iv.getStart());
uint endIdx = sbIndex(iv.getEnd());
if (scenarios[sc].firstSlot > 0 && scenarios[sc].lastSlot > 0)
{
if (startIdx < (uint) scenarios[sc].firstSlot)
startIdx = scenarios[sc].firstSlot;
if (endIdx > (uint) scenarios[sc].lastSlot)
endIdx = scenarios[sc].lastSlot;
}
if (endIdx < startIdx)
return false;
return isAllocatedSub(sc, startIdx, endIdx, prjId);
}
bool
Resource::isAllocatedSub(int sc, uint startIdx, uint endIdx, const QString&
prjId) const
{
/* If resource is a group, check members first. */
for (ResourceListIterator rli(*sub); *rli != 0; ++rli)
if ((*rli)->isAllocatedSub(sc, startIdx, endIdx, prjId))
return true;
if (!scoreboards[sc])
return false;
for (uint i = startIdx; i <= endIdx; i++)
{
SbBooking* b = scoreboards[sc][i];
if (b < (SbBooking*) 4)
continue;
if (prjId.isNull() || b->getTask()->getProjectId() == prjId)
return true;
}
return false;
}
bool
Resource::isAllocated(int sc, const Interval& period, const Task* task) const
{
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return false;
uint startIdx = sbIndex(iv.getStart());
uint endIdx = sbIndex(iv.getEnd());
if (scenarios[sc].firstSlot > 0 && scenarios[sc].lastSlot > 0)
{
if (startIdx < (uint) scenarios[sc].firstSlot)
startIdx = scenarios[sc].firstSlot;
if (endIdx > (uint) scenarios[sc].lastSlot)
endIdx = scenarios[sc].lastSlot;
}
if (endIdx < startIdx)
return false;
return isAllocatedSub(sc, startIdx, endIdx, task);
}
bool
Resource::isAllocatedSub(int sc, uint startIdx, uint endIdx, const Task* task)
const
{
/* If resource is a group, check members first. */
for (ResourceListIterator rli(*sub); *rli != 0; ++rli)
if ((*rli)->isAllocatedSub(sc, startIdx, endIdx, task))
return true;
if (!scoreboards[sc])
return false;
for (uint i = startIdx; i <= endIdx; i++)
{
SbBooking* b = scoreboards[sc][i];
if (b < (SbBooking*) 4)
continue;
if (!task || b->getTask() == task || b->getTask()->isDescendantOf(task))
return true;
}
return false;
}
void
Resource::getPIDs(int sc, const Interval& period, const Task* task,
QStringList& pids) const
{
Interval iv(period);
if (!iv.overlap(Interval(project->getStart(), project->getEnd())))
return;
for (ResourceListIterator rli(*sub); *rli != 0; ++rli)
(*rli)->getPIDs(sc, iv, task, pids);
if (!scoreboards[sc])
return;
for (uint i = sbIndex(iv.getStart());
i <= sbIndex(iv.getEnd()) && i < sbSize; i++)
{
SbBooking* b = scoreboards[sc][i];
if (b < (SbBooking*) 4)
continue;
if ((!task || task == b->getTask() ||
b->getTask()->isDescendantOf(task)) &&
pids.indexOf(b->getTask()->getProjectId()) == -1)
{
pids.append(b->getTask()->getProjectId());
}
}
}
QString
Resource::getProjectIDs(int sc, const Interval& period, const Task* task) const
{
QStringList pids;
getPIDs(sc, period, task, pids);
QString pidStr;
for (QStringList::ConstIterator it = pids.constBegin(); it != pids.constEnd(); ++it)
pidStr += QString(it != pids.constBegin() ? ", " : "") + *it;
return pidStr;
}
bool
Resource::hasVacationDay(time_t day) const
{
Interval fullDay(midnight(day),
sameTimeNextDay(midnight(day)) - 1);
for (QListIterator<Interval*> vli(vacations); vli.hasNext();) {
if (vli.next()->overlaps(fullDay))
return true;
}
if (shifts.isVacationDay(day))
return true;
if (workingHours[dayOfWeek(day, false)]->isEmpty())
return true;
return false;
}
bool
Resource::isOnShift(const Interval& slot) const
{
for (ShiftSelectionList::Iterator ssli(shifts); ssli.hasNext();) {
ShiftSelection *s = ssli.next();
if (s->getPeriod().contains(slot))
return s->getShift()->isOnShift(slot);
}
int dow = dayOfWeek(slot.getStart(), false);
for (QListIterator<Interval*> ivi(*workingHours[dow]); ivi.hasNext();) {
if (ivi.next()->contains(Interval(secondsOfDay(slot.getStart()), secondsOfDay(slot.getEnd())))) {
return true;
}
}
return false;
}
void
Resource::setWorkingHours(int day, const QList<Interval*>& l)
{
while (!workingHours[day]->isEmpty()) delete workingHours[day]->takeFirst();
delete workingHours[day];
// Create a deep copy of the interval list.
workingHours[day] = new QList<Interval*>;
// workingHours[day]->setAutoDelete(true);
for (QListIterator<Interval*> pli(l); pli.hasNext();)
workingHours[day]->append(new Interval(*(pli.next())));
}
BookingList
Resource::getJobs(int sc) const
{
BookingList bl;
if (scoreboards[sc])
{
SbBooking* b = 0;
uint startIdx = 0;
for (uint i = 0; i < sbSize; i++)
if (scoreboards[sc][i] != b)
{
if (b)
bl.append(new Booking(Interval(index2start(startIdx),
index2end(i - 1)),
scoreboards[sc][startIdx]));
if (scoreboards[sc][i] >= (SbBooking*) 4)
{
b = scoreboards[sc][i];
startIdx = i;
}
else
b = 0;
}
}
return bl;
}
QVector<Interval> Resource::getBookedIntervals(int sc, const TJ::Task* task) const
{
QVector<Interval> lst;
if (scoreboards[sc] == 0)
return lst;
for (uint i = 0; i < sbSize; ++i)
{
if (scoreboards[sc][i] > ((SbBooking*) 3) && scoreboards[sc][i]->getTask() == task) {
time_t s = index2start(i);
time_t e = index2end(i);
Interval ti(s, e);
if (!lst.isEmpty() && lst.last().append(ti)) {
continue;
}
lst << ti;
}
}
return lst;
}
time_t
Resource::getStartOfFirstSlot(int sc, const Task* task)
{
if (scoreboards[sc] == 0)
return 0;
for (uint i = 0; i < sbSize; ++i)
{
if (scoreboards[sc][i] > ((SbBooking*) 3) &&
scoreboards[sc][i]->getTask() == task)
return index2start(i);
}
return 0;
}
time_t
Resource::getEndOfLastSlot(int sc, const Task* task)
{
if (scoreboards[sc] == 0)
return 0;
int i = sbSize;
for ( ; ; )
{
--i;
if (scoreboards[sc][i] > ((SbBooking*) 3) &&
scoreboards[sc][i]->getTask() == task)
return index2end(i);
if (i == 0)
break;
}
return 0;
}
void
Resource::copyBookings(int sc, SbBooking*** src, SbBooking*** dst)
{
/* This function copies a set of bookings the specified scenario. If the
* destination set already contains bookings it is cleared first.
*/
if (dst[sc])
for (uint i = 0; i < sbSize; i++)
if (dst[sc][i] >= (SbBooking*) 4)
{
/* Small pointers are fake bookings. We can safely ignore
* them. Identical pointers in successiv slots must only be
* deleted once. */
uint j;
for (j = i + 1; j < sbSize &&
dst[sc][i] == dst[sc][j]; j++)
;
delete dst[sc][i];
i = j - 1;
}
// Now copy the source set to the destination.
if (src[sc])
{
if (!dst[sc])
dst[sc] = new SbBooking*[sbSize];
for (uint i = 0; i < sbSize; i++)
if (src[sc][i] >= (SbBooking*) 4)
{
/* Small pointers can just be copied. Identical successiv
* pointers need to be allocated once and can then be assigned
* to all destination slots. */
dst[sc][i] = new SbBooking(src[sc][i]);
uint j;
for (j = i + 1; j < sbSize &&
src[sc][i] == src[sc][j]; j++)
dst[sc][j] = dst[sc][i];
i = j - 1;
}
else
dst[sc][i] = src[sc][i];
}
else
{
delete [] dst[sc];
dst[sc] = 0;
}
}
void
Resource::saveSpecifiedBookings()
{
for (int sc = 0; sc < project->getMaxScenarios(); sc++)
copyBookings(sc, scoreboards, specifiedBookings);
}
void
Resource::prepareScenario(int sc)
{
copyBookings(sc, specifiedBookings, scoreboards);
scoreboard = scoreboards[sc];
updateSlotMarks(sc);
}
void
Resource::finishScenario(int sc)
{
scoreboards[sc] = scoreboard;
updateSlotMarks(sc);
}
bool
Resource::bookingsOk(int sc)
{
if (scoreboards[sc] == 0)
return true;
if (hasSubs())
{
TJMH.debugMessage(QString("Group resource may not have bookings"), this);
return false;
}
for (uint i = 0; i < sbSize; ++i)
if (scoreboards[sc][i] >= ((SbBooking*) 4))
{
time_t start = index2start(i);
time_t end = index2end(i);
time_t tStart = scoreboards[sc][i]->getTask()->getStart(sc);
time_t tEnd = scoreboards[sc][i]->getTask()->getEnd(sc);
if (start < tStart || start > tEnd ||
end < tStart || end > tEnd)
{
TJMH.errorMessage(xi18nc("@info/plain 1=task name, 2, 3, 4=datetime", "Booking on task '%1' at %2 is outside of task interval (%3 - %4)", scoreboards[sc][i]->getTask()->getName(),formatTime(start), formatTime(tStart), formatTime(tEnd)), this);
return false;
}
}
return true;
}
// void
// Resource::addJournalEntry(JournalEntry* entry)
// {
// journal.append(entry);
// }
//
// Journal::Iterator
// Resource::getJournalIterator() const
// {
// return Journal::Iterator(journal);
// }
void
Resource::updateSlotMarks(int sc)
{
scenarios[sc].allocatedTasks.clear();
scenarios[sc].firstSlot = -1;
scenarios[sc].lastSlot = -1;
if (scoreboard)
{
for (uint i = 0; i < sbSize; i++)
if (scoreboard[i] > (SbBooking*) 4)
{
if (scenarios[sc].firstSlot == -1)
scenarios[sc].firstSlot = i;
scenarios[sc].lastSlot = i;
scenarios[sc].addTask(scoreboard[i]->getTask());
}
}
}
QDomElement Resource::xmlIDElement( QDomDocument& doc ) const
{
Q_UNUSED(doc);
QDomElement elem;/* = ReportXML::createXMLElem( doc, "Resource", getName());
elem.setAttribute( "Id", getId() );*/
return( elem );
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/ResourceList.cpp b/src/plugins/schedulers/tj/taskjuggler/ResourceList.cpp
index fb1545c9..6c0664b4 100644
--- a/src/plugins/schedulers/tj/taskjuggler/ResourceList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/ResourceList.cpp
@@ -1,103 +1,104 @@
/*
* ResourceList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "ResourceList.h"
#include "TjMessageHandler.h"
#include "Resource.h"
#include "Project.h"
#include "UsageLimits.h"
namespace TJ
{
ResourceList::ResourceList() :
CoreAttributesList()
{
sorting[0] = CoreAttributesList::TreeMode;
sorting[1] = CoreAttributesList::IdUp;
}
bool
ResourceList::isSupportedSortingCriteria(int sc)
{
switch (sc)
{
case TreeMode:
case MinEffortUp:
case MinEffortDown:
case MaxEffortUp:
case MaxEffortDown:
case RateUp:
case RateDown:
return true;
default:
return CoreAttributesList::isSupportedSortingCriteria(sc);
}
}
int
ResourceList::compareItemsLevel(CoreAttributes* c1, CoreAttributes* c2,
int level)
{
Resource* r1 = static_cast<Resource*>(c1);
Resource* r2 = static_cast<Resource*>(c2);
if (level < 0 || level >= maxSortingLevel)
return -1;
switch (sorting[level])
{
case TreeMode:
if (level == 0)
return compareTreeItemsT(this, r1, r2);
else
return r1->getSequenceNo() == r2->getSequenceNo() ? 0 :
r1->getSequenceNo() < r2->getSequenceNo() ? -1 : 1;
case MinEffortUp:
return r1->minEffort == r2->minEffort ? 0 :
r1->minEffort < r2->minEffort ? -1 : 1;
case MinEffortDown:
return r1->minEffort == r2->minEffort ? 0 :
r1->minEffort < r2->minEffort ? 1 : -1;
case MaxEffortUp:
return r1->limits->getDailyMax() == r2->limits->getDailyMax() ? 0 :
r1->limits->getDailyMax() < r2->limits->getDailyMax() ? -1 : 1;
case MaxEffortDown:
return r1->limits->getDailyMax() == r2->limits->getDailyMax() ? 0 :
r1->limits->getDailyMax() < r2->limits->getDailyMax() ? 1 : -1;
case RateUp:
return r1->rate == r2->rate ? 0 : r1->rate < r2->rate ? -1 : 1;
case RateDown:
return r1->rate == r2->rate ? 0 : r1->rate < r2->rate ? 1 : -1;
default:
return CoreAttributesList::compareItemsLevel(r1, r2, level);
}
}
Resource*
ResourceList::getResource(const QString& id) const
{
for (ResourceListIterator rli(*this); *rli != 0; ++rli)
if ((*rli)->getId() == id)
return *rli;
return 0;
}
Resource* ResourceListIterator::operator*()
{
return static_cast<Resource*>(CoreAttributesListIterator::operator*());
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/Scenario.cpp b/src/plugins/schedulers/tj/taskjuggler/Scenario.cpp
index 133c0b9c..b4f73e46 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Scenario.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Scenario.cpp
@@ -1,53 +1,54 @@
/*
* Scenario.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Scenario.h"
#include "Project.h"
namespace TJ
{
Scenario::Scenario(Project* p, const QString& i, const QString& n,
Scenario* pr) :
CoreAttributes(p, i, n, pr),
enabled(true),
projectionMode(false),
strictBookings(false),
optimize(false),
minSlackRate(0.05),
maxPaths(10000000)
{
p->addScenario(this);
if (pr)
{
// Inherit settings from parent scenario.
enabled = pr->enabled;
projectionMode = pr->projectionMode;
optimize = pr->optimize;
strictBookings = pr->strictBookings;
minSlackRate = pr->minSlackRate;
maxPaths = pr->maxPaths;
}
}
Scenario::~Scenario()
{
project->deleteScenario(this);
}
ScenarioListIterator
Scenario::getSubListIterator() const
{
return ScenarioListIterator(*sub);
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/ScenarioList.cpp b/src/plugins/schedulers/tj/taskjuggler/ScenarioList.cpp
index d451dc8f..664343d6 100644
--- a/src/plugins/schedulers/tj/taskjuggler/ScenarioList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/ScenarioList.cpp
@@ -1,82 +1,83 @@
/*
* ScenarioList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "ScenarioList.h"
#include "Scenario.h"
namespace TJ
{
ScenarioList::ScenarioList() :
CoreAttributesList()
{
sorting[0] = CoreAttributesList::TreeMode;
sorting[1] = CoreAttributesList::IdUp;
}
Scenario*
ScenarioList::operator[](int i)
{
return static_cast<Scenario*>(at(i));
}
bool
ScenarioList::isSupportedSortingCriteria(int sc)
{
switch (sc)
{
case TreeMode:
return true;
default:
return CoreAttributesList::isSupportedSortingCriteria(sc);
}
}
int
ScenarioList::compareItemsLevel(CoreAttributes* c1, CoreAttributes* c2,
int level)
{
Scenario* r1 = static_cast<Scenario*>(c1);
Scenario* r2 = static_cast<Scenario*>(c2);
if (level < 0 || level >= maxSortingLevel)
return -1;
switch (sorting[level])
{
case TreeMode:
if (level == 0)
return compareTreeItemsT(this, r1, r2);
else
return r1->getSequenceNo() == r2->getSequenceNo() ? 0 :
r1->getSequenceNo() < r2->getSequenceNo() ? -1 : 1;
default:
return CoreAttributesList::compareItemsLevel(r1, r2, level);
}
}
Scenario*
ScenarioList::getScenario(const QString& id) const
{
for (ScenarioListIterator rli(*this); *rli != 0; ++rli)
if ((*rli)->getId() == id)
return *rli;
return 0;
}
Scenario* ScenarioListIterator::operator*()
{
return static_cast<Scenario*>(CoreAttributesListIterator::operator*());
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/Shift.cpp b/src/plugins/schedulers/tj/taskjuggler/Shift.cpp
index 4d94a840..99e524f4 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Shift.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Shift.cpp
@@ -1,137 +1,138 @@
/*
* Shift.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
* Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Shift.h"
#include "Project.h"
#include "debug.h"
namespace TJ
{
Shift::Shift(Project* prj, const QString& i, const QString& n, Shift* p,
const QString& df, uint dl) :
CoreAttributes(prj, i, n, p, df, dl),
workingHours()
{
prj->addShift(this);
for (int i = 0; i < 7; i++)
{
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
}
}
Shift::~Shift()
{
for (int i = 0; i < 7; i++)
delete workingHours[i];
project->deleteShift(this);
}
void
Shift::inheritValues()
{
Shift* p = (Shift*) parent;
if (p)
{
// Inherit start values from parent resource.
for (int i = 0; i < 7; i++)
{
while (!workingHours[i]->isEmpty()) delete workingHours[i]->takeFirst();
delete workingHours[i];
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
for (QListIterator<Interval*> ivi(*(p->workingHours[i])); ivi.hasNext();)
workingHours[i]->append(new Interval(*ivi.next()));
}
}
else
{
// Inherit start values from project defaults.
for (int i = 0; i < 7; i++)
{
while (!workingHours[i]->isEmpty()) delete workingHours[i]->takeFirst();
delete workingHours[i];
workingHours[i] = new QList<Interval*>();
// workingHours[i]->setAutoDelete(true);
for (QListIterator<Interval*>
ivi(project->getWorkingHoursIterator(i)); ivi.hasNext();)
workingHours[i]->append(new Interval(*ivi.next()));
}
}
}
void
Shift::setWorkingHours(int day, const QList<Interval*>& l)
{
while (!workingHours[day]->isEmpty()) delete workingHours[day]->takeFirst();
delete workingHours[day];
// Create a deep copy of the interval list.
workingHours[day] = new QList<Interval*>;
// workingHours[day]->setAutoDelete(true);
for (QListIterator<Interval*> pli(l); pli.hasNext();)
workingHours[day]->append(new Interval(*(pli.next())));
}
ShiftListIterator
Shift::getSubListIterator() const
{
return ShiftListIterator(*sub);
}
bool
Shift::isOnShift(const Interval& iv) const
{
// Hacky way to enable handling working hours specified in timezone different from local time
if (!workingIntervals.isEmpty()) {
if (iv.getStart() >= workingIntervals.last().getEnd()) {
return false;
}
Q_FOREACH(const Interval &i, workingIntervals) {
if (iv.getEnd() <= i.getStart()) {
return false;
}
if (iv.overlaps(i)) {
return true;
}
}
return false;
}
int dow = dayOfWeek(iv.getStart(), false);
int ivStart = secondsOfDay(iv.getStart());
int ivEnd = secondsOfDay(iv.getEnd());
Interval dayIv(ivStart, ivEnd);
for (QListIterator<Interval*> ili(*(workingHours[dow])); ili.hasNext();)
if (ili.next()->contains(dayIv))
return true;
return false;
}
bool
Shift::isVacationDay(time_t day) const
{
return workingHours[dayOfWeek(day, false)]->isEmpty();
}
void
Shift::addWorkingInterval(const Interval &interval)
{
workingIntervals.append(interval);
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/ShiftList.cpp b/src/plugins/schedulers/tj/taskjuggler/ShiftList.cpp
index 2b05b455..d02723c4 100644
--- a/src/plugins/schedulers/tj/taskjuggler/ShiftList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/ShiftList.cpp
@@ -1,58 +1,59 @@
/*
* ShiftList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "ShiftList.h"
#include "Shift.h"
#include "Project.h"
namespace TJ
{
Shift*
ShiftList::getShift(const QString& id) const
{
for (ShiftListIterator sli(*this); *sli != 0; ++sli)
if ((*sli)->getId() == id)
return *sli;
return 0;
}
int
ShiftList::compareItemsLevel(CoreAttributes* c1, CoreAttributes* c2,
int level)
{
Shift* s1 = static_cast<Shift*>(c1);
Shift* s2 = static_cast<Shift*>(c2);
if (level < 0 || level >= maxSortingLevel)
return -1;
switch (sorting[level])
{
case TreeMode:
if (level == 0)
return compareTreeItemsT(this, s1, s2);
else
return s1->getSequenceNo() == s2->getSequenceNo() ? 0 :
s1->getSequenceNo() < s2->getSequenceNo() ? -1 : 1;
default:
return CoreAttributesList::compareItemsLevel(s1, s2, level);
}
}
Shift* ShiftListIterator::operator*()
{
return static_cast<Shift*>(CoreAttributesListIterator::operator*());
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/ShiftSelection.cpp b/src/plugins/schedulers/tj/taskjuggler/ShiftSelection.cpp
index d6a7b49d..129fc5ba 100644
--- a/src/plugins/schedulers/tj/taskjuggler/ShiftSelection.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/ShiftSelection.cpp
@@ -1,32 +1,33 @@
/*
* ShiftSelection.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "ShiftSelection.h"
namespace TJ
{
ShiftSelection::ShiftSelection(const ShiftSelection& sl) :
period(new Interval(*sl.period)),
shift(sl.shift)
{
}
bool
ShiftSelection::isVacationDay(time_t day) const
{
return period->contains(day) && shift->isVacationDay(day);
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/ShiftSelectionList.cpp b/src/plugins/schedulers/tj/taskjuggler/ShiftSelectionList.cpp
index ed87357c..7f96e32a 100644
--- a/src/plugins/schedulers/tj/taskjuggler/ShiftSelectionList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/ShiftSelectionList.cpp
@@ -1,61 +1,62 @@
/*
* ShiftSelectionList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "ShiftSelectionList.h"
// int
// ShiftSelectionList::compareItems(QCollection::Item i1, QCollection::Item i2)
// {
// ShiftSelection* s1 = static_cast<ShiftSelection*>(i1);
// ShiftSelection* s2 = static_cast<ShiftSelection*>(i2);
//
// return s1->period->compare(*s2->period);
// }
namespace TJ
{
bool
ShiftSelectionList::isOnShift(const Interval& iv) const
{
/* Check whether any of the defined shift intervals contains the interval
* 'iv'. If not return true. If it does, check whether the interval 'iv'
* lies within the specified working hours. */
for (ShiftSelectionList::Iterator ssli(*this); ssli.hasNext();) {
ShiftSelection *s = ssli.next();
if (s->getPeriod().contains(iv))
return s->getShift()->isOnShift(iv);
}
return true;
}
bool
ShiftSelectionList::isVacationDay(time_t day) const
{
for (ShiftSelectionList::Iterator ssli(*this);
ssli.hasNext() && day <= ssli.peekNext()->getPeriod().getEnd();)
if (ssli.next()->isVacationDay(day))
return true;
return false;
}
bool
ShiftSelectionList::insert(ShiftSelection* s)
{
for (ShiftSelectionList::Iterator ssli(*this); ssli.hasNext();)
if (ssli.next()->getPeriod().overlaps(s->getPeriod()))
return false;
append(s);
return true;
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/Task.cpp b/src/plugins/schedulers/tj/taskjuggler/Task.cpp
index c317e441..0ef1a775 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Task.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Task.cpp
@@ -1,3950 +1,3951 @@
/*
* Task.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
* Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Task.h"
#include <KLocalizedString>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include "TjMessageHandler.h"
#include "tjlib-internal.h"
#include "Resource.h"
// #include "Account.h"
#include "Project.h"
#include "ResourceTreeIterator.h"
#include "Allocation.h"
#include "Booking.h"
// #include "ReportXML.h"
#include "Scenario.h"
#include "CustomAttributeDefinition.h"
#include "UsageLimits.h"
#include "Shift.h"
#include "ShiftSelection.h"
#include <QExplicitlySharedDataPointer>
namespace TJ
{
Task::Task(Project* proj, const QString& id_, const QString& n, Task* p,
const QString& df, int dl) :
CoreAttributes(proj, id_, n, p, df, dl),
note(),
// journal(),
ref(),
refLabel(),
depends(),
precedes(),
predecessors(),
successors(),
previous(),
followers(),
projectId(),
milestone(false),
priority(0),
scheduling(ASAP),
responsible(0),
shifts(),
allocations(),
// account(0),
scenarios(new TaskScenario[proj->getMaxScenarios()]),
start(0),
end(0),
length(0.0),
effort(0.0),
duration(0.0),
doneEffort(0.0),
doneLength(0.0),
doneDuration(0.0),
workStarted(false),
tentativeStart(0),
tentativeEnd(0),
lastSlot(0),
schedulingDone(false),
runAway(false),
bookedResources()
{
// qDebug()<<"Task:"<<this;
Q_ASSERT(proj->getMaxScenarios() > 0);
// allocations.setAutoDelete(true);
// shifts.setAutoDelete(true);
// depends.setAutoDelete(true);
// precedes.setAutoDelete(true);
proj->addTask(this);
for (int i = 0; i < proj->getMaxScenarios(); i++)
{
scenarios[i].task = this;
scenarios[i].index = i;
}
scenarios[0].startBuffer = 0.0;
scenarios[0].endBuffer = 0.0;
scenarios[0].startCredit = 0.0;
scenarios[0].endCredit = 0.0;
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
scenarios[sc].minStart = scenarios[sc].minEnd = 0;
scenarios[sc].maxStart = scenarios[sc].maxEnd = 0;
}
Q_ASSERT(duration == 0.0);
Q_ASSERT(length == 0.0);
Q_ASSERT(effort == 0.0);
}
Task::~Task()
{
// qDebug()<<"~Task:"<<this;
project->deleteTask(this);
delete [] scenarios;
qDeleteAll( depends );
qDeleteAll( precedes );
qDeleteAll( allocations );
}
void
Task::inheritValues()
{
Task* p = static_cast<Task*>(parent);
if (p)
{
// Inherit flags from parent task.
for (QStringList::ConstIterator it = p->flags.constBegin();
it != p->flags.constEnd(); ++it)
addFlag(*it);
// Set attributes that are inherited from parent task.
projectId = p->projectId;
priority = p->priority;
responsible = p->responsible;
// account = p->account;
scheduling = p->scheduling;
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
scenarios[sc].minStart = p->scenarios[sc].minStart;
scenarios[sc].maxStart = p->scenarios[sc].maxEnd;
scenarios[sc].minEnd = p->scenarios[sc].minStart;
scenarios[sc].maxEnd = p->scenarios[sc].maxEnd;
}
// Inherit depends from parent. Relative IDs need to get another '!'.
for (QListIterator<TaskDependency*> tdi(p->depends); tdi.hasNext();)
{
TaskDependency *tdc = static_cast<TaskDependency*>(tdi.next());
QString id = tdc->getTaskRefId();
if (id[0] == '!')
id = '!' + id;
TaskDependency* td = new TaskDependency(id,
project->getMaxScenarios());
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
td->setGapDuration(sc, tdc->getGapDurationNR(sc));
td->setGapLength(sc, tdc->getGapLengthNR(sc));
}
depends.append(td);
}
// Inherit precedes from parent. Relative IDs need to get another '!'.
for (QListIterator<TaskDependency*> tdi(p->precedes); tdi.hasNext();)
{
TaskDependency *tdc = static_cast<TaskDependency*>(tdi.next());
QString id = tdc->getTaskRefId();
if (id[0] == '!')
id = '!' + id;
TaskDependency* td = new TaskDependency(id,
project->getMaxScenarios());
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
td->setGapDuration(sc, tdc->getGapDurationNR(sc));
td->setGapLength(sc, tdc->getGapLengthNR(sc));
}
precedes.append(td);
}
// Inherit allocations from parent.
for (QListIterator<Allocation*> ali(p->allocations); ali.hasNext();) {
allocations.append(new Allocation(*(ali.next())));
}
// Inherit inheritable custom attributes
inheritCustomAttributes(project->getTaskAttributeDict());
}
else
{
// Set attributes that are inherited from global attributes.
projectId = project->getCurrentId();
priority = project->getPriority();
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
scenarios[sc].minStart = scenarios[sc].minEnd = 0;
scenarios[sc].maxStart = scenarios[sc].maxEnd = 0;
}
}
}
// void
// Task::addJournalEntry(JournalEntry* entry)
// {
// journal.inSort(entry);
// }
//
// Journal::Iterator
// Task::getJournalIterator() const
// {
// return Journal::Iterator(journal);
// }
TaskDependency*
Task::addDepends(const QString& rid)
{
foreach ( TaskDependency *d, depends ) {
if ( rid == d->getTaskRefId() ) {
return d;
}
}
TaskDependency* td = new TaskDependency(rid, project->getMaxScenarios());
depends.append(td);
return td;
}
TaskDependency*
Task::addPrecedes(const QString& rid)
{
foreach ( TaskDependency *d, precedes ) {
if ( rid == d->getTaskRefId() ) {
return d;
}
}
TaskDependency* td = new TaskDependency(rid, project->getMaxScenarios());
precedes.append(td);
return td;
}
bool
Task::addShift(const Interval& i, Shift* s)
{
return shifts.insert(new ShiftSelection(i, s));
}
void
Task::errorMessage(const QString& msg) const
{
TJMH.errorMessage(msg, this);
// TJMH.errorMessage(msg, definitionFile, definitionLine);
}
void
Task::warningMessage(const QString& msg) const
{
TJMH.warningMessage(msg, this);
// TJMH.warningMessage(msg, definitionFile, definitionLine);
}
bool
Task::isWorkingTime(const Interval& slot) const
{
if (shifts.isEmpty()) {
return project->isWorkingTime(slot);
}
for (ShiftSelectionList::Iterator ssli(shifts); ssli.hasNext();) {
ShiftSelection *s = ssli.next();
if (s->getPeriod().contains(slot))
return s->getShift()->isOnShift(slot);
}
return false;
}
bool
Task::schedule(int sc, time_t& date, time_t slotDuration)
{
// Has the task been scheduled already or is it a container?
if (schedulingDone || !sub->isEmpty())
return false;
if (DEBUGTS(15))
qDebug()<<QString("Trying to schedule %1 at %2").arg(name).arg(time2tjp(date));
if (scheduling == Task::ASAP)
{
if (start == 0 ||
(effort == 0.0 && length == 0.0 && duration == 0.0 && end == 0)) {
if ( start == 0 ) {
warningMessage(xi18nc("@info/plain", "Cannot schedule: Valid start time is not set"));
} else {
warningMessage(xi18nc("@info/plain", "Cannot schedule: Estimate is 0"));
}
return false;
}
if (lastSlot == 0)
{
lastSlot = start - 1;
tentativeEnd = date + slotDuration - 1;
if (DEBUGTS(5))
qDebug()<<"Scheduling of ASAP task"<<name<<"starts at"<<time2tjp(start)<<"("<<time2tjp(date)<<")";
}
/* Do not schedule anything if the time slot is not directly
* following the time slot that was previously scheduled.
* The project should get back to us later */
if (!((date - slotDuration <= lastSlot) && (lastSlot < date))) {
if (DEBUGTS(20)) {
qDebug()<<"Scheduling of ASAP task"<<name<<"not continuous slots:"<<time2tjp(date)<<"last:"<<time2tjp(lastSlot);
}
//warningMessage(xi18nc("info/plain", "ASAP: Not continuous slots: %1 last: %2", time2ISO(date), time2ISO(lastSlot)));
return false;
}
lastSlot = date + slotDuration - 1;
}
else
{
if (end == 0 ||
(effort == 0.0 && length == 0.0 && duration == 0.0 && start == 0)) {
if ( end == 0 ) {
warningMessage(xi18nc("@info/plain", "Cannot schedule: Valid end time is not set"));
} else {
warningMessage(xi18nc("@info/plain", "Cannot schedule: Estimate is 0"));
}
return false;
}
if (lastSlot == 0)
{
lastSlot = end + 1;
tentativeStart = date;
if (DEBUGTS(5))
qDebug()<<"Scheduling of ALAP task"<<name<<"starts at"<<time2tjp(lastSlot)<<"("<<time2tjp(date)<<")";
}
/* Do not schedule anything if the current time slot is not
* directly preceding the previously scheduled time slot. */
if (!((date + slotDuration <= lastSlot) &&
(lastSlot < date + 2 * slotDuration)))
return false;
lastSlot = date;
}
if (DEBUGTS(10))
qDebug()<<"Scheduling"<<name<<"at"<<time2tjp(date);
if ((duration > 0.0) || (length > 0.0))
{
// TJMH.debugMessage(QString("Scheduling duration/length at: %1").arg(time2tjp(date)), this);
/* Length specifies the number of working days (as daily load)
* and duration specifies the number of calendar days. */
if (!allocations.isEmpty())
bookResources(sc, date, slotDuration);
doneDuration += ((double) slotDuration) / ONEDAY;
if (isWorkingTime(Interval(date, date + slotDuration - 1))) {
doneLength += project->convertToDailyLoad(slotDuration);
}
if (DEBUGTS(10))
qDebug("Length: %f/%f Duration: %f/%f",
doneLength, length,
doneDuration, duration);
// Check whether we are done with this task.
/* The accumulated done* values contain rounding errors. This prevents
* exact float comparisons. To avoid rounding problems we compare the
* rounded values of the done* values multiplied by 2048. This should
* result in worst case errors of smaller than a minute. The value
* 2048 was chosen in the hope that a compiler is clever enough to
* avoid a costly multiplication if possible. */
if ((length > 0.0 &&
qRound(doneLength * 2048) >= qRound(length * 2048)) ||
(duration > 0.0 &&
qRound(doneDuration * 2048) >= qRound(duration * 2048)))
{
if (scheduling == ASAP)
propagateEnd(sc, date + slotDuration - 1);
else
propagateStart(sc, date);
schedulingDone = true;
if (DEBUGTS(4)) {
qDebug()<<"Scheduling of task"<<name<<"completed";
if (length > 0.0) qDebug()<<"Length estimate:"<<length<<"done:"<<doneLength;
if (duration > 0.0) qDebug()<<"Duration estimate:"<<duration<<"done:"<<doneDuration;
}
if (length > 0.0) {
TJMH.debugMessage(QString("Task scheduled: %1 - %2, estimated length: %3").arg(time2ISO(start)).arg(time2ISO(end)).arg(length), this);
} else if (duration > 0.0) {
TJMH.debugMessage(QString("Task scheduled: %1 - %2, estimated duration: %3").arg(time2ISO(start)).arg(time2ISO(end)).arg(duration), this);
}
return true;
}
}
else if (effort > 0.0)
{
// TJMH.debugMessage(QString("Scheduling effort %2 at: %1").arg(time2tjp(date)).arg(effort), this);
/* The effort of the task has been specified. We have to look
* how much the resources can contribute over the following
* workings days until we have reached the specified
* effort. */
bookResources(sc, date, slotDuration);
// Check whether we are done with this task.
if (qRound(doneEffort * 2048) >= qRound(effort * 2048))
{
if (scheduling == ASAP)
propagateEnd(sc, tentativeEnd);
else
propagateStart(sc, tentativeStart);
schedulingDone = true;
if (DEBUGTS(4)) {
qDebug()<<"Scheduling of task"<<this<<"completed:"<<time2ISO(start)<<"-"<<time2ISO(end);
qDebug()<<"Effort estimate:"<<effort<<"done:"<<doneEffort;
}
TJMH.debugMessage(QString("Task scheduled: %3 - %4, estimated effort=%1d, booked=%2d").arg(effort).arg(doneEffort).arg(time2ISO(start)).arg(time2ISO(end)), this);
return true;
}
}
else if (milestone)
{
// Task is a milestone.
if (scheduling == ASAP)
propagateEnd(sc, start - 1);
else
propagateStart(sc, end + 1);
if (DEBUGTS(4)) {
qDebug()<<"Scheduling of task"<<this<<"completed:"<<time2ISO(start)<<"-"<<time2ISO(end);
}
TJMH.debugMessage(QString("Milestone scheduled: %1").arg(time2ISO(start)), this );
return true;
}
else if (start != 0 && end != 0)
{
// Task with start and end date but no duration criteria.
if (!allocations.isEmpty() && !project->isVacation(date))
bookResources(sc, date, slotDuration);
if ((scheduling == ASAP && (date + slotDuration) >= end) ||
(scheduling == ALAP && date <= start))
{
schedulingDone = true;
if (DEBUGTS(4))
qDebug()<<"Scheduling of task"<<name<<"completed";
TJMH.debugMessage(QString("Task scheduled: %1 - %2").arg(time2ISO(start)).arg(time2ISO(end)), this);
return true;
}
}
return false;
}
bool
Task::scheduleContainer(int sc)
{
if (schedulingDone || !isContainer())
return true;
time_t nStart = 0;
time_t nEnd = 0;
for (TaskListIterator tli(*sub); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
/* Make sure that all sub tasks have been scheduled. If not we
* can't yet schedule this task. */
if (t->start == 0 || t->end == 0)
return true;
if (nStart == 0 || t->start < nStart)
nStart = t->start;
if (t->end > nEnd)
nEnd = t->end;
}
if (start == 0 || start > nStart)
propagateStart(sc, nStart);
if (end == 0 || end < nEnd)
propagateEnd(sc, nEnd);
if (DEBUGTS(4))
qDebug()<<QString("Scheduling of task %1 completed").arg(name);
schedulingDone = true;
return false;
}
bool
Task::hasAlapPredecessor() const
{
foreach ( const CoreAttributes *t, predecessors ) {
if ( static_cast<const Task*>( t )->getScheduling() == TJ::Task::ALAP || static_cast<const Task*>( t )->hasAlapPredecessor() ) {
return true;
}
}
return false;
}
void
Task::propagateStart(int sc, time_t date)
{
start = date;
if (DEBUGTS(11))
qDebug()<<"PS1: Setting start of"<<this<<"to"<<time2tjp(start);
// TJMH.debugMessage(QString("Set start: %2 ").arg(time2ISO(start)), this);
/* If one end of a milestone is fixed, then the other end can be set as
* well. */
if (milestone && date > 0)
{
if (!schedulingDone) {
schedulingDone = true;
propagateEnd(sc, start - 1);
if (DEBUGTS(4)) {
qDebug()<<"Scheduling:"<<this<<"completed:"<<time2ISO(start)<<"-"<<time2ISO(end);
}
}
// schedule successor ASAP milestones
for (TaskListIterator tli(followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->milestone && !t->schedulingDone && t->scheduling == ASAP &&
t->start == 0 && t->latestEnd(sc) != 0)
{
/* Recursively propagate the start date */
t->propagateEnd(sc, t->latestEnd(sc));
}
}
}
/* Set start date to all previous that have no start date yet, but are
* ALAP task or have no duration. */
for (TaskListIterator tli(previous); *tli != 0; ++tli) {
if ((*tli)->end == 0 && (*tli)->latestEnd(sc) != 0 &&
!(*tli)->schedulingDone &&
((*tli)->scheduling == ALAP ||
((*tli)->effort == 0.0 && (*tli)->length == 0.0 &&
(*tli)->duration == 0.0 && !(*tli)->milestone)))
{
/* Recursively propagate the end date */
(*tli)->propagateEnd(sc, (*tli)->latestEnd(sc));
}
}
/* Propagate start time to sub tasks which have only an implicit
* dependency on the parent task. Do not touch container tasks. */
for (TaskListIterator tli(*sub); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (!t->hasStartDependency() && !t->schedulingDone)
{
/* Recursively propagate the start date */
t->propagateStart(sc, start);
}
}
if (parent)
{
if (DEBUGTS(11))
qDebug()<<"Scheduling parent of"<<name;
getParent()->scheduleContainer(sc);
}
}
void
Task::propagateEnd(int sc, time_t date)
{
end = date;
if (DEBUGTS(11))
qDebug()<<"PE1: Setting end of"<<name<<"to"<<time2tjp(end);
// TJMH.debugMessage(QString("Set end: %2 ").arg(time2ISO(end)), this);
/* If one end of a milestone is fixed, then the other end can be set as
* well. */
if (milestone && date > 0)
{
if (!schedulingDone) {
schedulingDone = true;
propagateStart(sc, end + 1);
if (DEBUGTS(4)) {
qDebug()<<"Scheduling:"<<this<<"completed:"<<time2ISO(start)<<"-"<<time2ISO(end);
}
}
// schedule predecessor ALAP milestones
for (TaskListIterator tli(previous); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->milestone && !t->schedulingDone && t->scheduling == ALAP &&
t->start == 0 && t->earliestStart(sc) != 0)
{
/* Recursively propagate the start date */
t->propagateEnd(sc, t->latestEnd(sc));
}
}
}
/* Set start date to all followers that have no start date yet, but are
* ASAP task or have no duration. */
for (TaskListIterator tli(followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->start == 0 && t->earliestStart(sc) != 0 &&
!t->schedulingDone &&
(t->scheduling == ASAP ||
(t->effort == 0.0 && t->length == 0.0 &&
t->duration == 0.0 && !t->milestone)))
{
/* Recursively propagate the start date */
t->propagateStart(sc, t->earliestStart(sc));
}
}
/* Propagate end time to sub tasks which have only an implicit
* dependency on the parent task. Do not touch container tasks. */
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (!t->hasEndDependency() && !t->schedulingDone)
{
/* Recursively propagate the end date */
t->propagateEnd(sc, end);
}
}
if (parent)
{
if (DEBUGTS(11))
qDebug()<<"Scheduling parent of"<<name;
getParent()->scheduleContainer(sc);
}
}
void
Task::propagateInitialValues(int sc)
{
if (start != 0)
propagateStart(sc, start);
if (end != 0)
propagateEnd(sc, end);
// Check if the some data of sub tasks can already be propagated.
if (!sub->isEmpty())
scheduleContainer(sc);
}
void
Task::setRunaway()
{
schedulingDone = true;
runAway = true;
}
bool
Task::isRunaway() const
{
/* If a container task has runaway sub tasts, it is very likely that they
* are the culprits. So we don't report such a container task as runaway.
*/
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->isRunaway())
return false;
}
return runAway;
}
int
Task::isAvailable( Allocation *allocation, Resource *resource, time_t slot ) const
{
int max = resource->isAvailable(slot);
if (allocation->hasRequiredResources(resource)) {
foreach (Resource *r, allocation->getRequiredResources(resource)) {
int a = r->isAvailable(slot);
if ( a > max ) {
// TJMH.debugMessage(QString("Required resource '%1' is not available at %2").arg(r->getName()).arg(time2ISO(slot)), this);
max = a;
}
}
}
return max;
}
void
Task::bookResources(int sc, time_t date, time_t slotDuration)
{
/* If the time slot overlaps with a specified shift interval, the
* time slot must also be within the specified working hours of that
* shift interval. */
if (!shifts.isOnShift(Interval(date, date + slotDuration - 1)))
{
if (DEBUGRS(15))
qDebug()<<"Task"<<name<<"is not active at"<<time2tjp(date);
// TJMH.debugMessage(QString("Task is not active at %1").arg(time2tjp(date)), this);
return;
}
/* In projection mode we do not allow bookings prior to the current date
* for any task (in strict mode) or tasks which have user specified
* bookings (sloppy mode). */
if (project->getScenario(sc)->getProjectionMode() &&
date < project->getNow() &&
(project->getScenario(sc)->getStrictBookings() ||
!scenarios[sc].specifiedBookedResources.isEmpty()))
{
if (DEBUGRS(15))
qDebug()<<"No allocations prior to current date for task"<<id;
TJMH.debugMessage(QString("Allocations prior to 'now' %1 is not allowed").arg(time2tjp(project->getNow())), this);
return;
}
/* If any of the resources is marked as being mandatory, we have to check
* if this resource is available. In case it's not available we do not
* allocate any of the other resources for the time slot. */
bool allMandatoriesAvailables = true;
QList<Resource*> mandatoryResources;
for (QListIterator<Allocation*> ali(allocations); ali.hasNext();) {
Allocation *a = static_cast<Allocation*>(ali.next());
if (a->isMandatory())
{
if (!a->isOnShift(Interval(date, date + slotDuration - 1)))
{
// TJMH.debugMessage(QString("Mandatory allocation not on shift at: %1").arg(time2tjp(date)), this);
allMandatoriesAvailables = false;
break;
}
if (a->isPersistent() && a->getLockedResource())
{
int availability = isAvailable(a, a->getLockedResource(), date);
if (availability > 0)
{
allMandatoriesAvailables = false;
if (availability >= 4 && !a->getConflictStart())
a->setConflictStart(date);
break;
}
}
else
{
/* For a mandatory allocation with alternatives at least one
* of the resources or resource groups must be available. */
bool found = false;
int maxAvailability = 0;
QList<Resource*> candidates = a->getCandidates();
foreach (Resource *r, candidates)
{
/* If a resource group is marked mandatory, all members
* of the group must be available. */
int availability = 0;
bool allAvailable = true;
for (ResourceTreeIterator rti(r); *rti != 0; ++rti) {
if ((availability = isAvailable(a, (*rti), date)) > 0 ||
mandatoryResources.contains(*rti))
{
// TJMH.debugMessage(QString("Mandatory resource '%2' not available at: %1").arg(time2tjp(date)).arg((*rti)->getName()), this);
allAvailable = false;
if (availability >= maxAvailability)
maxAvailability = availability;
}
else {
mandatoryResources.append(*rti);
// TJMH.debugMessage(QString("Mandatory resource '%2' available at: %1").arg(time2tjp(date)).arg((*rti)->getName()));
}
}
if (allAvailable)
found = true;
}
if (!found)
{
if (maxAvailability >= 4 && !a->getConflictStart())
a->setConflictStart(date);
allMandatoriesAvailables = false;
break;
}
}
}
}
if ( ! allMandatoriesAvailables ) {
// TJMH.debugMessage(QString("All mandatory resourcea are not available"), this);
}
for (QListIterator<Allocation*> ali(allocations);
ali.hasNext() && allMandatoriesAvailables &&
(effort == 0.0 || doneEffort < effort);)
{
/* If a shift has been defined for a resource for this task, there
* must be a shift interval defined for this day and the time must
* be within the working hours of that shift. */
Allocation *a = static_cast<Allocation*>(ali.next());
if (!a->isOnShift(Interval(date, date + slotDuration - 1)))
{
if (DEBUGRS(15))
qDebug()<<"Allocation not on shift at"<<time2tjp(date);
// TJMH.debugMessage(QString("Allocation not on shift at: %1").arg(time2tjp(date)), this);
continue;
}
/* Now check the limits set for this allocation. */
const UsageLimits* limits = a->getLimits();
/* This variable holds the number of slots that are still available to
* hit the nearest limit. -1 means unlimited slots. */
int slotsToLimit = -1;
if (limits)
{
QList<Resource*> resources = a->getCandidates();
QString resStr;
foreach (Resource *r, resources) {
resStr += r->getId() + QLatin1Char(' ');
}
if (limits->getDailyUnits() > 0) {
uint bookedSlots = 0;
int workSlots = 0;
foreach (Resource *r, resources) {
workSlots += r->getWorkSlots(date); // returns 0 if no bookings yet
bookedSlots += r->getCurrentDaySlots(date, this); // booked to this task
}
if ( workSlots > 0 ) {
workSlots = (workSlots * limits->getDailyUnits()) / 100;
if (workSlots == 0) {
workSlots = 1;
}
}
int freeSlots = bookedSlots > 0 ? workSlots - bookedSlots : 1; // always allow one booking
if (freeSlots <= 0) {
if (DEBUGRS(6)) {
qDebug()<<" Resource(s)"<<resStr<<"overloaded";
}
continue;
} else if (slotsToLimit < 0 || slotsToLimit > freeSlots) {
slotsToLimit = freeSlots;
}
}
else if (limits->getDailyMax() > 0)
{
uint slotCount = 0;
foreach (Resource *r, resources)
slotCount += r->getCurrentDaySlots(date, this);
int freeSlots = limits->getDailyMax() - slotCount;
if (freeSlots <= 0)
{
if (DEBUGRS(6))
qDebug()<<" Resource(s)"<<resStr<<"overloaded";
continue;
}
else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
slotsToLimit = freeSlots;
}
if (limits->getWeeklyMax() > 0)
{
uint slotCount = 0;
foreach (Resource *r, resources)
slotCount += r->getCurrentWeekSlots(date, this);
int freeSlots = limits->getWeeklyMax() - slotCount;
if (freeSlots <= 0)
{
if (DEBUGRS(6))
qDebug()<<" Resource(s)"<<resStr<<"overloaded";
continue;
}
else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
slotsToLimit = freeSlots;
}
if (limits->getMonthlyMax() > 0)
{
uint slotCount = 0;
foreach (Resource *r, resources)
slotCount += r->getCurrentMonthSlots(date, this);
int freeSlots = limits->getMonthlyMax() - slotCount;
if (freeSlots <= 0)
{
if (DEBUGRS(6))
qDebug()<<" Resource(s)"<<resStr<<"overloaded";
continue;
}
else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
slotsToLimit = freeSlots;
}
}
/* If the allocation has be marked persistent and a resource
* has already been picked, try to book this resource again. If the
* resource is not available there will be no booking for this
* time slot. */
int maxAvailability = 0;
if (a->isPersistent() && a->getLockedResource())
{
if (!bookResource(a, a->getLockedResource(), date, slotDuration,
slotsToLimit, maxAvailability))
{
if (maxAvailability >= 4 && !a->getConflictStart())
a->setConflictStart(date);
}
else if (a->getConflictStart())
{
if (DEBUGRS(2))
qDebug()<<"Resource"<<a->getLockedResource()->getId()<<"is not available for task '"<<id<<"'"
<<"from"<<time2ISO(a->getConflictStart())<<"to"<<time2ISO(date);
// TJMH.debugMessage(QString("Resource %1 is not available from %2 to %3").arg(a->getLockedResource()->getName()).arg(time2ISO(a->getConflictStart())).arg(time2ISO(date)), this);
a->setConflictStart(0);
}
}
else
{
QList<Resource*> cl = createCandidateList(sc, date, a);
bool found = false;
foreach (Resource *r, cl)
if (bookResource(a, r, date, slotDuration, slotsToLimit,
maxAvailability))
{
a->setLockedResource(r);
found = true;
break;
}
if (!found && maxAvailability >= 4 && !a->getConflictStart())
a->setConflictStart(date);
else if (found && a->getConflictStart())
{
if (DEBUGRS(2))
{
QString candidates;
bool first = true;
foreach (Resource *r, cl)
{
if (first)
first = false;
else
candidates += ", ";
candidates += r->getId();
}
qDebug()<<"No resource of the allocation ("<<candidates<<") is available for task '"<<name<<"' from"<<time2ISO(a->getConflictStart())<<"to"<<time2ISO(date);
}
// TJMH.debugMessage(QString("No resource is available for task from %2 to %3").arg(time2ISO(a->getConflictStart())).arg(time2ISO(date)), this);
a->setConflictStart(0);
}
}
}
}
bool
Task::bookResource(Allocation *allocation, Resource* r, time_t date, time_t slotDuration,
int& slotsToLimit, int& maxAvailability)
{
bool booked = false;
double intervalLoad = project->convertToDailyLoad(slotDuration);
for (ResourceTreeIterator rti(r); *rti != 0; ++rti)
{
int availability = isAvailable(allocation, (*rti), date);
if (availability == 0)
{
if (!(*rti)->book(new Booking(Interval(date, date + slotDuration - 1), this))) {
warningMessage(xi18nc("@info/plain 1=resource name 2=datetime", "Failed to book resource: '%1' at %2", (*rti)->getName(), formatTime(date)));
if (DEBUGTS(2)) {
qWarning()<<" Failed to book resource"<<(*rti)->getName()<<"at"<<time2ISO(date);
}
continue;
}
addBookedResource(*rti);
TJMH.debugMessage(QString("Booked resource: '%1' at %2").arg((*rti)->getName()).arg(time2ISO(date)), this);
if (DEBUGTS(20)) {
qDebug()<<" Booked resource"<<(*rti)->getName()<<"at"<<time2ISO(date);
}
if (allocation->hasRequiredResources(*rti)) {
foreach(Resource *r, allocation->getRequiredResources(*rti)) {
if (r->book(new Booking(Interval(date, date + slotDuration - 1), this))) {
addBookedResource(r);
// TJMH.debugMessage(QString("Booked required resource: '%1' at %2").arg(r->getName()).arg(time2ISO(date)), this);
if (DEBUGTS(20)) {
qDebug()<<" Booked required resource"<<r->getName()<<"at"<<time2ISO(date);
}
} else {
warningMessage(xi18nc("@info/plain 1=resource name 2=datetime", "Failed to book required resource: '%1' at %2", r->getName(), formatTime(date)));
if (DEBUGTS(2)) {
qWarning()<<" Failed to book required resource"<<r->getName()<<"at"<<time2ISO(date);
}
// NOTE: just go on as booking the main resource worked ok
}
}
}
/* Move the start date to make sure that there is
* some work going on at the start date. */
if (!workStarted)
{
if (scheduling == ASAP)
start = date;
else if (scheduling == ALAP)
end = date + slotDuration - 1;
else
qFatal("Unknown scheduling mode");
workStarted = true;
}
tentativeStart = date;
tentativeEnd = date + slotDuration - 1;
doneEffort += intervalLoad * (*rti)->getEfficiency();
booked = true;
if (slotsToLimit > 0 && --slotsToLimit <= 0)
return true;
}
else if (availability > maxAvailability)
maxAvailability = availability;
}
return booked;
}
QList<Resource*>
Task::createCandidateList(int sc, time_t date, Allocation* a)
{
/* This function generates a list of resources that could be allocated to
* the task. The order of the list is determined by the specified
* selection function of the alternatives list. From this list, the
* first available resource is picked later on. */
QList<Resource*> candidates = a->getCandidates();
QList<Resource*> cl;
/* We try to minimize resource changes for consecutives time slots. So
* the resource used for the previous time slot is put to the 1st position
* of the list. */
if (a->getLockedResource())
{
cl.append(a->getLockedResource());
candidates.removeAll(a->getLockedResource());
/* When an allocation is booked the resource is saved as locked
* resource. */
a->setLockedResource(0);
}
switch (a->getSelectionMode())
{
case Allocation::order:
if (DEBUGTS(25))
qDebug("order");
while (!candidates.isEmpty())
{
cl.append(candidates.takeFirst());
}
break;
case Allocation::minAllocationProbability:
{
if (DEBUGTS(25))
qDebug("minAllocationProbability");
/* This is another heuristic to optimize scheduling results. The
* idea is to pick the resource that is most likely to be used
* least during this project (because of the specified
* allocations) and try to use it first. Unfortunately this
* algorithm can make things worse in certain plan setups. */
while (!candidates.isEmpty())
{
/* Find canidate with smallest allocationProbability and
* append it to the candidate list. */
double minProbability = 0;
Resource* minProbResource = 0;
foreach (Resource *r, candidates)
{
double probability = r->getAllocationProbability(sc);
if (minProbability == 0 || probability < minProbability)
{
minProbability = probability;
minProbResource = r;
}
}
cl.append(minProbResource);
candidates.removeAt(candidates.indexOf(minProbResource));
}
break;
}
case Allocation::minLoaded:
{
if (DEBUGTS(25))
qDebug("minLoad");
while (!candidates.isEmpty())
{
double minLoad = 0;
Resource* minLoaded = 0;
foreach (Resource *r, candidates)
{
/* We calculate the load as a relative value to the daily
* max load. This way part time people will reach their
* max as slowly as the full timers. */
double load =
r->getCurrentLoad(Interval(project->getStart(),
date), 0) /
((r->getLimits() &&
r->getLimits()->getDailyMax() > 0) ?
project->convertToDailyLoad
(r->getLimits()->getDailyMax() *
project->getScheduleGranularity()) : 1.0);
if (minLoaded == 0 || load < minLoad)
{
minLoad = load;
minLoaded = r;
}
}
cl.append(minLoaded);
candidates.removeAt(candidates.indexOf(minLoaded));
}
break;
}
case Allocation::maxLoaded:
{
if (DEBUGTS(25))
qDebug("maxLoad");
while (!candidates.isEmpty())
{
double maxLoad = 0;
Resource* maxLoaded = 0;
foreach (Resource *r, candidates)
{
/* We calculate the load as a relative value to the daily
* max load. This way part time people will reach their
* max as fast as the full timers. */
double load =
r->getCurrentLoad(Interval(project->getStart(),
date), 0) /
((r->getLimits() &&
r->getLimits()->getDailyMax() > 0) ?
project->convertToDailyLoad
(r->getLimits()->getDailyMax() *
project->getScheduleGranularity()) : 1.0);
if (maxLoaded == 0 || load > maxLoad)
{
maxLoad = load;
maxLoaded = r;
}
}
cl.append(maxLoaded);
candidates.removeAt(candidates.indexOf(maxLoaded));
}
break;
}
case Allocation::random:
{
if (DEBUGTS(25))
qDebug("random");
while (!candidates.isEmpty())
{
cl.append(candidates.at(rand() % candidates.count()));
candidates.takeFirst(); //### do not understand this
}
break;
}
default:
qFatal("Illegal selection mode %d", a->getSelectionMode());
}
return cl;
}
QString
Task::getSchedulingText() const
{
if (isLeaf())
{
return scheduling == ASAP ? "ASAP |-->|" : "ALAP |<--|";
}
else
{
QString text;
for (TaskListIterator tli(*sub); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (text.isEmpty())
text = t->getSchedulingText();
else if (text != t->getSchedulingText())
{
text = "Mixed";
break;
}
}
return text;
}
return QString();
}
QString
Task::getStatusText(int sc) const
{
QString text;
switch (getStatus(sc))
{
case NotStarted:
text = QString("Not yet started");
break;
case InProgressLate:
text = QString("Behind schedule");
break;
case InProgress:
text = QString("Work in progress");
break;
case OnTime:
text = QString("On schedule");
break;
case InProgressEarly:
text = QString("Ahead of schedule");
break;
case Finished:
text = QString("Finished");
break;
case Late:
text = QString("Late");
break;
default:
text = QString("Unknown status");
break;
}
return text;
}
bool
Task::isCompleted(int sc, time_t date) const
{
if (scenarios[sc].reportedCompletion >= 0.0)
{
if (scenarios[sc].reportedCompletion >= 100.0)
return true;
// some completion degree has been specified.
if (scenarios[sc].effort > 0.0)
{
return qRound((scenarios[sc].effort *
(scenarios[sc].reportedCompletion / 100.0)) * 1000)
>= qRound(getLoad(sc, Interval(scenarios[sc].start, date), 0)
* 1000);
}
else
{
return (date <=
scenarios[sc].start +
static_cast<int>((scenarios[sc].reportedCompletion /
100.0) * (scenarios[sc].end -
scenarios[sc].start)));
}
}
if (isContainer())
{
return (date <=
scenarios[sc].start +
static_cast<int>((scenarios[sc].containerCompletion /
100.0) * (scenarios[sc].end -
scenarios[sc].start)));
}
return (project->getNow() > date);
}
bool
Task::isBuffer(int sc, const Interval& iv) const
{
return iv.overlaps(Interval(scenarios[sc].start,
scenarios[sc].startBufferEnd)) ||
iv.overlaps(Interval(scenarios[sc].endBufferStart,
scenarios[sc].end));
}
time_t
Task::earliestStart(int sc) const
{
time_t date = 0;
// All tasks this task depends on must have an end date set.
for (TaskListIterator tli(previous); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->end == 0)
{
if (t->scheduling == ASAP) {
if (DEBUGTS(1)) {
qDebug()<<"Earliest start:"<<this<<":"<<t<<"end == 0";
}
return 0;
}
}
else if (t->end + 1 > date)
date = t->end + 1;
}
for (QListIterator<TaskDependency*> tdi(depends); tdi.hasNext();)
{
/* Add the gapDuration and/or gapLength to the end of the dependent
* task. */
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
time_t potentialDate = td->getTaskRef()->end + 1;
time_t dateAfterLengthGap;
long gapLength = td->getGapLength(sc);
for (dateAfterLengthGap = potentialDate;
gapLength > 0 && dateAfterLengthGap < project->getEnd();
dateAfterLengthGap += project->getScheduleGranularity())
if (isWorkingTime(dateAfterLengthGap))
gapLength -= project->getScheduleGranularity();
if (dateAfterLengthGap > potentialDate + td->getGapDuration(sc))
potentialDate = dateAfterLengthGap;
else
potentialDate += td->getGapDuration(sc);
// Set 'date' to the latest end date plus gaps of all preceding tasks.
if (potentialDate > date)
date = potentialDate;
}
/* If any of the parent tasks has an explicit start date, the task must
* start at or after this date. */
for (Task* t = getParent(); t; t = t->getParent())
if (t->start > date)
return t->start;
if (DEBUGTS(15)) {
qDebug()<<"Earliest start:"<<this<<time2ISO(date);
}
return date;
}
time_t
Task::latestEnd(int sc) const
{
time_t date = 0;
// All tasks this task precedes must have a start date set.
for (TaskListIterator tli(followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->start == 0)
{
if (t->scheduling == ALAP) {
if (DEBUGTS(1)) {
qDebug()<<"Latest end:"<<this<<":"<<t<<"start == 0";
}
return 0;
}
}
else if (date == 0 || t->start - 1 < date) {
date = t->start - 1;
if (DEBUGTS(15)) {
qDebug()<<"Latest end:"<<this<<time2ISO(date)<<"from follower:"<<t<<time2ISO(t->start-1);
}
}
}
for (QListIterator<TaskDependency*> tdi(precedes); tdi.hasNext();)
{
/* Subtract the gapDuration and/or gapLength from the start of the
* following task. */
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
time_t potentialDate = td->getTaskRef()->start - 1;
time_t dateBeforeLengthGap;
long gapLength = td->getGapLength(sc);
for (dateBeforeLengthGap = potentialDate;
gapLength > 0 && dateBeforeLengthGap >= project->getStart();
dateBeforeLengthGap -= project->getScheduleGranularity())
if (isWorkingTime(dateBeforeLengthGap))
gapLength -= project->getScheduleGranularity();
if (dateBeforeLengthGap < potentialDate - td->getGapDuration(sc))
potentialDate = dateBeforeLengthGap;
else
potentialDate -= td->getGapDuration(sc);
/* Set 'date' to the earliest end date minus gaps of all following
* tasks. */
if (date == 0 || potentialDate < date)
date = potentialDate;
if (DEBUGTS(15)) {
qDebug()<<"Latest end:"<<this<<time2ISO(date)<<"from successor:"<<td->getTaskRef()<<time2ISO(td->getTaskRef()->start);
}
}
/* If any of the parent tasks has an explicit end date, the task must
* end at or before this date. */
for (Task* t = getParent(); t; t = t->getParent())
if (t->end != 0 && t->end < date)
return t->end;
if (DEBUGTS(15)) {
qDebug()<<"Latest end:"<<this<<time2ISO(date);
}
return date;
}
double
Task::getCalcEffort(int sc) const
{
if (milestone)
return 0.0;
return getLoad(sc, Interval(scenarios[sc].start, scenarios[sc].end));
}
double
Task::getCalcDuration(int sc) const
{
if (milestone)
return 0.0;
return static_cast<double>(scenarios[sc].end + 1 - scenarios[sc].start) / ONEDAY;
}
double
Task::getLoad(int sc, const Interval& period, const Resource* resource) const
{
if (milestone)
return 0.0;
double load = 0.0;
if (isContainer())
{
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
load += t->getLoad(sc, period, resource);
}
}
else
{
if (resource)
load += resource->getEffectiveLoad(sc, period, AllAccounts, this);
else
for (ResourceListIterator rli(scenarios[sc].bookedResources);
rli.hasNext();)
load += static_cast<Resource*>(rli.next())->getEffectiveLoad(sc, period, AllAccounts, this);
}
return load;
}
double
Task::getAllocatedTimeLoad(int sc, const Interval& period,
const Resource* resource) const
{
return project->convertToDailyLoad
(getAllocatedTime(sc, period, resource));
}
long
Task::getAllocatedTime(int sc, const Interval& period,
const Resource* resource) const
{
if (milestone)
return 0;
long allocatedTime = 0;
if (isContainer())
{
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
allocatedTime += t->getAllocatedTime(sc, period, resource);
}
}
else
{
if (resource)
allocatedTime += resource->getAllocatedTime(sc, period, AllAccounts,
this);
else
for (ResourceListIterator rli(scenarios[sc].bookedResources);
*rli;)
allocatedTime += static_cast<Resource*>(rli.next())->getAllocatedTime(sc, period,
AllAccounts, this);
}
return allocatedTime;
}
// double
// Task::getCredits(int sc, const Interval& period, AccountType acctType,
// const Resource* resource, bool recursive) const
// {
// double credits = 0.0;
//
// if (recursive && !sub->isEmpty())
// {
// for (TaskListIterator tli(*sub); tli.hasNext();) {
// Task *t = static_cast<Task*>(tli.next());
// credits += t->getCredits(sc, period, acctType, resource,
// recursive);
// }
// }
//
// if (acctType != AllAccounts &&
// (account == 0 || acctType != account->getAcctType()))
// return credits;
//
// if (resource)
// credits += resource->getCredits(sc, period, acctType, this);
// else
// for (ResourceListIterator rli(scenarios[sc].bookedResources);
// *rli != 0; ++rli)
// credits += r->getCredits(sc, period, acctType, this);
//
// if (period.contains(scenarios[sc].start))
// credits += scenarios[sc].startCredit;
// if (period.contains(scenarios[sc].end + (milestone ? 1 : 0)))
// credits += scenarios[sc].endCredit;
//
// return credits;
// }
bool
Task::xRef(QMap<QString, Task*>& hash)
{
if (DEBUGPF(5))
qDebug()<<QString("Creating cross references for task %1 ...").arg(name);
int errors = 0;
QList<TaskDependency*> brokenDeps;
for (QListIterator<TaskDependency*> tdi(depends); tdi.hasNext();)
{
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
QString absId = resolveId(td->getTaskRefId());
Task* t;
if ((t = hash.value(absId)) == 0)
{
errorMessage(QString("Unknown dependency '%1'").arg(absId));
brokenDeps.append(td);
errors++;
}
else
{
for (QListIterator<TaskDependency*> tdi2(depends); tdi2.hasNext();) {
TaskDependency *td2 = static_cast<TaskDependency*>(tdi2.next());
if (td2->getTaskRef() == t)
{
warningMessage(QString("No need to specify dependency %1 "
"multiple times.").arg(absId));
break;
}
}
if (errors == 0)
{
td->setTaskRef(t);
if (t == this)
{
errorMessage(QString("Task '%1' cannot depend on self.")
.arg(name));
break;
}
if (t->isDescendantOf(this))
{
errorMessage(QString("Task '%1' cannot depend on child.")
.arg(name));
break;
}
if (isDescendantOf(t))
{
errorMessage(QString/**/("Task '%1' cannot depend on parent.")
.arg(t->id));
break;
}
if ( ! predecessors.contains( t ) ) {
// Unidirectional link
predecessors.append(t);
}
// Bidirectional link
if ( ! previous.contains( t ) ) {
previous.append(t);
}
if ( ! t->followers.contains( this ) ) {
t->followers.append(this);
}
if (DEBUGPF(11))
qDebug()<<"Registering dependency:"<<this<<"precedes"<<t;
}
}
}
// Remove broken dependencies as they can cause trouble later on.
for (QListIterator<TaskDependency*> tdi(brokenDeps); tdi.hasNext();) {
depends.removeAt(depends.indexOf(tdi.next()));
}
brokenDeps.clear();
for (QListIterator<TaskDependency*> tdi(precedes); tdi.hasNext();)
{
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
QString absId = resolveId(td->getTaskRefId());
Task* t;
if ((t = hash.value(absId)) == 0)
{
errorMessage(QString("Unknown dependency '%1'").arg(absId));
brokenDeps.append(td);
}
else
{
for (QListIterator<TaskDependency*> tdi2(precedes); tdi2.hasNext();) {
TaskDependency *td = static_cast<TaskDependency*>(tdi2.next());
if (td->getTaskRef() == t)
{
warningMessage(QString("No need to specify dependency '%1'"
"multiple times").arg(absId));
break;
}
}
if (errors == 0)
{
td->setTaskRef(t);
if (t == this)
{
errorMessage(QString("Task '%1' cannot precede self.")
.arg(name));
break;
}
if (t->isDescendantOf(this))
{
errorMessage(QString("Task '%1' cannot precede a child.")
.arg(name));
break;
}
if (isDescendantOf(t))
{
errorMessage(QString("Task '%1' cannot precede parent.")
.arg(t->id));
break;
}
if ( ! successors.contains( t ) ) {
// Unidirectional link
successors.append(t);
}
// Bidirectional link
if ( ! followers.contains( t ) ) {
followers.append(t);
}
if ( ! t->previous.contains( this ) ) {
t->previous.append(this);
}
if (DEBUGPF(11))
qDebug()<<"Registering predecessor"<<this<<"with task"<<t;
}
}
}
// Remove broken dependencies as they can cause trouble later on.
for (QListIterator<TaskDependency*> tdi(brokenDeps); tdi.hasNext();) {
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
precedes.removeAt(precedes.indexOf(td));
}
brokenDeps.clear();
return errors > 0;
}
void
Task::implicitXRef()
{
/* Every time the scheduling related information of a single task has been
* changed, we have to reset the cache flags for the start and end
* determinability. */
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
scenarios[sc].startCanBeDetermined = false;
scenarios[sc].endCanBeDetermined = false;
}
if (!sub->isEmpty())
return;
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
/* Propagate implicit dependencies. If a task has no specified start or
* end date and no start or end dependencies, we check if a parent task
* has an explicit start or end date which can be used. */
if (milestone)
{
if (scenarios[sc].specifiedStart != 0 &&
scenarios[sc].specifiedEnd == 0)
scenarios[sc].specifiedEnd = scenarios[sc].specifiedStart - 1;
if (scenarios[sc].specifiedEnd != 0 &&
scenarios[sc].specifiedStart == 0)
scenarios[sc].specifiedStart = scenarios[sc].specifiedEnd + 1;
}
bool hasDurationSpec = scenarios[sc].duration != 0 ||
scenarios[sc].length != 0 ||
scenarios[sc].effort != 0;
if (scenarios[sc].specifiedStart == 0 && depends.isEmpty() &&
!(hasDurationSpec && scheduling == ALAP))
for (Task* tp = getParent(); tp; tp = tp->getParent())
{
if (tp->scenarios[sc].specifiedStart != 0)
{
if (DEBUGPF(11))
qDebug()<<"Setting start of task '"<<id<<"' in scenario"<<project->getScenarioId(sc)<<"to"<<time2ISO(tp->scenarios[sc].specifiedStart);
scenarios[sc].specifiedStart = tp->scenarios[sc].specifiedStart;
break;
}
}
/* And the same for end values */
if (scenarios[sc].specifiedEnd == 0 && precedes.isEmpty() &&
!(hasDurationSpec && scheduling == ASAP))
for (Task* tp = getParent(); tp; tp = tp->getParent())
{
if (tp->scenarios[sc].specifiedEnd != 0)
{
if (DEBUGPF(11))
qDebug()<<"Setting end of task '"<<id<<"' in scenario"<<project->getScenarioId(sc)<<"to"<<time2ISO(tp->scenarios[sc].specifiedEnd),
scenarios[sc].specifiedEnd = tp->scenarios[sc].specifiedEnd;
break;
}
}
}
if (!isMilestone() && isLeaf())
{
/* Automatic milestone marker. As a convenience we convert tasks that
* only have a start or end criteria as a milestone. This is handy
* when in the early stage of a project draft, when you just want to
* specify the project outline and fill in subtasks and details
* later. */
bool hasStartSpec = false;
bool hasEndSpec = false;
bool hasDurationSpec = false;
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
{
if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty())
hasStartSpec = true;
if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
hasEndSpec = true;
if (scenarios[sc].duration != 0 || scenarios[sc].length != 0 ||
scenarios[sc].effort != 0)
hasDurationSpec = true;
// qDebug()<<"Task::implicitXRef:"<<id<<"effort="<<getEffort( sc )<<"sc="<<sc<<":"<<hasDurationSpec<<(hasStartSpec ^ hasEndSpec);
}
if (!hasDurationSpec && (hasStartSpec ^ hasEndSpec)) {
milestone = true;
// qDebug()<<"Task::implicitXRef:"<<"set"<<id<<"to milestone ("<<hasDurationSpec<<(hasStartSpec ^ hasEndSpec)<<")";
}
}
}
void
Task::sortAllocations()
{
if (allocations.isEmpty())
return;
// allocations.setAutoDelete(false);
for (QListIterator<Allocation*> ali(allocations); ali.hasNext(); )
{
Allocation *a = static_cast<Allocation*>(ali.next());
if (!a->isWorker())
{
/* If the resource does not do any work we move it to the front of
* the list. That way the 0 effective resources are always
* allocated no matter if the effort limit has been reached or not.
* At least in the same booking call. */
allocations.removeAt(allocations.indexOf(a));
allocations.prepend(a);
}
}
// allocations.setAutoDelete(true);
}
void
Task::saveSpecifiedBookedResources()
{
/* The project file readers use the same resource booking mechanism as the
* scheduler. So we need to save the up to now booked resources as
* specified resources. */
for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
scenarios[sc].specifiedBookedResources =
scenarios[sc].bookedResources;
}
bool
Task::loopDetector(LDIList& chkedTaskList) const
{
/* Only check top-level tasks. All other tasks will be checked then as
* well. */
if (parent)
return false;
if (DEBUGPF(2))
qDebug()<<"Running loop detector for task "<<id;
// Check ASAP tasks
LDIList list;
if (loopDetection(list, chkedTaskList, false, true))
return true;
// Check ALAP tasks
if (loopDetection(list, chkedTaskList, true, true))
return true;
return false;
}
bool
Task::loopDetection(LDIList& list, LDIList& chkedTaskList, bool atEnd,
bool fromOutside) const
{
if (DEBUGPF(10))
qDebug()<<QString().fill(' ', list.count() + 1)<<"loopDetection at"<<id<<"("<<(atEnd ? "End" : "Start")<<")";
// First, check whether the task has already been checked for loops.
{
LoopDetectorInfo thisTask(this, atEnd);
if (chkedTaskList.find(&thisTask))
{
// Already checked
return false;
}
}
if (checkPathForLoops(list, atEnd))
return true;
/* Now we have to traverse the graph in the direction of the specified
* dependencies. 'precedes' and 'depends' specify dependencies in the
* opposite direction of the flow of the tasks. So we have to make sure
* that we do not follow the arcs in the direction that precedes and
* depends points us. Parent/Child relationships also specify a
* dependency. The scheduling mode of the child determines the direction
* of the flow. With help of the 'caller' parameter we make sure that we
* only visit children if we were referred to the task by a non-parent-child
* relationship. */
if (!atEnd)
{
if (fromOutside)
{
/*
|
v
+--------
-->| o--+
+--- | --
|
V
*/
/* If we were not called from a sub task we check all sub tasks.*/
for (TaskListIterator tli(*sub); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking sub task"<<t->getId()<<"of"<<id;
if (t->loopDetection(list, chkedTaskList, false, true))
return true;
}
/*
|
v
+--------
-->| o---->
+--------
*/
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking end of task"<<id;
if (loopDetection(list, chkedTaskList, true, false))
return true;
}
else
{
/*
^
|
+ | -----
| o <--
+--------
^
|
*/
if (parent)
{
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking parent task of"<<id;
if (getParent()->loopDetection(list, chkedTaskList, false,
false))
return true;
}
/*
+--------
<--|- o <--
+--------
^
|
*/
// Now check all previous tasks.
for (TaskListIterator tli(previous); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking previous"<<t->getId()<<"of task"<<id;
if(t->loopDetection(list, chkedTaskList, true, true))
return true;
}
}
}
else
{
if (fromOutside)
{
/*
|
v
--------+
+--o |<--
-- | ---+
|
v
*/
/* If we were not called from a sub task we check all sub tasks.*/
for (TaskListIterator tli(*sub); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking sub task"<<t->getId()<<"of"<<id;
if (t->loopDetection(list, chkedTaskList, true, true))
return true;
}
/*
|
v
--------+
<----o |<--
--------+
*/
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking start of task"<<id;
if (loopDetection(list, chkedTaskList, false, false))
return true;
}
else
{
/*
^
|
----- | +
--> o |
--------+
^
|
*/
if (parent)
{
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking parent task of"<<id;
if (getParent()->loopDetection(list, chkedTaskList, true,
false))
return true;
}
/*
--------+
--> o-|-->
--------+
^
|
*/
// Now check all following tasks.
for (TaskListIterator tli(followers); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (DEBUGPF(15))
qDebug()<<QString().fill(' ', list.count())<<"Checking follower"<<t->getId()<<"of task"<<id;
if (t->loopDetection(list, chkedTaskList, false, true))
return true;
}
}
}
chkedTaskList.append(list.popLast());
if (DEBUGPF(5))
qDebug()<<QString().fill(' ', list.count())<<"No loops found in"<<id<<"("<<(atEnd ? "End" : "Start")<<")";
return false;
}
bool
Task::checkPathForLoops(LDIList& list, bool atEnd) const
{
/* If we find the current task (with same position) in the list, we have
* detected a loop. In case there is no loop detected we add this tasks at
* the end of the list. */
LoopDetectorInfo* thisTask = new LoopDetectorInfo(this, atEnd);
if (list.find(thisTask))
{
QString loopChain;
LoopDetectorInfo* it;
/* Find the first occurrence of this task in the list. This is the
* start of the loop. */
for (it = list.first(); *it != *thisTask; it = it->next())
;
/* Then copy all loop elements to the loopChain string. */
for ( ; it != 0; it = it->next())
{
loopChain += QString("%1 (%2) -> ")
.arg(it->getTask()->getId())
.arg(it->getAtEnd() ? "End" : "Start");
}
loopChain += QString("%1 (%2)").arg(name)
.arg(atEnd ? "End" : "Start");
delete thisTask;
errorMessage(QString("Dependency loop detected: %1").arg(loopChain));
return true;
}
list.append(thisTask);
return false;
}
bool
Task::checkDetermination(int sc) const
{
/* Check if the task and it's dependencies have enough information to
* produce a fixed determined schedule. */
if (DEBUGPF(10))
qDebug()<<"Checking determination of task"<<id;
LDIList list;
if (!startCanBeDetermined(list, sc))
{
/* The error message must only be shown if the task has prececessors.
* If not, is has been reported before already. */
if (!previous.isEmpty())
errorMessage
(QString("The start of task '%1' is "
"underspecified. This is caused by underspecified "
"dependent tasks. You must use more fixed dates to "
"solve this problem.")
.arg(name));
return false;
}
if (!endCanBeDetermined(list, sc))
{
/* The error message must only be shown if the task has followers.
* If not, is has been reported before already. */
if (!followers.isEmpty())
errorMessage
(QString("The end of task '%1' is underspecified. "
"This is caused by underspecified dependent tasks. You "
"must use more fixed dates to solve this problem.")
.arg(name));
return false;
}
return true;
}
bool
Task::startCanBeDetermined(LDIList& list, int sc) const
{
if (DEBUGPF(10))
qDebug()<<"Checking if start of task"<<id<<"can be determined";
if (scenarios[sc].startCanBeDetermined)
{
if (DEBUGPF(10))
qDebug()<<"Start of task"<<id<<"can be determined (cached)";
return true;
}
if (checkPathForLoops(list, false))
return false;
for (const Task* t = this; t; t = static_cast<const Task*>(t->parent))
if (scenarios[sc].specifiedStart != 0) //FIXME ?? t->scenarios[sc] ??
{
if (DEBUGPF(10))
qDebug()<<"Start of task"<<id<<"can be determined (fixed date)";
goto isDetermined;
}
if (scheduling == ALAP &&
(scenarios[sc].duration != 0.0 || scenarios[sc].length != 0.0 ||
scenarios[sc].effort != 0.0 || milestone) &&
endCanBeDetermined(list, sc))
{
if (DEBUGPF(10))
qDebug()<<"Start of task"<<id<<"can be determined (end + fixed length)";
goto isDetermined;
}
for (TaskListIterator tli(predecessors); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->endCanBeDetermined(list, sc))
{
if (DEBUGPF(10))
qDebug()<<"Start of task"<<id<<"can be determined (dependency)";
goto isDetermined;
}
}
if (hasSubs())
{
for (TaskListIterator tli = getSubListIterator(); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (!t->startCanBeDetermined(list, sc))
goto isNotDetermined;
}
if (DEBUGPF(10))
qDebug()<<"Start of task"<<id<<"can be determined (children)";
goto isDetermined;
}
isNotDetermined:
if (DEBUGPF(10))
qDebug()<<"*** Start of task"<<id<<"cannot be determined";
list.removeLast();
return false;
isDetermined:
list.removeLast();
scenarios[sc].startCanBeDetermined = true;
return true;
}
bool
Task::endCanBeDetermined(LDIList& list, int sc) const
{
if (DEBUGPF(10))
qDebug()<<"Checking if end of task"<<id<<"can be determined";
if (scenarios[sc].endCanBeDetermined)
{
if (DEBUGPF(10))
qDebug()<<"End of task"<<id<<"can be determined";
return true;
}
if (checkPathForLoops(list, true))
return false;
for (const Task* t = this; t; t = static_cast<const Task*>(t->parent))
if (scenarios[sc].specifiedEnd != 0) //FIXME ?? t->scenarios ??
{
if (DEBUGPF(10))
qDebug()<<"End of task"<<id<<"can be determined (fixed date)";
goto isDetermined;
}
if (scheduling == ASAP &&
(scenarios[sc].duration != 0.0 || scenarios[sc].length != 0.0 ||
scenarios[sc].effort != 0.0 || milestone) &&
startCanBeDetermined(list, sc))
{
if (DEBUGPF(10))
qDebug()<<"End of task"<<id<<"can be determined (end + fixed length)";
goto isDetermined;
}
for (TaskListIterator tli(successors); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->startCanBeDetermined(list, sc))
{
if (DEBUGPF(10))
qDebug()<<"End of task"<<id<<"can be determined (dependency)";
goto isDetermined;
}
}
if (hasSubs())
{
for (TaskListIterator tli = getSubListIterator(); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (!t->endCanBeDetermined(list, sc))
{
if (DEBUGPF(10))
qDebug()<<"End of task"<<id<<"cannot be determined (child"<<t->id<<")";
goto isNotDetermined;
}
}
if (DEBUGPF(10))
qDebug()<<"End of task"<<id<<"can be determined (children)";
goto isDetermined;
}
isNotDetermined:
if (DEBUGPF(10))
qDebug()<<"*** End of task"<<id<<"cannot be determined";
list.removeLast();
return false;
isDetermined:
list.removeLast();
scenarios[sc].endCanBeDetermined = true;
return true;
}
QString
Task::resolveId(QString relId)
{
/* Converts a relative ID to an absolute ID. Relative IDs start
* with a number of bangs. A set of bangs means 'Name of the n-th
* parent task' with n being the number of bangs. */
if (relId[0] != '!')
return relId;
Task* t = this;
int i;
for (i = 0; i < relId.length() && relId.mid(i, 1) == "!"; ++i)
{
if (t == 0)
{
errorMessage(QString("Illegal relative ID '%1'").arg(relId));
return relId;
}
t = t->getParent();
}
if (t)
return t->id + QLatin1Char('.') + relId.right(relId.length() - i);
else
return relId.right(relId.length() - i);
}
bool
Task::hasStartDependency(int sc) const
{
/* Checks whether the task has a start specification for the
* scenario. This can be a fixed start time or a dependency on another
* task's end or an implicit dependency on the fixed start time of a
* parent task. */
if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty()) {
// qDebug()<<"Task::hasStartDependency:"<<id<<sc<<(scenarios[sc].specifiedStart != 0)<<(!depends.isEmpty());
return true;
}
for (Task* p = getParent(); p; p = p->getParent()) {
if (p->scenarios[sc].specifiedStart != 0) {
// qDebug()<<"Task::hasStartDependency:"<<id<<sc<<"parent";
return true;
}
}
// qDebug()<<"Task::hasStartDependency:"<<id<<sc<<"no dependency:"<<(scenarios[sc].specifiedStart != 0)<<(!depends.isEmpty());
return false;
}
bool
Task::hasEndDependency(int sc) const
{
/* Checks whether the task has an end specification for the
* scenario. This can be a fixed end time or a dependency on another
* task's start or an implicit dependency on the fixed end time of a
* parent task. */
if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
return true;
for (Task* p = getParent(); p; p = p->getParent())
if (p->scenarios[sc].specifiedEnd != 0)
return true;
return false;
}
bool
Task::hasStartDependency() const
{
/* Check whether the task or any of it's sub tasks has a start
* dependency. */
if (start != 0 || !previous.isEmpty() || scheduling == ALAP)
return true;
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->hasStartDependency())
return true;
}
return false;
}
bool
Task::hasEndDependency() const
{
/* Check whether the task or any of it's sub tasks has an end
* dependency. */
if (end != 0 || !followers.isEmpty() || scheduling == ASAP)
return true;
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->hasEndDependency())
return true;
}
return false;
}
bool
Task::preScheduleOk(int sc)
{
// if (account && !account->isLeaf())
// {
// errorMessage(QString
// ("Task '%1' must not have an account group ('%2') "
// "assigned to it.")
// .arg(name).arg(account->getId()));
// return false;
// }
if (hasSubs() && !scenarios[sc].bookedResources.isEmpty())
{
errorMessage(QString
("Task '%1' is a container task and must not have "
"bookings assigned to it.").arg(name));
return false;
}
if (milestone && !scenarios[sc].bookedResources.isEmpty())
{
errorMessage(QString
("Task '%1' is a milestone task and must not have "
"bookings assigned to it.").arg(name));
return false;
}
if (scenarios[sc].specifiedScheduled && !sub->isEmpty() &&
(scenarios[sc].specifiedStart == 0 ||
scenarios[sc].specifiedEnd == 0))
{
errorMessage(QString
("Task '%1' is marked as scheduled but does not have "
"a fixed start and end date.").arg(name));
return false;
}
if (scenarios[sc].effort > 0.0 && allocations.count() == 0 &&
!scenarios[sc].specifiedScheduled)
{
errorMessage(QString
("No allocations specified for effort based task '%1'")
.arg(name));
return false;
}
if (scenarios[sc].startBuffer + scenarios[sc].endBuffer >= 100.0)
{
errorMessage(QString
("Start and end buffers may not overlap. So their sum must be smaller then 100%."));
return false;
}
int durationSpec = 0;
if (scenarios[sc].effort > 0.0)
durationSpec++;
if (scenarios[sc].length > 0.0)
durationSpec++;
if (scenarios[sc].duration > 0.0)
durationSpec++;
if (durationSpec > 1)
{
errorMessage(QString("Task '%1' may only have one duration "
"criteria in '%2' scenario.").arg(name)
.arg(project->getScenarioId(sc)));
return false;
}
/*
|: fixed start or end date
-: no fixed start or end date
M: Milestone
D: start or end dependency
x->: ASAP task with duration criteria
<-x: ALAP task with duration criteria
-->: ASAP task without duration criteria
<--: ALAP task without duration criteria
*/
bool hasStartDep = hasStartDependency(sc);
bool hasEndDep = hasEndDependency(sc);
if (!sub->isEmpty())
{
if (durationSpec != 0)
{
errorMessage(QString
("Container task '%1' may not have a duration "
"criteria in '%2' scenario").arg(name)
.arg(project->getScenarioId(sc)));
return false;
}
if (milestone)
{
errorMessage(QString
("The container task '%1' may not be a "
"milestone.").arg(name));
return false;
}
}
else if (milestone)
{
if (durationSpec != 0)
{
errorMessage(QString
("Milestone '%1' may not have a duration "
"criteria in '%2' scenario").arg(name)
.arg(project->getScenarioId(sc)));
return false;
}
/*
| M - ok |D M - ok - M - err1 -D M - ok
| M | err2 |D M | err2 - M | ok -D M | ok
| M -D ok |D M -D ok - M -D ok -D M -D ok
| M |D err2 |D M |D err2 - M |D ok -D M |D ok
*/
/* err1: no start and end
- M -
*/
if (!hasStartDep && !hasEndDep)
{
errorMessage(QString("Milestone '%1' must have a start or end "
"specification in '%2' scenario.")
.arg(name).arg(project->getScenarioId(sc)));
return false;
}
/* err2: different start and end
| M |
| M |D
|D M |
|D M |D
*/
if (scenarios[sc].specifiedStart != 0 &&
scenarios[sc].specifiedEnd != 0 &&
scenarios[sc].specifiedStart != scenarios[sc].specifiedEnd + 1)
{
errorMessage(QString
("Milestone '%1' may not have both a start "
"and an end specification that do not "
"match in the '%2' scenario.").arg(name)
.arg(project->getScenarioId(sc)));
return false;
}
}
else
{
/*
Error table for non-container, non-milestone tasks:
| x-> - ok |D x-> - ok - x-> - err3 -D x-> - ok
| x-> | err1 |D x-> | err1 - x-> | err3 -D x-> | err1
| x-> -D ok |D x-> -D ok - x-> -D err3 -D x-> -D ok
| x-> |D err1 |D x-> |D err1 - x-> |D err3 -D x-> |D err1
| --> - err2 |D --> - err2 - --> - err3 -D --> - err2
| --> | ok |D --> | ok - --> | err3 -D --> | ok
| --> -D ok |D --> -D ok - --> -D err3 -D --> -D ok
| --> |D ok |D --> |D ok - --> |D err3 -D --> |D ok
| <-x - err4 |D <-x - err4 - <-x - err4 -D <-x - err4
| <-x | err1 |D <-x | err1 - <-x | ok -D <-x | ok
| <-x -D err1 |D <-x -D err1 - <-x -D ok -D <-x -D ok
| <-x |D err1 |D <-x |D err1 - <-x |D ok -D <-x |D ok
| <-- - err4 |D <-- - err4 - <-- - err4 -D <-- - err4
| <-- | ok |D <-- | ok - <-- | err2 -D <-- | ok
| <-- -D ok |D <-- -D ok - <-- -D err2 -D <-- -D ok
| <-- |D ok |D <-- |D ok - <-- |D err2 -D <-- |D ok
*/
/*
err1: Overspecified (12 cases)
| x-> |
| <-x |
| x-> |D
| <-x |D
|D x-> |
|D <-x |
|D <-x |D
|D x-> |D
-D x-> |
-D x-> |D
|D <-x -D
| <-x -D
*/
if (((scenarios[sc].specifiedStart != 0 &&
scenarios[sc].specifiedEnd != 0) ||
(hasStartDep && scenarios[sc].specifiedStart == 0 &&
scenarios[sc].specifiedEnd != 0 && scheduling == ASAP) ||
(scenarios[sc].specifiedStart != 0 && scheduling == ALAP &&
hasEndDep && scenarios[sc].specifiedEnd == 0)) &&
durationSpec != 0 && !scenarios[sc].specifiedScheduled)
{
errorMessage(QString("Task '%1' has a start, an end and a "
"duration specification for '%2' scenario.")
.arg(name).arg(project->getScenarioId(sc)));
return false;
}
/*
err2: Underspecified (6 cases)
| --> -
|D --> -
-D --> -
- <-- |
- <-- |D
- <-- -D
*/
if ((hasStartDep ^ hasEndDep) && durationSpec == 0)
{
errorMessage(QString
("Task '%1' has only a start or end specification "
"but no duration for the '%2' scenario.")
.arg(name).arg(project->getScenarioId(sc)));
return false;
}
/*
err3: ASAP + Duration must have fixed start (8 cases)
- x-> -
- x-> |
- x-> -D
- x-> |D
- --> -
- --> |
- --> -D
- --> |D
*/
if (!hasStartDep && scheduling == ASAP)
{
errorMessage(QString
("Task '%1' needs a start specification to be "
"scheduled in ASAP mode in the '%2' scenario.")
.arg(name).arg(project->getScenarioId(sc)));
return false;
}
/*
err4: ALAP + Duration must have fixed end (8 cases)
- <-x -
| <-x -
|D <-x -
-D <-x -
- <-- -
| <-- -
-D <-- -
|D <-- -
*/
if (!hasEndDep && scheduling == ALAP)
{
errorMessage(QString
("Task '%1' needs an end specification to be "
"scheduled in ALAP mode in the '%2' scenario.")
.arg(name).arg(project->getScenarioId(sc)));
return false;
}
}
// if (!account &&
// (scenarios[sc].startCredit > 0.0 || scenarios[sc].endCredit > 0.0))
// {
// errorMessage(QString
// ("Task '%1' has a specified start- or endcredit "
// "but no account assigned in scenario '%2'.")
// .arg(name).arg(project->getScenarioId(sc)));
// return false;
// }
if (!scenarios[sc].bookedResources.isEmpty() && scheduling == ALAP &&
!scenarios[sc].specifiedScheduled)
{
errorMessage
(QString("Error in task '%1'. "
"An ALAP task can only have bookings if it has been "
"completely scheduled. The 'scheduled' attribute must be "
"present. Keep in mind that certain attributes such as "
"'precedes' or 'end' implicitly set the scheduling mode "
"to ALAP. Put 'scheduling asap' at the end of the task "
"definition to avoid the problem.")
.arg(name));
return false;
}
return true;
}
bool
Task::scheduleOk(int sc) const
{
const QString scenario = project->getScenarioId(sc);
/* It is of little use to report errors of container tasks, if any of
* their sub tasks has errors. */
int oldErrors = TJMH.getErrors();
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
t->scheduleOk(sc);
}
if (oldErrors != TJMH.getErrors())
{
if (DEBUGPS(2))
tjDebug(QString("Scheduling errors in sub tasks of '%1'.")
.arg(name));
return false;
}
/* Runaway errors have already been reported. Since the data of this task
* is very likely completely bogus, we just return false. */
if (runAway)
return false;
if (DEBUGPS(3))
qDebug()<<"Checking task"<<name;
/* If any of the dependent tasks is a runAway, we can safely suppress all
* other error messages. */
for (QListIterator<TaskDependency*> tdi(depends); tdi.hasNext();) {
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
if (td->getTaskRef()->runAway)
return false;
}
for (QListIterator<TaskDependency*> tdi(precedes); tdi.hasNext();) {
TaskDependency *td = static_cast<TaskDependency*>(tdi.next());
if (td->getTaskRef()->runAway)
return false;
}
if (start == 0)
{
warningMessage(xi18nc("@info/plain", "Start time is not calculated"));
return false;
}
if (start < project->getStart() || start > project->getEnd())
{
warningMessage(xi18nc("@info/plain", "Start time %1 is outside of the project target times (%2 - %3)",
formatTime(start),
formatTime(project->getStart()),
formatTime(project->getEnd())));
return false;
}
#if 0
NOT USED ATM
if (scenarios[sc].minStart != 0 && start < scenarios[sc].minStart)
{
warningMessage(QString("'%1' start time of task '%2' is too early\n"
"Date is: %3\n"
"Limit is: %4")
.arg(scenario).arg(name).arg(time2tjp(start))
.arg(time2tjp(scenarios[sc].minStart)));
return false;
}
if (scenarios[sc].maxStart != 0 && start > scenarios[sc].maxStart)
{
warningMessage(QString("'%1' start time of task '%2' is too late\n"
"Date is: %3\n"
"Limit is: %4")
.arg(scenario).arg(name)
.arg(time2tjp(start))
.arg(time2tjp(scenarios[sc].maxStart)));
return false;
}
#endif
if (end == 0)
{
warningMessage(xi18nc("info/plain", "End time is not calculated"));
return false;
}
if ((end + 1) < project->getStart() || (end > project->getEnd()))
{
warningMessage(xi18nc("info/plain", "End time %1 is outside of the project target times (%2 - %3)",
formatTime(end + 1),
formatTime(project->getStart()),
formatTime(project->getEnd() + 1)));
return false;
}
#if 0
NOT USED ATM
if (scenarios[sc].minEnd != 0 && end < scenarios[sc].minEnd)
{
warningMessage(QString("'%1' end time of task '%2' is too early\n"
"Date is: %3\n"
"Limit is: %4")
.arg(scenario).arg(name)
.arg(time2tjp(end + 1))
.arg(time2tjp(scenarios[sc].minEnd + 1)));
return false;
}
if (scenarios[sc].maxEnd != 0 && end > scenarios[sc].maxEnd)
{
warningMessage(QString("'%1' end time of task '%2' is too late\n"
"Date is: %2\n"
"Limit is: %3")
.arg(scenario).arg(name)
.arg(time2tjp(end + 1))
.arg(time2tjp(scenarios[sc].maxEnd + 1)));
return false;
}
if (!sub->isEmpty())
{
// All sub task must fit into their parent task.
for (TaskListIterator tli(*sub); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (start > t->start)
{
if (!t->runAway)
{
errorMessage(QString("Task '%1' has earlier '%2' start than "
"parent\n"
"%3 start date: %4\n"
"%5 start date: %6")
.arg(t->getId()).arg(scenario)
.arg(name)
.arg(time2ISO(start))
.arg(t->getId())
.arg(time2ISO(t->start)));
}
return false;
}
if (end < t->end)
{
if (!t->runAway)
{
errorMessage(QString("Task '%1' has later '%2' end than "
"parent")
.arg(name).arg(scenario));
}
return false;
}
}
}
#endif
// Check if all previous tasks end before start of this task.
for (TaskListIterator tli(predecessors); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->end > start && !t->runAway) {
if (t->end == 0) {
warningMessage(xi18nc("@info/plain", "Impossible dependency:<nl/>"
"Predeccessor task '%1': End time not calculated", t->getName()));
} else {
warningMessage(xi18nc("@info/plain", "Impossible dependency:<nl/>"
"Task '%1' ends at %2 but must precede<nl/>"
"task '%3' which starts at %4",
t->getName(), formatTime(t->end + 1),
name, formatTime(start)));
}
return false;
}
}
// Check if all following task start after this tasks end.
for (TaskListIterator tli(successors); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (end > t->start && !t->runAway) {
if (t->start == 0) {
warningMessage(xi18nc("@info/plain", "Impossible dependency:<nl/>"
"Successor task '%1': Start time not calculated", t->getName()));
} else {
warningMessage(xi18nc("@info/plain", "Impossible dependency:<nl/>"
"Task '%1' starts at %2 but must follow<nl/>"
"task %3 which ends at %4",
t->getName(), formatTime(t->start),
name, formatTime(end + 1)));
}
return false;
}
}
if (!schedulingDone)
{
warningMessage(xi18nc("info/plain", "Task has not been marked completed.\n"
"It is scheduled to last from %1 to %2.\n"
"This might be a bug in the scheduler.",
formatTime(start), formatTime(end + 1)));
return false;
}
return true;
}
time_t
Task::nextSlot(time_t slotDuration) const
{
if (scheduling == ASAP)
{
if (lastSlot == 0)
return start;
return lastSlot + 1;
}
else
{
if (lastSlot == 0)
return end - slotDuration + 1;
return lastSlot - slotDuration;
}
return 0;
}
bool
Task::isReadyForScheduling() const
{
/* This function returns true if the tasks has all the necessary
* information to be scheduled and has not been completely scheduled yet.
*/
if (schedulingDone)
return false;
if (scheduling == ASAP)
{
if (start != 0)
{
if (effort == 0.0 && length == 0.0 && duration == 0.0 &&
!milestone && end == 0)
return false;
return true;
}
}
else
{
if (end != 0)
{
if (effort == 0.0 && length == 0.0 && duration == 0.0 &&
!milestone && start == 0) {
return false;
}
return true;
}
}
// TJMH.debugMessage(QString("Not ready for scheduling: %1 start=%2, end=%3").arg(scheduling==ASAP?"ASAP":"ALAP").arg(start).arg(end), this);
return false;
}
bool
Task::isActive(int sc, const Interval& period) const
{
return period.overlaps(Interval(scenarios[sc].start,
milestone ? scenarios[sc].start :
scenarios[sc].end));
}
bool
Task::isSubTask(const Task* tsk) const
{
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (*tli == tsk || t->isSubTask(tsk))
return true;
}
return false;
}
void
Task::overlayScenario(int base, int sc)
{
/* Copy all values that the scenario sc does not provide, but that are
* provided by the base scenario to the scenario sc. */
if (scenarios[sc].specifiedStart == 0)
scenarios[sc].specifiedStart = scenarios[base].specifiedStart;
if (scenarios[sc].specifiedEnd == 0)
scenarios[sc].specifiedEnd = scenarios[base].specifiedEnd;
if (scenarios[sc].minStart == 0)
scenarios[sc].minStart = scenarios[base].minStart;
if (scenarios[sc].maxStart == 0)
scenarios[sc].maxStart = scenarios[base].maxStart;
if (scenarios[sc].minEnd == 0)
scenarios[sc].minEnd = scenarios[base].minEnd;
if (scenarios[sc].maxEnd == 0)
scenarios[sc].maxEnd = scenarios[base].maxEnd;
if (scenarios[sc].duration == 0.0)
scenarios[sc].duration = scenarios[base].duration;
if (scenarios[sc].length == 0.0)
scenarios[sc].length = scenarios[base].length;
if (scenarios[sc].effort == 0.0)
scenarios[sc].effort = scenarios[base].effort;
if (scenarios[sc].startBuffer < 0.0)
scenarios[sc].startBuffer = scenarios[base].startBuffer;
if (scenarios[sc].endBuffer < 0.0)
scenarios[sc].endBuffer = scenarios[base].endBuffer;
if (scenarios[sc].startCredit < 0.0)
scenarios[sc].startCredit = scenarios[base].startCredit;
if (scenarios[sc].endCredit < 0.0)
scenarios[sc].endCredit = scenarios[base].endCredit;
if (scenarios[sc].reportedCompletion < 0.0)
scenarios[sc].reportedCompletion = scenarios[base].reportedCompletion;
}
void
Task::prepareScenario(int sc)
{
start = scenarios[sc].start = scenarios[sc].specifiedStart;
end = scenarios[sc].end = scenarios[sc].specifiedEnd;
schedulingDone = scenarios[sc].scheduled = scenarios[sc].specifiedScheduled;
scenarios[sc].isOnCriticalPath = false;
scenarios[sc].pathCriticalness = -1.0;
duration = scenarios[sc].duration;
length = scenarios[sc].length;
effort = scenarios[sc].effort;
lastSlot = 0;
doneEffort = 0.0;
doneDuration = 0.0;
doneLength = 0.0;
tentativeStart = tentativeEnd = 0;
workStarted = false;
runAway = false;
bookedResources.clear();
bookedResources = scenarios[sc].specifiedBookedResources;
/* The user could have made manual bookings already. The effort of these
* bookings needs to be calculated so that the scheduler only schedules
* the still missing effort. Scheduling will begin after the last booking.
* This will only work for ASAP tasks. ALAP tasks cannot be partly booked.
*/
time_t firstSlot = 0;
for (ResourceListIterator rli(bookedResources); rli.hasNext();)
{
Resource *r = static_cast<Resource*>(rli.next());
double effort = r->getEffectiveLoad
(sc, Interval(project->getStart(), project->getEnd()),
AllAccounts, this);
if (effort > 0.0)
{
doneEffort += effort;
if (firstSlot == 0 ||
firstSlot > r->getStartOfFirstSlot(sc, this))
{
firstSlot = r->getStartOfFirstSlot(sc, this);
}
time_t ls = r->getEndOfLastSlot(sc, this);
if (ls > lastSlot)
lastSlot = ls;
}
}
if (lastSlot > 0)
{
if (schedulingDone)
{
/* If the done flag is set, the user declares that the task is
* done. If no end date has been specified, set the start and end
* date to the begin of the first slot and end of the last slot.
*/
if (scenarios[sc].start == 0)
start = scenarios[sc].start = firstSlot;
if (scenarios[sc].end == 0)
end = scenarios[sc].end = lastSlot;
}
else
{
/* Some bookings have been specified for the task, but it is not
* marked completed yet. */
workStarted = true;
// Trim start to first booked time slot.
start = firstSlot;
/* In projection mode, we assume that the completed work has been
* reported with booking attributes. Now we compute the completion
* degree according to the overall effort. Then the end date of
* the task is calculated. */
if (project->getScenario(sc)->getProjectionMode() && effort > 0.0)
{
scenarios[sc].reportedCompletion = doneEffort / effort * 100.0;
if (scenarios[sc].reportedCompletion > 100.0)
scenarios[sc].reportedCompletion = 100.0;
if (doneEffort >= effort)
{
/* In case the required effort is reached or exceeded by
* the specified bookings for this task, we set the task
* end to the last booking and mark the task as completely
* scheduled. */
end = scenarios[sc].end = lastSlot;
schedulingDone = true;
/* We allow up to one time slot fuzziness before we
* generate a warning. */
if (project->getScenario(sc)->getStrictBookings() &&
doneEffort > effort +
project->convertToDailyLoad
(project->getScheduleGranularity() - 1))
{
/* In case the bookings exceed the specified effort
* in strict mode, show a warning. */
warningMessage(xi18nc("info/plain", "Planned effort %1 exceeds estimated effort %2",
doneEffort,
effort));
}
}
else
lastSlot = project->getNow() - 1;
}
}
}
/*
* To determine the criticalness of an effort based task, we need to
* determine the allocation probability of all of the resources. The more
* the resources that are allocated to a task are allocated the smaller is
* the likelyhood that the task will get it's allocation, the more
* critical it is.
*
* The allocation probability of a resource for this task is basically
* effort divided by number of allocated resources. Since the efficiency
* of resources can vary we need to determine the overall efficiency
* first.
*
* TODO: We need to respect limits and shifts here!
*/
double allocationEfficiency = 0;
for (QListIterator<Allocation*> ali(allocations); ali.hasNext();)
{
Allocation *a = static_cast<Allocation*>(ali.next());
a->init();
if (a->isPersistent() && !bookedResources.isEmpty())
{
/* If the allocation is persistent and we have already bookings,
* we need to find the resource with the last booking for this
* task and save it as looked resource. */
time_t lastSlot = 0;
Resource* lastResource = 0;
for (QListIterator<Resource*> rli = a->getCandidatesIterator(); rli.hasNext();) {
Resource *r = static_cast<Resource*>(rli.next());
for (ResourceTreeIterator rti(r); *rti; ++rti) {
if (bookedResources.indexOf(*rti) != -1 &&
(lastResource == 0 ||
lastSlot < (*rti)->getEndOfLastSlot(sc, this)))
{
lastSlot = (*rti)->getEndOfLastSlot(sc, this);
lastResource = r;
}
}
}
a->setLockedResource(lastResource);
}
if (scenarios[sc].effort > 0.0)
{
double maxEfficiency = 0;
for (QListIterator<Resource*> rli = a->getCandidatesIterator(); rli.hasNext();)
{
Resource *r = static_cast<Resource*>(rli.next());
for (ResourceTreeIterator rti(r); *rti; ++rti) {
if ((*rti)->getEfficiency() > maxEfficiency)
maxEfficiency = (*rti)->getEfficiency();
}
}
allocationEfficiency += maxEfficiency;
}
}
if (scenarios[sc].effort > 0.0)
{
/* Now we can add the allocation probability for this task to all the
* individual resources. */
double effortPerResource = effort / allocationEfficiency;
for (QListIterator<Allocation*> ali(allocations); ali.hasNext();) {
Allocation *a = static_cast<Allocation*>(ali.next());
for (QListIterator<Resource*> rli = a->getCandidatesIterator(); rli.hasNext();) {
Resource *r = static_cast<Resource*>(rli.next());
for (ResourceTreeIterator rti(r); *rti; ++rti) {
(*rti)->addAllocationProbability
(sc, effortPerResource * (*rti)->getEfficiency());
}
}
}
}
}
void
Task::computeCriticalness(int sc)
{
if (scenarios[sc].effort > 0.0)
{
double overallAllocationProbability = 0;
for (QListIterator<Allocation*> ali(allocations); ali.hasNext();)
{
/* We assume that out of the candidates for an allocation the
* one with the smallest overall allocation probability will
* be assigned to the task. */
Allocation *a = static_cast<Allocation*>(ali.next());
double smallestAllocationProbablity = 0;
for (QListIterator<Resource*> rli =
a->getCandidatesIterator(); rli.hasNext();)
{
/* If the candidate is a resource group we use the average
* allocation probability of all the resources of the group.
*/
int resources = 0;
double averageProbability = 0.0;
for (ResourceTreeIterator rti(rli.next()); *rti; ++rti, ++resources) {
averageProbability +=
(*rti)->getAllocationProbability(sc);
}
if (resources > 0)
averageProbability /= resources;
if (smallestAllocationProbablity == 0 ||
averageProbability < smallestAllocationProbablity)
smallestAllocationProbablity = averageProbability;
}
overallAllocationProbability += smallestAllocationProbablity;
}
/* New we normalize the allocationProbability to the duration of the
* project (working days). For a resource that is statistically
* allocated no more and no less than the number of working days in
* the expected project time the probability will be one. This
* certainly neglects many things like vacations, shifts, parallel
* assignments and other factors. But we don't know enough about
* these factors yet, to take them into account. So we have to live
* with what we got. */
overallAllocationProbability /=
allocations.count() *
((project->getEnd() - project->getStart()) / (60.0 * 60 * 24)) *
(project->getYearlyWorkingDays() / 365.0);
/* Weight the average allocation probability with the effort of the
* task. The higher the effort and the higher the probability that the
* resources are allocated, the more critical the task rating gets. To
* ensure that the criticalness is at least as high as a comparable
* 'length' tasks use the effort value as a baseline and add the
* weighted effort ontop. */
scenarios[sc].criticalness = (1 + overallAllocationProbability) *
scenarios[sc].effort;
}
else if (scenarios[sc].duration > 0.0)
scenarios[sc].criticalness = duration;
else if (scenarios[sc].length > 0.0)
scenarios[sc].criticalness = length *
(365 / project->getYearlyWorkingDays());
else if (isMilestone())
{
/* People think of milestones usually as something important. So let's
* assume a milestone has the importance of a full working day. This
* is only done to raise the criticalness of paths that contain
* milestones. */
scenarios[sc].criticalness = 1.0;
}
else
scenarios[sc].criticalness = 0;
}
double
Task::computePathCriticalness(int sc)
{
/*
* The path criticalness is a measure for the overall criticalness of the
* task taking the dependencies into account. The fact that a task is part
* of a chain of effort-based task raises all the task in the chain to a
* higher criticalness level than the individual tasks. In fact, the path
* criticalness of this chain is equal to the sum of the individual
* criticalnesses of the tasks that are trailing this task. It does not
* take the user-defined priorities into account.
*/
// If the value has been computed already, just return it.
if (scenarios[sc].pathCriticalness >= 0.0)
return scenarios[sc].pathCriticalness;
double maxCriticalness = 0.0;
if (hasSubs())
{
double criticalness;
for (TaskListIterator tli(getSubListIterator()); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
criticalness = t->computePathCriticalness(sc);
if (criticalness > maxCriticalness)
maxCriticalness = criticalness;
}
}
else
{
/* We only care about leaf tasks because that's were the resources are
* actually used. Container tasks are respected during path tracking,
* though.
* Therefore, we generate a list of all successors to this task. These
* are directly specified successors or successors of any of the
* parent tasks of this task. */
TaskList followerList;
Task* t = this;
while (t)
{
for (TaskListIterator tli(t->followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (followerList.indexOf(t) == -1)
followerList.append(t);
}
t = static_cast<Task*>(t->parent);
}
double criticalness;
for (TaskListIterator tli(followerList); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
criticalness = t->computePathCriticalness(sc);
if (criticalness > maxCriticalness)
maxCriticalness = criticalness;
}
}
scenarios[sc].pathCriticalness = scenarios[sc].criticalness +
maxCriticalness;
return scenarios[sc].pathCriticalness;
}
void
Task::checkAndMarkCriticalPath(int sc, double minSlack, time_t maxEnd)
{
// The algorithm has to start at a leaf task that has no predecessors.
if (hasSubs() || !previous.isEmpty())
return;
if (DEBUGPA(3))
qDebug()<<"Starting critical path search at"<<id;
long worstMinSlackTime = static_cast<long>((maxEnd - getStart(sc)) *
minSlack);
long checks = 0;
long found = 0;
analyzePath(sc, minSlack, getStart(sc), 0, worstMinSlackTime, checks,
found);
}
bool
Task::analyzePath(int sc, double minSlack, time_t pathStart, long busyTime,
long worstMinSlackTime, long& checks, long& found)
{
/* Safeguard to limit the runtime for this NP hard algorithm. */
long maxPaths = project->getScenario(sc)->getMaxPaths();
if (maxPaths > 0 && checks >= maxPaths)
return false;
if (DEBUGPA(14))
qDebug()<<" * Checking task"<<id;
bool critical = false;
if (hasSubs())
{
if (DEBUGPA(15))
qDebug()<<" > Sub check started for"<<id;
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->analyzePath(sc, minSlack, pathStart, busyTime,
worstMinSlackTime, checks, found))
critical = true;
}
if (DEBUGPA(15))
qDebug()<<" < Sub check finished for"<<id;
}
else
{
busyTime += (getEnd(sc) + 1 - getStart(sc));
/* If we have enough slack already that the path cannot be critical,
* we stop looking at the rest of the path. */
long currentSlack = (getEnd(sc) + 1 - pathStart) - busyTime;
if (currentSlack > worstMinSlackTime)
{
checks++;
if (DEBUGPA(6))
qDebug()<<"Path cannot be critical. Stopping at task"<<id;
return false;
}
/* Find out if any of the followers is a sibling of the parent of this
* task. */
bool hasBrotherFollower = false;
for (TaskListIterator tli(followers); tli.hasNext() && !hasBrotherFollower;)
for (Task* t = static_cast<Task*>(tli.next()); t; t = t->getParent())
if (t == getParent())
{
hasBrotherFollower = true;
break;
}
/* We first have to gather a list of all followers of this task. This
* list must also include the followers registered for all parent
* tasks of this task as they are followers as well. */
TaskList allFollowers;
for (Task* task = this; task; task = task->getParent())
{
for (TaskListIterator tli(task->followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (allFollowers.indexOf(t) < 0)
allFollowers.append(t);
}
/* If the task has a follower that is a sibling of the same parent
* we ignore the parent followers. */
if (hasBrotherFollower)
break;
}
/* Get a list of all transient followers that follow the direct and
* indirect followers of this task. If the allFollowers list contain
* any of the transient followers, we can ignore it later. */
TaskList transientFollowers;
for (TaskListIterator tli(allFollowers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
t->collectTransientFollowers(transientFollowers);
}
/* For inherited dependencies we only follow the bottommost task that
* is a follower. All parents in the allFollowers list are ignored. */
TaskList ignoreList;
for (TaskListIterator tli(allFollowers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
for (Task* p = t->getParent(); p; p = p->getParent()) {
if (allFollowers.indexOf(p) >= 0 && ignoreList.indexOf(p) < 0)
ignoreList.append(p);
}
}
/* Now we can check the paths through the remaining followers. */
for (TaskListIterator tli(allFollowers); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if (ignoreList.indexOf(t) >= 0 ||
transientFollowers.indexOf(t) >= 0)
continue;
if (DEBUGPA(16))
qDebug()<<QString(" > Follower check started for %1").arg(t->id);
if (t->analyzePath(sc, minSlack, pathStart, busyTime,
worstMinSlackTime, checks, found))
{
if (scenarios[sc].criticalLinks.indexOf(t) < 0)
{
if (DEBUGPA(5))
qDebug()<<QString(" +++ Critical link %1 -> %2").arg(name).arg(t->id);
scenarios[sc].criticalLinks.append(t);
}
critical = true;
}
if (DEBUGPA(16))
qDebug()<<" < Follower check finished for"<<id;
}
if (allFollowers.isEmpty())
{
// We've reached the end of a path. Now let's see if it's critical.
long overallDuration = getEnd(sc) + 1 - pathStart;
/* A path is considered critical if the ratio of busy time and
* overall path time is above the minSlack threshold and the path
* contains more than one task. */
critical = overallDuration > 0 &&
((double) busyTime / overallDuration) > (1.0 - minSlack);
if (critical)
{
found++;
if (DEBUGPA(5))
qDebug()<<"Critical path with"<<(100.0 - ((double) busyTime / overallDuration) * 100.0)<<"% slack ending at"<<id<<"found";
}
else
{
if (DEBUGPA(11))
qDebug()<<"Path ending at"<<id<<"is not critical";
}
if (++checks == maxPaths)
{
warningMessage(QString("Maximum number of paths reached during "
"critical path analysis. Set 'maxPaths' "
"to 0 if you want an exhaustive search. "
"Aborting critical paths detection."));
return false;
}
if (checks % 100000 == 0 && DEBUGPA(1))
qDebug()<<"Already check"<<checks<<"paths."<<found<<"critical found.";
}
}
if (critical)
scenarios[sc].isOnCriticalPath = true;
if (DEBUGPA(14))
qDebug()<<" - Check of task"<<id<<"completed ("<<checks<<")";
return critical;
}
void
Task::collectTransientFollowers(TaskList& list)
{
if (hasSubs())
{
for (TaskListIterator tli(followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (list.indexOf(t) < 0)
{
list.append(t);
t->collectTransientFollowers(list);
}
}
}
else
{
for (Task* task = getParent(); task; task = task->getParent()) {
for (TaskListIterator tli(task->followers); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (list.indexOf(t) < 0)
{
list.append(t);
t->collectTransientFollowers(list);
}
}
}
}
}
void
Task::finishScenario(int sc)
{
scenarios[sc].start = start;
scenarios[sc].end = end;
scenarios[sc].bookedResources = bookedResources;
scenarios[sc].scheduled = schedulingDone;
// qDebug()<<"finishScenario: '"<< name << "' finish: " << time2ISO(start) << " - " << time2ISO(end);
}
void
Task::computeBuffers()
{
int sg = project->getScheduleGranularity();
for (int sc = 0; sc < project->getMaxScenarios(); sc++)
{
scenarios[sc].startBufferEnd = scenarios[sc].start - 1;
scenarios[sc].endBufferStart = scenarios[sc].end + 1;
if (scenarios[sc].start == 0 || scenarios[sc].end == 0)
{
scenarios[sc].startBufferEnd = scenarios[sc].endBufferStart = 0;
continue;
}
if (duration > 0.0)
{
if (scenarios[sc].startBuffer > 0.0)
scenarios[sc].startBufferEnd = scenarios[sc].start +
static_cast<time_t>((scenarios[sc].end -
scenarios[sc].start) *
scenarios[sc].startBuffer / 100.0);
if (scenarios[sc].endBuffer > 0.0)
scenarios[sc].endBufferStart = scenarios[sc].end -
static_cast<time_t>((scenarios[sc].end -
scenarios[sc].start) *
scenarios[sc].endBuffer / 100.0);
}
else if (length > 0.0)
{
double l;
if (scenarios[sc].startBuffer > 0.0)
{
for (l = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
scenarios[sc].startBufferEnd += sg)
{
if (project->isWorkingDay(scenarios[sc].startBufferEnd))
l += (double) sg / ONEDAY;
if (l >= scenarios[sc].length *
scenarios[sc].startBuffer / 100.0)
break;
}
}
if (scenarios[sc].endBuffer > 0.0)
{
for (l = 0.0; scenarios[sc].endBufferStart >
scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
{
if (project->isWorkingDay(scenarios[sc].endBufferStart))
l += (double) sg / ONEDAY;
if (l >= scenarios[sc].length *
scenarios[sc].endBuffer / 100.0)
break;
}
}
}
else if (effort > 0.0)
{
double e;
if (scenarios[sc].startBuffer > 0.0)
{
for (e = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
scenarios[sc].startBufferEnd += sg)
{
e += getLoad(sc,
Interval(scenarios[sc].startBufferEnd,
scenarios[sc].startBufferEnd + sg));
if (e >= scenarios[sc].effort *
scenarios[sc].startBuffer / 100.0)
break;
}
}
if (scenarios[sc].endBuffer > 0.0)
{
for (e = 0.0; scenarios[sc].endBufferStart >
scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
{
e += getLoad(sc,
Interval(scenarios[sc].endBufferStart - sg,
scenarios[sc].endBufferStart));
if (e >= scenarios[sc].effort *
scenarios[sc].endBuffer / 100.0)
break;
}
}
}
}
}
void
Task::calcCompletionDegree(int sc)
{
time_t now = project->getNow();
/* In-progress container task are pretty complex to deal with. The mixture
* of effort, length and duration tasks makes it impossible to use one
* coherent criteria to determine the progress of a task. */
if (isContainer() && scenarios[sc].start < now && now <= scenarios[sc].end)
calcContainerCompletionDegree(sc, now);
else
/* Calc completion for simple tasks and determine the task state. */
scenarios[sc].calcCompletionDegree(now);
}
double
Task::getCompletionDegree(int sc) const
{
if(scenarios[sc].reportedCompletion >= 0.0)
return(scenarios[sc].reportedCompletion);
return isContainer() && scenarios[sc].containerCompletion >= 0.0 ?
scenarios[sc].containerCompletion : scenarios[sc].completionDegree;
}
double
Task::getCalcedCompletionDegree(int sc) const
{
return scenarios[sc].completionDegree;
}
void
Task::calcContainerCompletionDegree(int sc, time_t now)
{
assert(isContainer());
assert(scenarios[sc].start < now && now <= scenarios[sc].end);
scenarios[sc].status = InProgress;
int totalMilestones = 0;
int completedMilestones = 0;
int reportedCompletedMilestones = 0;
if (countMilestones(sc, now, totalMilestones, completedMilestones,
reportedCompletedMilestones))
{
scenarios[sc].completionDegree = completedMilestones * 100.0 /
totalMilestones;
scenarios[sc].containerCompletion = reportedCompletedMilestones
* 100.0 / totalMilestones;
return;
}
double totalEffort = 0.0;
double completedEffort = 0.0;
double reportedCompletedEffort = 0.0;
if (sumUpEffort(sc, now, totalEffort, completedEffort,
reportedCompletedEffort))
{
scenarios[sc].completionDegree = completedEffort * 100.0 /
totalEffort;
scenarios[sc].containerCompletion = reportedCompletedEffort * 100.0 /
totalEffort;
}
else
{
/* We can't determine the completion degree for mixed work/non-work
* tasks. So we use -1.0 as "in progress" value. */
double comp = -1.0;
if (scenarios[sc].start > now)
comp = 0.0; // not yet started
else if (scenarios[sc].end < now)
comp = 100.0; // completed
scenarios[sc].completionDegree =
scenarios[sc].containerCompletion = comp;
}
}
double
Task::getCompletedLoad(int sc) const
{
return getLoad(sc, Interval(project->getStart(), project->getEnd())) *
getCompletionDegree(sc) / 100.0;
}
double
Task::getRemainingLoad(int sc) const
{
return getLoad(sc, Interval(project->getStart(), project->getEnd())) *
(1.0 - getCompletionDegree(sc) / 100.0);
}
bool
Task::countMilestones(int sc, time_t now, int& totalMilestones,
int& completedMilestones,
int& reportedCompletedMilestones)
{
if (isContainer())
{
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (!t->countMilestones(sc, now, totalMilestones,
completedMilestones,
reportedCompletedMilestones))
return false;
}
/* A reported completion for a container always overrides the computed
* completion. */
if (scenarios[sc].reportedCompletion >= 0.0)
reportedCompletedMilestones = static_cast<int>(totalMilestones *
scenarios[sc].reportedCompletion / 100.0);
return true;
}
else if (isMilestone())
{
totalMilestones++;
if (scenarios[sc].start <= now)
completedMilestones++;
if (scenarios[sc].reportedCompletion >= 100.0)
reportedCompletedMilestones++;
else
if (scenarios[sc].start <= now)
reportedCompletedMilestones++;
return true;
}
return false;
}
bool
Task::sumUpEffort(int sc, time_t now, double& totalEffort,
double& completedEffort, double& reportedCompletedEffort)
{
if (isContainer())
{
for (TaskListIterator tli(*sub); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (!t->sumUpEffort(sc, now, totalEffort, completedEffort,
reportedCompletedEffort))
return false;
}
/* A reported completion for a container always overrides the computed
* completion. */
if (scenarios[sc].reportedCompletion >= 0.0)
reportedCompletedEffort = totalEffort *
scenarios[sc].reportedCompletion / 100.0;
return true;
}
if (scenarios[sc].effort > 0.0)
{
/* Pure effort based tasks are simple to handle. The total effort is
* specified and the effort up to 'now' can be computed. */
totalEffort += scenarios[sc].effort;
double load = getLoad(sc, Interval(scenarios[sc].start, now));
if (scenarios[sc].start < now)
completedEffort += load;
/* If the user reported a completion we use this instead of the
* calculated completion. */
if (scenarios[sc].reportedCompletion >= 0.0)
reportedCompletedEffort +=
getLoad(sc, Interval(scenarios[sc].start, scenarios[sc].end)) *
scenarios[sc].reportedCompletion / 100.0;
else
reportedCompletedEffort += load;
return true;
}
if (!allocations.isEmpty())
{
/* This is for length and duration tasks that have allocations. We
* handle them similar to effort tasks. Since there is no specified
* total effort, we calculate the total allocated effort. */
double totalLoad = getLoad(sc, Interval(scenarios[sc].start,
scenarios[sc].end));
totalEffort += totalLoad;
double load = getLoad(sc, Interval(scenarios[sc].start, now));
if (scenarios[sc].start < now)
completedEffort += load;
/* If the user reported a completion we use this instead of the
* calculated completion. */
if (scenarios[sc].reportedCompletion >= 0.0)
reportedCompletedEffort += totalLoad *
scenarios[sc].reportedCompletion / 100.0;
else
reportedCompletedEffort += load;
return true;
}
if (isMilestone())
{
/* We assume that milestones are only dependent on sub tasks of this
* tasks. So we can ignore them for the completion degree. In case
* there is a non completed task that the milestone depends on, the
* milestone is accounted for as well. This approximation should work
* fine for most real world projects. */
return true;
}
return false;
}
QDomElement Task::xmlElement( QDomDocument& doc, bool /* absId */ )
{
QDomElement taskElem = doc.createElement( "Task" );
/* QDomElement tempElem;
QString idStr = getId();
if( !absId )
idStr = idStr.section( '.', -1 );
taskElem.setAttribute( "Id", idStr );
QDomText t;
taskElem.appendChild( ReportXML::createXMLElem( doc, "Index", QString::number(getIndex()) ));
taskElem.appendChild( ReportXML::createXMLElem( doc, "Name", getName() ));
taskElem.appendChild( ReportXML::createXMLElem( doc, "ProjectID", projectId ));
taskElem.appendChild( ReportXML::createXMLElem( doc, "Priority", QString::number(getPriority())));
double cmplt = getCompletionDegree(0);
taskElem.appendChild( ReportXML::createXMLElem( doc, "complete", QString::number(cmplt, 'f', 1) ));
QString tType = "Milestone";
if( !isMilestone() )
{
if( isContainer() )
tType = "Container";
else
tType = "Task";
}
taskElem.appendChild( ReportXML::createXMLElem( doc, "Type", tType ));
CoreAttributes *parent = getParent();
if( parent )
taskElem.appendChild( ReportXML::ReportXML::createXMLElem( doc, "ParentTask", parent->getId()));
if( !note.isEmpty())
taskElem.appendChild( ReportXML::createXMLElem( doc, "Note", getNote()));
if(!ref.isEmpty())
taskElem.appendChild(ReportXML::createXMLElem(doc, "Reference",
ref));
if(!refLabel.isEmpty())
taskElem.appendChild(ReportXML::createXMLElem(doc, "ReferenceLabel",
refLabel));
if (scenarios[0].minStart != 0)
{
tempElem = ReportXML::createXMLElem
( doc, "minStart", QString::number(scenarios[0].minStart));
tempElem.setAttribute( "humanReadable",
time2ISO(scenarios[0].minStart));
taskElem.appendChild( tempElem );
}
if (scenarios[0].maxStart != 0)
{
tempElem = ReportXML::createXMLElem
(doc, "maxStart", QString::number(scenarios[0].maxStart));
tempElem.setAttribute( "humanReadable",
time2ISO(scenarios[0].maxStart));
taskElem.appendChild( tempElem );
}
if (scenarios[0].minEnd != 0)
{
tempElem = ReportXML::createXMLElem
(doc, "minEnd", QString::number(scenarios[0].minEnd));
tempElem.setAttribute( "humanReadable",
time2ISO(scenarios[0].minEnd));
taskElem.appendChild( tempElem );
}
if (scenarios[0].maxEnd != 0)
{
tempElem = ReportXML::createXMLElem
(doc, "maxEnd", QString::number(scenarios[0].maxEnd));
tempElem.setAttribute( "humanReadable",
time2ISO(scenarios[0].maxEnd));
taskElem.appendChild( tempElem );
}
if (project->getMaxScenarios() > 1)
{
tempElem = ReportXML::createXMLElem( doc, "actualStart",
QString::number(scenarios[1].start));
tempElem.setAttribute( "humanReadable",
time2ISO(scenarios[1].start));
taskElem.appendChild( tempElem );
tempElem = ReportXML::createXMLElem( doc, "actualEnd",
QString::number(scenarios[1].end + 1));
tempElem.setAttribute( "humanReadable",
time2ISO(scenarios[1].end + 1));
taskElem.appendChild( tempElem );
}
tempElem = ReportXML::createXMLElem( doc, "planStart", QString::number( scenarios[0].start ));
tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].start ));
taskElem.appendChild( tempElem );
tempElem = ReportXML::createXMLElem( doc, "planEnd",
QString::number(scenarios[0].end + 1));
tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].end + 1));
taskElem.appendChild( tempElem );
// Start- and Endbuffer
if(getStartBuffer(0) > 0.01)
{
// startbuffer exists
tempElem = ReportXML::createXMLElem
(doc, "startBufferSize",
QString::number(getStartBuffer(0)));
taskElem.appendChild( tempElem );
tempElem = ReportXML::createXMLElem
(doc, "PlanStartBufferEnd",
QString::number(getStartBufferEnd(0)));
tempElem.setAttribute("humanReadable",
time2ISO(getStartBufferEnd(0)));
taskElem.appendChild(tempElem);
tempElem = ReportXML::createXMLElem
(doc, "PlanStartBufferEnd",
QString::number(getStartBufferEnd(0)));
tempElem.setAttribute("humanReadable",
time2ISO(getStartBufferEnd(0)));
taskElem.appendChild(tempElem);
}
if(getEndBuffer(0) > 0.01)
{
// startbuffer exists
tempElem = ReportXML::createXMLElem
(doc, "EndBufferSize", QString::number(getEndBuffer(0)));
taskElem.appendChild(tempElem);
tempElem = ReportXML::createXMLElem
(doc, "PlanEndBufferStart",
QString::number(getEndBufferStart(0)));
tempElem.setAttribute("humanReadable",
time2ISO(getEndBufferStart(0)));
taskElem.appendChild(tempElem);
tempElem = ReportXML::createXMLElem
(doc, "PlanEndBufferStart",
QString::number(getEndBufferStart(0)));
tempElem.setAttribute("humanReadable",
time2ISO(getStartBufferEnd(0)));
taskElem.appendChild(tempElem);
}
// Responsible persons
if( getResponsible() )
taskElem.appendChild( getResponsible()->xmlIDElement( doc ));
// Now start the subtasks
int cnt = 0;
QDomElement subTaskElem = doc.createElement( "SubTasks" );
for (Task* t = subFirst(); t != 0; t = subNext())
{
if( t != this )
{
QDomElement sTask = t->xmlElement( doc, false );
subTaskElem.appendChild( sTask );
cnt++;
}
}
if( cnt > 0 )
taskElem.appendChild( subTaskElem);
// list of tasks by id which are previous
if( previous.count() > 0 )
{
for (TaskListIterator tli(previous); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if( *tli != this )
{
taskElem.appendChild( ReportXML::createXMLElem( doc, "Previous",
t->getId()));
}
}
}
// list of tasks by id which follow
if( followers.count() > 0 )
{
for (TaskListIterator tli(followers); tli.hasNext();)
{
Task *t = static_cast<Task*>(tli.next());
if( t != this )
{
taskElem.appendChild( ReportXML::createXMLElem( doc, "Follower",
(t)->getId()));
}
}
}
** Allocations and Booked Resources
* With the following code, the task in XML contains simply a list of all Allocations
* wiht the ResourceID for which resource the allocation is. After that, there comes
* a list of all Resources, again having the Resource Id as key. That could be put
* in a hierarchy like
* <Resource Id="dev2" >Larry Bono
* <Income>1000</Income>
* <Allocation>
* <Load>100</Load>
* <Persistent>Yes</Persistent>
* </Allocation>
* </Resource>
*
* But we do not ;-) to have full flexibility.
*
*
// Allocations
if( allocations.count() > 0 )
{
QPtrList<Allocation> al(allocations);
for (QListIterator<Allocation*> ali(al); ali.hasNext();)
{
Allocation *a = static_cast<Allocation*>(ali.next());
taskElem.appendChild( a->xmlElement( doc ));
}
}
// booked Resources
if( bookedResources.count() > 0 )
{
for (ResourceListIterator rli(bookedResources); *rli != 0; ++rli)
{
taskElem.appendChild( r->xmlIDElement( doc ));
}
}
*/
return( taskElem );
}
bool
Task::isOrHasDescendantOnCriticalPath(int sc) const
{
if (isOnCriticalPath(sc, false))
return true;
if (isContainer())
{
for (TaskListIterator tli = getSubListIterator(); tli.hasNext();) {
Task *t = static_cast<Task*>(tli.next());
if (t->isOrHasDescendantOnCriticalPath(sc))
return true;
}
}
return false;
}
} // namespace TJ
QDebug operator<<( QDebug dbg, const TJ::Task* t )
{
if ( t == 0 ) {
return dbg << (void*)t;
}
return operator<<( dbg, *t );
}
QDebug operator<<( QDebug dbg, const TJ::Task& t )
{
dbg << (t.isMilestone() ? "Milestone[" : "Task[");
dbg << t.getName() << (t.getScheduling() == TJ::Task::ASAP ? "(ASAP)" : "(ALAP)");
if ( t.isSchedulingDone() ) {
dbg << "Scheduled";
} else if ( t.isReadyForScheduling() ) {
dbg << "ReadyForScheduling";
} else if ( t.isRunaway() ) {
dbg << "Runaway";
}
dbg << "]";
return dbg;
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/TaskDependency.cpp b/src/plugins/schedulers/tj/taskjuggler/TaskDependency.cpp
index 3884cd0a..5bb2376b 100644
--- a/src/plugins/schedulers/tj/taskjuggler/TaskDependency.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/TaskDependency.cpp
@@ -1,85 +1,86 @@
/*
* TaskDependency.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "TaskDependency.h"
#include <assert.h>
#include "Project.h"
#include "Task.h"
#include "Scenario.h"
namespace TJ
{
TaskDependency::TaskDependency(QString tri, int maxScenarios) :
taskRefId(tri),
taskRef(0),
gapDuration(new long[maxScenarios]),
gapLength(new long[maxScenarios])
{
for (int sc = 0; sc < maxScenarios; ++sc)
gapDuration[sc] = gapLength[sc] = (sc == 0 ? 0 : -1);
}
TaskDependency::~TaskDependency()
{
delete [] gapDuration;
delete [] gapLength;
}
long
TaskDependency::getGapDuration(int sc) const
{
for ( ; ; )
{
if (gapDuration[sc] >= 0)
return gapDuration[sc];
Project* p = taskRef->getProject();
Scenario* parent = p->getScenario(sc)->getParent();
assert(parent);
sc = p->getScenarioIndex(parent->getId()) - 1;
}
}
long
TaskDependency::getGapLength(int sc) const
{
for ( ; ; )
{
if (gapLength[sc] >= 0)
return gapLength[sc];
Project* p = taskRef->getProject();
Scenario* parent = p->getScenario(sc)->getParent();
assert(parent);
sc = p->getScenarioIndex(parent->getId()) - 1;
}
}
} // namespace TJ
QDebug operator<<( QDebug dbg, const TJ::TaskDependency *dep )
{
return dep == 0 ? (dbg<<0x000000) : operator<<( dbg, *dep );
}
QDebug operator<<( QDebug dbg, const TJ::TaskDependency &dep )
{
dbg<<"TaskDependency[";
if ( dep.getTaskRef() ) {
dbg.nospace()<<"ref="<<dep.getTaskRef()->getId();
} else {
dbg.nospace()<<"id="<<dep.getTaskRefId();
}
dbg << ']';
return dbg;
}
diff --git a/src/plugins/schedulers/tj/taskjuggler/TaskList.cpp b/src/plugins/schedulers/tj/taskjuggler/TaskList.cpp
index 9cf019ab..06537507 100644
--- a/src/plugins/schedulers/tj/taskjuggler/TaskList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/TaskList.cpp
@@ -1,191 +1,192 @@
/*
* TaskList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "TaskList.h"
#include "Resource.h"
namespace TJ
{
Task*
TaskList::getTask(const QString& id) const
{
for (TaskListIterator tli(*this); *tli != 0; ++tli)
if ((*tli)->getId() == id)
return *tli;
return 0;
}
bool
TaskList::isSupportedSortingCriteria(int sc)
{
switch (sc & 0xFFFF)
{
case TreeMode:
case StartUp:
case StartDown:
case EndUp:
case EndDown:
case StatusUp:
case StatusDown:
case CompletedUp:
case CompletedDown:
case PrioUp:
case PrioDown:
case ResponsibleUp:
case ResponsibleDown:
case CriticalnessUp:
case CriticalnessDown:
case PathCriticalnessUp:
case PathCriticalnessDown:
return true;
default:
return CoreAttributesList::isSupportedSortingCriteria(sc);
}
}
int
TaskList::compareItemsLevel(CoreAttributes* c1, CoreAttributes* c2, int level)
{
Task* t1 = static_cast<Task*>(c1);
Task* t2 = static_cast<Task*>(c2);
if (level < 0 || level >= maxSortingLevel)
return -1;
switch (sorting[level])
{
case TreeMode:
if (level == 0)
return compareTreeItemsT(this, t1, t2);
else
return t1->getSequenceNo() == t2->getSequenceNo() ? 0 :
t1->getSequenceNo() < t2->getSequenceNo() ? -1 : 1;
case StartUp:
return t1->scenarios[sortScenario].start ==
t2->scenarios[sortScenario].start ? 0 :
t1->scenarios[sortScenario].start <
t2->scenarios[sortScenario].start ? -1 : 1;
case StartDown:
return t1->scenarios[sortScenario].start ==
t2->scenarios[sortScenario].start ? 0 :
t1->scenarios[sortScenario].start >
t2->scenarios[sortScenario].start ? -1 : 1;
case EndUp:
return t1->scenarios[sortScenario].end ==
t2->scenarios[sortScenario].end ? 0 :
t1->scenarios[sortScenario].end < t2->scenarios[sortScenario].end
? -1 : 1;
case EndDown:
return t1->scenarios[sortScenario].end ==
t2->scenarios[sortScenario].end ? 0 :
t1->scenarios[sortScenario].end > t2->scenarios[sortScenario].end
? -1 : 1;
case StatusUp:
return t1->scenarios[sortScenario].status ==
t2->scenarios[sortScenario].status ? 0 :
t1->scenarios[sortScenario].status <
t2->scenarios[sortScenario].status ? -1 : 1;
case StatusDown:
return t1->scenarios[sortScenario].status ==
t2->scenarios[sortScenario].status ? 0 :
t1->scenarios[sortScenario].status >
t2->scenarios[sortScenario].status ? -1 : 1;
case CompletedUp:
{
/* Unfortunately the floating point arithmetic on x86 processors is
* slightly different from other processors. To get identical
* results on all CPUs we ignore some precision that we don't need
* anyhow. */
int cd1 = static_cast<int> (t1->getCompletionDegree(sortScenario) *
1000);
int cd2 = static_cast<int> (t2->getCompletionDegree(sortScenario) *
1000);
return cd1 == cd2 ? 0 : cd1 < cd2 ? -1 : 1;
}
case CompletedDown:
{
int cd1 = static_cast<int> (t1->getCompletionDegree(sortScenario) *
1000);
int cd2 = static_cast<int> (t2->getCompletionDegree(sortScenario) *
1000);
return cd1 == cd2 ? 0 : cd1 > cd2 ? -1 : 1;
}
case PrioUp:
if (t1->priority == t2->priority)
{
if (t1->scheduling == t2->scheduling)
return 0;
else if (t1->scheduling == Task::ASAP)
return -1;
}
else
return (t1->priority - t2->priority);
Q_FALLTHROUGH();
case PrioDown:
if (t1->priority == t2->priority)
{
if (t1->scheduling == t2->scheduling)
return 0;
else if (t1->scheduling == Task::ASAP)
return 1;
}
else
return (t2->priority - t1->priority);
Q_FALLTHROUGH();
case ResponsibleUp:
{
QString fn1;
t1->responsible->getFullName(fn1);
QString fn2;
t2->responsible->getFullName(fn2);
return fn1.compare(fn2);
}
case ResponsibleDown:
{
QString fn1;
t1->responsible->getFullName(fn1);
QString fn2;
t2->responsible->getFullName(fn2);
return -fn1.compare(fn2);
}
case CriticalnessUp:
return t1->scenarios[sortScenario].criticalness ==
t2->scenarios[sortScenario].criticalness ? 0 :
t1->scenarios[sortScenario].criticalness <
t2->scenarios[sortScenario].criticalness ? -1 : 1;
case CriticalnessDown:
return t1->scenarios[sortScenario].criticalness ==
t2->scenarios[sortScenario].criticalness ? 0 :
t1->scenarios[sortScenario].criticalness >
t2->scenarios[sortScenario].criticalness ? -1 : 1;
case PathCriticalnessUp:
return t1->scenarios[sortScenario].pathCriticalness ==
t2->scenarios[sortScenario].pathCriticalness ? 0 :
t1->scenarios[sortScenario].pathCriticalness <
t2->scenarios[sortScenario].pathCriticalness ? -1 : 1;
case PathCriticalnessDown:
return t1->scenarios[sortScenario].pathCriticalness ==
t2->scenarios[sortScenario].pathCriticalness ? 0 :
t1->scenarios[sortScenario].pathCriticalness >
t2->scenarios[sortScenario].pathCriticalness ? -1 : 1;
default:
return CoreAttributesList::compareItemsLevel(t1, t2, level);
}
}
Task* TaskListIterator::operator*()
{
return static_cast<Task*>(CoreAttributesListIterator::operator*());
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/TaskScenario.cpp b/src/plugins/schedulers/tj/taskjuggler/TaskScenario.cpp
index cbcbedfa..e5e78954 100644
--- a/src/plugins/schedulers/tj/taskjuggler/TaskScenario.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/TaskScenario.cpp
@@ -1,112 +1,113 @@
/*
* TaskScenario.h - TaskJuggler
*
* Copyright (c) 2002 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "TaskScenario.h"
#include "Resource.h"
#include "ResourceTreeIterator.h"
#include "Project.h"
namespace TJ
{
TaskScenario::TaskScenario() :
task(0),
index(0),
specifiedStart(0),
specifiedEnd(0),
start(0),
end(0),
minStart(0),
maxStart(0),
minEnd(0),
maxEnd(0),
startBuffer(-1.0),
endBuffer(-1.0),
startBufferEnd(0),
endBufferStart(0),
duration(0.0),
length(0.0),
effort(0.0),
startCredit(-1.0),
endCredit(-1.0),
criticalness(0.0),
pathCriticalness(0.0),
isOnCriticalPath(false),
reportedCompletion(-1.0),
containerCompletion(-1.0),
completionDegree(0.0),
status(Undefined),
statusNote(),
specifiedScheduled(false),
scheduled(false),
startCanBeDetermined(false),
endCanBeDetermined(false),
specifiedBookedResources(),
bookedResources(),
criticalLinks()
{
qDebug()<<"TaskScenario:"<<this;
}
void
TaskScenario::calcCompletionDegree(time_t now)
{
if (now > end)
{
completionDegree = 100.0;
status = reportedCompletion >= 0 && reportedCompletion < 100 ?
Late : Finished;
}
else if (now <= start)
{
completionDegree = 0.0;
status = reportedCompletion > 0 ? InProgressEarly : NotStarted;
}
else
{
status = OnTime;
if (effort > 0.0)
{
completionDegree = (100.0 / effort) *
task->getLoad(index, Interval(start, now));
}
else if (length > 0.0)
{
completionDegree = (100.0 /
task->getProject()->calcWorkingDays(Interval(start, end))) *
task->getProject()->calcWorkingDays(Interval(start, now));
}
else
completionDegree = (100.0 / (end - start + 1)) * (now - start);
if (reportedCompletion >= 0.0)
{
if (reportedCompletion < completionDegree)
status = InProgressLate;
else if (reportedCompletion > completionDegree)
status = InProgressEarly;
}
}
}
bool TaskScenario::isDutyOf(const Resource* r) const
{
for (ConstResourceTreeIterator rti(r); *rti; ++rti)
if (bookedResources.contains
(const_cast<CoreAttributes*>(static_cast<const CoreAttributes*>(*rti))))
return true;
return false;
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/TjMessageHandler.cpp b/src/plugins/schedulers/tj/taskjuggler/TjMessageHandler.cpp
index adca519a..851db739 100644
--- a/src/plugins/schedulers/tj/taskjuggler/TjMessageHandler.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/TjMessageHandler.cpp
@@ -1,109 +1,110 @@
/*
* TjMessageHandler.h - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007
* by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "TjMessageHandler.h"
#include "taskjuggler.h"
#include "Utility.h"
#include <QDebug>
namespace TJ
{
TjMessageHandler TJMH(false);
void
TjMessageHandler::warningMessage(const QString& msg, const CoreAttributes *object )
{
warningMessage( msg, QString() );
emit message( (int)TJ::WarningMsg, msg, const_cast<CoreAttributes*>(object));
}
void
TjMessageHandler::warningMessage(const QString& msg, const QString& file, int
line)
{
warnings++;
warningPositions << messages.count();
messages << msg;
if (consoleMode)
{
if (file.isEmpty())
qWarning()<<msg;
else
qWarning()<<file<<":"<<line<<":"<<msg;
}
else
printWarning(msg, file, line);
}
void
TjMessageHandler::errorMessage( const QString& msg, const CoreAttributes *object )
{
errorMessage( msg, QString() );
emit message( (int)TJ::ErrorMsg, msg, const_cast<CoreAttributes*>(object) );
}
void
TjMessageHandler::errorMessage(const QString& msg, const QString& file, int
line)
{
errors++;
errorPositions << messages.count();
messages << msg;
if (consoleMode)
{
if (file.isEmpty())
qWarning()<<msg;
else
qWarning()<<file<<":"<<line<<":"<<msg;
}
else
printError(msg, file, line);
}
void
TjMessageHandler::fatalMessage(const QString& msg, const QString& file, int
line)
{
if (consoleMode)
{
if (file.isEmpty())
qWarning()<<msg;
else
qWarning()<<file<<":"<<line<<":"<<msg;
}
else
printFatal(msg, file, line);
}
void
TjMessageHandler::infoMessage( const QString &msg, const CoreAttributes *object )
{
++infos;
infoPositions << messages.count();
messages << msg;
emit message( (int)TJ::InfoMsg, msg, const_cast<CoreAttributes*>(object) );
}
void
TjMessageHandler::debugMessage( const QString &msg, const CoreAttributes *object )
{
++debugs;
debugPositions << messages.count();
messages << msg;
emit message( (int)TJ::DebugMsg, msg, const_cast<CoreAttributes*>(object) );
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/Utility.cpp b/src/plugins/schedulers/tj/taskjuggler/Utility.cpp
index 7c3cdad8..93945968 100644
--- a/src/plugins/schedulers/tj/taskjuggler/Utility.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/Utility.cpp
@@ -1,962 +1,963 @@
/*
* Utility.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
* Copyright (c) 2011 by Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "Utility.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <QLocale>
#include <QMap>
#include "TjMessageHandler.h"
#include "tjlib-internal.h"
#if defined(__SVR4) && defined(__sun)
int setenv(const char* var, const char* val, int ignore);
int unsetenv (const char *var);
#endif
#if _MSC_VER >= 1900
#define tzname _tzname
#define tzset _tzset
#endif
namespace TJ
{
static QMap<QString, const char*> TZDict;
static bool TZDictReady = false;
static QString UtilityError;
/* localtime() calls are fairly expensive, so we implement a hashtable based
* cache to avoid redundant calls for the same value. Changing the timezone
* invalidates the cache though. */
class LtHashTabEntry
{
public:
LtHashTabEntry() : tms( 0 ), next( 0 ) {}
~LtHashTabEntry() { /*qDebug()<<"~LtHashTabEntry";*/ delete tms; }
time_t t;
struct tm* tms;
LtHashTabEntry* next;
};
static long LTHASHTABSIZE;
static LtHashTabEntry** LtHashTab = 0;
bool
isRichText(const QString& str)
{
/* This function tries to guess whether a string is a rich-text string or
* not. It looks for xml tags marks and does a simple validation on them.
*/
bool hasTags = false;
bool inTag = false;
for (int i = 0; i < str.length(); ++i)
{
if (str[i] == '<')
{
if (inTag)
return false;
inTag = hasTags = true;
}
else if (str[i] == '>')
{
if (!inTag)
return false;
inTag = false;
}
}
return hasTags && !inTag;
}
const char*
timezone2tz(const char* tzone)
{
if (!TZDictReady)
{
// TZDict.setAutoDelete(false);
// Let's start with generic timezones
TZDict.insert("+1300", "GMT-13:00");
TZDict.insert("+1200", "GMT-12:00");
TZDict.insert("+1100", "GMT-11:00");
TZDict.insert("+1000", "GMT-10:00");
TZDict.insert("+0900", "GMT-9:00");
TZDict.insert("+0800", "GMT-8:00");
TZDict.insert("+0700", "GMT-7:00");
TZDict.insert("+0600", "GMT-6:00");
TZDict.insert("+0500", "GMT-5:00");
TZDict.insert("+0400", "GMT-4:00");
TZDict.insert("+0300", "GMT-3:00");
TZDict.insert("+0200", "GMT-2:00");
TZDict.insert("+0100", "GMT-1:00");
TZDict.insert("+0000", "GMT-0:00");
TZDict.insert("-0100", "GMT+1:00");
TZDict.insert("-0200", "GMT+2:00");
TZDict.insert("-0300", "GMT+3:00");
TZDict.insert("-0400", "GMT+4:00");
TZDict.insert("-0500", "GMT+5:00");
TZDict.insert("-0600", "GMT+6:00");
TZDict.insert("-0700", "GMT+7:00");
TZDict.insert("-0800", "GMT+8:00");
TZDict.insert("-0900", "GMT+9:00");
TZDict.insert("-1000", "GMT+10:00");
TZDict.insert("-1100", "GMT+11:00");
TZDict.insert("-1200", "GMT+12:00");
// Now some convenience timezones. There will be more in the future.
TZDict.insert("PST", "GMT+8:00");
TZDict.insert("PDT", "GMT+7:00");
TZDict.insert("MST", "GMT+7:00");
TZDict.insert("MDT", "GMT+6:00");
TZDict.insert("CST", "GMT+6:00");
TZDict.insert("CDT", "GMT+5:00");
TZDict.insert("EST", "GMT+5:00");
TZDict.insert("EDT", "GMT+4:00");
TZDict.insert("GMT", "GMT");
TZDict.insert("UTC", "GMT");
TZDict.insert("CET", "GMT-1:00");
TZDict.insert("CEDT", "GMT-2:00");
TZDictReady = true;
}
return TZDict[tzone];
}
void initUtility(long dictSize)
{
if (LtHashTab)
exitUtility();
/* Find a prime number that is equal or bigger than dictSize. */
for (long i = 2; i < (dictSize / 2); i++)
if (dictSize % i == 0)
{
dictSize++;
i = 1;
}
LtHashTab = new LtHashTabEntry*[LTHASHTABSIZE = dictSize];
for (long i = 0; i < LTHASHTABSIZE; ++i)
LtHashTab[i] = 0;
}
void exitUtility()
{
qDebug()<<"exitUtility:"<<LtHashTab;
if (!LtHashTab)
return;
qDebug()<<"exitUtility:"<<LTHASHTABSIZE;
for (long i = 0; i < LTHASHTABSIZE; ++i)
for (LtHashTabEntry* htep = LtHashTab[i]; htep; )
{
LtHashTabEntry* tmp = htep->next;
delete htep;
htep = tmp;
}
delete [] LtHashTab;
LtHashTab = 0;
}
bool
setTimezone(const char* tZone)
{
UtilityError.clear();
if (!qputenv("TZ", tZone))
qFatal("Ran out of space in environment section while "
"setting timezone.");
/* To validate the tZone value we call tzset(). It will convert the zone
* into a three-letter acronym in case the tZone value is good. If not, it
* will just copy the wrong value to tzname[0] (glibc < 2.5) or fall back
* to UTC. */
tzset();
if (timezone2tz(tZone) == 0 &&
(strcmp(tzname[0], tZone) == 0 ||
(strcmp(tZone, "UTC") != 0 && strcmp(tzname[0], "UTC") == 0)))
{
qDebug("1: %s, 2: %s", tzname[0], tzname[1]);
UtilityError = QString(QString("Illegal timezone '%1'")).arg(tZone);
return false;
}
if (!LtHashTab)
return true;
for (long i = 0; i < LTHASHTABSIZE; ++i)
{
for (LtHashTabEntry* htep = LtHashTab[i]; htep; )
{
LtHashTabEntry* tmp = htep->next;
delete htep->tms;
htep = tmp;
}
if (LtHashTab[i])
LtHashTab[i] = 0;
}
return true;
}
const struct tm *
clocaltime(const time_t* t)
{
/* In some cases we haven't initialized the module yet. So we do not use
* the cache. */
time_t tt = *t < 0 ? 0 : *t;
if (!LtHashTab)
return localtime(&tt);
long index = tt % LTHASHTABSIZE;
if (LtHashTab[index])
for (LtHashTabEntry* htep = LtHashTab[index]; htep;
htep = htep->next)
if (htep->t == tt)
return htep->tms;
LtHashTabEntry* htep = new LtHashTabEntry;
htep->next = LtHashTab[index];
htep->t = tt;
htep->tms = new struct tm;
memcpy(htep->tms, localtime(&tt), sizeof(struct tm));
LtHashTab[index] = htep;
return htep->tms;
}
const QString&
getUtilityError()
{
return UtilityError;
}
QString
monthAndYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char s[32];
strftime(s, sizeof(s), "%b %Y", tms);
return QString::fromLocal8Bit(s);
}
QString
shortMonthName(int mon)
{
struct tm tms;
tms.tm_mday = 1;
tms.tm_mon = mon;
tms.tm_year = 2000;
static char s[32];
strftime(s, sizeof(s), "%b", &tms);
return QString::fromLocal8Bit(s);
}
bool
isWeekend(time_t t)
{
const struct tm* tms = clocaltime(&t);
return (tms->tm_wday < 1 || tms->tm_wday > 5);
}
int
daysLeftInMonth(time_t t)
{
int left = 0;
const struct tm* tms = clocaltime(&t);
for (int m = tms->tm_mon; tms->tm_mon == m; )
{
left++;
t = sameTimeNextDay(t);
tms = clocaltime(&t);
}
return left;
}
int
weeksLeftInMonth(time_t t)
{
int left = 0;
const struct tm* tms = clocaltime(&t);
for (int m = tms->tm_mon; tms->tm_mon == m; )
{
left++;
t = sameTimeNextWeek(t);
tms = clocaltime(&t);
}
return left;
}
int
monthLeftInYear(time_t t)
{
int left = 0;
const struct tm* tms = clocaltime(&t);
for (int m = tms->tm_year; tms->tm_year == m; )
{
left++;
t = sameTimeNextMonth(t);
tms = clocaltime(&t);
}
return left;
}
int
quartersLeftInYear(time_t t)
{
int left = 0;
const struct tm* tms = clocaltime(&t);
for (int m = tms->tm_year; tms->tm_year == m; )
{
left++;
t = sameTimeNextQuarter(t);
tms = clocaltime(&t);
}
return left;
}
int
daysBetween(time_t t1, time_t t2)
{
int days = 0;
// TODO: Very slow!!!
for (time_t t = t1; t < t2; t = sameTimeNextDay(t))
days++;
return days;
}
int
weeksBetween(time_t t1, time_t t2)
{
int days = 0;
// TODO: Very slow!!!
for (time_t t = t1; t < t2; t = sameTimeNextWeek(t))
days++;
return days;
}
int
monthsBetween(time_t t1, time_t t2)
{
int months = 0;
// TODO: Very slow!!!
for (time_t t = t1; t < t2; t = sameTimeNextMonth(t))
months++;
return months;
}
int
quartersBetween(time_t t1, time_t t2)
{
int quarters = 0;
// TODO: Very slow!!!
for (time_t t = t1; t < t2; t = sameTimeNextQuarter(t))
quarters++;
return quarters;
}
int
secondsOfDay(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_sec + tms->tm_min * 60 + tms->tm_hour * 3600;
}
int
hourOfDay(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_hour;
}
int
dayOfMonth(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_mday;
}
int
weekOfYear(time_t t, bool beginOnMonday)
{
/* The ISO 8601:1988 week number of the current year as a decimal
* number, range 1 to 53, where week 1 is the first week that has at
* least 4 days in the current year, and with Monday as the first day
* of the week. This is also compliant with DIN 1355. */
uint week = 0;
uint weekday1Jan = dayOfWeek(beginOfYear(t), beginOnMonday);
const struct tm* tms = clocaltime(&t);
int days = tms->tm_yday;
if (weekday1Jan > 3)
days = days - (7 - weekday1Jan);
else
days = days + weekday1Jan;
if (days < 0)
if ((weekday1Jan == 4) ||
(dayOfWeek(beginOfYear(beginOfYear(t) - 1), beginOnMonday) == 3))
week = 53;
else
week = 52;
else
week = days / 7 + 1;
if ((days > 360) && (week > 52))
{
if (weekday1Jan == 3)
week = 53;
else if (dayOfWeek(sameTimeNextYear(beginOfYear(t)),
beginOnMonday) == 4)
week = 53;
else
week = 1;
}
return week;
}
int
monthOfWeek(time_t t, bool beginOnMonday)
{
const struct tm* tms = clocaltime(&t);
int tm_mon = tms->tm_mon;
int tm_mday = tms->tm_mday;
int lastDayOfMonth = dayOfMonth(beginOfMonth(sameTimeNextMonth(t)) - 1);
if (tm_mday < 4)
{
if (dayOfWeek(t, beginOnMonday) - tm_mday >= 3)
{
if (tm_mon == 0)
{
return 12;
}
else
{
return tm_mon;
}
}
}
else if (tm_mday > lastDayOfMonth - 4)
{
if (tm_mday - dayOfWeek(t, beginOnMonday) > lastDayOfMonth - 4)
{
if (tm_mon == 11)
{
return 1;
}
else
{
return tm_mon + 2;
}
}
}
return tm_mon + 1;
}
int
monthOfYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_mon + 1;
}
int
quarterOfYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_mon / 3 + 1;
}
int
dayOfWeek(time_t t, bool beginOnMonday)
{
const struct tm* tms = clocaltime(&t);
if (beginOnMonday)
return tms->tm_wday ? tms->tm_wday - 1 : 6;
else
return tms->tm_wday;
}
QString
dayOfWeekName(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char buf[64];
strftime(buf, 63, "%A", tms);
return QString::fromLocal8Bit(buf);
}
int
dayOfYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_yday + 1;
}
int
year(time_t t)
{
const struct tm* tms = clocaltime(&t);
return tms->tm_year + 1900;
}
int
yearOfWeek(time_t t, bool beginOnMonday)
{
const struct tm* tms = clocaltime(&t);
int tm_year = tms->tm_year;
int lastDayOfYear = dayOfYear(beginOfYear(sameTimeNextYear(t)) - 1);
if (dayOfYear(t) < 4)
{
if (dayOfWeek(t, beginOnMonday) - dayOfYear(t) >= 3)
return 1900 + tm_year - 1;
}
else if (dayOfYear(t) > lastDayOfYear - 4)
{
if (dayOfYear(t) - dayOfWeek(t, beginOnMonday) > lastDayOfYear - 4)
return 1900 + tm_year + 1;
}
return 1900 + tm_year;
}
time_t
beginOfHour(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_sec = tmc.tm_min = 0;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
midnight(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
beginOfWeek(time_t t, bool beginOnMonday)
{
const struct tm* tms;
for (tms = clocaltime(&t) ; tms->tm_wday != (beginOnMonday ? 1 : 0); )
{
t = sameTimeYesterday(t);
tms = clocaltime(&t);
}
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
beginOfMonth(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mday = 1;
tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
beginOfQuarter(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mon = (tmc.tm_mon / 3) * 3;
tmc.tm_mday = 1;
tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
beginOfYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mon = 0;
tmc.tm_mday = 1;
tmc.tm_sec = tmc.tm_min = tmc.tm_hour = 0;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
hoursLater(int h, time_t t)
{
// I hope this is correct under all circumstances.
return t + h * ONEHOUR;
}
time_t
sameTimeNextDay(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mday++;
tmc.tm_isdst = -1;
if (mktime(&tmc) == -1)
qFatal("Error at %s", time2ISO(t).toLatin1().constData());
return mktime(&tmc);
}
time_t
sameTimeYesterday(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mday--;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
sameTimeNextWeek(time_t t)
{
const struct tm* tms = clocaltime(&t);
int weekday = tms->tm_wday;
do
{
t = sameTimeNextDay(t);
tms = clocaltime(&t);
} while (tms->tm_wday != weekday);
return t;
}
time_t
sameTimeLastWeek(time_t t)
{
const struct tm* tms = clocaltime(&t);
int weekday = tms->tm_wday;
do
{
t = sameTimeYesterday(t);
tms = clocaltime(&t);
} while (tms->tm_wday != weekday);
return t;
}
time_t
sameTimeNextMonth(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mon++;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
sameTimeNextQuarter(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_mon += 3;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
sameTimeNextYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_year++;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
sameTimeLastYear(time_t t)
{
const struct tm* tms = clocaltime(&t);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_year--;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
QString
time2ISO(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char buf[128];
strftime(buf, 127, "%Y-%m-%d %H:%M:%S %Z", tms);
return QString::fromLocal8Bit(buf);
}
QString
time2tjp(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char buf[128];
strftime(buf, 127, "%Y-%m-%d-%H:%M:%S-%z", tms);
return QString::fromLocal8Bit(buf);
}
QString
time2user(time_t t, const QString& timeFormat, bool localtime)
{
if (t == 0)
return QString("undefined");
const struct tm* tms;
if (localtime)
tms = clocaltime(&t);
else
tms = gmtime(&t);
static char buf[128];
strftime(buf, 127, timeFormat.toLocal8Bit(), tms);
return QString::fromLocal8Bit(buf);
}
QString
time2time(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char buf[128];
strftime(buf, 127, "%H:%M %Z", tms);
return QString::fromLocal8Bit(buf);
}
QString
time2date(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char buf[128];
strftime(buf, 127, "%Y-%m-%d", tms);
return QString::fromLocal8Bit(buf);
}
QString
time2weekday(time_t t)
{
const struct tm* tms = clocaltime(&t);
static char buf[128];
strftime(buf, 127, "%A", tms);
return QString::fromLocal8Bit(buf);
}
time_t
addTimeToDate(time_t day, time_t hour)
{
day = midnight(day);
const struct tm* tms = clocaltime(&day);
struct tm tmc;
memcpy(&tmc, tms, sizeof(struct tm));
tmc.tm_hour = hour / (60 * 60);
tmc.tm_min = (hour / 60) % 60;
tmc.tm_sec = hour % 60;
tmc.tm_isdst = -1;
return mktime(&tmc);
}
time_t
date2time(const QString& date)
{
UtilityError.clear();
int y, m, d, hour, min, sec;
char tZone[64] = "";
std::string savedTZ;
bool restoreTZ = false;
if (sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d:%d-%s",
&y, &m, &d, &hour, &min, &sec, tZone) == 7 ||
(sec = 0) || // set sec to 0
sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d-%s",
&y, &m, &d, &hour, &min, tZone) == 6)
{
const char* tz;
if ((tz = getenv("TZ")) != 0)
{
savedTZ = tz;
}
if ((tz = timezone2tz(tZone)) == 0)
{
UtilityError = QString("Illegal timezone %1").arg(tZone);
return 0;
}
else
{
if (!qputenv("TZ", tz))
qFatal("date2time: Ran out of space in environment section.");
restoreTZ = true;
}
}
else if (sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d:%d",
&y, &m, &d, &hour, &min, &sec) == 6)
tZone[0] = '\0';
else if (sscanf(date.toLocal8Bit(), "%d-%d-%d-%d:%d", &y, &m, &d, &hour, &min) == 5)
{
sec = 0;
tZone[0] = '\0';
}
else if (sscanf(date.toLocal8Bit(), "%d-%d-%d", &y, &m, &d) == 3)
{
tZone[0] = '\0';
hour = min = sec = 0;
}
else
{
qFatal("%s", QString("Illegal date: %1").arg(date).toLocal8Bit().constData());
return 0;
}
if (y < 1970)
{
UtilityError = QString("Year must be larger than 1969");
return 0;
}
if (m < 1 || m > 12)
{
UtilityError = QString("Month must be between 1 and 12");
return 0;
}
if (d < 1 || d > 31)
{
UtilityError = QString("Day must be between 1 and 31");
return 0;
}
if (hour < 0 || hour > 23)
{
UtilityError = QString("Hour must be between 0 and 23");
return 0;
}
if (min < 0 || min > 59)
{
UtilityError = QString("Minutes must be between 0 and 59");
return 0;
}
if (sec < 0 || sec > 59)
{
UtilityError = QString("Seconds must be between 0 and 59");
return 0;
}
#if defined(Q_OS_WIN) || defined(__CYGWIN__) || (defined(__SVR4) && defined(__sun))
struct tm t = { sec, min, hour, d, m - 1, y - 1900, 0, 0, -1 };
#else
struct tm t = { sec, min, hour, d, m - 1, y - 1900, 0, 0, -1, 0, 0 };
#endif
time_t localTime = mktime(&t);
if (restoreTZ)
{
if (!savedTZ.empty())
{
if (!qputenv("TZ", savedTZ.c_str()))
qFatal("date2time: Ran out of space in environment section.");
}
else
qunsetenv("TZ");
}
return localTime;
}
QString
formatTime(time_t t)
{
return QLocale().toString(QDateTime::fromTime_t(t), QLocale::ShortFormat);
}
QDate
time2qdate(time_t t)
{
return QDate(year(t), monthOfYear(t), dayOfMonth(t));
}
time_t
qdate2time(const QDate& d)
{
#if defined(Q_OS_WIN) || defined(__CYGWIN__) || (defined(__SVR4) && defined(__sun))
struct tm t = { 0, 0, 0, d.day(), d.month() - 1, d.year() - 1900,
0, 0, -1 };
#else
struct tm t = { 0, 0, 0, d.day(), d.month() - 1, d.year() - 1900,
0, 0, -1, 0, 0 };
#endif
return mktime(&t);
}
#if defined(__SVR4) && defined(__sun)
/*
* Note: a proper implementation of a "setenv" function for Solaris
* would take a map where the "variable-name" is linked to a
* pointer. This would allow freeing the memory allocated here
* (a kind of garbage collection).
*/
int setenv(const char* var, const char* val, int ignore)
{
int varLen = strlen(var);
int valLen = strlen(val);
char *buffer = NULL;
if ((buffer = static_cast<char*>(malloc(varLen + valLen + 2))) == NULL)
return -1;
sprintf (buffer, "%s=%s", var, val);
return putenv(buffer) ? -1 : 0;
}
int unsetenv (const char *var)
{
return 0; /* SKIP */
}
#endif
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/taskjuggler/VacationList.cpp b/src/plugins/schedulers/tj/taskjuggler/VacationList.cpp
index df946e24..6ec5447d 100644
--- a/src/plugins/schedulers/tj/taskjuggler/VacationList.cpp
+++ b/src/plugins/schedulers/tj/taskjuggler/VacationList.cpp
@@ -1,66 +1,67 @@
/*
* VacationList.cpp - TaskJuggler
*
* Copyright (c) 2001, 2002, 2003, 2004 by Chris Schlaeger <cs@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+// clazy:excludeall=qstring-arg
#include "VacationList.h"
namespace TJ
{
VacationList::~VacationList()
{
while(!isEmpty()) {
delete takeFirst();
}
}
void
VacationList::inSort(VacationInterval* vi)
{
//TODO
append(vi);
}
void
VacationList::add(const QString& name, const Interval& i)
{
inSort(new VacationInterval(name, i));
}
void
VacationList::add(VacationInterval* vi)
{
inSort(vi);
}
bool
VacationList::isVacation(time_t date) const
{
for (VacationList::Iterator vli(*this); vli.hasNext();)
if (vli.next()->contains(date))
return true;
return false;
}
QString
VacationList::vacationName(time_t date) const
{
for (VacationList::Iterator vli(*this); vli.hasNext();) {
VacationInterval *v = vli.next();
if (v->contains(date))
return v->getName();
}
return QString();
}
} // namespace TJ
diff --git a/src/plugins/schedulers/tj/tests/SchedulerTester.cpp b/src/plugins/schedulers/tj/tests/SchedulerTester.cpp
index e8766337..88e04f0e 100644
--- a/src/plugins/schedulers/tj/tests/SchedulerTester.cpp
+++ b/src/plugins/schedulers/tj/tests/SchedulerTester.cpp
@@ -1,136 +1,137 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2011 Dag Andersen <danders@get2net.dk>
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 "SchedulerTester.h"
#include "PlanTJPlugin.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include "kptxmlloaderobject.h"
#include <QTest>
#include "tests/DateTimeTester.h"
#include "tests/debug.cpp"
namespace KPlato
{
QStringList SchedulerTester::data()
{
return QStringList()
<< "test1.plan"
<< "startnotearlier.plan"
;
}
void SchedulerTester::test()
{
QString dir = QFINDTESTDATA("data/");
foreach ( const QString &fname, data() ) {
qDebug()<<"Testing file:"<<fname;
QFile file( dir + fname );
QVERIFY2( file.open( QIODevice::ReadOnly ), fname.toLatin1() );
KoXmlDocument doc;
QString error;
bool setContent;
int line, column;
if ( ! ( setContent = doc.setContent( &file, &error, &line, &column ) ) ) {
file.close();
QString s = QString( "%1: %2 Line %3, column %4" ).arg( fname ).arg( error ).arg( line ).arg( column );
QVERIFY2( setContent, s.toLatin1() );
}
file.close();
testProject( fname, doc );
}
}
void SchedulerTester::testProject( const QString &fname, const KoXmlDocument &doc )
{
KoXmlElement pel = doc.documentElement().namedItem( "project" ).toElement();
if ( pel.isNull() ) {
QString s = QString( "%1: Cannot find 'project' element" ).arg( fname );
QVERIFY2( pel.isNull(), s.toLatin1() );
}
Project project;
project.setTimeZone( QTimeZone( "UTC" ) );
XMLLoaderObject status;
status.setProject( &project );
status.setVersion( doc.documentElement().attribute( "version", PLAN_FILE_SYNTAX_VERSION ) );
bool projectLoad = project.load( pel, status );
if ( ! projectLoad ) {
QString s = QString( "%1: Failed to load project" ).arg( fname );
QVERIFY2( projectLoad, s.toLatin1() );
}
QString s = project.description();
if ( ! s.isEmpty() ) {
qDebug();
qDebug()<<project.description();
qDebug();
}
ScheduleManager *manager = project.scheduleManagers().value( 0 );
s = QString( "%1: No schedule to compare with" ).arg( fname );
QVERIFY2( manager, s.toLatin1() );
ScheduleManager *sm = project.createScheduleManager( "Test Plan" );
project.addScheduleManager( sm );
PlanTJPlugin tj( 0, QVariantList() );
qDebug() << "+++++++++++++++++++++++++++calculate-start";
tj.calculate( project, sm, true/*nothread*/ );
qDebug() << "+++++++++++++++++++++++++++calculate-end";
s = QString( "%1: Scheduling failed" ).arg( fname );
QVERIFY2( sm->calculationResult() == ScheduleManager::CalculationDone, s.toLatin1() );
long id1 = manager->scheduleId();
long id2 = sm->scheduleId();
qDebug()<<project.startTime( id1 )<<project.startTime( id2 )<<project.timeZone();
s = QString( "%1: Compare project schedules:\n Expected: %2\n Result: %3" )
.arg( fname )
.arg( project.startTime( id1 ).toString( Qt::ISODate ) )
.arg( project.startTime( id2 ).toString( Qt::ISODate ) );
QVERIFY2( project.startTime( id1 ) == project.startTime( id2 ), s.toLatin1() );
foreach ( Node *n, project.allNodes() ) {
compare( fname, n, id1, id2 );
}
}
void SchedulerTester::compare( const QString &fname, Node *n, long id1, long id2 )
{
QString s = QString( "%1: '%2' Compare task schedules:\n Expected: %3\n Result: %4" ).arg( fname ).arg( n->name() );
QVERIFY2( n->startTime( id1 ) == n->startTime( id2 ), (s.arg(n->startTime( id1 ).toString(Qt::ISODate)).arg(n->startTime( id2 ).toString(Qt::ISODate))).toLatin1() );
QVERIFY2( n->endTime( id1 ) == n->endTime( id2 ), (s.arg(n->endTime( id1 ).toString(Qt::ISODate)).arg(n->endTime( id2 ).toString(Qt::ISODate))).toLatin1() );
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::SchedulerTester )
diff --git a/src/plugins/schedulers/tj/tests/TaskJuggler.cpp b/src/plugins/schedulers/tj/tests/TaskJuggler.cpp
index 815686df..4b107d52 100644
--- a/src/plugins/schedulers/tj/tests/TaskJuggler.cpp
+++ b/src/plugins/schedulers/tj/tests/TaskJuggler.cpp
@@ -1,1016 +1,1017 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2011 Dag Andersen <danders@get2net.dk>
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 "TaskJuggler.h"
#include "Allocation.h"
#include "Project.h"
#include "Interval.h"
#include "Task.h"
#include "Resource.h"
#include "CoreAttributesList.h"
#include "Utility.h"
#include "UsageLimits.h"
#include "debug.h"
#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include <QTest>
#include "tests/DateTimeTester.h"
#include "tests/debug.cpp"
namespace KPlato
{
void TaskJuggler::initTestCase()
{
DebugCtrl.setDebugLevel(0);
DebugCtrl.setDebugMode(0xffff);
project = new TJ::Project();
qDebug()<<"Project created:"<<project;
project->setScheduleGranularity( TJ::ONEHOUR ); // seconds
QDateTime dt = QDateTime::fromString( "2011-07-01 08:00:00", Qt::ISODate );
project->setStart( dt.toTime_t() );
project->setEnd( dt.addDays(7).addSecs( -1 ).toTime_t() );
qDebug()<<project->getStart()<<project->getEnd();
QList<TJ::Interval*> *lst;
lst = project->getWorkingHours(0);
QCOMPARE( lst->count(), 0 );
qDebug()<<"Sunday:"<<lst;
lst = project->getWorkingHours(1);
QCOMPARE( lst->count(), 2 );
qDebug()<<"Monday:"<<lst;
lst = project->getWorkingHours(2);
QCOMPARE( lst->count(), 2 );
qDebug()<<"Tuesday:"<<lst;
lst = project->getWorkingHours(3);
QCOMPARE( lst->count(), 2 );
qDebug()<<"Wednesday:"<<lst;
lst = project->getWorkingHours(4);
QCOMPARE( lst->count(), 2 );
qDebug()<<"Thursday:"<<lst;
lst = project->getWorkingHours(5);
QCOMPARE( lst->count(), 2 );
qDebug()<<"Friday:"<<lst;
lst = project->getWorkingHours(6);
QCOMPARE( lst->count(), 0 );
qDebug()<<"Saturday:"<<lst;
qDebug()<<"finished";
}
void TaskJuggler::cleanupTestCase()
{
DebugCtrl.setDebugLevel(0);
DebugCtrl.setDebugMode(0);
delete project;
}
void TaskJuggler::projectTest()
{
}
void TaskJuggler::oneTask()
{
qDebug();
TJ::Task *t = new TJ::Task(project, "T1", "T1 name", 0, QString(), 0);
qDebug()<<"Task added:"<<t;
QCOMPARE( t->getId(), QString("T1") );
QCOMPARE( t->getName(), QString("T1 name") );
qDebug()<<"finished";
int sc = project->getScenarioIndex( "plan" );
QCOMPARE( sc, 1 );
t->setDuration( sc-1, TJ::ONEHOUR );
QCOMPARE( t->getDuration( sc-1 ), (double)TJ::ONEHOUR );
QVERIFY( ! t->isMilestone() );
}
void TaskJuggler::list()
{
TJ::CoreAttributesList lst;
TJ::CoreAttributes *a = new TJ::CoreAttributes(project, "A1", "A1 name", 0);
a->setSequenceNo(1);
lst.inSort(a);
a = new TJ::CoreAttributes(project, "A2", "A2 name", 0);
a->setSequenceNo(3);
lst.inSort(a);
QCOMPARE( lst.count(), 2);
QCOMPARE( lst.at(0)->getId(), QString( "A1" ) );
QCOMPARE( lst.at(1)->getId(), QString( "A2" ) );
a = new TJ::CoreAttributes(project, "A3", "A3 name", 0);
a->setSequenceNo(2);
lst.inSort(a);
QCOMPARE( lst.at(0)->getId(), QString( "A1" ) );
QCOMPARE( lst.at(1)->getId(), QString( "A3" ) );
QCOMPARE( lst.at(2)->getId(), QString( "A2" ) );
lst.setSorting(TJ::CoreAttributesList::SequenceDown, 0);
lst.setSorting(TJ::CoreAttributesList::SequenceDown, 1);
lst.setSorting(TJ::CoreAttributesList::SequenceDown, 2);
lst.sort();
QCOMPARE( lst.at(0)->getId(), QString( "A2" ) );
QCOMPARE( lst.at(1)->getId(), QString( "A3" ) );
QCOMPARE( lst.at(2)->getId(), QString( "A1" ) );
lst.setSorting(TJ::CoreAttributesList::IdDown, 0);
lst.setSorting(TJ::CoreAttributesList::SequenceDown, 1);
lst.setSorting(TJ::CoreAttributesList::SequenceDown, 2);
lst.sort();
QStringList s; foreach(TJ::CoreAttributes *a, lst) s << a->getId();
qDebug()<<s;
QCOMPARE( lst.at(0)->getId(), QString( "A3" ) );
QCOMPARE( lst.at(1)->getId(), QString( "A2" ) );
QCOMPARE( lst.at(2)->getId(), QString( "A1" ) );
while ( ! lst.isEmpty() ) {
delete lst.takeFirst();
}
}
void TaskJuggler::oneResource()
{
TJ::Resource *r = new TJ::Resource( project, "R1", "R1 name", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(project->getWorkingHours(day)) );
foreach( TJ::Interval *i, *( r->getWorkingHours()[day] ) ) {
qDebug()<<day<<":"<<*i;
}
}
QCOMPARE( project->resourceCount(), (uint)1 );
}
void TaskJuggler::allocation()
{
TJ::Task *t = project->getTask( "T1" );
QVERIFY( t != 0 );
TJ::Resource *r = project->getResource( "R1" );
QVERIFY( r != 0 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
QCOMPARE( a->getCandidates().count(), 1 );
t->addAllocation( a );
}
void TaskJuggler::dependency()
{
int sc = project->getScenarioIndex( "plan" );
QCOMPARE( sc, 1);
TJ::Task *m = new TJ::Task( project, "M1", "M1 name", 0, QString(), 0 );
m->setMilestone( true );
m->setSpecifiedStart( sc-1, project->getStart() );
TJ::Task *t = project->getTask( "T1" );
QVERIFY( t != 0 );
TJ::TaskDependency *d = t->addDepends( m->getId() );
QVERIFY( d != 0 );
}
void TaskJuggler::scheduleResource()
{
QCOMPARE( project->getMaxScenarios(), 1 );
TJ::Task *t = project->getTask( "T1" );
QVERIFY( t != 0 );
t->setDuration( 0, 0.0 );
t->setEffort( 0, 5. );
QCOMPARE( t->getEffort( 0 ), 5. );
qDebug()<<t->getId()<<"effort="<<t->getEffort( 0 );
QVERIFY( project->pass2( true ) );
QVERIFY( project->scheduleAllScenarios() );
qDebug()<<QDateTime::fromTime_t( t->getStart( 0 ) )<<QDateTime::fromTime_t( t->getEnd( 0 ) );
}
void TaskJuggler::scheduleDependencies()
{
QString s;
QDateTime pstart = QDateTime::fromString( "2011-07-01 00:00:00", Qt::ISODate );
QDateTime pend = pstart.addDays(1);
{
s = "Test one ALAP milestone --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ALAP );
m->setSpecifiedEnd( 0, proj->getEnd() - 1 );
QVERIFY( proj->pass2( true ) );
QVERIFY( proj->scheduleAllScenarios() );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QCOMPARE( mstart, pend );
QCOMPARE( mend, pend.addSecs( -1 ) );
delete proj;
}
{
s = "Test one ALAP milestone + one ASAP task --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ALAP );
m->setSpecifiedEnd( 0, proj->getEnd() - 1 );
TJ::Task *t = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
t->setSpecifiedStart( 0, proj->getStart() );
QVERIFY( proj->pass2( true ) );
QVERIFY( proj->scheduleAllScenarios() );
QDateTime tstart = QDateTime::fromTime_t( t->getStart( 0 ) );
QDateTime tend = QDateTime::fromTime_t( t->getEnd( 0 ) );
QCOMPARE( tstart, pstart );
QCOMPARE( tend, pstart.addSecs( TJ::ONEHOUR - 1 ) );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QCOMPARE( mstart, pend );
QCOMPARE( mend, pend.addSecs( -1 ) );
delete proj;
}
{
s = "Test combination of ASAP/ALAP tasks and milestones --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( 300 ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setScheduling( TJ::Task::ASAP );
t1->setSpecifiedStart( 0, proj->getStart() );
t1->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
TJ::Task *t2 = new TJ::Task(proj, "T2", "T2", 0, QString(), 0);
t2->setScheduling( TJ::Task::ALAP );
t2->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
t2->setSpecifiedEnd( 0, proj->getEnd() - 1 );
// m->addPrecedes( t->getId() );
TJ::Task *m1 = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m1->setMilestone( true );
m1->setScheduling( TJ::Task::ASAP );
m1->addDepends( t1->getId() );
m1->addPrecedes( t2->getId() );
TJ::Task *m2 = new TJ::Task(proj, "M2", "M2", 0, QString(), 0);
m2->setMilestone( true );
m2->setScheduling( TJ::Task::ALAP );
m2->addDepends( t1->getId() );
m2->addPrecedes( t2->getId() );
TJ::Task *t3 = new TJ::Task(proj, "T3", "T3", 0, QString(), 0);
t3->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
t3->addPrecedes( m2->getId() );
t3->setScheduling( TJ::Task::ALAP ); // since t4 is ALAP, this must be ALAP too
TJ::Task *t4 = new TJ::Task(proj, "T4", "T4", 0, QString(), 0);
t4->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
t4->addPrecedes( t3->getId() );
t4->setScheduling( TJ::Task::ALAP );
QVERIFY( proj->pass2( true ) );
QVERIFY( proj->scheduleAllScenarios() );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t3start = QDateTime::fromTime_t( t3->getStart( 0 ) );
QDateTime t3end = QDateTime::fromTime_t( t3->getEnd( 0 ) );
QDateTime t4end = QDateTime::fromTime_t( t4->getEnd( 0 ) );
QDateTime m1end = QDateTime::fromTime_t( m1->getEnd( 0 ) );
QDateTime m2start = QDateTime::fromTime_t( m2->getStart( 0 ) );
QDateTime m2end = QDateTime::fromTime_t( m2->getEnd( 0 ) );
QCOMPARE( m1end, t1end );
QCOMPARE( m2start, t2start );
QCOMPARE( m2end, t3end );
QCOMPARE( t3start, t4end.addSecs(1) );
delete proj;
}
{
DebugCtrl.setDebugLevel(1000);
DebugCtrl.setDebugMode(7);
s = "Test sequeces of ASAP/ALAP milestones --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( 300 ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
// First milestone on project start
TJ::Task *m1 = new TJ::Task(proj, "Start", "Start", 0, QString(), 0);
m1->setMilestone( true );
m1->setScheduling( TJ::Task::ASAP );
m1->setSpecifiedStart( 0, proj->getStart() );
TJ::Task *m2 = new TJ::Task(proj, "M2-ASAP", "M2", 0, QString(), 0);
m2->setMilestone( true );
m2->setScheduling( TJ::Task::ASAP );
TJ::Task *m3 = new TJ::Task(proj, "M3-ASAP", "M3", 0, QString(), 0);
m3->setMilestone( true );
m3->setScheduling( TJ::Task::ASAP );
TJ::Task *m7 = new TJ::Task(proj, "M/-ASAP", "M7", 0, QString(), 0);
m7->setMilestone( true );
m7->setScheduling( TJ::Task::ASAP );
TJ::Task *m4 = new TJ::Task(proj, "M4-ALAP", "M4", 0, QString(), 0);
m4->setScheduling( TJ::Task::ALAP );
m4->setMilestone( true );
TJ::Task *m5 = new TJ::Task(proj, "M5-ALAP", "M5", 0, QString(), 0);
m5->setScheduling( TJ::Task::ALAP );
m5->setMilestone( true );
TJ::Task *m8 = new TJ::Task(proj, "M8-ALAP", "M8", 0, QString(), 0);
m8->setScheduling( TJ::Task::ALAP );
m8->setMilestone( true );
// ALAP milestone on project end
TJ::Task *m6 = new TJ::Task(proj, "End", "End", 0, QString(), 0);
m6->setMilestone( true );
m6->setScheduling( TJ::Task::ALAP );
m6->setSpecifiedEnd( 0, proj->getEnd() - 1 );
m2->addDepends( m1->getId() );
m3->addDepends( m2->getId() );
m7->addDepends( m3->getId() );
m6->addDepends( m7->getId() );
m1->addPrecedes( m4->getId() );
m4->addPrecedes( m5->getId() );
m5->addPrecedes( m8->getId() );
m8->addPrecedes( m6->getId() );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
qDebug()<<m1<<"->"<<m2<<"->"<<m3<<"->"<<m7<<"->"<<m6;
qDebug()<<m1<<"->"<<m4<<"->"<<m5<<"->"<<m8<<"->"<<m6;
QDateTime m1start = QDateTime::fromTime_t( m1->getStart( 0 ) );
QDateTime m1end = QDateTime::fromTime_t( m1->getEnd( 0 ) );
QDateTime m2start = QDateTime::fromTime_t( m2->getStart( 0 ) );
QDateTime m2end = QDateTime::fromTime_t( m2->getEnd( 0 ) );
QDateTime m3start = QDateTime::fromTime_t( m3->getStart( 0 ) );
QDateTime m3end = QDateTime::fromTime_t( m3->getEnd( 0 ) );
QDateTime m4start = QDateTime::fromTime_t( m4->getStart( 0 ) );
QDateTime m4end = QDateTime::fromTime_t( m4->getEnd( 0 ) );
QDateTime m5start = QDateTime::fromTime_t( m5->getStart( 0 ) );
QDateTime m5end = QDateTime::fromTime_t( m5->getEnd( 0 ) );
QDateTime m6start = QDateTime::fromTime_t( m6->getStart( 0 ) );
QDateTime m6end = QDateTime::fromTime_t( m6->getEnd( 0 ) );
QDateTime m7start = QDateTime::fromTime_t( m7->getStart( 0 ) );
QDateTime m7end = QDateTime::fromTime_t( m7->getEnd( 0 ) );
QDateTime m8start = QDateTime::fromTime_t( m8->getStart( 0 ) );
QDateTime m8end = QDateTime::fromTime_t( m8->getEnd( 0 ) );
QCOMPARE( m1start, pstart );
QCOMPARE( m2start, m1start );
QCOMPARE( m3start, m2start );
QCOMPARE( m7start, m3start );
QCOMPARE( m6start, pend );
QCOMPARE( m8end, m6end );
QCOMPARE( m5end, m8end );
QCOMPARE( m4end, m5end );
delete proj;
}
{
DebugCtrl.setDebugLevel(1000);
DebugCtrl.setDebugMode(7);
s = "Test sequeces of ASAP/ALAP milestones and tasks ----------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( 300 ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
// First milestone on project start
TJ::Task *m1 = new TJ::Task(proj, "Start", "Start", 0, QString(), 0);
m1->setMilestone( true );
m1->setScheduling( TJ::Task::ASAP );
m1->setSpecifiedStart( 0, proj->getStart() );
// ASAP milestone dependent on m1
TJ::Task *m2 = new TJ::Task(proj, "M2-ASAP", "M2", 0, QString(), 0);
m2->setMilestone( true );
m2->setScheduling( TJ::Task::ASAP );
// ALAP milestone dependent on m1
TJ::Task *m3 = new TJ::Task(proj, "M3-ALAP", "M3", 0, QString(), 0);
m3->setMilestone( true );
m3->setScheduling( TJ::Task::ALAP );
TJ::Task *t1 = new TJ::Task(proj, "T1-ASAP", "T1", 0, QString(), 0);
t1->setScheduling( TJ::Task::ASAP );
t1->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
TJ::Task *t2 = new TJ::Task(proj, "T2-ALAP", "T2", 0, QString(), 0);
t2->setScheduling( TJ::Task::ALAP );
t2->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
// ALAP milestone on project end
TJ::Task *m4 = new TJ::Task(proj, "End", "End", 0, QString(), 0);
m4->setMilestone( true );
m4->setScheduling( TJ::Task::ALAP );
m4->setSpecifiedEnd( 0, proj->getEnd() - 1 );
m1->addPrecedes( m2->getId() );
m2->addDepends( m1->getId() );
m2->addPrecedes( t2->getId() );
t2->addDepends( m2->getId() ); // t2 (ALAP) depends on ASAP milestone
t2->addPrecedes( m4->getId() );
m4->addDepends( t2->getId() );
m1->addPrecedes( m3->getId() );
m3->addDepends( m1->getId() );
m3->addPrecedes( t1->getId() ); // m3 ALAP
t1->addDepends( m3->getId() ); // t1 (ASAP) depends on ALAP milestone
t1->addPrecedes( m4->getId() );
m4->addDepends( t1->getId() );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
qDebug()<<m1->getId()<<"->"<<m2->getId()<<"->"<<t2->getId()<<"->"<<m4->getId();
qDebug()<<m1->getId()<<"->"<<m3->getId()<<"->"<<t1->getId()<<"->"<<m4->getId();
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t2end = QDateTime::fromTime_t( t2->getEnd( 0 ) );
QDateTime m1start = QDateTime::fromTime_t( m1->getStart( 0 ) );
QDateTime m1end = QDateTime::fromTime_t( m1->getEnd( 0 ) );
QDateTime m2start = QDateTime::fromTime_t( m2->getStart( 0 ) );
QDateTime m2end = QDateTime::fromTime_t( m2->getEnd( 0 ) );
QDateTime m3start = QDateTime::fromTime_t( m3->getStart( 0 ) );
QDateTime m3end = QDateTime::fromTime_t( m3->getEnd( 0 ) );
QDateTime m4start = QDateTime::fromTime_t( m4->getStart( 0 ) );
QDateTime m4end = QDateTime::fromTime_t( m4->getEnd( 0 ) );
QCOMPARE( m1start, pstart );
QCOMPARE( m2start, m1start );
QCOMPARE( m3start, t1start );
QCOMPARE( t2end, m4end );
QCOMPARE( m4start, pend );
delete proj;
}
{
DebugCtrl.setDebugLevel(1000);
DebugCtrl.setDebugMode(7);
s = "Test backwards ----------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( 300 ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
// First an ASAP milestone on project start to precede m2
TJ::Task *m1 = new TJ::Task(proj, "Start", "Start", 0, QString(), 0);
m1->setMilestone( true );
m1->setScheduling( TJ::Task::ASAP );
m1->setSpecifiedStart( 0, proj->getStart() );
// ALAP milestone dependent on m1 to simulate backwards
TJ::Task *m2 = new TJ::Task(proj, "M2-ALAP", "M2", 0, QString(), 0);
m2->setMilestone( true );
m2->setScheduling( TJ::Task::ASAP );
// Then the "project"
TJ::Task *m3 = new TJ::Task(proj, "M3-ASAP", "M3", 0, QString(), 0);
m3->setMilestone( true );
m3->setScheduling( TJ::Task::ASAP );
TJ::Task *t1 = new TJ::Task(proj, "T1-ASAP", "T1", 0, QString(), 0);
t1->setScheduling( TJ::Task::ASAP );
t1->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
TJ::Task *t2 = new TJ::Task(proj, "T2-ALAP", "T2", 0, QString(), 0);
t2->setScheduling( TJ::Task::ALAP );
t2->setDuration( 0, (double)(TJ::ONEHOUR) / TJ::ONEDAY );
// Then an ALAP milestone on project end
TJ::Task *m4 = new TJ::Task(proj, "End", "End", 0, QString(), 0);
m4->setMilestone( true );
m4->setScheduling( TJ::Task::ALAP );
m4->setSpecifiedEnd( 0, proj->getEnd() - 1 );
m1->addPrecedes( m2->getId() );
m2->addPrecedes( m3->getId() );
m3->addDepends( m2->getId() );
t1->addDepends( m3->getId() );
t1->addPrecedes( t2->getId() );
t2->addPrecedes( m4->getId() );
qDebug()<<m1<<"->"<<m2<<"->"<<m3<<"->"<<t1<<"->"<<t2<<"->"<<m4;
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
qDebug()<<m1<<"->"<<m2<<"->"<<m3<<"->"<<t1<<"->"<<t2<<"->"<<m4;
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t2end = QDateTime::fromTime_t( t2->getEnd( 0 ) );
QDateTime m1start = QDateTime::fromTime_t( m1->getStart( 0 ) );
QDateTime m1end = QDateTime::fromTime_t( m1->getEnd( 0 ) );
QDateTime m2start = QDateTime::fromTime_t( m2->getStart( 0 ) );
QDateTime m2end = QDateTime::fromTime_t( m2->getEnd( 0 ) );
QDateTime m3start = QDateTime::fromTime_t( m3->getStart( 0 ) );
QDateTime m3end = QDateTime::fromTime_t( m3->getEnd( 0 ) );
QDateTime m4start = QDateTime::fromTime_t( m4->getStart( 0 ) );
QDateTime m4end = QDateTime::fromTime_t( m4->getEnd( 0 ) );
QCOMPARE( m1start, pstart );
QCOMPARE( m2start, m3start );
QCOMPARE( m3start, t1start );
QCOMPARE( t2end, m4end );
QCOMPARE( m4start, pend );
delete proj;
}
}
void TaskJuggler::scheduleConstraints()
{
DebugCtrl.setDebugMode( 0 );
DebugCtrl.setDebugLevel( 100 );
QString s;
QDateTime pstart = QDateTime::fromString( "2011-07-01 09:00:00", Qt::ISODate );
QDateTime pend = pstart.addDays(1);
{
s = "Test MustStartOn --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR / 2 );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ASAP );
m->setSpecifiedStart( 0, proj->getStart() );
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR );
t1->setEffort( 0, 1.0/24.0 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
QVERIFY( proj->pass2( true ) );
QVERIFY( proj->scheduleAllScenarios() );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QCOMPARE( mstart, pstart );
QCOMPARE( t1start, mstart.addSecs( TJ::ONEHOUR ) );
delete proj;
}
{
s = "Test one MustStartOn + StartNotEarlier on same time -------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR / 2 );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ASAP );
m->setSpecifiedStart( 0, proj->getStart() );
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setPriority( 600 ); // high prio so it is likely it will be scheduled on time
t1->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR );
t1->setEffort( 0, 1.0/24.0 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
TJ::Task *t2 = new TJ::Task(proj, "T2", "T2", 0, QString(), 0);
t2->setPriority( 500 ); // less than t1
t2->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR );
t2->setEffort( 0, 1.0/24.0 );
a = new TJ::Allocation();
a->addCandidate( r );
t2->addAllocation( a );
m->addPrecedes( t1->getId() );
t1->addDepends( m->getId() );
m->addPrecedes( t2->getId() );
t2->addDepends( m->getId() );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t2end = QDateTime::fromTime_t( t2->getEnd( 0 ) );
QCOMPARE( mstart, pstart );
QCOMPARE( t1start, mstart.addSecs( TJ::ONEHOUR ) );
QCOMPARE( t2start, t1end.addSecs( 1 ) );
delete proj;
}
{
s = "Test one MustStartOn + StartNotEarlier overlapping -------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR / 4 );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ASAP );
m->setSpecifiedStart( 0, proj->getStart() );
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setPriority( 600 ); // high prio so it is likely it will be scheduled on time
t1->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR );
t1->setEffort( 0, 1.0 / proj->getDailyWorkingHours() );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
TJ::Task *t2 = new TJ::Task(proj, "T2", "T2", 0, QString(), 0);
t2->setPriority( 500 ); // less than t1
t2->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR / 2 );
t2->setEffort( 0, 1.0 / proj->getDailyWorkingHours() );
a = new TJ::Allocation();
a->addCandidate( r );
t2->addAllocation( a );
m->addPrecedes( t1->getId() );
t1->addDepends( m->getId() );
m->addPrecedes( t2->getId() );
t2->addDepends( m->getId() );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t2end = QDateTime::fromTime_t( t2->getEnd( 0 ) );
QCOMPARE( mstart, pstart );
QCOMPARE( t1start, mstart.addSecs( TJ::ONEHOUR ) );
QCOMPARE( t2start, mstart.addSecs( TJ::ONEHOUR / 2 ) );
QCOMPARE( t2end, t1end.addSecs( TJ::ONEHOUR / 2 ) );
delete proj;
}
{
s = "Fixed interval with/without allocation-------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR / 4 );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ASAP );
m->setSpecifiedStart( 0, proj->getStart() );
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setPriority( 600 ); // high prio so it is likely it will be scheduled on time
t1->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR );
t1->setSpecifiedEnd( 0, proj->getStart() + ( 2*TJ::ONEHOUR) -1 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
TJ::Task *t2 = new TJ::Task(proj, "T2", "T2", 0, QString(), 0);
t2->setPriority( 500 ); // less than t1
t2->setSpecifiedStart( 0, proj->getStart() + TJ::ONEHOUR );
t1->setSpecifiedEnd( 0, proj->getStart() + ( 2*TJ::ONEHOUR) -1 );
a = new TJ::Allocation();
a->addCandidate( r );
t2->addAllocation( a );
m->addPrecedes( t1->getId() );
t1->addDepends( m->getId() );
m->addPrecedes( t2->getId() );
t2->addDepends( m->getId() );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t2end = QDateTime::fromTime_t( t2->getEnd( 0 ) );
QCOMPARE( mstart, pstart );
QCOMPARE( t1start, mstart.addSecs( TJ::ONEHOUR ) );
QCOMPARE( t2start, mstart.addSecs( TJ::ONEHOUR ) );
delete proj;
}
}
void TaskJuggler::resourceConflict()
{
DebugCtrl.setDebugLevel( 0 );
DebugCtrl.setDebugMode( 0xffff );
QString s;
QDateTime pstart = QDateTime::fromString( "2011-07-04 09:00:00", Qt::ISODate );
QDateTime pend = pstart.addDays(1);
{
s = "Test 2 tasks, allocate same resource --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR ); // seconds
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
QCOMPARE( QDateTime::fromTime_t( proj->getStart() ), pstart );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *m = new TJ::Task(proj, "M1", "M1", 0, QString(), 0);
m->setMilestone( true );
m->setScheduling( TJ::Task::ASAP );
m->setSpecifiedStart( 0, proj->getStart() );
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setPriority( 100 ); // this should be scheduled before t2
t1->setEffort( 0, 1.0/24.0 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
TJ::Task *t2 = new TJ::Task(proj, "T2", "T2", 0, QString(), 0);
t2->setPriority( 10 );
t2->setEffort( 0, 1.0/24.0 );
a = new TJ::Allocation();
a->addCandidate( r );
t2->addAllocation( a );
m->addPrecedes( t1->getId() );
t1->addDepends( m->getId() );
m->addPrecedes( t2->getId() );
t2->addDepends( m->getId() );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime mstart = QDateTime::fromTime_t( m->getStart( 0 ) );
QDateTime mend = QDateTime::fromTime_t( m->getEnd( 0 ) );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
QDateTime t2start = QDateTime::fromTime_t( t2->getStart( 0 ) );
QDateTime t2end = QDateTime::fromTime_t( t2->getEnd( 0 ) );
QCOMPARE( mstart, pstart );
QCOMPARE( t1start, mstart );
QCOMPARE( t2start, t1end.addSecs( 1 ) );
delete proj;
}
}
void TaskJuggler::units()
{
DebugCtrl.setDebugLevel( 1000 );
DebugCtrl.setDebugMode( TSDEBUG + RSDEBUG );
QString s;
QDateTime pstart = QDateTime::fromString( "2011-07-04 09:00:00", Qt::ISODate );
QDateTime pend = pstart.addDays(3);
{
s = "Test one task, resource 50% using resource limit --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
QCOMPARE( QDateTime::fromTime_t( proj->getStart() ), pstart );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
TJ::UsageLimits *l = new TJ::UsageLimits();
l->setDailyUnits( 50 );
r->setLimits( l );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setSpecifiedStart( 0, proj->getStart() );
t1->setEffort( 0, 1.0 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
// working hours: 09:00 - 12:00, 13:00 - 18:00
QCOMPARE( t1start, pstart );
QCOMPARE( t1end, t1start.addDays( 1 ).addSecs( 5 * TJ::ONEHOUR - 1) ); // remember lunch
delete proj;
}
{
s = "Test one task, resource 50% using resource efficiency --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR / 2 );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
QCOMPARE( QDateTime::fromTime_t( proj->getStart() ), pstart );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 0.5 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setSpecifiedStart( 0, proj->getStart() );
t1->setEffort( 0, 1.0 / proj->getDailyWorkingHours() );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
t1->addAllocation( a );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
// working hours: 09:00 - 12:00, 13:00 - 18:00
QCOMPARE( t1start, pstart );
QCOMPARE( t1end, t1start.addSecs( 2 * TJ::ONEHOUR - 1) );
delete proj;
}
{
s = "Test one task, allocation limit 50% per day --------------------";
qDebug()<<s;
TJ::Project *proj = new TJ::Project();
proj->setScheduleGranularity( TJ::ONEHOUR / 2 );
proj->setStart( pstart.toTime_t() );
proj->setEnd( pend.toTime_t() );
QCOMPARE( QDateTime::fromTime_t( proj->getStart() ), pstart );
TJ::Resource *r = new TJ::Resource( proj, "R1", "R1", 0 );
r->setEfficiency( 1.0 );
for (int day = 0; day < 7; ++day) {
r->setWorkingHours( day, *(proj->getWorkingHours(day)) );
}
TJ::Task *t1 = new TJ::Task(proj, "T1", "T1", 0, QString(), 0);
t1->setSpecifiedStart( 0, proj->getStart() );
t1->setEffort( 0, 1.0 );
TJ::Allocation *a = new TJ::Allocation();
a->addCandidate( r );
TJ::UsageLimits *l = new TJ::UsageLimits();
l->setDailyUnits( 50 );
a->setLimits( l );
t1->addAllocation( a );
QVERIFY2( proj->pass2( true ), s.toLatin1() );
QVERIFY2( proj->scheduleAllScenarios(), s.toLatin1() );
QDateTime t1start = QDateTime::fromTime_t( t1->getStart( 0 ) );
QDateTime t1end = QDateTime::fromTime_t( t1->getEnd( 0 ) );
// working hours: 09:00 - 12:00, 13:00 - 18:00
QCOMPARE( t1start, pstart );
QCOMPARE( t1end, t1start.addDays( 1 ).addSecs( 5 * TJ::ONEHOUR - 1) ); // remember lunch
delete proj;
}
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::TaskJuggler )
diff --git a/src/plugins/scripting/Account.cpp b/src/plugins/scripting/Account.cpp
index 1f98127d..ab5dfbca 100644
--- a/src/plugins/scripting/Account.cpp
+++ b/src/plugins/scripting/Account.cpp
@@ -1,121 +1,122 @@
/* This file is part of the Calligra project
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* 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 "Account.h"
#include "Project.h"
#include <kptaccount.h>
Scripting::Account::Account( Scripting::Project *project, KPlato::Account *account, QObject *parent )
: QObject( parent ), m_project( project ), m_account( account )
{
}
QObject *Scripting::Account::project()
{
return m_project;
}
QString Scripting::Account::name() const
{
return m_account->name();
}
int Scripting::Account::childCount() const
{
return m_account->childCount();
}
QObject *Scripting::Account::childAt( int index )
{
return m_project->account( m_account->childAt( index ) );
}
QVariant Scripting::Account::plannedEffortCostPrDay( const QVariant &start, const QVariant &end, const QVariant &schedule )
{
//kDebug(planDbg())<<start<<end<<schedule;
QVariantMap map;
QDate s = start.toDate();
QDate e = end.toDate();
if ( ! s.isValid() ) {
s = QDate::currentDate();
}
if ( ! e.isValid() ) {
e = QDate::currentDate();
}
KPlato::Accounts *a = m_account->list();
if ( a == 0 ) {
return QVariant();
}
KPlato::EffortCostMap ec = a->plannedCost( *m_account, s, e, schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
QVariant Scripting::Account::actualEffortCostPrDay( const QVariant &start, const QVariant &end, const QVariant &schedule )
{
//kDebug(planDbg())<<start<<end<<schedule;
QVariantMap map;
QDate s = start.toDate();
QDate e = end.toDate();
if ( ! s.isValid() ) {
s = QDate::currentDate();
}
if ( ! e.isValid() ) {
e = QDate::currentDate();
}
KPlato::Accounts *a = m_account->list();
if ( a == 0 ) {
return QVariant();
}
KPlato::EffortCostMap ec = a->actualCost( *m_account, s, e, schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
QVariant Scripting::Account::plannedEffortCostPrDay( const QVariant &schedule )
{
//kDebug(planDbg())<<start<<end<<schedule;
QVariantMap map;
KPlato::EffortCostMap ec = m_account->plannedCost( schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
QVariant Scripting::Account::actualEffortCostPrDay( const QVariant &schedule )
{
//kDebug(planDbg())<<start<<end<<schedule;
QVariantMap map;
KPlato::EffortCostMap ec = m_account->actualCost( schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
diff --git a/src/plugins/scripting/Calendar.cpp b/src/plugins/scripting/Calendar.cpp
index f0cff40f..84fd2def 100644
--- a/src/plugins/scripting/Calendar.cpp
+++ b/src/plugins/scripting/Calendar.cpp
@@ -1,55 +1,56 @@
/* This file is part of the Calligra project
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* 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 "Calendar.h"
#include "Project.h"
#include "kptcalendar.h"
Scripting::Calendar::Calendar( Scripting::Project *project, KPlato::Calendar *calendar, QObject *parent )
: QObject( parent ), m_project( project ), m_calendar( calendar )
{
}
QObject *Scripting::Calendar::project()
{
return m_project;
}
QString Scripting::Calendar::id() const
{
return m_calendar->id();
}
QString Scripting::Calendar::name() const
{
return m_calendar->name();
}
int Scripting::Calendar::childCount() const
{
return m_calendar->childCount();
}
QObject *Scripting::Calendar::childAt( int index )
{
KPlato::Calendar *c = m_calendar->childAt( index );
return c == 0 ? 0 : m_project->calendar( c );
}
diff --git a/src/plugins/scripting/Module.cpp b/src/plugins/scripting/Module.cpp
index 1f0826c9..b8ad6e2b 100644
--- a/src/plugins/scripting/Module.cpp
+++ b/src/plugins/scripting/Module.cpp
@@ -1,202 +1,203 @@
/*
* This file is part of KPlato
*
* Copyright (c) 2006 Sebastian Sauer <mail@dipe.org>
* Copyright (c) 2008, 2011 Dag Andersen <danders@get2net>
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "Module.h"
#include "Project.h"
#include "Account.h"
#include "Calendar.h"
#include "Node.h"
#include "Resource.h"
#include "ResourceGroup.h"
#include "Schedule.h"
#include "ScriptingWidgets.h"
#include "kplatoscripting_export.h"
// qt
#include <QPointer>
#include <QWidget>
#include <QMap>
// plan
#include "kptmaindocument.h"
#include "kptpart.h"
#include "kptview.h"
#include "kptproject.h"
#include "kptnode.h"
#include "kptcommand.h"
extern "C"
{
KPLATOSCRIPTING_EXPORT QObject* krossmodule()
{
return new Scripting::Module();
}
}
namespace Scripting {
/// \internal d-pointer class.
class Q_DECL_HIDDEN Module::Private
{
public:
QPointer<KPlato::MainDocument> doc;
Project *project;
QMap<QString, Module*> modules;
KPlato::MacroCommand *command; // used for beginCommand()/endCommand()
};
Module::Module(QObject* parent)
: KoScriptingModule(parent, "Plan")
, d( new Private() )
{
d->doc = 0;
d->project = 0;
d->command = 0;
}
Module::~Module()
{
endCommand();
qDeleteAll( d->modules );
delete d->project;
delete d;
}
KPlato::MainDocument* Module::part()
{
if(! d->doc) {
if( KPlato::View* v = dynamic_cast< KPlato::View* >(view()) ) {
d->doc = v->getPart();
}
if( ! d->doc ) {
KPlato::Part *part = new KPlato::Part(this);
d->doc = new KPlato::MainDocument(part);
part->setDocument(d->doc);
}
}
return d->doc;
}
KoDocument* Module::doc()
{
return part();
}
void Module::openUrl( const QString &url )
{
doc()->openUrl( QUrl(url) ); // QT5TODO: check QUrl usage here
}
QObject *Module::openDocument( const QString &tag, const QString &url )
{
Module *m = d->modules[ tag ];
if ( m == 0 ) {
m = new Module();
d->modules[ tag ] = m;
}
m->part()->openUrl( QUrl(url) ); // QT5TODO: check QUrl usage here
return m;
}
void Module::beginCommand( const KUndo2MagicString &name )
{
endCommand();
d->command = new KPlato::MacroCommand( name );
}
void Module::endCommand()
{
if ( d->command && ! d->command->isEmpty() ) {
KPlato::MacroCommand *c = new KPlato::MacroCommand( KUndo2MagicString() );
doc()->addCommand( c );
doc()->endMacro(); // executes c and enables undo/redo
c->addCommand( d->command ); // this command is already executed
d->command = 0;
} else {
delete d->command;
d->command = 0;
}
}
void Module::revertCommand()
{
if ( d->command ) {
if ( ! d->command->isEmpty() ) {
endCommand();
doc()->undoStack()->undo();
} else {
endCommand();
}
}
}
QObject *Module::project()
{
if ( d->project != 0 && d->project->kplatoProject() != &( part()->getProject() ) ) {
// need to replace the project, happens when new document is loaded
delete d->project;
d->project = 0;
}
if ( d->project == 0 ) {
d->project = new Project( this, &(part()->getProject()) );
}
return d->project;
}
QWidget *Module::createScheduleListView( QWidget *parent )
{
ScriptingScheduleListView *v = new ScriptingScheduleListView( this, parent );
if ( parent && parent->layout() ) {
parent->layout()->addWidget( v );
}
return v;
}
QWidget *Module::createDataQueryView( QWidget *parent )
{
ScriptingDataQueryView *v = new ScriptingDataQueryView( this, parent );
if ( parent && parent->layout() ) {
parent->layout()->addWidget( v );
}
return v;
}
void Module::slotAddCommand( KUndo2Command *cmd )
{
if ( d->command ) {
if ( d->command->isEmpty() ) {
doc()->beginMacro( d->command->text() ); // used to disable undo/redo
}
cmd->redo();
d->command->addCommand( cmd );
} else {
doc()->addCommand( cmd );
}
}
void Module::addCommand( KUndo2Command *cmd )
{
slotAddCommand( cmd );
}
} //namespace Scripting
diff --git a/src/plugins/scripting/Node.cpp b/src/plugins/scripting/Node.cpp
index 2ac3f823..8f3c9e2d 100644
--- a/src/plugins/scripting/Node.cpp
+++ b/src/plugins/scripting/Node.cpp
@@ -1,129 +1,130 @@
/* This file is part of the Calligra project
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* 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 "Node.h"
#include "Project.h"
#include "kptnode.h"
#include "kpteffortcostmap.h"
#include "kptduration.h"
Scripting::Node::Node( Scripting::Project *project, KPlato::Node *node, QObject *parent )
: QObject( parent ), m_project( project ), m_node( node )
{
}
QObject *Scripting::Node::project()
{
return m_project;
}
QString Scripting::Node::name()
{
return m_node->name();
}
QDate Scripting::Node::startDate()
{
return m_node->startTime().date();
}
QDate Scripting::Node::endDate()
{
return m_node->endTime().date();
}
QString Scripting::Node::id()
{
return m_node->id();
}
QVariant Scripting::Node::type()
{
return m_node->typeToString();
}
int Scripting::Node::childCount() const
{
return m_node->numChildren();
}
QObject *Scripting::Node::childAt( int index )
{
return m_project->node( m_node->childNode( index ) );
}
QObject *Scripting::Node::parentNode()
{
return m_project->node( m_node->parentNode() );
}
QVariant Scripting::Node::plannedEffortCostPrDay( const QVariant &start, const QVariant &end, const QVariant &schedule )
{
//kDebug(planDbg())<<start<<end<<schedule;
QVariantMap map;
QDate s = start.toDate();
QDate e = end.toDate();
if ( ! s.isValid() ) {
s = QDate::currentDate();
}
if ( ! e.isValid() ) {
e = QDate::currentDate();
}
KPlato::EffortCostMap ec = m_node->plannedEffortCostPrDay( s, e, schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
QVariant Scripting::Node::bcwsPrDay( const QVariant &schedule ) const
{
QVariantMap map;
KPlato::EffortCostMap ec = m_node->bcwsPrDay( schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
QVariant Scripting::Node::bcwpPrDay( const QVariant &schedule ) const
{
QVariantMap map;
KPlato::EffortCostMap ec = m_node->bcwpPrDay( schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
QVariant Scripting::Node::acwpPrDay( const QVariant &schedule ) const
{
QVariantMap map;
KPlato::EffortCostMap ec = m_node->acwp( schedule.toLongLong() );
KPlato::EffortCostDayMap::ConstIterator it = ec.days().constBegin();
for (; it != ec.days().constEnd(); ++it ) {
map.insert( it.key().toString( Qt::ISODate ), QVariantList() << it.value().effort().toDouble( KPlato::Duration::Unit_h ) << it.value().cost() );
}
return map;
}
diff --git a/src/plugins/scripting/Project.cpp b/src/plugins/scripting/Project.cpp
index 4c316299..b3581219 100644
--- a/src/plugins/scripting/Project.cpp
+++ b/src/plugins/scripting/Project.cpp
@@ -1,910 +1,911 @@
/* This file is part of the Calligra project
* Copyright (C) 2006 Sebastian Sauer <mail@dipe.org>
* Copyright (c) 2008, 2011 Dag Andersen <danders@get2net>
*
* 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 "Project.h"
#include "Account.h"
#include "Calendar.h"
#include "Resource.h"
#include "ResourceGroup.h"
#include "Schedule.h"
#include "ScriptingDebug.h"
#include "kptglobal.h"
#include "kptaccount.h"
#include "kptcalendar.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptresource.h"
#include "kptcommand.h"
#include <QMetaEnum>
Scripting::Project::Project( Scripting::Module* module, KPlato::Project *project )
: Node( this, project, module ), m_module( module )
{
debugPlanScripting<<this<<"KPlato::"<<project;
m_nodeModel.setProject( project );
m_nodeModel.setShowProject( true );
m_nodeModel.setReadWrite( true );
m_nodeModel.setReadOnly( NodeModel::NodeDescription, false );
connect(&m_nodeModel, SIGNAL(executeCommand(KUndo2Command*)), SLOT(slotAddCommand(KUndo2Command*)));
m_resourceModel.setProject( project );
m_resourceModel.setReadWrite( true );
connect(&m_resourceModel, SIGNAL(executeCommand(KUndo2Command*)), SLOT(slotAddCommand(KUndo2Command*)));
m_accountModel.setProject( project );
m_accountModel.setReadWrite( true );
connect(&m_accountModel, SIGNAL(executeCommand(KUndo2Command*)), SLOT(slotAddCommand(KUndo2Command*)));
m_calendarModel.setProject( project );
m_calendarModel.setReadWrite( true );
connect(&m_calendarModel, SIGNAL(executeCommand(KUndo2Command*)), SLOT(slotAddCommand(KUndo2Command*)));
// Define the role to use where data fetched with Qt::DisplayRole cannot used in setData
m_nodeprogramroles[ NodeModel::NodeEstimateType ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeEstimateCalendar ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeConstraint ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeConstraintStart ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeConstraintEnd ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeRunningAccount ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeStartupAccount ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeDescription ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeShutdownAccount ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeDescription ] = Qt::EditRole;
m_nodeprogramroles[ NodeModel::NodeDescription ] = Qt::EditRole;
m_resourceprogramroles[ ResourceModel::ResourceAvailableFrom ] = Qt::EditRole;
m_resourceprogramroles[ ResourceModel::ResourceAvailableUntil ] = Qt::EditRole;
m_resourceprogramroles[ ResourceModel::ResourceNormalRate ] = Qt::EditRole;
m_resourceprogramroles[ ResourceModel::ResourceOvertimeRate ] = Qt::EditRole;
// FIXME: faked for now
int c = m_calendarModel.columnCount();
m_calendarprogramroles[ c ] = Qt::EditRole; // Weekday
m_calendarprogramroles[ c + 1 ] = Qt::EditRole; // Date
}
Scripting::Project::~Project()
{
debugPlanScripting<<this;
qDeleteAll( m_nodes );
qDeleteAll( m_groups );
qDeleteAll( m_resources );
qDeleteAll( m_calendars );
qDeleteAll( m_schedules );
qDeleteAll( m_accounts );
}
QObject *Scripting::Project::defaultCalendar()
{
return calendar( kplatoProject()->defaultCalendar() );
}
void Scripting::Project::setDefaultCalendar(Scripting::Calendar* calendar)
{
if ( calendar ) {
setCalendarData( calendar->kplatoCalendar(), "Name", Qt::Checked, "CheckStateRole" );
}
}
QVariant Scripting::Project::data( QObject *object, const QString &property )
{
return data( object, property, "DisplayRole", -1 );
}
QVariant Scripting::Project::data( QObject *object, const QString &property, const QString &role, qlonglong scheduleId )
{
Node *n = qobject_cast<Node*>( object );
if ( n ) {
if ( n->project() != this ) {
return QVariant();
}
return nodeData( n->kplatoNode(), property, role, scheduleId );
}
Resource *r = qobject_cast<Resource*>( object );
if ( r ) {
if ( r->project() != this ) {
return QVariant();
}
return resourceData( r->kplatoResource(), property, role, scheduleId );
}
ResourceGroup *g = qobject_cast<ResourceGroup*>( object );
if ( g ) {
if ( g->project() != this ) {
return QVariant();
}
return resourceGroupData( g->kplatoResourceGroup(), property, role );
}
Account *a = qobject_cast<Account*>( object );
if ( a ) {
if ( a->project() != this ) {
return QVariant();
}
return accountData( a->kplatoAccount(), property, role );
}
Calendar *c = qobject_cast<Calendar*>( object );
if ( c ) {
if ( c->project() != this ) {
return QVariant();
}
return calendarData( c->kplatoCalendar(), property, role );
}
// TODO Schedule (if needed)
return QVariant();
}
QVariant Scripting::Project::setData( QObject* object, const QString& property, const QVariant& data, const QString& role )
{
Node *n = qobject_cast<Node*>( object );
if ( n ) {
if ( n->project() != this ) {
return "Invalid";
}
return setNodeData( n->kplatoNode(), property, data, role );
}
Resource *r = qobject_cast<Resource*>( object );
if ( r ) {
if ( r->project() != this ) {
return "Invalid";
}
return setResourceData( r->kplatoResource(), property, data, role );
}
ResourceGroup *g = qobject_cast<ResourceGroup*>( object );
if ( g ) {
if ( g->project() != this ) {
return "Invalid";
}
return setResourceGroupData( g->kplatoResourceGroup(), property, data, role );
}
Account *a = qobject_cast<Account*>( object );
if ( a ) {
if ( a->project() != this ) {
return "Invalid";
}
return setAccountData( a->kplatoAccount(), property, data, role );
}
Calendar *c = qobject_cast<Calendar*>( object );
if ( c ) {
if ( c->project() != this ) {
return "Invalid";
}
return setCalendarData( c->kplatoCalendar(), property, data, role );
}
return "Invalid";
}
QVariant Scripting::Project::headerData( int objectType, const QString &property, const QString &role )
{
switch ( objectType ) {
case 0: return taskHeaderData( property, role );
case 1: return resourceHeaderData( property, role );
case 2: return accountHeaderData( property, role );
case 3: return calendarHeaderData( property, role );
default: break;
}
return QVariant();
}
int Scripting::Project::scheduleCount() const
{
return kplatoProject()->numScheduleManagers();
}
QObject *Scripting::Project::scheduleAt( int index )
{
return schedule( kplatoProject()->scheduleManagers().value( index ) );
}
QObject *Scripting::Project::schedule( KPlato::ScheduleManager *sch )
{
if ( sch == 0 ) {
return 0;
}
if ( ! m_schedules.contains( sch ) ) {
m_schedules[ sch ] = new Schedule( this, sch, parent() );
}
return m_schedules[ sch ];
}
QStringList Scripting::Project::taskPropertyList()
{
QStringList lst;
QMetaEnum e = m_nodeModel.columnMap();
for ( int i = 0; i < e.keyCount(); ++i ) {
QString s = QString( e.key( i ) );
if ( s.left( 4 ) == "Node" ) {
s.remove( 0, 4 );
}
lst << QString( e.key( i ) );
}
return lst;
}
QVariant Scripting::Project::taskHeaderData( const QString &property, const QString &role )
{
int col = nodeColumnNumber( property );
return m_nodeModel.headerData( col, Qt::Horizontal, stringToRole( role ) );
}
int Scripting::Project::nodeColumnNumber( const QString &property ) const
{
int col = m_nodeModel.columnMap().keyToValue( property.toUtf8() );
if ( col > 0 ) {
return col;
}
QString prop = property;
if ( prop.left( 4 ) != "Node" ) {
prop.prepend( "Node" );
}
return m_nodeModel.columnMap().keyToValue( prop.toUtf8() );
}
int Scripting::Project::resourceColumnNumber( const QString &property ) const
{
QString prop = property;
if ( prop.left( 8 ) != "Resource" ) {
prop.prepend( "Resource" );
}
return m_resourceModel.columnMap().keyToValue( prop.toUtf8() );
}
int Scripting::Project::programRole( const QMap<int, int> &map, int column ) const
{
return map.contains( column ) ? map[ column ] : Qt::DisplayRole;
}
Scripting::Node *Scripting::Project::node( KPlato::Node *node )
{
if ( node == 0 ) {
return 0;
}
if ( ! m_nodes.contains( node ) ) {
m_nodes[ node ] = new Node( this, node, parent() );
}
return m_nodes[ node ];
}
int Scripting::Project::taskCount() const
{
return childCount();
}
QObject *Scripting::Project::taskAt( int index )
{
return childAt( index );
}
QVariant Scripting::Project::nodeData( const KPlato::Node *node, const QString &property, const QString &role, long schedule )
{
KPlato::ScheduleManager *sm = kplatoProject()->scheduleManager( schedule );
if ( m_nodeModel.scheduleManager() != sm ) {
m_nodeModel.setScheduleManager( kplatoProject()->scheduleManager( schedule ) );
}
int col = nodeColumnNumber( property );
QModelIndex idx = m_nodeModel.index( node );
idx = m_nodeModel.index( idx.row(), col, idx.parent() );
if ( ! idx.isValid() ) {
debugPlanScripting<<"Failed"<<node<<property<<idx;
return QVariant();
}
int r = stringToRole( role, m_nodeprogramroles.value( col ) );
if ( r < 0 ) {
return QVariant(); // invalid role
}
if ( col == NodeModel::NodeDescription && r == Qt::DisplayRole ) {
r = Qt::EditRole; // cannot use displayrole here
}
QVariant value = m_nodeModel.data( idx, r );
if ( r == Qt::EditRole ) {
switch ( col ) {
case NodeModel::NodeType:
value = QVariant( node->typeToString( KPlato::Node::NodeTypes( value.toInt() ), false ) );
break;
case NodeModel::NodeConstraint:
// ASAP, ALAP, MustStartOn, MustFinishOn, StartNotEarlier, FinishNotLater, FixedInterval
value = QVariant( node->constraintList( false ).value( value.toInt() ) );
break;
default:
break;
}
}
return value;
}
QVariant Scripting::Project::setNodeData( KPlato::Node *node, const QString &property, const QVariant &data, const QString &role )
{
int col = nodeColumnNumber( property );
QModelIndex idx = m_nodeModel.index( node, col );
if ( ! idx.isValid() ) {
return "Invalid";
}
if ( ( m_nodeModel.flags( idx ) & Qt::ItemIsEditable ) == 0 ) {
return "ReadOnly";
}
int datarole = stringToRole( role, Qt::EditRole );
if ( datarole < 0 ) {
return QVariant("Invalid role: " + role);
}
if ( nodeData( node, property, datarole == Qt::EditRole ? "ProgramRole" : role ) == data ) {
return "Success";
}
return m_nodeModel.setData( idx, data, datarole ) ? "Success" : "Error";
}
QObject *Scripting::Project::findTask( const QString &id )
{
return node( kplatoProject()->findNode( id ) );
}
QObject *Scripting::Project::createTask(const QObject* copy, QObject* parent, QObject* after)
{
const Node *cpy = static_cast<const Node*>( copy );
KPlato::Node *t = 0;
if ( cpy ) {
t = kplatoProject()->createTask( static_cast<KPlato::Task&>( *( cpy->kplatoNode() ) ) );
} else {
t = kplatoProject()->createTask();
}
KPlato::NamedCommand *cmd;
if ( parent ) {
KPlato::Node *par = static_cast<Node*>( parent )->kplatoNode();
cmd = new SubtaskAddCmd( kplatoProject(), t, par, kundo2_i18n( "Add task" ) );
} else {
KPlato::Node *aft = after ? static_cast<Node*>( after )->kplatoNode() : 0;
cmd = new TaskAddCmd( kplatoProject(), t, aft, kundo2_i18n( "Add task" ) );
}
m_module->addCommand( cmd );
return node( t );
}
QObject *Scripting::Project::createTask( QObject* parent, QObject* after )
{
KPlato::Task *t = kplatoProject()->createTask();
KPlato::NamedCommand *cmd;
if ( parent ) {
KPlato::Node *par = static_cast<Node*>( parent )->kplatoNode();
cmd = new SubtaskAddCmd( kplatoProject(), t, par, kundo2_i18n( "Add task" ) );
} else {
KPlato::Node *aft = after ? static_cast<Node*>( after )->kplatoNode() : 0;
cmd = new TaskAddCmd( kplatoProject(), t, aft, kundo2_i18n( "Add task" ) );
}
m_module->addCommand( cmd );
return node( t );
}
int Scripting::Project::resourceGroupCount() const
{
return kplatoProject()->resourceGroupCount();
}
QObject *Scripting::Project::resourceGroupAt( int index )
{
return resourceGroup( kplatoProject()->resourceGroupAt( index ) );
}
QObject *Scripting::Project::findResourceGroup( const QString &id )
{
KPlato::ResourceGroup *g = kplatoProject()->findResourceGroup( id );
return g == 0 ? 0 : resourceGroup( g );
}
QObject *Scripting::Project::createResourceGroup( QObject *group )
{
//debugPlanScripting<<this<<group;
KPlato::ResourceGroup *g = 0;
const ResourceGroup *gr = qobject_cast<ResourceGroup*>( group );
if ( gr == 0 ) {
return createResourceGroup();
}
KPlato::ResourceGroup *copyfrom = gr->kplatoResourceGroup();
if ( copyfrom == 0 ) {
debugPlanScripting<<"Nothing to copy from";
return 0;
}
if ( kplatoProject()->findResourceGroup( copyfrom->id() ) ) {
debugPlanScripting<<"Group with id already exists";
return 0;
}
g = new KPlato::ResourceGroup( copyfrom );
AddResourceGroupCmd *cmd = new AddResourceGroupCmd( kplatoProject(), g, kundo2_i18n( "Add resource group" ) );
m_module->addCommand( cmd );
return resourceGroup( g );
}
QObject *Scripting::Project::createResourceGroup()
{
KPlato::ResourceGroup *g = new KPlato::ResourceGroup();
AddResourceGroupCmd *cmd = new AddResourceGroupCmd( kplatoProject(), g, kundo2_i18n( "Add resource group" ) );
m_module->addCommand( cmd );
return resourceGroup( g );
}
QObject *Scripting::Project::resourceGroup( KPlato::ResourceGroup *group )
{
if ( group == 0 ) {
return 0;
}
if ( ! m_groups.contains( group ) ) {
m_groups[ group ] = new ResourceGroup( this, group, parent() );
}
return m_groups[ group ];
}
QVariant Scripting::Project::setResourceGroupData( KPlato::ResourceGroup *resource, const QString &property, const QVariant &data, const QString &role )
{
QModelIndex idx = m_resourceModel.index( resource, resourceColumnNumber( property ) );
if ( ! idx.isValid() ) {
return "Invalid";
}
if ( ( m_resourceModel.flags( idx ) & Qt::ItemIsEditable ) == 0 ) {
return "ReadOnly";
}
int datarole = stringToRole( role, Qt::EditRole );
if ( datarole < 0 ) {
return QVariant("Invalid role: " + role);
}
if ( resourceGroupData( resource, property, datarole == Qt::EditRole ? "ProgramRole" : role ) == data ) {
return "Success";
}
return m_resourceModel.setData( idx, data, datarole ) ? "Success" : "Error";
}
QVariant Scripting::Project::resourceGroupData( const KPlato::ResourceGroup *group, const QString &property, const QString &role, long /*schedule*/ )
{
QModelIndex idx = m_resourceModel.index( group );
idx = m_resourceModel.index( idx.row(), resourceColumnNumber( property ), idx.parent() );
if ( ! idx.isValid() ) {
return QVariant();
}
int r = m_resourceprogramroles.value( idx.column() );
r = stringToRole( role, r );
if ( r < 0 ) {
return QVariant(); // invalid role
}
return m_resourceModel.data( idx, r );
}
QObject *Scripting::Project::createResource( QObject *group, QObject *copy )
{
ResourceGroup *gr = qobject_cast<ResourceGroup*>( group );
if ( gr == 0 ) {
debugPlanScripting<<"No group specified";
return 0;
}
KPlato::ResourceGroup *g = kplatoProject()->findResourceGroup( gr->kplatoResourceGroup()->id() );
if ( g == 0 ) {
debugPlanScripting<<"Could not find group";
return 0;
}
KPlato::Resource *r = 0;
const Resource *rs = qobject_cast<Resource*>( copy );
if ( rs == 0 ) {
return createResource( group );
}
r = kplatoProject()->findResource( rs->kplatoResource()->id() );
if ( r ) {
debugPlanScripting<<"Resource already exists";
return 0;
}
r = new KPlato::Resource( rs->kplatoResource() );
KPlato::Calendar *c = rs->kplatoResource()->calendar( true );
if ( c ) {
c = kplatoProject()->calendar( c->id() );
}
r->setCalendar( c );
AddResourceCmd *cmd = new AddResourceCmd( g, r, kundo2_i18n( "Add resource" ) );
m_module->addCommand( cmd );
return resource( r );
}
QObject *Scripting::Project::createResource( QObject *group )
{
ResourceGroup *gr = qobject_cast<ResourceGroup*>( group );
if ( gr == 0 ) {
debugPlanScripting<<"No group specified";
return 0;
}
KPlato::ResourceGroup *g = kplatoProject()->findResourceGroup( gr->kplatoResourceGroup()->id() );
if ( g == 0 ) {
debugPlanScripting<<"Could not find group";
return 0;
}
KPlato::Resource *r = new KPlato::Resource();
AddResourceCmd *cmd = new AddResourceCmd( g, r, kundo2_i18n( "Add resource" ) );
m_module->addCommand( cmd );
return resource( r );
}
QObject *Scripting::Project::resource( KPlato::Resource *resource )
{
if ( resource == 0 ) {
return 0;
}
if ( ! m_resources.contains( resource ) ) {
m_resources[ resource ] = new Resource( this, resource, parent() );
}
return m_resources[ resource ];
}
QVariant Scripting::Project::setResourceData( KPlato::Resource *resource, const QString &property, const QVariant &data, const QString &role )
{
QModelIndex idx = m_resourceModel.index( resource, resourceColumnNumber( property ) );
if ( ! idx.isValid() ) {
return "Invalid";
}
if ( ( m_resourceModel.flags( idx ) & Qt::ItemIsEditable ) == 0 ) {
return "ReadOnly";
}
int datarole = stringToRole( role, Qt::EditRole );
if ( datarole < 0 ) {
return QVariant("Invalid role: " + role);
}
if ( resourceData( resource, property, datarole == Qt::EditRole ? "ProgramRole" : role ) == data ) {
return "Success";
}
return m_resourceModel.setData( idx, data, datarole ) ? "Success" : "Error";
}
QVariant Scripting::Project::resourceData( const KPlato::Resource *resource, const QString &property, const QString &role, long /*schedule*/ )
{
QModelIndex idx = m_resourceModel.index( resource );
idx = m_resourceModel.index( idx.row(), resourceColumnNumber( property ), idx.parent() );
if ( ! idx.isValid() ) {
debugPlanScripting<<"Invalid index"<<resource;
return QVariant();
}
int r = stringToRole( role, m_resourceprogramroles.value( idx.column() ) );
if ( r < 0 ) {
return QVariant(); // invalid role
}
return m_resourceModel.data( idx, r );
}
QStringList Scripting::Project::resourcePropertyList()
{
QStringList lst;
QMetaEnum e = m_resourceModel.columnMap();
for ( int i = 0; i < e.keyCount(); ++i ) {
QString s = QString( e.key( i ) );
if ( s.left( 8 ) == "Resource" ) {
s.remove( 0, 8 );
}
lst << s;
}
return lst;
}
QVariant Scripting::Project::resourceHeaderData( const QString &property, const QString &role )
{
int col = resourceColumnNumber( property );
return m_resourceModel.headerData( col, Qt::Horizontal, stringToRole( role ) );
}
QObject *Scripting::Project::findResource( const QString &id )
{
KPlato::Resource *r = kplatoProject()->findResource( id );
return r == 0 ? 0 : resource( r );
}
QVariantList Scripting::Project::externalProjects()
{
QVariantList lst;
QMap<QString, QString> map = kplatoProject()->externalProjects();
for ( QMapIterator<QString, QString> it( map ); it.hasNext(); ) {
it.next();
QVariantList m;
m << it.key() << it.value();
lst << QVariant(m);
}
return lst;
}
void Scripting::Project::addExternalAppointment( QObject *resource, const QVariant &id, const QString &name, const QVariantList &lst )
{
Resource *r = qobject_cast<Resource*>( resource );
if ( r == 0 ) {
return;
}
//debugPlanScripting<<id<<name<<lst;
KPlato::DateTime st = KPlato::DateTime::fromString( lst[0].toString() );
KPlato::DateTime et = KPlato::DateTime::fromString( lst[1].toString() );
double load = lst[2].toDouble();
if ( ! st.isValid() || ! et.isValid() ) {
return;
}
AddExternalAppointmentCmd *cmd = new AddExternalAppointmentCmd( r->kplatoResource(), id.toString(), name, st, et, load, kundo2_i18n( "Add external appointment" ) );
m_module->addCommand( cmd );
}
void Scripting::Project::clearExternalAppointments( QObject *resource, const QString &id )
{
Resource *r = qobject_cast<Resource*>( resource );
if ( r == 0 ) {
return;
}
ClearExternalAppointmentCmd *cmd = new ClearExternalAppointmentCmd( r->kplatoResource(), id, kundo2_i18n( "Clear external appointments" ) );
m_module->addCommand( cmd );
}
void Scripting::Project::clearExternalAppointments( const QString &id )
{
foreach ( KPlato::Resource *r, kplatoProject()->resourceList() ) {
ClearExternalAppointmentCmd *cmd = new ClearExternalAppointmentCmd( r, id, kundo2_i18n( "Clear external appointments" ) );
m_module->addCommand( cmd );
}
}
void Scripting::Project::clearExternalAppointments()
{
ClearAllExternalAppointmentsCmd *cmd = new ClearAllExternalAppointmentsCmd( kplatoProject(), kundo2_i18n( "Clear all external appointments" ) );
m_module->addCommand( cmd );
}
int Scripting::Project::calendarCount() const
{
return kplatoProject()->calendarCount();
}
QObject *Scripting::Project::calendarAt( int index )
{
return calendar( kplatoProject()->calendarAt( index ) );
}
QObject *Scripting::Project::findCalendar( const QString &id )
{
KPlato::Calendar *c = kplatoProject()->calendar( id );
debugPlanScripting<<id<<c;
return calendar( c );
}
QObject *Scripting::Project::createCalendar( QObject *copy, QObject *parent )
{
debugPlanScripting<<this<<copy<<parent;
const KPlato::Calendar *copyfrom = 0;
KPlato::Calendar *c = 0;
if ( copy == 0 ) {
return createCalendar( parent );
}
const Calendar *cal = qobject_cast<Calendar*>( copy );
copyfrom = cal->kplatoCalendar();
if ( copyfrom == 0 ) {
debugPlanScripting<<"Nothing to copy from";
return 0;
}
c = kplatoProject()->calendar( copyfrom->id() );
if ( c ) {
debugPlanScripting<<"Calendar already exists";
return 0;
}
Calendar *par = qobject_cast<Calendar*>( parent );
KPlato::Calendar *p = 0;
if ( par ) {
p = kplatoProject()->calendar( par->id() );
}
c = new KPlato::Calendar();
if ( copyfrom ) {
*c = *copyfrom;
c->setId( copyfrom->id() ); // NOTE: id is not copied
}
m_calendarModel.insertCalendar( c, -1, p );
Calendar *call = this->calendar( c );
debugPlanScripting<<call;
return call;
}
QObject *Scripting::Project::createCalendar( QObject *parent )
{
debugPlanScripting<<this<<parent;
Calendar *par = qobject_cast<Calendar*>( parent );
KPlato::Calendar *p = 0;
if ( par ) {
p = kplatoProject()->calendar( par->id() );
}
KPlato::Calendar *c = new KPlato::Calendar();
m_calendarModel.insertCalendar( c, -1, p );
return this->calendar( c );
}
Scripting::Calendar *Scripting::Project::calendar( KPlato::Calendar *calendar )
{
if ( calendar == 0 ) {
return 0;
}
if ( ! m_calendars.contains( calendar ) ) {
m_calendars[ calendar ] = new Calendar( this, calendar, parent() );
}
return m_calendars[ calendar ];
}
QVariant Scripting::Project::calendarData(const KPlato::Calendar* calendar, const QString& property, const QString& role, long int )
{
QModelIndex idx = m_calendarModel.index( calendar );
idx = m_calendarModel.index( idx.row(), calendarColumnNumber( property ), idx.parent() );
if ( ! idx.isValid() ) {
return QVariant();
}
int r = stringToRole( role, m_calendarprogramroles.value( idx.column() ) );
if ( r < 0 ) {
return QVariant(); // invalid role
}
debugPlanScripting<<"data:"<<calendar<<property<<role<<":"<<idx<<m_calendarModel.data( idx, r );
return m_calendarModel.data( idx, r );
}
QVariant Scripting::Project::setCalendarData( KPlato::Calendar *calendar, const QString &property, const QVariant &data, const QString &role )
{
QModelIndex idx = m_calendarModel.index( calendar, calendarColumnNumber( property ) );
if ( ! idx.isValid() ) {
return "Invalid";
}
if ( ( m_calendarModel.flags( idx ) & Qt::ItemIsEditable ) == 0 ) {
return "ReadOnly";
}
int datarole = stringToRole( role, Qt::EditRole );
if ( datarole < 0 ) {
return QString("Invalid role: " + role);
}
if ( calendarData( calendar, property, datarole == Qt::EditRole ? "ProgramRole" : role ) == data ) {
return "Success";
}
return m_calendarModel.setData( idx, data, datarole ) ? "Success" : "Error";
}
int Scripting::Project::calendarColumnNumber(const QString& property) const
{
int col = m_calendarModel.columnNumber( property );
debugPlanScripting<<"calendarColumnNumber:"<<property<<"="<<col;
return col;
}
QVariant Scripting::Project::calendarHeaderData( const QString &property, const QString &role )
{
int col = calendarColumnNumber( property );
return m_calendarModel.headerData( col, Qt::Horizontal, stringToRole( role ) );
}
QStringList Scripting::Project::calendarPropertyList()
{
//FIXME: fake this for now
QStringList lst;
lst << "Name" << "TimeZone" << "Weekday" << "Date";
return lst;
}
//-----------------------
int Scripting::Project::accountCount() const
{
return kplatoProject()->accounts().accountCount();
}
QObject *Scripting::Project::accountAt( int index )
{
return account( kplatoProject()->accounts().accountAt( index ) );
}
QObject *Scripting::Project::findAccount( const QString &id )
{
KPlato::Account *a = kplatoProject()->accounts().findAccount( id );
debugPlanScripting<<id<<a;
return a == 0 ? 0 : account( a );
}
QObject *Scripting::Project::account( KPlato::Account *account )
{
if ( account == 0 ) {
return 0;
}
if ( ! m_accounts.contains( account ) ) {
m_accounts[ account ] = new Account( this, account, parent() );
}
return m_accounts[ account ];
}
QObject *Scripting::Project::createAccount( QObject *parent )
{
Account *par = qobject_cast<Account*>( parent );
KPlato::Account *p = par ? par->kplatoAccount() : 0;
KPlato::Account *a = new KPlato::Account();
AddAccountCmd *cmd = new AddAccountCmd( *kplatoProject(), a, p );
m_module->addCommand( cmd );
return account( a );
}
QVariant Scripting::Project::accountHeaderData( const QString &property, const QString &role )
{
int col = accountColumnNumber( property );
return m_accountModel.headerData( col, Qt::Horizontal, stringToRole( role ) );
}
QStringList Scripting::Project::accountPropertyList()
{
QStringList lst;
QMetaEnum e = m_accountModel.columnMap();
for ( int i = 0; i < e.keyCount(); ++i ) {
lst << QString( e.key( i ) );
}
return lst;
}
int Scripting::Project::accountColumnNumber( const QString &property ) const
{
return m_accountModel.columnMap().keyToValue( property.toUtf8() );
}
QVariant Scripting::Project::setAccountData( KPlato::Account *account, const QString &property, const QVariant &data, const QString &role )
{
QModelIndex idx = m_accountModel.index( account, accountColumnNumber( property ) );
if ( ! idx.isValid() ) {
return "Invalid";
}
if ( ( m_accountModel.flags( idx ) & Qt::ItemIsEditable ) == 0 ) {
return "ReadOnly";
}
int datarole = stringToRole( role, Qt::EditRole );
if ( datarole < 0 ) {
return QString("Invalid role: " + role);
}
if ( accountData( account, property, datarole == Qt::EditRole ? "ProgramRole" : role ) == data ) {
return "Success";
}
return m_accountModel.setData( idx, data, datarole ) ? "Success" : "Error";
}
QVariant Scripting::Project::accountData( const KPlato::Account *account, const QString &property, const QString &role, long /*schedule*/ )
{
QModelIndex idx = m_accountModel.index( account );
idx = m_accountModel.index( idx.row(), accountColumnNumber( property ), idx.parent() );
if ( ! idx.isValid() ) {
return QVariant();
}
int r = stringToRole( role );
if ( r < 0 ) {
return QVariant();
}
return m_accountModel.data( idx, r );
}
int Scripting::Project::stringToRole( const QString &role, int programrole ) const
{
if ( role == "ProgramRole" ) {
return programrole;
}
const QMetaEnum e = metaObject()->enumerator( metaObject()->indexOfEnumerator("Roles") );
return e.keyToValue( role.toUtf8() );
}
void Scripting::Project::addCommand( KUndo2Command *cmd )
{
slotAddCommand( cmd );
}
void Scripting::Project::slotAddCommand( KUndo2Command *cmd )
{
m_module->addCommand( cmd );
}
diff --git a/src/plugins/scripting/Resource.cpp b/src/plugins/scripting/Resource.cpp
index 6b27ead9..7bc6c2c1 100644
--- a/src/plugins/scripting/Resource.cpp
+++ b/src/plugins/scripting/Resource.cpp
@@ -1,115 +1,116 @@
/* This file is part of the Calligra project
* Copyright (c) 2008, 2011 Dag Andersen <danders@get2net>
*
* 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 "Resource.h"
#include "Project.h"
#include "ScriptingDebug.h"
#include "kptresource.h"
#include "kptappointment.h"
#include "kptdatetime.h"
#include "kptcommand.h"
Scripting::Resource::Resource( Scripting::Project *project, KPlato::Resource *resource, QObject *parent )
: QObject( parent ), m_project( project ), m_resource( resource )
{
}
QObject *Scripting::Resource::project()
{
return m_project;
}
QVariant Scripting::Resource::type()
{
return m_resource->typeToString();
}
QString Scripting::Resource::id() const
{
return m_resource->id();
}
QVariantList Scripting::Resource::appointmentIntervals( qlonglong schedule ) const
{
KPlato::Appointment app = m_resource->appointmentIntervals( schedule );
QVariantList lst;
foreach ( const KPlato::AppointmentInterval &ai, app.intervals().map() ) {
lst << QVariant( QVariantList() << ai.startTime().toString() << ai.endTime().toString() << ai.load() );
}
return lst;
}
void Scripting::Resource::addExternalAppointment( const QVariant &id, const QString &name, const QVariantList &lst )
{
m_project->addExternalAppointment( this, id, name, lst );
}
QVariantList Scripting::Resource::externalAppointments() const
{
KPlato::AppointmentIntervalList ilst = m_resource->externalAppointments();
QVariantList lst;
foreach ( const KPlato::AppointmentInterval &ai, ilst.map() ) {
lst << QVariant( QVariantList() << ai.startTime().toString() << ai.endTime().toString() << ai.load() );
}
return lst;
}
void Scripting::Resource::clearExternalAppointments( const QString &id )
{
m_project->clearExternalAppointments( this, id );
}
int Scripting::Resource::childCount() const
{
return kplatoResource()->type() == KPlato::Resource::Type_Team ? kplatoResource()->teamMembers().count() : 0;
}
QObject *Scripting::Resource::childAt( int index ) const
{
if ( kplatoResource()->type() == KPlato::Resource::Type_Team ) {
return m_project->resource( kplatoResource()->teamMembers().value( index ) );
}
return 0;
}
void Scripting::Resource::setChildren( const QList<QObject*> &children )
{
debugPlanScripting<<"setTeamMembers:"<<children;
KPlato::Resource *team = kplatoResource();
// atm. only teams have children
if ( team->type() != KPlato::Resource::Type_Team ) {
return;
}
KPlato::MacroCommand *cmd = new KPlato::MacroCommand( kundo2_i18n( "Set resource team members" ) );
foreach ( const QString &id, team->teamMemberIds() ) {
cmd->addCommand( new KPlato::RemoveResourceTeamCmd( team, id ) );
}
foreach ( QObject *o, children ) {
Resource *r = qobject_cast<Resource*>( o );
if ( r && r->kplatoResource() ) {
cmd->addCommand( new KPlato::AddResourceTeamCmd( team, r->kplatoResource()->id() ) );
}
}
if ( ! cmd->isEmpty() ) {
m_project->addCommand( cmd );
}
debugPlanScripting<<"setTeamMembers:"<<team->teamMembers();
}
diff --git a/src/plugins/scripting/ResourceGroup.cpp b/src/plugins/scripting/ResourceGroup.cpp
index f46b078c..2ebde5a7 100644
--- a/src/plugins/scripting/ResourceGroup.cpp
+++ b/src/plugins/scripting/ResourceGroup.cpp
@@ -1,64 +1,65 @@
/* This file is part of the Calligra project
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* 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 "ResourceGroup.h"
#include "Project.h"
#include <kptresource.h>
Scripting::ResourceGroup::ResourceGroup( Scripting::Project *project, KPlato::ResourceGroup *group, QObject *parent )
: QObject( parent ), m_project( project ), m_group( group )
{
}
QObject *Scripting::ResourceGroup::project()
{
return m_project;
}
QString Scripting::ResourceGroup::id()
{
return m_group->id();
}
QString Scripting::ResourceGroup::type()
{
return m_group->typeToString();
}
int Scripting::ResourceGroup::resourceCount() const
{
return m_group->numResources();
}
QObject *Scripting::ResourceGroup::resourceAt( int index ) const
{
return m_project->resource( m_group->resourceAt( index ) );
}
int Scripting::ResourceGroup::childCount() const
{
return m_group->numResources();
}
QObject *Scripting::ResourceGroup::childAt( int index ) const
{
return m_project->resource( m_group->resourceAt( index ) );
}
diff --git a/src/plugins/scripting/Schedule.cpp b/src/plugins/scripting/Schedule.cpp
index 4e1697f6..015fb590 100644
--- a/src/plugins/scripting/Schedule.cpp
+++ b/src/plugins/scripting/Schedule.cpp
@@ -1,67 +1,68 @@
/* This file is part of the Calligra project
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* 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 "Schedule.h"
#include "Project.h"
#include <kptschedule.h>
Scripting::Schedule::Schedule( Scripting::Project *project, KPlato::ScheduleManager *schedule, QObject *parent )
: QObject( parent ), m_project( project ), m_schedule( schedule )
{
}
qlonglong Scripting::Schedule::id() const
{
return (qlonglong)m_schedule ? m_schedule->scheduleId() : -1;
}
QString Scripting::Schedule::name() const
{
return m_schedule ? m_schedule->name() : "";
}
bool Scripting::Schedule::isScheduled() const
{
return m_schedule ? m_schedule->isScheduled() : false;
}
QDate Scripting::Schedule::startDate()
{
return QDate(); //m_schedule->startTime().dateTime().date();
}
QDate Scripting::Schedule::endDate()
{
return QDate(); //m_schedule->endTime().dateTime().date();
}
int Scripting::Schedule::childCount() const
{
return m_schedule ? m_schedule->childCount() : 0;
}
QObject *Scripting::Schedule::childAt( int index )
{
if ( m_schedule && m_project ) {
return m_project->schedule( m_schedule->childAt( index ) );
}
return 0;
}
diff --git a/src/plugins/scripting/ScriptingDebug.cpp b/src/plugins/scripting/ScriptingDebug.cpp
index a4268cec..b5b2624c 100644
--- a/src/plugins/scripting/ScriptingDebug.cpp
+++ b/src/plugins/scripting/ScriptingDebug.cpp
@@ -1,27 +1,28 @@
/*
* This file is part of KPlato
*
* Copyright (c) 2016 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "ScriptingDebug.h"
const QLoggingCategory &PLANSCRIPTING_LOG()
{
static const QLoggingCategory category("calligra.plan.scripting");
return category;
}
diff --git a/src/plugins/scripting/ScriptingPart.cpp b/src/plugins/scripting/ScriptingPart.cpp
index d1599252..5ae1ca6e 100644
--- a/src/plugins/scripting/ScriptingPart.cpp
+++ b/src/plugins/scripting/ScriptingPart.cpp
@@ -1,61 +1,62 @@
/*
* This file is part of KPlato
*
* Copyright (c) 2006 Sebastian Sauer <mail@dipe.org>
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "ScriptingPart.h"
#include "Module.h"
#include "ScriptingDebug.h"
#include <QStandardPaths>
// KF5
#include <kpluginfactory.h>
// kross
#include <kross/core/manager.h>
#include <kross/core/interpreter.h>
#include <kross/core/action.h>
#include <kross/ui/model.h>
// calligra
#include <KoDockRegistry.h>
#include <KoMainWindow.h>
#include <KoDocument.h>
#include <kptview.h>
K_PLUGIN_FACTORY_WITH_JSON(KPlatoScriptingFactory, "planscripting.json",
registerPlugin<KPlatoScriptingPart>();)
KPlatoScriptingPart::KPlatoScriptingPart(QObject *parent, const QVariantList &args)
: KoScriptingPart(new Scripting::Module(parent))
{
Q_UNUSED(args);
//setComponentData(ScriptingPart::componentData());
setXMLFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligraplan/viewplugins/scripting.rc"), true);
debugPlanScripting <<"PlanScripting plugin. Class:" << metaObject()->className() <<", Parent:" <<(parent?parent->metaObject()->className():"0");
}
KPlatoScriptingPart::~KPlatoScriptingPart()
{
}
#include "ScriptingPart.moc"
diff --git a/src/plugins/scripting/ScriptingWidgets.cpp b/src/plugins/scripting/ScriptingWidgets.cpp
index 8bccb346..e8a503ef 100644
--- a/src/plugins/scripting/ScriptingWidgets.cpp
+++ b/src/plugins/scripting/ScriptingWidgets.cpp
@@ -1,225 +1,226 @@
/*
* This file is part of KPlato
*
* Copyright (c) 2007 Sebastian Sauer <mail@dipe.org>
* Copyright (c) 2008 Dag Andersen <danders@get2net.dk>
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "ScriptingWidgets.h"
#include "Module.h"
#include "Project.h"
#include "ScriptingDebug.h"
#include "kptproject.h"
#include "kptschedule.h"
#include "kptnodeitemmodel.h"
#include <KLocalizedString>
#include <QVBoxLayout>
#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QStringList>
#include <QListWidget>
#include <QListWidgetItem>
ScriptingScheduleListView::ScriptingScheduleListView(Scripting::Module* module, QWidget* parent)
: QWidget(parent), m_module(module)
{
debugPlanScripting<<this<<parent;
if ( parent->layout() ) {
parent->layout()->addWidget( this );
}
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
setLayout(layout);
m_view = new QTreeView(this);
m_view->setAlternatingRowColors(true);
m_view->setRootIsDecorated(false);
m_view->setSortingEnabled(false);
m_view->setItemsExpandable(false);
// m_view->setEditTriggers(QAbstractItemView::AllEditTriggers);
QStandardItemModel *model = new QStandardItemModel(m_view);
model->setHorizontalHeaderLabels( QStringList() << i18n( "Schedule Name" ) );
KPlato::Project *p = static_cast<Scripting::Project*>( m_module->project() )->kplatoProject();
debugPlanScripting<<p;
foreach ( KPlato::ScheduleManager *sm, p->allScheduleManagers() ) {
if ( sm->isScheduled() ) {
QStandardItem *i = new QStandardItem( sm->name() );
i->setData( (qlonglong)sm->scheduleId() );
model->appendRow( i );
debugPlanScripting<<i<<model->rowCount();
}
}
layout->addWidget(m_view);
m_view->setModel( model );
}
ScriptingScheduleListView::~ScriptingScheduleListView()
{
debugPlanScripting<<"gone!";
}
QVariant ScriptingScheduleListView::currentSchedule() const
{
QModelIndex i = m_view->currentIndex();
debugPlanScripting<<i<<i.isValid();
if ( ! i.isValid() ) {
debugPlanScripting<<"index not valid";
return -1;
}
debugPlanScripting<<m_view->model();
QStandardItem *item = static_cast<QStandardItemModel*>(m_view->model())->itemFromIndex( i );
debugPlanScripting<<item;
if ( item == 0 ) {
return -1;
}
debugPlanScripting<<item->data();
return item->data();
}
//--------------------------------
ScriptingNodePropertyListView::ScriptingNodePropertyListView(Scripting::Module* module, QWidget* parent)
: KActionSelector( parent ),
m_module(module)
{
debugPlanScripting<<this<<parent;
KPlato::NodeModel m;
const QMetaEnum e = m.columnMap();
if ( e.keyCount() > 0 ) {
QListWidgetItem *item = new QListWidgetItem( m.headerData( 0 ).toString() );
item->setToolTip( m.headerData( 0 ).toString() );
item->setData( Qt::UserRole, e.key( 0 ) ); // should be name
selectedListWidget()->addItem( item );
for ( int i = 1; i < e.keyCount(); ++i ) {
QListWidgetItem *item = new QListWidgetItem( m.headerData( i ).toString() );
item->setToolTip( m.headerData( i ).toString() );
item->setData( Qt::UserRole, e.key( i ) );
availableListWidget()->addItem( item );
}
}
}
ScriptingNodePropertyListView::~ScriptingNodePropertyListView()
{
}
QVariant ScriptingNodePropertyListView::selectedProperties() const
{
QStringList lst;
QListWidget *s = selectedListWidget();
for ( int i = 0; i < s->count(); ++i ) {
lst << s->item( i )->data( Qt::UserRole ).toString();
}
return lst;
}
//--------------------------------
ScriptingDataQueryView::ScriptingDataQueryView(Scripting::Module* module, QWidget* parent)
: QWidget( parent ),
m_module(module)
{
debugPlanScripting<<this<<parent;
setupUi( this );
setup();
connect( ui_objectType, SIGNAL(currentIndexChanged(int)), SLOT(slotObjectTypeChanged(int)) );
}
ScriptingDataQueryView::~ScriptingDataQueryView()
{
}
void ScriptingDataQueryView::setup()
{
slotObjectTypeChanged( objectType().toInt() );
}
void ScriptingDataQueryView::setupLists( QListWidget *list, const QString &tag, const QString &property, const QString &tooltip )
{
QListWidgetItem *item = new QListWidgetItem( property );
item->setToolTip( tooltip );
item->setData( Qt::UserRole, tag );
list->addItem( item );
}
void ScriptingDataQueryView::slotObjectTypeChanged( int /*index*/ )
{
ui_properties->availableListWidget()->clear();
ui_properties->selectedListWidget()->clear();
QMetaEnum e;
switch ( objectType().toInt() ) {
case 0: {
KPlato::NodeModel m; e = m.columnMap();
if ( e.keyCount() > 0 ) {
setupLists( ui_properties->selectedListWidget(), e.key( 0 ), m.headerData( 0 ).toString(), m.headerData( 0, Qt::ToolTipRole ).toString() );
for ( int i = 1; i < e.keyCount(); ++i ) {
setupLists( ui_properties->availableListWidget(), e.key( i ), m.headerData( i ).toString(), m.headerData( i, Qt::ToolTipRole ).toString() );
}
}
break;
}
case 1: {
KPlato::ResourceModel m; e = m.columnMap();
if ( e.keyCount() > 0 ) {
setupLists( ui_properties->selectedListWidget(), e.key( 0 ), m.headerData( 0 ).toString(), m.headerData( 0, Qt::ToolTipRole ).toString() );
for ( int i = 1; i < e.keyCount(); ++i ) {
setupLists( ui_properties->availableListWidget(), e.key( i ), m.headerData( i ).toString(), m.headerData( i, Qt::ToolTipRole ).toString() );
}
}
break;
}
case 2: {
KPlato::AccountModel m; e = m.columnMap();
if ( e.keyCount() > 0 ) {
setupLists( ui_properties->selectedListWidget(), e.key( 0 ), m.headerData( 0 ).toString(), m.headerData( 0, Qt::ToolTipRole ).toString() );
for ( int i = 1; i < e.keyCount(); ++i ) {
setupLists( ui_properties->availableListWidget(), e.key( i ), m.headerData( i ).toString(), m.headerData( i, Qt::ToolTipRole ).toString() );
}
}
break;
}
default:
break;
}
}
QVariant ScriptingDataQueryView::includeHeaders() const
{
return ui_includeHeaders->checkState() == Qt::Checked;
}
QVariant ScriptingDataQueryView::objectType() const
{
return ui_objectType->currentIndex();
}
QVariant ScriptingDataQueryView::selectedProperties() const
{
QStringList lst;
QListWidget *s = ui_properties->selectedListWidget();
for ( int i = 0; i < s->count(); ++i ) {
lst << s->item( i )->data( Qt::UserRole ).toString();
}
return lst;
}
diff --git a/src/plugins/scripting/tests/ScriptingTester.cpp b/src/plugins/scripting/tests/ScriptingTester.cpp
index 2a9d8ac7..cfe10cd1 100644
--- a/src/plugins/scripting/tests/ScriptingTester.cpp
+++ b/src/plugins/scripting/tests/ScriptingTester.cpp
@@ -1,97 +1,98 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2011 Dag Andersen <danders@get2net.dk>
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 "ScriptingTester.h"
#include "TestResult.h"
#include "Module.h"
#include <QTest>
#include <Kross/Core/Action>
#include <Kross/Core/Manager>
namespace KPlato
{
QStringList ScriptingTester::initTestList()
{
QStringList scripts;
scripts << "project_access.py"
<< "account_access.py"
<< "account_readwrite.py"
<< "calendar_access.py"
<< "calendar_readwrite.py"
<< "task_access.py"
<< "task_readwrite.py"
<< "resource_access.py"
<< "resource_readwrite.py"
<< "resource_team.py"
;
return scripts;
}
void ScriptingTester::initTestCase()
{
Kross::Action *a = new Kross::Action( 0, "PythonDetection" );
a->setInterpreter( "python" );
bool py = a->initialize();
a->finalize();
if ( ! py ) {
QEXPECT_FAIL( "", "Python not available, tests will not be executed", Continue );
QVERIFY( py == true );
} else {
m_module = new Scripting::Module( this );
m_result = new TestResult( this );
Kross::Manager::self().addObject( m_module, "Plan" );
Kross::Manager::self().addObject( m_result, "TestResult" );
QStringList scripts = initTestList();
for ( int i = 0; i < scripts.count(); ++i ) {
//Create a new Kross::Action instance.
Kross::Action* action = new Kross::Action(0, QString( "%1" ).arg( scripts.at( i ) ) );
// Set the script file we like to execute.
action->setFile( QString( "%1/%2" ).arg( FILES_DATA_DIR ).arg( scripts.at( i ) ) );
m_tests << action;
}
}
}
void ScriptingTester::cleanupTestCase()
{
while ( ! m_tests.isEmpty() ) {
delete m_tests.takeFirst();
}
}
void ScriptingTester::test()
{
qDebug()<<m_tests;
foreach ( Kross::Action *a, m_tests ) {
m_result->setResult( false );
m_result->message = QString( "%1: Failed to run test" ).arg( a->objectName() );
a->trigger();
QVERIFY2( m_result->isOk(), m_result->message.toLocal8Bit() );
qDebug() << "PASS: " << a->objectName();
}
}
} //namespace KPlato
QTEST_GUILESS_MAIN( KPlato::ScriptingTester )
diff --git a/src/plugins/scripting/tests/TestResult.cpp b/src/plugins/scripting/tests/TestResult.cpp
index a2734ad4..17d38653 100644
--- a/src/plugins/scripting/tests/TestResult.cpp
+++ b/src/plugins/scripting/tests/TestResult.cpp
@@ -1,61 +1,62 @@
/*
* This file is part of KPlato
*
* Copyright (c) 2006 Sebastian Sauer <mail@dipe.org>
* Copyright (c) 2008 Dag Andersen <danders@get2net>
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "TestResult.h"
#include "kplatoscripting_export.h"
#include <QDebug>
extern "C"
{
KPLATOSCRIPTING_EXPORT QObject* krossmodule()
{
return new TestResult();
}
}
TestResult::TestResult(QObject* parent)
: QObject(parent)
, m_result( false )
{
setObjectName( "TestResult" );
}
TestResult::~TestResult()
{
}
bool TestResult::isOk() const
{
return m_result;
}
void TestResult::setResult( bool ok)
{
m_result = ok;
}
void TestResult::setMessage( const QString &m )
{
qDebug()<<m;
message = m;
}
diff --git a/src/tests/InsertFileTester.cpp b/src/tests/InsertFileTester.cpp
index 7cd0bc29..e8dfca3b 100644
--- a/src/tests/InsertFileTester.cpp
+++ b/src/tests/InsertFileTester.cpp
@@ -1,142 +1,143 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "InsertFileTester.h"
#include "kptcommand.h"
#include "kptmaindocument.h"
#include "kptcalendar.h"
#include "kptresource.h"
#include "kpttask.h"
#include "kptnode.h"
#include <QTest>
#include <QUrl>
#include <QFileInfo>
namespace KPlato
{
void InsertFileTester::testVersion_0_6()
{
Part part;
QFileInfo file( "version-0-6.kplato" );
QVERIFY( file.exists() );
bool res = part.openUrl( QUrl::fromLocalFile( file.absoluteFilePath() ) );
QVERIFY( res );
Project &p = part.getProject();
QCOMPARE( p.name(), QString( "P1" ) );
QCOMPARE( p.leader(), QString( "pp" ) );
QCOMPARE( p.numChildren(), 2 );
QCOMPARE( p.numResourceGroups(), 1 );
QCOMPARE( p.resourceList().count(), 1 );
QCOMPARE( p.allCalendars().count(), 2 );
Node *n = p.childNode( 0 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "T1" ) );
n = p.childNode( 1 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "T2" ) );
}
void InsertFileTester::testProject_stats1()
{
Part part;
QFileInfo file( "project_stats1.kplato" );
QVERIFY( file.exists() );
bool res = part.openUrl( QUrl::fromLocalFile( file.absoluteFilePath() ) );
QVERIFY( res );
Project &p = part.getProject();
QCOMPARE( p.name(), QString( "P1" ) );
QCOMPARE( p.leader(), QString( "Robin" ) );
QCOMPARE( p.numChildren(), 1 );
QCOMPARE( p.numResourceGroups(), 1 );
QCOMPARE( p.resourceList().count(), 1 );
QCOMPARE( p.allCalendars().count(), 1 );
Node *n = p.childNode( 0 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "T1" ) );
}
void InsertFileTester::testPert1()
{
Part part;
QFileInfo file( "pert1.kplato" );
QVERIFY( file.exists() );
bool res = part.openUrl( QUrl::fromLocalFile( file.absoluteFilePath() ) );
QVERIFY( res );
Project &p = part.getProject();
QCOMPARE( p.name(), QString( "PERT 1" ) );
QCOMPARE( p.leader(), QString( "PM" ) );
QCOMPARE( p.numChildren(), 7 );
QCOMPARE( p.numResourceGroups(), 0 );
QCOMPARE( p.resourceList().count(), 0 );
QCOMPARE( p.allCalendars().count(), 1 );
Node *n = p.childNode( 0 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "a" ) );
n = p.childNode( 1 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "b" ) );
n = p.childNode( 2 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "c" ) );
n = p.childNode( 3 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "d" ) );
n = p.childNode( 4 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "e" ) );
n = p.childNode( 5 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "f" ) );
n = p.childNode( 6 );
QCOMPARE( n->type(), (int)Node::Type_Task );
QCOMPARE( n->name(), QString( "g" ) );
}
} //namespace KPlato
QTEST_MAIN(KPlato::InsertFileTester)
diff --git a/src/tests/InsertProjectTester.cpp b/src/tests/InsertProjectTester.cpp
index 706453fa..ea82e02c 100644
--- a/src/tests/InsertProjectTester.cpp
+++ b/src/tests/InsertProjectTester.cpp
@@ -1,672 +1,673 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "InsertProjectTester.h"
#include "kptcommand.h"
#include "kptmaindocument.h"
#include "kptpart.h"
#include "kptcalendar.h"
#include "kptresource.h"
#include "kpttask.h"
#include "kptaccount.h"
#include <KoXmlReader.h>
#include <QTest>
namespace KPlato
{
Account *InsertProjectTester::addAccount( MainDocument &part, Account *parent )
{
Project &p = part.getProject();
Account *a = new Account();
QString s = parent == 0 ? "Account" : parent->name();
a->setName( p.accounts().uniqueId( s ) );
KUndo2Command *c = new AddAccountCmd( p, a, parent );
part.addCommand( c );
return a;
}
void InsertProjectTester::testAccount()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addAccount( part );
Project &p = part.getProject();
QCOMPARE( p.accounts().accountCount(), 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QCOMPARE( part2.getProject().accounts().accountCount(), 1 );
part2.insertProject( part.getProject(), 0, 0 );
QCOMPARE( part2.getProject().accounts().accountCount(), 1 );
Part ppB(0);
MainDocument partB( &ppB );
ppB.setDocument( &partB );
Account *parent = addAccount( partB );
QCOMPARE( partB.getProject().accounts().accountCount(), 1 );
addAccount( partB, parent );
QCOMPARE( partB.getProject().accounts().accountCount(), 1 );
QCOMPARE( parent->childCount(), 1 );
part2.insertProject( partB.getProject(), 0, 0 );
QCOMPARE( part2.getProject().accounts().accountCount(), 1 );
QCOMPARE( part2.getProject().accounts().accountAt( 0 )->childCount(), 1 );
}
Calendar *InsertProjectTester::addCalendar( MainDocument &part )
{
Project &p = part.getProject();
Calendar *c = new Calendar();
p.setCalendarId( c );
part.addCommand( new CalendarAddCmd( &p, c, -1, 0 ) );
return c;
}
void InsertProjectTester::testCalendar()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
Project &p = part.getProject();
QVERIFY( p.calendarCount() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( part.getProject(), 0, 0 );
QVERIFY( part2.getProject().calendarCount() == 1 );
QVERIFY( part2.getProject().defaultCalendar() == 0 );
}
void InsertProjectTester::testDefaultCalendar()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
Calendar *c = addCalendar( part );
Project &p = part.getProject();
p.setDefaultCalendar( c );
QVERIFY( p.calendarCount() == 1 );
QCOMPARE( p.defaultCalendar(), c );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().calendarCount() == 1 );
QCOMPARE( part2.getProject().defaultCalendar(), c );
Part ppB(0);
MainDocument partB( &ppB );
ppB.setDocument( &partB );
Calendar *c2 = addCalendar( partB );
partB.getProject().setDefaultCalendar( c2 );
part2.insertProject( partB.getProject(), 0, 0 );
QVERIFY( part2.getProject().calendarCount() == 2 );
QCOMPARE( part2.getProject().defaultCalendar(), c ); // NB: still c, not c2
}
ResourceGroup *InsertProjectTester::addResourceGroup( MainDocument &part )
{
Project &p = part.getProject();
ResourceGroup *g = new ResourceGroup();
KUndo2Command *c = new AddResourceGroupCmd( &p, g );
part.addCommand( c );
QString s = QString( "G%1" ).arg( part.getProject().indexOf( g ) );
g->setName( s );
return g;
}
void InsertProjectTester::testResourceGroup()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addResourceGroup( part );
Project &p = part.getProject();
QVERIFY( p.resourceGroupCount() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().resourceGroupCount() == 1 );
}
Resource *InsertProjectTester::addResource( MainDocument &part, ResourceGroup *g )
{
Project &p = part.getProject();
if ( g == 0 ) {
g = p.resourceGroupAt( 0 );
}
Resource *r = new Resource();
KUndo2Command *c = new AddResourceCmd( g, r );
part.addCommand( c );
QString s = QString( "%1.R%2" ).arg( r->parentGroup()->name() ).arg( r->parentGroup()->indexOf( r ) );
r->setName( s );
return r;
}
void InsertProjectTester::testResource()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addResourceGroup( part );
addResource( part );
Project &p = part.getProject();
QVERIFY( p.resourceGroupAt( 0 )->numResources() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
}
void InsertProjectTester::testTeamResource()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addResourceGroup( part );
Resource *r = addResource( part );
r->setType( Resource::Type_Team );
ResourceGroup *tg = addResourceGroup( part );
Resource *t1 = addResource( part, tg );
Resource *t2 = addResource( part, tg );
r->setRequiredIds( QStringList() << t1->id() << t2->id() );
Project &p = part.getProject();
QVERIFY( p.resourceGroupAt( 0 )->numResources() == 1 );
QVERIFY( p.resourceGroupAt( 1 )->numResources() == 2 );
QList<Resource*> required = p.resourceGroupAt( 0 )->resources().at( 0 )->requiredResources();
QCOMPARE( required.count(), 2 );
QCOMPARE( required.at( 0 ), t1 );
QCOMPARE( required.at( 1 ), t2 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
Project &p2 = part2.getProject();
QVERIFY( p2.resourceGroupAt( 0 )->numResources() == 1 );
QVERIFY( p2.resourceGroupAt( 1 )->numResources() == 2 );
required = p2.resourceGroupAt( 0 )->resources().at( 0 )->requiredResources();
QCOMPARE( required.count(), 2 );
QCOMPARE( required.at( 0 ), t1 );
QCOMPARE( required.at( 1 ), t2 );
}
void InsertProjectTester::testResourceAccount()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addResourceGroup( part );
Resource *r = addResource( part );
Account *a = addAccount( part );
part.addCommand( new ResourceModifyAccountCmd( *r, r->account(), a ) );
Project &p = part.getProject();
QVERIFY( p.resourceGroupAt( 0 )->numResources() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
QVERIFY( part2.getProject().accounts().allAccounts().contains( a ) );
QCOMPARE( part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->account(), a );
}
void InsertProjectTester::testResourceCalendar()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
Calendar *c = addCalendar( part );
Project &p = part.getProject();
QVERIFY( p.calendarCount() == 1 );
addResourceGroup( part );
Resource *r = addResource( part );
part.addCommand( new ModifyResourceCalendarCmd( r, c ) );
QVERIFY( p.resourceGroupAt( 0 )->numResources() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
QCOMPARE( part2.getProject().allCalendars().count(), 1 );
QVERIFY( part2.getProject().allCalendars().contains( c ) );
QCOMPARE( part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->calendar( true ), c );
}
Task *InsertProjectTester::addTask( MainDocument &part )
{
Project &p = part.getProject();
Task *t = new Task();
t->setId( p.uniqueNodeId() );
KUndo2Command *c = new TaskAddCmd( &p, t, 0 );
part.addCommand( c );
return t;
}
void InsertProjectTester::testTask()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addTask( part );
Project &p = part.getProject();
QVERIFY( p.numChildren() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().numChildren() == 1 );
}
void InsertProjectTester::addGroupRequest( MainDocument &part )
{
Project &p = part.getProject();
Task *t = static_cast<Task*>( p.childNode( 0 ) );
KUndo2Command *c = new AddResourceGroupRequestCmd( *t, new ResourceGroupRequest( p.resourceGroupAt( 0 ), 1 ) );
part.addCommand( c );
}
void InsertProjectTester::testGroupRequest()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
addResourceGroup( part );
addResource( part );
addTask( part );
addGroupRequest( part );
Project &p = part.getProject();
QVERIFY( p.numChildren() == 1 );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
Project &p2 = part2.getProject();
QVERIFY( p2.childNode( 0 )->resourceGroupRequest( p2.resourceGroupAt( 0 ) ) != 0 );
}
void InsertProjectTester::addResourceRequest( MainDocument &part )
{
Project &p = part.getProject();
ResourceGroupRequest *g = p.childNode( 0 )->requests().requests().at( 0 );
KUndo2Command *c = new AddResourceRequestCmd( g, new ResourceRequest( p.resourceGroupAt( 0 )->resourceAt( 0 ), 1 ) );
part.addCommand( c );
}
void InsertProjectTester::testResourceRequest()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
addResourceGroup( part );
addResource( part );
addTask( part );
addGroupRequest( part );
addResourceRequest( part );
Project &p = part.getProject();
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
Project &p2 = part2.getProject();
QVERIFY( p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) ) != 0 );
}
void InsertProjectTester::testTeamResourceRequest()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
addResourceGroup( part );
Resource *r = addResource( part );
r->setType( Resource::Type_Team );
ResourceGroup *tg = addResourceGroup( part );
Resource *t1 = addResource( part, tg );
r->addTeamMemberId( t1->id() );
Resource *t2 = addResource( part, tg );
r->addTeamMemberId( t2->id() );
addTask( part );
addGroupRequest( part );
addResourceRequest( part );
qDebug()<<"Start test:";
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( part.getProject(), 0, 0 );
Project &p2 = part2.getProject();
ResourceRequest *rr = p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QCOMPARE( rr->resource(), r );
QCOMPARE( rr->resource()->teamMembers().count(), 2 );
QCOMPARE( rr->resource()->teamMembers().at( 0 ), t1 );
QCOMPARE( rr->resource()->teamMembers().at( 1 ), t2 );
}
Relation *InsertProjectTester::addDependency( MainDocument &part, Task *t1, Task *t2 )
{
Project &p = part.getProject();
Relation *r = new Relation( t1, t2 );
part.addCommand( new AddRelationCmd( p, r ) );
return r;
}
void InsertProjectTester::testDependencies()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
Task *t1 = addTask( part );
Task *t2 = addTask( part );
QCOMPARE( t1->numDependChildNodes(), 0 );
QCOMPARE( t2->numDependParentNodes(), 0 );
Relation *r = addDependency( part, t1, t2 );
QCOMPARE( t1->numDependChildNodes(), 1 );
QCOMPARE( t2->numDependParentNodes(), 1 );
QCOMPARE( t1->getDependChildNode( 0 ), r );
QCOMPARE( t2->getDependParentNode( 0 ), r );
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
QVERIFY( part2.insertProject( part.getProject(), 0, 0 ) );
Project &p2 = part2.getProject();
QVERIFY( p2.numChildren() == 2 );
QCOMPARE( p2.childNode( 0 ), t1 );
QCOMPARE( p2.childNode( 1 ), t2 );
QCOMPARE( t1->numDependChildNodes(), 1 );
QCOMPARE( t2->numDependParentNodes(), 1 );
QCOMPARE( t1->getDependChildNode( 0 ), r );
QCOMPARE( t2->getDependParentNode( 0 ), r );
}
void InsertProjectTester::testExistingResourceAccount()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addResourceGroup( part );
Resource *r = addResource( part );
Account *a = addAccount( part );
part.addCommand( new ResourceModifyAccountCmd( *r, r->account(), a ) );
Project &p = part.getProject();
QVERIFY( p.resourceGroupAt( 0 )->numResources() == 1 );
QDomDocument doc = part.saveXML();
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
QVERIFY( part2.getProject().accounts().allAccounts().contains( a ) );
QCOMPARE( part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->account(), a );
part2.addCommand( new ResourceModifyAccountCmd( *(part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )), part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->account(), 0 ) );
KoXmlDocument xdoc;
xdoc.setContent( doc.toString() );
part.loadXML( xdoc, 0 );
part2.insertProject( part.getProject(), 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
QVERIFY( part2.getProject().accounts().allAccounts().contains( a ) );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->account() == 0 );
}
void InsertProjectTester::testExistingResourceCalendar()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
Calendar *c = addCalendar( part );
Project &p = part.getProject();
QVERIFY( p.calendarCount() == 1 );
addResourceGroup( part );
Resource *r = addResource( part );
part.addCommand( new ModifyResourceCalendarCmd( r, c ) );
QVERIFY( p.resourceGroupAt( 0 )->numResources() == 1 );
QDomDocument doc = part.saveXML();
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
QCOMPARE( part2.getProject().allCalendars().count(), 1 );
QVERIFY( part2.getProject().allCalendars().contains( c ) );
QCOMPARE( part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->calendar( true ), c );
part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->setCalendar( 0 );
KoXmlDocument xdoc;
xdoc.setContent( doc.toString() );
part.loadXML( xdoc, 0 );
part2.insertProject( part.getProject(), 0, 0 );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->numResources() == 1 );
QCOMPARE( part2.getProject().allCalendars().count(), 1 );
QVERIFY( part2.getProject().allCalendars().contains( c ) );
QVERIFY( part2.getProject().resourceGroupAt( 0 )->resourceAt( 0 )->calendar( true ) == 0 );
}
void InsertProjectTester::testExistingResourceRequest()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
addResourceGroup( part );
addResource( part );
addTask( part );
addGroupRequest( part );
addResourceRequest( part );
QDomDocument doc = part.saveXML();
Project &p = part.getProject();
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
Project &p2 = part2.getProject();
QVERIFY( p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) ) != 0 );
KoXmlDocument xdoc;
xdoc.setContent( doc.toString() );
part.loadXML( xdoc, 0 );
part2.insertProject( part.getProject(), 0, 0 );
QVERIFY( p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) ) != 0 );
QVERIFY( p2.childNode( 1 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) ) != 0 );
}
void InsertProjectTester::testExistingRequiredResourceRequest()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
addResourceGroup( part );
Resource *r = addResource( part );
ResourceGroup *g = addResourceGroup( part );
g->setType( ResourceGroup::Type_Material );
QList<Resource*> m; m << addResource( part, g );
m.first()->setType( Resource::Type_Material );
r->setRequiredIds( QStringList() << m.first()->id() );
addTask( part );
addGroupRequest( part );
addResourceRequest( part );
QDomDocument doc = part.saveXML();
Project &p = part.getProject();
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
part2.insertProject( p, 0, 0 );
Project &p2 = part2.getProject();
ResourceRequest *rr = p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QVERIFY( ! rr->requiredResources().isEmpty() );
QCOMPARE( rr->requiredResources().at( 0 ), m.first() );
KoXmlDocument xdoc;
xdoc.setContent( doc.toString() );
part.loadXML( xdoc, 0 );
part2.insertProject( part.getProject(), 0, 0 );
rr = p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QVERIFY( ! rr->requiredResources().isEmpty() );
QCOMPARE( rr->requiredResources().at( 0 ), m.first() );
rr = p2.childNode( 1 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QVERIFY( ! rr->requiredResources().isEmpty() );
QCOMPARE( rr->requiredResources().at( 0 ), m.first() );
}
void InsertProjectTester::testExistingTeamResourceRequest()
{
Part pp(0);
MainDocument part( &pp );
pp.setDocument( &part );
addCalendar( part );
addResourceGroup( part );
Resource *r = addResource( part );
r->setName( "R1" );
r->setType( Resource::Type_Team );
ResourceGroup *tg = addResourceGroup( part );
tg->setName( "TG" );
Resource *t1 = addResource( part, tg );
t1->setName( "T1" );
r->addTeamMemberId( t1->id() );
Resource *t2 = addResource( part, tg );
t2->setName( "T2" );
r->addTeamMemberId( t2->id() );
addTask( part );
addGroupRequest( part );
addResourceRequest( part );
QDomDocument doc = part.saveXML();
Part pp2(0);
MainDocument part2( &pp2 );
pp2.setDocument( &part2 );
Project &p2 = part2.getProject();
part2.insertProject( part.getProject(), 0, 0 );
ResourceRequest *rr = p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QCOMPARE( rr->resource()->teamMembers().count(), 2 );
QCOMPARE( rr->resource()->teamMembers().at( 0 ), t1 );
QCOMPARE( rr->resource()->teamMembers().at( 1 ), t2 );
KoXmlDocument xdoc;
xdoc.setContent( doc.toString() );
part.loadXML( xdoc, 0 );
part2.insertProject( part.getProject(), 0, 0 );
QCOMPARE( p2.numChildren(), 2 );
QVERIFY( ! p2.childNode( 0 )->requests().isEmpty() );
rr = p2.childNode( 0 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QCOMPARE( rr->resource()->teamMembers().count(), 2 );
QCOMPARE( rr->resource()->teamMembers().at( 0 ), t1 );
QCOMPARE( rr->resource()->teamMembers().at( 1 ), t2 );
QVERIFY( ! p2.childNode( 1 )->requests().isEmpty() );
rr = p2.childNode( 1 )->requests().find( p2.resourceGroupAt( 0 )->resourceAt( 0 ) );
QVERIFY( rr );
QCOMPARE( rr->resource()->teamMembers().count(), 2 );
QCOMPARE( rr->resource()->teamMembers().at( 0 ), t1 );
QCOMPARE( rr->resource()->teamMembers().at( 1 ), t2 );
}
} //namespace KPlato
QTEST_MAIN(KPlato::InsertProjectTester)
diff --git a/src/workpackage/commandlineparser.cpp b/src/workpackage/commandlineparser.cpp
index 432f99ad..4154ee42 100644
--- a/src/workpackage/commandlineparser.cpp
+++ b/src/workpackage/commandlineparser.cpp
@@ -1,103 +1,104 @@
/* This file is part of the KDE project
* Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "commandlineparser.h"
#include "part.h"
#include "mainwindow.h"
#include "aboutdata.h"
#include <kiconloader.h>
#include <KLocalizedString>
#include <KAboutData>
#include <KStartupInfo>
#include <KWindowSystem>
#include <KMessageBox>
#include <QApplication>
#include <QDir>
#include "debugarea.h"
CommandLineParser::CommandLineParser()
: QObject(),
m_mainwindow( 0 )
{
KAboutData *aboutData = KPlatoWork::newAboutData();
KAboutData::setApplicationData( *aboutData );
qApp->setWindowIcon(QIcon::fromTheme(QStringLiteral("calligraplanwork"), qApp->windowIcon()));
aboutData->setupCommandLine(&m_commandLineParser);
m_commandLineParser.addHelpOption();
m_commandLineParser.addVersionOption();
m_commandLineParser.addPositionalArgument(QStringLiteral("[file]"), i18n("File to open"));
m_commandLineParser.process(*qApp);
aboutData->processCommandLine(&m_commandLineParser);
delete aboutData;
}
CommandLineParser::~CommandLineParser()
{
}
void CommandLineParser::handleActivateRequest(const QStringList &arguments, const QString &workingDirectory)
{
m_commandLineParser.parse(arguments);
handleCommandLine(QDir(workingDirectory));
// terminate startup notification and activate the mainwindow
KStartupInfo::setNewStartupId(m_mainwindow, KStartupInfo::startupId());
KWindowSystem::forceActiveWindow(m_mainwindow->winId());
}
void CommandLineParser::handleCommandLine(const QDir &workingDirectory)
{
QList<KMainWindow*> lst = KMainWindow::memberList();
if ( lst.count() > 1 ) {
warnPlanWork<<"windows count > 1:"<<lst.count();
return; // should never happen
}
if ( lst.isEmpty() ) {
Q_ASSERT( m_mainwindow == 0 );
}
if ( m_mainwindow == 0 ) {
m_mainwindow = new KPlatoWork_MainWindow();
m_mainwindow->show();
}
// Get the command line arguments which we have to parse
const QStringList fileUrls = m_commandLineParser.positionalArguments();
// TODO: remove once Qt has proper handling itself
const QRegExp withProtocolChecker( QStringLiteral("^[a-zA-Z]+:") );
foreach(const QString &fileUrl, fileUrls) {
// convert to an url
const bool startsWithProtocol = (withProtocolChecker.indexIn(fileUrl) == 0);
const QUrl url = startsWithProtocol ?
QUrl::fromUserInput(fileUrl) :
QUrl::fromLocalFile(workingDirectory.absoluteFilePath(fileUrl));
// For now create an empty document
if ( ! m_mainwindow->openDocument(url) ) {
KMessageBox::error(0, i18n("Failed to open document") );
}
}
}
diff --git a/src/workpackage/debugarea.cpp b/src/workpackage/debugarea.cpp
index 5ec9b33c..d875dcb2 100644
--- a/src/workpackage/debugarea.cpp
+++ b/src/workpackage/debugarea.cpp
@@ -1,26 +1,27 @@
/* This file is part of the KDE project
Copyright (C) 2012 Dag Andersen <danders@get2net.dk>
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 "debugarea.h"
const QLoggingCategory &PLANWORK_LOG()
{
static const QLoggingCategory category("calligra.planwork");
return category;
}
diff --git a/src/workpackage/factory.cpp b/src/workpackage/factory.cpp
index 9c31992d..1d459384 100644
--- a/src/workpackage/factory.cpp
+++ b/src/workpackage/factory.cpp
@@ -1,85 +1,86 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "factory.h"
#include "part.h"
#include "aboutdata.h"
#include <KoResourcePaths.h>
#include <KoComponentData.h>
#include <kiconloader.h>
namespace KPlatoWork
{
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 );
Q_UNUSED( iface );
Part *part = new Part( parentWidget, parent );
return part;
}
KAboutData* Factory::aboutData()
{
if ( !s_aboutData )
s_aboutData = newAboutData();
return s_aboutData;
}
const KoComponentData &Factory::global()
{
if ( !s_global )
{
s_global = new KoComponentData( *aboutData() );
// Add any application-specific resource directories here
KoResourcePaths::addResourceType("planwork_template", "data", "calligraplanwork/templates/");
KoResourcePaths::addResourceType("projects", "data", "calligraplanwork/projects/");
// Tell the iconloader about share/apps/calligra/icons
KIconLoader::global()->addAppDir("calligra");
}
return *s_global;
}
} // KPlatoWork namespace
diff --git a/src/workpackage/factoryinit.cpp b/src/workpackage/factoryinit.cpp
index 8b519986..c2760bad 100644
--- a/src/workpackage/factoryinit.cpp
+++ b/src/workpackage/factoryinit.cpp
@@ -1,22 +1,23 @@
/* This file is part of the KDE project
Copyright (C) 2005 David Faure <faure@kde.org>
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 "factory.h"
#include "moc_factoryinit.cpp"
diff --git a/src/workpackage/main.cpp b/src/workpackage/main.cpp
index d15db721..958e2df3 100644
--- a/src/workpackage/main.cpp
+++ b/src/workpackage/main.cpp
@@ -1,52 +1,53 @@
/* This file is part of the KDE project
Copyright (C) 2001 Thomas zander <zander@kde.org>
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 "commandlineparser.h"
#include <KDBusService>
#include <QApplication>
#include <QDir>
#include <Calligra2Migration.h>
extern "C" Q_DECL_EXPORT int kdemain( int argc, char **argv )
{
QApplication app(argc, argv);
#ifdef Q_OS_MACOS
// app.applicationName() will return "Plan Work" because of the nice name
// set in the Info.plist. DBus doesn't like the resulting space in the
// service name, so reset the application name:
app.setApplicationName("calligraplanwork");
#endif
KDBusService service(KDBusService::Unique);
// we come here only once...
// Migrate data from kde4 to kf5 locations
Calligra2Migration m("calligraplanwork", "planwork");
m.setConfigFiles(QStringList() << QStringLiteral("planworkrc"));
m.setUiFiles(QStringList() << QStringLiteral("planwork.rc") << QStringLiteral("planwork_readonly.rc") << QStringLiteral("planworkui.rc"));
m.migrate();
CommandLineParser cmd;
QObject::connect(&service, &KDBusService::activateRequested, &cmd, &CommandLineParser::handleActivateRequest);
cmd.handleCommandLine(QDir::current());
return app.exec();
}
diff --git a/src/workpackage/mainwindow.cpp b/src/workpackage/mainwindow.cpp
index fc86bd14..0409acf3 100644
--- a/src/workpackage/mainwindow.cpp
+++ b/src/workpackage/mainwindow.cpp
@@ -1,161 +1,162 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 1999 Simon Hausmann <hausmann@kde.org>
Copyright (C) 2000-2005 David Faure <faure@kde.org>
Copyright (C) 2005, 2006 Sven Lüppken <sven@kde.org>
Copyright (C) 2008 - 2009, 2012 Dag Andersen <danders@get2net.dk>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
+// clazy:excludeall=qstring-arg
#include "mainwindow.h"
#include "part.h"
#include "view.h"
#include "kptdocuments.h"
#include <QSplitter>
#include <QLabel>
#include <QWidget>
#include <QFileDialog>
#include <QApplication>
#include <kundo2qstack.h>
#include <assert.h>
//#include "koshellsettings.h"
#include <KoDocument.h>
#include <KLocalizedString>
#include <kmessagebox.h>
#include <kactioncollection.h>
#include <ktoolinvocation.h>
#include <KIO/StatJob>
#include <kxmlguiwindow.h>
#include <KoDocumentInfo.h>
#include <KoView.h>
#include <KoFilterManager.h>
#include "debugarea.h"
KPlatoWork_MainWindow::KPlatoWork_MainWindow()
: KParts::MainWindow()
{
debugPlanWork<<this;
m_part = new KPlatoWork::Part( this, this );
KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
KStandardAction::open(this, SLOT(slotFileOpen()), actionCollection());
// KStandardAction::save(this, SLOT(slotFileSave()), actionCollection());
QAction *a = KStandardAction::undo(m_part->undoStack(), SLOT(undo()), actionCollection());
a->setEnabled( false );
connect( m_part->undoStack(), &KUndo2QStack::canUndoChanged, a, &QAction::setEnabled );
a = KStandardAction::redo(m_part->undoStack(), SLOT(redo()), actionCollection());
a->setEnabled( false );
connect( m_part->undoStack(), &KUndo2QStack::canRedoChanged, a, &QAction::setEnabled );
setCentralWidget( m_part->widget() );
setupGUI( KXmlGuiWindow::ToolBar | KXmlGuiWindow::Keys | KXmlGuiWindow::StatusBar | KXmlGuiWindow::Save);
createGUI( m_part );
connect( m_part, SIGNAL(captionChanged(QString,bool)), SLOT(setCaption(QString,bool)) );
}
KPlatoWork_MainWindow::~KPlatoWork_MainWindow()
{
debugPlanWork;
}
void KPlatoWork_MainWindow::setCaption( const QString & )
{
KParts::MainWindow::setCaption( QString() );
}
void KPlatoWork_MainWindow::setCaption( const QString &, bool modified )
{
KParts::MainWindow::setCaption( QString(), modified );
}
bool KPlatoWork_MainWindow::openDocument(const QUrl & url)
{
// TODO: m_part->openUrl will find out about this as well, no?
KIO::StatJob* statJob = KIO::stat( url );
statJob->setSide( KIO::StatJob::SourceSide );
const bool isUrlReadable = statJob->exec();
if (! isUrlReadable) {
KMessageBox::error(0L, 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 m_part->openUrl( url );
}
QString KPlatoWork_MainWindow::configFile() const
{
//return readConfigFile( QStandardPaths::locate(QStandardPaths::GenericDataLocation "koshell/koshell_shell.rc" ) );
return QString(); // use UI standards only for now
}
//called from slotFileSave(), slotFileSaveAs(), queryClose(), slotEmailFile()
bool KPlatoWork_MainWindow::saveDocument( bool saveas, bool silent )
{
debugPlanWork<<saveas<<silent;
KPlatoWork::Part *doc = rootDocument();
if ( doc == 0 ) {
return true;
}
return doc->saveWorkPackages( silent );
}
bool KPlatoWork_MainWindow::queryClose()
{
KPlatoWork::Part *part = rootDocument();
if ( part == 0 ) {
return true;
}
return part->queryClose();
}
void KPlatoWork_MainWindow::slotFileClose()
{
if (queryClose()) {
}
}
void KPlatoWork_MainWindow::slotFileSave()
{
saveDocument();
}
void KPlatoWork_MainWindow::slotFileOpen()
{
const QUrl file = QFileDialog::getOpenFileUrl( 0, QString(), QUrl(), "*.planwork" );
if ( ! file.isEmpty() ) {
openDocument( file );
}
}
diff --git a/src/workpackage/packagesettings.cpp b/src/workpackage/packagesettings.cpp
index 4004c983..699b2af3 100644
--- a/src/workpackage/packagesettings.cpp
+++ b/src/workpackage/packagesettings.cpp
@@ -1,98 +1,99 @@
/* This file is part of the KDE project
Copyright (C) 2009 Dag Andersen <danders@get2net.dk>
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 "packagesettings.h"
#include "workpackage.h"
#include <QComboBox>
#include <KLocalizedString>
namespace KPlatoWork
{
PackageSettingsDialog::PackageSettingsDialog(WorkPackage &p, QWidget *parent)
: KoDialog(parent)
{
setCaption( i18n("Work Package Settings") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
//debugPlanWork<<&p;
dia = new PackageSettingsPanel(p, this);
setMainWidget(dia);
enableButtonOk(false);
connect(dia, &PackageSettingsPanel::changed, this, &KoDialog::enableButtonOk);
}
KUndo2Command *PackageSettingsDialog::buildCommand()
{
//debugPlanWork;
return dia->buildCommand();
}
PackageSettingsPanel::PackageSettingsPanel(WorkPackage &p, QWidget *parent)
: QWidget(parent),
m_package( p )
{
setupUi(this);
setSettings( p.settings() );
connect( ui_usedEffort, &QCheckBox::stateChanged, this, &PackageSettingsPanel::slotChanged );
connect( ui_progress, &QCheckBox::stateChanged, this, &PackageSettingsPanel::slotChanged );
connect( ui_documents, &QCheckBox::stateChanged, this, &PackageSettingsPanel::slotChanged );
}
KUndo2Command *PackageSettingsPanel::buildCommand()
{
//debugPlanWork;
WorkPackageSettings s = settings();
if ( s == m_package.settings() ) {
return 0;
}
return new ModifyPackageSettingsCmd( &m_package, s, kundo2_i18n( "Modify package settings" ) );
}
void PackageSettingsPanel::slotChanged() {
emit changed( settings() != m_package.settings() );
}
WorkPackageSettings PackageSettingsPanel::settings() const
{
WorkPackageSettings s;
s.usedEffort = ui_usedEffort->checkState() == Qt::Checked;
s.progress = ui_progress->checkState() == Qt::Checked;
s.documents = ui_documents->checkState() == Qt::Checked;
return s;
}
void PackageSettingsPanel::setSettings( const WorkPackageSettings &s )
{
ui_usedEffort->setCheckState( s.usedEffort ? Qt::Checked : Qt::Unchecked );
ui_progress->setCheckState( s.progress ? Qt::Checked : Qt::Unchecked );
ui_documents->setCheckState( s.documents ? Qt::Checked : Qt::Unchecked );
}
} //KPlatoWork namespace
diff --git a/src/workpackage/part.cpp b/src/workpackage/part.cpp
index 9dda10de..66143adc 100644
--- a/src/workpackage/part.cpp
+++ b/src/workpackage/part.cpp
@@ -1,843 +1,844 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2004 - 2009 Dag Andersen <danders@get2net.dk>
Copyright (C) 2006 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 - 2009, 2012 Dag Andersen <danders@get2net.dk>
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 <KoXmlReader.h>
#include <KoStore.h>
#include <KoDocumentInfo.h>
#include <KoResourcePaths.h>
#include <QPainter>
#include <QFileInfo>
#include <QDir>
#include <QTimer>
#include <QFileSystemWatcher>
#include <kundo2qstack.h>
#include <QPointer>
#include <QUrl>
#include <QMimeDatabase>
#include <QApplication>
#include <KLocalizedString>
#include <kmessagebox.h>
#include <kparts/partmanager.h>
#include <kopenwithdialog.h>
#include <kmimetypetrader.h>
//#include <kserviceoffer.h>
#include <KIO/DesktopExecParser>
#include <krun.h>
#include <kprocess.h>
#include <kactioncollection.h>
#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<KoDocument*>( editor ) ) {
// debugPlanWork<<"Creating Calligra doc";
// m_type = Type_Calligra;
// connect( static_cast<KoDocument*>( 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<WorkPackage*>( parent() );
}
void DocumentChild::setFileInfo( const QUrl &url )
{
m_fileinfo.setFile( url.path() );
//debugPlanWork<<url;
bool res = connect( m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &DocumentChild::slotDirty );
//debugPlanWork<<res<<filePath();
#ifndef NDEBUG
Q_ASSERT( res );
#else
Q_UNUSED( res );
#endif
m_fileSystemWatcher->addPath( filePath() );
}
void DocumentChild::setModified( bool mod )
{
debugPlanWork<<mod<<filePath();
if ( m_editormodified != mod ) {
m_editormodified = mod;
emit modified( mod );
}
}
void DocumentChild::slotDirty( const QString &file )
{
//debugPlanWork<<filePath()<<file<<m_filemodified;
if ( file == filePath() && ! m_filemodified ) {
debugPlanWork<<file<<"is modified";
m_filemodified = true;
emit fileModified( true );
}
}
void DocumentChild::slotUpdateModified()
{
if ( m_type == Type_KParts && m_editor && ( m_editor->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:<br>%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:<br>%1", doc->url().url() ) );
return false;
}
m_copy = true;
} else {
url = doc->url();
}
if ( ! url.isValid() ) {
KMessageBox::error( 0, i18n( "Invalid URL:<br>%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:<br>%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:<br>%1", doc->url().path() ) );
return false;
}
m_copy = true;
} else {
url = doc->url();
}
if ( ! url.isValid() ) {
KMessageBox::error( 0, i18n( "Invalid URL:<br>%1", url.url() ) );
return false;
}
setFileInfo( url );
return true;
}
bool DocumentChild::editDoc()
{
Q_ASSERT( m_doc != 0 );
debugPlanWork<<"file:"<<filePath();
if ( isOpen() ) {
KMessageBox::error( 0, i18n( "Document is already open:<br> %1", m_doc->url().path() ) );
return false;
}
if ( ! m_fileinfo.exists() ) {
KMessageBox::error( 0, i18n( "File does not exist:<br>%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<QUrl> files;
if ( url.isValid() ) {
files << url;
}
if ( service ) {
KIO::DesktopExecParser parser(*service, files);
parser.setUrlsAreTempFiles(false);
args = parser.resultingArguments();
} else {
QList<QUrl> list;
QPointer<KOpenWithDialog> 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<<args;
m_process = new KProcess();
m_process->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<<m_process->pid()<<m_process->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<<par<<filePath();
delete m_process;
m_process = 0;
}
void DocumentChild::slotEditError( QProcess::ProcessError status )
{
debugPlanWork<<status;
if ( status == QProcess::FailedToStart || status == QProcess::Crashed ) {
m_process->deleteLater();
m_process = 0;
} else debugPlanWork<<"Error="<<status<<" what to do?";
}
bool DocumentChild::saveToStore( KoStore *store )
{
debugPlanWork<<filePath();
m_fileSystemWatcher->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:"<<fileName();
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;
setComponentData( *Factory::aboutData() );
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("<p>The work package already exists in the projects store.</p>"
"<p>Project: %1<br>Task: %2</p>"
"<p>Do you want to update the existing package with data from the new?</p>",
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<<node->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<Node*> &nodes )
{
//debugPlanWork<<node->name();
MacroCommand *m = new MacroCommand( kundo2_i18np( "Remove work package", "Remove work packages", nodes.count() ) );
foreach ( Node *n, nodes ) {
removeWorkPackage( n, m );
}
if ( m->isEmpty() ) {
delete m;
} else {
addCommand( m );
}
}
void Part::removeWorkPackage( WorkPackage *wp )
{
//debugPlanWork;
int row = indexOf( wp );
if (row >= 0) {
const QList<QString> &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<<lst;
foreach ( const QString &file, lst ) {
if ( ! loadNativeFormatFromStore( file ) ) {
KMessageBox::information( 0, i18n( "Failed to load file:<br>%1" , file ) );
}
}
m_loadingFromProjectStore = false;
return true;
}
bool Part::loadNativeFormatFromStore(const QString& file)
{
debugPlanWork<<file;
KoStore * store = KoStore::createStore(file, KoStore::Read, "", KoStore::Auto);
if (store->bad()) {
KMessageBox::error( 0, i18n("Not a valid work package file:<br>%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<br>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)<br>"
"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)<br>"
"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<WorkPackage*>( wp );
}
}
return 0;
}
WorkPackage *Part::findWorkPackage( const DocumentChild *child ) const
{
foreach ( const WorkPackage *wp, m_packageMap ) {
if ( wp->contains( child ) ) {
return const_cast<WorkPackage*>( 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<<doc<<doc->url();
// start in any suitable application
return editOtherDocument( doc );
}
bool Part::editOtherDocument( const Document *doc )
{
Q_ASSERT( doc != 0 );
//debugPlanWork<<doc->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<<doc;
if ( doc == 0 ) {
return;
}
QUrl filename;
if ( doc->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:"<<filename;
if ( ! filename.isValid() ) {
//KMessageBox::error( 0, i18n( "Cannot open document. Invalid url: %1", filename.pathOrUrl() ) );
return false;
}
KRun *run = new KRun( filename, 0 );
Q_UNUSED(run); // XXX: shouldn't run be deleted?
return true;
}
void Part::setDocumentClean( bool clean )
{
debugPlanWork<<clean;
setModified( ! clean );
if ( ! clean ) {
saveModifiedWorkPackages();
return;
}
}
void Part::setModified( bool mod )
{
KParts::ReadWritePart::setModified( mod );
emit captionChanged( QString(), mod );
}
bool Part::saveAs( const QUrl &/*url*/ )
{
return false;
}
void Part::saveModifiedWorkPackages()
{
foreach ( WorkPackage *wp, m_packageMap ) {
if ( wp->isModified() ) {
saveWorkPackage( wp );
}
}
m_undostack->setClean();
}
void Part::saveWorkPackage( WorkPackage *wp )
{
wp->saveToProjects( this );
}
bool Part::saveWorkPackages( bool silent )
{
debugPlanWork<<silent;
foreach ( WorkPackage *wp, m_packageMap ) {
wp->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<WorkPackage*> 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<<localFilePath();
return loadNativeFormatFromStore( localFilePath() );
}
bool Part::saveFile()
{
return false;
}
} //KPlatoWork namespace
diff --git a/src/workpackage/taskcompletiondialog.cpp b/src/workpackage/taskcompletiondialog.cpp
index d85ca1a2..7dadf1db 100644
--- a/src/workpackage/taskcompletiondialog.cpp
+++ b/src/workpackage/taskcompletiondialog.cpp
@@ -1,487 +1,488 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "taskcompletiondialog.h"
#include "workpackage.h"
#include "kptusedefforteditor.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kptaccountsmodel.h" // FIXME hack to get at i18n'ed header text
#include <KoIcon.h>
#include <KLocalizedString>
#include <QComboBox>
#include "debugarea.h"
using namespace KPlato;
namespace KPlatoWork
{
TaskCompletionDialog::TaskCompletionDialog(WorkPackage &p, ScheduleManager *sm, QWidget *parent)
: KoDialog(parent)
{
setCaption( i18n("Task Progress") );
setButtons( Ok|Cancel );
setDefaultButton( Ok );
showButtonSeparator( true );
m_panel = new TaskCompletionPanel( p, sm, this);
setMainWidget(m_panel);
enableButtonOk(false);
connect(m_panel, &TaskCompletionPanel::changed, this, &TaskCompletionDialog::slotChanged);
}
void TaskCompletionDialog::slotChanged( bool )
{
enableButtonOk( true );
}
KUndo2Command *TaskCompletionDialog::buildCommand()
{
//debugPlanWork;
return m_panel->buildCommand();
}
TaskCompletionPanel::TaskCompletionPanel(WorkPackage &p, ScheduleManager *sm, QWidget *parent)
: QWidget(parent),
m_package( &p )
{
//debugPlanWork;
setupUi(this);
addEntryBtn->setIcon(koIcon("list-add"));
removeEntryBtn->setIcon(koIcon("list-remove"));
CompletionEntryItemModel *m = new CompletionEntryItemModel( this );
entryTable->setItemDelegateForColumn ( 1, new ProgressBarDelegate( this ) );
entryTable->setItemDelegateForColumn ( 2, new DurationSpinBoxDelegate( this ) );
entryTable->setItemDelegateForColumn ( 3, new DurationSpinBoxDelegate( this ) );
entryTable->setCompletionModel( m );
Task *task = qobject_cast<Task*>( p.node() );
m_completion = task->completion();
started->setChecked(m_completion.isStarted());
finished->setChecked(m_completion.isFinished());
startTime->setDateTime(m_completion.startTime());
finishTime->setDateTime(m_completion.finishTime());
finishTime->setMinimumDateTime( qMax( startTime->dateTime(), QDateTime(m_completion.entryDate(), QTime(), Qt::LocalTime) ) );
scheduledEffort = p.node()->estimate()->expectedValue();
if ( m_completion.usedEffortMap().isEmpty() || task->requests().isEmpty() ) {
foreach ( ResourceGroupRequest *g, task->requests().requests() ) {
foreach ( ResourceRequest *r, g->resourceRequests() ) {
m_completion.addUsedEffort( r->resource() );
}
}
}
enableWidgets();
started->setFocus();
m->setManager( sm );
m->setTask( task );
Resource *r = p.project()->findResource( task->workPackage().ownerId() );
m->setSource( r, task );
entryTable->horizontalHeader()->swapSections( CompletionEntryItemModel::Property_PlannedEffort, CompletionEntryItemModel::Property_ActualAccumulated );
//FIXME when string freeze is lifted
Duration pr = task->plannedEffort( r );
Duration tr = task->plannedEffort();
if ( pr == tr ) {
ui_plannedFrame->hide();
} else {
ui_plannedLabel->setText( m->headerData( CompletionEntryItemModel::Property_PlannedEffort, Qt::Horizontal ).toString() );
ui_labelResource->setText( r->name() );
ui_plannedResource->setText( pr.format() );
ui_labelTask->setText( Node::typeToString( Node::Type_Task, true ) );
ui_plannedTask->setText( tr.format() );
}
if ( m->rowCount() > 0 ) {
QModelIndex idx = m->index( m->rowCount() -1, 0 );
entryTable->scrollTo( idx );
}
connect( addEntryBtn, &QAbstractButton::clicked, this, &TaskCompletionPanel::slotAddEntry );
connect( removeEntryBtn, &QAbstractButton::clicked, entryTable, &KPlato::CompletionEntryEditor::removeEntry );
connect( entryTable, &KPlato::CompletionEntryEditor::rowInserted, this, &TaskCompletionPanel::slotEntryAdded );
connect(entryTable, &KPlato::CompletionEntryEditor::changed, this, &TaskCompletionPanel::slotChanged );
connect(entryTable, &KPlato::CompletionEntryEditor::changed, this, &TaskCompletionPanel::slotEntryChanged );
connect(entryTable, &KPlato::CompletionEntryEditor::rowInserted, this, &TaskCompletionPanel::slotChanged );
connect(entryTable, &KPlato::CompletionEntryEditor::rowInserted, this, &TaskCompletionPanel::slotEntryChanged );
connect(entryTable, &KPlato::CompletionEntryEditor::rowRemoved, this, &TaskCompletionPanel::slotEntryChanged );
connect(entryTable, &KPlato::CompletionEntryEditor::selectedItemsChanged, this, &TaskCompletionPanel::slotSelectionChanged );
connect(started, &QAbstractButton::toggled, this, &TaskCompletionPanel::slotStartedChanged);
connect(started, &QAbstractButton::toggled, this, &TaskCompletionPanel::slotChanged);
connect(finished, &QAbstractButton::toggled, this, &TaskCompletionPanel::slotFinishedChanged);
connect(finished, &QAbstractButton::toggled, this, &TaskCompletionPanel::slotChanged);
connect(startTime, &QDateTimeEdit::dateTimeChanged, this, &TaskCompletionPanel::slotChanged);
connect(startTime, &QDateTimeEdit::dateTimeChanged, this, &TaskCompletionPanel::slotStartTimeChanged);
connect(finishTime, &QDateTimeEdit::dateTimeChanged, this, &TaskCompletionPanel::slotChanged);
connect(finishTime, &QDateTimeEdit::dateTimeChanged, this, &TaskCompletionPanel::slotFinishTimeChanged);
removeEntryBtn->setEnabled( false );
}
QSize TaskCompletionPanel::sizeHint() const
{
return QWidget::sizeHint().expandedTo( QSize( 610, 0 ) );
}
KUndo2Command *TaskCompletionPanel::buildCommand()
{
MacroCommand *cmd = new MacroCommand( kundo2_i18n("Modify task completion") );
Completion &org = m_package->task()->completion();
if ( org.entrymode() != m_completion.entrymode() ) {
cmd->addCommand( new ModifyCompletionEntrymodeCmd(org, m_completion.entrymode() ) );
}
if ( org.isStarted() != m_completion.isStarted() ) {
cmd->addCommand( new ModifyCompletionStartedCmd(org, m_completion.isStarted() ) );
}
if ( org.isFinished() != m_completion.isFinished() ) {
cmd->addCommand( new ModifyCompletionFinishedCmd(org, m_completion.isFinished() ) );
}
if ( org.startTime() != m_completion.startTime() ) {
cmd->addCommand( new ModifyCompletionStartTimeCmd(org, m_completion.startTime() ) );
}
if ( org.finishTime() != m_completion.finishTime() ) {
cmd->addCommand( new ModifyCompletionFinishTimeCmd(org, m_completion.finishTime() ) );
}
QList<QDate> orgdates = org.entries().keys();
QList<QDate> m_completiondates = m_completion.entries().keys();
foreach ( const QDate &d, orgdates ) {
if ( m_completiondates.contains( d ) ) {
if ( m_completion.entry( d ) == org.entry( d ) ) {
continue;
}
Completion::Entry *e = new Completion::Entry( *( m_completion.entry( d ) ) );
cmd->addCommand( new ModifyCompletionEntryCmd(org, d, e ) );
} else {
cmd->addCommand( new RemoveCompletionEntryCmd(org, d ) );
}
}
foreach ( const QDate &d, m_completiondates ) {
if ( ! orgdates.contains( d ) ) {
Completion::Entry *e = new Completion::Entry( * ( m_completion.entry( d ) ) );
cmd->addCommand( new AddCompletionEntryCmd(org, d, e ) );
}
}
if ( cmd->isEmpty() ) {
delete cmd;
return 0;
}
return cmd;
}
void TaskCompletionPanel::slotChanged()
{
emit changed( true ); //FIXME
}
void TaskCompletionPanel::slotStartedChanged(bool state) {
m_completion.setStarted( state );
if (state) {
m_completion.setStartTime( QDateTime::currentDateTime() );
startTime->setDateTime( m_completion.startTime() );
slotCalculateEffort();
}
enableWidgets();
}
void TaskCompletionPanel::setFinished() {
finishTime->setDateTime( QDateTime::currentDateTime() );
slotFinishTimeChanged( finishTime->dateTime() );
}
void TaskCompletionPanel::slotFinishedChanged(bool state) {
debugPlanWork<<state;
m_completion.setFinished( state );
if (state) {
debugPlanWork<<state;
setFinished();
Completion::Entry *e = m_completion.entry( m_completion.finishTime().date() );
if ( e == 0 ) {
debugPlanWork<<"no entry on this date, just add one:"<<m_completion.finishTime().date();
e = new Completion::Entry( 100, Duration::zeroDuration, m_package->node()->plannedEffort() );
m_completion.addEntry( m_completion.finishTime().date(), e );
entryTable->setCompletion( &m_completion );
debugPlanWork<<"Entry added:"<<m_completion.finishTime().date()<<m_completion.entry( m_completion.finishTime().date() );
} else {
// row exists, use model to update to respect calculation mode
int row = entryTable->model()->rowCount() - 1;
QModelIndex idx = entryTable->model()->index( row, CompletionEntryItemModel::Property_Completion );
entryTable->model()->setData( idx, 100 );
}
}
enableWidgets();
}
void TaskCompletionPanel::slotFinishTimeChanged( const QDateTime &dt )
{
m_completion.setFinishTime( dt );
}
void TaskCompletionPanel::slotStartTimeChanged( const QDateTime &dt )
{
m_completion.setStartTime( dt );
finishTime->setMinimumDateTime( qMax( startTime->dateTime(), QDateTime(m_completion.entryDate(), QTime(), Qt::LocalTime) ) );
}
void TaskCompletionPanel::slotAddEntry()
{
CompletionEntryItemModel *m = static_cast<CompletionEntryItemModel*>( entryTable->model() );
int col = KPlato::CompletionEntryItemModel::Property_UsedEffort;
entryTable->addEntry();
m_completion.setEntrymode( Completion::EnterEffortPerTask );
m->setFlags( col, Qt::ItemIsEditable );
}
void TaskCompletionPanel::slotEntryChanged()
{
finishTime->setMinimumDateTime( qMax( startTime->dateTime(), QDateTime(m_completion.entryDate(), QTime(), Qt::LocalTime) ) );
if ( ! finished->isChecked() && ! m_completion.isFinished() && m_completion.percentFinished() == 100 ) {
finished->setChecked( true );
}
}
void TaskCompletionPanel::enableWidgets() {
started->setEnabled(!finished->isChecked());
finished->setEnabled(started->isChecked());
finishTime->setEnabled(finished->isChecked());
startTime->setEnabled(started->isChecked() && !finished->isChecked());
}
void TaskCompletionPanel::slotPercentFinishedChanged( int ) {
slotCalculateEffort();
}
void TaskCompletionPanel::slotCalculateEffort()
{
}
void TaskCompletionPanel::slotEntryAdded( const QDate& date )
{
debugPlanWork<<date;
}
void TaskCompletionPanel::slotSelectionChanged( const QItemSelection &sel )
{
removeEntryBtn->setEnabled( !sel.isEmpty() );
}
void TaskCompletionPanel::slotEditmodeChanged( int index )
{
Q_UNUSED( index );
}
//-------------------
CompletionEntryItemModel::CompletionEntryItemModel( QObject *parent )
: KPlato::CompletionEntryItemModel( parent ),
m_calculate( false ),
m_resource( 0 ),
m_task( 0 )
{
// FIXME after string freeze is lifted
CostBreakdownItemModel m;
m_headers << m.headerData( 2, Qt::Horizontal ).toString();
}
void CompletionEntryItemModel::setSource( Resource *resource, Task *task )
{
m_resource = resource;
m_task = task;
setCompletion( &(task->completion()) );
}
int CompletionEntryItemModel::columnCount( const QModelIndex& ) const
{
return 6;
}
QVariant CompletionEntryItemModel::actualEffort ( int row, int role ) const
{
Completion::Entry *e = m_completion->entry( date( row ).toDate() );
if ( e == 0 ) {
return QVariant();
}
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
Duration v = e->totalPerformed;
if ( row > 0 ) {
v -= m_completion->entry( date( row - 1 ).toDate() )->totalPerformed;
}
//debugPlanWork<<m_node->name()<<": "<<v<<" "<<unit<<" : "<<scales<<endl;
return v.format();
}
case Qt::EditRole: {
Duration v = e->totalPerformed;
if ( row > 0 ) {
v -= m_completion->entry( date( row - 1 ).toDate() )->totalPerformed;
}
//debugPlanWork<<m_node->name()<<": "<<v<<" "<<unit<<" : "<<scales<<endl;
return v.toDouble( Duration::Unit_h );
}
case Role::DurationScales: {
QVariantList lst;
lst << 24 << 60 << 60 << 1000;
return lst;
}
case Role::DurationUnit:
return static_cast<int>( Duration::Unit_h );
case Role::Minimum:
return static_cast<int>( Duration::Unit_h );
case Role::Maximum:
return static_cast<int>( Duration::Unit_h );
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
default:
break;
}
return QVariant();
}
QVariant CompletionEntryItemModel::data( const QModelIndex &idx, int role ) const
{
if ( idx.column() == Property_PlannedEffort && m_resource ) {
switch ( role ) {
case Qt::DisplayRole: {
Duration v = m_task->plannedEffortTo( m_resource, date( idx.row() ).toDate() );
return v.format();
}
default:
return QVariant();
}
} else if ( idx.column() == Property_ActualAccumulated ) {
switch ( role ) {
case Qt::DisplayRole: {
Duration v;
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e ) {
v = e->totalPerformed;
}
return v.format();
}
default:
return QVariant();
}
}
return KPlato::CompletionEntryItemModel::data( idx, role );
}
bool CompletionEntryItemModel::setData( const QModelIndex &idx, const QVariant &value, int role )
{
//debugPlanWork;
switch ( role ) {
case Qt::EditRole: {
if ( idx.column() == Property_Date ) {
QDate od = date( idx.row() ).toDate();
removeEntry( od );
addEntry( value.toDate() );
// emit dataChanged( idx, idx );
m_calculate = true;
return true;
}
if ( idx.column() == Property_Completion ) {
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e == 0 ) {
return false;
}
e->percentFinished = value.toInt();
if ( m_calculate && m_node && idx.row() == rowCount() - 1 ) {
// calculate used/remaining
Duration est = m_node->plannedEffort( id(), ECCT_EffortWork );
e->totalPerformed = est * e->percentFinished / 100;
e->remainingEffort = est - e->totalPerformed;
} else if ( e->percentFinished == 100 && e->remainingEffort != 0 ) {
e->remainingEffort = Duration::zeroDuration;
}
emit dataChanged( idx, createIndex( idx.row(), 3 ) );
return true;
}
if ( idx.column() == Property_ActualEffort ) {
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e == 0 ) {
return false;
}
m_calculate = false;
Duration prev;
if ( idx.row() > 0 ) {
Completion::Entry *pe = m_completion->entry( date( idx.row() - 1 ).toDate() );
if ( pe ) {
prev = pe->totalPerformed;
}
}
double v( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration d = Estimate::scale( v, unit, scales() );
if ( d + prev == e->totalPerformed ) {
return false;
}
e->totalPerformed = d + prev;
emit dataChanged( idx, idx );
return true;
}
if ( idx.column() == Property_RemainigEffort ) {
Completion::Entry *e = m_completion->entry( date( idx.row() ).toDate() );
if ( e == 0 ) {
return false;
}
m_calculate = false;
double v( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration d = Estimate::scale( v, unit, scales() );
if ( d == e->remainingEffort ) {
return false;
}
e->remainingEffort = d;
debugPlanWork<<value<<d.format()<<e->remainingEffort.format();
emit dataChanged( idx, idx );
return true;
}
break;
}
default: break;
}
return false;
}
} //KPlatoWork namespace
diff --git a/src/workpackage/taskworkpackagemodel.cpp b/src/workpackage/taskworkpackagemodel.cpp
index 0c12a982..4010f305 100644
--- a/src/workpackage/taskworkpackagemodel.cpp
+++ b/src/workpackage/taskworkpackagemodel.cpp
@@ -1,709 +1,710 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "taskworkpackagemodel.h"
#include "part.h"
#include "workpackage.h"
#include "kptglobal.h"
#include "kptresource.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptcommand.h"
#include "kptitemmodelbase.h"
#include "kpttaskcompletedelegate.h"
#include <KGanttGlobal>
#include <QModelIndex>
#include <QMetaEnum>
#include <QObject>
#include <QAbstractItemDelegate>
#include "debugarea.h"
using namespace KPlato;
namespace KPlatoWork
{
TaskWorkPackageModel::TaskWorkPackageModel( Part *part, QObject *parent )
: ItemModelBase( parent ),
m_part( part )
{
connect( part, &Part::workPackageAdded, this, &TaskWorkPackageModel::addWorkPackage );
connect( part, &Part::workPackageRemoved, this, &TaskWorkPackageModel::removeWorkPackage );
}
Qt::ItemFlags TaskWorkPackageModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
flags &= ~( Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
Node *n = nodeForIndex( index );
if ( n == 0 ) {
return flags;
}
if ( n->type() != Node::Type_Task && n->type() != Node::Type_Milestone ) {
return flags;
}
Task *t = static_cast<Task*>( n );
if ( ! t->completion().isStarted() ) {
switch ( index.column() ) {
case NodeActualStart:
flags |= Qt::ItemIsEditable;
break;
case NodeCompleted:
flags |= Qt::ItemIsEditable;
break;
default: break;
}
} else if ( ! t->completion().isFinished() ) {
// task is running
switch ( index.column() ) {
case NodeActualFinish:
case NodeCompleted:
case NodeRemainingEffort:
case NodeActualEffort:
flags |= Qt::ItemIsEditable;
break;
default: break;
}
}
return flags;
}
void TaskWorkPackageModel::slotNodeToBeInserted( Node *parent, int row )
{
//debugPlanWork<<parent->name()<<"; "<<row;
beginInsertRows( indexForNode( parent ), row, row );
}
void TaskWorkPackageModel::slotNodeInserted( Node */*node*/ )
{
//debugPlanWork<<node->parentNode()->name()<<"-->"<<node->name();
endInsertRows();
}
void TaskWorkPackageModel::slotNodeToBeRemoved( Node *node )
{
//debugPlanWork<<node->name();
int row = indexForNode( node ).row();
beginRemoveRows( indexForNode( node->parentNode() ), row, row );
}
void TaskWorkPackageModel::slotNodeRemoved( Node */*node*/ )
{
//debugPlanWork<<node->name();
endRemoveRows();
}
void TaskWorkPackageModel::slotNodeChanged( Node *node )
{
if ( node == 0 || node->type() == Node::Type_Project ) {
return;
}
int row = indexForNode( node ).row();
debugPlanWork<<node->name()<<row;
emit dataChanged( createIndex( row, 0, node->parentNode() ), createIndex( row, columnCount()-1, node->parentNode() ) );
}
void TaskWorkPackageModel::slotDocumentAdded( Node *node, Document */*doc*/, int row )
{
QModelIndex parent = indexForNode( node );
if ( parent.isValid() ) {
beginInsertRows( parent, row, row );
endInsertRows();
}
}
void TaskWorkPackageModel::slotDocumentRemoved( Node *node, Document */*doc*/, int row )
{
QModelIndex parent = indexForNode( node );
if ( parent.isValid() ) {
beginRemoveRows( parent, row, row );
endRemoveRows();
}
}
void TaskWorkPackageModel::slotDocumentChanged( Node *node, Document */*doc*/, int row )
{
QModelIndex parent = indexForNode( node );
if ( parent.isValid() ) {
emit dataChanged( index( row, 0, parent ), index( row, columnCount( parent ), parent ) );
}
}
void TaskWorkPackageModel::addWorkPackage( WorkPackage *package, int row )
{
beginInsertRows( QModelIndex(), row, row );
Project *project = package->project();
endInsertRows();
if ( project ) {
connect( project, &KPlato::Project::nodeChanged, this, &TaskWorkPackageModel::slotNodeChanged );
connect( project, &KPlato::Project::nodeToBeAdded, this, &TaskWorkPackageModel::slotNodeToBeInserted );
connect( project, &KPlato::Project::nodeToBeRemoved, this, &TaskWorkPackageModel::slotNodeToBeRemoved );
connect( project, &KPlato::Project::nodeAdded, this, &TaskWorkPackageModel::slotNodeInserted );
connect( project, &KPlato::Project::nodeRemoved, this, &TaskWorkPackageModel::slotNodeRemoved );
connect(project, &KPlato::Project::documentAdded, this, &TaskWorkPackageModel::slotDocumentAdded);
connect(project, &KPlato::Project::documentRemoved, this, &TaskWorkPackageModel::slotDocumentRemoved);
connect(project, &KPlato::Project::documentChanged, this, &TaskWorkPackageModel::slotDocumentChanged);
}
}
void TaskWorkPackageModel::removeWorkPackage( WorkPackage *package, int row )
{
beginRemoveRows( QModelIndex(), row, row );
Project *project = package->project();
debugPlanWork<<package->project();
if ( project ) {
disconnect( project, &KPlato::Project::nodeChanged, this, &TaskWorkPackageModel::slotNodeChanged );
disconnect( project, &KPlato::Project::nodeToBeAdded, this, &TaskWorkPackageModel::slotNodeToBeInserted );
disconnect( project, &KPlato::Project::nodeToBeRemoved, this, &TaskWorkPackageModel::slotNodeToBeRemoved );
disconnect( project, &KPlato::Project::nodeAdded, this, &TaskWorkPackageModel::slotNodeInserted );
disconnect( project, &KPlato::Project::nodeRemoved, this, &TaskWorkPackageModel::slotNodeRemoved );
disconnect(project, &KPlato::Project::documentAdded, this, &TaskWorkPackageModel::slotDocumentAdded);
disconnect(project, &KPlato::Project::documentRemoved, this, &TaskWorkPackageModel::slotDocumentRemoved);
disconnect(project, &KPlato::Project::documentChanged, this, &TaskWorkPackageModel::slotDocumentChanged);
}
endRemoveRows();
}
QVariant TaskWorkPackageModel::name( const Resource *r, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return r->name();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant TaskWorkPackageModel::email( const Resource *r, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
return r->email();
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant TaskWorkPackageModel::projectName( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole: {
const Node *proj = node->projectNode();
return proj == 0 ? QVariant() : proj->name();
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
QVariant TaskWorkPackageModel::projectManager( const Node *node, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole: {
const Node *proj = node->projectNode();
return proj == 0 ? QVariant() : proj->leader();
}
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return QVariant();
}
return QVariant();
}
int TaskWorkPackageModel::rowCount( const QModelIndex &parent ) const
{
if ( ! parent.isValid() ) {
//debugPlanWork<<parent<<"nodes:"<<m_part->workPackageCount();
return m_part->workPackageCount(); // == no of nodes (1 node pr wp)
}
Node *n = nodeForIndex( parent );
if ( n ) {
//debugPlanWork<<parent<<"docs:"<<n->documents().count();
return n->documents().count();
}
//debugPlanWork<<parent<<"rows:"<<0;
return 0; // documents have no children
}
int TaskWorkPackageModel::columnCount( const QModelIndex & ) const
{
return columnMap().keyCount();
}
QVariant TaskWorkPackageModel::data( const QModelIndex &index, int role ) const
{
if ( ! index.isValid() ) {
return QVariant();
}
Node *n = nodeForIndex( index );
if ( n ) {
return nodeData( n, index.column(), role );
}
Document *doc = documentForIndex( index );
if ( doc ) {
return documentData( doc, index.column(), role );
}
return QVariant();
}
QVariant TaskWorkPackageModel::actualStart( Node *n, int role ) const
{
QVariant v = m_nodemodel.startedTime( n, role );
if ( role == Qt::EditRole && ! v.toDateTime().isValid() ) {
v = QDateTime::currentDateTime();
}
return v;
}
QVariant TaskWorkPackageModel::actualFinish( Node *n, int role ) const
{
QVariant v = m_nodemodel.finishedTime( n, role );
if ( role == Qt::EditRole && ! v.toDateTime().isValid() ) {
v = QDateTime::currentDateTime();
}
return v;
}
QVariant TaskWorkPackageModel::plannedEffort( Node *n, int role ) const
{
switch ( role ) {
case Qt::DisplayRole:
case Qt::ToolTipRole: {
Duration v = n->plannedEffort( CURRENTSCHEDULE, ECCT_EffortWork );
return v.format();
}
default:
break;
}
return QVariant();
}
QVariant TaskWorkPackageModel::status( Node *n, int role ) const
{
return m_nodemodel.status( n, role );
}
QVariant TaskWorkPackageModel::nodeData( Node *n, int column, int role ) const
{
if ( role >= Qt::UserRole ) {
// debugPlanWork<<this<<n->name()<<column<<role;
switch ( role ) {
case KGantt::ItemTypeRole:
switch ( n->type() ) {
case Node::Type_Task: return KGantt::TypeTask;
default: break;
}
break;
case KGantt::StartTimeRole:
debugPlanWork<<this<<n->name()<<"start:"<<n->startTime();
return m_nodemodel.data( n, NodeModel::NodeStartTime, Qt::EditRole );
case KGantt::EndTimeRole:
debugPlanWork<<this<<n->name()<<"end:"<<n->endTime();
return m_nodemodel.data( n, NodeModel::NodeEndTime, Qt::EditRole );
default: break;
}
}
switch ( column ) {
case NodeName: return m_nodemodel.data( n, NodeModel::NodeName, role );
case NodeType: return m_nodemodel.data( n, NodeModel::NodeType, role );
case NodeResponsible: return m_nodemodel.data( n, NodeModel::NodeResponsible, role );
case NodeDescription: return m_nodemodel.data( n, NodeModel::NodeDescription, role );
// After scheduling
case NodeStartTime: return m_nodemodel.data( n, NodeModel::NodeStartTime, role );
case NodeEndTime: return m_nodemodel.data( n, NodeModel::NodeEndTime, role );
case NodeAssignments: return m_nodemodel.data( n, NodeModel::NodeAssignments, role );
// Completion
case NodeCompleted: return m_nodemodel.data( n, NodeModel::NodeCompleted, role );
case NodeActualEffort: return m_nodemodel.data( n, NodeModel::NodeActualEffort, role );
case NodeRemainingEffort: return m_nodemodel.data( n, NodeModel::NodeRemainingEffort, role );
case NodePlannedEffort: return plannedEffort( n, role );
case NodeActualStart: return actualStart( n, role );
case NodeStarted: return m_nodemodel.data( n, NodeModel::NodeStarted, role );
case NodeActualFinish: return actualFinish( n, role );
case NodeFinished: return m_nodemodel.data( n, NodeModel::NodeFinished, role );
case NodeStatus: return status( n, role );
case NodeStatusNote: return m_nodemodel.data( n, NodeModel::NodeStatusNote, role );
case ProjectName: return projectName( n, role );
case ProjectManager: return projectManager( n, role );
default:
//debugPlanWork<<"Invalid column number: "<<index.column()<<endl;
break;
}
return "";
}
QVariant TaskWorkPackageModel::documentData( Document *doc, int column, int role ) const
{
//debugPlanWork<<doc->url().fileName()<<column<<role;
if ( role == Qt::DisplayRole ) {
switch ( column ) {
case NodeName: return doc->name();
case NodeType: return doc->typeToString( doc->type(), true );
case NodeStatusNote: return doc->status();
default:
return "";
}
} else if ( role == Qt::ToolTipRole ) {
switch ( column ) {
case NodeName: return doc->typeToString( doc->type(), true );
default:
break;
}
}
return QVariant();
}
bool TaskWorkPackageModel::setCompletion( Node *node, const QVariant &value, int role )
{
if ( role != Qt::EditRole ) {
return false;
}
if ( node->type() == Node::Type_Task ) {
Completion &c = static_cast<Task*>( node )->completion();
QDate date = qMax( c.entryDate(), QDate::currentDate() );
QDateTime dt( date, QTime::currentTime() );
// xgettext: no-c-format
MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify completion" ) );
if ( ! c.isStarted() ) {
m->addCommand( new ModifyCompletionStartedCmd( c, true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) );
}
m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, value.toInt() ) );
if ( value.toInt() == 100 ) {
m->addCommand( new ModifyCompletionFinishedCmd( c, true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) );
}
bool newentry = c.entryDate() < date;
emit executeCommand( m ); // also adds a new entry if necessary
if ( newentry ) {
// new entry so calculate used/remaining based on completion
Duration planned = static_cast<Task*>( node )->plannedEffort( m_nodemodel.id() );
Duration actual = ( planned * value.toInt() ) / 100;
debugPlanWork<<planned.toString()<<value.toInt()<<actual.toString();
NamedCommand *cmd = new ModifyCompletionActualEffortCmd( c, date, actual );
cmd->execute();
m->addCommand( cmd );
cmd = new ModifyCompletionRemainingEffortCmd( c, date, planned - actual );
cmd->execute();
m->addCommand( cmd );
} else if ( c.isFinished() && c.remainingEffort() != 0 ) {
ModifyCompletionRemainingEffortCmd *cmd = new ModifyCompletionRemainingEffortCmd( c, date, Duration::zeroDuration );
cmd->execute();
m->addCommand( cmd );
}
return true;
}
if ( node->type() == Node::Type_Milestone ) {
Completion &c = static_cast<Task*>( node )->completion();
if ( value.toInt() > 0 ) {
QDateTime dt = QDateTime::currentDateTime();
QDate date = dt.date();
MacroCommand *m = new MacroCommand( kundo2_i18n( "Set finished" ) );
m->addCommand( new ModifyCompletionStartedCmd( c, true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionFinishedCmd( c, true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) );
m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, 100 ) );
emit executeCommand( m ); // also adds a new entry if necessary
return true;
}
return false;
}
return false;
}
bool TaskWorkPackageModel::setRemainingEffort( Node *node, const QVariant &value, int role )
{
if ( role == Qt::EditRole && node->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( node );
double d( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration dur( d, unit );
emit executeCommand( new ModifyCompletionRemainingEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify remaining effort" ) ) );
return true;
}
return false;
}
bool TaskWorkPackageModel::setActualEffort( Node *node, const QVariant &value, int role )
{
if ( role == Qt::EditRole && node->type() == Node::Type_Task ) {
Task *t = static_cast<Task*>( node );
double d( value.toList()[0].toDouble() );
Duration::Unit unit = static_cast<Duration::Unit>( value.toList()[1].toInt() );
Duration dur( d, unit );
emit executeCommand( new ModifyCompletionActualEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify actual effort" ) ) );
return true;
}
return false;
}
bool TaskWorkPackageModel::setStartedTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Task *t = qobject_cast<Task*>( node );
if ( t == 0 ) {
return false;
}
MacroCommand *m = new MacroCommand( kundo2_noi18n(headerData( NodeModel::NodeActualStart, Qt::Horizontal, Qt::DisplayRole ).toString()) ); //FIXME: proper description when string freeze is lifted
if ( ! t->completion().isStarted() ) {
m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) );
}
m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->type() == Node::Type_Milestone ) {
m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) );
m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->completion().percentFinished() < 100 ) {
Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration );
m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) );
}
}
emit executeCommand( m );
return true;
}
}
return false;
}
bool TaskWorkPackageModel::setFinishedTime( Node *node, const QVariant &value, int role )
{
switch ( role ) {
case Qt::EditRole: {
Task *t = qobject_cast<Task*>( node );
if ( t == 0 ) {
return false;
}
MacroCommand *m = new MacroCommand( kundo2_noi18n(headerData( NodeModel::NodeActualFinish, Qt::Horizontal, Qt::DisplayRole ).toString()) ); //FIXME: proper description when string freeze is lifted
if ( ! t->completion().isFinished() ) {
m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) );
if ( t->completion().percentFinished() < 100 ) {
QDate lastdate = t->completion().entryDate();
if ( ! lastdate.isValid() || lastdate < value.toDate() ) {
Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration );
m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) );
} else {
Completion::Entry *e = new Completion::Entry( *( t->completion().entry( lastdate ) ) );
e->percentFinished = 100;
m->addCommand( new ModifyCompletionEntryCmd( t->completion(), lastdate, e ) );
}
}
}
m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) );
if ( t->type() == Node::Type_Milestone ) {
m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) );
m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) );
}
emit executeCommand( m );
return true;
}
}
return false;
}
bool TaskWorkPackageModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( ! index.isValid() ) {
return ItemModelBase::setData( index, value, role );
}
switch ( index.column() ) {
case NodeCompleted:
return setCompletion( nodeForIndex( index ), value, role );
case NodeRemainingEffort:
return setRemainingEffort( nodeForIndex( index ), value, role );
case NodeActualEffort:
return setActualEffort( nodeForIndex( index ), value, role );
case NodeActualStart:
return setStartedTime( nodeForIndex( index ), value, role );
case NodeActualFinish:
return setFinishedTime( nodeForIndex( index ), value, role );
default:
break;
}
return false;
}
QVariant TaskWorkPackageModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Vertical ) {
return section;
}
if ( role == Qt::DisplayRole ) {
switch ( section ) {
case NodeName: return i18n( "Name" );
case NodeType: return i18n( "Type" );
case NodeResponsible: return i18n( "Responsible" );
case NodeDescription: return i18n( "Description" );
// After scheduling
case NodeStartTime: return i18n( "Planned Start" );
case NodeEndTime: return i18n( "Planned Finish" );
case NodeAssignments: return i18n( "Resource Assignments" );
// Completion
case NodeCompleted: return i18n( "Completion" );
case NodeActualEffort: return i18n( "Actual Effort" );
case NodeRemainingEffort: return i18n( "Remaining Effort" );
case NodePlannedEffort: return i18n( "Planned Effort" );
case NodeActualStart: return i18n( "Actual Start" );
case NodeStarted: return i18n( "Started" );
case NodeActualFinish: return i18n( "Actual Finish" );
case NodeFinished: return i18n( "Finished" );
case NodeStatus: return i18nc( "@title:column", "Status" );
case NodeStatusNote: return i18n( "Note" );
case ProjectName: return i18n( "Project Name" );
case ProjectManager: return i18n( "Project Manager" );
default:
//debugPlanWork<<"Invalid column number: "<<index.column()<<endl;
break;
}
}
return QVariant();
}
QModelIndex TaskWorkPackageModel::parent( const QModelIndex &idx ) const
{
if ( ! idx.isValid() ) {
return QModelIndex();
}
if ( isDocument( idx ) ) {
// a document index has a node as parent
return indexForNode( ptrToNode( idx ) );
}
// a node index has no parent
return QModelIndex();
}
QModelIndex TaskWorkPackageModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( ! parent.isValid() ) {
// create a node index
return createIndex( row, column, workPackage( row ) );
}
if ( isNode( parent ) ) {
// create a document index
return createIndex( row, column, nodeForIndex( parent ) );
}
// documents don't have children, so shouldn't get here
return QModelIndex();
}
Node *TaskWorkPackageModel::nodeForIndex( const QModelIndex &index ) const
{
WorkPackage *wp = ptrToWorkPackage( index );
if ( wp ) {
//debugPlanWork<<index<<parent->node()->name();
return wp->node();
}
return 0;
}
Document *TaskWorkPackageModel::documentForIndex( const QModelIndex &index ) const
{
if ( index.isValid() ) {
Node *parent = ptrToNode( index );
if ( parent && index.row() < parent->documents().count() ) {
//debugPlanWork<<index<<parent->name();
return parent->documents().value( index.row() );
}
}
return 0;
}
QModelIndex TaskWorkPackageModel::indexForNode( Node *node ) const
{
WorkPackage *p = m_part->workPackage( node );
if ( p == 0 ) {
return QModelIndex();
}
return createIndex( m_part->indexOf( p ), 0, p );
}
WorkPackage *TaskWorkPackageModel::workPackage( int index ) const
{
return m_part->workPackage( index );
}
QAbstractItemDelegate *TaskWorkPackageModel::createDelegate( int column, QWidget *parent ) const
{
switch ( column ) {
case NodeCompleted: return new TaskCompleteDelegate( parent );
case NodeRemainingEffort: return new DurationSpinBoxDelegate( parent );
case NodeActualEffort: return new DurationSpinBoxDelegate( parent );
case NodeActualStart: return new DateTimeCalendarDelegate( parent );
case NodeActualFinish: return new DateTimeCalendarDelegate( parent );
default: break;
}
return 0;
}
WorkPackage *TaskWorkPackageModel::ptrToWorkPackage( const QModelIndex &idx ) const
{
return qobject_cast<WorkPackage*>( static_cast<QObject*>( idx.internalPointer() ) );
}
Node *TaskWorkPackageModel::ptrToNode( const QModelIndex &idx ) const
{
return qobject_cast<Node*>( static_cast<QObject*>( idx.internalPointer() ) );
}
bool TaskWorkPackageModel::isNode( const QModelIndex &idx ) const
{
// a node index: ptr is WorkPackage*
return qobject_cast<WorkPackage*>( static_cast<QObject*>( idx.internalPointer() ) ) != 0;
}
bool TaskWorkPackageModel::isDocument( const QModelIndex &idx ) const
{
// a document index: ptr is Node*
return qobject_cast<Node*>( static_cast<QObject*>( idx.internalPointer() ) ) != 0;
}
} //namespace KPlato
diff --git a/src/workpackage/taskworkpackageview.cpp b/src/workpackage/taskworkpackageview.cpp
index 5a1e1490..7dee0c4e 100644
--- a/src/workpackage/taskworkpackageview.cpp
+++ b/src/workpackage/taskworkpackageview.cpp
@@ -1,930 +1,931 @@
/* This file is part of the KDE project
Copyright (C) 2007 - 2009, 2012 Dag Andersen <danders@get2net.dk>
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 <KGanttGraphicsView>
#include <KGanttTreeViewRowController>
#include <KGanttProxyModel>
#include <KGanttDateTimeGrid>
#include <KGanttStyleOptionGanttItem>
#include <KoIcon.h>
#include <KoXmlReader.h>
#include <QDragMoveEvent>
#include <QMenu>
#include <QModelIndex>
#include <QWidget>
#include <QSortFilterProxyModel>
#include <QHeaderView>
#include <QPointer>
#include <QAction>
#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<int> lst1; lst1 << 2 << -1; // display column 0 and 1 (NodeName and NodeType ) in left view
masterView()->setDefaultColumns( QList<int>() << 0 << 1 );
QList<int> show;
show << TaskWorkPackageModel::NodeCompleted
<< TaskWorkPackageModel::NodeActualEffort
<< TaskWorkPackageModel::NodeRemainingEffort
<< TaskWorkPackageModel::NodePlannedEffort
<< TaskWorkPackageModel::NodeStartTime
<< TaskWorkPackageModel::NodeActualStart
<< TaskWorkPackageModel::NodeEndTime
<< TaskWorkPackageModel::NodeActualFinish
<< TaskWorkPackageModel::ProjectName
<< TaskWorkPackageModel::ProjectManager;
QList<int> lst2;
for ( int i = 0; i < m->columnCount(); ++i ) {
if ( ! show.contains( i ) ) {
lst2 << i;
}
}
hideColumns( lst1, lst2 );
slaveView()->setDefaultColumns( show );
masterView()->setFocus();
debugPlanWork<<PlanWorkSettings::self()->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<TaskWorkPackageModel*>( static_cast<QSortFilterProxyModel*>( model() )->sourceModel() );
}
Project *TaskWorkPackageTreeView::project() const
{
return itemModel()->project();
}
Document *TaskWorkPackageTreeView::currentDocument() const
{
QSortFilterProxyModel *sf = qobject_cast<QSortFilterProxyModel*>( model() );
Q_ASSERT( sf );
if ( sf == 0 ) {
return 0;
}
return itemModel()->documentForIndex( sf->mapToSource( selectionModel()->currentIndex() ) );
}
Node *TaskWorkPackageTreeView::currentNode() const
{
QSortFilterProxyModel *sf = qobject_cast<QSortFilterProxyModel*>( model() );
Q_ASSERT( sf );
if ( sf == 0 ) {
return 0;
}
return itemModel()->nodeForIndex( sf->mapToSource( selectionModel()->currentIndex() ) );
}
QList<Node*> TaskWorkPackageTreeView::selectedNodes() const
{
QList<Node*> lst;
QSortFilterProxyModel *sf = qobject_cast<QSortFilterProxyModel*>( 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<<index.column();
}
void TaskWorkPackageTreeView::dragMoveEvent(QDragMoveEvent */*event*/)
{
/* if (dragDropMode() == InternalMove
&& (event->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<Node*> AbstractView::selectedNodes() const
{
return QList<Node*>();
}
Node *AbstractView::currentNode() const
{
return 0;
}
Document *AbstractView::currentDocument() const
{
return 0;
}
void AbstractView::slotHeaderContextMenuRequested( const QPoint &pos )
{
debugPlanWork;
QList<QAction*> 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<<node->name()<<" :"<<pos;
QString name;
switch ( node->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<<name;
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
emit requestPopupMenu( name, pos );
}
void AbstractView::slotContextMenuRequested( Document *doc, const QPoint& pos )
{
debugPlanWork<<doc->url()<<" :"<<pos;
QString name;
switch ( doc->type() ) {
case Document::Type_Product:
name = "editdocument_popup";
break;
default:
name = "viewdocument_popup";
break;
}
debugPlanWork<<name;
if ( name.isEmpty() ) {
slotHeaderContextMenuRequested( pos );
return;
}
emit requestPopupMenu( name, pos );
}
void AbstractView::sectionsMoved()
{
saveContext();
}
bool AbstractView::loadContext()
{
return true;
}
void AbstractView::saveContext()
{
}
KoPrintJob *AbstractView::createPrintJob()
{
return 0;
}
//-----------------------------------
TaskWorkPackageView::TaskWorkPackageView( Part *part, QWidget *parent )
: AbstractView( part, parent )
{
debugPlanWork<<"-------------------- creating TaskWorkPackageView -------------------";
QVBoxLayout * l = new QVBoxLayout( this );
l->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)), 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<Node*> 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<<index<<pos;
if ( ! index.isValid() ) {
slotHeaderContextMenuRequested( pos );
return;
}
QSortFilterProxyModel *sf = qobject_cast<QSortFilterProxyModel*>( 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<SplitItemViewSettupDialog> 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<<endl<<doc.toString();
}
//-------------------------------------------
GanttItemDelegate::GanttItemDelegate( QObject *parent )
: KPlato::GanttItemDelegate( parent )
{
showResources = false;
showTaskName = true;
showTaskLinks = false;
showProgress = true;
showPositiveFloat = false;
showNegativeFloat = false;
showCriticalPath = false;
showCriticalTasks = false;
showAppointments = false;
showNoInformation = false;
showTimeConstraint = false;
showSchedulingError = false;
showStatus = true;
QLinearGradient b( 0., 0., 0., QApplication::fontMetrics().height() );
b.setColorAt( 0., Qt::green );
b.setColorAt( 1., Qt::darkGreen );
m_brushes.insert( Brush_Normal, QBrush( b ) );
b.setColorAt( 0., Qt::red );
b.setColorAt( 1., Qt::darkRed );
m_brushes.insert( Brush_Late, QBrush( b ) );
b.setColorAt( 0., Qt::gray );
b.setColorAt( 1., Qt::darkGray );
m_brushes.insert( Brush_Finished, QBrush( b ) );
b.setColorAt( 0., Qt::blue );
b.setColorAt( 1., Qt::darkBlue );
m_brushes.insert( Brush_ReadyToStart, QBrush( b ) );
b.setColorAt( 0., Qt::white );
b.setColorAt( 1., Qt::gray );
m_brushes.insert( Brush_NotReadyToStart, QBrush( b ) );
b.setColorAt( 0., Qt::white );
b.setColorAt( 1., Qt::white );
m_brushes.insert( Brush_NotScheduled, QBrush( b ) );
}
void GanttItemDelegate::paintGanttItem( QPainter* painter, const KGantt::StyleOptionGanttItem& opt, const QModelIndex& idx )
{
if ( !idx.isValid() ) return;
const KGantt::ItemType typ = static_cast<KGantt::ItemType>( 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<int>( 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<QBrush>() : 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<nl/>"
"Actual finish: %2<nl/>"
"Planned finish: %3<nl/>"
"Status: %4<nl/>"
"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<nl/>"
"Completion: %2 %<nl/>"
"Actual start: %3<nl/>"
"Planned: %4 - %5<nl/>"
"Status: %6<nl/>"
"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<nl/>"
"Planned: %2 - %3<nl/>"
"Status: %4<nl/>"
"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<int> 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<KGantt::ProxyModel*>( 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<KGantt::DateTimeGrid*>( 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<<parent<<start<<end;
if ( ! parent.isValid() ) {
for ( int i = start; i <= end; ++i ) {
updateDateTimeGrid( m_itemmodel->workPackage( i ) );
}
}
}
void GanttView::slotRowsRemoved( const QModelIndex &/*parent*/, int /*start*/, int /*end*/ )
{
KGantt::DateTimeGrid *g = static_cast<KGantt::DateTimeGrid*>( grid() );
g->setStartDateTime( QDateTime() );
for ( int i = 0; i < m_part->workPackageCount(); ++i ) {
updateDateTimeGrid( m_part->workPackage( i ) );
}
}
void GanttView::updateDateTimeGrid( WorkPackage *wp )
{
debugPlanWork<<wp;
if ( ! wp || ! wp->project() || ! wp->project()->childNode( 0 ) ) {
return;
}
Task *task = static_cast<Task*>( 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<KGantt::DateTimeGrid*>( 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<Node*> GanttView::selectedNodes() const
{
QList<Node*> 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<Node*> 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<<idx<<pos;
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 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<ItemViewSettupDialog> 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<<endl<<doc.toString();
}
} // namespace KPlatoWork
diff --git a/src/workpackage/view.cpp b/src/workpackage/view.cpp
index 72ff73de..18ee90f1 100644
--- a/src/workpackage/view.cpp
+++ b/src/workpackage/view.cpp
@@ -1,494 +1,495 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2002 - 2009, 2011, 2012 Dag Andersen <danders@get2net.dk>
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 "view.h"
#include "mainwindow.h"
#include "taskworkpackageview.h"
#include "workpackage.h"
#include "packagesettings.h"
#include "taskcompletiondialog.h"
#include "calligraplanworksettings.h"
#include "kpttaskeditor.h"
#include "kpttaskdescriptiondialog.h"
#include "kptcommonstrings.h"
#include "KoDocumentInfo.h"
#include <KoMainWindow.h>
#include <QApplication>
#include <QLabel>
#include <QString>
#include <QSize>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QPrinter>
#include <QPrintDialog>
#include <QDomDocument>
#include <QPointer>
#include <QMenu>
#include <QAction>
#include <KLocalizedString>
#include <ktoolbar.h>
#include <kxmlguifactory.h>
#include <ktoolinvocation.h>
#include <kactioncollection.h>
#include <QTemporaryFile>
#include <kmessagebox.h>
#include <KoIcon.h>
#include "part.h"
#include "factory.h"
#include "kptviewbase.h"
#include "kptdocumentseditor.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kpttask.h"
#include "kptcommand.h"
#include "kptdocuments.h"
#include "kpttaskprogressdialog.h"
#include "kptcalendar.h"
#include <assert.h>
#include "debugarea.h"
namespace KPlatoWork
{
View::View( Part *part, QWidget *parent, KActionCollection *collection )
: QStackedWidget( parent ),
m_part( part ),
m_scheduleActionGroup( new QActionGroup( this ) ),
m_manager( 0 )
{
m_readWrite = part->isReadWrite();
debugPlanWork<<m_readWrite;
// Add sub views
createViews();
// The menu items
// ------ Edit
actionRemoveSelectedPackages = new QAction(koIcon("edit-delete"), i18n("Remove Packages"), this);
collection->addAction("package_remove_selected", actionRemoveSelectedPackages );
connect( actionRemoveSelectedPackages, &QAction::triggered, this, &View::slotRemoveSelectedPackages );
actionRemoveCurrentPackage = new QAction(koIcon("edit-delete"), i18n("Remove Package"), this);
collection->addAction("package_remove_current", actionRemoveCurrentPackage );
connect( actionRemoveCurrentPackage, &QAction::triggered, this, &View::slotRemoveCurrentPackage );
actionViewList = new QAction(koIcon("view-list-tree"), i18n("List"), this);
actionViewList->setToolTip( i18nc( "@info:tooltip", "Select task list" ) );
collection->addAction("view_list", actionViewList );
connect( actionViewList, &QAction::triggered, this, &View::slotViewList );
actionViewGantt = new QAction(koIcon("view-time-schedule"), i18n("Gantt"), this);
actionViewGantt->setToolTip( i18nc( "@info:tooltip", "Select timeline" ) );
collection->addAction("view_gantt", actionViewGantt );
connect( actionViewGantt, &QAction::triggered, this, &View::slotViewGantt );
// actionTaskProgress = new QAction(koIcon("document-edit"), i18n("Progress..."), this);
// collection->addAction("task_progress", actionTaskProgress );
// connect( actionTaskProgress, SIGNAL(triggered(bool)), SLOT(slotTaskProgress()) );
//------ Settings
actionConfigure = new QAction(koIcon("configure"), i18n("Configure PlanWork..."), this);
collection->addAction("configure", actionConfigure );
connect( actionConfigure, &QAction::triggered, this, &View::slotConfigure );
//------ Popups
actionEditDocument = new QAction(koIcon("document-edit"), i18n("Edit..."), this);
collection->addAction("edit_document", actionEditDocument );
connect( actionEditDocument, SIGNAL(triggered(bool)), SLOT(slotEditDocument()) );
actionViewDocument = new QAction(koIcon("document-preview"), i18nc( "@verb", "View..."), this);
collection->addAction("view_document", actionViewDocument );
connect( actionViewDocument, &QAction::triggered, this, &View::slotViewDocument );
// FIXME remove UndoText::removeDocument() when string freeze is lifted
actionRemoveDocument = new QAction(koIcon("list-remove"), UndoText::removeDocument().toString(), this);
collection->addAction("remove_document", actionRemoveDocument );
connect( actionRemoveDocument, &QAction::triggered, this, &View::slotRemoveDocument );
actionSendPackage = new QAction(koIcon("mail-send"), i18n("Send Package..."), this);
collection->addAction("edit_sendpackage", actionSendPackage );
connect( actionSendPackage, &QAction::triggered, this, &View::slotSendPackage );
actionPackageSettings = new QAction(koIcon("document-properties"), i18n("Package Settings..."), this);
collection->addAction("edit_packagesettings", actionPackageSettings );
connect( actionPackageSettings, &QAction::triggered, this, &View::slotPackageSettings );
actionTaskCompletion = new QAction(koIcon("document-edit"), i18n("Edit Progress..."), this);
collection->addAction("task_progress", actionTaskCompletion );
connect( actionTaskCompletion, &QAction::triggered, this, &View::slotTaskCompletion );
actionViewDescription = new QAction(/*koIcon("document_view"),*/ i18n("View Description..."), this);
collection->addAction("task_description", actionViewDescription );
connect( actionViewDescription, &QAction::triggered, this, &View::slotTaskDescription );
updateReadWrite( m_readWrite );
//debugPlanWork<<" end";
loadContext();
slotCurrentChanged( currentIndex() );
connect( this, &QStackedWidget::currentChanged, this, &View::slotCurrentChanged );
slotSelectionChanged();
}
View::~View()
{
saveContext();
}
void View::slotCurrentChanged( int index )
{
actionViewList->setEnabled( index != 0 );
actionViewGantt->setEnabled( index != 1 );
saveContext();
}
void View::slotViewList()
{
debugPlanWork;
setCurrentIndex( 0 );
}
void View::slotViewGantt()
{
debugPlanWork;
setCurrentIndex( 1 );
}
void View::createViews()
{
QWidget *v = createTaskWorkPackageView();
addWidget( v );
v = createGanttView();
addWidget( v );
}
TaskWorkPackageView *View::createTaskWorkPackageView()
{
TaskWorkPackageView *v = new TaskWorkPackageView( part(), this );
connect( v, &AbstractView::requestPopupMenu, this, &View::slotPopupMenu );
connect( v, &AbstractView::selectionChanged, this, &View::slotSelectionChanged );
v->updateReadWrite( m_readWrite );
v->loadContext();
return v;
}
TaskWPGanttView *View::createGanttView()
{
TaskWPGanttView *v = new TaskWPGanttView( part(), this );
connect( v, &AbstractView::requestPopupMenu, this, &View::slotPopupMenu );
connect( v, &AbstractView::selectionChanged, this, &View::slotSelectionChanged );
v->updateReadWrite( m_readWrite );
v->loadContext();
return v;
}
void View::setupPrinter( QPrinter &/*printer*/, QPrintDialog &/*printDialog */)
{
//debugPlanWork;
}
void View::print( QPrinter &/*printer*/, QPrintDialog &/*printDialog*/ )
{
}
void View::slotSelectionChanged()
{
bool enable = ! currentView()->selectedNodes().isEmpty();
actionRemoveSelectedPackages->setEnabled( enable );
actionRemoveCurrentPackage->setEnabled( enable );
}
void View::slotEditCut()
{
//debugPlanWork;
}
void View::slotEditCopy()
{
//debugPlanWork;
}
void View::slotEditPaste()
{
//debugPlanWork;
}
void View::slotProgressChanged( int )
{
}
void View::slotConfigure()
{
}
ScheduleManager *View::currentScheduleManager() const
{
return 0; // atm we always work with default manager
}
void View::updateReadWrite( bool readwrite )
{
debugPlanWork<<m_readWrite<<"->"<<readwrite;
m_readWrite = readwrite;
// actionTaskProgress->setEnabled( readwrite );
emit sigUpdateReadWrite( readwrite );
}
Part *View::part() const
{
return m_part;
}
void View::slotPopupMenu( const QString& name, const QPoint & pos )
{
Q_ASSERT( m_part->factory() );
if ( m_part->factory() == 0 ) {
return;
}
QMenu *menu = ( ( QMenu* ) m_part->factory() ->container( name, m_part ) );
if ( menu == 0 ) {
return;
}
QList<QAction*> lst;
AbstractView *v = currentView();
if ( v ) {
lst = v->contextActionList();
debugPlanWork<<lst;
if ( ! lst.isEmpty() ) {
menu->addSeparator();
foreach ( QAction *a, lst ) {
menu->addAction( a );
}
}
}
menu->exec( pos );
foreach ( QAction *a, lst ) {
menu->removeAction( a );
}
}
bool View::loadContext()
{
debugPlanWork;
setCurrentIndex( PlanWorkSettings::self()->currentView() );
return true;
}
void View::saveContext() const
{
debugPlanWork;
PlanWorkSettings::self()->setCurrentView( currentIndex() );
PlanWorkSettings::self()->save();
}
void View::slotEditDocument()
{
slotEditDocument( currentDocument() );
}
void View::slotEditDocument( Document *doc )
{
debugPlanWork<<doc;
if ( doc == 0 ) {
debugPlanWork<<"No document";
return;
}
if ( doc->type() != Document::Type_Product ) {
KMessageBox::error( 0, i18n( "This file is not editable" ) );
return;
}
part()->editWorkpackageDocument( doc );
}
void View::slotViewDocument()
{
emit viewDocument( currentDocument() );
}
void View::slotRemoveDocument()
{
part()->removeDocument( currentDocument() );
}
void View::slotPackageSettings()
{
WorkPackage *wp = part()->findWorkPackage( currentNode() );
if ( wp == 0 ) {
return;
}
QPointer<PackageSettingsDialog> dia = new PackageSettingsDialog( *wp, this );
if ( dia->exec() == QDialog::Accepted && dia ) {
KUndo2Command *cmd = dia->buildCommand();
if ( cmd ) {
debugPlanWork;
part()->addCommand( cmd );
}
}
delete dia;
}
void View::slotSendPackage()
{
Node *node = currentNode();
if ( node == 0 ) {
KMessageBox::error(0, i18n("No work package is selected" ) );
return;
}
debugPlanWork<<node->name();
WorkPackage *wp = part()->findWorkPackage( node );
if ( wp == 0 ) {
KMessageBox::error(0, i18n("Cannot find work package" ) );
return;
}
/* if ( wp->isModified() ) {
int r = KMessageBox::questionYesNoCancel( 0, i18n("This work package has been modified.\nDo you want to save it before sending?" ), node->name() );
switch ( r ) {
case KMessageBox::Cancel: return;
case KMessageBox::Yes: wp->saveToProjects( part() ); break;
default: break;
}
}*/
QTemporaryFile temp(QDir::tempPath() + QLatin1String("/calligraplanwork_XXXXXX") + QLatin1String( ".planwork" ));
temp.setAutoRemove( false );
if ( ! temp.open() ) {
KMessageBox::error( 0, i18n("Could not open temporary file. Sending is aborted." ) );
return;
}
bool wasmodified = wp->isModified();
wp->saveNativeFormat( part(), temp.fileName() );
wp->setModified( wasmodified );
QStringList attachURLs;
attachURLs << temp.fileName();
QString to = node->projectNode()->leader();
QString cc;
QString bcc;
QString subject = i18n( "Work Package: %1", node->name() );
QString body = node->projectNode()->name();
QString messageFile;
KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs );
}
void View::slotTaskDescription()
{
Task *node = qobject_cast<Task*>( currentNode() );
if ( node == 0 ) {
return;
}
QPointer<TaskDescriptionDialog> dlg = new TaskDescriptionDialog( *node, this, true );
dlg->exec();
delete dlg;
}
AbstractView *View::currentView() const
{
return qobject_cast<AbstractView*>( currentWidget() );
}
Node *View::currentNode() const
{
AbstractView *v = currentView();
return v ? v->currentNode() : 0;
}
Document *View::currentDocument() const
{
AbstractView *v = currentView();
return v ? v->currentDocument() : 0;
}
void View::slotTaskProgress()
{
debugPlanWork;
Task *n = qobject_cast<Task*>( currentNode() );
if ( n == 0 ) {
return;
}
StandardWorktime *w = qobject_cast<Project*>( n->projectNode() )->standardWorktime();
QPointer<TaskProgressDialog> dlg = new TaskProgressDialog( *n, currentScheduleManager(), w, this );
if ( dlg->exec() == QDialog::Accepted && dlg ) {
KUndo2Command *cmd = dlg->buildCommand();
if ( cmd ) {
cmd->redo(); //FIXME m_part->addCommand( cmd );
}
}
}
void View::slotTaskCompletion()
{
debugPlanWork;
WorkPackage *wp = m_part->findWorkPackage( currentNode() );
if ( wp == 0 ) {
return;
}
QPointer<TaskCompletionDialog> dlg = new TaskCompletionDialog( *wp, currentScheduleManager(), this );
if ( dlg->exec() == QDialog::Accepted && dlg ) {
KUndo2Command *cmd = dlg->buildCommand();
if ( cmd ) {
m_part->addCommand( cmd );
}
}
delete dlg;
}
void View::slotRemoveSelectedPackages()
{
debugPlanWork;
QList<Node*> lst = currentView()->selectedNodes();
if ( lst.isEmpty() ) {
return;
}
m_part->removeWorkPackages( lst );
}
void View::slotRemoveCurrentPackage()
{
debugPlanWork;
Node *n = currentNode();
if ( n == 0 ) {
return;
}
m_part->removeWorkPackage( n );
}
} //KPlatoWork namespace
diff --git a/src/workpackage/workpackage.cpp b/src/workpackage/workpackage.cpp
index d031cc2e..ad8d2640 100644
--- a/src/workpackage/workpackage.cpp
+++ b/src/workpackage/workpackage.cpp
@@ -1,821 +1,822 @@
/* This file is part of the KDE project
Copyright (C) 2009, 2012 Dag Andersen <danders@get2net.dk>
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 "workpackage.h"
#include "KPlatoXmlLoader.h" //NOTE: this file should probably be moved
#include "part.h"
#include "kptglobal.h"
#include "kptnode.h"
#include "kptproject.h"
#include "kptdocuments.h"
#include "kptcommand.h"
#include "kptxmlloaderobject.h"
#include "kptconfigbase.h"
#include "kptcommonstrings.h"
#include <KoStore.h>
#include <KoXmlReader.h>
#include <KoStoreDevice.h>
#include <KoResourcePaths.h>
#include <QDir>
#include <QUrl>
#include <QTimer>
#include <QDateTime>
#include <QDomDocument>
#include <kmessagebox.h>
#include "debugarea.h"
using namespace KPlato;
namespace KPlatoWork
{
WorkPackage::WorkPackage( bool fromProjectStore )
: m_project( new Project() ),
m_fromProjectStore( fromProjectStore ),
m_modified( false)
{
m_project->setConfig( &m_config );
}
WorkPackage::WorkPackage( Project *project, bool fromProjectStore )
: m_project( project ),
m_fromProjectStore( fromProjectStore ),
m_modified( false)
{
Q_ASSERT( project );
Q_ASSERT ( project->childNode( 0 ) );
m_project->setConfig( &m_config );
if ( ! project->scheduleManagers().isEmpty() ) {
// should be only one manager, so just get the first
const QList<ScheduleManager*> &lst = m_project->scheduleManagers();
project->setCurrentSchedule(lst.first()->scheduleId());
}
connect( project, &KPlato::Project::projectChanged, this, &WorkPackage::projectChanged );
}
WorkPackage::~WorkPackage()
{
delete m_project;
qDeleteAll( m_childdocs );
}
void WorkPackage::setSettings( const WorkPackageSettings &settings )
{
if ( m_settings != settings ) {
m_settings = settings;
setModified( true );
}
}
//TODO find a way to know when changes are undone
void WorkPackage::projectChanged()
{
debugPlanWork;
setModified( true );
}
bool WorkPackage::addChild( Part */*part*/, const Document *doc )
{
DocumentChild *ch = findChild( doc );
if ( ch ) {
if ( ch->isOpen() ) {
KMessageBox::error( 0, i18n( "Document is already open" ) );
return false;
}
} else {
ch = new DocumentChild( this );
if ( ! ch->setDoc( doc ) ) {
delete ch;
return false;
}
}
if ( ! ch->editDoc() ) {
delete ch;
return false;
}
if ( ! m_childdocs.contains( ch ) ) {
m_childdocs.append( ch );
connect( ch, &DocumentChild::fileModified, this, &WorkPackage::slotChildModified );
}
return true;
}
void WorkPackage::slotChildModified( bool mod )
{
debugPlanWork<<mod;
emit modified( isModified() );
emit saveWorkPackage( this );
}
void WorkPackage::removeChild( DocumentChild *child )
{
disconnect( child, &DocumentChild::fileModified, this, &WorkPackage::slotChildModified );
int i = m_childdocs.indexOf( child );
if ( i != -1 ) {
// TODO: process etc
m_childdocs.removeAt( i );
delete child;
} else {
warnPlanWork<<"Could not find document child";
}
}
bool WorkPackage::contains( const Document *doc ) const
{
return node() ? node()->documents().contains( doc ) : false;
}
DocumentChild *WorkPackage::findChild( const Document *doc ) const
{
foreach ( DocumentChild *c, m_childdocs ) {
if ( c->doc() == doc ) {
return c;
}
}
return 0;
}
bool WorkPackage::loadXML( const KoXmlElement &element, XMLLoaderObject &status )
{
bool ok = false;
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
debugPlanWork<<e.tagName();
if ( e.tagName() == "project" ) {
status.setProject( m_project );
debugPlanWork<<"loading new project";
if ( ! ( ok = m_project->load( e, status ) ) ) {
status.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" );
KMessageBox::error( 0, i18n( "Failed to load project: %1" , m_project->name() ) );
}
}
}
if ( ok ) {
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
debugPlanWork<<e.tagName();
if ( e.tagName() == "workpackage" ) {
Task *t = static_cast<Task*>( m_project->childNode( 0 ) );
t->workPackage().setOwnerName( e.attribute( "owner" ) );
t->workPackage().setOwnerId( e.attribute( "owner-id" ) );
Resource *r = m_project->findResource( t->workPackage().ownerId() );
if ( r == 0 ) {
debugPlanWork<<"Cannot find resource id!!"<<t->workPackage().ownerId()<<t->workPackage().ownerName();
}
debugPlanWork<<"is this me?"<<t->workPackage().ownerName();
KoXmlNode ch = e.firstChild();
for ( ; ! ch.isNull(); ch = ch.nextSibling() ) {
if ( ! ch.isElement() ) {
continue;
}
KoXmlElement el = ch.toElement();
debugPlanWork<<el.tagName();
if ( el.tagName() == "settings" ) {
m_settings.loadXML( el );
}
}
}
}
}
if ( ! m_project->scheduleManagers().isEmpty() ) {
// should be only one manager
const QList<ScheduleManager*> &lst = m_project->scheduleManagers();
m_project->setCurrentSchedule(lst.first()->scheduleId());
}
return ok;
}
bool WorkPackage::loadKPlatoXML( const KoXmlElement &element, XMLLoaderObject &status )
{
bool ok = false;
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
debugPlanWork<<e.tagName();
if ( e.tagName() == "project" ) {
status.setProject( m_project );
KPlatoXmlLoader loader( status, m_project );
debugPlanWork<<"loading new project";
if ( ! ( ok = loader.load( m_project, e, status ) ) ) {
status.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" );
KMessageBox::error( 0, i18n( "Failed to load project: %1" , m_project->name() ) );
}
}
}
if ( ok ) {
KoXmlNode n = element.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
debugPlanWork<<e.tagName();
if ( e.tagName() == "workpackage" ) {
Task *t = static_cast<Task*>( m_project->childNode( 0 ) );
t->workPackage().setOwnerName( e.attribute( "owner" ) );
t->workPackage().setOwnerId( e.attribute( "owner-id" ) );
Resource *r = m_project->findResource( t->workPackage().ownerId() );
if ( r == 0 ) {
debugPlanWork<<"Cannot find resource id!!"<<t->workPackage().ownerId()<<t->workPackage().ownerName();
}
debugPlanWork<<"is this me?"<<t->workPackage().ownerName();
KoXmlNode ch = e.firstChild();
for ( ; ! ch.isNull(); ch = ch.nextSibling() ) {
if ( ! ch.isElement() ) {
continue;
}
KoXmlElement el = ch.toElement();
debugPlanWork<<el.tagName();
if ( el.tagName() == "settings" ) {
m_settings.loadXML( el );
}
}
}
}
}
if ( ! m_project->scheduleManagers().isEmpty() ) {
// should be only one manager
const QList<ScheduleManager*> &lst = m_project->scheduleManagers();
m_project->setCurrentSchedule(lst.first()->scheduleId());
}
return ok;
}
bool WorkPackage::saveToStream( QIODevice * dev )
{
QDomDocument doc = saveXML();
// 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())
warnPlanWork << "wrote " << nwritten << "- expected" << s.size();
return nwritten == (int)s.size();
}
bool WorkPackage::saveNativeFormat( Part */*part*/, const QString &path )
{
if ( path.isEmpty() ) {
KMessageBox::error( 0, i18n("Cannot save to empty filename") );
return false;
}
debugPlanWork<<node()->name()<<path;
KoStore* store = KoStore::createStore(path, KoStore::Write, "application/x-vnd.kde.plan.work", KoStore::Auto );
if (store->bad()) {
KMessageBox::error( 0, i18n("Could not create the file for saving") );
delete store;
return false;
}
if (store->open("root")) {
KoStoreDevice dev(store);
if ( ! saveToStream(&dev) || ! store->close() ) {
debugPlanWork << "saveToStream failed";
delete store;
return false;
}
} else {
KMessageBox::error( 0, i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml") ) );
delete store;
return false;
}
if (!completeSaving(store)) {
delete store;
return false;
}
if (!store->finalize()) {
delete store;
return false;
}
// Success
delete store;
m_modified = false;
return true;
}
bool WorkPackage::completeSaving( KoStore *store )
{
debugPlanWork;
KoStore *oldstore = KoStore::createStore( filePath(), KoStore::Read, "", KoStore::Zip );
if ( oldstore->bad() ) {
KMessageBox::error( 0, i18n( "Failed to open store:\n %1", filePath() ) );
return false;
}
if (oldstore->hasFile( "documentinfo.xml" ) ) {
copyFile( oldstore, store, "documentinfo.xml" );
}
if (oldstore->hasFile( "preview.png" ) ) {
copyFile( oldstore, store, "preview.png" );
}
// First get all open documents
debugPlanWork<<m_childdocs.count();
foreach ( DocumentChild *cd, m_childdocs ) {
if ( ! cd->saveToStore( store ) ) {
}
}
// Then get new files
foreach ( const Document *doc, node()->documents().documents() ) {
if ( m_newdocs.contains( doc ) ) {
store->addLocalFile( m_newdocs[ doc ].path(), doc->url().fileName() );
m_newdocs.remove( doc );
// TODO remove temp file ??
}
}
// Then get files from the old store copied to the new store
foreach ( Document *doc, node()->documents().documents() ) {
if ( doc->sendAs() != Document::SendAs_Copy ) {
continue;
}
if ( ! store->hasFile( doc->url().fileName() ) ) {
copyFile( oldstore, store, doc->url().fileName() );
}
}
return true;
}
QString WorkPackage::fileName( const Part *part ) const
{
Q_UNUSED(part);
if ( m_project == 0 ) {
warnPlanWork<<"No project in this package";
return QString();
}
Node *n = node();
if ( n == 0 ) {
warnPlanWork<<"No node in this project";
return QString();
}
QString projectName = m_project->name().remove( ' ' );
// FIXME: workaround: KoResourcePaths::saveLocation( "projects", projectName + '/' );
const QString path = KoResourcePaths::saveLocation( "appdata", "projects/" + projectName + '/' );
QString wpName = QString( n->name().remove( ' ' ) + '_' + n->id() + ".planwork" );
return path + wpName;
}
void WorkPackage::removeFile()
{
QFile file( m_filePath );
if ( ! file.exists() ) {
warnPlanWork<<"No project in this package";
return;
}
file.remove();
}
void WorkPackage::saveToProjects( Part *part )
{
debugPlanWork;
QString path = fileName( part );
debugPlanWork<<node()->name();
if ( saveNativeFormat( part, path ) ) {
m_fromProjectStore = true;
m_filePath = path;
} else {
KMessageBox::error( 0, i18n( "Cannot save to projects store:\n%1" , path ) );
}
return;
}
bool WorkPackage::isModified() const
{
if ( m_modified ) {
return true;
}
foreach ( DocumentChild *ch, m_childdocs ) {
if ( ch->isModified() || ch->isFileModified() ) {
return true;
}
}
return false;
}
Node *WorkPackage::node() const
{
return m_project == 0 ? 0 : m_project->childNode( 0 );
}
Task *WorkPackage::task() const
{
Task *task = qobject_cast<Task*>( node() );
Q_ASSERT( task );
return task;
}
bool WorkPackage::removeDocument( Part *part, Document *doc )
{
Node *n = node();
if ( n == 0 ) {
return false;
}
part->addCommand( new DocumentRemoveCmd( n->documents(), doc, UndoText::removeDocument() ) );
return true;
}
bool WorkPackage::copyFile( KoStore *from, KoStore *to, const QString &filename )
{
QByteArray data;
if ( ! from->extractFile( filename , data ) ) {
KMessageBox::error( 0, i18n( "Failed read file:\n %1", filename ) );
return false;
}
if ( ! to->addDataToFile( data, filename ) ) {
KMessageBox::error( 0, i18n( "Failed write file:\n %1", filename ) );
return false;
}
debugPlanWork<<"Copied file:"<<filename;
return true;
}
QDomDocument WorkPackage::saveXML()
{
debugPlanWork;
QDomDocument document( "plan-workpackage" );
document.appendChild( document.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "planwork" );
doc.setAttribute( "editor", "PlanWork" );
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" );
wp.setAttribute( "time-tag", QDateTime::currentDateTime().toString( Qt::ISODate ) );
m_settings.saveXML( wp );
Task *t = qobject_cast<Task*>( node() );
if ( t ) {
wp.setAttribute( "owner", t->workPackage().ownerName() );
wp.setAttribute( "owner-id", t->workPackage().ownerId() );
}
doc.appendChild( wp );
m_project->save( doc );
return document;
}
void WorkPackage::merge( Part *part, const WorkPackage *wp, KoStore *store )
{
debugPlanWork;
const Node *from = wp->node();
Node *to = node();
MacroCommand *m = new MacroCommand( kundo2_i18n( "Merge data" ) );
if ( to->name() != from->name() ) {
m->addCommand( new NodeModifyNameCmd( *to, from->name() ) );
}
if ( to->description() != from->description() ) {
m->addCommand( new NodeModifyDescriptionCmd( *to, from->description() ) );
}
if ( to->startTime() != from->startTime() && from->startTime().isValid() ) {
m->addCommand( new NodeModifyStartTimeCmd( *to, from->startTime() ) );
}
if ( to->endTime() != from->endTime() && from->endTime().isValid() ) {
m->addCommand( new NodeModifyEndTimeCmd( *to, from->endTime() ) );
}
if ( to->leader() != from->leader() ) {
m->addCommand( new NodeModifyLeaderCmd( *to, from->leader() ) );
}
if ( from->type() == Node::Type_Task && from->type() == Node::Type_Task ) {
if ( static_cast<Task*>( to )->workPackage().ownerId() != static_cast<const Task*>( from )->workPackage().ownerId() ) {
debugPlanWork<<"merge:"<<"different owners"<<static_cast<const Task*>( from )->workPackage().ownerName()<<static_cast<Task*>( to )->workPackage().ownerName();
if ( static_cast<Task*>( to )->workPackage().ownerId().isEmpty() ) {
//TODO cmd
static_cast<Task*>( to )->workPackage().setOwnerId( static_cast<const Task*>( from )->workPackage().ownerId() );
static_cast<Task*>( to )->workPackage().setOwnerName( static_cast<const Task*>( from )->workPackage().ownerName() );
}
}
foreach ( Document *doc, from->documents().documents() ) {
Document *org = to->documents().findDocument( doc->url() );
if ( org ) {
// TODO: also handle modified type, sendas
// update ? what if open, modified ...
if ( doc->type() == Document::Type_Product ) {
//### FIXME. user feedback
warnPlanWork<<"We do not update existing deliverables (except name change)";
if ( doc->name() != org->name() ) {
m->addCommand( new DocumentModifyNameCmd( org, doc->name() ) );
}
} else {
if ( doc->name() != org->name() ) {
m->addCommand( new DocumentModifyNameCmd( org, doc->name() ) );
}
if ( doc->sendAs() != org->sendAs() ) {
m->addCommand( new DocumentModifySendAsCmd( org, doc->sendAs() ) );
}
if ( doc->sendAs() == Document::SendAs_Copy ) {
debugPlanWork<<"Update existing doc:"<<org->url();
openNewDocument( org, store );
}
}
} else {
debugPlanWork<<"new document:"<<doc->typeToString(doc->type())<<doc->url();
Document *newdoc = new Document( *doc );
m->addCommand( new DocumentAddCmd( to->documents(), newdoc ) );
if ( doc->sendAs() == Document::SendAs_Copy ) {
debugPlanWork<<"Copy file";
openNewDocument( newdoc, store );
}
}
}
}
const Project *fromProject = wp->project();
Project *toProject = m_project;
const ScheduleManager *fromSm = fromProject->scheduleManagers().value( 0 );
Q_ASSERT( fromSm );
ScheduleManager *toSm = toProject->scheduleManagers().value( 0 );
Q_ASSERT( toSm );
if ( fromSm->managerId() != toSm->managerId() || fromSm->scheduleId() != toSm->scheduleId() ) {
// rescheduled, update schedules
m->addCommand( new CopySchedulesCmd( *fromProject, *toProject ) );
}
if ( m->isEmpty() ) {
delete m;
} else {
part->addCommand( m );
}
}
void WorkPackage::openNewDocument( const Document *doc, KoStore *store )
{
const QUrl url = extractFile( doc, store );
if ( url.url().isEmpty() ) {
KMessageBox::error( 0, i18n( "Could not extract document from storage:<br>%1", doc->url().path() ) );
return;
}
if ( ! url.isValid() ) {
KMessageBox::error( 0, i18n( "Invalid URL:<br>%1", url.path() ) );
return;
}
m_newdocs.insert( doc, url );
}
int WorkPackage::queryClose( Part *part )
{
debugPlanWork<<isModified();
QString name = node()->name();
QStringList lst;
if ( ! m_childdocs.isEmpty() ) {
foreach ( DocumentChild *ch, m_childdocs ) {
if ( ch->isOpen() && ch->doc()->sendAs() == Document::SendAs_Copy ) {
lst << ch->doc()->url().fileName();
}
}
}
if ( ! lst.isEmpty() ) {
KMessageBox::ButtonCode result = KMessageBox::warningContinueCancelList( 0,
i18np(
"<p>The work package <b>'%2'</b> has an open document.</p><p>Data may be lost if you continue.</p>",
"<p>The work package <b>'%2'</b> has open documents.</p><p>Data may be lost if you continue.</p>",
lst.count(),
name ),
lst );
switch (result) {
case KMessageBox::Continue: {
debugPlanWork<<"Continue";
break;
}
default: // case KMessageBox::Cancel :
debugPlanWork<<"Cancel";
return KMessageBox::Cancel;
break;
}
}
if ( ! isModified() ) {
return KMessageBox::Yes;
}
KMessageBox::ButtonCode res = KMessageBox::warningYesNoCancel( 0,
i18n("<p>The work package <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name),
QString(),
KStandardGuiItem::save(),
KStandardGuiItem::discard());
switch (res) {
case KMessageBox::Yes: {
debugPlanWork<<"Yes";
saveToProjects( part );
break;
}
case KMessageBox::No:
debugPlanWork<<"No";
break;
default: // case KMessageBox::Cancel :
debugPlanWork<<"Cancel";
break;
}
return res;
}
QUrl WorkPackage::extractFile( const Document *doc )
{
KoStore *store = KoStore::createStore( m_filePath, KoStore::Read, "", KoStore::Zip );
if ( store->bad() )
{
KMessageBox::error( 0, i18n( "<p>Work package <b>'%1'</b></p><p>Could not open store:</p><p>%2</p>", node()->name(), m_filePath ) );
delete store;
return QUrl();
}
const QUrl url = extractFile( doc, store );
delete store;
return url;
}
QUrl WorkPackage::extractFile( const Document *doc, KoStore *store )
{
//FIXME: should use a special tmp dir
QString tmp = QDir::tempPath() + QLatin1Char('/') + doc->url().fileName();
const QUrl url = QUrl::fromLocalFile( tmp );
debugPlanWork<<"Extract: "<<doc->url().fileName()<<" -> "<<url.path();
if ( ! store->extractFile( doc->url().fileName(), url.path() ) ) {
KMessageBox::error( 0, i18n( "<p>Work package <b>'%1'</b></p><p>Could not extract file:</p><p>%2</p>", node()->name(), doc->url().fileName() ) );
return QUrl();
}
return url;
}
QString WorkPackage::id() const
{
QString id;
if ( node() ) {
id = m_project->id() + node()->id();
}
return id;
}
//--------------------------------
PackageRemoveCmd::PackageRemoveCmd( Part *part, WorkPackage *value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_part( part ),
m_value( value ),
m_mine( false )
{
}
PackageRemoveCmd::~PackageRemoveCmd()
{
if ( m_mine ) {
m_value->removeFile();
delete m_value;
}
}
void PackageRemoveCmd::execute()
{
m_part->removeWorkPackage( m_value );
m_mine = true;
}
void PackageRemoveCmd::unexecute()
{
m_part->addWorkPackage( m_value );
m_mine = false;
}
//---------------------
ModifyPackageSettingsCmd::ModifyPackageSettingsCmd( WorkPackage *wp, WorkPackageSettings &value, const KUndo2MagicString& name )
: NamedCommand( name ),
m_wp( wp ),
m_value( value ),
m_oldvalue( wp->settings() )
{
}
void ModifyPackageSettingsCmd::execute()
{
m_wp->setSettings( m_value );
}
void ModifyPackageSettingsCmd::unexecute()
{
m_wp->setSettings( m_oldvalue );
}
//---------------------
CopySchedulesCmd::CopySchedulesCmd( const Project &fromProject, Project &toProject, const KUndo2MagicString &name )
: NamedCommand( name ),
m_project( toProject )
{
QDomDocument olddoc;
QDomElement e = olddoc.createElement( "old" );
olddoc.appendChild( e );
toProject.save( e );
m_olddoc = olddoc.toString();
QDomDocument newdoc;
e = newdoc.createElement( "new" );
newdoc.appendChild( e );
fromProject.save( e );
m_newdoc = newdoc.toString();
}
void CopySchedulesCmd::execute()
{
load( m_newdoc );
}
void CopySchedulesCmd::unexecute()
{
load( m_olddoc );
}
void CopySchedulesCmd::load( const QString &doc )
{
clearSchedules();
KoXmlDocument d;
d.setContent( doc );
KoXmlElement proj = d.documentElement().namedItem( "project").toElement();
Q_ASSERT( ! proj.isNull() );
KoXmlElement task = proj.namedItem( "task").toElement();
Q_ASSERT( ! task.isNull() );
KoXmlElement ts = task.namedItem( "schedules").namedItem( "schedule").toElement();
Q_ASSERT( ! ts.isNull() );
KoXmlElement ps = proj.namedItem( "schedules").namedItem( "plan" ).toElement();
Q_ASSERT( ! ps.isNull() );
XMLLoaderObject status;
status.setProject( &m_project );
status.setVersion( PLAN_FILE_SYNTAX_VERSION );
// task first
NodeSchedule *ns = new NodeSchedule();
if ( ns->loadXML( ts, status ) ) {
debugPlanWork<<ns->name()<<ns->type()<<ns->id();
ns->setNode( m_project.childNode( 0 ) );
m_project.childNode( 0 )->addSchedule( ns );
} else {
Q_ASSERT( false );
delete ns;
}
// schedule manager next (includes main schedule and resource schedule)
ScheduleManager *sm = new ScheduleManager( m_project );
if ( sm->loadXML( ps, status ) ) {
m_project.addScheduleManager( sm );
} else {
Q_ASSERT( false );
delete sm;
}
if ( sm ) {
m_project.setCurrentSchedule( sm->scheduleId() );
}
m_project.childNode( 0 )->changed();
}
void CopySchedulesCmd::clearSchedules()
{
foreach ( Schedule *s, m_project.schedules() ) {
m_project.takeSchedule( s );
}
foreach ( Schedule *s, m_project.childNode( 0 )->schedules() ) {
foreach ( Appointment *a, s->appointments() ) {
if ( a->resource() && a->resource()->resource() ) {
a->resource()->resource()->takeSchedule( a->resource() );
}
}
m_project.childNode( 0 )->takeSchedule( s );
}
foreach ( ScheduleManager *sm, m_project.scheduleManagers() ) {
m_project.takeScheduleManager( sm );
delete sm;
}
}
} //KPlatoWork namespace