1 /***************************************************************************
2                           timetableviewstudentstimehorizontalform.cpp  -  description
3                              -------------------
4     begin                : 2017
5     copyright            : (C) 2017 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 <Qt>
19 
20 #include <QtGlobal>
21 
22 #include "longtextmessagebox.h"
23 
24 #include "fetmainform.h"
25 #include "timetableviewstudentstimehorizontalform.h"
26 #include "timetable_defs.h"
27 #include "timetable.h"
28 #include "solution.h"
29 
30 #include "fet.h"
31 
32 #include "matrix.h"
33 
34 #include "lockunlock.h"
35 
36 #include <QMessageBox>
37 
38 #include <QTableWidget>
39 #include <QTableWidgetItem>
40 #include <QHeaderView>
41 
42 #include <QAbstractItemView>
43 
44 #include <QListWidget>
45 
46 #include <QList>
47 
48 #include <QCoreApplication>
49 #include <QApplication>
50 
51 #include <QString>
52 #include <QStringList>
53 
54 #include <QSplitter>
55 #include <QSettings>
56 #include <QObject>
57 #include <QMetaObject>
58 
59 //begin by Marco Vassura
60 #include <QBrush>
61 #include <QColor>
62 //end by Marco Vassura
63 
64 #include <QHash>
65 #include <QSet>
66 #include <QPair>
67 
68 extern const QString COMPANY;
69 extern const QString PROGRAM;
70 
71 extern bool students_schedule_ready;
72 extern bool teachers_schedule_ready;
73 
74 extern Solution best_solution;
75 
76 extern bool simulation_running;
77 extern bool simulation_running_multi;
78 
79 //extern Matrix3D<bool> subgroupNotAvailableDayHour;
80 extern Matrix2D<bool> breakDayHour;
81 extern QHash<QString, QSet<QPair<int, int>>> studentsSetNotAvailableDayHour;
82 
83 extern QSet<int> idsOfLockedTime;		//care about locked activities in view forms
84 extern QSet<int> idsOfLockedSpace;		//care about locked activities in view forms
85 extern QSet<int> idsOfPermanentlyLockedTime;	//care about locked activities in view forms
86 extern QSet<int> idsOfPermanentlyLockedSpace;	//care about locked activities in view forms
87 
88 extern CommunicationSpinBox communicationSpinBox;	//small hint to sync the forms
89 
90 extern const int MINIMUM_WIDTH_SPIN_BOX_VALUE; //trick found on the internet, so that these two constants are visible in timetableviewteacherstimehorizontalform.cpp
91 extern const int MINIMUM_HEIGHT_SPIN_BOX_VALUE;
92 const int MINIMUM_WIDTH_SPIN_BOX_VALUE=9;
93 const int MINIMUM_HEIGHT_SPIN_BOX_VALUE=9;
94 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const95 void TimetableViewStudentsTimeHorizontalDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
96 {
97 	QStyledItemDelegate::paint(painter, option, index);
98 
99 	//int day=index.column()/gt.rules.nHoursPerDay;
100 	//int hour=index.column()%gt.rules.nHoursPerDay;
101 	int hour=index.column()%nColumns;
102 
103 	/*if(day>=0 && day<gt.rules.nDaysPerWeek-1 && hour==gt.rules.nHoursPerDay-1){
104 		QPen pen(painter->pen());
105 		pen.setWidth(2);
106 		painter->setPen(pen);
107 		painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
108 	}*/
109 
110 	/*assert(table!=nullptr);
111 	QBrush bg(table->item(index.row(), index.column())->background());
112 	QPen pen(painter->pen());
113 
114 	double brightness = bg.color().redF()*0.299 + bg.color().greenF()*0.587 + bg.color().blueF()*0.114;
115 	if (brightness<0.5)
116 		pen.setColor(Qt::white);
117 	else
118 		pen.setColor(Qt::black);
119 
120 	painter->setPen(pen);*/
121 
122 	if(hour==0)
123 		painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft());
124 	if(hour==nColumns-1)
125 		painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
126 
127 	if(index.row()==0)
128 		painter->drawLine(option.rect.topLeft(), option.rect.topRight());
129 	if(index.row()==nRows-1)
130 		painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
131 }
132 
133 
TimetableViewStudentsTimeHorizontalForm(QWidget * parent)134 TimetableViewStudentsTimeHorizontalForm::TimetableViewStudentsTimeHorizontalForm(QWidget* parent): QDialog(parent)
135 {
136 	setupUi(this);
137 
138 	closePushButton->setDefault(true);
139 
140 	detailsTextEdit->setReadOnly(true);
141 
142 	//columnResizeModeInitialized=false;
143 
144 	verticalSplitter->setStretchFactor(0, 20);
145 	verticalSplitter->setStretchFactor(1, 1);
146 	horizontalSplitter->setStretchFactor(0, 5);
147 	horizontalSplitter->setStretchFactor(1, 1);
148 
149 	studentsTimetableTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
150 
151 	connect(closePushButton, SIGNAL(clicked()), this, SLOT(close()));
152 	connect(studentsTimetableTable, SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)), this, SLOT(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)));
153 	connect(lockTimePushButton, SIGNAL(clicked()), this, SLOT(lockTime()));
154 	connect(lockSpacePushButton, SIGNAL(clicked()), this, SLOT(lockSpace()));
155 	connect(lockTimeSpacePushButton, SIGNAL(clicked()), this, SLOT(lockTimeSpace()));
156 
157 	connect(helpPushButton, SIGNAL(clicked()), this, SLOT(help()));
158 
159 	lockRadioButton->setChecked(true);
160 	unlockRadioButton->setChecked(false);
161 	toggleRadioButton->setChecked(false);
162 
163 	centerWidgetOnScreen(this);
164 	restoreFETDialogGeometry(this);
165 
166 	//restore vertical splitter state
167 	QSettings settings(COMPANY, PROGRAM);
168 	if(settings.contains(this->metaObject()->className()+QString("/vertical-splitter-state")))
169 		verticalSplitter->restoreState(settings.value(this->metaObject()->className()+QString("/vertical-splitter-state")).toByteArray());
170 
171 	//restore horizontal splitter state
172 	//QSettings settings(COMPANY, PROGRAM);
173 	if(settings.contains(this->metaObject()->className()+QString("/horizontal-splitter-state")))
174 		horizontalSplitter->restoreState(settings.value(this->metaObject()->className()+QString("/horizontal-splitter-state")).toByteArray());
175 
176 	if(settings.contains(this->metaObject()->className()+QString("/lock-radio-button")))
177 		lockRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/lock-radio-button")).toBool());
178 	if(settings.contains(this->metaObject()->className()+QString("/unlock-radio-button")))
179 		unlockRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/unlock-radio-button")).toBool());
180 	if(settings.contains(this->metaObject()->className()+QString("/toggle-radio-button")))
181 		toggleRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/toggle-radio-button")).toBool());
182 
183 ///////////just for testing
184 	QSet<int> backupLockedTime;
185 	QSet<int> backupPermanentlyLockedTime;
186 	QSet<int> backupLockedSpace;
187 	QSet<int> backupPermanentlyLockedSpace;
188 
189 	backupLockedTime=idsOfLockedTime;
190 	backupPermanentlyLockedTime=idsOfPermanentlyLockedTime;
191 	backupLockedSpace=idsOfLockedSpace;
192 	backupPermanentlyLockedSpace=idsOfPermanentlyLockedSpace;
193 
194 	//added by Volker Dirr
195 	//these 2 lines are not really needed - just to be safer
196 	LockUnlock::computeLockedUnlockedActivitiesTimeSpace();
197 
198 	assert(backupLockedTime==idsOfLockedTime);
199 	assert(backupPermanentlyLockedTime==idsOfPermanentlyLockedTime);
200 	assert(backupLockedSpace==idsOfLockedSpace);
201 	assert(backupPermanentlyLockedSpace==idsOfPermanentlyLockedSpace);
202 ///////////
203 
204 	/*if(gt.rules.nInternalTeachers!=gt.rules.teachersList.count()){
205 		QMessageBox::warning(this, tr("FET warning"), tr("Cannot display the timetable, because you added or removed some teachers. Please regenerate the timetable and then view it"));
206 		return;
207 	}*/
208 
209 	//Commented on 2018-07-20
210 	//LockUnlock::increaseCommunicationSpinBox();
211 
212 	usedStudentsSet.clear();
213 	for(int i=0; i<gt.rules.nInternalActivities; i++){
214 		Activity* act=&gt.rules.internalActivitiesList[i];
215 		for(const QString& students : qAsConst(act->studentsNames)){
216 			if(!usedStudentsSet.contains(students))
217 				usedStudentsSet.insert(students);
218 		}
219 	}
220 
221 	QHash<QString, QSet<QPair<int, int>>>::const_iterator it=studentsSetNotAvailableDayHour.constBegin();
222 	while(it!=studentsSetNotAvailableDayHour.constEnd()){
223 		if(!usedStudentsSet.contains(it.key()))
224 			usedStudentsSet.insert(it.key());
225 
226 		it++;
227 	}
228 
229 	QSet<QString> studentsSet2;
230 	usedStudentsList.clear();
231 	for(StudentsYear* year : qAsConst(gt.rules.augmentedYearsList)){
232 		if(usedStudentsSet.contains(year->name) && !studentsSet2.contains(year->name)){
233 			usedStudentsList.append(year->name);
234 			studentsSet2.insert(year->name);
235 		}
236 		for(StudentsGroup* group : qAsConst(year->groupsList)){
237 			if(usedStudentsSet.contains(group->name) && !studentsSet2.contains(group->name)){
238 				usedStudentsList.append(group->name);
239 				studentsSet2.insert(group->name);
240 			}
241 			for(StudentsSubgroup* subgroup : qAsConst(group->subgroupsList)){
242 				if(usedStudentsSet.contains(subgroup->name) && !studentsSet2.contains(subgroup->name)){
243 					usedStudentsList.append(subgroup->name);
244 					studentsSet2.insert(subgroup->name);
245 				}
246 			}
247 		}
248 	}
249 
250 	studentsTimetableTable->setRowCount(usedStudentsList.count());
251 	studentsTimetableTable->setColumnCount(gt.rules.nDaysPerWeek*gt.rules.nHoursPerDay);
252 
253 	oldItemDelegate=studentsTimetableTable->itemDelegate();
254 	newItemDelegate=new TimetableViewStudentsTimeHorizontalDelegate(nullptr, studentsTimetableTable->rowCount(), gt.rules.nHoursPerDay);
255 	studentsTimetableTable->setItemDelegate(newItemDelegate);
256 
257 	bool min2letters=false;
258 	for(int d=0; d<gt.rules.nDaysPerWeek; d++){
259 		if(gt.rules.daysOfTheWeek[d].size()>gt.rules.nHoursPerDay){
260 			min2letters=true;
261 			break;
262 		}
263 	}
264 	for(int d=0; d<gt.rules.nDaysPerWeek; d++){
265 		QString dayName=gt.rules.daysOfTheWeek[d];
266 		int t=dayName.size();
267 		int q=t/gt.rules.nHoursPerDay;
268 		int r=t%gt.rules.nHoursPerDay;
269 		QStringList list;
270 
271 		if(q==0)
272 			q=1;
273 
274 		for(int i=0; i<gt.rules.nHoursPerDay; i++){
275 			if(!min2letters){
276 				list.append(dayName.left(1));
277 				dayName.remove(0, 1);
278 			}
279 			else if(i<r || q<=1){
280 				assert(q>=1);
281 				list.append(dayName.left(q+1));
282 				dayName.remove(0, q+1);
283 			}
284 			else{
285 				list.append(dayName.left(q));
286 				dayName.remove(0, q);
287 			}
288 		}
289 
290 		for(int h=0; h<gt.rules.nHoursPerDay; h++){
291 			QTableWidgetItem* item=new QTableWidgetItem(list.at(h)+"\n"+gt.rules.hoursOfTheDay[h]);
292 			item->setToolTip(gt.rules.daysOfTheWeek[d]+"\n"+gt.rules.hoursOfTheDay[h]);
293 			studentsTimetableTable->setHorizontalHeaderItem(d*gt.rules.nHoursPerDay+h, item);
294 		}
295 	}
296 	for(int t=0; t<usedStudentsList.count(); t++){
297 		QTableWidgetItem* item=new QTableWidgetItem(usedStudentsList.at(t));
298 		item->setToolTip(usedStudentsList.at(t));
299 		studentsTimetableTable->setVerticalHeaderItem(t, item);
300 	}
301 
302 	for(int t=0; t<usedStudentsList.count(); t++){
303 		for(int d=0; d<gt.rules.nDaysPerWeek; d++){
304 			for(int h=0; h<gt.rules.nHoursPerDay; h++){
305 				QTableWidgetItem* item= new QTableWidgetItem();
306 				item->setTextAlignment(Qt::AlignCenter);
307 				item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
308 
309 				studentsTimetableTable->setItem(t, d*gt.rules.nHoursPerDay+h, item);
310 			}
311 		}
312 	}
313 
314 	//resize columns
315 	//if(!columnResizeModeInitialized){
316 //	teachersTimetableTable->horizontalHeader()->setMinimumSectionSize(teachersTimetableTable->horizontalHeader()->defaultSectionSize());
317 	//	columnResizeModeInitialized=true;
318 
319 	initialRecommendedHeight=studentsTimetableTable->verticalHeader()->sectionSizeHint(0);
320 
321 	int h;
322 	int w;
323 
324 	if(settings.contains(this->metaObject()->className()+QString("/vertical-header-size"))){
325 		h=settings.value(this->metaObject()->className()+QString("/vertical-header-size")).toInt();
326 		if(h==0)
327 			h=MINIMUM_HEIGHT_SPIN_BOX_VALUE;
328 	}
329 	else{
330 		h=MINIMUM_HEIGHT_SPIN_BOX_VALUE;
331 	}
332 //	if(h==0)
333 //		h=initialRecommendedHeight;
334 
335 	if(settings.contains(this->metaObject()->className()+QString("/horizontal-header-size"))){
336 		w=settings.value(this->metaObject()->className()+QString("/horizontal-header-size")).toInt();
337 		if(w==0)
338 			w=MINIMUM_WIDTH_SPIN_BOX_VALUE;
339 	}
340 	else{
341 		w=MINIMUM_WIDTH_SPIN_BOX_VALUE;
342 	}
343 //	if(w==0)
344 //		w=2*initialRecommendedHeight;
345 
346 	widthSpinBox->setSuffix(QString(" ")+tr("px", "Abbreviation for pixels"));
347 	widthSpinBox->setMinimum(MINIMUM_WIDTH_SPIN_BOX_VALUE);
348 #if QT_VERSION >= QT_VERSION_CHECK(5,2,0)
349 	widthSpinBox->setMaximum(studentsTimetableTable->verticalHeader()->maximumSectionSize());
350 #else
351 	widthSpinBox->setMaximum(maxScreenWidth(this));
352 #endif
353 	widthSpinBox->setValue(w);
354 	widthSpinBox->setSpecialValueText(tr("Automatic"));
355 
356 	heightSpinBox->setSuffix(QString(" ")+tr("px", "Abbreviation for pixels"));
357 	heightSpinBox->setMinimum(MINIMUM_HEIGHT_SPIN_BOX_VALUE);
358 #if QT_VERSION >= QT_VERSION_CHECK(5,2,0)
359 	heightSpinBox->setMaximum(studentsTimetableTable->verticalHeader()->maximumSectionSize());
360 #else
361 	heightSpinBox->setMaximum(maxScreenWidth(this));
362 #endif
363 	heightSpinBox->setValue(h);
364 	heightSpinBox->setSpecialValueText(tr("Automatic"));
365 
366 	widthSpinBoxValueChanged();
367 	heightSpinBoxValueChanged();
368 
369 	connect(widthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(widthSpinBoxValueChanged()));
370 	connect(heightSpinBox, SIGNAL(valueChanged(int)), this, SLOT(heightSpinBoxValueChanged()));
371 
372 //	teachersTimetableTable->verticalHeader()->setDefaultSectionSize(h);
373 //	teachersTimetableTable->horizontalHeader()->setDefaultSectionSize(w);
374 
375 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
376 	studentsTimetableTable->verticalHeader()->setSectionResizeMode(QHeaderView::Interactive);
377 	studentsTimetableTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
378 #else
379 	studentsTimetableTable->verticalHeader()->setResizeMode(QHeaderView::Interactive);
380 	studentsTimetableTable->horizontalHeader()->setResizeMode(QHeaderView::Interactive);
381 #endif
382 	//}
383 	////////////////
384 
385 	subjectsCheckBox->setChecked(true);
386 	teachersCheckBox->setChecked(false);
387 
388 	if(settings.contains(this->metaObject()->className()+QString("/subjects-check-box-state"))){
389 		bool state=settings.value(this->metaObject()->className()+QString("/subjects-check-box-state")).toBool();
390 		subjectsCheckBox->setChecked(state);
391 	}
392 	if(settings.contains(this->metaObject()->className()+QString("/teachers-check-box-state"))){
393 		bool state=settings.value(this->metaObject()->className()+QString("/teachers-check-box-state")).toBool();
394 		teachersCheckBox->setChecked(state);
395 	}
396 
397 	connect(subjectsCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateStudentsTimetableTable()));
398 	connect(teachersCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateStudentsTimetableTable()));
399 
400 	//added by Volker Dirr
401 	connect(&communicationSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateStudentsTimetableTable()));
402 
403 	updateStudentsTimetableTable();
404 }
405 
newTimetableGenerated()406 void TimetableViewStudentsTimeHorizontalForm::newTimetableGenerated()
407 //Similar to the constructor
408 {
409 	//setupUi(this);
410 
411 	//closePushButton->setDefault(true);
412 
413 	//detailsTextEdit->setReadOnly(true);
414 
415 	//columnResizeModeInitialized=false;
416 
417 	/*verticalSplitter->setStretchFactor(0, 20);
418 	verticalSplitter->setStretchFactor(1, 1);
419 	horizontalSplitter->setStretchFactor(0, 5);
420 	horizontalSplitter->setStretchFactor(1, 1);
421 
422 	studentsTimetableTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
423 
424 	connect(closePushButton, SIGNAL(clicked()), this, SLOT(close()));
425 	connect(studentsTimetableTable, SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)), this, SLOT(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)));
426 	connect(lockTimePushButton, SIGNAL(clicked()), this, SLOT(lockTime()));
427 	connect(lockSpacePushButton, SIGNAL(clicked()), this, SLOT(lockSpace()));
428 	connect(lockTimeSpacePushButton, SIGNAL(clicked()), this, SLOT(lockTimeSpace()));
429 
430 	connect(helpPushButton, SIGNAL(clicked()), this, SLOT(help()));*/
431 
432 	/*lockRadioButton->setChecked(true);
433 	unlockRadioButton->setChecked(false);
434 	toggleRadioButton->setChecked(false);
435 
436 	centerWidgetOnScreen(this);
437 	restoreFETDialogGeometry(this);
438 
439 	//restore vertical splitter state
440 	QSettings settings(COMPANY, PROGRAM);
441 	if(settings.contains(this->metaObject()->className()+QString("/vertical-splitter-state")))
442 		verticalSplitter->restoreState(settings.value(this->metaObject()->className()+QString("/vertical-splitter-state")).toByteArray());
443 
444 	//restore horizontal splitter state
445 	//QSettings settings(COMPANY, PROGRAM);
446 	if(settings.contains(this->metaObject()->className()+QString("/horizontal-splitter-state")))
447 		horizontalSplitter->restoreState(settings.value(this->metaObject()->className()+QString("/horizontal-splitter-state")).toByteArray());
448 
449 	if(settings.contains(this->metaObject()->className()+QString("/lock-radio-button")))
450 		lockRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/lock-radio-button")).toBool());
451 	if(settings.contains(this->metaObject()->className()+QString("/unlock-radio-button")))
452 		unlockRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/unlock-radio-button")).toBool());
453 	if(settings.contains(this->metaObject()->className()+QString("/toggle-radio-button")))
454 		toggleRadioButton->setChecked(settings.value(this->metaObject()->className()+QString("/toggle-radio-button")).toBool());*/
455 
456 ///////////just for testing
457 	QSet<int> backupLockedTime;
458 	QSet<int> backupPermanentlyLockedTime;
459 	QSet<int> backupLockedSpace;
460 	QSet<int> backupPermanentlyLockedSpace;
461 
462 	backupLockedTime=idsOfLockedTime;
463 	backupPermanentlyLockedTime=idsOfPermanentlyLockedTime;
464 	backupLockedSpace=idsOfLockedSpace;
465 	backupPermanentlyLockedSpace=idsOfPermanentlyLockedSpace;
466 
467 	//added by Volker Dirr
468 	//these 2 lines are not really needed - just to be safer
469 	LockUnlock::computeLockedUnlockedActivitiesTimeSpace();
470 
471 	assert(backupLockedTime==idsOfLockedTime);
472 	assert(backupPermanentlyLockedTime==idsOfPermanentlyLockedTime);
473 	assert(backupLockedSpace==idsOfLockedSpace);
474 	assert(backupPermanentlyLockedSpace==idsOfPermanentlyLockedSpace);
475 ///////////
476 
477 	/*if(gt.rules.nInternalTeachers!=gt.rules.teachersList.count()){
478 		QMessageBox::warning(this, tr("FET warning"), tr("Cannot display the timetable, because you added or removed some teachers. Please regenerate the timetable and then view it"));
479 		return;
480 	}*/
481 
482 	//DON'T UNCOMMENT THIS CODE -> LEADS TO CRASH IF THERE ARE MORE VIEWS OPENED.
483 	//LockUnlock::increaseCommunicationSpinBox();
484 
485 	usedStudentsSet.clear();
486 	for(int i=0; i<gt.rules.nInternalActivities; i++){
487 		Activity* act=&gt.rules.internalActivitiesList[i];
488 		for(const QString& students : qAsConst(act->studentsNames)){
489 			if(!usedStudentsSet.contains(students))
490 				usedStudentsSet.insert(students);
491 		}
492 	}
493 
494 	QHash<QString, QSet<QPair<int, int>>>::const_iterator it=studentsSetNotAvailableDayHour.constBegin();
495 	while(it!=studentsSetNotAvailableDayHour.constEnd()){
496 		if(!usedStudentsSet.contains(it.key()))
497 			usedStudentsSet.insert(it.key());
498 
499 		it++;
500 	}
501 
502 	QSet<QString> studentsSet2;
503 	usedStudentsList.clear();
504 	for(StudentsYear* year : qAsConst(gt.rules.augmentedYearsList)){
505 		if(usedStudentsSet.contains(year->name) && !studentsSet2.contains(year->name)){
506 			usedStudentsList.append(year->name);
507 			studentsSet2.insert(year->name);
508 		}
509 		for(StudentsGroup* group : qAsConst(year->groupsList)){
510 			if(usedStudentsSet.contains(group->name) && !studentsSet2.contains(group->name)){
511 				usedStudentsList.append(group->name);
512 				studentsSet2.insert(group->name);
513 			}
514 			for(StudentsSubgroup* subgroup : qAsConst(group->subgroupsList)){
515 				if(usedStudentsSet.contains(subgroup->name) && !studentsSet2.contains(subgroup->name)){
516 					usedStudentsList.append(subgroup->name);
517 					studentsSet2.insert(subgroup->name);
518 				}
519 			}
520 		}
521 	}
522 
523 	studentsTimetableTable->clear();
524 	studentsTimetableTable->setRowCount(usedStudentsList.count());
525 	studentsTimetableTable->setColumnCount(gt.rules.nDaysPerWeek*gt.rules.nHoursPerDay);
526 
527 	newItemDelegate->nRows=studentsTimetableTable->rowCount();
528 	newItemDelegate->nColumns=gt.rules.nHoursPerDay;
529 	/*studentsTimetableTable->setItemDelegate(oldItemDelegate);
530 	delete newItemDelegate;
531 	//oldItemDelegate=studentsTimetableTable->itemDelegate();
532 	newItemDelegate=new TimetableViewStudentsTimeHorizontalDelegate(nullptr, studentsTimetableTable->rowCount(), gt.rules.nHoursPerDay);
533 	studentsTimetableTable->setItemDelegate(newItemDelegate);*/
534 
535 	bool min2letters=false;
536 	for(int d=0; d<gt.rules.nDaysPerWeek; d++){
537 		if(gt.rules.daysOfTheWeek[d].size()>gt.rules.nHoursPerDay){
538 			min2letters=true;
539 			break;
540 		}
541 	}
542 	for(int d=0; d<gt.rules.nDaysPerWeek; d++){
543 		QString dayName=gt.rules.daysOfTheWeek[d];
544 		int t=dayName.size();
545 		int q=t/gt.rules.nHoursPerDay;
546 		int r=t%gt.rules.nHoursPerDay;
547 		QStringList list;
548 
549 		if(q==0)
550 			q=1;
551 
552 		for(int i=0; i<gt.rules.nHoursPerDay; i++){
553 			if(!min2letters){
554 				list.append(dayName.left(1));
555 				dayName.remove(0, 1);
556 			}
557 			else if(i<r || q<=1){
558 				assert(q>=1);
559 				list.append(dayName.left(q+1));
560 				dayName.remove(0, q+1);
561 			}
562 			else{
563 				list.append(dayName.left(q));
564 				dayName.remove(0, q);
565 			}
566 		}
567 
568 		for(int h=0; h<gt.rules.nHoursPerDay; h++){
569 			QTableWidgetItem* item=new QTableWidgetItem(list.at(h)+"\n"+gt.rules.hoursOfTheDay[h]);
570 			item->setToolTip(gt.rules.daysOfTheWeek[d]+"\n"+gt.rules.hoursOfTheDay[h]);
571 			studentsTimetableTable->setHorizontalHeaderItem(d*gt.rules.nHoursPerDay+h, item);
572 		}
573 	}
574 	for(int t=0; t<usedStudentsList.count(); t++){
575 		QTableWidgetItem* item=new QTableWidgetItem(usedStudentsList.at(t));
576 		item->setToolTip(usedStudentsList.at(t));
577 		studentsTimetableTable->setVerticalHeaderItem(t, item);
578 	}
579 
580 	for(int t=0; t<usedStudentsList.count(); t++){
581 		for(int d=0; d<gt.rules.nDaysPerWeek; d++){
582 			for(int h=0; h<gt.rules.nHoursPerDay; h++){
583 				QTableWidgetItem* item= new QTableWidgetItem();
584 				item->setTextAlignment(Qt::AlignCenter);
585 				item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
586 
587 				studentsTimetableTable->setItem(t, d*gt.rules.nHoursPerDay+h, item);
588 			}
589 		}
590 	}
591 
592 	//resize columns
593 	//if(!columnResizeModeInitialized){
594 //	teachersTimetableTable->horizontalHeader()->setMinimumSectionSize(teachersTimetableTable->horizontalHeader()->defaultSectionSize());
595 	//	columnResizeModeInitialized=true;
596 
597 	/*initialRecommendedHeight=studentsTimetableTable->verticalHeader()->sectionSizeHint(0);
598 
599 	int h;
600 	int w;
601 
602 	if(settings.contains(this->metaObject()->className()+QString("/vertical-header-size"))){
603 		h=settings.value(this->metaObject()->className()+QString("/vertical-header-size")).toInt();
604 		if(h==0)
605 			h=MINIMUM_HEIGHT_SPIN_BOX_VALUE;
606 	}
607 	else{
608 		h=MINIMUM_HEIGHT_SPIN_BOX_VALUE;
609 	}
610 //	if(h==0)
611 //		h=initialRecommendedHeight;
612 
613 	if(settings.contains(this->metaObject()->className()+QString("/horizontal-header-size"))){
614 		w=settings.value(this->metaObject()->className()+QString("/horizontal-header-size")).toInt();
615 		if(w==0)
616 			w=MINIMUM_WIDTH_SPIN_BOX_VALUE;
617 	}
618 	else{
619 		w=MINIMUM_WIDTH_SPIN_BOX_VALUE;
620 	}
621 //	if(w==0)
622 //		w=2*initialRecommendedHeight;
623 
624 	widthSpinBox->setSuffix(QString(" ")+tr("px", "Abbreviation for pixels"));
625 	widthSpinBox->setMinimum(MINIMUM_WIDTH_SPIN_BOX_VALUE);
626 #if QT_VERSION >= QT_VERSION_CHECK(5,2,0)
627 	widthSpinBox->setMaximum(studentsTimetableTable->verticalHeader()->maximumSectionSize());
628 #else
629 	widthSpinBox->setMaximum(maxScreenWidth(this));
630 #endif
631 	widthSpinBox->setValue(w);
632 	widthSpinBox->setSpecialValueText(tr("Automatic"));
633 
634 	heightSpinBox->setSuffix(QString(" ")+tr("px", "Abbreviation for pixels"));
635 	heightSpinBox->setMinimum(MINIMUM_HEIGHT_SPIN_BOX_VALUE);
636 #if QT_VERSION >= QT_VERSION_CHECK(5,2,0)
637 	heightSpinBox->setMaximum(studentsTimetableTable->verticalHeader()->maximumSectionSize());
638 #else
639 	heightSpinBox->setMaximum(maxScreenWidth(this));
640 #endif
641 	heightSpinBox->setValue(h);
642 	heightSpinBox->setSpecialValueText(tr("Automatic"));
643 
644 	widthSpinBoxValueChanged();
645 	heightSpinBoxValueChanged();
646 
647 	connect(widthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(widthSpinBoxValueChanged()));
648 	connect(heightSpinBox, SIGNAL(valueChanged(int)), this, SLOT(heightSpinBoxValueChanged()));
649 
650 //	teachersTimetableTable->verticalHeader()->setDefaultSectionSize(h);
651 //	teachersTimetableTable->horizontalHeader()->setDefaultSectionSize(w);
652 
653 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
654 	studentsTimetableTable->verticalHeader()->setSectionResizeMode(QHeaderView::Interactive);
655 	studentsTimetableTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
656 #else
657 	studentsTimetableTable->verticalHeader()->setResizeMode(QHeaderView::Interactive);
658 	studentsTimetableTable->horizontalHeader()->setResizeMode(QHeaderView::Interactive);
659 #endif
660 	//}
661 	////////////////
662 
663 	subjectsCheckBox->setChecked(true);
664 	teachersCheckBox->setChecked(false);
665 
666 	if(settings.contains(this->metaObject()->className()+QString("/subjects-check-box-state"))){
667 		bool state=settings.value(this->metaObject()->className()+QString("/subjects-check-box-state")).toBool();
668 		subjectsCheckBox->setChecked(state);
669 	}
670 	if(settings.contains(this->metaObject()->className()+QString("/teachers-check-box-state"))){
671 		bool state=settings.value(this->metaObject()->className()+QString("/teachers-check-box-state")).toBool();
672 		teachersCheckBox->setChecked(state);
673 	}
674 
675 	connect(subjectsCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateStudentsTimetableTable()));
676 	connect(teachersCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateStudentsTimetableTable()));*/
677 
678 	//added by Volker Dirr
679 	//connect(&communicationSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateStudentsTimetableTable()));
680 
681 	updateStudentsTimetableTable();
682 }
683 
~TimetableViewStudentsTimeHorizontalForm()684 TimetableViewStudentsTimeHorizontalForm::~TimetableViewStudentsTimeHorizontalForm()
685 {
686 	saveFETDialogGeometry(this);
687 
688 	//save vertical splitter state
689 	QSettings settings(COMPANY, PROGRAM);
690 	settings.setValue(this->metaObject()->className()+QString("/vertical-splitter-state"), verticalSplitter->saveState());
691 
692 	//save horizontal splitter state
693 	//QSettings settings(COMPANY, PROGRAM);
694 	settings.setValue(this->metaObject()->className()+QString("/horizontal-splitter-state"), horizontalSplitter->saveState());
695 
696 	settings.setValue(this->metaObject()->className()+QString("/lock-radio-button"), lockRadioButton->isChecked());
697 	settings.setValue(this->metaObject()->className()+QString("/unlock-radio-button"), unlockRadioButton->isChecked());
698 	settings.setValue(this->metaObject()->className()+QString("/toggle-radio-button"), toggleRadioButton->isChecked());
699 
700 	if(heightSpinBox->value()<=MINIMUM_HEIGHT_SPIN_BOX_VALUE)
701 		settings.setValue(this->metaObject()->className()+QString("/vertical-header-size"), 0);
702 	else
703 		settings.setValue(this->metaObject()->className()+QString("/vertical-header-size"), heightSpinBox->value());
704 
705 	if(widthSpinBox->value()<=MINIMUM_WIDTH_SPIN_BOX_VALUE)
706 		settings.setValue(this->metaObject()->className()+QString("/horizontal-header-size"), 0);
707 	else
708 		settings.setValue(this->metaObject()->className()+QString("/horizontal-header-size"), widthSpinBox->value());
709 
710 	settings.setValue(this->metaObject()->className()+QString("/subjects-check-box-state"), subjectsCheckBox->isChecked());
711 	settings.setValue(this->metaObject()->className()+QString("/teachers-check-box-state"), teachersCheckBox->isChecked());
712 
713 	studentsTimetableTable->setItemDelegate(oldItemDelegate);
714 	delete newItemDelegate;
715 
716 	usedStudentsList.clear();
717 	usedStudentsSet.clear();
718 	//activitiesForStudentsSet.clear();
719 
720 	//notAvailableHash.clear();
721 }
722 
resizeRowsAfterShow()723 void TimetableViewStudentsTimeHorizontalForm::resizeRowsAfterShow()
724 {
725 //	studentsTimetableTable->resizeRowsToContents();
726 }
727 
updateStudentsTimetableTable()728 void TimetableViewStudentsTimeHorizontalForm::updateStudentsTimetableTable(){
729 	if(!(students_schedule_ready && teachers_schedule_ready)){
730 		QMessageBox::warning(this, tr("FET warning"), tr("Timetable not available in view students timetable dialog - please generate a new timetable "
731 		"or close the timetable view students dialog"));
732 		return;
733 	}
734 	assert(students_schedule_ready && teachers_schedule_ready);
735 
736 	if(gt.rules.nInternalRooms!=gt.rules.roomsList.count()){
737 		QMessageBox::warning(this, tr("FET warning"), tr("Cannot display the timetable, because you added or removed some rooms. Please regenerate the timetable and then view it"));
738 		return;
739 	}
740 
741 	assert(gt.rules.initialized);
742 
743 	for(int t=0; t<usedStudentsList.count(); t++){
744 		assert(t<studentsTimetableTable->rowCount());
745 
746 		if(!gt.rules.studentsHash.contains(usedStudentsList.at(t)))
747 			continue;
748 
749 		assert(gt.rules.studentsHash.contains(usedStudentsList.at(t)));
750 		StudentsSet* ss=gt.rules.studentsHash.value(usedStudentsList.at(t), nullptr);
751 		assert(ss!=nullptr);
752 		int sbg=-1;
753 		if(ss->type==STUDENTS_YEAR){
754 			StudentsYear* year=(StudentsYear*)ss;
755 			sbg=year->groupsList.at(0)->subgroupsList.at(0)->indexInInternalSubgroupsList;
756 		}
757 		else if(ss->type==STUDENTS_GROUP){
758 			StudentsGroup* group=(StudentsGroup*)ss;
759 			sbg=group->subgroupsList.at(0)->indexInInternalSubgroupsList;
760 		}
761 		else if(ss->type==STUDENTS_SUBGROUP){
762 			StudentsSubgroup* subgroup=(StudentsSubgroup*)ss;
763 			sbg=subgroup->indexInInternalSubgroupsList;
764 		}
765 		else{
766 			assert(0);
767 		}
768 
769 		assert(sbg>=0 && sbg<gt.rules.nInternalSubgroups);
770 
771 		QSet<QPair<int, int>> notAvailableDayHour=studentsSetNotAvailableDayHour.value(usedStudentsList.at(t), QSet<QPair<int, int>>());
772 
773 		/*ConstraintStudentsSetNotAvailableTimes* ctr=notAvailableHash.value(usedStudentsList.at(t), nullptr);
774 		if(ctr!=nullptr){
775 			for(int i=0; i<ctr->days.count(); i++){
776 				int d=ctr->days.at(i);
777 				int h=ctr->hours.at(i);
778 				notAvailableDayHour.insert(QPair<int,int>(d,h));
779 			}
780 		}*/
781 
782 		for(int d=0; d<gt.rules.nDaysPerWeek; d++){
783 			for(int h=0; h<gt.rules.nHoursPerDay; h++){
784 				assert(d*gt.rules.nHoursPerDay+h<studentsTimetableTable->columnCount());
785 
786 				//begin by Marco Vassura
787 				// add colors (start)
788 				//if(USE_GUI_COLORS) {
789 				studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setBackground(studentsTimetableTable->palette().color(QPalette::Base));
790 				studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setForeground(studentsTimetableTable->palette().color(QPalette::Text));
791 				//}
792 				// add colors (end)
793 				//end by Marco Vassura
794 
795 				QString s = "";
796 
797 				int ai=students_timetable_weekly[sbg][d][h]; //activity index
798 
799 				bool ok=true;
800 				if(ai==UNALLOCATED_ACTIVITY){
801 					ok=false;
802 				}
803 				else{
804 					if(!gt.rules.internalActivitiesList[ai].studentsNames.contains(usedStudentsList.at(t))){
805 						ok=false;
806 					}
807 				}
808 
809 				//Activity* act=gt.rules.activitiesList.at(ai);
810 				if(ok){
811 					Activity* act=&gt.rules.internalActivitiesList[ai];
812 					assert(act!=nullptr);
813 
814 					if(TIMETABLE_HTML_PRINT_ACTIVITY_TAGS){
815 						QString ats=act->activityTagsNames.join(", ");
816 						s += act->subjectName+" "+ats;
817 					}
818 					else{
819 						s += act->subjectName;
820 					}
821 
822 					//teachers
823 					if(act->teachersNames.count()>0){
824 						//s+=" ";
825 						s+="\n";
826 						s+=act->teachersNames.join(", ");
827 					}
828 
829 					if(act->studentsNames.count()==1){
830 						//Comment taken from the teachers view timetable time horizontal
831 						//Don't do the assert below, because it crashes if you change the teacher's name and view the teachers' timetable,
832 						//without generating again (as reported by Yush Yuen).
833 						//assert(act->teachersNames.at(0)==teachername);
834 					}
835 					else{
836 						assert(act->studentsNames.count()>=2);
837 						//Comment taken from the teachers view timetable time horizontal
838 						//Don't do the assert below, because it crashes if you change the teacher's name and view the teachers' timetable,
839 						//without generating again (as reported by Yush Yuen).
840 						//assert(act->teachersNames.contains(teachername));
841 						//s+=" ";
842 						s+="\n";
843 						s+=act->studentsNames.join(", ");
844 					}
845 
846 					int r=best_solution.rooms[ai];
847 					if(r!=UNALLOCATED_SPACE && r!=UNSPECIFIED_ROOM){
848 						//s+=" ";
849 						//s+=tr("R:%1", "Room").arg(gt.rules.internalRoomsList[r]->name);
850 						//s+=" ";
851 						s+="\n";
852 						s+=gt.rules.internalRoomsList[r]->name;
853 					}
854 
855 					//added by Volker Dirr (start)
856 					QString descr="";
857 					QString tt="";
858 					if(idsOfPermanentlyLockedTime.contains(act->id)){
859 						descr+=QCoreApplication::translate("TimetableViewForm", "PLT", "Abbreviation for permanently locked time. There are 4 strings: permanently locked time, permanently locked space, "
860 							"locked time, locked space. Make sure their abbreviations contain different letters and are visually different, so user can easily differentiate between them."
861 							" These abbreviations may appear also in other places, please use the same abbreviations.");
862 						tt=", ";
863 					}
864 					else if(idsOfLockedTime.contains(act->id)){
865 						descr+=QCoreApplication::translate("TimetableViewForm", "LT", "Abbreviation for locked time. There are 4 strings: permanently locked time, permanently locked space, "
866 						"locked time, locked space. Make sure their abbreviations contain different letters and are visually different, so user can easily differentiate between them."
867 							" These abbreviations may appear also in other places, please use the same abbreviations.");
868 						tt=", ";
869 					}
870 					if(idsOfPermanentlyLockedSpace.contains(act->id)){
871 						descr+=tt+QCoreApplication::translate("TimetableViewForm", "PLS", "Abbreviation for permanently locked space. There are 4 strings: permanently locked time, permanently locked space, "
872 							"locked time, locked space. Make sure their abbreviations contain different letters and are visually different, so user can easily differentiate between them."
873 							" These abbreviations may appear also in other places, please use the same abbreviations.");
874 					}
875 					else if(idsOfLockedSpace.contains(act->id)){
876 						descr+=tt+QCoreApplication::translate("TimetableViewForm", "LS", "Abbreviation for locked space. There are 4 strings: permanently locked time, permanently locked space, "
877 							"locked time, locked space. Make sure their abbreviations contain different letters and are visually different, so user can easily differentiate between them."
878 							" These abbreviations may appear also in other places, please use the same abbreviations.");
879 					}
880 					if(descr!=""){
881 						descr.prepend("\n(");
882 						//descr.prepend(" ");
883 						descr.append(")");
884 					}
885 
886 					if(idsOfPermanentlyLockedTime.contains(act->id) || idsOfLockedTime.contains(act->id)){
887 						QFont font(studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->font());
888 						font.setBold(true);
889 						studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setFont(font);
890 					}
891 					else{
892 						QFont font(studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->font());
893 						font.setBold(false);
894 						studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setFont(font);
895 					}
896 
897 					if(idsOfPermanentlyLockedSpace.contains(act->id) || idsOfLockedSpace.contains(act->id)){
898 						QFont font(studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->font());
899 						font.setItalic(true);
900 						studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setFont(font);
901 					}
902 					else{
903 						QFont font(studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->font());
904 						font.setItalic(false);
905 						studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setFont(font);
906 					}
907 
908 					s+=descr;
909 					//added by Volker Dirr (end)
910 
911 					//begin by Marco Vassura
912 					// add colors (start)
913 					if(USE_GUI_COLORS /*&& act->studentsNames.count()>0*/){
914 						QBrush bg(stringToColor(act->subjectName));
915 						studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setBackground(bg);
916 						double brightness = bg.color().redF()*0.299 + bg.color().greenF()*0.587 + bg.color().blueF()*0.114;
917 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
918 						if (brightness<0.5)
919 							studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setForeground(QBrush(QColorConstants::White));
920 						else
921 							studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setForeground(QBrush(QColorConstants::Black));
922 #else
923 						if (brightness<0.5)
924 							studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setForeground(QBrush(Qt::white));
925 						else
926 							studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setForeground(QBrush(Qt::black));
927 #endif
928 					}
929 					// add colors (end)
930 					//end by Marco Vassura
931 				}
932 				else{
933 //					if(subgroupNotAvailableDayHour[sbg][d][h] && PRINT_NOT_AVAILABLE_TIME_SLOTS)
934 					if(notAvailableDayHour.contains(QPair<int,int>(d,h)) && PRINT_NOT_AVAILABLE_TIME_SLOTS)
935 						s+="-x-";
936 					else if(breakDayHour[d][h] && PRINT_BREAK_TIME_SLOTS)
937 						s+="-X-";
938 				}
939 				if(ok){
940 					QString s2;
941 					Activity* act=&gt.rules.internalActivitiesList[ai];
942 					if(teachersCheckBox->isChecked() && !act->teachersNames.isEmpty()){
943 						s2+=act->teachersNames.join(", ");
944 					}
945 					if(subjectsCheckBox->isChecked()){
946 						if(!s2.isEmpty()){
947 							s2+=" ";
948 							//s2+="\n";
949 						}
950 						s2+=act->subjectName;
951 					}
952 					studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setText(s2);
953 				}
954 				else
955 					studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setText(s);
956 				studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->setToolTip(s);
957 			}
958 		}
959 	}
960 	//	for(int i=0; i<gt.rules.nHoursPerDay; i++) //added in version 3_9_16, on 16 Oct. 2004
961 	//		teachersTimetableTable->adjustRow(i);
962 
963 //	teachersTimetableTable->resizeRowsToContents();
964 
965 	detailActivity(studentsTimetableTable->currentItem());
966 }
967 
968 /*void TimetableViewTeachersTimeHorizontalForm::resizeEvent(QResizeEvent* event)
969 {
970 	QDialog::resizeEvent(event);
971 
972 //	teachersTimetableTable->resizeRowsToContents();
973 }*/
974 
975 //begin by Marco Vassura
976 //slightly modified by Liviu Lalescu on 2021-03-01
stringToColor(const QString & s)977 QColor TimetableViewStudentsTimeHorizontalForm::stringToColor(const QString& s)
978 {
979 	// CRC-24 based on RFC 2440 Section 6.1
980 	unsigned long int crc = 0xB704CEUL;
981 	QByteArray ba=s.toUtf8();
982 	for(char c : qAsConst(ba)){
983 		unsigned char uc=(unsigned char)(c);
984 		crc ^= (uc & 0xFF) << 16;
985 		for (int i = 0; i < 8; i++) {
986 			crc <<= 1;
987 			if (crc & 0x1000000UL)
988 				crc ^= 0x1864CFBUL;
989 		}
990 	}
991 	return QColor::fromRgb(int((crc>>16) & 0xFF), int((crc>>8) & 0xFF), int(crc & 0xFF));
992 }
993 //end by Marco Vassura
994 
currentItemChanged(QTableWidgetItem * current,QTableWidgetItem * previous)995 void TimetableViewStudentsTimeHorizontalForm::currentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
996 {
997 	Q_UNUSED(previous);
998 
999 	detailActivity(current);
1000 }
1001 
detailActivity(QTableWidgetItem * item)1002 void TimetableViewStudentsTimeHorizontalForm::detailActivity(QTableWidgetItem* item){
1003 	if(item==nullptr){
1004 		detailsTextEdit->setPlainText(QString(""));
1005 		return;
1006 	}
1007 
1008 	if(!(students_schedule_ready && teachers_schedule_ready)){
1009 		QMessageBox::warning(this, tr("FET warning"), tr("Timetable not available in view students timetable dialog - please generate a new timetable"));
1010 		return;
1011 	}
1012 	assert(students_schedule_ready && teachers_schedule_ready);
1013 
1014 	if(item->row()>=usedStudentsList.count() || item->column()>=gt.rules.nDaysPerWeek*gt.rules.nHoursPerDay){
1015 		QMessageBox::warning(this, tr("FET warning"), tr("Timetable not available in view students timetable dialog - please generate a new timetable "
1016 		"or close the timetable view students dialog"));
1017 		return;
1018 	}
1019 
1020 	if(gt.rules.nInternalRooms!=gt.rules.roomsList.count()){
1021 		QMessageBox::warning(this, tr("FET warning"), tr("Cannot display the timetable, because you added or removed some rooms. Please regenerate the timetable and then view it"));
1022 		return;
1023 	}
1024 
1025 	assert(item->row()>=0);
1026 	assert(item->column()>=0);
1027 
1028 	int t=item->row();
1029 
1030 	/*if(gt.rules.nInternalTeachers!=gt.rules.teachersList.count()){
1031 		QMessageBox::warning(this, tr("FET warning"), tr("Cannot display the timetable, because you added or removed some teachers. Please regenerate the timetable and then view it"));
1032 		return;
1033 	}*/
1034 
1035 	int d=item->column()/gt.rules.nHoursPerDay;
1036 	int h=item->column()%gt.rules.nHoursPerDay;
1037 
1038 	int sbg=-1;
1039 
1040 	if(!gt.rules.studentsHash.contains(usedStudentsList.at(t))){
1041 		QMessageBox::warning(this, tr("FET warning"), tr("The students set is invalid - please close this dialog and open a new view students timetable"));
1042 		return;
1043 	}
1044 	assert(gt.rules.studentsHash.contains(usedStudentsList.at(t)));
1045 	StudentsSet* ss=gt.rules.studentsHash.value(usedStudentsList.at(t), nullptr);
1046 	assert(ss!=nullptr);
1047 	if(ss->type==STUDENTS_YEAR){
1048 		StudentsYear* year=(StudentsYear*)ss;
1049 		sbg=year->groupsList.at(0)->subgroupsList.at(0)->indexInInternalSubgroupsList;
1050 	}
1051 	else if(ss->type==STUDENTS_GROUP){
1052 		StudentsGroup* group=(StudentsGroup*)ss;
1053 		sbg=group->subgroupsList.at(0)->indexInInternalSubgroupsList;
1054 	}
1055 	else if(ss->type==STUDENTS_SUBGROUP){
1056 		StudentsSubgroup* subgroup=(StudentsSubgroup*)ss;
1057 		sbg=subgroup->indexInInternalSubgroupsList;
1058 	}
1059 	else{
1060 		assert(0);
1061 	}
1062 
1063 	assert(sbg>=0 && sbg<gt.rules.nInternalSubgroups);
1064 
1065 	QSet<QPair<int, int>> notAvailableDayHour=studentsSetNotAvailableDayHour.value(usedStudentsList.at(t), QSet<QPair<int, int>>());
1066 
1067 	/*QSet<QPair<int, int>> notAvailableDayHour;
1068 	QSet<ConstraintStudentsSetNotAvailableTimes*> cs=gt.rules.ssnatHash.value(usedStudentsList.at(t), QSet<ConstraintStudentsSetNotAvailableTimes*>());
1069 	if(!cs.isEmpty()){
1070 		assert(cs.count()==1);
1071 		ConstraintStudentsSetNotAvailableTimes* ctr=*(cs.begin());
1072 
1073 		for(int i=0; i<ctr->days.count(); i++){
1074 			int d=ctr->days.at(i);
1075 			int h=ctr->hours.at(i);
1076 			notAvailableDayHour.insert(QPair<int,int>(d,h));
1077 		}
1078 	}*/
1079 
1080 	/*int teacher=gt.rules.searchTeacher(gt.rules.internalTeachersList[t]->name);
1081 	if(teacher<0){
1082 		QMessageBox::warning(this, tr("FET warning"), tr("The teacher is invalid - please close this dialog and open a new view teachers timetable"));
1083 		return;
1084 	}*/
1085 	QString s = "";
1086 	if(d>=0 && d<gt.rules.nDaysPerWeek && h>=0 && h<gt.rules.nHoursPerDay){
1087 		int ai=students_timetable_weekly[sbg][d][h]; //activity index
1088 
1089 		bool ok=true;
1090 		if(ai==UNALLOCATED_ACTIVITY){
1091 			ok=false;
1092 		}
1093 		else{
1094 			if(!gt.rules.internalActivitiesList[ai].studentsNames.contains(usedStudentsList.at(t))){
1095 				ok=false;
1096 			}
1097 		}
1098 
1099 		//Activity* act=gt.rules.activitiesList.at(ai);
1100 		if(ok){
1101 			Activity* act=&gt.rules.internalActivitiesList[ai];
1102 			assert(act!=nullptr);
1103 			//s += act->getDetailedDescriptionWithConstraints(gt.rules);
1104 			s += act->getDetailedDescription(gt.rules);
1105 
1106 			int r=best_solution.rooms[ai];
1107 			if(r!=UNALLOCATED_SPACE && r!=UNSPECIFIED_ROOM){
1108 				s+="\n";
1109 				s+=tr("Room: %1").arg(gt.rules.internalRoomsList[r]->name);
1110 
1111 				if(gt.rules.internalRoomsList[r]->isVirtual==true){
1112 					QStringList tsl;
1113 					for(int i : qAsConst(best_solution.realRoomsList[ai]))
1114 						tsl.append(gt.rules.internalRoomsList[i]->name);
1115 					s+=QString(" (")+tsl.join(", ")+QString(")");
1116 				}
1117 
1118 				if(gt.rules.internalRoomsList[r]->building!=""){
1119 					s+="\n";
1120 					s+=tr("Building=%1").arg(gt.rules.internalRoomsList[r]->building);
1121 				}
1122 				s+="\n";
1123 				s+=tr("Capacity=%1").arg(gt.rules.internalRoomsList[r]->capacity);
1124 			}
1125 			//added by Volker Dirr (start)
1126 			QString descr="";
1127 			QString tt="";
1128 			if(idsOfPermanentlyLockedTime.contains(act->id)){
1129 				descr+=QCoreApplication::translate("TimetableViewForm", "permanently locked time", "refers to activity");
1130 				tt=", ";
1131 			}
1132 			else if(idsOfLockedTime.contains(act->id)){
1133 				descr+=QCoreApplication::translate("TimetableViewForm", "locked time", "refers to activity");
1134 				tt=", ";
1135 			}
1136 			if(idsOfPermanentlyLockedSpace.contains(act->id)){
1137 				descr+=tt+QCoreApplication::translate("TimetableViewForm", "permanently locked space", "refers to activity");
1138 			}
1139 			else if(idsOfLockedSpace.contains(act->id)){
1140 				descr+=tt+QCoreApplication::translate("TimetableViewForm", "locked space", "refers to activity");
1141 			}
1142 			if(descr!=""){
1143 				descr.prepend("\n(");
1144 				descr.append(")");
1145 			}
1146 			s+=descr;
1147 			//added by Volker Dirr (end)
1148 		}
1149 		else{
1150 			//if(subgroupNotAvailableDayHour[sbg][d][h]){
1151 			if(notAvailableDayHour.contains(QPair<int,int>(d,h))){
1152 				s+=tr("Students set is not available 100% in this slot");
1153 				s+="\n";
1154 			}
1155 			if(breakDayHour[d][h]){
1156 				s+=tr("Break with weight 100% in this slot");
1157 				s+="\n";
1158 			}
1159 		}
1160 	}
1161 	detailsTextEdit->setPlainText(s);
1162 }
1163 
lockTime()1164 void TimetableViewStudentsTimeHorizontalForm::lockTime()
1165 {
1166 	this->lock(true, false);
1167 }
1168 
lockSpace()1169 void TimetableViewStudentsTimeHorizontalForm::lockSpace()
1170 {
1171 	this->lock(false, true);
1172 }
1173 
lockTimeSpace()1174 void TimetableViewStudentsTimeHorizontalForm::lockTimeSpace()
1175 {
1176 	this->lock(true, true);
1177 }
1178 
lock(bool lockTime,bool lockSpace)1179 void TimetableViewStudentsTimeHorizontalForm::lock(bool lockTime, bool lockSpace)
1180 {
1181 	if(simulation_running || simulation_running_multi){
1182 		QMessageBox::information(this, tr("FET information"),
1183 			tr("Allocation in course.\nPlease stop simulation before this."));
1184 		return;
1185 	}
1186 
1187 	if(!(students_schedule_ready && teachers_schedule_ready)){
1188 		QMessageBox::warning(this, tr("FET warning"), tr("Timetable not available in view students timetable dialog - please generate a new timetable"));
1189 		return;
1190 	}
1191 	assert(students_schedule_ready && teachers_schedule_ready);
1192 
1193 	if(gt.rules.nInternalRooms!=gt.rules.roomsList.count()){
1194 		QMessageBox::warning(this, tr("FET warning"), tr("Cannot display the timetable, because you added or removed some rooms. Please regenerate the timetable and then view it"));
1195 		return;
1196 	}
1197 
1198 	Solution* tc=&best_solution;
1199 
1200 	bool report=false; //the messages are annoying
1201 
1202 	int addedT=0, unlockedT=0;
1203 	int addedS=0, unlockedS=0;
1204 
1205 	QSet<int> dummyActivitiesColumn; //Dummy activities (no students) column to be considered, because the whole column is selected.
1206 	for(int d=0; d<gt.rules.nDaysPerWeek; d++){
1207 		for(int h=0; h<gt.rules.nHoursPerDay; h++){
1208 			assert(d*gt.rules.nHoursPerDay+h < studentsTimetableTable->columnCount());
1209 			bool wholeColumn=true;
1210 			for(int t=0; t<usedStudentsList.count(); t++){
1211 				assert(t<studentsTimetableTable->rowCount());
1212 
1213 				if(!studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->isSelected()){
1214 					wholeColumn=false;
1215 					break;
1216 				}
1217 			}
1218 			if(wholeColumn)
1219 				dummyActivitiesColumn.insert(d+h*gt.rules.nDaysPerWeek);
1220 		}
1221 	}
1222 
1223 	QSet<int> dummyActivities;
1224 	for(int ai=0; ai<gt.rules.nInternalActivities; ai++){
1225 		if(gt.rules.internalActivitiesList[ai].iSubgroupsList.count()==0){
1226 			if(tc->times[ai]!=UNALLOCATED_TIME){
1227 				int da=tc->times[ai]%gt.rules.nDaysPerWeek;
1228 				int ha=tc->times[ai]/gt.rules.nDaysPerWeek;
1229 				for(int ha2=ha; ha2<ha+gt.rules.internalActivitiesList[ai].duration; ha2++)
1230 					if(dummyActivitiesColumn.contains(da+ha2*gt.rules.nDaysPerWeek))
1231 						if(!dummyActivities.contains(ai))
1232 							dummyActivities.insert(ai);
1233 			}
1234 		}
1235 	}
1236 
1237 	QSet<int> realActivities;
1238 	for(int t=0; t<usedStudentsList.count(); t++){
1239 		assert(t<studentsTimetableTable->rowCount());
1240 
1241 		/*if(!gt.rules.studentsHash.contains(usedStudentsList.at(t))){
1242 			QMessagebox::warning(this, tr("FET warning"), tr("The students set is invalid - please close this dialog and open a new view students timetable"));
1243 			return;
1244 		}*/
1245 
1246 		if(!gt.rules.studentsHash.contains(usedStudentsList.at(t)))
1247 			continue;
1248 		assert(gt.rules.studentsHash.contains(usedStudentsList.at(t)));
1249 		StudentsSet* ss=gt.rules.studentsHash.value(usedStudentsList.at(t), nullptr);
1250 		assert(ss!=nullptr);
1251 		int sbg=-1;
1252 		if(ss->type==STUDENTS_YEAR){
1253 			StudentsYear* year=(StudentsYear*)ss;
1254 			sbg=year->groupsList.at(0)->subgroupsList.at(0)->indexInInternalSubgroupsList;
1255 		}
1256 		else if(ss->type==STUDENTS_GROUP){
1257 			StudentsGroup* group=(StudentsGroup*)ss;
1258 			sbg=group->subgroupsList.at(0)->indexInInternalSubgroupsList;
1259 		}
1260 		else if(ss->type==STUDENTS_SUBGROUP){
1261 			StudentsSubgroup* subgroup=(StudentsSubgroup*)ss;
1262 			sbg=subgroup->indexInInternalSubgroupsList;
1263 		}
1264 		else{
1265 			assert(0);
1266 		}
1267 
1268 		assert(sbg>=0 && sbg<gt.rules.nInternalSubgroups);
1269 
1270 		for(int d=0; d<gt.rules.nDaysPerWeek; d++){
1271 			for(int h=0; h<gt.rules.nHoursPerDay; h++){
1272 				assert(d*gt.rules.nHoursPerDay+h<studentsTimetableTable->columnCount());
1273 				if(studentsTimetableTable->item(t, d*gt.rules.nHoursPerDay+h)->isSelected()){
1274 
1275 					int ai=students_timetable_weekly[sbg][d][h];
1276 
1277 					bool ok=true;
1278 					if(ai==UNALLOCATED_ACTIVITY){
1279 						ok=false;
1280 					}
1281 					else{
1282 						if(!gt.rules.internalActivitiesList[ai].studentsNames.contains(usedStudentsList.at(t))){
1283 							ok=false;
1284 						}
1285 					}
1286 
1287 					//Activity* act=gt.rules.activitiesList.at(ai);
1288 					if(ok)
1289 						if(!realActivities.contains(ai))
1290 							realActivities.insert(ai);
1291 				}
1292 			}
1293 		}
1294 	}
1295 
1296 	for(int ai=0; ai<gt.rules.nInternalActivities; ai++){
1297 		assert( ! (realActivities.contains(ai) && dummyActivities.contains(ai)) );
1298 		if(realActivities.contains(ai) || dummyActivities.contains(ai)){
1299 			assert(tc->times[ai]!=UNALLOCATED_TIME);
1300 			int day=tc->times[ai]%gt.rules.nDaysPerWeek;
1301 			int hour=tc->times[ai]/gt.rules.nDaysPerWeek;
1302 
1303 			Activity* act=&gt.rules.internalActivitiesList[ai];
1304 
1305 			if(lockTime){
1306 				QString s;
1307 
1308 				QList<TimeConstraint*> tmptc;
1309 				int count=0;
1310 
1311 				for(ConstraintActivityPreferredStartingTime* c : gt.rules.apstHash.value(act->id, QSet<ConstraintActivityPreferredStartingTime*>())){
1312 					assert(c->activityId==act->id);
1313 					if(c->activityId==act->id && c->weightPercentage==100.0 && c->active && c->day>=0 && c->hour>=0){
1314 						count++;
1315 						if(c->permanentlyLocked){
1316 							if(idsOfLockedTime.contains(c->activityId) || !idsOfPermanentlyLockedTime.contains(c->activityId)){
1317 								QMessageBox::warning(this, tr("FET warning"), tr("Small problem detected")
1318 									+"\n\n"+tr("A possible problem might be that you have 2 or more constraints of type activity preferred starting time with weight 100% related to activity id %1, please leave only one of them").arg(act->id)
1319 									+"\n\n"+tr("A possible problem might be synchronization - so maybe try to close the timetable view dialog and open it again")
1320 									+"\n\n"+tr("Please report possible bug")
1321 									);
1322 							}
1323 							else{
1324 								if(unlockRadioButton->isChecked() || toggleRadioButton->isChecked()){
1325 									s+=tr("Constraint %1 will not be removed, because it is permanently locked. If you want to unlock it you must go to the constraints menu.").arg("\n"+c->getDetailedDescription(gt.rules)+"\n");
1326 								}
1327 							}
1328 						}
1329 						else{
1330 							if(!idsOfLockedTime.contains(c->activityId) || idsOfPermanentlyLockedTime.contains(c->activityId)){
1331 								QMessageBox::warning(this, tr("FET warning"), tr("Small problem detected")
1332 									+"\n\n"+tr("A possible problem might be that you have 2 or more constraints of type activity preferred starting time with weight 100% related to activity id %1, please leave only one of them").arg(act->id)
1333 									+"\n\n"+tr("A possible problem might be synchronization - so maybe try to close the timetable view dialog and open it again")
1334 									+"\n\n"+tr("Please report possible bug")
1335 									);
1336 							}
1337 							else{
1338 								if(unlockRadioButton->isChecked() || toggleRadioButton->isChecked()){
1339 									tmptc.append((TimeConstraint*)c);
1340 								}
1341 							}
1342 						}
1343 					}
1344 				}
1345 				if(count==0 && (lockRadioButton->isChecked() || toggleRadioButton->isChecked())){
1346 					ConstraintActivityPreferredStartingTime* ctr=new ConstraintActivityPreferredStartingTime(100.0, act->id, day, hour, false);
1347 					bool t=gt.rules.addTimeConstraint(ctr);
1348 					QString s;
1349 					if(t){
1350 						addedT++;
1351 						idsOfLockedTime.insert(act->id);
1352 						s+=tr("Added the following constraint:")+"\n"+ctr->getDetailedDescription(gt.rules);
1353 					}
1354 					else{
1355 						delete ctr;
1356 
1357 						QMessageBox::warning(this, tr("FET warning"), tr("You may have a problem, because FET expected to add 1 constraint, but this is not possible. "
1358 						 "Please report possible bug"));
1359 					}
1360 				}
1361 				else if(count>=1 && (unlockRadioButton->isChecked() || toggleRadioButton->isChecked())){
1362 					if(count>=2)
1363 						QMessageBox::warning(this, tr("FET warning"), tr("You may have a problem, because FET expected to delete 1 constraint, but will delete %1 constraints").arg(tmptc.size()));
1364 
1365 					for(TimeConstraint* deltc : qAsConst(tmptc)){
1366 						s+=tr("The following constraint will be deleted:")+"\n"+deltc->getDetailedDescription(gt.rules)+"\n";
1367 						gt.rules.removeTimeConstraint(deltc);
1368 						idsOfLockedTime.remove(act->id);
1369 						unlockedT++;
1370 						//delete deltc; - done by rules.removeTimeConstraint(...)
1371 					}
1372 				}
1373 				tmptc.clear();
1374 
1375 				if(report){
1376 					/*int k;
1377 					k=QMessageBox::information(this, tr("FET information"), s,
1378 						tr("Skip information"), tr("See next"), QString(), 1, 0 );
1379 
1380 					if(k==0)
1381 						report=false;*/
1382 					QMessageBox::StandardButton k;
1383 					k=QMessageBox::information(this, tr("FET information"), s, QMessageBox::YesToAll | QMessageBox::Ok);
1384 
1385 					if(k==QMessageBox::YesToAll)
1386 						report=false;
1387 				}
1388 			}
1389 
1390 			int ri=tc->rooms[ai];
1391 			if(ri!=UNALLOCATED_SPACE && ri!=UNSPECIFIED_ROOM && lockSpace){
1392 				QString s;
1393 
1394 				QList<SpaceConstraint*> tmpsc;
1395 				int count=0;
1396 
1397 				for(ConstraintActivityPreferredRoom* c : gt.rules.aprHash.value(act->id, QSet<ConstraintActivityPreferredRoom*>())){
1398 					assert(c->activityId==act->id);
1399 
1400 					if(c->activityId==act->id && c->weightPercentage==100.0 && c->active
1401 					 && (gt.rules.internalRoomsList[ri]->isVirtual==false
1402 					 || (gt.rules.internalRoomsList[ri]->isVirtual==true && !c->preferredRealRoomsNames.isEmpty()))){
1403 						count++;
1404 						if(c->permanentlyLocked){
1405 							if(idsOfLockedSpace.contains(c->activityId) || !idsOfPermanentlyLockedSpace.contains(c->activityId)){
1406 								QMessageBox::warning(this, tr("FET warning"), tr("Small problem detected")
1407 									+"\n\n"+tr("A possible problem might be that you have 2 or more constraints of type activity preferred room with weight 100% related to activity id %1, please leave only one of them").arg(act->id)
1408 									+"\n\n"+tr("A possible problem might be synchronization - so maybe try to close the timetable view dialog and open it again")
1409 									+"\n\n"+tr("Please report possible bug")
1410 									);
1411 							}
1412 							else{
1413 								if(unlockRadioButton->isChecked() || toggleRadioButton->isChecked()){
1414 									s+=tr("Constraint %1 will not be removed, because it is permanently locked. If you want to unlock it you must go to the constraints menu.").arg("\n"+c->getDetailedDescription(gt.rules)+"\n");
1415 								}
1416 							}
1417 						}
1418 						else{
1419 							if(!idsOfLockedSpace.contains(c->activityId) || idsOfPermanentlyLockedSpace.contains(c->activityId)){
1420 								QMessageBox::warning(this, tr("FET warning"), tr("Small problem detected")
1421 									+"\n\n"+tr("A possible problem might be that you have 2 or more constraints of type activity preferred room with weight 100% related to activity id %1, please leave only one of them").arg(act->id)
1422 									+"\n\n"+tr("A possible problem might be synchronization - so maybe try to close the timetable view dialog and open it again")
1423 									+"\n\n"+tr("Please report possible bug")
1424 									);
1425 							}
1426 							else{
1427 								if(unlockRadioButton->isChecked() || toggleRadioButton->isChecked()){
1428 									tmpsc.append((SpaceConstraint*)c);
1429 								}
1430 							}
1431 						}
1432 					}
1433 				}
1434 				if(count==0 && (lockRadioButton->isChecked() || toggleRadioButton->isChecked())){
1435 					QStringList tl;
1436 					if(gt.rules.internalRoomsList[ri]->isVirtual==false)
1437 						assert(tc->realRoomsList[ai].isEmpty());
1438 					else
1439 						for(int rr : qAsConst(tc->realRoomsList[ai]))
1440 							tl.append(gt.rules.internalRoomsList[rr]->name);
1441 
1442 					ConstraintActivityPreferredRoom* ctr=new ConstraintActivityPreferredRoom(100, act->id, (gt.rules.internalRoomsList[ri])->name, tl, false);
1443 					bool t=gt.rules.addSpaceConstraint(ctr);
1444 
1445 					QString s;
1446 
1447 					if(t){
1448 						addedS++;
1449 						idsOfLockedSpace.insert(act->id);
1450 						s+=tr("Added the following constraint:")+"\n"+ctr->getDetailedDescription(gt.rules);
1451 					}
1452 					else{
1453 						delete ctr;
1454 
1455 						QMessageBox::warning(this, tr("FET warning"), tr("You may have a problem, because FET expected to add 1 constraint, but this is not possible. "
1456 						 "Please report possible bug"));
1457 					}
1458 				}
1459 				else if(count>=1 && (unlockRadioButton->isChecked() || toggleRadioButton->isChecked())){
1460 					if(count>=2)
1461 						QMessageBox::warning(this, tr("FET warning"), tr("You may have a problem, because FET expected to delete 1 constraint, but will delete %1 constraints").arg(tmpsc.size()));
1462 
1463 					for(SpaceConstraint* delsc : qAsConst(tmpsc)){
1464 						s+=tr("The following constraint will be deleted:")+"\n"+delsc->getDetailedDescription(gt.rules)+"\n";
1465 						gt.rules.removeSpaceConstraint(delsc);
1466 						idsOfLockedSpace.remove(act->id);
1467 						unlockedS++;
1468 						//delete delsc; done by rules.removeSpaceConstraint(...)
1469 					}
1470 				}
1471 				tmpsc.clear();
1472 
1473 				if(report){
1474 					/*int k;
1475 					k=QMessageBox::information(this, tr("FET information"), s,
1476 						tr("Skip information"), tr("See next"), QString(), 1, 0 );
1477 
1478 					if(k==0)
1479 						report=false;*/
1480 					QMessageBox::StandardButton k;
1481 					k=QMessageBox::information(this, tr("FET information"), s, QMessageBox::YesToAll | QMessageBox::Ok);
1482 
1483 					if(k==QMessageBox::YesToAll)
1484 						report=false;
1485 				}
1486 			}
1487 		}
1488 	}
1489 
1490 	QStringList added;
1491 	QStringList removed;
1492 	if(FET_LANGUAGE=="en_US"){
1493 		if(addedT>0){
1494 			if(addedT==1)
1495 				added << QString("Added 1 locking time constraint.");
1496 			else
1497 				added << QString("Added %1 locking time constraints.").arg(addedT);
1498 		}
1499 		if(addedS>0){
1500 			if(addedS==1)
1501 				added << QString("Added 1 locking space constraint.");
1502 			else
1503 				added << QString("Added %1 locking space constraints.").arg(addedS);
1504 		}
1505 		if(unlockedT>0){
1506 			if(unlockedT==1)
1507 				removed << QString("Removed 1 locking time constraint.");
1508 			else
1509 				removed << QString("Removed %1 locking time constraints.").arg(unlockedT);
1510 		}
1511 		if(unlockedS>0){
1512 			if(unlockedS==1)
1513 				removed << QString("Removed 1 locking space constraint.");
1514 			else
1515 				removed << QString("Removed %1 locking space constraints.").arg(unlockedS);
1516 		}
1517 	}
1518 	else{
1519 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
1520 		if(addedT>0){
1521 			added << QCoreApplication::translate("TimetableViewForm", "Added %n locking time constraint(s).",
1522 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1523 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1524 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1525 			 "(open these files with Qt Linguist and see the translation of this field).",
1526 			 addedT);
1527 		}
1528 		if(addedS>0){
1529 			added << QCoreApplication::translate("TimetableViewForm", "Added %n locking space constraint(s).",
1530 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1531 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1532 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1533 			 "(open these files with Qt Linguist and see the translation of this field).",
1534 			 addedS);
1535 		}
1536 		if(unlockedT>0){
1537 			removed << QCoreApplication::translate("TimetableViewForm", "Removed %n locking time constraint(s).",
1538 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1539 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1540 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1541 			 "(open these files with Qt Linguist and see the translation of this field).",
1542 			 unlockedT);
1543 		}
1544 		if(unlockedS>0){
1545 			removed << QCoreApplication::translate("TimetableViewForm", "Removed %n locking space constraint(s).",
1546 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1547 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1548 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1549 			 "(open these files with Qt Linguist and see the translation of this field).",
1550 			 unlockedS);
1551 		}
1552 #else
1553 		if(addedT>0){
1554 			added << QCoreApplication::translate("TimetableViewForm", "Added %n locking time constraint(s).",
1555 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1556 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1557 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1558 			 "(open these files with Qt Linguist and see the translation of this field).", QCoreApplication::UnicodeUTF8,
1559 			 addedT);
1560 		}
1561 		if(addedS>0){
1562 			added << QCoreApplication::translate("TimetableViewForm", "Added %n locking space constraint(s).",
1563 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1564 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1565 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1566 			 "(open these files with Qt Linguist and see the translation of this field).", QCoreApplication::UnicodeUTF8,
1567 			 addedS);
1568 		}
1569 		if(unlockedT>0){
1570 			removed << QCoreApplication::translate("TimetableViewForm", "Removed %n locking time constraint(s).",
1571 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1572 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1573 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1574 			 "(open these files with Qt Linguist and see the translation of this field).", QCoreApplication::UnicodeUTF8,
1575 			 unlockedT);
1576 		}
1577 		if(unlockedS>0){
1578 			removed << QCoreApplication::translate("TimetableViewForm", "Removed %n locking space constraint(s).",
1579 			 "See http://doc.qt.io/qt-5/i18n-plural-rules.html for advice on how to correctly translate this field."
1580 			 "Also, see http://doc.qt.io/qt-5/i18n-source-translation.html, section 'Handling Plurals'."
1581 			 "You have two examples on how to translate this field in fet_en_GB.ts and in fet_ro.ts"
1582 			 "(open these files with Qt Linguist and see the translation of this field).", QCoreApplication::UnicodeUTF8,
1583 			 unlockedS);
1584 		}
1585 #endif
1586 	}
1587 	QString ad=added.join("\n");
1588 	QString re=removed.join("\n");
1589 	QStringList all;
1590 	if(!ad.isEmpty())
1591 		all<<ad;
1592 	if(!re.isEmpty())
1593 		all<<re;
1594 	QString s=all.join("\n\n");
1595 	if(s.isEmpty())
1596 		s=QCoreApplication::translate("TimetableViewForm", "No locking constraints added or removed.");
1597 	QMessageBox::information(this, tr("FET information"), s);
1598 
1599 ////////// just for testing
1600 	QSet<int> backupLockedTime;
1601 	QSet<int> backupPermanentlyLockedTime;
1602 	QSet<int> backupLockedSpace;
1603 	QSet<int> backupPermanentlyLockedSpace;
1604 
1605 	backupLockedTime=idsOfLockedTime;
1606 	backupPermanentlyLockedTime=idsOfPermanentlyLockedTime;
1607 	backupLockedSpace=idsOfLockedSpace;
1608 	backupPermanentlyLockedSpace=idsOfPermanentlyLockedSpace;
1609 
1610 	LockUnlock::computeLockedUnlockedActivitiesTimeSpace(); //not needed, just for testing
1611 
1612 	assert(backupLockedTime==idsOfLockedTime);
1613 	assert(backupPermanentlyLockedTime==idsOfPermanentlyLockedTime);
1614 	assert(backupLockedSpace==idsOfLockedSpace);
1615 	assert(backupPermanentlyLockedSpace==idsOfPermanentlyLockedSpace);
1616 ///////////
1617 
1618 	LockUnlock::increaseCommunicationSpinBox();
1619 }
1620 
widthSpinBoxValueChanged()1621 void TimetableViewStudentsTimeHorizontalForm::widthSpinBoxValueChanged()
1622 {
1623 	if(widthSpinBox->value()==MINIMUM_WIDTH_SPIN_BOX_VALUE)
1624 		studentsTimetableTable->horizontalHeader()->setDefaultSectionSize(2*initialRecommendedHeight);
1625 	else
1626 		studentsTimetableTable->horizontalHeader()->setDefaultSectionSize(widthSpinBox->value());
1627 }
1628 
heightSpinBoxValueChanged()1629 void TimetableViewStudentsTimeHorizontalForm::heightSpinBoxValueChanged()
1630 {
1631 	if(heightSpinBox->value()==MINIMUM_HEIGHT_SPIN_BOX_VALUE)
1632 		studentsTimetableTable->verticalHeader()->setDefaultSectionSize(initialRecommendedHeight);
1633 	else
1634 		studentsTimetableTable->verticalHeader()->setDefaultSectionSize(heightSpinBox->value());
1635 }
1636 
help()1637 void TimetableViewStudentsTimeHorizontalForm::help()
1638 {
1639 	QString s="";
1640 	s+=QCoreApplication::translate("TimetableViewForm", "Lock/unlock: you can select one or more activities in the table and toggle lock/unlock in time, space or both.");
1641 	s+=" ";
1642 	s+=QCoreApplication::translate("TimetableViewForm", "There will be added or removed locking constraints for the selected activities (they can be unlocked only if they are not permanently locked).");
1643 	s+="\n\n";
1644 	s+=QCoreApplication::translate("TimetableViewForm", "Locking time constraints are constraints of type activity preferred starting time. Locking space constraints are constraints of type"
1645 		" activity preferred room. You can see these constraints in the corresponding constraints dialogs. New locking constraints are added at the end of the list of constraints.");
1646 	s+="\n\n";
1647 	s+=QCoreApplication::translate("TimetableViewForm", "If a cell is (permanently) locked in time or space, it contains abbreviations to show that: PLT (permanently locked time), LT (locked time), "
1648 		"PLS (permanently locked space) or LS (locked space).", "Translate the abbreviations also. Make sure the abbreviations in your language are different between themselves "
1649 		"and the user can differentiate easily between them. These abbreviations may appear also in other places, please use the same abbreviations.");
1650 
1651 	s+="\n\n";
1652 	s+=tr("If a whole column (day+hour) is selected, there will be locked/unlocked also the dummy activities (activities with no students sets) from that column.");
1653 
1654 	s+="\n\n";
1655 	s+=tr("A bold font cell means that the activity is locked in time, either permanently or not.");
1656 	s+=" ";
1657 	s+=tr("An italic font cell means that the activity is locked in space, either permanently or not.");
1658 
1659 	LongTextMessageBox::largeInformation(this, tr("FET help"), s);
1660 }
1661