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 | |
36 | namespace KPlato |
37 | { |
38 | |
39 | ChartItemModel::ChartItemModel( QObject *parent ) |
40 | : ItemModelBase( parent ), |
41 | m_localizeValues( false ) |
42 | { |
43 | } |
44 | |
45 | QModelIndex ChartItemModel::parent( const QModelIndex &index ) const |
46 | { |
47 | Q_UNUSED(index); |
48 | return QModelIndex(); |
49 | } |
50 | |
51 | const QMetaEnum ChartItemModel::columnMap() const |
52 | { |
53 | return metaObject()->enumerator( metaObject()->indexOfEnumerator("Properties" ) ); |
54 | } |
55 | |
56 | int ChartItemModel::columnCount( const QModelIndex &/*parent*/ ) const |
57 | { |
58 | return columnMap().keyCount(); |
59 | } |
60 | |
61 | int ChartItemModel::rowCount( const QModelIndex &/*parent */) const |
62 | { |
63 | return startDate().daysTo( endDate() ) + 1; |
64 | } |
65 | |
66 | QModelIndex 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 | |
78 | double ChartItemModel::bcwsEffort( int day ) const |
79 | { |
80 | return m_bcws.hoursTo( startDate().addDays( day ) ); |
81 | } |
82 | |
83 | double 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 | |
95 | double ChartItemModel::acwpEffort( int day ) const |
96 | { |
97 | return m_acwp.hoursTo( startDate().addDays( day ) ); |
98 | } |
99 | |
100 | double ChartItemModel::bcwsCost( int day ) const |
101 | { |
102 | return m_bcws.costTo( startDate().addDays( day ) ); |
103 | } |
104 | |
105 | double 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 | |
117 | double ChartItemModel::acwpCost( int day ) const |
118 | { |
119 | return m_acwp.costTo( startDate().addDays( day ) ); |
120 | } |
121 | |
122 | double 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 | |
129 | double 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 | |
136 | double 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 | |
143 | double 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 | |
150 | QVariant 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 | |
212 | QVariant ChartItemModel::( 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 | |
292 | void 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 | |
314 | void ChartItemModel::setScheduleManager( ScheduleManager *sm ) |
315 | { |
316 | m_manager = sm; |
317 | calculate(); |
318 | reset(); |
319 | } |
320 | |
321 | void ChartItemModel::setNodes( const QList<Node*> &nodes ) |
322 | { |
323 | kDebug(planDbg())<<nodes; |
324 | m_nodes = nodes; |
325 | calculate(); |
326 | reset(); |
327 | } |
328 | |
329 | void ChartItemModel::addNode( Node *node ) |
330 | { |
331 | m_nodes.append( node ); |
332 | calculate(); |
333 | reset(); |
334 | } |
335 | |
336 | void ChartItemModel::clearNodes() |
337 | { |
338 | m_nodes.clear(); |
339 | calculate(); |
340 | reset(); |
341 | } |
342 | |
343 | void 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 | |
353 | void 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 | |
370 | void ChartItemModel::slotResourceChanged( Resource* ) |
371 | { |
372 | calculate(); |
373 | reset(); |
374 | } |
375 | |
376 | void ChartItemModel::slotResourceChanged( const Resource* ) |
377 | { |
378 | calculate(); |
379 | reset(); |
380 | } |
381 | |
382 | QDate 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 | |
393 | QDate ChartItemModel::endDate() const |
394 | { |
395 | return qMax( m_bcws.endDate(), m_acwp.endDate() ); |
396 | } |
397 | |
398 | void 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 | |
424 | void ChartItemModel::setLocalizeValues( bool on ) |
425 | { |
426 | m_localizeValues = on; |
427 | } |
428 | |
429 | //------------------------- |
430 | PerformanceDataCurrentDateModel::PerformanceDataCurrentDateModel( QObject *parent ) |
431 | : ChartItemModel( parent ) |
432 | { |
433 | setLocalizeValues( true ); |
434 | } |
435 | |
436 | |
437 | int PerformanceDataCurrentDateModel::rowCount( const QModelIndex &parent ) const |
438 | { |
439 | if ( parent.isValid() ) { |
440 | return 0; |
441 | } |
442 | return 2; |
443 | } |
444 | |
445 | int PerformanceDataCurrentDateModel::columnCount( const QModelIndex &/*parent*/ ) const |
446 | { |
447 | return 5; |
448 | } |
449 | |
450 | QModelIndex 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 | |
458 | QVariant PerformanceDataCurrentDateModel::data(const QModelIndex &idx, int role) const |
459 | { |
460 | return ChartItemModel::data( mapIndex( idx ), role ); |
461 | } |
462 | |
463 | QVariant PerformanceDataCurrentDateModel::( 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 | |
503 | QModelIndex 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 | |