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