1 /***************************************************************************
2                           addactivityform.cpp  -  description
3                              -------------------
4     begin                : Wed Apr 23 2003
5     copyright            : (C) 2003 by Lalescu Liviu
6     email                : Please see https://lalescu.ro/liviu/ for details about contacting Liviu Lalescu (in particular, you can find here the e-mail address)
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software: you can redistribute it and/or modify  *
12  *   it under the terms of the GNU Affero General Public License as        *
13  *   published by the Free Software Foundation, either version 3 of the    *
14  *   License, or (at your option) any later version.                       *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include <QtGlobal>
19 
20 #include "longtextmessagebox.h"
21 
22 #include "addactivityform.h"
23 #include "teacher.h"
24 #include "subject.h"
25 #include "studentsset.h"
26 
27 #include "activityplanningform.h"
28 
29 #include <QMessageBox>
30 
31 #include <QDialog>
32 
33 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
34 #include <QtWidgets>
35 #else
36 #include <QtGui>
37 #endif
38 
39 #include <QList>
40 
41 #include <QListWidget>
42 #include <QAbstractItemView>
43 #include <QModelIndex>
44 #include <QScrollBar>
45 
46 #include <QSettings>
47 #include <QObject>
48 #include <QMetaObject>
49 
50 extern const QString COMPANY;
51 extern const QString PROGRAM;
52 
dur(int i)53 QSpinBox* AddActivityForm::dur(int i)
54 {
55 	assert(i>=0 && i<durList.count());
56 	assert(i<MAX_SPLIT_OF_AN_ACTIVITY);
57 	return durList.at(i);
58 }
59 
activ(int i)60 QCheckBox* AddActivityForm::activ(int i)
61 {
62 	assert(i>=0 && i<activList.count());
63 	assert(i<MAX_SPLIT_OF_AN_ACTIVITY);
64 	return activList.at(i);
65 }
66 
AddActivityForm(QWidget * parent,const QString & teacherName,const QString & studentsSetName,const QString & subjectName,const QString & activityTagName)67 AddActivityForm::AddActivityForm(QWidget* parent, const QString& teacherName, const QString& studentsSetName, const QString& subjectName, const QString& activityTagName): QDialog(parent)
68 {
69 	setupUi(this);
70 
71 	studentsSeparatelyCheckBox->setChecked(false);
72 
73 	for(Teacher* tch : qAsConst(gt.rules.teachersList))
74 		teacherNamesSet.insert(tch->name);
75 	for(Subject* sbj : qAsConst(gt.rules.subjectsList))
76 		subjectNamesSet.insert(sbj->name);
77 	for(ActivityTag* at : qAsConst(gt.rules.activityTagsList))
78 		activityTagNamesSet.insert(at->name);
79 
80 	allTeachersListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
81 	selectedTeachersListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
82 	allStudentsListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
83 	selectedStudentsListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
84 	allActivityTagsListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
85 	selectedActivityTagsListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
86 
87 	splitSpinBox->setMinimum(1);
88 	splitSpinBox->setMaximum(MAX_SPLIT_OF_AN_ACTIVITY);
89 	splitSpinBox->setValue(1);
90 
91 	durList.clear();
92 	activList.clear();
93 
94 	populateSubactivitiesTabWidget(splitSpinBox->value());
95 
96 	QSettings settings(COMPANY, PROGRAM);
97 
98 	subgroupsCheckBox->setChecked(settings.value(this->metaObject()->className()+QString("/show-subgroups-check-box-state"), "false").toBool());
99 	groupsCheckBox->setChecked(settings.value(this->metaObject()->className()+QString("/show-groups-check-box-state"), "true").toBool());
100 	yearsCheckBox->setChecked(settings.value(this->metaObject()->className()+QString("/show-years-check-box-state"), "true").toBool());
101 
102 	allTeachersRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/all-teachers-radio-button-state"), "true").toBool());
103 	qualifiedTeachersRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/qualified-teachers-radio-button-state"), "false").toBool());
104 
105 	connect(subgroupsCheckBox, SIGNAL(toggled(bool)), this, SLOT(showSubgroupsChanged()));
106 	connect(groupsCheckBox, SIGNAL(toggled(bool)), this, SLOT(showGroupsChanged()));
107 	connect(yearsCheckBox, SIGNAL(toggled(bool)), this, SLOT(showYearsChanged()));
108 
109 	connect(splitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(splitChanged()));
110 
111 	connect(closePushButton, SIGNAL(clicked()), this, SLOT(close()));
112 	connect(addActivityPushButton, SIGNAL(clicked()), this, SLOT(addActivity()));
113 	connect(helpPushButton, SIGNAL(clicked()), this, SLOT(help()));
114 
115 	connect(allTeachersListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(addTeacher()));
116 	connect(selectedTeachersListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(removeTeacher()));
117 	connect(allStudentsListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(addStudents()));
118 	connect(selectedStudentsListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(removeStudents()));
119 	connect(allActivityTagsListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(addActivityTag()));
120 	connect(selectedActivityTagsListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(removeActivityTag()));
121 
122 	connect(clearActivityTagPushButton, SIGNAL(clicked()), this, SLOT(clearActivityTags()));
123 	connect(clearStudentsPushButton, SIGNAL(clicked()), this, SLOT(clearStudents()));
124 	connect(clearTeacherPushButton, SIGNAL(clicked()), this, SLOT(clearTeachers()));
125 
126 	connect(minDayDistanceSpinBox, SIGNAL(valueChanged(int)), this, SLOT(minDaysChanged()));
127 
128 	centerWidgetOnScreen(this);
129 	restoreFETDialogGeometry(this);
130 
131 	QSize tmp3=subjectsComboBox->minimumSizeHint();
132 	Q_UNUSED(tmp3);
133 
134 	selectedStudentsListWidget->clear();
135 	updateStudentsListWidget();
136 
137 	updateSubjectsComboBox();
138 	if(subjectName!=""){
139 		int pos=-1;
140 		for(int i=0; i<subjectsComboBox->count(); i++){
141 			if(subjectsComboBox->itemText(i)==subjectName){
142 				pos=i;
143 				break;
144 			}
145 		}
146 		assert(pos>=0);
147 		subjectsComboBox->setCurrentIndex(pos);
148 	}
149 	else{
150 		//begin trick to pass a Qt 4.6.0 bug: the first entry is not highlighted with mouse until you move to second entry and then back up
151 		//also, this trick makes the combo box behave nicer under Windows: the first subject is shown with an ugly edge if not using this trick.
152 		if(subjectsComboBox->view()){
153 			subjectsComboBox->view()->setCurrentIndex(QModelIndex());
154 		}
155 		//end trick
156 		subjectsComboBox->setCurrentIndex(-1);
157 	}
158 
159 	updateActivityTagsListWidget();
160 
161 	//after updateSubjectsComboBox
162 	connect(subjectsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateAllTeachersListWidget()));
163 	connect(allTeachersRadioButton, SIGNAL(toggled(bool)), this, SLOT(allTeachersRadioButtonToggled(bool)));
164 	connect(qualifiedTeachersRadioButton, SIGNAL(toggled(bool)), this, SLOT(qualifiedTeachersRadioButtonToggled(bool)));
165 	updateAllTeachersListWidget();
166 	selectedTeachersListWidget->clear();
167 
168 	if(gt.rules.mode!=MORNINGS_AFTERNOONS)
169 		minDayDistanceSpinBox->setMaximum(gt.rules.nDaysPerWeek-1);
170 	else
171 		minDayDistanceSpinBox->setMaximum(gt.rules.nDaysPerWeek/2-1);
172 	minDayDistanceSpinBox->setMinimum(0);
173 	minDayDistanceSpinBox->setValue(1);
174 
175 	int nSplit=splitSpinBox->value();
176 
177 	minDayDistanceTextLabel->setEnabled(nSplit>=2);
178 	minDayDistanceSpinBox->setEnabled(nSplit>=2);
179 	percentageTextLabel->setEnabled(nSplit>=2 && minDayDistanceSpinBox->value()>0);
180 	percentageLineEdit->setEnabled(nSplit>=2 && minDayDistanceSpinBox->value()>0);
181 	forceConsecutiveCheckBox->setEnabled(nSplit>=2 && minDayDistanceSpinBox->value()>0);
182 
183 	nStudentsSpinBox->setMinimum(-1);
184 	nStudentsSpinBox->setMaximum(MAX_ROOM_CAPACITY);
185 	nStudentsSpinBox->setValue(-1);
186 
187 	addActivityPushButton->setDefault(true);
188 	addActivityPushButton->setFocus();
189 
190 	if(teacherName!="")
191 		selectedTeachersListWidget->addItem(teacherName);
192 	if(studentsSetName!="")
193 		selectedStudentsListWidget->addItem(studentsSetName);
194 	if(activityTagName!="")
195 		selectedActivityTagsListWidget->addItem(activityTagName);
196 }
197 
~AddActivityForm()198 AddActivityForm::~AddActivityForm()
199 {
200 	saveFETDialogGeometry(this);
201 
202 	QSettings settings(COMPANY, PROGRAM);
203 
204 	settings.setValue(this->metaObject()->className()+QString("/show-subgroups-check-box-state"), subgroupsCheckBox->isChecked());
205 	settings.setValue(this->metaObject()->className()+QString("/show-groups-check-box-state"), groupsCheckBox->isChecked());
206 	settings.setValue(this->metaObject()->className()+QString("/show-years-check-box-state"), yearsCheckBox->isChecked());
207 
208 	settings.setValue(this->metaObject()->className()+QString("/qualified-teachers-radio-button-state"), qualifiedTeachersRadioButton->isChecked());
209 	settings.setValue(this->metaObject()->className()+QString("/all-teachers-radio-button-state"), allTeachersRadioButton->isChecked());
210 }
211 
populateSubactivitiesTabWidget(int n)212 void AddActivityForm::populateSubactivitiesTabWidget(int n)
213 {
214 	int oldIndex=subactivitiesTabWidget->currentIndex();
215 
216 	int oldN=subactivitiesTabWidget->count();
217 	if(oldN>n){
218 		for(int i=oldN-1; i>=n; i--){
219 			QWidget* wd=subactivitiesTabWidget->widget(i);
220 			subactivitiesTabWidget->removeTab(i);
221 			assert(durList.count()>0);
222 			durList.removeLast();
223 			assert(activList.count()>0);
224 			activList.removeLast();
225 			delete wd;
226 		}
227 
228 		if(oldIndex<subactivitiesTabWidget->count())
229 			subactivitiesTabWidget->setCurrentIndex(oldIndex);
230 		else
231 			subactivitiesTabWidget->setCurrentIndex(subactivitiesTabWidget->count()-1);
232 	}
233 	else if(oldN<n){
234 		for(int i=oldN; i<n; i++){
235 			QWidget* wd=new QWidget(subactivitiesTabWidget);
236 
237 			QCheckBox* cb=new QCheckBox(tr("Active"), wd);
238 			cb->setChecked(true);
239 			QSpinBox* sb=new QSpinBox(wd);
240 			sb->setMinimum(1);
241 			sb->setMaximum(MAX_HOURS_PER_DAY);
242 			sb->setValue(1);
243 			QLabel* ld=new QLabel(tr("Duration"), wd);
244 
245 			QHBoxLayout* hld=new QHBoxLayout();
246 			hld->addWidget(ld);
247 			hld->addWidget(sb);
248 
249 			QHBoxLayout* hla=new QHBoxLayout();
250 			hla->addStretch();
251 			hla->addWidget(cb);
252 
253 			QVBoxLayout* vl=new QVBoxLayout(wd);
254 			vl->addLayout(hld);
255 			vl->addLayout(hla);
256 
257 			subactivitiesTabWidget->addTab(wd, QString::number(i+1));
258 
259 			durList.append(sb);
260 			activList.append(cb);
261 		}
262 
263 		subactivitiesTabWidget->setCurrentIndex(subactivitiesTabWidget->count()-1);
264 	}
265 }
266 
allTeachersRadioButtonToggled(bool toggled)267 void AddActivityForm::allTeachersRadioButtonToggled(bool toggled)
268 {
269 	if(toggled)
270 		updateAllTeachersListWidget();
271 }
272 
qualifiedTeachersRadioButtonToggled(bool toggled)273 void AddActivityForm::qualifiedTeachersRadioButtonToggled(bool toggled)
274 {
275 	if(toggled)
276 		updateAllTeachersListWidget();
277 }
278 
updateAllTeachersListWidget()279 void AddActivityForm::updateAllTeachersListWidget()
280 {
281 	allTeachersListWidget->clear();
282 
283 	for(int i=0; i<gt.rules.teachersList.size(); i++){
284 		Teacher* tch=gt.rules.teachersList[i];
285 		if(allTeachersRadioButton->isChecked() || subjectsComboBox->currentIndex()==-1){
286 			allTeachersListWidget->addItem(tch->name);
287 		}
288 		else{
289 			assert(qualifiedTeachersRadioButton->isChecked());
290 			assert(subjectsComboBox->currentText()!="");
291 			assert(subjectNamesSet.contains(subjectsComboBox->currentText()));
292 			if(tch->qualifiedSubjectsHash.contains(subjectsComboBox->currentText())){
293 				allTeachersListWidget->addItem(tch->name);
294 			}
295 		}
296 	}
297 }
298 
addTeacher()299 void AddActivityForm::addTeacher()
300 {
301 	if(allTeachersListWidget->currentRow()<0 || allTeachersListWidget->currentRow()>=allTeachersListWidget->count())
302 		return;
303 
304 	for(int i=0; i<selectedTeachersListWidget->count(); i++)
305 		if(selectedTeachersListWidget->item(i)->text()==allTeachersListWidget->currentItem()->text())
306 			return;
307 
308 	selectedTeachersListWidget->addItem(allTeachersListWidget->currentItem()->text());
309 	selectedTeachersListWidget->setCurrentRow(selectedTeachersListWidget->count()-1);
310 }
311 
removeTeacher()312 void AddActivityForm::removeTeacher()
313 {
314 	if(selectedTeachersListWidget->count()<=0 || selectedTeachersListWidget->currentRow()<0 ||
315 	 selectedTeachersListWidget->currentRow()>=selectedTeachersListWidget->count())
316 		return;
317 
318 	int i=selectedTeachersListWidget->currentRow();
319 	selectedTeachersListWidget->setCurrentRow(-1);
320 	QListWidgetItem* item=selectedTeachersListWidget->takeItem(i);
321 	delete item;
322 	if(i<selectedTeachersListWidget->count())
323 		selectedTeachersListWidget->setCurrentRow(i);
324 	else
325 		selectedTeachersListWidget->setCurrentRow(selectedTeachersListWidget->count()-1);
326 }
327 
addStudents()328 void AddActivityForm::addStudents()
329 {
330 	if(allStudentsListWidget->currentRow()<0 || allStudentsListWidget->currentRow()>=allStudentsListWidget->count())
331 		return;
332 
333 	assert(canonicalStudentsSetsNames.count()==allStudentsListWidget->count());
334 	QString sn=canonicalStudentsSetsNames.at(allStudentsListWidget->currentRow());
335 
336 	for(int i=0; i<selectedStudentsListWidget->count(); i++)
337 		if(selectedStudentsListWidget->item(i)->text()==sn)
338 			return;
339 
340 	selectedStudentsListWidget->addItem(sn);
341 	selectedStudentsListWidget->setCurrentRow(selectedStudentsListWidget->count()-1);
342 }
343 
removeStudents()344 void AddActivityForm::removeStudents()
345 {
346 	if(selectedStudentsListWidget->count()<=0 || selectedStudentsListWidget->currentRow()<0 ||
347 	 selectedStudentsListWidget->currentRow()>=selectedStudentsListWidget->count())
348 		return;
349 
350 	int i=selectedStudentsListWidget->currentRow();
351 	selectedStudentsListWidget->setCurrentRow(-1);
352 	QListWidgetItem* item=selectedStudentsListWidget->takeItem(i);
353 	delete item;
354 	if(i<selectedStudentsListWidget->count())
355 		selectedStudentsListWidget->setCurrentRow(i);
356 	else
357 		selectedStudentsListWidget->setCurrentRow(selectedStudentsListWidget->count()-1);
358 }
359 
addActivityTag()360 void AddActivityForm::addActivityTag()
361 {
362 	if(allActivityTagsListWidget->currentRow()<0 || allActivityTagsListWidget->currentRow()>=allActivityTagsListWidget->count())
363 		return;
364 
365 	for(int i=0; i<selectedActivityTagsListWidget->count(); i++)
366 		if(selectedActivityTagsListWidget->item(i)->text()==allActivityTagsListWidget->currentItem()->text())
367 			return;
368 
369 	selectedActivityTagsListWidget->addItem(allActivityTagsListWidget->currentItem()->text());
370 	selectedActivityTagsListWidget->setCurrentRow(selectedActivityTagsListWidget->count()-1);
371 }
372 
removeActivityTag()373 void AddActivityForm::removeActivityTag()
374 {
375 	if(selectedActivityTagsListWidget->count()<=0 || selectedActivityTagsListWidget->currentRow()<0 ||
376 	 selectedActivityTagsListWidget->currentRow()>=selectedActivityTagsListWidget->count())
377 		return;
378 
379 	int i=selectedActivityTagsListWidget->currentRow();
380 	selectedActivityTagsListWidget->setCurrentRow(-1);
381 	QListWidgetItem* item=selectedActivityTagsListWidget->takeItem(i);
382 	delete item;
383 	if(i<selectedActivityTagsListWidget->count())
384 		selectedActivityTagsListWidget->setCurrentRow(i);
385 	else
386 		selectedActivityTagsListWidget->setCurrentRow(selectedActivityTagsListWidget->count()-1);
387 }
388 
updateSubjectsComboBox()389 void AddActivityForm::updateSubjectsComboBox()
390 {
391 	subjectsComboBox->clear();
392 	for(int i=0; i<gt.rules.subjectsList.size(); i++){
393 		Subject* sbj=gt.rules.subjectsList[i];
394 		subjectsComboBox->addItem(sbj->name);
395 	}
396 }
397 
updateActivityTagsListWidget()398 void AddActivityForm::updateActivityTagsListWidget()
399 {
400 	allActivityTagsListWidget->clear();
401 	for(int i=0; i<gt.rules.activityTagsList.size(); i++){
402 		ActivityTag* at=gt.rules.activityTagsList[i];
403 		allActivityTagsListWidget->addItem(at->name);
404 	}
405 
406 	selectedActivityTagsListWidget->clear();
407 }
408 
showYearsChanged()409 void AddActivityForm::showYearsChanged()
410 {
411 	updateStudentsListWidget();
412 }
413 
showGroupsChanged()414 void AddActivityForm::showGroupsChanged()
415 {
416 	updateStudentsListWidget();
417 }
418 
showSubgroupsChanged()419 void AddActivityForm::showSubgroupsChanged()
420 {
421 	updateStudentsListWidget();
422 }
423 
updateStudentsListWidget()424 void AddActivityForm::updateStudentsListWidget()
425 {
426 	const int INDENT=2;
427 
428 	bool showYears=yearsCheckBox->isChecked();
429 	bool showGroups=groupsCheckBox->isChecked();
430 	bool showSubgroups=subgroupsCheckBox->isChecked();
431 
432 	allStudentsListWidget->clear();
433 	canonicalStudentsSetsNames.clear();
434 	for(int i=0; i<gt.rules.yearsList.size(); i++){
435 		StudentsYear* sty=gt.rules.yearsList[i];
436 		if(showYears){
437 			allStudentsListWidget->addItem(sty->name);
438 			canonicalStudentsSetsNames.append(sty->name);
439 		}
440 		for(int j=0; j<sty->groupsList.size(); j++){
441 			StudentsGroup* stg=sty->groupsList[j];
442 			if(showGroups){
443 				QString begin=QString("");
444 				QString end=QString("");
445 				begin=QString(INDENT, ' ');
446 				allStudentsListWidget->addItem(begin+stg->name+end);
447 				canonicalStudentsSetsNames.append(stg->name);
448 			}
449 			if(showSubgroups) for(int k=0; k<stg->subgroupsList.size(); k++){
450 				StudentsSubgroup* sts=stg->subgroupsList[k];
451 
452 				QString begin=QString("");
453 				QString end=QString("");
454 				begin=QString(2*INDENT, ' ');
455 				allStudentsListWidget->addItem(begin+sts->name+end);
456 				canonicalStudentsSetsNames.append(sts->name);
457 			}
458 		}
459 	}
460 
461 	int q=allStudentsListWidget->verticalScrollBar()->minimum();
462 	allStudentsListWidget->verticalScrollBar()->setValue(q);
463 }
464 
splitChanged()465 void AddActivityForm::splitChanged()
466 {
467 	int nSplit=splitSpinBox->value();
468 
469 	minDayDistanceTextLabel->setEnabled(nSplit>=2);
470 	minDayDistanceSpinBox->setEnabled(nSplit>=2);
471 	percentageTextLabel->setEnabled(nSplit>=2 && minDayDistanceSpinBox->value()>0);
472 	percentageLineEdit->setEnabled(nSplit>=2 && minDayDistanceSpinBox->value()>0);
473 	forceConsecutiveCheckBox->setEnabled(nSplit>=2 && minDayDistanceSpinBox->value()>0);
474 
475 	populateSubactivitiesTabWidget(nSplit);
476 }
477 
SecondMinDaysDialog(QWidget * p,int minD,double w)478 SecondMinDaysDialog::SecondMinDaysDialog(QWidget* p, int minD, double w) :QDialog(p)
479 {
480 	weight=-1;
481 
482 	QString l=tr
483 	 ("You selected min days between activities %1 (above 1) and weight %2 (under 100.0). "
484 	  "Would you like to add also a second constraint to ensure that almost certainly the "
485 	  "distance between activities is at least %3 (%1-1) days? If yes, please select weight (recommended "
486 	  "95.0%-100.0%) and click Yes. If no, please click No (only one constraint will be added)").arg(CustomFETString::number(minD)).arg(w).arg(minD-1);
487 	l+="\n\n";
488 	l+=tr("(Yes means to add an additional constraint min %1 days between activities, weight 0.0%-100.0%. "
489 	  "If you say Yes, you will have 2 constraints min days added for current activities. "
490 	  "Adding the second constraint might lead to impossible timetables if the condition is "
491 	  "too tight, but you can remove the second constraint at any time).").arg(minD-1);
492 	l+="\n\n";
493 	l+=tr("Note: 95% is usually enough for min days constraints referring to same activities. "
494 	  "The weights are cumulated if referring to the same activities. If you have 2 constraints with say 95%"
495 	  " (say min n days and min n-1 days), "
496 	  "the min n days constraint is skipped with probability 5%, then min n-1 days constraint is skipped with "
497 	  "probability 0.25%=5%*5%, so you'll get in 99.75% cases the min n-1 days constraint respected.");
498 	l+="\n\n";
499 	l+=tr("Recommended answer is Yes, 95% (or higher).");
500 
501 	setWindowTitle(tr("Add a second constraint or not?"));
502 
503 	QVBoxLayout* vl=new QVBoxLayout(this);
504 
505 	QPlainTextEdit* la=new QPlainTextEdit();
506 	la->setPlainText(l);
507 	la->setReadOnly(true);
508 
509 	vl->addWidget(la);
510 
511 	QPushButton* yes=new QPushButton(tr("Yes"));
512 	yes->setDefault(true);
513 
514 	QPushButton* no=new QPushButton(tr("No"));
515 
516 	QLabel* percLabel=new QLabel(this);
517 	percLabel->setText("Percentage");
518 	percText=new QLineEdit(this);
519 	percText->setText("95.0");
520 
521 	//QHBoxLayout* hl2=new QHBoxLayout(vl);
522 	QHBoxLayout* hl2=new QHBoxLayout();
523 	vl->addLayout(hl2);
524 
525 	//////
526 	QLabel* minDaysLabel=new QLabel(this);
527 	minDaysLabel->setText("Min days");
528 	QSpinBox* minDaysSpinBox=new QSpinBox(this);
529 	minDaysSpinBox->setMinimum(minD-1);
530 	minDaysSpinBox->setMaximum(minD-1);
531 	minDaysSpinBox->setValue(minD-1);
532 	minDaysSpinBox->setEnabled(false);
533 	//////
534 
535 	//////
536 	hl2->addStretch(1);
537 	hl2->addWidget(minDaysLabel);
538 	hl2->addWidget(minDaysSpinBox);
539 	//////
540 
541 	hl2->addStretch(1);
542 	hl2->addWidget(percLabel);
543 	hl2->addWidget(percText);
544 
545 	//QHBoxLayout* hl=new QHBoxLayout(vl);
546 	QHBoxLayout* hl=new QHBoxLayout();
547 	vl->addLayout(hl);
548 
549 	hl->addStretch(1);
550 	hl->addWidget(yes);
551 	hl->addWidget(no);
552 
553 	connect(yes, SIGNAL(clicked()), this, SLOT(yesPressed()));
554 	connect(no, SIGNAL(clicked()), this, SLOT(reject()));
555 
556 	int ww=this->sizeHint().width();
557 	if(ww>1000)
558 		ww=1000;
559 	if(ww<590)
560 		ww=590;
561 
562 	int hh=this->sizeHint().height();
563 	if(hh>650)
564 		hh=650;
565 	if(hh<380)
566 		hh=380;
567 
568 	this->resize(ww, hh);
569 	centerWidgetOnScreen(this);
570 	restoreFETDialogGeometry(this);
571 }
572 
~SecondMinDaysDialog()573 SecondMinDaysDialog::~SecondMinDaysDialog()
574 {
575 	saveFETDialogGeometry(this);
576 }
577 
yesPressed()578 void SecondMinDaysDialog::yesPressed()
579 {
580 	double wt;
581 	QString tmp=percText->text();
582 	weight_sscanf(tmp, "%lf", &wt);
583 	if(wt<0.0 || wt>100.0){
584 		QMessageBox::warning(this, tr("FET information"),
585 			tr("Invalid weight (percentage) - must be >=0 and <=100.0"));
586 		return;
587 	}
588 	weight=wt;
589 	accept();
590 }
591 
addActivity()592 void AddActivityForm::addActivity()
593 {
594 	if(studentsSeparatelyCheckBox->isChecked()){
595 		addMultipleActivities();
596 		return;
597 	}
598 
599 	double weight;
600 	QString tmp=percentageLineEdit->text();
601 	weight_sscanf(tmp, "%lf", &weight);
602 	if(percentageLineEdit->isEnabled() && (weight<0.0 || weight>100.0)){
603 		QMessageBox::warning(this, tr("FET information"),
604 			tr("Invalid weight (percentage) for added constraint min days between activities"));
605 		return;
606 	}
607 
608 	//subject
609 	QString subject_name=subjectsComboBox->currentText();
610 	bool found=subjectNamesSet.contains(subject_name);
611 	/*int subject_index=gt.rules.searchSubject(subject_name);
612 	if(subject_index<0){*/
613 	if(!found){
614 		QMessageBox::warning(this, tr("FET warning"),
615 			tr("Invalid subject"));
616 		return;
617 	}
618 
619 	QStringList activity_tags_names;
620 	for(int i=0; i<selectedActivityTagsListWidget->count(); i++){
621 		//assert(gt.rules.searchActivityTag(selectedActivityTagsListWidget->item(i)->text())>=0);
622 		assert(activityTagNamesSet.contains(selectedActivityTagsListWidget->item(i)->text()));
623 		activity_tags_names.append(selectedActivityTagsListWidget->item(i)->text());
624 	}
625 
626 	//teachers
627 	QStringList teachers_names;
628 	if(selectedTeachersListWidget->count()<=0){
629 		int t=QMessageBox::question(this, tr("FET question"),
630 		 tr("Do you really want to add an activity without teacher(s)?"),
631 		 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
632 
633 		if(t==QMessageBox::No)
634 			return;
635 	}
636 	else{
637 		for(int i=0; i<selectedTeachersListWidget->count(); i++){
638 			//assert(gt.rules.searchTeacher(selectedTeachersListWidget->item(i)->text())>=0);
639 			assert(teacherNamesSet.contains(selectedTeachersListWidget->item(i)->text()));
640 			teachers_names.append(selectedTeachersListWidget->item(i)->text());
641 		}
642 	}
643 
644 	//students
645 	int numberOfStudents=0;
646 	QStringList students_names;
647 	if(selectedStudentsListWidget->count()<=0){
648 		int t=QMessageBox::question(this, tr("FET question"),
649 		 tr("Do you really want to add an activity without student set(s)?"),
650 		 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
651 
652 		if(t==QMessageBox::No)
653 			return;
654 
655 		//Crash bug fixed when adding an activity without students but with a specified number of students >= 1
656 		//Reported by Zsolt Udvari
657 		//Reported and fixed on 2017-07-25
658 		if(nStudentsSpinBox->value()>=0)
659 			numberOfStudents=nStudentsSpinBox->value();
660 	}
661 	else{
662 		for(int i=0; i<selectedStudentsListWidget->count(); i++){
663 			//assert(gt.rules.searchStudentsSet(selectedStudentsListWidget->item(i)->text())!=nullptr);
664 			/*assert(numberOfStudentsHash.contains(selectedStudentsListWidget->item(i)->text()));
665 			numberOfStudents+=numberOfStudentsHash.value(selectedStudentsListWidget->item(i)->text());*/
666 			assert(gt.rules.permanentStudentsHash.contains(selectedStudentsListWidget->item(i)->text()));
667 			numberOfStudents+=gt.rules.permanentStudentsHash.value(selectedStudentsListWidget->item(i)->text())->numberOfStudents;
668 			students_names.append(selectedStudentsListWidget->item(i)->text());
669 		}
670 
671 		if(nStudentsSpinBox->value()>=0)
672 			numberOfStudents=nStudentsSpinBox->value();
673 	}
674 
675 	if(splitSpinBox->value()==1){ //indivisible activity
676 		int duration=dur(0)->value();
677 		if(duration<0){
678 			QMessageBox::warning(this, tr("FET information"),
679 				tr("Invalid duration"));
680 			return;
681 		}
682 
683 		bool active=false;
684 		if(activ(0)->isChecked())
685 			active=true;
686 
687 		int activityid=0; //We set the id of this newly added activity = (the largest existing id + 1)
688 		for(int i=0; i<gt.rules.activitiesList.size(); i++){
689 			Activity* act=gt.rules.activitiesList[i];
690 			if(act->id > activityid)
691 				activityid = act->id;
692 		}
693 		activityid++;
694 		Activity a(gt.rules, activityid, 0, teachers_names, subject_name, activity_tags_names, students_names,
695 			duration, duration, /*parity,*/ active, (nStudentsSpinBox->value()==-1), nStudentsSpinBox->value(), numberOfStudents);
696 
697 		bool already_existing=false;
698 		for(int i=0; i<gt.rules.activitiesList.size(); i++){
699 			Activity* act=gt.rules.activitiesList[i];
700 			if((*act)==a)
701 				already_existing=true;
702 		}
703 
704 		if(already_existing){
705 			/*int t=QMessageBox::question(this, tr("FET question"),
706 				tr("A similar activity already exists. Do you want to insert current activity?"),
707 				tr("Yes"),tr("No"));
708 			assert(t==0 || t==1 || t==-1);
709 			if(t==1) //no pressed
710 				return;
711 			if(t==-1) //Esc pressed
712 				return;*/
713 			QMessageBox::StandardButton t=QMessageBox::question(this, tr("FET question"),
714 				tr("A similar activity already exists. Do you want to insert current activity?"),
715 				QMessageBox::Yes | QMessageBox::No);
716 			if(t==QMessageBox::No)
717 				return;
718 		}
719 
720 		bool tmp=gt.rules.addSimpleActivityFast(this, activityid, 0, teachers_names, subject_name, activity_tags_names,
721 			students_names, duration, duration, active,
722 			(nStudentsSpinBox->value()==-1), nStudentsSpinBox->value(), numberOfStudents);
723 		if(tmp)
724 			QMessageBox::information(this, tr("FET information"), tr("Activity added"));
725 		else
726 			QMessageBox::critical(this, tr("FET information"), tr("Activity NOT added - please report error"));
727 	}
728 	else{ //split activity
729 		if(gt.rules.mode!=MORNINGS_AFTERNOONS){
730 			if(minDayDistanceSpinBox->value()>0 && splitSpinBox->value()>gt.rules.nDaysPerWeek){
731 				int t=LongTextMessageBox::largeConfirmation(this, tr("FET confirmation"),
732 				 tr("Possible incorrect setting. Are you sure you want to add current activity? See details below:")+"\n\n"+
733 				 tr("You want to add a container activity split into more than the number of days per week and also add a constraint min days between activities."
734 				  " This is a very bad practice from the way the algorithm of generation works (it slows down the generation and makes it harder to find a solution).")+
735 				 "\n\n"+
736 				 tr("The best way to add the activities would be:")+
737 				 "\n\n"+
738 				 tr("1. If you add 'force consecutive if same day', then couple extra activities in pairs to obtain a number of activities equal to the number of days per week"
739 				  ". Example: 7 activities with duration 1 in a 5 days week, then transform into 5 activities with durations: 2,2,1,1,1 and add a single container activity with these 5 components"
740 				  " (possibly raising the weight of added constraint min days between activities up to 100%)")+
741 				  "\n\n"+
742 				 tr("2. If you don't add 'force consecutive if same day', then add a larger activity split into a number of"
743 				  " activities equal with the number of days per week and the remaining components into other larger split activity."
744 				  " For example, suppose you need to add 7 activities with duration 1 in a 5 days week. Add 2 larger container activities,"
745 				  " first one split into 5 activities with duration 1 and second one split into 2 activities with duration 1"
746 				  " (possibly raising the weight of added constraints min days between activities for each of the 2 containers up to 100%)")+
747 			  	 "\n\n"+
748 				 tr("Do you want to add current activities as they are now (not recommended) or cancel and edit them as instructed?")
749 				  ,
750 				 tr("Yes"), tr("No"), QString(), 0, 1);
751 
752 				if(t==1)
753 					return;
754 			}
755 		}
756 		else{
757 			if(minDayDistanceSpinBox->value()>0 && splitSpinBox->value()>gt.rules.nDaysPerWeek/2){
758 				int t=LongTextMessageBox::largeConfirmation(this, tr("FET confirmation"),
759 				 tr("Possible incorrect setting. Are you sure you want to add current activity? See details below:")+"\n\n"+
760 				 tr("You want to add a container activity split into more than the number of real days per week and also add a constraint min days between activities."
761 				  " This is a very bad practice from the way the algorithm of generation works (it slows down the generation and makes it harder to find a solution).")+
762 				 "\n\n"+
763 				 tr("The best way to add the activities would be:")+
764 				 "\n\n"+
765 				 tr("1. If you add 'force consecutive if same day', then couple extra activities in pairs to obtain a number of activities equal to the number of real days per week"
766 				  ". Example: 7 activities with duration 1 in a 5 real days week, then transform into 5 activities with durations: 2,2,1,1,1 and add a single container activity with these 5 components"
767 				  " (possibly raising the weight of added constraint min days between activities up to 100%)")+
768 				  "\n\n"+
769 				 tr("2. If you don't add 'force consecutive if same day', then add a larger activity split into a number of"
770 				  " activities equal with the number of real days per week and the remaining components into other larger split activity."
771 				  " For example, suppose you need to add 7 activities with duration 1 in a 5 real days week. Add 2 larger container activities,"
772 				  " first one split into 5 activities with duration 1 and second one split into 2 activities with duration 1"
773 				  " (possibly raising the weight of added constraints min days between activities for each of the 2 containers up to 100%)")+
774 			  	 "\n\n"+
775 				 tr("Do you want to add current activities as they are now (not recommended) or cancel and edit them as instructed?")
776 				  ,
777 				 tr("Yes"), tr("No"), QString(), 0, 1);
778 
779 				if(t==1)
780 					return;
781 			}
782 		}
783 
784 		int totalduration;
785 		QList<int> durations;
786 		QList<bool> active;
787 		int nsplit=splitSpinBox->value();
788 
789 		totalduration=0;
790 		for(int i=0; i<nsplit; i++){
791 			durations.append(dur(i)->value());
792 			active.append(false);
793 			if(activ(i)->isChecked())
794 				active[i]=true;
795 
796 			totalduration+=durations[i];
797 		}
798 
799 		//the group id of this split activity and the id of the first partial activity
800 		//it is the maximum already existing id + 1
801 		int firstactivityid=0;
802 		for(int i=0; i<gt.rules.activitiesList.size(); i++){
803 			Activity* act=gt.rules.activitiesList[i];
804 			if(act->id > firstactivityid)
805 				firstactivityid = act->id;
806 		}
807 		firstactivityid++;
808 
809 		int minD=minDayDistanceSpinBox->value();
810 		bool tmp=gt.rules.addSplitActivityFast(this, firstactivityid, firstactivityid,
811 			teachers_names, subject_name, activity_tags_names, students_names,
812 			nsplit, totalduration, durations,
813 			active, minD, weight, forceConsecutiveCheckBox->isChecked(),
814 			(nStudentsSpinBox->value()==-1), nStudentsSpinBox->value(), numberOfStudents);
815 		if(tmp){
816 			if(minD>1 && weight<100.0){
817 				SecondMinDaysDialog second(this, minD, weight);
818 				setParentAndOtherThings(&second, this);
819 				int code=second.exec();
820 
821 				if(code==QDialog::Accepted){
822 					assert(second.weight>=0 && second.weight<=100.0);
823 					QList<int> acts;
824 					for(int i=0; i<nsplit; i++){
825 						acts.append(firstactivityid+i);
826 					}
827 					TimeConstraint* c=new ConstraintMinDaysBetweenActivities(second.weight, forceConsecutiveCheckBox->isChecked(), nsplit, acts, minD-1);
828 					bool tmp=gt.rules.addTimeConstraint(c);
829 					assert(tmp);
830 				}
831 			}
832 
833 			QMessageBox::information(this, tr("FET information"), tr("Split activity added."
834 			 " Please note that FET currently cannot check for duplicates when adding split activities"
835 			 ". It is advisable to check the statistics after adding all the activities"));
836 		}
837 		else
838 			QMessageBox::critical(this, tr("FET information"), tr("Split activity NOT added - error???"));
839 	}
840 
841 	PlanningChanged::increasePlanningCommunicationSpinBox();
842 }
843 
addMultipleActivities()844 void AddActivityForm::addMultipleActivities()
845 {
846 	assert(studentsSeparatelyCheckBox->isChecked());
847 
848 	double weight;
849 	QString tmp=percentageLineEdit->text();
850 	weight_sscanf(tmp, "%lf", &weight);
851 	if(percentageLineEdit->isEnabled() && (weight<0.0 || weight>100.0)){
852 		QMessageBox::warning(this, tr("FET information"),
853 			tr("Invalid weight (percentage) for added constraint min days between activities"));
854 		return;
855 	}
856 
857 	//subject
858 	QString subject_name=subjectsComboBox->currentText();
859 	bool found=subjectNamesSet.contains(subject_name);
860 	/*int subject_index=gt.rules.searchSubject(subject_name);
861 	if(subject_index<0){*/
862 	if(!found){
863 		QMessageBox::warning(this, tr("FET warning"),
864 			tr("Invalid subject"));
865 		return;
866 	}
867 
868 	QStringList activity_tags_names;
869 	for(int i=0; i<selectedActivityTagsListWidget->count(); i++){
870 		//assert(gt.rules.searchActivityTag(selectedActivityTagsListWidget->item(i)->text())>=0);
871 		assert(activityTagNamesSet.contains(selectedActivityTagsListWidget->item(i)->text()));
872 		activity_tags_names.append(selectedActivityTagsListWidget->item(i)->text());
873 	}
874 
875 	//teachers
876 	QStringList teachers_names;
877 	if(selectedTeachersListWidget->count()<=0){
878 		int t=QMessageBox::question(this, tr("FET question"),
879 		 tr("Do you really want to add an activity without teacher(s)?"),
880 		 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
881 
882 		if(t==QMessageBox::No)
883 			return;
884 	}
885 	else{
886 		for(int i=0; i<selectedTeachersListWidget->count(); i++){
887 			//assert(gt.rules.searchTeacher(selectedTeachersListWidget->item(i)->text())>=0);
888 			assert(teacherNamesSet.contains(selectedTeachersListWidget->item(i)->text()));
889 			teachers_names.append(selectedTeachersListWidget->item(i)->text());
890 		}
891 	}
892 
893 	//students
894 	//int numberOfStudents=0;
895 	QStringList students_names;
896 	if(selectedStudentsListWidget->count()<=1){
897 		QMessageBox::warning(this, tr("FET question"),
898 		 tr("If you intend to add multiple activities for students, please select at least 2 students sets."));
899 
900 		return;
901 	}
902 	else{
903 		for(int i=0; i<selectedStudentsListWidget->count(); i++){
904 			assert(gt.rules.permanentStudentsHash.contains(selectedStudentsListWidget->item(i)->text()));
905 			students_names.append(selectedStudentsListWidget->item(i)->text());
906 		}
907 	}
908 
909 	if(splitSpinBox->value()==1){ //indivisible activity
910 		int duration=dur(0)->value();
911 		if(duration<0){
912 			QMessageBox::warning(this, tr("FET information"),
913 				tr("Invalid duration"));
914 			return;
915 		}
916 
917 		bool active=false;
918 		if(activ(0)->isChecked())
919 			active=true;
920 
921 		int activityid=0; //We set the id of this newly added activity = (the largest existing id + 1)
922 		for(int i=0; i<gt.rules.activitiesList.size(); i++){
923 			Activity* act=gt.rules.activitiesList[i];
924 			if(act->id > activityid)
925 				activityid = act->id;
926 		}
927 
928 		int cnt_act=0;
929 		activityid++;
930 
931 		for(int st=0; st<students_names.count(); st++){
932 			int n1=nStudentsSpinBox->value();
933 			int n2;
934 			if(n1==-1)
935 				n2=gt.rules.permanentStudentsHash.value(students_names.at(st))->numberOfStudents;
936 			else
937 				n2=nStudentsSpinBox->value();
938 
939 			Activity a(gt.rules, activityid, 0, teachers_names, subject_name, activity_tags_names, QStringList(students_names.at(st)),
940 				duration, duration, /*parity,*/ active, (nStudentsSpinBox->value()==-1), n1, n2);
941 
942 			bool already_existing=false;
943 			for(int i=0; i<gt.rules.activitiesList.size(); i++){
944 				Activity* act=gt.rules.activitiesList[i];
945 				if((*act)==a)
946 					already_existing=true;
947 			}
948 
949 			if(already_existing){
950 				/*int t=QMessageBox::question(this, tr("FET question"),
951 					tr("A similar activity for students set %1 already exists. Do you want to insert current activity?").arg(students_names.at(st)),
952 					tr("Yes"),tr("No"));
953 				assert(t==0 || t==1 ||t==-1);
954 				if(t==1){ //no pressed
955 					continue;
956 				}
957 				if(t==-1){ //Esc pressed
958 					continue;
959 				}*/
960 
961 				QMessageBox::StandardButton t=QMessageBox::question(this, tr("FET question"),
962 					tr("A similar activity for students set %1 already exists. Do you want to insert current activity?").arg(students_names.at(st)),
963 					QMessageBox::Yes | QMessageBox::No);
964 				if(t==QMessageBox::No)
965 					return;
966 			}
967 
968 			bool tmp=gt.rules.addSimpleActivityFast(this, activityid, 0, teachers_names, subject_name, activity_tags_names,
969 				QStringList(students_names.at(st)), duration, duration, active,
970 				(nStudentsSpinBox->value()==-1), n1, n2);
971 			if(tmp){
972 				cnt_act++;
973 				activityid++;
974 			}
975 		}
976 		QMessageBox::information(this, tr("FET information"), tr("%1 activities added").arg(cnt_act));
977 	}
978 	else{ //split activity
979 		if(gt.rules.mode!=MORNINGS_AFTERNOONS){
980 			if(minDayDistanceSpinBox->value()>0 && splitSpinBox->value()>gt.rules.nDaysPerWeek){
981 				int t=LongTextMessageBox::largeConfirmation(this, tr("FET confirmation"),
982 				 tr("Possible incorrect setting. Are you sure you want to add current activity? See details below:")+"\n\n"+
983 				 tr("You want to add a container activity split into more than the number of days per week and also add a constraint min days between activities."
984 				  " This is a very bad practice from the way the algorithm of generation works (it slows down the generation and makes it harder to find a solution).")+
985 				 "\n\n"+
986 				 tr("The best way to add the activities would be:")+
987 				 "\n\n"+
988 				 tr("1. If you add 'force consecutive if same day', then couple extra activities in pairs to obtain a number of activities equal to the number of days per week"
989 				  ". Example: 7 activities with duration 1 in a 5 days week, then transform into 5 activities with durations: 2,2,1,1,1 and add a single container activity with these 5 components"
990 				  " (possibly raising the weight of added constraint min days between activities up to 100%)")+
991 				  "\n\n"+
992 				 tr("2. If you don't add 'force consecutive if same day', then add a larger activity split into a number of"
993 				  " activities equal with the number of days per week and the remaining components into other larger split activity."
994 				  " For example, suppose you need to add 7 activities with duration 1 in a 5 days week. Add 2 larger container activities,"
995 				  " first one split into 5 activities with duration 1 and second one split into 2 activities with duration 1"
996 				  " (possibly raising the weight of added constraints min days between activities for each of the 2 containers up to 100%)")+
997 			  	 "\n\n"+
998 				 tr("Do you want to add current activities as they are now (not recommended) or cancel and edit them as instructed?")
999 				  ,
1000 				 tr("Yes"), tr("No"), QString(), 0, 1);
1001 
1002 				if(t==1)
1003 					return;
1004 			}
1005 		}
1006 		else{
1007 			if(minDayDistanceSpinBox->value()>0 && splitSpinBox->value()>gt.rules.nDaysPerWeek/2){
1008 				int t=LongTextMessageBox::largeConfirmation(this, tr("FET confirmation"),
1009 				 tr("Possible incorrect setting. Are you sure you want to add current activity? See details below:")+"\n\n"+
1010 				 tr("You want to add a container activity split into more than the number of real days per week and also add a constraint min days between activities."
1011 				  " This is a very bad practice from the way the algorithm of generation works (it slows down the generation and makes it harder to find a solution).")+
1012 				 "\n\n"+
1013 				 tr("The best way to add the activities would be:")+
1014 				 "\n\n"+
1015 				 tr("1. If you add 'force consecutive if same day', then couple extra activities in pairs to obtain a number of activities equal to the number of days per week"
1016 				  ". Example: 7 activities with duration 1 in a 5 real days week, then transform into 5 activities with durations: 2,2,1,1,1 and add a single container activity with these 5 components"
1017 				  " (possibly raising the weight of added constraint min days between activities up to 100%)")+
1018 				  "\n\n"+
1019 				 tr("2. If you don't add 'force consecutive if same day', then add a larger activity split into a number of"
1020 				  " activities equal with the number of days per week and the remaining components into other larger split activity."
1021 				  " For example, suppose you need to add 7 activities with duration 1 in a 5 real days week. Add 2 larger container activities,"
1022 				  " first one split into 5 activities with duration 1 and second one split into 2 activities with duration 1"
1023 				  " (possibly raising the weight of added constraints min days between activities for each of the 2 containers up to 100%)")+
1024 			  	 "\n\n"+
1025 				 tr("Do you want to add current activities as they are now (not recommended) or cancel and edit them as instructed?")
1026 				  ,
1027 				 tr("Yes"), tr("No"), QString(), 0, 1);
1028 
1029 				if(t==1)
1030 					return;
1031 			}
1032 		}
1033 
1034 		int totalduration;
1035 		QList<int> durations;
1036 		QList<bool> active;
1037 		int nsplit=splitSpinBox->value();
1038 
1039 		totalduration=0;
1040 		for(int i=0; i<nsplit; i++){
1041 			durations.append(dur(i)->value());
1042 			active.append(false);
1043 			if(activ(i)->isChecked())
1044 				active[i]=true;
1045 
1046 			totalduration+=durations[i];
1047 		}
1048 
1049 		//the group id of this split activity and the id of the first partial activity
1050 		//it is the maximum already existing id + 1
1051 		int firstactivityid=0;
1052 		for(int i=0; i<gt.rules.activitiesList.size(); i++){
1053 			Activity* act=gt.rules.activitiesList[i];
1054 			if(act->id > firstactivityid)
1055 				firstactivityid = act->id;
1056 		}
1057 		firstactivityid++;
1058 
1059 		int cnt_act=0;
1060 
1061 		bool begin=true;
1062 		bool addSecondConstraint=false;
1063 		double secondWeight=-1.0;
1064 
1065 		for(int st=0; st<students_names.count(); st++){
1066 			int n1=nStudentsSpinBox->value();
1067 			int n2;
1068 			if(n1==-1)
1069 				n2=gt.rules.permanentStudentsHash.value(students_names.at(st))->numberOfStudents;
1070 			else
1071 				n2=nStudentsSpinBox->value();
1072 
1073 			int minD=minDayDistanceSpinBox->value();
1074 			bool tmp=gt.rules.addSplitActivityFast(this, firstactivityid, firstactivityid,
1075 				teachers_names, subject_name, activity_tags_names, QStringList(students_names.at(st)),
1076 				nsplit, totalduration, durations,
1077 				active, minD, weight, forceConsecutiveCheckBox->isChecked(),
1078 				(nStudentsSpinBox->value()==-1), n1, n2);
1079 			if(tmp){
1080 				firstactivityid+=nsplit;
1081 				cnt_act++;
1082 			}
1083 
1084 			if(tmp && begin){
1085 				begin=false;
1086 				if(minD>1 && weight<100.0){
1087 					SecondMinDaysDialog second(this, minD, weight);
1088 					setParentAndOtherThings(&second, this);
1089 					int code=second.exec();
1090 
1091 					if(code==QDialog::Accepted){
1092 						addSecondConstraint=true;
1093 						assert(second.weight>=0 && second.weight<=100.0);
1094 						secondWeight=second.weight;
1095 						QList<int> acts;
1096 						for(int i=0; i<nsplit; i++){
1097 							acts.append(firstactivityid+i-nsplit);
1098 						}
1099 						TimeConstraint* c=new ConstraintMinDaysBetweenActivities(secondWeight, forceConsecutiveCheckBox->isChecked(), nsplit, acts, minD-1);
1100 						bool tmp=gt.rules.addTimeConstraint(c);
1101 						assert(tmp);
1102 					}
1103 					else
1104 						addSecondConstraint=false;
1105 				}
1106 			}
1107 			else if(tmp){
1108 				if(addSecondConstraint){
1109 					assert(secondWeight>=0 && secondWeight<=100.0);
1110 					QList<int> acts;
1111 					for(int i=0; i<nsplit; i++){
1112 						acts.append(firstactivityid+i-nsplit);
1113 					}
1114 					TimeConstraint* c=new ConstraintMinDaysBetweenActivities(secondWeight, forceConsecutiveCheckBox->isChecked(), nsplit, acts, minD-1);
1115 					bool tmp=gt.rules.addTimeConstraint(c);
1116 					assert(tmp);
1117 				}
1118 			}
1119 		}
1120 		QMessageBox::information(this, tr("FET information"), tr("%1 split activities added").arg(cnt_act));
1121 	}
1122 
1123 	PlanningChanged::increasePlanningCommunicationSpinBox();
1124 }
1125 
clearTeachers()1126 void AddActivityForm::clearTeachers()
1127 {
1128 	selectedTeachersListWidget->clear();
1129 }
1130 
clearStudents()1131 void AddActivityForm::clearStudents()
1132 {
1133 	selectedStudentsListWidget->clear();
1134 }
1135 
clearActivityTags()1136 void AddActivityForm::clearActivityTags()
1137 {
1138 	selectedActivityTagsListWidget->clear();
1139 }
1140 
help()1141 void AddActivityForm::help()
1142 {
1143 	QString s;
1144 
1145 	s+=tr("Abbreviations in this dialog:");
1146 	s+="\n\n";
1147 	s+=tr("'Students' (the text near the spin box), means 'Number of students (-1 for automatic)'");
1148 	s+="\n";
1149 	s+=tr("'Split' means 'Split into ... activities per week'");
1150 	s+="\n";
1151 	s+=tr("'Min days' means 'The minimum required distance in days between each pair of activities'");
1152 	s+="\n";
1153 	s+=tr("'Weight %' means 'Percentage of added constraint (min days between activities constraint). Recommended: 95.0%-100.0%'");
1154 	s+="\n";
1155 	s+=tr("'Consecutive' means 'If activities on same day, force consecutive?'");
1156 	s+="\n";
1157 	s+=tr("The 'Duration' spin box and the 'Active' check box refer to each component of current activity, you can change "
1158 	 "them for each component, separately, by selecting the corresponding tab in the tab widget.");
1159 	s+="\n";
1160 	s+=tr("'Qualified' means that only the teachers who are qualified to teach the selected subject will be shown in the 'Teachers' list.",
1161 	 "Qualified refers to teachers");
1162 	s+="\n";
1163 	s+=tr("'Separately' means that multiple activities will be added, one for each selected students set, separately.");
1164 	s+="\n\n";
1165 
1166 	s+=tr("A first notice: "
1167 	 "If you use a 5 days week: "
1168 	 "when adding an activity split into only 2 components "
1169 	 "per week, the best practice is to add min days between activities to be 2. "
1170 	 "If you split an activity into 3 components per week - please read FAQ question Q1-5-September-2008");
1171 	s+="\n\n";
1172 
1173 	s+=tr("You can select a teacher from all the teachers with the mouse or with the keyboard tab/up/down, then "
1174 	 "double click it to add it to the selected teachers for current activity. "
1175 	 "You can then choose to remove a teacher from the selected teachers. You can highlight it "
1176 	 "with the mouse or with the keyboard, then double click it to remove this teacher from the selected teachers.");
1177 
1178 	s+="\n\n";
1179 
1180 	s+=tr("The same procedure (double click) applies to students sets and activity tags.");
1181 
1182 	s+="\n\n";
1183 
1184 	s+=tr("You can check/uncheck show years, show groups or show subgroups.");
1185 	s+="\n\n";
1186 
1187 	 s+=tr("If you split a larger activity into more activities per week, you have a multitude of choices:\n"
1188 	 "You can choose the minimum distance in days between each pair of activities."
1189 	 " Please note that a minimum distance of 1 means that the activities must not be in the same day, "
1190 	 "a minimum distance of 2 means that the activities must be separated by one day (distance from Monday"
1191 	 " to Wednesday for instance is 2 days), etc.");
1192 
1193 	s+="\n\n";
1194 
1195 	 s+=tr("If you have for instance an activity with 2 lessons per week and you want to spread them to at "
1196 	 "least 2 days distance, you can add a constraint min days with min days = 2 and weight 95% "
1197 	 "(or higher). If you want also to ensure that activities will "
1198 	 "be separated by at least one day, you can use this feature: "
1199 	 "add a constraint min days with minimum days 2 and weight 95% or lower, and after that you'll get "
1200 	 "the possibility to add another constraint with min 1 days and weight 95% or higher. "
1201 	 "It works if you first select in the dialog the min days >= 2 and click Add activities. Or you can add manually the constraints "
1202 	 "(difficult this way). "
1203 	 "Important: it is best practice to consider both constraints to have 95% weight. The combination assures that "
1204 	 "the resultant is 99.75% weight");
1205 
1206 	s+="\n\n";
1207 
1208 	s+=tr("Please note that the min days distance is a time constraint and you can only see/modify it in the "
1209 	 "time constraints dialogs, not in the modify activity dialog. Additionally, you can see the constraints "
1210 	 "for each activity in the details text box of each activity");
1211 
1212 	s+="\n\n";
1213 
1214 	 s+=tr("If you choose a value greater or equal with 1 for min days, a time constraint min days between activities will be added automatically "
1215 	 "(you can see this constraint in the time constraints list or you can see this constraint in the "
1216 	 "detailed description of the activity). You can select a weight percentage for this constraint. "
1217 	 "If you select 100%, the constraint must be respected all the time. If you select 95%, there is a small chance "
1218 	 "that the timetable will not respect this constraint. Recommended values are 95.0%-100.0% (maybe you could try "
1219 	 "with 95%, then 99.75%, or even 100.0%, but the generation time might be larger). Generally, 99.75% might be a good value. "
1220 	 "Note: if you put a value less than 100% and the constraint is too tough, FET is able to find that this constraint "
1221 	 "is impossible and will break it. 99.75% might be better than 95% but possibly slower. The percentage is subjective "
1222 	 "(if you put 95% you may get 6 soft conflicts and if you put 99.75% you may get 3 soft conflicts). "
1223 	 "Starting with FET-5.3.6, it is possible to change this value for all constraints in one click, in constraint min days"
1224 	 " between activities dialog.");
1225 
1226 	s+="\n\n";
1227 
1228 	s+=tr("There is another option, if the activities are in the same day, force consecutive activities. You can select "
1229 	 "this option for instance if you have 5 lessons of math in 5 days, and there is no timetable which respects "
1230 	 "fully the days separation. Then, you can set the weight percent of the min days constraint to 95% and "
1231 	 "add consecutive if same day. You will have as results say 3 lessons with duration 1 and a 2 hours lesson in another day. "
1232 	 "Please be careful: if the activities are on the same day, even if the constraint has 0% weight, then the activities are forced to be "
1233 	 "consecutive.");
1234 
1235 	s+="\n\n";
1236 
1237 	s+=tr("Current algorithm cannot schedule 3 activities in the same day if consecutive is checked, so "
1238 	 "you will get no solution in such extreme cases (for instance, if you have 3 lessons and a teacher which works only 1 day per week, "
1239 	 "and select 'force consecutive if same day', you will get an imposssible timetable. But these are extremely unlikely cases).");
1240 
1241 	s+="\n\n";
1242 
1243 	s+=tr("Note: You cannot add 'consecutive if same day' with min days=0. If you want this, you have to add "
1244 	 "min days at least 1 (and any weight percentage).");
1245 
1246 	s+="\n\n";
1247 
1248 	s+=tr("Starting with version 5.0.0, it is possible to add activities with no students or no teachers");
1249 
1250 	s+="\n\n";
1251 
1252 	s+=tr("If you select a number of min days above 1 (say this number is n), you will get the possibility "
1253 	 "to add a second constraint min days between activities, with min days = n-1 and a percentage of your choice. Just click "
1254 	 "Add activities");
1255 
1256 	//show the message in a dialog
1257 	QDialog dialog(this);
1258 
1259 	dialog.setWindowTitle(tr("FET - help on adding activity(ies)"));
1260 
1261 	QVBoxLayout* vl=new QVBoxLayout(&dialog);
1262 	QPlainTextEdit* te=new QPlainTextEdit();
1263 	te->setPlainText(s);
1264 	te->setReadOnly(true);
1265 	QPushButton* pb=new QPushButton(tr("OK"));
1266 
1267 	QHBoxLayout* hl=new QHBoxLayout(0);
1268 	hl->addStretch(1);
1269 	hl->addWidget(pb);
1270 
1271 	vl->addWidget(te);
1272 	vl->addLayout(hl);
1273 	connect(pb, SIGNAL(clicked()), &dialog, SLOT(close()));
1274 
1275 	dialog.resize(700,500);
1276 	centerWidgetOnScreen(&dialog);
1277 
1278 	setParentAndOtherThings(&dialog, this);
1279 	dialog.exec();
1280 }
1281 
minDaysChanged()1282 void AddActivityForm::minDaysChanged()
1283 {
1284 	percentageTextLabel->setEnabled(splitSpinBox->value()>=2 && minDayDistanceSpinBox->value()>0);
1285 	percentageLineEdit->setEnabled(splitSpinBox->value()>=2 && minDayDistanceSpinBox->value()>0);
1286 	forceConsecutiveCheckBox->setEnabled(splitSpinBox->value()>=2 && minDayDistanceSpinBox->value()>0);
1287 }
1288