1/* This file is part of the Calligra project
2 * Copyright (c) 2008, 2012 Dag Andersen <danders@get2net.dk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "kptnodechartmodel.h"
21#include "kptnode.h"
22#include "kptproject.h"
23#include "kptschedule.h"
24#include "kptresource.h"
25#include "kptdebug.h"
26
27#include <QPointF>
28#include <QVariant>
29#include <QColor>
30#include <QPen>
31
32#include "KDChartGlobal"
33#include "KDChartPalette"
34
35
36namespace KPlato
37{
38
39ChartItemModel::ChartItemModel( QObject *parent )
40 : ItemModelBase( parent ),
41 m_localizeValues( false )
42{
43}
44
45QModelIndex ChartItemModel::parent( const QModelIndex &index ) const
46{
47 Q_UNUSED(index);
48 return QModelIndex();
49}
50
51const QMetaEnum ChartItemModel::columnMap() const
52{
53 return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties") );
54}
55
56int ChartItemModel::columnCount( const QModelIndex &/*parent*/ ) const
57{
58 return columnMap().keyCount();
59}
60
61int ChartItemModel::rowCount( const QModelIndex &/*parent */) const
62{
63 return startDate().daysTo( endDate() ) + 1;
64}
65
66QModelIndex ChartItemModel::index( int row, int column, const QModelIndex &parent ) const
67{
68 if ( m_project == 0 || row < 0 || column < 0 ) {
69 //kDebug(planDbg())<<"No project"<<m_project<<" or illegal row, column"<<row<<column;
70 return QModelIndex();
71 }
72 if ( parent.isValid() ) {
73 return QModelIndex();
74 }
75 return createIndex( row, column );
76}
77
78double ChartItemModel::bcwsEffort( int day ) const
79{
80 return m_bcws.hoursTo( startDate().addDays( day ) );
81}
82
83double ChartItemModel::bcwpEffort( int day ) const
84{
85 double res = 0.0;
86 QDate date = startDate().addDays( day );
87 if ( m_bcws.days().contains( date ) ) {
88 res = m_bcws.bcwpEffort( date );
89 } else if ( date > m_bcws.endDate() ) {
90 res = m_bcws.bcwpEffort( date );
91 }
92 return res;
93}
94
95double ChartItemModel::acwpEffort( int day ) const
96{
97 return m_acwp.hoursTo( startDate().addDays( day ) );
98}
99
100double ChartItemModel::bcwsCost( int day ) const
101{
102 return m_bcws.costTo( startDate().addDays( day ) );
103}
104
105double ChartItemModel::bcwpCost( int day ) const
106{
107 double res = 0.0;
108 QDate date = startDate().addDays( day );
109 if ( m_bcws.days().contains( date ) ) {
110 res = m_bcws.bcwpCost( date );
111 } else if ( date > m_bcws.endDate() ) {
112 res = m_bcws.bcwpCost( m_bcws.endDate() );
113 }
114 return res;
115}
116
117double ChartItemModel::acwpCost( int day ) const
118{
119 return m_acwp.costTo( startDate().addDays( day ) );
120}
121
122double ChartItemModel::spiEffort( int day ) const
123{
124 double p = bcwpEffort( day );
125 double s = bcwsEffort( day );
126 return s == 0.0 ? 0.0 : p / s;
127}
128
129double ChartItemModel::spiCost( int day ) const
130{
131 double p = bcwpCost( day );
132 double s = bcwsCost( day );
133 return s == 0.0 ? 0.0 : p / s;
134}
135
136double ChartItemModel::cpiEffort( int day ) const
137{
138 double p = bcwpEffort( day );
139 double a = acwpEffort( day );
140 return a == 0.0 ? 0.0 : p / a;
141}
142
143double ChartItemModel::cpiCost( int day ) const
144{
145 double p = bcwpCost( day );
146 double a = acwpCost( day );
147 return a == 0.0 ? 0.0 : p / a;
148}
149
150QVariant ChartItemModel::data( const QModelIndex &index, int role ) const
151{
152 QVariant result;
153 if ( role == Qt::DisplayRole ) {
154 if ( ! m_localizeValues ) {
155 return data( index, Qt::EditRole );
156 } else {
157 KLocale *l = project() ? project()->locale() : KGlobal::locale();
158 switch ( index.column() ) {
159 case BCWSCost: result = l->formatMoney( bcwsCost( index.row() ), 0 ); break;
160 case BCWPCost: result = l->formatMoney( bcwpCost( index.row() ), 0 ); break;
161 case ACWPCost: result = l->formatMoney( acwpCost( index.row() ), 0 ); break;
162 case BCWSEffort: result = l->formatNumber( bcwsEffort( index.row() ), 0 ); break;
163 case BCWPEffort: result = l->formatNumber( bcwpEffort( index.row() ), 0 ); break;
164 case ACWPEffort: result = l->formatNumber( acwpEffort( index.row() ), 0 ); break;
165 case SPICost: result = l->formatNumber( spiCost( index.row() ), 2 ); break;
166 case CPICost: result = l->formatNumber( cpiCost( index.row() ), 2 ); break;
167 case SPIEffort: result = l->formatNumber( spiEffort( index.row() ), 2 ); break;
168 case CPIEffort: result = l->formatNumber( cpiEffort( index.row() ), 2 ); break;
169 default: break;
170 }
171 }
172 //kDebug(planDbg())<<index<<role<<result;
173 return result;
174 } else if ( role == Qt::EditRole ) {
175 switch ( index.column() ) {
176 case BCWSCost: result = bcwsCost( index.row() ); break;
177 case BCWPCost: result = bcwpCost( index.row() ); break;
178 case ACWPCost: result = acwpCost( index.row() ); break;
179 case BCWSEffort: result = bcwsEffort( index.row() ); break;
180 case BCWPEffort: result = bcwpEffort( index.row() ); break;
181 case ACWPEffort: result = acwpEffort( index.row() ); break;
182 case SPICost: result = spiCost( index.row() ); break;
183 case CPICost: result = cpiCost( index.row() ); break;
184 case SPIEffort: result = spiEffort( index.row() ); break;
185 case CPIEffort: result = cpiEffort( index.row() ); break;
186 default: break;
187 }
188 //kDebug(planDbg())<<index<<role<<result;
189 return result;
190 } else if ( role == Qt::ForegroundRole ) {
191 double v = 0.0;
192 switch ( index.column() ) {
193 case SPICost: v = spiCost( index.row() ); break;
194 case CPICost: v = cpiCost( index.row() ); break;
195 case SPIEffort: v = spiEffort( index.row() ); break;
196 case CPIEffort: v = cpiEffort( index.row() ); break;
197 default: break;
198 }
199 if ( v > 0.0 && v < 1.0 ) {
200 result = QBrush( Qt::red );
201 }
202 return result;
203 } else if ( role == KDChart::DatasetBrushRole ) {
204 return headerData( index.column(), Qt::Horizontal, role );
205 } else if ( role == KDChart::DatasetPenRole ) {
206 return headerData( index.column(), Qt::Horizontal, role );
207 }
208 //kDebug(planDbg())<<index<<role<<result;
209 return result;
210}
211
212QVariant ChartItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
213{
214 KLocale *locale = project() ? project()->locale() : KGlobal::locale();
215 QVariant result;
216 if ( role == Qt::DisplayRole ) {
217 if ( orientation == Qt::Horizontal ) {
218 switch ( section ) {
219 case BCWSCost: return i18nc( "Cost based Budgeted Cost of Work Scheduled", "BCWS Cost" );
220 case BCWPCost: return i18nc( "Cost based Budgeted Cost of Work Performed", "BCWP Cost" );
221 case ACWPCost: return i18nc( "Cost based Actual Cost of Work Performed", "ACWP Cost" );
222 case BCWSEffort: return i18nc( "Effort based Budgeted Cost of Work Scheduled", "BCWS Effort" );
223 case BCWPEffort: return i18nc( "Effort based Budgeted Cost of Work Performed", "BCWP Effort" );
224 case ACWPEffort: return i18nc( "Effort based Actual Cost of Work Performed", "ACWP Effort" );
225 case SPICost: return i18nc( "Cost based Schedule Performance Index", "SPI Cost" );
226 case CPICost: return i18nc( "Cost based Cost Performance Index", "CPI Cost" );
227 case SPIEffort: return i18nc( "Effort based Schedule Performance Index", "SPI Effort" );
228 case CPIEffort: return i18nc( "Effort based Cost Performance Index", "CPI Effort" );
229 default: return QVariant();
230 }
231 } else {
232 return startDate().addDays( section ).toString( i18nc( "Date format used as chart axis labels. Must follow QDate specification.", "MM.dd" ) );
233 }
234 } else if ( role == Qt::ToolTipRole ) {
235 if ( orientation == Qt::Horizontal ) {
236 switch ( section ) {
237 case BCWSCost: return i18nc( "@info:tooltip", "Cost based Budgeted Cost of Work Scheduled" );
238 case BCWPCost: return i18nc( "@info:tooltip", "Cost based Budgeted Cost of Work Performed" );
239 case ACWPCost: return i18nc( "@info:tooltip", "Cost based Actual Cost of Work Performed" );
240 case BCWSEffort: return i18nc( "@info:tooltip", "Effort based Budgeted Cost of Work Scheduled" );
241 case BCWPEffort: return i18nc( "@info:tooltip", "Effort based Budgeted Cost of Work Performed" );
242 case ACWPEffort: return i18nc( "@info:tooltip", "Effort based Actual Cost of Work Performed" );
243 case SPICost: return i18nc( "@info:tooltip", "Cost based Schedule Performance Index (BCWP/BCWS)" );
244 case CPICost: return i18nc( "@info:tooltip", "Cost based Cost Performance Index (BCWP/ACWS)" );
245 case SPIEffort: return i18nc( "@info:tooltip", "Effort based Schedule Performance Index (BCWP/BCWS)" );
246 case CPIEffort: return i18nc( "@info:tooltip", "Effort based Cost Performance Index (BCWP/ACWS)" );
247 default: return QVariant();
248 }
249 } else {
250 return locale->formatDate( startDate().addDays( section ) );
251 }
252 } else if ( role == Qt::EditRole ) {
253 if ( orientation == Qt::Horizontal ) {
254 switch ( section ) {
255 case BCWSCost: return "BCWS Cost";
256 case BCWPCost: return "BCWP Cost";
257 case ACWPCost: return "ACWP Cost";
258 case BCWSEffort: return "BCWS Effort";
259 case BCWPEffort: return "BCWP Effort";
260 case ACWPEffort: return "ACWP Effort";
261 case SPICost: return "SPI Cost";
262 case CPICost: return "CPI Cost";
263 case SPIEffort: return "SPI Effort";
264 case CPIEffort: return "CPI Effort";
265 default: return QVariant();
266 }
267 } else {
268 return startDate().addDays( section );
269 }
270#ifdef PLAN_CHART_DEBUG
271 } else if ( role == Qt::BackgroundRole ) {
272 if ( orientation == Qt::Vertical ) {
273 if ( startDate().addDays( section ) == QDate::currentDate() ) {
274 return QBrush( Qt::red );
275 }
276 }
277#endif
278 } else if ( role == KDChart::DatasetBrushRole ) {
279 if ( orientation == Qt::Horizontal ) {
280 return KDChart::Palette::defaultPalette().getBrush( section );
281 }
282 } else if ( role == KDChart::DatasetPenRole ) {
283 QPen p;
284 p.setBrush( headerData( section, orientation, KDChart::DatasetBrushRole ).value<QBrush>() );
285 result = p;
286 //kDebug(planDbg())<<section<<"DatasetPenRole"<<result;
287 return result;
288 }
289 return ItemModelBase::headerData(section, orientation, role);
290}
291
292void ChartItemModel::setProject( Project *project )
293{
294 m_bcws.clear();
295 m_acwp.clear();
296 if ( m_project ) {
297 disconnect( m_project, SIGNAL(projectCalculated(ScheduleManager*)), this, SLOT(setScheduleManager(ScheduleManager*)) );
298 disconnect( m_project, SIGNAL(nodeRemoved(Node*)), this, SLOT(slotNodeRemoved(Node*)) );
299 disconnect( m_project, SIGNAL(nodeChanged(Node*)), this, SLOT(slotNodeChanged(Node*)) );
300 disconnect( m_project, SIGNAL(resourceRemoved(const Resource*)), this, SLOT(slotResourceChanged(const Resource*)) );
301 disconnect( m_project, SIGNAL(resourceChanged(Resource*)), this, SLOT(slotResourceChanged(Resource*)) );
302 }
303 m_project = project;
304 if ( m_project ) {
305 connect( m_project, SIGNAL(projectCalculated(ScheduleManager*)), this, SLOT(setScheduleManager(ScheduleManager*)) );
306 connect( m_project, SIGNAL(nodeRemoved(Node*)), this, SLOT(slotNodeRemoved(Node*)) );
307 connect( m_project, SIGNAL(nodeChanged(Node*)), this, SLOT(slotNodeChanged(Node*)) );
308 connect( m_project, SIGNAL(resourceRemoved(const Resource*)), this, SLOT(slotResourceChanged(const Resource*)) );
309 connect( m_project, SIGNAL(resourceChanged(Resource*)), this, SLOT(slotResourceChanged(Resource*)) );
310 }
311 reset();
312}
313
314void ChartItemModel::setScheduleManager( ScheduleManager *sm )
315{
316 m_manager = sm;
317 calculate();
318 reset();
319}
320
321void ChartItemModel::setNodes( const QList<Node*> &nodes )
322{
323 kDebug(planDbg())<<nodes;
324 m_nodes = nodes;
325 calculate();
326 reset();
327}
328
329void ChartItemModel::addNode( Node *node )
330{
331 m_nodes.append( node );
332 calculate();
333 reset();
334}
335
336void ChartItemModel::clearNodes()
337{
338 m_nodes.clear();
339 calculate();
340 reset();
341}
342
343void ChartItemModel::slotNodeRemoved( Node *node )
344{
345 if ( m_nodes.contains( node ) ) {
346 m_nodes.removeAt( m_nodes.indexOf( node ) );
347 calculate();
348 reset();
349 return;
350 }
351}
352
353void ChartItemModel::slotNodeChanged( Node *node )
354{
355 //kDebug(planDbg())<<this<<node;
356 if ( m_nodes.contains( node ) ) {
357 calculate();
358 reset();
359 return;
360 }
361 foreach ( Node *n, m_nodes ) {
362 if ( node->isChildOf( n ) ) {
363 calculate();
364 reset();
365 return;
366 }
367 }
368}
369
370void ChartItemModel::slotResourceChanged( Resource* )
371{
372 calculate();
373 reset();
374}
375
376void ChartItemModel::slotResourceChanged( const Resource* )
377{
378 calculate();
379 reset();
380}
381
382QDate ChartItemModel::startDate() const
383{
384 QDate d = m_bcws.startDate();
385 if ( m_acwp.startDate().isValid() ) {
386 if ( ! d.isValid() || d > m_acwp.startDate() ) {
387 d = m_acwp.startDate();
388 }
389 }
390 return d;
391}
392
393QDate ChartItemModel::endDate() const
394{
395 return qMax( m_bcws.endDate(), m_acwp.endDate() );
396}
397
398void ChartItemModel::calculate()
399{
400 //kDebug(planDbg())<<m_project<<m_manager<<m_nodes;
401 m_bcws.clear();
402 m_acwp.clear();
403 if ( m_manager ) {
404 if ( m_project ) {
405 foreach ( Node *n, m_nodes ) {
406 bool skip = false;
407 foreach ( Node *p, m_nodes ) {
408 if ( n->isChildOf( p ) ) {
409 skip = true;
410 break;
411 }
412 }
413 if ( ! skip ) {
414 m_bcws += n->bcwpPrDay( m_manager->scheduleId(), ECCT_EffortWork );
415 m_acwp += n->acwp( m_manager->scheduleId() );
416 }
417 }
418 }
419 }
420 //kDebug(planDbg())<<"bcwp"<<m_bcws;
421 //kDebug(planDbg())<<"acwp"<<m_acwp;
422}
423
424void ChartItemModel::setLocalizeValues( bool on )
425{
426 m_localizeValues = on;
427}
428
429//-------------------------
430PerformanceDataCurrentDateModel::PerformanceDataCurrentDateModel( QObject *parent )
431 : ChartItemModel( parent )
432{
433 setLocalizeValues( true );
434}
435
436
437int PerformanceDataCurrentDateModel::rowCount( const QModelIndex &parent ) const
438{
439 if ( parent.isValid() ) {
440 return 0;
441 }
442 return 2;
443}
444
445int PerformanceDataCurrentDateModel::columnCount( const QModelIndex &/*parent*/ ) const
446{
447 return 5;
448}
449
450QModelIndex PerformanceDataCurrentDateModel::index( int row, int column, const QModelIndex &parent ) const
451{
452 if ( parent.isValid() ) {
453 return QModelIndex();
454 }
455 return createIndex( row, column );
456}
457
458QVariant PerformanceDataCurrentDateModel::data(const QModelIndex &idx, int role) const
459{
460 return ChartItemModel::data( mapIndex( idx ), role );
461}
462
463QVariant PerformanceDataCurrentDateModel::headerData( int section, Qt::Orientation o, int role ) const
464{
465 if ( role == Qt::DisplayRole ) {
466 if ( o == Qt::Horizontal ) {
467 switch ( section ) {
468 case 0: return i18nc( "@title:column Budgeted Cost of Work Scheduled", "BCWS" );
469 case 1: return i18nc( "@title:column Budgeted Cost of Work Performed", "BCWP" );
470 case 2: return i18nc( "@title:column Actual Cost of Work Performed", "ACWP" );
471 case 3: return i18nc( "@title:column Schedule Performance Index", "SPI" );
472 case 4: return i18nc( "@title:column Cost Performance Index", "CPI" );
473 default: break;
474 }
475 } else {
476 switch ( section ) {
477 case 0: return i18nc( "@title:column", "Cost:" );
478 case 1: return i18nc( "@title:column", "Effort:" );
479 default: break;
480 }
481 }
482 } else if ( role == Qt::ToolTipRole ) {
483 if ( o == Qt::Horizontal ) {
484 switch ( section ) {
485 case 0: return i18nc( "@info:tooltip", "Budgeted Cost of Work Scheduled" );
486 case 1: return i18nc( "@info:tooltip", "Budgeted Cost of Work Performed" );
487 case 2: return i18nc( "@info:tooltip", "Actual Cost of Work Performed" );
488 case 3: return i18nc( "@info:tooltip", "Schedule Performance Index" );
489 case 4: return i18nc( "@info:tooltip", "Cost Performance Index" );
490 default: break;
491 }
492 } else {
493 switch ( section ) {
494 case 0: return i18nc( "@info:tooltip", "Performance indicators based on cost" );
495 case 1: return i18nc( "@info:tooltip", "Performance indicators based on effort" );
496 default: break;
497 }
498 }
499 }
500 return QVariant();
501}
502
503QModelIndex PerformanceDataCurrentDateModel::mapIndex( const QModelIndex &idx ) const
504{
505 if ( ! startDate().isValid() ) {
506 return QModelIndex();
507 }
508 int row = startDate().daysTo( QDate::currentDate() );
509 if ( row < 0 ) {
510 return QModelIndex();
511 }
512 int column = -1;
513 switch ( idx.column() ) {
514 case 0: column = idx.row() == 0 ? BCWSCost : BCWSEffort; break; // BCWS
515 case 1: column = idx.row() == 0 ? BCWPCost : BCWPEffort; break; // BCWP
516 case 2: column = idx.row() == 0 ? ACWPCost : ACWPEffort; break; // ACWP
517 case 3: column = idx.row() == 0 ? SPICost : SPIEffort; break; // SPI
518 case 4: column = idx.row() == 0 ? CPICost : CPIEffort; break; // CPI
519 default: break;
520 }
521 if ( column < 0 ) {
522 return QModelIndex();
523 }
524 return ChartItemModel::index( row, column );
525}
526
527} //namespace KPlato
528
529#include "kptnodechartmodel.moc"
530