1 /* This file is part of the KDE project
2    Copyright (C) 2017 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 // clazy:excludeall=qstring-arg
21 #include "TasksGeneralPanel.h"
22 #include "kpttaskdialog.h"
23 #include "kpttask.h"
24 #include "kptcommand.h"
25 #include "kptduration.h"
26 #include "kptdurationspinbox.h"
27 #include "kptcalendar.h"
28 #include "kptdatetime.h"
29 #include "kptproject.h"
30 
31 #include <KLocalizedString>
32 
33 #ifdef PLAN_KDEPIMLIBS_FOUND
34 #include <akonadi/contact/emailaddressselectiondialog.h>
35 #include <akonadi/contact/emailaddressselectionwidget.h>
36 #include <akonadi/contact/emailaddressselection.h>
37 #endif
38 
39 #include <QDateTime>
40 
41 #include <kptdebug.h>
42 
43 namespace KPlato
44 {
45 
TasksGeneralPanel(Project & project,QList<Task * > & tasks,QWidget * p,const char * n)46 TasksGeneralPanel::TasksGeneralPanel(Project &project, QList<Task*> &tasks, QWidget *p, const char *n)
47     : TasksGeneralPanelImpl(p, n),
48       m_tasks(tasks),
49       m_project(project)
50 {
51     Q_ASSERT(!tasks.isEmpty());
52 
53     useTime = true;
54     setStartValues(tasks.at(0));
55 
56 //     if (task.isBaselined(BASELINESCHEDULE)) {
57 //         schedulingGroup->setEnabled(false);
58 //     }
59 
60 }
61 
setStartValues(Task * task)62 void TasksGeneralPanel::setStartValues(Task *task) {
63     m_estimate = m_duration = task->estimate()->expectedValue();
64     leaderfield->setText(task->leader());
65 
66     int cal = 0;
67     m_calendars.clear();
68     calendarCombo->addItem(i18n("None"));
69     m_calendars.insert(0, 0);
70     QList<Calendar*> list = m_project.allCalendars();
71     int i=1;
72     foreach (Calendar *c, list) {
73         calendarCombo->insertItem(i, c->name());
74         m_calendars.insert(i, c);
75         if (c == task->estimate()->calendar()) {
76             cal = i;
77         }
78         ++i;
79     }
80     calendarCombo->setCurrentIndex(cal);
81 
82     estimate->setMinimumUnit((Duration::Unit)(m_project.config().minimumDurationUnit()));
83     estimate->setMaximumUnit((Duration::Unit)(m_project.config().maximumDurationUnit()));
84     estimate->setUnit(task->estimate()->unit());
85     setEstimateType(task->estimate()->type());
86     if (task->estimate()->type() == Estimate::Type_Effort && task->estimate()->expectedEstimate() == 0.0) {
87         setEstimateType(2 /*Milestone*/);
88     }
89     setSchedulingType(task->constraint());
90     if (task->constraintStartTime().isValid()) {
91         setStartDateTime(task->constraintStartTime());
92     } else {
93         QDate date = QDate::currentDate();
94         setStartDateTime(QDateTime(date, QTime(), Qt::LocalTime));
95     }
96     if (task->constraintEndTime().isValid()) {
97         setEndDateTime(task->constraintEndTime());
98     } else {
99         setEndDateTime(QDateTime(startDate().addDays(1), QTime(), Qt::LocalTime));
100     }
101     //debugPlan<<"Estimate:"<<task->estimate()->expected().toString();
102     setEstimate(task->estimate()->expectedEstimate());
103     setOptimistic(task->estimate()->optimisticRatio());
104     setPessimistic(task->estimate()->pessimisticRatio());
105     setRisktype(task->estimate()->risktype());
106 
107     namefield->setFocus();
108 }
109 
buildCommand()110 MacroCommand *TasksGeneralPanel::buildCommand() {
111     MacroCommand *cmd = new MacroCommand(kundo2_i18n("Modify Tasks"));
112     bool modified = false;
113 
114     for (Task *t : m_tasks) {
115         if (!leaderfield->isHidden() && t->leader() != leaderfield->text()) {
116             cmd->addCommand(new NodeModifyLeaderCmd(*t, leaderfield->text()));
117             modified = true;
118         }
119         Node::ConstraintType c = (Node::ConstraintType)schedulingType();
120         if (c != t->constraint()) {
121             cmd->addCommand(new NodeModifyConstraintCmd(*t, c));
122             modified = true;
123         }
124         if (startDateTime() != t->constraintStartTime() &&
125             (c == Node::FixedInterval || c == Node::StartNotEarlier || c == Node::MustStartOn)) {
126             cmd->addCommand(new NodeModifyConstraintStartTimeCmd(*t, startDateTime()));
127             modified = true;
128         }
129         if (endDateTime() != t->constraintEndTime() &&
130             (c == Node::FinishNotLater || c == Node::FixedInterval || c == Node::MustFinishOn)) {
131             cmd->addCommand(new NodeModifyConstraintEndTimeCmd(*t, endDateTime()));
132             modified = true;
133         }
134         int et = estimationType();
135         if (et == 2 /*Milestome*/) {
136             et = 0; /*Effort*/
137         }
138         if (et != t->estimate()->type()) {
139             cmd->addCommand(new ModifyEstimateTypeCmd(*t,  t->estimate()->type(), et));
140             modified = true;
141         }
142         bool unitchanged = estimate->unit() != t->estimate()->unit();
143         if (unitchanged) {
144             cmd->addCommand(new ModifyEstimateUnitCmd(*t, t->estimate()->unit(), estimate->unit()));
145             modified = true;
146         }
147         bool expchanged = estimationValue() != t->estimate()->expectedEstimate();
148         if (expchanged) {
149             cmd->addCommand(new ModifyEstimateCmd(*t, t->estimate()->expectedEstimate(), estimationValue()));
150             modified = true;
151         }
152         int x = optimistic();
153         if (x != t->estimate()->optimisticRatio() || expchanged || unitchanged) {
154             cmd->addCommand(new EstimateModifyOptimisticRatioCmd(*t, t->estimate()->optimisticRatio(), x));
155             modified = true;
156         }
157         x = pessimistic();
158         if (x != t->estimate()->pessimisticRatio() || expchanged || unitchanged) {
159             cmd->addCommand(new EstimateModifyPessimisticRatioCmd(*t, t->estimate()->pessimisticRatio(), x));
160             modified = true;
161         }
162         if (t->estimate()->risktype() != risktype()) {
163             cmd->addCommand(new EstimateModifyRiskCmd(*t, t->estimate()->risktype(), risktype()));
164             modified = true;
165         }
166         if (t->estimate()->calendar() != calendar()) {
167             cmd->addCommand(new ModifyEstimateCalendarCmd(*t, t->estimate()->calendar(), calendar()));
168             modified = true;
169         }
170     }
171     if (!modified) {
172         delete cmd;
173         return 0;
174     }
175     return cmd;
176 }
177 
ok()178 bool TasksGeneralPanel::ok() {
179     return true;
180 }
181 
estimationTypeChanged(int type)182 void TasksGeneralPanel::estimationTypeChanged(int type) {
183     if (type == 0 /*Effort*/) {
184         estimate->setEnabled(true);
185         calendarCombo->setEnabled(false);
186     } else if (type == 1 /*Duration*/) {
187         calendarCombo->setEnabled(false);
188         if (schedulingType() == 6) { /*Fixed interval*/
189             estimate->setEnabled(false);
190         } else {
191             estimate->setEnabled(true);
192             calendarCombo->setEnabled(true);
193         }
194     } else if (type == 2 /* Milestone */) {
195         estimate->setValue(0);
196         estimate->setEnabled(false);
197         calendarCombo->setEnabled(false);
198     }
199     TasksGeneralPanelImpl::estimationTypeChanged(type);
200 }
201 
scheduleTypeChanged(int value)202 void TasksGeneralPanel::scheduleTypeChanged(int value)
203 {
204     if (value == 6 /*Fixed interval*/) {
205         if (estimateType->currentIndex() == 1/*duration*/){
206 //            setEstimateScales(24);
207             estimate->setEnabled(false);
208 //TODO            setEstimate(DateTime(endDateTime(), KDateTime::UTC) - DateTime(startDateTime(), KDateTime::UTC));
209         }
210     } else {
211         estimate->setEnabled(true);
212     }
213     TasksGeneralPanelImpl::scheduleTypeChanged(value);
214 }
215 
216 //-----------------------------
TasksGeneralPanelImpl(QWidget * p,const char * n)217 TasksGeneralPanelImpl::TasksGeneralPanelImpl(QWidget *p, const char *n)
218     : QWidget(p) {
219 
220     setObjectName(n);
221     setupUi(this);
222 
223 #ifndef PLAN_KDEPIMLIBS_FOUND
224     chooseLeader->hide();
225 #endif
226 
227     // FIXME
228     // [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 addressbook
229     chooseLeader->hide();
230 
231     connect(namefield, SIGNAL(textChanged(QString)), SLOT(checkAllFieldsFilled()));
232     connect(leaderfield, SIGNAL(textChanged(QString)), SLOT(checkAllFieldsFilled()));
233     connect(chooseLeader, SIGNAL(clicked()), SLOT(changeLeader()));
234     connect(estimateType, SIGNAL(activated(int)), SLOT(estimationTypeChanged(int)));
235     connect(scheduleType, SIGNAL(activated(int)), SLOT(scheduleTypeChanged(int)));
236     connect(scheduleStartDate, SIGNAL(dateChanged(QDate)), SLOT(startDateChanged()));
237     connect(scheduleStartTime, SIGNAL(timeChanged(QTime)), SLOT(startTimeChanged(QTime)));
238     connect(scheduleEndDate, SIGNAL(dateChanged(QDate)), SLOT(endDateChanged()));
239     connect(scheduleEndTime, SIGNAL(timeChanged(QTime)), SLOT(endTimeChanged(QTime)));
240     connect(estimate, SIGNAL(valueChanged(double)), SLOT(checkAllFieldsFilled()));
241     connect(optimisticValue, SIGNAL(valueChanged(int)), SLOT(checkAllFieldsFilled()));
242     connect(pessimisticValue, SIGNAL(valueChanged(int)), SLOT(checkAllFieldsFilled()));
243     connect(risk, SIGNAL(activated(int)), SLOT(checkAllFieldsFilled()));
244     connect(calendarCombo, SIGNAL(activated(int)), SLOT(calendarChanged(int)));
245 
246 }
247 
setSchedulingType(int type)248 void TasksGeneralPanelImpl::setSchedulingType(int type)
249 {
250     enableDateTime(type);
251     scheduleType->setCurrentIndex(type);
252     emit schedulingTypeChanged(type);
253 }
254 
schedulingType() const255 int TasksGeneralPanelImpl::schedulingType() const
256 {
257     return scheduleType->currentIndex();
258 }
259 
changeLeader()260 void TasksGeneralPanelImpl::changeLeader()
261 {
262 #ifdef PLAN_KDEPIMLIBS_FOUND
263     QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog(this);
264     if (dlg->exec() && dlg) {
265         QStringList names;
266         const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
267         foreach (const Akonadi::EmailAddressSelection &selection, selections) {
268             QString s = selection.name();
269             if (! selection.email().isEmpty()) {
270                 if (! selection.name().isEmpty()) {
271                     s += " <";
272                 }
273                 s += selection.email();
274                 if (! selection.name().isEmpty()) {
275                     s += '>';
276                 }
277                 if (! s.isEmpty()) {
278                     names << s;
279                 }
280             }
281         }
282         if (! names.isEmpty()) {
283             leaderfield->setText(names.join(", "));
284         }
285     }
286 #endif
287 }
288 
setEstimationType(int type)289 void TasksGeneralPanelImpl::setEstimationType(int type)
290 {
291     estimateType->setCurrentIndex(type);
292 }
293 
estimationType() const294 int TasksGeneralPanelImpl::estimationType() const
295 {
296     return estimateType->currentIndex();
297 }
298 
setOptimistic(int value)299 void TasksGeneralPanelImpl::setOptimistic(int value)
300 {
301     optimisticValue->setValue(value);
302 }
303 
setPessimistic(int value)304 void TasksGeneralPanelImpl::setPessimistic(int value)
305 {
306     pessimisticValue->setValue(value);
307 }
308 
optimistic() const309 int TasksGeneralPanelImpl::optimistic() const
310 {
311     return optimisticValue->value();
312 }
313 
pessimistic()314 int TasksGeneralPanelImpl::pessimistic()
315 {
316     return pessimisticValue->value();
317 }
318 
enableDateTime(int scheduleType)319 void TasksGeneralPanelImpl::enableDateTime(int scheduleType)
320 {
321     scheduleStartTime->setEnabled(false);
322     scheduleEndTime->setEnabled(false);
323     scheduleStartDate->setEnabled(false);
324     scheduleEndDate->setEnabled(false);
325     switch (scheduleType)
326     {
327     case 0: //ASAP
328     case 1: //ALAP
329         break;
330     case 2: //Must start on
331     case 4: // Start not earlier
332         if (useTime) {
333             scheduleStartTime->setEnabled(true);
334             scheduleEndTime->setEnabled(false);
335         }
336         scheduleStartDate->setEnabled(true);
337         scheduleEndDate->setEnabled(false);
338         break;
339     case 3: //Must finish on
340     case 5: // Finish not later
341         if (useTime) {
342             scheduleStartTime->setEnabled(false);
343             scheduleEndTime->setEnabled(true);
344         }
345         scheduleStartDate->setEnabled(false);
346         scheduleEndDate->setEnabled(true);
347         break;
348     case 6: //Fixed interval
349         if (useTime) {
350             scheduleStartTime->setEnabled(true);
351             scheduleEndTime->setEnabled(true);
352         }
353         scheduleStartDate->setEnabled(true);
354         scheduleEndDate->setEnabled(true);
355         break;
356     default:
357         break;
358     }
359 }
360 
361 
estimationTypeChanged(int)362 void TasksGeneralPanelImpl::estimationTypeChanged(int /*type*/)
363 {
364     checkAllFieldsFilled();
365 }
366 
calendarChanged(int)367 void TasksGeneralPanelImpl::calendarChanged(int /*index*/)
368 {
369     checkAllFieldsFilled();
370 }
371 
setEstimate(double duration)372 void TasksGeneralPanelImpl::setEstimate(double duration)
373 {
374     estimate->setValue(duration);
375 }
376 
377 
setEstimateType(int type)378 void TasksGeneralPanelImpl::setEstimateType(int type)
379 {
380     estimateType->setCurrentIndex(type);
381     estimationTypeChanged(type);
382 }
383 
384 
checkAllFieldsFilled()385 void TasksGeneralPanelImpl::checkAllFieldsFilled()
386 {
387     emit changed();
388     emit obligatedFieldsFilled(true); // do not block save even if name is not filled
389 }
390 
391 
estimationValue()392 double TasksGeneralPanelImpl::estimationValue()
393 {
394     return estimate->value();
395 }
396 
startDateChanged()397 void TasksGeneralPanelImpl::startDateChanged()
398 {
399     if (!scheduleStartDate->isEnabled()) {
400         return;
401     }
402     QDate date = startDate();
403     if (startDateTime() > endDateTime())
404     {
405         scheduleEndTime->blockSignals(true);
406         scheduleEndDate->blockSignals(true);
407         setEndDate(date);
408         setEndTime(startTime());
409         scheduleEndTime->blockSignals(false);
410         scheduleEndDate->blockSignals(false);
411     }
412     if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
413     {
414         estimationTypeChanged(estimateType->currentIndex());
415     }
416     checkAllFieldsFilled();
417 }
418 
startTimeChanged(const QTime & time)419 void TasksGeneralPanelImpl::startTimeChanged(const QTime &time)
420 {
421     if (!scheduleStartTime->isEnabled()) {
422         return;
423     }
424     if (startDateTime() > endDateTime())
425     {
426         scheduleEndTime->blockSignals(true);
427         setEndTime(time);
428         scheduleEndTime->blockSignals(false);
429     }
430     if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
431     {
432         estimationTypeChanged(estimateType->currentIndex());
433     }
434     checkAllFieldsFilled();
435 }
436 
437 
endDateChanged()438 void TasksGeneralPanelImpl::endDateChanged()
439 {
440     if (!scheduleEndDate->isEnabled()) {
441         return;
442     }
443     QDate date = endDate();
444     if (endDateTime() < startDateTime())
445     {
446         scheduleStartTime->blockSignals(true);
447         scheduleStartDate->blockSignals(true);
448         setStartDate(date);
449         setStartTime(endTime());
450         scheduleStartTime->blockSignals(false);
451         scheduleStartDate->blockSignals(false);
452     }
453 
454     if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
455     {
456         estimationTypeChanged(estimateType->currentIndex());
457     }
458     checkAllFieldsFilled();
459 }
460 
endTimeChanged(const QTime & time)461 void TasksGeneralPanelImpl::endTimeChanged(const QTime &time)
462 {
463     if (!scheduleEndTime->isEnabled()) {
464         return;
465     }
466     if (endDateTime() < startDateTime())
467     {
468         scheduleStartTime->blockSignals(true);
469         setStartTime(time);
470         scheduleStartTime->blockSignals(false);
471     }
472 
473     if (scheduleType->currentIndex() == 6 /*FixedInterval*/)
474     {
475         estimationTypeChanged(estimateType->currentIndex());
476     }
477     checkAllFieldsFilled();
478 }
479 
scheduleTypeChanged(int value)480 void TasksGeneralPanelImpl::scheduleTypeChanged(int value)
481 {
482      estimationTypeChanged(estimateType->currentIndex());
483      enableDateTime(value);
484      checkAllFieldsFilled();
485 }
486 
487 
startDateTime()488 QDateTime TasksGeneralPanelImpl::startDateTime()
489 {
490     return QDateTime(startDate(), startTime(), Qt::LocalTime);
491 }
492 
493 
endDateTime()494 QDateTime TasksGeneralPanelImpl::endDateTime()
495 {
496     return QDateTime(endDate(), endTime(), Qt::LocalTime);
497 }
498 
setStartTime(const QTime & time)499 void TasksGeneralPanelImpl::setStartTime(const QTime &time)
500 {
501     scheduleStartTime->setTime(QTime(time.hour(), time.minute(), 0));
502 }
503 
setEndTime(const QTime & time)504 void TasksGeneralPanelImpl::setEndTime(const QTime &time)
505 {
506     scheduleEndTime->setTime(QTime(time.hour(), time.minute(), 0));
507 }
508 
startTime() const509 QTime TasksGeneralPanelImpl::startTime() const
510 {
511     QTime t = scheduleStartTime->time();
512     t.setHMS(t.hour(), t.minute(), 0);
513     return t;
514 }
515 
endTime()516 QTime TasksGeneralPanelImpl::endTime()
517 {
518     QTime t = scheduleEndTime->time();
519     t.setHMS(t.hour(), t.minute(), 0);
520     return t;
521 }
522 
startDate()523 QDate TasksGeneralPanelImpl::startDate()
524 {
525     return scheduleStartDate->date();
526 }
527 
528 
endDate()529 QDate TasksGeneralPanelImpl::endDate()
530 {
531     return scheduleEndDate->date();
532 }
533 
setStartDateTime(const QDateTime & dt)534 void TasksGeneralPanelImpl::setStartDateTime(const QDateTime &dt)
535 {
536     setStartDate(dt.date());
537     setStartTime(dt.time());
538 }
539 
540 
setEndDateTime(const QDateTime & dt)541 void TasksGeneralPanelImpl::setEndDateTime(const QDateTime &dt)
542 {
543     setEndDate(dt.date());
544     setEndTime(dt.time());
545 }
546 
setStartDate(const QDate & date)547 void TasksGeneralPanelImpl::setStartDate(const QDate &date)
548 {
549     scheduleStartDate->setDate(date);
550 }
551 
552 
setEndDate(const QDate & date)553 void TasksGeneralPanelImpl::setEndDate(const QDate &date)
554 {
555     scheduleEndDate->setDate(date);
556 }
557 
setRisktype(int r)558 void TasksGeneralPanelImpl::setRisktype(int r)
559 {
560     risk->setCurrentIndex(r);
561 }
562 
risktype() const563 int TasksGeneralPanelImpl::risktype() const
564 {
565     return risk->currentIndex();
566 }
567 
calendar() const568 Calendar *TasksGeneralPanelImpl::calendar() const
569 {
570     return m_calendars.value(calendarCombo->currentIndex());
571 }
572 
573 }  //KPlato namespace
574