1 /* This file is part of the KDE project
2    Copyright (C) 2003 - 2011, 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 // clazy:excludeall=qstring-arg
21 #include "kptresourcedialog.h"
22 
23 #include "kptlocale.h"
24 #include "kptcommand.h"
25 #include "kptproject.h"
26 #include "kptresource.h"
27 #include "kptcalendar.h"
28 #include "kptresourcemodel.h"
29 #include "kptdebug.h"
30 
31 #include <QList>
32 #include <QStringList>
33 #include <QSortFilterProxyModel>
34 #include <QStandardItem>
35 
36 #ifdef PLAN_KDEPIMLIBS_FOUND
37 #include <akonadi/contact/emailaddressselectiondialog.h>
38 #include <akonadi/contact/emailaddressselectionwidget.h>
39 #include <akonadi/contact/emailaddressselection.h>
40 #endif
41 
42 
43 namespace KPlato
44 {
45 
ResourceDialogImpl(const Project & project,Resource & resource,bool baselined,QWidget * parent)46 ResourceDialogImpl::ResourceDialogImpl(const Project &project, Resource &resource, bool baselined, QWidget *parent)
47     : QWidget(parent),
48     m_project(project),
49     m_resource(resource)
50 {
51     setupUi(this);
52 
53 #ifndef PLAN_KDEPIMLIBS_FOUND
54     chooseBtn->hide();
55 #endif
56 
57     // FIXME
58     // [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
59     chooseBtn->hide();
60 
61     QSortFilterProxyModel *pr = new QSortFilterProxyModel(ui_teamView);
62     QStandardItemModel *m = new QStandardItemModel(ui_teamView);
63     pr->setSourceModel(new QStandardItemModel(ui_teamView));
64     ui_teamView->setModel(m);
65     m->setHorizontalHeaderLabels(QStringList() << xi18nc("title:column", "Select team members") << xi18nc("title:column", "Group"));
66     foreach (Resource *r, m_project.resourceList()) {
67         if (r->type() != Resource::Type_Work || r->id() == m_resource.id()) {
68             continue;
69         }
70         QList<QStandardItem *> items;
71         QStandardItem *item = new QStandardItem(r->name());
72         item->setCheckable(true);
73         item->setCheckState(m_resource.teamMemberIds().contains(r->id()) ? Qt::Checked : Qt::Unchecked);
74         items << item;
75         item = new QStandardItem(r->parentGroup()->name());
76         items << item;
77 
78         // Add id so we can find the resource
79         item = new QStandardItem(r->id());
80         items << item;
81         m->appendRow(items);
82     }
83     if (baselined) {
84         type->setEnabled(false);
85         rateEdit->setEnabled(false);
86         overtimeEdit->setEnabled(false);
87         account->setEnabled(false);
88     }
89     // hide resource identity (last column)
90     ui_teamView->setColumnHidden(m->columnCount() - 1, true);
91     ui_teamView->resizeColumnToContents(0);
92     ui_teamView->sortByColumn(0, Qt::AscendingOrder);
93     slotTypeChanged(resource.type());
94     connect(m, &QAbstractItemModel::dataChanged, this, &ResourceDialogImpl::slotTeamChanged);
95 
96     connect(group, SIGNAL(activated(int)), SLOT(slotChanged()));
97     connect(type, SIGNAL(activated(int)), SLOT(slotTypeChanged(int)));
98     connect(units, SIGNAL(valueChanged(int)), SLOT(slotChanged()));
99     connect(nameEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
100     connect(initialsEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
101     connect(emailEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
102 
103     connect(calendarList, SIGNAL(activated(int)), SLOT(slotChanged()));
104 
105     connect(rateEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
106     connect(overtimeEdit, &QLineEdit::textChanged, this, &ResourceDialogImpl::slotChanged);
107 
108     connect(chooseBtn, &QAbstractButton::clicked, this, &ResourceDialogImpl::slotChooseResource);
109 
110     connect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotChanged);
111     connect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotChanged);
112     connect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableFromChanged);
113     connect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableUntilChanged);
114 
115     connect(ui_rbfrom, &QAbstractButton::toggled, this, &ResourceDialogImpl::slotChanged);
116     connect(ui_rbuntil, &QAbstractButton::toggled, this, &ResourceDialogImpl::slotChanged);
117 
118     connect(ui_rbfrom, &QAbstractButton::toggled, availableFrom, &QWidget::setEnabled);
119     connect(ui_rbuntil, &QAbstractButton::toggled, availableUntil, &QWidget::setEnabled);
120 
121     connect(useRequired, &QCheckBox::stateChanged, this, &ResourceDialogImpl::slotUseRequiredChanged);
122 
123     connect(account, SIGNAL(activated(int)), SLOT(slotChanged()));
124 }
125 
slotTeamChanged(const QModelIndex & index)126 void ResourceDialogImpl::slotTeamChanged(const QModelIndex &index) {
127     if (! index.isValid()) {
128         return;
129     }
130     bool checked = (bool)(index.data(Qt::CheckStateRole).toInt());
131     int idCol = index.model()->columnCount() - 1;
132     QString id = index.model()->index(index.row(), idCol).data().toString();
133     if (checked) {
134         if (! m_resource.teamMemberIds().contains(id)) {
135             m_resource.addTeamMemberId(id);
136         }
137     } else {
138         m_resource.removeTeamMemberId(id);
139     }
140     emit changed();
141 }
142 
slotTypeChanged(int index)143 void ResourceDialogImpl::slotTypeChanged(int index) {
144     switch (index) {
145         case Resource::Type_Work:
146             ui_stackedWidget->setCurrentIndex(0);
147             useRequired->setEnabled(true);
148             slotUseRequiredChanged(useRequired->checkState());
149             break;
150         case Resource::Type_Material:
151             ui_stackedWidget->setCurrentIndex(0);
152             useRequired->setEnabled(false);
153             slotUseRequiredChanged(false);
154             break;
155         case Resource::Type_Team:
156             ui_stackedWidget->setCurrentIndex(1);
157             break;
158     }
159     emit changed();
160 }
161 
slotChanged()162 void ResourceDialogImpl::slotChanged() {
163     emit changed();
164 }
165 
setCurrentIndexes(const QModelIndexList & lst)166 void ResourceDialogImpl::setCurrentIndexes(const QModelIndexList &lst)
167 {
168     m_currentIndexes.clear();
169     foreach (const QModelIndex &idx, lst) {
170         m_currentIndexes << QPersistentModelIndex(idx);
171     }
172     useRequired->setCheckState(m_currentIndexes.isEmpty() ? Qt::Unchecked : Qt::Checked);
173     if (useRequired->isChecked()) {
174         required->setCurrentIndexes(m_currentIndexes);
175     }
176     required->setEnabled(useRequired->isChecked());
177 }
178 
slotUseRequiredChanged(int state)179 void ResourceDialogImpl::slotUseRequiredChanged(int state)
180 {
181     required->setEnabled(state);
182     if (state) {
183         required->setCurrentIndexes(m_currentIndexes);
184     } else {
185         m_currentIndexes = required->currentIndexes();
186         required->setCurrentIndexes(QList<QPersistentModelIndex>());
187     }
188     slotChanged();
189 }
190 
slotAvailableFromChanged(const QDateTime &)191 void ResourceDialogImpl::slotAvailableFromChanged(const QDateTime&) {
192     if (availableUntil->dateTime() < availableFrom->dateTime()) {
193         disconnect(availableUntil, &QDateTimeEdit::dateTimeChanged, this,  &ResourceDialogImpl::slotAvailableUntilChanged);
194         //debugPlan<<"From:"<<availableFrom->dateTime().toString()<<" until="<<availableUntil->dateTime().toString();
195         availableUntil->setDateTime(availableFrom->dateTime());
196         connect(availableUntil, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableUntilChanged);
197     }
198 }
199 
slotAvailableUntilChanged(const QDateTime &)200 void ResourceDialogImpl::slotAvailableUntilChanged(const QDateTime&) {
201     if (availableFrom->dateTime() > availableUntil->dateTime()) {
202         disconnect(availableFrom, &QDateTimeEdit::dateTimeChanged, this,  &ResourceDialogImpl::slotAvailableFromChanged);
203         //debugPlan<<"Until:"<<availableUntil->dateTime().toString()<<" from="<<availableFrom->dateTime().toString();
204         availableFrom->setDateTime(availableUntil->dateTime());
205         connect(availableFrom, &QDateTimeEdit::dateTimeChanged, this, &ResourceDialogImpl::slotAvailableFromChanged);
206     }
207 }
208 
slotCalculationNeeded(const QString &)209 void ResourceDialogImpl::slotCalculationNeeded(const QString&) {
210     emit calculate();
211     emit changed();
212 }
213 
slotChooseResource()214 void ResourceDialogImpl::slotChooseResource()
215 {
216 #ifdef PLAN_KDEPIMLIBS_FOUND
217     QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog(this);
218     if (dlg->exec() && dlg) {
219         QStringList s;
220         const Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
221         if (! selections.isEmpty()) {
222             const Akonadi::EmailAddressSelection s = selections.first();
223             nameEdit->setText(s.name());
224             emailEdit->setText(s.email());
225             const QStringList l = s.name().split(' ');
226             QString in;
227             QStringList::ConstIterator it = l.begin();
228             for (/*int i = 0*/; it != l.end(); ++it) {
229                 in += (*it)[0];
230             }
231             initialsEdit->setText(in);
232         }
233     }
234 #endif
235 }
236 
237 //////////////////  ResourceDialog  ////////////////////////
238 
ResourceDialog(Project & project,Resource * resource,QWidget * parent,const char * name)239 ResourceDialog::ResourceDialog(Project &project, Resource *resource, QWidget *parent, const char *name)
240     : KoDialog(parent),
241       m_project(project),
242       m_original(resource),
243       m_resource(resource),
244       m_calculationNeeded(false)
245 {
246     setObjectName(name);
247 
248     setCaption(i18n("Resource Settings"));
249     setButtons(Ok|Cancel);
250     setDefaultButton(Ok);
251     showButtonSeparator(true);
252     dia = new ResourceDialogImpl(project, m_resource, resource->isBaselined(), this);
253     setMainWidget(dia);
254     KoDialog::enableButtonOk(false);
255 
256     if (resource->parentGroup() == 0) {
257         //HACK to handle calls from ResourcesPanel
258         dia->groupLabel->hide();
259         dia->group->hide();
260     } else {
261         foreach (ResourceGroup *g, project.resourceGroups()) {
262             m_groups.insert(g->name(), g);
263         }
264         dia->group->addItems(m_groups.keys());
265         dia->group->setCurrentIndex(m_groups.values().indexOf(resource->parentGroup())); // clazy:exclude=container-anti-pattern
266     }
267     dia->nameEdit->setText(resource->name());
268     dia->initialsEdit->setText(resource->initials());
269     dia->emailEdit->setText(resource->email());
270     dia->type->setCurrentIndex((int)resource->type()); // NOTE: must match enum
271     dia->units->setValue(resource->units());
272     DateTime dt = resource->availableFrom();
273     if (dt.isValid()) {
274         dia->ui_rbfrom->click();
275     } else {
276         dia->ui_rbfromunlimited->click();
277     }
278     dia->availableFrom->setDateTime(dt.isValid() ? dt : QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime));
279     dia->availableFrom->setEnabled(dt.isValid());
280 
281     dt = resource->availableUntil();
282     if (dt.isValid()) {
283         dia->ui_rbuntil->click();
284     } else {
285         dia->ui_rbuntilunlimited->click();
286     }
287     dia->availableUntil->setDateTime(dt.isValid() ? dt : QDateTime(QDate::currentDate().addYears(2), QTime(0, 0, 0), Qt::LocalTime));
288     dia->availableUntil->setEnabled(dt.isValid());
289     dia->rateEdit->setText(project.locale()->formatMoney(resource->normalRate()));
290     dia->overtimeEdit->setText(project.locale()->formatMoney(resource->overtimeRate()));
291 
292     int cal = 0;
293     dia->calendarList->addItem(i18n("None"));
294     m_calendars.insert(0, 0);
295     QList<Calendar*> list = project.allCalendars();
296     int i=1;
297     foreach (Calendar *c, list) {
298         dia->calendarList->insertItem(i, c->name());
299         m_calendars.insert(i, c);
300         if (c == resource->calendar(true)) {
301             cal = i;
302          }
303         ++i;
304     }
305     dia->calendarList->setCurrentIndex(cal);
306 
307     ResourceItemSFModel *m = new ResourceItemSFModel(this);
308     m->setProject(&project);
309     dia->required->setModel(m);
310     dia->required->view()->expandAll();
311 
312     QItemSelectionModel *sm = dia->required->view()->selectionModel();
313     foreach (Resource *r, resource->requiredResources()) {
314         sm->select(m->index(r), QItemSelectionModel::Select | QItemSelectionModel::Rows);
315     }
316     dia->setCurrentIndexes(sm->selectedRows());
317 
318     QStringList lst;
319     lst << i18n("None") << m_project.accounts().costElements();
320     dia->account->addItems(lst);
321     if (resource->account()) {
322         dia->account->setCurrentIndex(lst.indexOf(resource->account()->name()));
323     }
324     connect(dia, SIGNAL(changed()), SLOT(enableButtonOk()));
325     connect(dia, &ResourceDialogImpl::calculate, this, &ResourceDialog::slotCalculationNeeded);
326     connect(dia->calendarList, SIGNAL(activated(int)), SLOT(slotCalendarChanged(int)));
327     connect(dia->required, SIGNAL(changed()), SLOT(enableButtonOk()));
328     connect(dia->account, SIGNAL(currentIndexChanged(QString)), SLOT(slotAccountChanged(QString)));
329 
330     connect(&project, &Project::resourceRemoved, this, &ResourceDialog::slotResourceRemoved);
331 }
332 
slotResourceRemoved(const Resource * resource)333 void ResourceDialog::slotResourceRemoved(const Resource *resource)
334 {
335     if (m_original == resource) {
336         reject();
337     }
338 }
339 
enableButtonOk()340 void ResourceDialog::enableButtonOk() {
341 		KoDialog::enableButtonOk(true);
342 }
343 
slotCalculationNeeded()344 void ResourceDialog::slotCalculationNeeded() {
345     m_calculationNeeded = true;
346 }
347 
slotButtonClicked(int button)348 void ResourceDialog::slotButtonClicked(int button) {
349     if (button == KoDialog::Ok) {
350         slotOk();
351     } else {
352         KoDialog::slotButtonClicked(button);
353     }
354 }
355 
slotOk()356 void ResourceDialog::slotOk() {
357     if (! m_groups.isEmpty()) {
358         //HACK to handle calls from ResourcesPanel
359         m_resource.setParentGroup(m_groups.value(dia->group->currentText()));
360     }
361     m_resource.setName(dia->nameEdit->text());
362     m_resource.setInitials(dia->initialsEdit->text());
363     m_resource.setEmail(dia->emailEdit->text());
364     m_resource.setType((Resource::Type)(dia->type->currentIndex()));
365     m_resource.setUnits(dia->units->value());
366 
367     m_resource.setNormalRate(m_project.locale()->readMoney(dia->rateEdit->text()));
368     m_resource.setOvertimeRate(m_project.locale()->readMoney(dia->overtimeEdit->text()));
369     m_resource.setCalendar(m_calendars[dia->calendarList->currentIndex()]);
370     m_resource.setAvailableFrom(dia->ui_rbfrom->isChecked() ? dia->availableFrom->dateTime() : QDateTime());
371     m_resource.setAvailableUntil(dia->ui_rbuntil->isChecked() ? dia->availableUntil->dateTime() : QDateTime());
372     ResourceItemSFModel *m = static_cast<ResourceItemSFModel*>(dia->required->model());
373     QStringList lst;
374     foreach (const QModelIndex &i, dia->required->currentIndexes()) {
375         Resource *r = m->resource(i);
376         if (r) lst << r->id();
377     }
378     m_resource.setRequiredIds(lst);
379     accept();
380 }
381 
slotCalendarChanged(int)382 void ResourceDialog::slotCalendarChanged(int /*cal*/) {
383 
384 }
385 
slotAccountChanged(const QString & name)386 void ResourceDialog::slotAccountChanged(const QString &name)
387 {
388     m_resource.setAccount(m_project.accounts().findAccount(name));
389 }
390 
buildCommand()391 MacroCommand *ResourceDialog::buildCommand() {
392     return buildCommand(m_original, m_resource);
393 }
394 
395 // static
buildCommand(Resource * original,Resource & resource)396 MacroCommand *ResourceDialog::buildCommand(Resource *original, Resource &resource) {
397     MacroCommand *m=0;
398     KUndo2MagicString n = kundo2_i18n("Modify Resource");
399     if (resource.parentGroup() != 0 && resource.parentGroup() != original->parentGroup()) {
400         if (!m) m = new MacroCommand(n);
401         m->addCommand(new MoveResourceCmd(resource.parentGroup(), original));
402     }
403     if (resource.name() != original->name()) {
404         if (!m) m = new MacroCommand(n);
405         m->addCommand(new ModifyResourceNameCmd(original, resource.name()));
406     }
407     if (resource.initials() != original->initials()) {
408         if (!m) m = new MacroCommand(n);
409         m->addCommand(new ModifyResourceInitialsCmd(original, resource.initials()));
410     }
411     if (resource.email() != original->email()) {
412         if (!m) m = new MacroCommand(n);
413         m->addCommand(new ModifyResourceEmailCmd(original, resource.email()));
414     }
415     if (resource.type() != original->type()) {
416         if (!m) m = new MacroCommand(n);
417         m->addCommand(new ModifyResourceTypeCmd(original, resource.type()));
418     }
419     if (resource.units() != original->units()) {
420         if (!m) m = new MacroCommand(n);
421         m->addCommand(new ModifyResourceUnitsCmd(original, resource.units()));
422     }
423     if (resource.availableFrom() != original->availableFrom()) {
424         if (!m) m = new MacroCommand(n);
425         m->addCommand(new ModifyResourceAvailableFromCmd(original, resource.availableFrom()));
426     }
427     if (resource.availableUntil() != original->availableUntil()) {
428         if (!m) m = new MacroCommand(n);
429         m->addCommand(new ModifyResourceAvailableUntilCmd(original, resource.availableUntil()));
430     }
431     if (resource.normalRate() != original->normalRate()) {
432         if (!m) m = new MacroCommand(n);
433         m->addCommand(new ModifyResourceNormalRateCmd(original, resource.normalRate()));
434     }
435     if (resource.overtimeRate() != original->overtimeRate()) {
436         if (!m) m = new MacroCommand(n);
437         m->addCommand(new ModifyResourceOvertimeRateCmd(original, resource.overtimeRate()));
438     }
439     if (resource.calendar(true) != original->calendar(true)) {
440         if (!m) m = new MacroCommand(n);
441         m->addCommand(new ModifyResourceCalendarCmd(original, resource.calendar(true)));
442     }
443     if (resource.requiredIds() != original->requiredIds()) {
444         if (!m) m = new MacroCommand(n);
445         m->addCommand(new ModifyRequiredResourcesCmd(original, resource.requiredIds()));
446     }
447     if (resource.account() != original->account()) {
448         if (!m) m = new MacroCommand(n);
449         m->addCommand(new ResourceModifyAccountCmd(*original, original->account(), resource.account()));
450     }
451     if (resource.type() == Resource::Type_Team) {
452         //debugPlan<<original->teamMembers()<<resource.teamMembers();
453         foreach (const QString &id, resource.teamMemberIds()) {
454             if (! original->teamMemberIds().contains(id)) {
455                 if (!m) m = new MacroCommand(n);
456                 m->addCommand(new AddResourceTeamCmd(original, id));
457             }
458         }
459         foreach (const QString &id, original->teamMemberIds()) {
460             if (! resource.teamMemberIds().contains(id)) {
461                 if (!m) m = new MacroCommand(n);
462                 m->addCommand(new RemoveResourceTeamCmd(original, id));
463             }
464         }
465     }
466     return m;
467 }
468 
469 }  //KPlato namespace
470