1 /*
2 File statisticsexport.cpp
3 */
4 
5 /***************************************************************************
6                                 FET
7                           -------------------
8    copyright            : (C) by Lalescu Liviu
9     email                : Please see https://lalescu.ro/liviu/ for details about contacting Liviu Lalescu (in particular, you can find here the e-mail address)
10  ***************************************************************************
11                       statisticsexport.cpp  -  description
12                              -------------------
13     begin                : Sep 2008
14     copyright            : (C) by Volker Dirr
15                          : https://www.timetabling.de/
16  ***************************************************************************
17  *                                                                         *
18  *   This program is free software: you can redistribute it and/or modify  *
19  *   it under the terms of the GNU Affero General Public License as        *
20  *   published by the Free Software Foundation, either version 3 of the    *
21  *   License, or (at your option) any later version.                       *
22  *                                                                         *
23  ***************************************************************************/
24 
25 // Code contributed by Volker Dirr ( https://www.timetabling.de/ )
26 // Many thanks to Liviu Lalescu. He told me some speed optimizations.
27 
28 #include "timetable_defs.h"
29 #include "statisticsexport.h"
30 
31 // BE CAREFUL: DON'T USE INTERNAL VARIABLES HERE, because maybe computeInternalStructure() is not done!
32 
33 #include <QString>
34 #include <QStringList>
35 #include <QMultiHash>
36 #include <QMap>
37 #include <QSet>
38 #include <QList>
39 
40 #include <QMessageBox>
41 
42 #include <QLocale>
43 #include <QTime>
44 #include <QDate>
45 
46 #include <QDir>
47 
48 #include <QFile>
49 #include <QTextStream>
50 
51 //#include <QApplication>
52 #include <QProgressDialog>
53 //extern QApplication* pqapplication;
54 
55 #include <QtGlobal>
56 
57 extern Timetable gt;
58 
59 //extern bool simulation_running;	//needed?
60 
61 //TODO: use the external string!!!
62 //extern const QString STRING_EMPTY_SLOT;
63 const QString STRING_EMPTY_SLOT_STATISTICS="---";
64 
65 const char TEACHERS_STUDENTS_STATISTICS[]="teachers_students.html";
66 const char TEACHERS_SUBJECTS_STATISTICS[]="teachers_subjects.html";
67 const char STUDENTS_TEACHERS_STATISTICS[]="students_teachers.html";
68 const char STUDENTS_SUBJECTS_STATISTICS[]="students_subjects.html";
69 const char SUBJECTS_TEACHERS_STATISTICS[]="subjects_teachers.html";
70 const char SUBJECTS_STUDENTS_STATISTICS[]="subjects_students.html";
71 const char STYLESHEET_STATISTICS[]="stylesheet.css";
72 const char INDEX_STATISTICS[]="index.html";
73 
74 QString DIRECTORY_STATISTICS;
75 QString PREFIX_STATISTICS;
76 
77 class StringListPair{
78 public:
79 	QStringList list1;
80 	QStringList list2;
81 };
82 
operator <(const StringListPair & pair1,const StringListPair & pair2)83 bool operator <(const StringListPair& pair1, const StringListPair& pair2)
84 {
85 	//return (pair1.list1.join("")+pair1.list2.join("")) < (pair2.list1.join("")+pair2.list2.join(""));
86 	//by Rodolfo Ribeiro Gomes
87 	return (pair1.list1.join("")+pair1.list2.join("")).localeAwareCompare(pair2.list1.join("")+pair2.list2.join(""))<0;
88 }
89 
StatisticsExport()90 StatisticsExport::StatisticsExport()
91 {
92 }
93 
~StatisticsExport()94 StatisticsExport::~StatisticsExport()
95 {
96 }
97 
exportStatistics(QWidget * parent)98 void StatisticsExport::exportStatistics(QWidget* parent){
99 	assert(gt.rules.initialized);
100 	assert(TIMETABLE_HTML_LEVEL>=0);
101 	assert(TIMETABLE_HTML_LEVEL<=7);
102 
103 	FetStatistics statisticValues;
104 	computeHashForIDsStatistics(&statisticValues);
105 	getNamesAndHours(&statisticValues);
106 
107 	DIRECTORY_STATISTICS=OUTPUT_DIR+FILE_SEP+"statistics";
108 
109 	if(INPUT_FILENAME_XML=="")
110 		DIRECTORY_STATISTICS.append(FILE_SEP+"unnamed");
111 	else{
112 		DIRECTORY_STATISTICS.append(FILE_SEP+INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1));
113 
114 		if(DIRECTORY_STATISTICS.right(4)==".fet")
115 			DIRECTORY_STATISTICS=DIRECTORY_STATISTICS.left(DIRECTORY_STATISTICS.length()-4);
116 		//else if(INPUT_FILENAME_XML!="")
117 		//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
118 	}
119 
120 	PREFIX_STATISTICS=DIRECTORY_STATISTICS+FILE_SEP;
121 
122 	int ok=QMessageBox::question(parent, tr("FET Question"),
123 		 StatisticsExport::tr("Do you want to export detailed statistics files into directory %1 as html files?").arg(QDir::toNativeSeparators(DIRECTORY_STATISTICS)), QMessageBox::Yes | QMessageBox::No);
124 	if(ok==QMessageBox::No)
125 		return;
126 
127 	/* need if i use iTeachersList. Currently unneeded. please remove commented asserts in other functions if this is needed again!
128 	bool tmp=gt.rules.computeInternalStructure();
129 	if(!tmp){
130 		QMessageBox::critical(parent, tr("FET critical"),
131 		StatisticsExport::tr("Incorrect data")+"\n");
132 		return;
133 		assert(0==1);
134 	}*/
135 
136 	QDir dir;
137 	if(!dir.exists(OUTPUT_DIR))
138 		dir.mkpath(OUTPUT_DIR);
139 	if(!dir.exists(DIRECTORY_STATISTICS))
140 		dir.mkpath(DIRECTORY_STATISTICS);
141 
142 	QDate dat=QDate::currentDate();
143 	QTime tim=QTime::currentTime();
144 	QLocale loc(FET_LANGUAGE);
145 	QString sTime=loc.toString(dat, QLocale::ShortFormat)+" "+loc.toString(tim, QLocale::ShortFormat);
146 
147 	ok=exportStatisticsStylesheetCss(parent, sTime, statisticValues);
148 	if(ok)
149 		ok=exportStatisticsIndex(parent, sTime);
150 	if(ok)
151 		ok=exportStatisticsTeachersSubjects(parent, sTime, statisticValues, TIMETABLE_HTML_LEVEL);
152 	if(ok)
153 		ok=exportStatisticsSubjectsTeachers(parent, sTime, statisticValues, TIMETABLE_HTML_LEVEL);
154 	if(ok)
155 		ok=exportStatisticsTeachersStudents(parent, sTime, statisticValues, TIMETABLE_HTML_LEVEL);
156 	if(ok)
157 		ok=exportStatisticsStudentsTeachers(parent, sTime, statisticValues, TIMETABLE_HTML_LEVEL);
158 	if(ok)
159 		ok=exportStatisticsSubjectsStudents(parent, sTime, statisticValues, TIMETABLE_HTML_LEVEL);
160 	if(ok)
161 		ok=exportStatisticsStudentsSubjects(parent, sTime, statisticValues, TIMETABLE_HTML_LEVEL);
162 
163 	if(ok){
164 		QMessageBox::information(parent, tr("FET Information"),
165 		 StatisticsExport::tr("Statistics files were exported to directory %1 as html files.").arg(QDir::toNativeSeparators(DIRECTORY_STATISTICS)));
166 	} else {
167 		QMessageBox::warning(parent, tr("FET warning"),
168 		 StatisticsExport::tr("Statistics export incomplete")+"\n");
169 	}
170 }
171 
computeHashForIDsStatistics(FetStatistics * statisticValues)172 void StatisticsExport::computeHashForIDsStatistics(FetStatistics *statisticValues){		// by Volker Dirr
173 	//TODO if we use a relational data base this is unneded, because we can use the primary key id of the database
174 	//This is very similar to timetable compute hash. so always check it if you change something here!
175 
176 	assert((*statisticValues).hashStudentIDsStatistics.isEmpty());
177 	assert((*statisticValues).hashSubjectIDsStatistics.isEmpty());
178 	assert((*statisticValues).hashActivityTagIDsStatistics.isEmpty());
179 	assert((*statisticValues).hashTeacherIDsStatistics.isEmpty());
180 	//assert((*statisticValues).hashRoomIDsStatistics.isEmpty());
181 	//assert((*statisticValues).hashDayIDsStatistics.isEmpty());
182 
183 	int cnt=1;
184 	for(int i=0; i<gt.rules.yearsList.size(); i++){
185 		StudentsYear* sty=gt.rules.yearsList[i];
186 		if(!(*statisticValues).hashStudentIDsStatistics.contains(sty->name)){
187 			(*statisticValues).hashStudentIDsStatistics.insert(sty->name, CustomFETString::number(cnt));
188 			cnt++;
189 		}
190 		for(int j=0; j<sty->groupsList.size(); j++){
191 			StudentsGroup* stg=sty->groupsList[j];
192 			if(!(*statisticValues).hashStudentIDsStatistics.contains(stg->name)){
193 				(*statisticValues).hashStudentIDsStatistics.insert(stg->name, CustomFETString::number(cnt));
194 				cnt++;
195 			}
196 			for(int k=0; k<stg->subgroupsList.size(); k++){
197 				StudentsSubgroup* sts=stg->subgroupsList[k];
198 				if(!(*statisticValues).hashStudentIDsStatistics.contains(sts->name)){
199 					(*statisticValues).hashStudentIDsStatistics.insert(sts->name, CustomFETString::number(cnt));
200 					cnt++;
201 				}
202 			}
203 		}
204 	}
205 	for(int i=0; i<gt.rules.subjectsList.size(); i++){
206 		(*statisticValues).hashSubjectIDsStatistics.insert(gt.rules.subjectsList[i]->name, CustomFETString::number(i+1));
207 	}
208 	for(int i=0; i<gt.rules.activityTagsList.size(); i++){
209 		(*statisticValues).hashActivityTagIDsStatistics.insert(gt.rules.activityTagsList[i]->name, CustomFETString::number(i+1));
210 	}
211 	for(int i=0; i<gt.rules.teachersList.size(); i++){
212 		(*statisticValues).hashTeacherIDsStatistics.insert(gt.rules.teachersList[i]->name, CustomFETString::number(i+1));
213 	}
214 	/*for(int room=0; room<gt.rules.roomsList.size(); room++){
215 		(*statisticValues).hashRoomIDsStatistics.insert(gt.rules.roomsList[room]->name, CustomFETString::number(room+1));
216 	}*/
217 	/*for(int k=0; k<gt.rules.nDaysPerWeek; k++){
218 		(*statisticValues).hashDayIDsStatistics.insert(gt.rules.daysOfTheWeek[k], CustomFETString::number(k+1));
219 	}*/
220 }
221 
getNamesAndHours(FetStatistics * statisticValues)222 void StatisticsExport::getNamesAndHours(FetStatistics *statisticValues){
223 	assert((*statisticValues).allStudentsNames.isEmpty());
224 	assert((*statisticValues).allSubjectsNames.isEmpty());
225 	assert((*statisticValues).allTeachersNames.isEmpty());
226 
227 	assert((*statisticValues).studentsTotalNumberOfHours.isEmpty());
228 	assert((*statisticValues).studentsTotalNumberOfHours2.isEmpty());
229 
230 	assert((*statisticValues).subjectsTotalNumberOfHours.isEmpty());
231 	assert((*statisticValues).subjectsTotalNumberOfHours4.isEmpty());
232 
233 	assert((*statisticValues).teachersTotalNumberOfHours.isEmpty());
234 	assert((*statisticValues).teachersTotalNumberOfHours2.isEmpty());
235 
236 	assert((*statisticValues).studentsActivities.isEmpty());
237 	assert((*statisticValues).subjectsActivities.isEmpty());
238 	assert((*statisticValues).teachersActivities.isEmpty());
239 
240 	QSet<QString> allStudentsNamesSet;
241 	for(StudentsYear* sty : qAsConst(gt.rules.yearsList)){
242 		if(!allStudentsNamesSet.contains(sty->name)){
243 			(*statisticValues).allStudentsNames<<sty->name;
244 			allStudentsNamesSet.insert(sty->name);
245 		}
246 		for(StudentsGroup* stg : qAsConst(sty->groupsList)){
247 			if(!allStudentsNamesSet.contains(stg->name)){
248 				(*statisticValues).allStudentsNames<<stg->name;
249 				allStudentsNamesSet.insert(stg->name);
250 			}
251 			for(StudentsSubgroup* sts : qAsConst(stg->subgroupsList)){
252 				if(!allStudentsNamesSet.contains(sts->name)){
253 					(*statisticValues).allStudentsNames<<sts->name;
254 					allStudentsNamesSet.insert(sts->name);
255 				}
256 			}
257 		}
258 	}
259 
260 	for(Teacher* t : qAsConst(gt.rules.teachersList)){
261 		(*statisticValues).allTeachersNames<<t->name;
262 	}
263 
264 	for(Subject* s : qAsConst(gt.rules.subjectsList)){
265 		(*statisticValues).allSubjectsNames<<s->name;
266 	}
267 
268 	//QProgressDialog progress(parent);
269 	//progress.setLabelText(tr("Processing activities...please wait"));
270 	//progress.setRange(0,gt.rules.activitiesList.count());
271 	//progress.setModal(true);
272 
273 	for(int ai=0; ai<gt.rules.activitiesList.size(); ai++){
274 		//progress.setValue(ai);
275 		//pqapplication->processEvents();
276 
277 		Activity* act=gt.rules.activitiesList[ai];
278 		if(act->active){
279 				(*statisticValues).subjectsActivities.insert(act->subjectName, ai);
280 				int tmp=(*statisticValues).subjectsTotalNumberOfHours.value(act->subjectName)+act->duration;
281 				(*statisticValues).subjectsTotalNumberOfHours.insert(act->subjectName, tmp);						// (1) so teamlearning-teaching is not counted twice!
282 				for(const QString& t : qAsConst(act->teachersNames)){
283 					(*statisticValues).teachersActivities.insert(t, ai);
284 					tmp=(*statisticValues).teachersTotalNumberOfHours.value(t)+act->duration;
285 					(*statisticValues).teachersTotalNumberOfHours.insert(t, tmp);							// (3)
286 					//subjectstTotalNumberOfHours2[act->subjectIndex]+=duration;				// (1) so teamteaching is counted twice!
287 				}
288 				for(const QString& st : qAsConst(act->studentsNames)){
289 					(*statisticValues).studentsActivities.insert(st, ai);
290 					tmp=(*statisticValues).studentsTotalNumberOfHours.value(st)+act->duration;
291 					(*statisticValues).studentsTotalNumberOfHours.insert(st, tmp);							// (2)
292 					//subjectstTotalNumberOfHours3[act->subjectIndex]+=duration;				// (1) so teamlearning is counted twice!
293 				}
294 				for(const QString& t : qAsConst(act->teachersNames)){
295 					tmp=(*statisticValues).teachersTotalNumberOfHours2.value(t);
296 					tmp += act->duration * act->studentsNames.count();
297 					(*statisticValues).teachersTotalNumberOfHours2.insert(t, tmp);						// (3)
298 				}
299 				for(const QString& st : qAsConst(act->studentsNames)){
300 					tmp=(*statisticValues).studentsTotalNumberOfHours2.value(st);
301 					tmp += act->duration * act->teachersNames.count();
302 					(*statisticValues).studentsTotalNumberOfHours2.insert(st, tmp);					// (2)
303 				}
304 				tmp=(*statisticValues).subjectsTotalNumberOfHours4.value(act->subjectName);
305 				tmp += act->duration * act->studentsNames.count() * act->teachersNames.count();
306 				(*statisticValues).subjectsTotalNumberOfHours4.insert(act->subjectName, tmp);			// (1) so teamlearning-teaching is counted twice!
307 			}
308 	}
309 	//progress.setValue(gt.rules.activitiesList.count());
310 	QStringList tmp;
311 	tmp.clear();
312 	for(const QString& students : qAsConst((*statisticValues).allStudentsNames)){
313 		if((*statisticValues).studentsTotalNumberOfHours.value(students)==0 && (*statisticValues).studentsTotalNumberOfHours2.value(students)==0){
314 			(*statisticValues).studentsTotalNumberOfHours.remove(students);
315 			(*statisticValues).studentsTotalNumberOfHours2.remove(students);
316 		} else
317 			tmp<<students;
318 	}
319 	(*statisticValues).allStudentsNames=tmp;
320 	tmp.clear();
321 	for(const QString& teachers : qAsConst((*statisticValues).allTeachersNames)){
322 		if((*statisticValues).teachersTotalNumberOfHours.value(teachers)==0 && (*statisticValues).teachersTotalNumberOfHours2.value(teachers)==0){
323 				(*statisticValues).teachersTotalNumberOfHours.remove(teachers);
324 				(*statisticValues).teachersTotalNumberOfHours2.remove(teachers);
325 		} else
326 			tmp<<teachers;
327 	}
328 	(*statisticValues).allTeachersNames=tmp;
329 	tmp.clear();
330 	for(const QString& subjects : qAsConst((*statisticValues).allSubjectsNames)){
331 		if((*statisticValues).subjectsTotalNumberOfHours.value(subjects)==0 && (*statisticValues).subjectsTotalNumberOfHours4.value(subjects)==0){
332 			(*statisticValues).subjectsTotalNumberOfHours.remove(subjects);
333 			(*statisticValues).subjectsTotalNumberOfHours4.remove(subjects);
334 		} else
335 			tmp<<subjects;
336 	}
337 	(*statisticValues).allSubjectsNames=tmp;
338 	tmp.clear();
339 }
340 
exportStatisticsStylesheetCss(QWidget * parent,QString saveTime,FetStatistics statisticValues)341 bool StatisticsExport::exportStatisticsStylesheetCss(QWidget* parent, QString saveTime, FetStatistics statisticValues){
342 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
343 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
344 
345 	if(s2.right(4)==".fet")
346 		s2=s2.left(s2.length()-4);
347 	//else if(INPUT_FILENAME_XML!="")
348 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
349 
350 	QString bar;
351 	if(INPUT_FILENAME_XML=="")
352 		bar="";
353 	else
354 		bar="_";
355 
356 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+STYLESHEET_STATISTICS;
357 
358 	QFile file(htmlfilename);
359 	if(!file.open(QIODevice::WriteOnly)){
360 		QMessageBox::critical(parent, tr("FET critical"),
361 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
362 		return false;
363 	}
364 	QTextStream tos(&file);
365 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
366 	tos.setEncoding(QStringConverter::Utf8);
367 #else
368 	tos.setCodec("UTF-8");
369 #endif
370 	tos.setGenerateByteOrderMark(true);
371 
372 	//get used students	//similar to timetableexport.cpp, so maybe use a function?
373 	QSet<QString> usedStudents;
374 	for(int i=0; i<gt.rules.activitiesList.size(); i++){
375 		for(const QString& st : qAsConst(gt.rules.activitiesList[i]->studentsNames)){
376 			if(gt.rules.activitiesList[i]->active){
377 				if(!usedStudents.contains(st))
378 					usedStudents<<st;
379 			}
380 		}
381 	}
382 
383 	tos<<"@charset \"UTF-8\";"<<"\n\n";
384 
385 	QString tt=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);
386 	if(INPUT_FILENAME_XML=="")
387 		tt=tr("unnamed");
388 	tos<<"/* "<<StatisticsExport::tr("CSS Stylesheet of %1", "%1 is the file name").arg(tt);
389 	tos<<"\n";
390 	tos<<"   "<<StatisticsExport::tr("Stylesheet generated with FET %1 on %2", "%1 is FET version, %2 is date and time").arg(FET_VERSION).arg(saveTime)<<" */\n\n";
391 
392 	tos<<"/* "<<StatisticsExport::tr("To hide an element just write the following phrase into the element: %1 (without quotes).",
393 		"%1 is a short phrase beginning and ending with quotes, and we want the user to be able to add it, but without quotes").arg("\"display:none;\"")<<" */\n\n";
394 	tos<<"table {\n  text-align: center;\n}\n\n";
395 	tos<<"table.detailed {\n  margin-left:auto; margin-right:auto;\n  text-align: center;\n  border: 0px;\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n";
396 	tos<<"caption {\n\n}\n\n";
397 	tos<<"thead {\n\n}\n\n";
398 
399 	//workaround begin.
400 	tos<<"/* "<<StatisticsExport::tr("Some programs import \"tfoot\" incorrectly. So we use \"tr.foot\" instead of \"tfoot\".",
401 	 "Please keep tfoot and tr.foot untranslated, as they are in the original English phrase")<<" */\n\n";
402 	//tos<<"tfoot {\n\n}\n\n";
403 	tos<<"tr.foot {\n\n}\n\n";
404 	//workaround end
405 
406 	tos<<"tbody {\n\n}\n\n";
407 	tos<<"th {\n\n}\n\n";
408 	tos<<"td {\n\n}\n\n";
409 	tos<<"td.detailed {\n  border: 1px dashed silver;\n  border-bottom: 0;\n  border-top: 0;\n}\n\n";
410 	if(TIMETABLE_HTML_LEVEL>=2){
411 		tos<<"th.xAxis {\n/*width: 8em; */\n}\n\n";
412 		tos<<"th.yAxis {\n  height: 8ex;\n}\n\n";
413 	}
414 	if(TIMETABLE_HTML_LEVEL>=4){ // must be written before LEVEL 3, because LEVEL 3 should have higher priority
415 		for(int i=0; i<gt.rules.subjectsList.size(); i++){
416 			tos << "span.s_"<<statisticValues.hashSubjectIDsStatistics.value(gt.rules.subjectsList[i]->name)<<" { /* subject "<<gt.rules.subjectsList[i]->name<<" */\n\n}\n\n";
417 		}
418 		if(TIMETABLE_HTML_PRINT_ACTIVITY_TAGS){
419 			for(int i=0; i<gt.rules.activityTagsList.size(); i++){
420 				if(gt.rules.activityTagsList[i]->printable)
421 					tos << "span.at_"<<statisticValues.hashActivityTagIDsStatistics.value(gt.rules.activityTagsList[i]->name)<<" { /* activity tag "<<gt.rules.activityTagsList[i]->name<<" */\n\n}\n\n";
422 			}
423 		}
424 		for(int i=0; i<gt.rules.yearsList.size(); i++){
425 			StudentsYear* sty=gt.rules.yearsList[i];
426 			if(usedStudents.contains(sty->name))
427 				tos << "span.ss_"<<statisticValues.hashStudentIDsStatistics.value(sty->name)<<" { /* students set "<<sty->name<<" */\n\n}\n\n";
428 			for(int j=0; j<sty->groupsList.size(); j++){
429 				StudentsGroup* stg=sty->groupsList[j];
430 				if(usedStudents.contains(stg->name))
431 					tos << "span.ss_"<<statisticValues.hashStudentIDsStatistics.value(stg->name)<<" { /* students set "<<stg->name<<" */\n\n}\n\n";
432 				for(int k=0; k<stg->subgroupsList.size(); k++){
433 					StudentsSubgroup* sts=stg->subgroupsList[k];
434 					if(usedStudents.contains(sts->name))
435 						tos << "span.ss_"<<statisticValues.hashStudentIDsStatistics.value(sts->name)<<" { /* students set "<<sts->name<<" */\n\n}\n\n";
436 				}
437 			}
438 		}
439 		for(int i=0; i<gt.rules.teachersList.size(); i++){
440 			tos << "span.t_"<<statisticValues.hashTeacherIDsStatistics.value(gt.rules.teachersList[i]->name)<<" { /* teacher "<<gt.rules.teachersList[i]->name<<" */\n\n}\n\n";
441 		}
442 		//for(int room=0; room<gt.rules.roomsList.size(); room++){
443 		//	tos << "span.r_"<<statisticValues.hashRoomIDsStatistics.value(gt.rules.roomsList[room]->name)<<" { /* room "<<gt.rules.roomsList[room]->name<<" */\n\n}\n\n";
444 		//}
445 	}
446 	if(TIMETABLE_HTML_LEVEL>=3){
447 		tos<<"span.subject {\n\n}\n\n";
448 		if(TIMETABLE_HTML_PRINT_ACTIVITY_TAGS){
449 			bool havePrintableActivityTag=false;
450 			for(ActivityTag* at : qAsConst(gt.rules.activityTagsList)){
451 				if(at->printable){
452 					havePrintableActivityTag=true;
453 					break;
454 				}
455 			}
456 			if(havePrintableActivityTag)
457 				tos<<"span.activitytag {\n\n}\n\n";
458 		}
459 		tos<<"span.empty {\n  color: gray;\n}\n\n";
460 		tos<<"td.empty {\n  border-color:silver;\n  border-right-style:none;\n  border-bottom-style:none;\n  border-left-style:dotted;\n  border-top-style:dotted;\n}\n\n";
461 		//tos<<"span.notAvailable {\n  color: gray;\n}\n\n";
462 		//tos<<"td.notAvailable {\n  border-color:silver;\n  border-right-style:none;\n  border-bottom-style:none;\n  border-left-style:dotted;\n  border-top-style:dotted;\n}\n\n";
463 		tos<<"tr.studentsset {\n\n}\n\n";
464 		tos<<"tr.teacher {\n\n}\n\n";
465 		//tos<<"td.room, div.room {\n\n}\n\n";
466 		tos<<"tr.duration {\n\n}\n\n";
467 		//tos<<"tr.line0 {\n  font-size: smaller;\n}\n\n";
468 		tos<<"tr.line1 {\n\n}\n\n";
469 		tos<<"tr.line2 {\n  font-size: smaller;\n  color: gray;\n}\n\n";
470 		//tos<<"tr.line3, div.line3 {\n  font-size: smaller;\n  color: silver;\n}\n\n";
471 	}
472 
473 	tos<<"/* "<<StatisticsExport::tr("End of file.")<<" */\n";
474 
475 	if(file.error()>0){
476 		QMessageBox::critical(parent, tr("FET critical"),
477 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
478 		return false;
479 	}
480 	file.close();
481 	return true;
482 }
483 
exportStatisticsIndex(QWidget * parent,QString saveTime)484 bool StatisticsExport::exportStatisticsIndex(QWidget* parent, QString saveTime){
485 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
486 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
487 
488 	if(s2.right(4)==".fet")
489 		s2=s2.left(s2.length()-4);
490 	//else if(INPUT_FILENAME_XML!="")
491 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
492 
493 	QString bar;
494 	if(INPUT_FILENAME_XML=="")
495 		bar="";
496 	else
497 		bar="_";
498 
499 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+INDEX_STATISTICS;
500 	QFile file(htmlfilename);
501 	if(!file.open(QIODevice::WriteOnly)){
502 		QMessageBox::critical(parent, tr("FET critical"),
503 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
504 		return false;
505 	}
506 	QTextStream tos(&file);
507 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
508 	tos.setEncoding(QStringConverter::Utf8);
509 #else
510 	tos.setCodec("UTF-8");
511 #endif
512 	tos.setGenerateByteOrderMark(true);
513 
514 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
515 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
516 
517 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
518 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
519 	else
520 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
521 	tos<<"  <head>\n";
522 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
523 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
524 
525 	if(TIMETABLE_HTML_LEVEL>=1){
526 		QString bar;
527 		if(INPUT_FILENAME_XML=="")
528 			bar="";
529 		else
530 			bar="_";
531 
532 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
533 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
534 	}
535 	if(TIMETABLE_HTML_LEVEL>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
536 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
537 		tos<<"    <script type=\"text/javascript\">\n";
538 		tos<<"      function highlight(classval) {\n";
539 		tos<<"        var spans = document.getElementsByTagName('span');\n";
540 		tos<<"        for(var i=0; spans.length>i; i++) {\n";
541 		tos<<"          if (spans[i].className == classval) {\n";
542 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
543 		tos<<"          } else {\n";
544 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
545 		tos<<"          }\n";
546 		tos<<"        }\n";
547 		tos<<"      }\n";
548 		tos<<"    </script>\n";
549 	}
550 	tos<<"  </head>\n\n";
551 
552 	tos<<"  <body>\n";
553 
554 	tos<<"    <table>\n      <tr align=\"left\" valign=\"top\">\n        <th>"+tr("Institution name")+":</th>\n        <td>"+protect2(gt.rules.institutionName)+"</td>\n      </tr>\n    </table>\n";
555 	tos<<"    <table>\n      <tr align=\"left\" valign=\"top\">\n        <th>"+tr("Comments")+":</th>\n        <td>"+protect2(gt.rules.comments).replace(QString("\n"), QString("<br />\n"))+"</td>\n      </tr>\n    </table>\n";
556 	tos<<"    <p>\n";
557 	tos<<"    </p>\n";
558 
559 	tos<<"    <table border=\"1\">\n";
560 	tos<<"      <caption>"<<protect2(gt.rules.institutionName)<<"</caption>\n";
561 	tos<<"      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\"3\">"+tr("Statistics")+"</th></tr>\n";
562 	tos<<"        <tr>\n          <!-- span -->\n";
563 	tos<<"          <th>"+tr("Teachers")+"</th><th>"+tr("Students")+"</th><th>"+tr("Subjects")+"</th>\n";
564 	tos<<"        </tr>\n";
565 	tos<<"      </thead>\n";
566 	tos<<"      <tbody>\n";
567 	tos<<"        <tr>\n";
568 	tos<<"          <th>"+tr("Teachers")+"</th>\n";
569 	tos<<"          <td>"<<protect2(STRING_EMPTY_SLOT_STATISTICS)<<"</td>\n";
570 	tos<<"          <td><a href=\""<<s2+bar+TEACHERS_STUDENTS_STATISTICS<<"\">"+tr("view")+"</a></td>\n";
571 	tos<<"          <td><a href=\""<<s2+bar+TEACHERS_SUBJECTS_STATISTICS<<"\">"+tr("view")+"</a></td>\n";
572 	tos<<"        </tr>\n";
573 	tos<<"        <tr>\n";
574 	tos<<"          <th>"+tr("Students")+"</th>\n";
575 	tos<<"          <td><a href=\""<<s2+bar+STUDENTS_TEACHERS_STATISTICS<<"\">"+tr("view")+"</a></td>\n";
576 	tos<<"          <td>"<<protect2(STRING_EMPTY_SLOT_STATISTICS)<<"</td>\n";
577 	tos<<"          <td><a href=\""<<s2+bar+STUDENTS_SUBJECTS_STATISTICS<<"\">"+tr("view")+"</a></td>\n";
578 	tos<<"        </tr>\n";
579 	tos<<"        <tr>\n";
580 	tos<<"          <th>"+tr("Subjects")+"</th>\n";
581 	tos<<"          <td><a href=\""<<s2+bar+SUBJECTS_TEACHERS_STATISTICS<<"\">"+tr("view")+"</a></td>\n";
582 	tos<<"          <td><a href=\""<<s2+bar+SUBJECTS_STUDENTS_STATISTICS<<"\">"+tr("view")+"</a></td>\n";
583 	tos<<"          <td>"<<protect2(STRING_EMPTY_SLOT_STATISTICS)<<"</td>\n";
584 	tos<<"        </tr>\n";
585 	//workaround begin.
586 	tos<<"      <tr class=\"foot\"><td></td><td colspan=\"3\">"<<StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)<<"</td></tr>\n";
587 	//workaround end.
588 	tos<<"      </tbody>\n";
589 	tos<<"    </table>\n";
590 	tos<<"  </body>\n</html>\n";
591 
592 	if(file.error()>0){
593 		QMessageBox::critical(parent, tr("FET critical"),
594 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
595 		return false;
596 	}
597 	file.close();
598 	return true;
599 }
600 
exportStatisticsTeachersSubjects(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel)601 bool StatisticsExport::exportStatisticsTeachersSubjects(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel){
602 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
603 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
604 
605 	if(s2.right(4)==".fet")
606 		s2=s2.left(s2.length()-4);
607 	//else if(INPUT_FILENAME_XML!="")
608 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
609 
610 	QString bar;
611 	if(INPUT_FILENAME_XML=="")
612 		bar="";
613 	else
614 		bar="_";
615 
616 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+TEACHERS_SUBJECTS_STATISTICS;
617 	QFile file(htmlfilename);
618 	if(!file.open(QIODevice::WriteOnly)){
619 		QMessageBox::critical(parent, tr("FET critical"),
620 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
621 		return false;
622 	}
623 	QTextStream tos(&file);
624 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
625 	tos.setEncoding(QStringConverter::Utf8);
626 #else
627 	tos.setCodec("UTF-8");
628 #endif
629 	tos.setGenerateByteOrderMark(true);
630 
631 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
632 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
633 
634 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
635 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
636 	else
637 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
638 	tos<<"  <head>\n";
639 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
640 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
641 	if(htmlLevel>=1){
642 		QString bar;
643 		if(INPUT_FILENAME_XML=="")
644 			bar="";
645 		else
646 			bar="_";
647 
648 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
649 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
650 	}
651 	if(htmlLevel>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
652 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
653 		tos<<"    <script type=\"text/javascript\">\n";
654 		tos<<"      function highlight(classval) {\n";
655 		tos<<"        var spans = document.getElementsByTagName('span');\n";
656 		tos<<"        for(var i=0;spans.length>i;i++) {\n";
657 		tos<<"          if (spans[i].className == classval) {\n";
658 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
659 		tos<<"          } else {\n";
660 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
661 		tos<<"          }\n";
662 		tos<<"        }\n";
663 		tos<<"      }\n";
664 		tos<<"    </script>\n";
665 	}
666 	tos<<"  </head>\n\n";
667 
668 	tos<<"  <body>\n";
669 	QSet<int> tmpSet;
670 	tos<<exportStatisticsTeachersSubjectsHtml(parent, saveTime, statisticValues, htmlLevel, TIMETABLE_HTML_PRINT_ACTIVITY_TAGS, statisticValues.allTeachersNames.count(), &tmpSet);
671 	tos<<"  </body>\n</html>\n";
672 
673 	if(file.error()>0){
674 		QMessageBox::critical(parent, tr("FET critical"),
675 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
676 		return false;
677 	}
678 	file.close();
679 	return true;
680 }
681 
exportStatisticsTeachersSubjectsHtml(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel,bool printActivityTags,int maxNames,QSet<int> * excludedNames)682 QString StatisticsExport::exportStatisticsTeachersSubjectsHtml(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel, bool printActivityTags, int maxNames, QSet<int> *excludedNames){
683 	int colspan=0;
684 	for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && colspan<maxNames; teacher++){
685 		if(!(*excludedNames).contains(teacher)){
686 			colspan++;
687 		}
688 	}
689 	QString tmp;
690 	tmp+="    <table border=\"1\">\n";
691 	tmp+="      <caption>"+protect2(gt.rules.institutionName)+"</caption>\n";
692 	tmp+="      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\""+QString::number(colspan+1)+"\">"+tr("Teachers - Subjects Matrix")+"</th></tr>\n";
693 	tmp+="        <tr>\n          <!-- span -->\n";
694 	int currentCount=0;
695 	for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && currentCount<maxNames; teacher++){
696 		if(!(*excludedNames).contains(teacher)){
697 			currentCount++;
698 			if(htmlLevel>=2)
699 				tmp+="          <th class=\"xAxis\">";
700 			else
701 				tmp+="          <th>";
702 			tmp+=protect2(statisticValues.allTeachersNames.at(teacher))+"</th>\n";
703 		}
704 	}
705 	if(htmlLevel>=2)
706 		tmp+="          <th class=\"xAxis\">";
707 	else
708 		tmp+="          <th>";
709 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
710 	tmp+="        </tr>\n";
711 	tmp+="      </thead>\n";
712 	tmp+="      <tbody>\n";
713 
714 	QProgressDialog progress(parent);
715 	progress.setWindowTitle(tr("Exporting statistics", "Title of a progress dialog"));
716 	progress.setLabelText(tr("Processing teachers with subjects...please wait"));
717 	progress.setRange(0, qMax(statisticValues.allSubjectsNames.count(), 1));
718 	progress.setModal(true);
719 
720 	int ttt=0;
721 
722 	for(const QString& subjects : qAsConst(statisticValues.allSubjectsNames)){
723 		progress.setValue(ttt);
724 		//pqapplication->processEvents();
725 		if(progress.wasCanceled()){
726 			progress.setValue(statisticValues.allSubjectsNames.count());
727 			QMessageBox::warning(parent, tr("FET warning"), tr("Canceled"));
728 			return /*false*/ tmp;
729 		}
730 		ttt++;
731 
732 		QList<int> tmpSubjects;
733 		QMultiHash<QString, int> tmpTeachers;
734 		tmpSubjects.clear();
735 		tmpTeachers.clear();
736 		tmpSubjects=statisticValues.subjectsActivities.values(subjects);
737 		for(int aidx : qAsConst(tmpSubjects)){
738 			Activity* act=gt.rules.activitiesList.at(aidx);
739 			for(const QString& teacher : qAsConst(act->teachersNames)){
740 				tmpTeachers.insert(teacher, aidx);
741 			}
742 		}
743 		tmp+="        <tr>\n";
744 		if(htmlLevel>=2)
745 			tmp+="          <th class=\"yAxis\">";
746 		else
747 			tmp+="          <th>";
748 		tmp+=protect2(subjects)+"</th>\n";
749 		currentCount=0;
750 		for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && currentCount<maxNames; teacher++){
751 			if(!(*excludedNames).contains(teacher)){
752 				currentCount++;
753 				QList<int> tmpActivities;
754 				tmpActivities.clear();
755 				tmpActivities=tmpTeachers.values(statisticValues.allTeachersNames.at(teacher));
756 				if(tmpActivities.isEmpty()){
757 					switch(htmlLevel){
758 						case 3 : ;
759 						case 4 : tmp+="          <td class=\"empty\"><span class=\"empty\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
760 						case 5 : ;
761 						case 6 : tmp+="          <td class=\"empty\"><span class=\"empty\" onmouseover=\"highlight('empty')\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
762 						default: tmp+="          <td>"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</td>\n";
763 					}
764 				} else {
765 					//optimized by Liviu Lalescu - 1
766 					QMap<StringListPair, int> durationMap;
767 					for(int tmpAct : qAsConst(tmpActivities)){
768 						Activity* act=gt.rules.activitiesList.at(tmpAct);
769 						StringListPair slp;
770 						slp.list1=act->studentsNames;
771 
772 						slp.list2.clear();
773 						if(printActivityTags){
774 							for(const QString& at : qAsConst(act->activityTagsNames)){
775 								int id=statisticValues.hashActivityTagIDsStatistics.value(at, "0").toInt()-1;
776 								assert(id>=0);
777 								assert(id<gt.rules.activityTagsList.count());
778 								if(gt.rules.activityTagsList[id]->printable)
779 									slp.list2.append(at);
780 							}
781 						}
782 						//slp.list2=act->activityTagsNames;
783 
784 						int dur=durationMap.value(slp, 0);
785 						dur+=act->duration;
786 						durationMap.insert(slp, dur);
787 					}
788 
789 					if(htmlLevel>=1)
790 						tmp+="          <td><table class=\"detailed\">";
791 					else
792 						tmp+="          <td><table>";
793 					if(htmlLevel>=3)
794 						tmp+="<tr class=\"duration line1\">";
795 					else	tmp+="<tr>";
796 
797 					QMap<StringListPair, int>::const_iterator it=durationMap.constBegin();
798 					while(it!=durationMap.constEnd()){
799 						if(htmlLevel>=1)
800 							tmp+="<td class=\"detailed\">";
801 						else
802 							tmp+="<td>";
803 						tmp+=QString::number(it.value())+"</td>";
804 						it++;
805 					}
806 
807 					tmp+="</tr>";
808 					if(htmlLevel>=3)
809 						tmp+="<tr class=\"studentsset line2\">";
810 					else	tmp+="<tr>";
811 
812 					it=durationMap.constBegin();
813 					while(it!=durationMap.constEnd()){
814 						if(htmlLevel>=1)
815 							tmp+="<td class=\"detailed\">";
816 						else
817 							tmp+="<td>";
818 
819 						const StringListPair& slp=it.key();
820 						const QStringList& studentsNames=slp.list1;
821 						const QStringList& activityTagsNames=slp.list2;
822 						QString tmpSt=QString("");
823 						if(studentsNames.size()>0||activityTagsNames.size()>0){
824 							for(QStringList::const_iterator st=studentsNames.constBegin(); st!=studentsNames.constEnd(); st++){
825 								switch(htmlLevel){
826 									case 4 : tmpSt+="<span class=\"ss_"+statisticValues.hashStudentIDsStatistics.value(*st)+"\">"+protect2(*st)+"</span>"; break;
827 									case 5 : ;
828 									case 6 : tmpSt+="<span class=\"ss_"+statisticValues.hashStudentIDsStatistics.value(*st)+"\" onmouseover=\"highlight('ss_"+statisticValues.hashStudentIDsStatistics.value(*st)+"')\">"+protect2(*st)+"</span>"; break;
829 									default: tmpSt+=protect2(*st); break;
830 									}
831 								if(st!=studentsNames.constEnd()-1)
832 									tmpSt+=", ";
833 							}
834 							if(printActivityTags){
835 								for(QStringList::const_iterator atn=activityTagsNames.constBegin(); atn!=activityTagsNames.constEnd(); atn++){
836 									assert(statisticValues.hashActivityTagIDsStatistics.contains(*atn));
837 									int id=statisticValues.hashActivityTagIDsStatistics.value(*atn, "0").toInt()-1;
838 									assert(id>=0);
839 									assert(id<statisticValues.hashActivityTagIDsStatistics.count());
840 									if(gt.rules.activityTagsList[id]->printable){
841 										switch(htmlLevel){
842 											case 3 : tmpSt+=" <span class=\"activitytag\">"+protect2(*atn)+"</span>"; break;
843 											case 4 : tmpSt+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\">"+protect2(*atn)+"</span></span>"; break;
844 											case 5 : ;
845 											case 6 : tmpSt+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\" onmouseover=\"highlight('at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"')\">"+protect2(*atn)+"</span></span>"; break;
846 											default: tmpSt+=" "+protect2(*atn); break;
847 										}
848 										tmpSt+=", ";
849 									}
850 								}
851 								if(tmpSt.endsWith(", ")){
852 									tmpSt.remove(tmpSt.size()-2, 2);
853 								}
854 							}
855 							if(tmpSt=="")
856 								tmpSt=" ";
857 						} else
858 							tmpSt=" ";
859 
860 						tmp+=tmpSt;
861 						tmp+="</td>";
862 						it++;
863 					}
864 
865 					tmp+="</tr>";
866 					tmp+="</table></td>\n";
867 				}
868 			}
869 		}
870 		tmp+="          <th>";
871 		tmp+=CustomFETString::number(statisticValues.subjectsTotalNumberOfHours.value(subjects));
872 		if(statisticValues.subjectsTotalNumberOfHours.value(subjects)!=statisticValues.subjectsTotalNumberOfHours4.value(subjects))
873 			tmp+="<br />("+CustomFETString::number(statisticValues.subjectsTotalNumberOfHours4.value(subjects))+")";
874 		tmp+="</th>\n";
875 		tmp+="        </tr>\n";
876 	}
877 
878 	progress.setValue(qMax(statisticValues.allSubjectsNames.count(), 1));
879 
880 	tmp+="        <tr>\n";
881 	if(htmlLevel>=2)
882 		tmp+="          <th class=\"xAxis\">";
883 	else
884 		tmp+="          <th>";
885 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
886 	currentCount=0;
887 	for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && currentCount<maxNames; teacher++){
888 		if(!(*excludedNames).contains(teacher)){
889 			currentCount++;
890 			tmp+="          <th>"+CustomFETString::number(statisticValues.teachersTotalNumberOfHours.value(statisticValues.allTeachersNames.at(teacher)));
891 			if(statisticValues.teachersTotalNumberOfHours.value(statisticValues.allTeachersNames.at(teacher))!=statisticValues.teachersTotalNumberOfHours2.value(statisticValues.allTeachersNames.at(teacher)))
892 				tmp+="<br />("+CustomFETString::number(statisticValues.teachersTotalNumberOfHours2.value(statisticValues.allTeachersNames.at(teacher)))+")";
893 			tmp+="</th>\n";
894 			*excludedNames<<teacher;
895 		}
896 	}
897 	tmp+="          <th></th>\n        </tr>\n";
898 	//workaround begin.
899 	tmp+="      <tr class=\"foot\"><td></td><td colspan=\""+QString::number(colspan+1)+"\">"+StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)+"</td></tr>\n";
900 	//workaround end.
901 	tmp+="      </tbody>\n";
902 	tmp+="    </table>\n";
903 	return tmp;
904 }
905 
exportStatisticsSubjectsTeachers(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel)906 bool StatisticsExport::exportStatisticsSubjectsTeachers(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel){
907 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
908 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
909 
910 	if(s2.right(4)==".fet")
911 		s2=s2.left(s2.length()-4);
912 	//else if(INPUT_FILENAME_XML!="")
913 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
914 
915 	QString bar;
916 	if(INPUT_FILENAME_XML=="")
917 		bar="";
918 	else
919 		bar="_";
920 
921 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+SUBJECTS_TEACHERS_STATISTICS;
922 
923 	QFile file(htmlfilename);
924 	if(!file.open(QIODevice::WriteOnly)){
925 		QMessageBox::critical(parent, tr("FET critical"),
926 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
927 		return false;
928 	}
929 	QTextStream tos(&file);
930 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
931 	tos.setEncoding(QStringConverter::Utf8);
932 #else
933 	tos.setCodec("UTF-8");
934 #endif
935 	tos.setGenerateByteOrderMark(true);
936 
937 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
938 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
939 
940 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
941 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
942 	else
943 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
944 	tos<<"  <head>\n";
945 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
946 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
947 	if(htmlLevel>=1){
948 		QString bar;
949 		if(INPUT_FILENAME_XML=="")
950 			bar="";
951 		else
952 			bar="_";
953 
954 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
955 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
956 	}
957 	if(htmlLevel>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
958 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
959 		tos<<"    <script type=\"text/javascript\">\n";
960 		tos<<"      function highlight(classval) {\n";
961 		tos<<"        var spans = document.getElementsByTagName('span');\n";
962 		tos<<"        for(var i=0;spans.length>i;i++) {\n";
963 		tos<<"          if (spans[i].className == classval) {\n";
964 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
965 		tos<<"          } else {\n";
966 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
967 		tos<<"          }\n";
968 		tos<<"        }\n";
969 		tos<<"      }\n";
970 		tos<<"    </script>\n";
971 	}
972 	tos<<"  </head>\n\n";
973 
974 	tos<<"  <body>\n";
975 	QSet<int> tmpSet;
976 	tos<<StatisticsExport::exportStatisticsSubjectsTeachersHtml(parent, saveTime, statisticValues, htmlLevel, TIMETABLE_HTML_PRINT_ACTIVITY_TAGS, statisticValues.allSubjectsNames.count(), &tmpSet);
977 	tos<<"  </body>\n</html>\n";
978 
979 	if(file.error()>0){
980 		QMessageBox::critical(parent, tr("FET critical"),
981 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
982 		return false;
983 	}
984 	file.close();
985 	return true;
986 }
987 
exportStatisticsSubjectsTeachersHtml(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel,bool printActivityTags,int maxNames,QSet<int> * excludedNames)988 QString StatisticsExport::exportStatisticsSubjectsTeachersHtml(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel, bool printActivityTags, int maxNames, QSet<int> *excludedNames){
989 	int colspan=0;
990 	for(int subject=0; subject<statisticValues.allSubjectsNames.count() && colspan<maxNames; subject++){
991 		if(!(*excludedNames).contains(subject)){
992 			colspan++;
993 		}
994 	}
995 	QString tmp;
996 	tmp+="    <table border=\"1\">\n";
997 	tmp+="      <caption>"+protect2(gt.rules.institutionName)+"</caption>\n";
998 	tmp+="      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\""+QString::number(colspan+1)+"\">"+tr("Subjects - Teachers Matrix")+"</th></tr>\n";
999 	tmp+="        <tr>\n          <!-- span -->\n";
1000 	int currentCount=0;
1001 	for(int subject=0; subject<statisticValues.allSubjectsNames.count() && currentCount<maxNames; subject++){
1002 		if(!(*excludedNames).contains(subject)){
1003 			currentCount++;
1004 			if(htmlLevel>=2)
1005 				tmp+="          <th class=\"xAxis\">";
1006 			else
1007 				tmp+="          <th>";
1008 			tmp+=protect2(statisticValues.allSubjectsNames.at(subject))+"</th>\n";
1009 		}
1010 	}
1011 	if(htmlLevel>=2)
1012 		tmp+="          <th class=\"xAxis\">";
1013 	else
1014 		tmp+="          <th>";
1015 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1016 	tmp+="        </tr>\n";
1017 	tmp+="      </thead>\n";
1018 	tmp+="      <tbody>\n";
1019 
1020 	QProgressDialog progress(parent);
1021 	progress.setWindowTitle(tr("Exporting statistics", "Title of a progress dialog"));
1022 	progress.setLabelText(tr("Processing subjects with teachers...please wait"));
1023 	progress.setRange(0, qMax(statisticValues.allTeachersNames.count(), 1));
1024 	progress.setModal(true);
1025 
1026 	int ttt=0;
1027 
1028 	for(const QString& teachers : qAsConst(statisticValues.allTeachersNames)){
1029 		progress.setValue(ttt);
1030 		//pqapplication->processEvents();
1031 		if(progress.wasCanceled()){
1032 			progress.setValue(statisticValues.allTeachersNames.count());
1033 			QMessageBox::warning(parent, tr("FET warning"), tr("Canceled"));
1034 			return /*false*/tmp;
1035 		}
1036 		ttt++;
1037 
1038 		QList<int> tmpTeachers;
1039 		QMultiHash<QString, int> tmpSubjects;
1040 		tmpTeachers.clear();
1041 		tmpSubjects.clear();
1042 		tmpTeachers=statisticValues.teachersActivities.values(teachers);
1043 		for(int aidx : qAsConst(tmpTeachers)){
1044 			Activity* act=gt.rules.activitiesList.at(aidx);
1045 			tmpSubjects.insert(act->subjectName, aidx);
1046 		}
1047 		tmp+="        <tr>\n";
1048 		if(htmlLevel>=2)
1049 			tmp+="          <th class=\"yAxis\">";
1050 		else
1051 			tmp+="          <th>";
1052 		tmp+=protect2(teachers)+"</th>\n";
1053 		currentCount=0;
1054 		for(int subject=0; subject<statisticValues.allSubjectsNames.count() && currentCount<maxNames; subject++){
1055 			if(!(*excludedNames).contains(subject)){
1056 				currentCount++;
1057 				QList<int> tmpActivities;
1058 				tmpActivities.clear();
1059 				tmpActivities=tmpSubjects.values(statisticValues.allSubjectsNames.at(subject));
1060 				if(tmpActivities.isEmpty()){
1061 					switch(htmlLevel){
1062 						case 3 : ;
1063 						case 4 : tmp+="          <td class=\"empty\"><span class=\"empty\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1064 						case 5 : ;
1065 						case 6 : tmp+="          <td class=\"empty\"><span class=\"empty\" onmouseover=\"highlight('empty')\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1066 						default: tmp+="          <td>"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</td>\n";
1067 					}
1068 				} else {
1069 					//optimized by Liviu Lalescu - 2
1070 					QMap<StringListPair, int> durationMap;
1071 					for(int tmpAct : qAsConst(tmpActivities)){
1072 						Activity* act=gt.rules.activitiesList.at(tmpAct);
1073 						StringListPair slp;
1074 						slp.list1=act->studentsNames;
1075 
1076 						slp.list2.clear();
1077 						if(printActivityTags){
1078 							for(const QString& at : qAsConst(act->activityTagsNames)){
1079 								int id=statisticValues.hashActivityTagIDsStatistics.value(at, "0").toInt()-1;
1080 								assert(id>=0);
1081 								assert(id<gt.rules.activityTagsList.count());
1082 								if(gt.rules.activityTagsList[id]->printable)
1083 									slp.list2.append(at);
1084 							}
1085 						}
1086 						//slp.list2=act->activityTagsNames;
1087 
1088 						int dur=durationMap.value(slp, 0);
1089 						dur+=act->duration;
1090 						durationMap.insert(slp, dur);
1091 					}
1092 
1093 					if(htmlLevel>=1)
1094 						tmp+="          <td><table class=\"detailed\">";
1095 					else
1096 						tmp+="          <td><table>";
1097 					if(htmlLevel>=3)
1098 						tmp+="<tr class=\"duration line1\">";
1099 					else	tmp+="<tr>";
1100 
1101 					QMap<StringListPair, int>::const_iterator it=durationMap.constBegin();
1102 					while(it!=durationMap.constEnd()){
1103 						if(htmlLevel>=1)
1104 							tmp+="<td class=\"detailed\">";
1105 						else
1106 							tmp+="<td>";
1107 						tmp+=QString::number(it.value())+"</td>";
1108 						it++;
1109 					}
1110 
1111 					tmp+="</tr>";
1112 					if(htmlLevel>=3)
1113 						tmp+="<tr class=\"studentsset line2\">";
1114 					else	tmp+="<tr>";
1115 
1116 					it=durationMap.constBegin();
1117 					while(it!=durationMap.constEnd()){
1118 						if(htmlLevel>=1)
1119 							tmp+="<td class=\"detailed\">";
1120 						else
1121 							tmp+="<td>";
1122 
1123 						const StringListPair& slp=it.key();
1124 						const QStringList& studentsNames=slp.list1;
1125 						const QStringList& activityTagsNames=slp.list2;
1126 						QString tmpSt=QString("");
1127 						if(studentsNames.size()>0||activityTagsNames.size()>0){
1128 							for(QStringList::const_iterator st=studentsNames.constBegin(); st!=studentsNames.constEnd(); st++){
1129 								switch(htmlLevel){
1130 									case 4 : tmpSt+="<span class=\"ss_"+statisticValues.hashStudentIDsStatistics.value(*st)+"\">"+protect2(*st)+"</span>"; break;
1131 									case 5 : ;
1132 									case 6 : tmpSt+="<span class=\"ss_"+statisticValues.hashStudentIDsStatistics.value(*st)+"\" onmouseover=\"highlight('ss_"+statisticValues.hashStudentIDsStatistics.value(*st)+"')\">"+protect2(*st)+"</span>"; break;
1133 									default: tmpSt+=protect2(*st); break;
1134 									}
1135 								if(st!=studentsNames.constEnd()-1)
1136 									tmpSt+=", ";
1137 							}
1138 							if(printActivityTags){
1139 								for(QStringList::const_iterator atn=activityTagsNames.constBegin(); atn!=activityTagsNames.constEnd(); atn++){
1140 									assert(statisticValues.hashActivityTagIDsStatistics.contains(*atn));
1141 									int id=statisticValues.hashActivityTagIDsStatistics.value(*atn, "0").toInt()-1;
1142 									assert(id>=0);
1143 									assert(id<statisticValues.hashActivityTagIDsStatistics.count());
1144 									if(gt.rules.activityTagsList[id]->printable){
1145 										switch(htmlLevel){
1146 											case 3 : tmpSt+=" <span class=\"activitytag\">"+protect2(*atn)+"</span>"; break;
1147 											case 4 : tmpSt+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\">"+protect2(*atn)+"</span></span>"; break;
1148 											case 5 : ;
1149 											case 6 : tmpSt+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\" onmouseover=\"highlight('at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"')\">"+protect2(*atn)+"</span></span>"; break;
1150 											default: tmpSt+=" "+protect2(*atn); break;
1151 										}
1152 										tmpSt+=", ";
1153 									}
1154 								}
1155 								if(tmpSt.endsWith(", ")){
1156 									tmpSt.remove(tmpSt.size()-2, 2);
1157 								}
1158 							}
1159 							if(tmpSt=="")
1160 								tmpSt=" ";
1161 						} else
1162 							tmpSt=" ";
1163 						tmp+=tmpSt;
1164 
1165 						tmp+="</td>";
1166 						it++;
1167 					}
1168 
1169 					tmp+="</tr>";
1170 					tmp+="</table></td>\n";
1171 				}
1172 			}
1173 		}
1174 		tmp+="          <th>";
1175 		tmp+=CustomFETString::number(statisticValues.teachersTotalNumberOfHours.value(teachers));
1176 		if(statisticValues.teachersTotalNumberOfHours.value(teachers)!=statisticValues.teachersTotalNumberOfHours2.value(teachers))
1177 			tmp+="<br />("+CustomFETString::number(statisticValues.teachersTotalNumberOfHours2.value(teachers))+")";
1178 		tmp+="</th>\n";
1179 		tmp+="        </tr>\n";
1180 	}
1181 
1182 	progress.setValue(qMax(statisticValues.allTeachersNames.count(), 1));
1183 
1184 	tmp+="        <tr>\n";
1185 	if(htmlLevel>=2)
1186 		tmp+="          <th class=\"xAxis\">";
1187 	else
1188 		tmp+="          <th>";
1189 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1190 	currentCount=0;
1191 	for(int subject=0; subject<statisticValues.allSubjectsNames.count() && currentCount<maxNames; subject++){
1192 		if(!(*excludedNames).contains(subject)){
1193 			currentCount++;
1194 			tmp+="          <th>"+CustomFETString::number(statisticValues.subjectsTotalNumberOfHours.value(statisticValues.allSubjectsNames.at(subject)));
1195 			if(statisticValues.subjectsTotalNumberOfHours.value(statisticValues.allSubjectsNames.at(subject))!=statisticValues.subjectsTotalNumberOfHours4.value(statisticValues.allSubjectsNames.at(subject)))
1196 				tmp+="<br />("+CustomFETString::number(statisticValues.subjectsTotalNumberOfHours4.value(statisticValues.allSubjectsNames.at(subject)))+")";
1197 			tmp+="</th>\n";
1198 			*excludedNames<<subject;
1199 		}
1200 	}
1201 	tmp+="          <th></th>\n        </tr>\n";
1202 	//workaround begin.
1203 	tmp+="      <tr class=\"foot\"><td></td><td colspan=\""+QString::number(colspan+1)+"\">"+StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)+"</td></tr>\n";
1204 	//workaround end.
1205 	tmp+="      </tbody>\n";
1206 	tmp+="    </table>\n";
1207 	return tmp;
1208 }
1209 
exportStatisticsTeachersStudents(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel)1210 bool StatisticsExport::exportStatisticsTeachersStudents(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel){
1211 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
1212 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
1213 
1214 	if(s2.right(4)==".fet")
1215 		s2=s2.left(s2.length()-4);
1216 	//else if(INPUT_FILENAME_XML!="")
1217 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
1218 
1219 	QString bar;
1220 	if(INPUT_FILENAME_XML=="")
1221 		bar="";
1222 	else
1223 		bar="_";
1224 
1225 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+TEACHERS_STUDENTS_STATISTICS;
1226 
1227 	QFile file(htmlfilename);
1228 	if(!file.open(QIODevice::WriteOnly)){
1229 		QMessageBox::critical(parent, tr("FET critical"),
1230 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
1231 		return false;
1232 	}
1233 	QTextStream tos(&file);
1234 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
1235 	tos.setEncoding(QStringConverter::Utf8);
1236 #else
1237 	tos.setCodec("UTF-8");
1238 #endif
1239 	tos.setGenerateByteOrderMark(true);
1240 
1241 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
1242 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
1243 
1244 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
1245 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
1246 	else
1247 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
1248 	tos<<"  <head>\n";
1249 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
1250 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
1251 	if(htmlLevel>=1){
1252 		QString bar;
1253 		if(INPUT_FILENAME_XML=="")
1254 			bar="";
1255 		else
1256 			bar="_";
1257 
1258 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
1259 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
1260 	}
1261 	if(htmlLevel>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
1262 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
1263 		tos<<"    <script type=\"text/javascript\">\n";
1264 		tos<<"      function highlight(classval) {\n";
1265 		tos<<"        var spans = document.getElementsByTagName('span');\n";
1266 		tos<<"        for(var i=0;spans.length>i;i++) {\n";
1267 		tos<<"          if (spans[i].className == classval) {\n";
1268 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
1269 		tos<<"          } else {\n";
1270 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
1271 		tos<<"          }\n";
1272 		tos<<"        }\n";
1273 		tos<<"      }\n";
1274 		tos<<"    </script>\n";
1275 	}
1276 	tos<<"  </head>\n\n";
1277 
1278 	tos<<"  <body>\n";
1279 	QSet<int> tmpSet;
1280 	tos<<StatisticsExport::exportStatisticsTeachersStudentsHtml(parent, saveTime, statisticValues, htmlLevel, TIMETABLE_HTML_PRINT_ACTIVITY_TAGS, statisticValues.allTeachersNames.count(), &tmpSet);
1281 	tos<<"  </body>\n</html>\n";
1282 
1283 	if(file.error()>0){
1284 		QMessageBox::critical(parent, tr("FET critical"),
1285 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
1286 		return false;
1287 	}
1288 	file.close();
1289 	return true;
1290 }
1291 
exportStatisticsTeachersStudentsHtml(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel,bool printActivityTags,int maxNames,QSet<int> * excludedNames)1292 QString StatisticsExport::exportStatisticsTeachersStudentsHtml(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel, bool printActivityTags, int maxNames, QSet<int> *excludedNames){
1293 	int colspan=0;
1294 	for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && colspan<maxNames; teacher++){
1295 		if(!(*excludedNames).contains(teacher)){
1296 			colspan++;
1297 		}
1298 	}
1299 	QString tmp;
1300 	tmp+="    <table border=\"1\">\n";
1301 	tmp+="      <caption>"+protect2(gt.rules.institutionName)+"</caption>\n";
1302 	tmp+="      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\""+QString::number(colspan+1)+"\">"+tr("Teachers - Students Matrix")+"</th></tr>\n";
1303 	tmp+="        <tr>\n          <!-- span -->\n";
1304 	int currentCount=0;
1305 	for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && currentCount<maxNames; teacher++){
1306 		if(!(*excludedNames).contains(teacher)){
1307 			currentCount++;
1308 			if(htmlLevel>=2)
1309 				tmp+="          <th class=\"xAxis\">";
1310 			else
1311 				tmp+="          <th>";
1312 			tmp+=protect2(statisticValues.allTeachersNames.at(teacher))+"</th>\n";
1313 		}
1314 	}
1315 	if(htmlLevel>=2)
1316 		tmp+="          <th class=\"xAxis\">";
1317 	else
1318 		tmp+="          <th>";
1319 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1320 	tmp+="        </tr>\n";
1321 	tmp+="      </thead>\n";
1322 	tmp+="      <tbody>\n";
1323 
1324 	QProgressDialog progress(parent);
1325 	progress.setWindowTitle(tr("Exporting statistics", "Title of a progress dialog"));
1326 	progress.setLabelText(tr("Processing teachers with students...please wait"));
1327 	progress.setRange(0, qMax(statisticValues.allStudentsNames.count(), 1));
1328 	progress.setModal(true);
1329 
1330 	int ttt=0;
1331 
1332 	for(const QString& students : qAsConst(statisticValues.allStudentsNames)){
1333 		progress.setValue(ttt);
1334 		//pqapplication->processEvents();
1335 		if(progress.wasCanceled()){
1336 			progress.setValue(statisticValues.allStudentsNames.count());
1337 			QMessageBox::warning(parent, tr("FET warning"), tr("Canceled"));
1338 			return /*false*/tmp;
1339 		}
1340 		ttt++;
1341 
1342 		QList<int> tmpStudents;
1343 		QMultiHash<QString, int> tmpTeachers;
1344 		tmpStudents.clear();
1345 		tmpTeachers.clear();
1346 		tmpStudents=statisticValues.studentsActivities.values(students);
1347 		for(int aidx : qAsConst(tmpStudents)){
1348 			Activity* act=gt.rules.activitiesList.at(aidx);
1349 			for(const QString& teacher : qAsConst(act->teachersNames)){
1350 				tmpTeachers.insert(teacher, aidx);
1351 			}
1352 		}
1353 		tmp+="        <tr>\n";
1354 		if(htmlLevel>=2)
1355 			tmp+="          <th class=\"yAxis\">";
1356 		else
1357 			tmp+="          <th>";
1358 		tmp+=protect2(students)+"</th>\n";
1359 		currentCount=0;
1360 		for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && currentCount<maxNames; teacher++){
1361 			if(!(*excludedNames).contains(teacher)){
1362 				currentCount++;
1363 				QList<int> tmpActivities;
1364 				tmpActivities.clear();
1365 				tmpActivities=tmpTeachers.values(statisticValues.allTeachersNames.at(teacher));
1366 				if(tmpActivities.isEmpty()){
1367 					switch(htmlLevel){
1368 						case 3 : ;
1369 						case 4 : tmp+="          <td class=\"empty\"><span class=\"empty\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1370 						case 5 : ;
1371 						case 6 : tmp+="          <td class=\"empty\"><span class=\"empty\" onmouseover=\"highlight('empty')\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1372 						default: tmp+="          <td>"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</td>\n";
1373 					}
1374 				} else {
1375 					//optimized by Liviu Lalescu - 3
1376 					QMap<StringListPair, int> durationMap;
1377 					for(int tmpAct : qAsConst(tmpActivities)){
1378 						Activity* act=gt.rules.activitiesList.at(tmpAct);
1379 						StringListPair slp;
1380 						slp.list1=QStringList(act->subjectName);
1381 
1382 						slp.list2.clear();
1383 						if(printActivityTags){
1384 							for(const QString& at : qAsConst(act->activityTagsNames)){
1385 								int id=statisticValues.hashActivityTagIDsStatistics.value(at, "0").toInt()-1;
1386 								assert(id>=0);
1387 								assert(id<gt.rules.activityTagsList.count());
1388 								if(gt.rules.activityTagsList[id]->printable)
1389 									slp.list2.append(at);
1390 							}
1391 						}
1392 						//slp.list2=act->activityTagsNames;
1393 
1394 						int dur=durationMap.value(slp, 0);
1395 						dur+=act->duration;
1396 						durationMap.insert(slp, dur);
1397 					}
1398 
1399 					if(htmlLevel>=1)
1400 						tmp+="          <td><table class=\"detailed\">";
1401 					else
1402 						tmp+="          <td><table>";
1403 					if(htmlLevel>=3)
1404 						tmp+="<tr class=\"duration line1\">";
1405 					else	tmp+="<tr>";
1406 
1407 					QMap<StringListPair, int>::const_iterator it=durationMap.constBegin();
1408 					while(it!=durationMap.constEnd()){
1409 						if(htmlLevel>=1)
1410 							tmp+="<td class=\"detailed\">";
1411 						else
1412 							tmp+="<td>";
1413 						tmp+=QString::number(it.value())+"</td>";
1414 						it++;
1415 					}
1416 
1417 					tmp+="</tr>";
1418 					if(htmlLevel>=3)
1419 						tmp+="<tr class=\"subject line2\">";
1420 					else	tmp+="<tr>";
1421 
1422 					it=durationMap.constBegin();
1423 					while(it!=durationMap.constEnd()){
1424 						if(htmlLevel>=1)
1425 							tmp+="<td class=\"detailed\">";
1426 						else
1427 							tmp+="<td>";
1428 
1429 						const StringListPair& slp=it.key();
1430 						assert(slp.list1.count()==1);
1431 						const QString& subjectName=slp.list1.at(0);
1432 						const QStringList& activityTagsNames=slp.list2;
1433 						QString tmpS=QString("");
1434 						if(!subjectName.isEmpty()||activityTagsNames.size()>0){
1435 							if(!subjectName.isEmpty())
1436 								switch(htmlLevel){
1437 									case 3 : tmpS+="<span class=\"subject\">"+protect2(subjectName)+"</span>"; break;
1438 									case 4 : tmpS+="<span class=\"subject\"><span class=\"s_"+statisticValues.hashSubjectIDsStatistics.value(subjectName)+"\">"+protect2(subjectName)+"</span></span>"; break;
1439 									case 5 : ;
1440 									case 6 : tmpS+="<span class=\"subject\"><span class=\"s_"+statisticValues.hashSubjectIDsStatistics.value(subjectName)+"\" onmouseover=\"highlight('s_"+statisticValues.hashSubjectIDsStatistics.value(subjectName)+"')\">"+protect2(subjectName)+"</span></span>"; break;
1441 									default: tmpS+=protect2(subjectName); break;
1442 								}
1443 							if(printActivityTags){
1444 								for(QStringList::const_iterator atn=activityTagsNames.constBegin(); atn!=activityTagsNames.constEnd(); atn++){
1445 									assert(statisticValues.hashActivityTagIDsStatistics.contains(*atn));
1446 									int id=statisticValues.hashActivityTagIDsStatistics.value(*atn, "0").toInt()-1;
1447 									assert(id>=0);
1448 									assert(id<statisticValues.hashActivityTagIDsStatistics.count());
1449 									if(gt.rules.activityTagsList[id]->printable){
1450 										switch(htmlLevel){
1451 											case 3 : tmpS+=" <span class=\"activitytag\">"+protect2(*atn)+"</span>"; break;
1452 											case 4 : tmpS+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\">"+protect2(*atn)+"</span></span>"; break;
1453 											case 5 : ;
1454 											case 6 : tmpS+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\" onmouseover=\"highlight('at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"')\">"+protect2(*atn)+"</span></span>"; break;
1455 											default: tmpS+=" "+protect2(*atn); break;
1456 										}
1457 										tmpS+=", ";
1458 									}
1459 								}
1460 								if(tmpS.endsWith(", ")){
1461 									tmpS.remove(tmpS.size()-2, 2);
1462 								}
1463 							}
1464 							if(tmpS=="")
1465 								tmpS=" ";
1466 						} else
1467 							tmpS=" ";
1468 
1469 						tmp+=tmpS;
1470 						tmp+="</td>";
1471 						it++;
1472 					}
1473 
1474 					tmp+="</tr>";
1475 					tmp+="</table></td>\n";
1476 				}
1477 			}
1478 		}
1479 		tmp+="          <th>";
1480 		tmp+=CustomFETString::number(statisticValues.studentsTotalNumberOfHours.value(students));
1481 		if(statisticValues.studentsTotalNumberOfHours.value(students)!=statisticValues.studentsTotalNumberOfHours2.value(students))
1482 			tmp+="<br />("+CustomFETString::number(statisticValues.studentsTotalNumberOfHours2.value(students))+")";
1483 		tmp+="</th>\n";
1484 		tmp+="        </tr>\n";
1485 	}
1486 
1487 	progress.setValue(qMax(statisticValues.allStudentsNames.count(), 1));
1488 
1489 	tmp+="        <tr>\n";
1490 	if(htmlLevel>=2)
1491 		tmp+="          <th class=\"xAxis\">";
1492 	else
1493 		tmp+="          <th>";
1494 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1495 	currentCount=0;
1496 	for(int teacher=0; teacher<statisticValues.allTeachersNames.count() && currentCount<maxNames; teacher++){
1497 		if(!(*excludedNames).contains(teacher)){
1498 			currentCount++;
1499 			tmp+="          <th>"+CustomFETString::number(statisticValues.teachersTotalNumberOfHours.value(statisticValues.allTeachersNames.at(teacher)));
1500 			if(statisticValues.teachersTotalNumberOfHours.value(statisticValues.allTeachersNames.at(teacher))!=statisticValues.teachersTotalNumberOfHours2.value(statisticValues.allTeachersNames.at(teacher)))
1501 				tmp+="<br />("+CustomFETString::number(statisticValues.teachersTotalNumberOfHours2.value(statisticValues.allTeachersNames.at(teacher)))+")";
1502 			tmp+="</th>\n";
1503 			*excludedNames<<teacher;
1504 		}
1505 	}
1506 	tmp+="          <th></th>\n        </tr>\n";
1507 	//workaround begin.
1508 	tmp+="      <tr class=\"foot\"><td></td><td colspan=\""+QString::number(colspan+1)+"\">"+StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)+"</td></tr>\n";
1509 	//workaround end.
1510 	tmp+="      </tbody>\n";
1511 	tmp+="    </table>\n";
1512 	return tmp;
1513 }
1514 
exportStatisticsStudentsTeachers(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel)1515 bool StatisticsExport::exportStatisticsStudentsTeachers(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel){
1516 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
1517 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
1518 
1519 	if(s2.right(4)==".fet")
1520 		s2=s2.left(s2.length()-4);
1521 	//else if(INPUT_FILENAME_XML!="")
1522 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
1523 
1524 	QString bar;
1525 	if(INPUT_FILENAME_XML=="")
1526 		bar="";
1527 	else
1528 		bar="_";
1529 
1530 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+STUDENTS_TEACHERS_STATISTICS;
1531 
1532 	QFile file(htmlfilename);
1533 	if(!file.open(QIODevice::WriteOnly)){
1534 		QMessageBox::critical(parent, tr("FET critical"),
1535 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
1536 		return false;
1537 	}
1538 	QTextStream tos(&file);
1539 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
1540 	tos.setEncoding(QStringConverter::Utf8);
1541 #else
1542 	tos.setCodec("UTF-8");
1543 #endif
1544 	tos.setGenerateByteOrderMark(true);
1545 
1546 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
1547 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
1548 
1549 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
1550 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
1551 	else
1552 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
1553 	tos<<"  <head>\n";
1554 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
1555 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
1556 	if(htmlLevel>=1){
1557 		QString bar;
1558 		if(INPUT_FILENAME_XML=="")
1559 			bar="";
1560 		else
1561 			bar="_";
1562 
1563 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
1564 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
1565 	}
1566 	if(htmlLevel>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
1567 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
1568 		tos<<"    <script type=\"text/javascript\">\n";
1569 		tos<<"      function highlight(classval) {\n";
1570 		tos<<"        var spans = document.getElementsByTagName('span');\n";
1571 		tos<<"        for(var i=0;spans.length>i;i++) {\n";
1572 		tos<<"          if (spans[i].className == classval) {\n";
1573 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
1574 		tos<<"          } else {\n";
1575 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
1576 		tos<<"          }\n";
1577 		tos<<"        }\n";
1578 		tos<<"      }\n";
1579 		tos<<"    </script>\n";
1580 	}
1581 	tos<<"  </head>\n\n";
1582 
1583 	tos<<"  <body>\n";
1584 	QSet<int> tmpSet;
1585 	tos<<StatisticsExport::exportStatisticsStudentsTeachersHtml(parent, saveTime, statisticValues, htmlLevel, TIMETABLE_HTML_PRINT_ACTIVITY_TAGS, statisticValues.allStudentsNames.count(), &tmpSet);
1586 	tos<<"  </body>\n</html>\n";
1587 
1588 	if(file.error()>0){
1589 		QMessageBox::critical(parent, tr("FET critical"),
1590 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
1591 		return false;
1592 	}
1593 	file.close();
1594 	return true;
1595 }
1596 
exportStatisticsStudentsTeachersHtml(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel,bool printActivityTags,int maxNames,QSet<int> * excludedNames)1597 QString StatisticsExport::exportStatisticsStudentsTeachersHtml(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel, bool printActivityTags, int maxNames, QSet<int> *excludedNames){
1598 	int colspan=0;
1599 	for(int students=0; students<statisticValues.allStudentsNames.count() && colspan<maxNames; students++){
1600 		if(!(*excludedNames).contains(students)){
1601 			colspan++;
1602 		}
1603 	}
1604 	QString tmp;
1605 	tmp+="    <table border=\"1\">\n";
1606 	tmp+="      <caption>"+protect2(gt.rules.institutionName)+"</caption>\n";
1607 	tmp+="      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\""+QString::number(colspan+1)+"\">"+tr("Students - Teachers Matrix")+"</th></tr>\n";
1608 	tmp+="        <tr>\n          <!-- span -->\n";
1609 	int currentCount=0;
1610 	for(int students=0; students<statisticValues.allStudentsNames.count() && currentCount<maxNames; students++){
1611 		if(!(*excludedNames).contains(students)){
1612 			currentCount++;
1613 			if(htmlLevel>=2)
1614 				tmp+="          <th class=\"xAxis\">";
1615 			else
1616 				tmp+="          <th>";
1617 			tmp+=protect2(statisticValues.allStudentsNames.at(students))+"</th>\n";
1618 		}
1619 	}
1620 	if(htmlLevel>=2)
1621 		tmp+="          <th class=\"xAxis\">";
1622 	else
1623 		tmp+="          <th>";
1624 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1625 	tmp+="        </tr>\n";
1626 	tmp+="      </thead>\n";
1627 	tmp+="      <tbody>\n";
1628 
1629 	QProgressDialog progress(parent);
1630 	progress.setWindowTitle(tr("Exporting statistics", "Title of a progress dialog"));
1631 	progress.setLabelText(tr("Processing students with teachers...please wait"));
1632 	progress.setRange(0, qMax(statisticValues.allTeachersNames.count(), 1));
1633 	progress.setModal(true);
1634 
1635 	int ttt=0;
1636 
1637 	for(const QString& teachers : qAsConst(statisticValues.allTeachersNames)){
1638 		progress.setValue(ttt);
1639 		//pqapplication->processEvents();
1640 		if(progress.wasCanceled()){
1641 			progress.setValue(statisticValues.allTeachersNames.count());
1642 			QMessageBox::warning(parent, tr("FET warning"), tr("Canceled"));
1643 			return /*false*/tmp;
1644 		}
1645 		ttt++;
1646 
1647 		QList<int> tmpTeachers;
1648 		QMultiHash<QString, int> tmpStudents;
1649 		tmpTeachers.clear();
1650 		tmpStudents.clear();
1651 		tmpTeachers=statisticValues.teachersActivities.values(teachers);
1652 		for(int aidx : qAsConst(tmpTeachers)){
1653 			Activity* act=gt.rules.activitiesList.at(aidx);
1654 			for(const QString& students : qAsConst(act->studentsNames)){
1655 				tmpStudents.insert(students, aidx);
1656 			}
1657 		}
1658 		tmp+="        <tr>\n";
1659 		if(htmlLevel>=2)
1660 			tmp+="          <th class=\"yAxis\">";
1661 		else
1662 			tmp+="          <th>";
1663 		tmp+=protect2(teachers)+"</th>\n";
1664 		currentCount=0;
1665 		for(int students=0; students<statisticValues.allStudentsNames.count() && currentCount<maxNames; students++){
1666 			if(!(*excludedNames).contains(students)){
1667 				currentCount++;
1668 				QList<int> tmpActivities;
1669 				tmpActivities.clear();
1670 				tmpActivities=tmpStudents.values(statisticValues.allStudentsNames.at(students));
1671 				if(tmpActivities.isEmpty()){
1672 					switch(htmlLevel){
1673 						case 3 : ;
1674 						case 4 : tmp+="          <td class=\"empty\"><span class=\"empty\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1675 						case 5 : ;
1676 						case 6 : tmp+="          <td class=\"empty\"><span class=\"empty\" onmouseover=\"highlight('empty')\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1677 						default: tmp+="          <td>"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</td>\n";
1678 					}
1679 				} else {
1680 					//optimized by Liviu Lalescu - 4
1681 					QMap<StringListPair, int> durationMap;
1682 					for(int tmpAct : qAsConst(tmpActivities)){
1683 						Activity* act=gt.rules.activitiesList.at(tmpAct);
1684 						StringListPair slp;
1685 						slp.list1=QStringList(act->subjectName);
1686 
1687 						slp.list2.clear();
1688 						if(printActivityTags){
1689 							for(const QString& at : qAsConst(act->activityTagsNames)){
1690 								int id=statisticValues.hashActivityTagIDsStatistics.value(at, "0").toInt()-1;
1691 								assert(id>=0);
1692 								assert(id<gt.rules.activityTagsList.count());
1693 								if(gt.rules.activityTagsList[id]->printable)
1694 									slp.list2.append(at);
1695 							}
1696 						}
1697 						//slp.list2=act->activityTagsNames;
1698 
1699 						int dur=durationMap.value(slp, 0);
1700 						dur+=act->duration;
1701 						durationMap.insert(slp, dur);
1702 					}
1703 
1704 					if(htmlLevel>=1)
1705 						tmp+="          <td><table class=\"detailed\">";
1706 					else
1707 						tmp+="          <td><table>";
1708 					if(htmlLevel>=3)
1709 						tmp+="<tr class=\"duration line1\">";
1710 					else	tmp+="<tr>";
1711 
1712 					QMap<StringListPair, int>::const_iterator it=durationMap.constBegin();
1713 					while(it!=durationMap.constEnd()){
1714 						if(htmlLevel>=1)
1715 							tmp+="<td class=\"detailed\">";
1716 						else
1717 							tmp+="<td>";
1718 						tmp+=QString::number(it.value())+"</td>";
1719 						it++;
1720 					}
1721 
1722 					tmp+="</tr>";
1723 					if(htmlLevel>=3)
1724 						tmp+="<tr class=\"subject line2\">";
1725 					else	tmp+="<tr>";
1726 
1727 					it=durationMap.constBegin();
1728 					while(it!=durationMap.constEnd()){
1729 						if(htmlLevel>=1)
1730 							tmp+="<td class=\"detailed\">";
1731 						else
1732 							tmp+="<td>";
1733 
1734 						const StringListPair& slp=it.key();
1735 						assert(slp.list1.count()==1);
1736 						const QString& subjectName=slp.list1.at(0);
1737 						const QStringList& activityTagsNames=slp.list2;
1738 						QString tmpS=QString("");
1739 						if(!subjectName.isEmpty()||activityTagsNames.size()>0){
1740 							if(!subjectName.isEmpty())
1741 								switch(htmlLevel){
1742 									case 3 : tmpS+="<span class=\"subject\">"+protect2(subjectName)+"</span>"; break;
1743 									case 4 : tmpS+="<span class=\"subject\"><span class=\"s_"+statisticValues.hashSubjectIDsStatistics.value(subjectName)+"\">"+protect2(subjectName)+"</span></span>"; break;
1744 									case 5 : ;
1745 									case 6 : tmpS+="<span class=\"subject\"><span class=\"s_"+statisticValues.hashSubjectIDsStatistics.value(subjectName)+"\" onmouseover=\"highlight('s_"+statisticValues.hashSubjectIDsStatistics.value(subjectName)+"')\">"+protect2(subjectName)+"</span></span>"; break;
1746 									default: tmpS+=protect2(subjectName); break;
1747 								}
1748 							if(printActivityTags){
1749 								for(QStringList::const_iterator atn=activityTagsNames.constBegin(); atn!=activityTagsNames.constEnd(); atn++){
1750 									assert(statisticValues.hashActivityTagIDsStatistics.contains(*atn));
1751 									int id=statisticValues.hashActivityTagIDsStatistics.value(*atn, "0").toInt()-1;
1752 									assert(id>=0);
1753 									assert(id<statisticValues.hashActivityTagIDsStatistics.count());
1754 									if(gt.rules.activityTagsList[id]->printable){
1755 										switch(htmlLevel){
1756 											case 3 : tmpS+=" <span class=\"activitytag\">"+protect2(*atn)+"</span>"; break;
1757 											case 4 : tmpS+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\">"+protect2(*atn)+"</span></span>"; break;
1758 											case 5 : ;
1759 											case 6 : tmpS+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\" onmouseover=\"highlight('at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"')\">"+protect2(*atn)+"</span></span>"; break;
1760 											default: tmpS+=" "+protect2(*atn); break;
1761 										}
1762 										tmpS+=", ";
1763 									}
1764 								}
1765 								if(tmpS.endsWith(", ")){
1766 									tmpS.remove(tmpS.size()-2, 2);
1767 								}
1768 							}
1769 							if(tmpS=="")
1770 								tmpS=" ";
1771 						} else
1772 							tmpS=" ";
1773 						tmp+=tmpS;
1774 
1775 						tmp+="</td>";
1776 						it++;
1777 					}
1778 
1779 					tmp+="</tr>";
1780 					tmp+="</table></td>\n";
1781 				}
1782 			}
1783 		}
1784 		tmp+="          <th>";
1785 		tmp+=CustomFETString::number(statisticValues.teachersTotalNumberOfHours.value(teachers));
1786 		if(statisticValues.teachersTotalNumberOfHours.value(teachers)!=statisticValues.teachersTotalNumberOfHours2.value(teachers))
1787 			tmp+="<br />("+CustomFETString::number(statisticValues.teachersTotalNumberOfHours2.value(teachers))+")";
1788 		tmp+="</th>\n";
1789 		tmp+="        </tr>\n";
1790 	}
1791 
1792 	progress.setValue(qMax(statisticValues.allTeachersNames.count(), 1));
1793 
1794 	tmp+="        <tr>\n";
1795 	if(htmlLevel>=2)
1796 		tmp+="          <th class=\"xAxis\">";
1797 	else
1798 		tmp+="          <th>";
1799 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1800 	currentCount=0;
1801 	for(int students=0; students<statisticValues.allStudentsNames.count() && currentCount<maxNames; students++){
1802 		if(!(*excludedNames).contains(students)){
1803 			currentCount++;
1804 			tmp+="          <th>"+CustomFETString::number(statisticValues.studentsTotalNumberOfHours.value(statisticValues.allStudentsNames.at(students)));
1805 			if(statisticValues.studentsTotalNumberOfHours.value(statisticValues.allStudentsNames.at(students))!=statisticValues.studentsTotalNumberOfHours2.value(statisticValues.allStudentsNames.at(students)))
1806 				tmp+="<br />("+CustomFETString::number(statisticValues.studentsTotalNumberOfHours2.value(statisticValues.allStudentsNames.at(students)))+")";
1807 			tmp+="</th>\n";
1808 			*excludedNames<<students;
1809 		}
1810 	}
1811 	tmp+="          <th></th>\n        </tr>\n";
1812 	//workaround begin.
1813 	tmp+="      <tr class=\"foot\"><td></td><td colspan=\""+QString::number(colspan+1)+"\">"+StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)+"</td></tr>\n";
1814 	//workaround end.
1815 	tmp+="      </tbody>\n";
1816 	tmp+="    </table>\n";
1817 	return tmp;
1818 }
1819 
exportStatisticsSubjectsStudents(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel)1820 bool StatisticsExport::exportStatisticsSubjectsStudents(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel){
1821 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
1822 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
1823 
1824 	if(s2.right(4)==".fet")
1825 		s2=s2.left(s2.length()-4);
1826 	//else if(INPUT_FILENAME_XML!="")
1827 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
1828 
1829 	QString bar;
1830 	if(INPUT_FILENAME_XML=="")
1831 		bar="";
1832 	else
1833 		bar="_";
1834 
1835 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+SUBJECTS_STUDENTS_STATISTICS;
1836 
1837 	QFile file(htmlfilename);
1838 	if(!file.open(QIODevice::WriteOnly)){
1839 		QMessageBox::critical(parent, tr("FET critical"),
1840 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
1841 		return false;
1842 	}
1843 	QTextStream tos(&file);
1844 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
1845 	tos.setEncoding(QStringConverter::Utf8);
1846 #else
1847 	tos.setCodec("UTF-8");
1848 #endif
1849 	tos.setGenerateByteOrderMark(true);
1850 
1851 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
1852 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
1853 
1854 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
1855 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
1856 	else
1857 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
1858 	tos<<"  <head>\n";
1859 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
1860 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
1861 	if(htmlLevel>=1){
1862 		QString bar;
1863 		if(INPUT_FILENAME_XML=="")
1864 			bar="";
1865 		else
1866 			bar="_";
1867 
1868 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
1869 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
1870 	}
1871 	if(htmlLevel>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
1872 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
1873 		tos<<"    <script type=\"text/javascript\">\n";
1874 		tos<<"      function highlight(classval) {\n";
1875 		tos<<"        var spans = document.getElementsByTagName('span');\n";
1876 		tos<<"        for(var i=0;spans.length>i;i++) {\n";
1877 		tos<<"          if (spans[i].className == classval) {\n";
1878 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
1879 		tos<<"          } else {\n";
1880 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
1881 		tos<<"          }\n";
1882 		tos<<"        }\n";
1883 		tos<<"      }\n";
1884 		tos<<"    </script>\n";
1885 	}
1886 	tos<<"  </head>\n\n";
1887 
1888 	tos<<"  <body>\n";
1889 	QSet<int> tmpSet;
1890 	tos<<StatisticsExport::exportStatisticsSubjectsStudentsHtml(parent, saveTime, statisticValues, htmlLevel, TIMETABLE_HTML_PRINT_ACTIVITY_TAGS, statisticValues.allSubjectsNames.count(), &tmpSet);
1891 	tos<<"  </body>\n</html>\n";
1892 
1893 	if(file.error()>0){
1894 		QMessageBox::critical(parent, tr("FET critical"),
1895 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
1896 		return false;
1897 	}
1898 	file.close();
1899 	return true;
1900 }
1901 
exportStatisticsSubjectsStudentsHtml(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel,bool printActivityTags,int maxNames,QSet<int> * excludedNames)1902 QString StatisticsExport::exportStatisticsSubjectsStudentsHtml(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel, bool printActivityTags, int maxNames, QSet<int> *excludedNames){
1903 	int colspan=0;
1904 	for(int subject=0; subject<statisticValues.allSubjectsNames.count() && colspan<maxNames; subject++){
1905 		if(!(*excludedNames).contains(subject)){
1906 			colspan++;
1907 		}
1908 	}
1909 	QString tmp;
1910 	tmp+="    <table border=\"1\">\n";
1911 	tmp+="      <caption>"+protect2(gt.rules.institutionName)+"</caption>\n";
1912 	tmp+="      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\""+QString::number(colspan+1)+"\">"+tr("Subjects - Students Matrix")+"</th></tr>\n";
1913 	tmp+="        <tr>\n          <!-- span -->\n";
1914 	int currentCount=0;
1915 	for(int subject=0; subject<statisticValues.allSubjectsNames.count() && currentCount<maxNames; subject++){
1916 		if(!(*excludedNames).contains(subject)){
1917 			currentCount++;
1918 			if(htmlLevel>=2)
1919 				tmp+="          <th class=\"xAxis\">";
1920 			else
1921 				tmp+="          <th>";
1922 			tmp+=protect2(statisticValues.allSubjectsNames.at(subject))+"</th>\n";
1923 		}
1924 	}
1925 	if(htmlLevel>=2)
1926 		tmp+="          <th class=\"xAxis\">";
1927 	else
1928 		tmp+="          <th>";
1929 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
1930 	tmp+="        </tr>\n";
1931 	tmp+="      </thead>\n";
1932 	tmp+="      <tbody>\n";
1933 
1934 	QProgressDialog progress(parent);
1935 	progress.setWindowTitle(tr("Exporting statistics", "Title of a progress dialog"));
1936 	progress.setLabelText(tr("Processing subjects with students...please wait"));
1937 	progress.setRange(0, qMax(statisticValues.allStudentsNames.count(), 1));
1938 	progress.setModal(true);
1939 
1940 	int ttt=0;
1941 
1942 	for(const QString& students : qAsConst(statisticValues.allStudentsNames)){
1943 		progress.setValue(ttt);
1944 		//pqapplication->processEvents();
1945 		if(progress.wasCanceled()){
1946 			progress.setValue(statisticValues.allStudentsNames.count());
1947 			QMessageBox::warning(parent, tr("FET warning"), tr("Canceled"));
1948 			return /*false*/tmp;
1949 		}
1950 		ttt++;
1951 
1952 		QList<int> tmpStudents;
1953 		QMultiHash<QString, int> tmpSubjects;
1954 		tmpStudents.clear();
1955 		tmpSubjects.clear();
1956 		tmpStudents=statisticValues.studentsActivities.values(students);
1957 		for(int aidx : qAsConst(tmpStudents)){
1958 			Activity* act=gt.rules.activitiesList.at(aidx);
1959 			tmpSubjects.insert(act->subjectName, aidx);
1960 		}
1961 		tmp+="        <tr>\n";
1962 		if(htmlLevel>=2)
1963 			tmp+="          <th class=\"yAxis\">";
1964 		else
1965 			tmp+="          <th>";
1966 		tmp+=protect2(students)+"</th>\n";
1967 		currentCount=0;
1968 		for(int subject=0; subject<statisticValues.allSubjectsNames.count() && currentCount<maxNames; subject++){
1969 			if(!(*excludedNames).contains(subject)){
1970 				currentCount++;
1971 				QList<int> tmpActivities;
1972 				tmpActivities.clear();
1973 				tmpActivities=tmpSubjects.values(statisticValues.allSubjectsNames.at(subject));
1974 				if(tmpActivities.isEmpty()){
1975 					switch(htmlLevel){
1976 						case 3 : ;
1977 						case 4 : tmp+="          <td class=\"empty\"><span class=\"empty\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1978 						case 5 : ;
1979 						case 6 : tmp+="          <td class=\"empty\"><span class=\"empty\" onmouseover=\"highlight('empty')\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
1980 						default: tmp+="          <td>"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</td>\n";
1981 					}
1982 				} else {
1983 					//optimized by Liviu Lalescu - 5
1984 					QMap<StringListPair, int> durationMap;
1985 					for(int tmpAct : qAsConst(tmpActivities)){
1986 						Activity* act=gt.rules.activitiesList.at(tmpAct);
1987 						StringListPair slp;
1988 						slp.list1=act->teachersNames;
1989 
1990 						slp.list2.clear();
1991 						if(printActivityTags){
1992 							for(const QString& at : qAsConst(act->activityTagsNames)){
1993 								int id=statisticValues.hashActivityTagIDsStatistics.value(at, "0").toInt()-1;
1994 								assert(id>=0);
1995 								assert(id<gt.rules.activityTagsList.count());
1996 								if(gt.rules.activityTagsList[id]->printable)
1997 									slp.list2.append(at);
1998 							}
1999 						}
2000 						//slp.list2=act->activityTagsNames;
2001 
2002 						int dur=durationMap.value(slp, 0);
2003 						dur+=act->duration;
2004 						durationMap.insert(slp, dur);
2005 					}
2006 
2007 					if(htmlLevel>=1)
2008 						tmp+="          <td><table class=\"detailed\">";
2009 					else
2010 						tmp+="          <td><table>";
2011 					if(htmlLevel>=3)
2012 						tmp+="<tr class=\"duration line1\">";
2013 					else	tmp+="<tr>";
2014 
2015 					QMap<StringListPair, int>::const_iterator it=durationMap.constBegin();
2016 					while(it!=durationMap.constEnd()){
2017 						if(htmlLevel>=1)
2018 							tmp+="<td class=\"detailed\">";
2019 						else
2020 							tmp+="<td>";
2021 						tmp+=QString::number(it.value())+"</td>";
2022 						it++;
2023 					}
2024 
2025 					tmp+="</tr>";
2026 					if(htmlLevel>=3)
2027 						tmp+="<tr class=\"teacher line2\">";
2028 					else	tmp+="<tr>";
2029 
2030 					it=durationMap.constBegin();
2031 					while(it!=durationMap.constEnd()){
2032 						if(htmlLevel>=1)
2033 							tmp+="<td class=\"detailed\">";
2034 						else
2035 							tmp+="<td>";
2036 
2037 						const StringListPair& slp=it.key();
2038 						const QStringList& teachersNames=slp.list1;
2039 						const QStringList& activityTagsNames=slp.list2;
2040 						QString tmpT=QString("");
2041 
2042 						if(teachersNames.size()>0||activityTagsNames.size()>0){
2043 							for(QStringList::const_iterator it=teachersNames.constBegin(); it!=teachersNames.constEnd(); it++){
2044 								switch(htmlLevel){
2045 									case 4 : tmpT+="<span class=\"t_"+statisticValues.hashTeacherIDsStatistics.value(*it)+"\">"+protect2(*it)+"</span>"; break;
2046 									case 5 : ;
2047 									case 6 : tmpT+="<span class=\"t_"+statisticValues.hashTeacherIDsStatistics.value(*it)+"\" onmouseover=\"highlight('t_"+statisticValues.hashTeacherIDsStatistics.value(*it)+"')\">"+protect2(*it)+"</span>"; break;
2048 									default: tmpT+=protect2(*it); break;
2049 								}
2050 								if(it!=teachersNames.constEnd()-1)
2051 									tmpT+=", ";
2052 							}
2053 							if(printActivityTags){
2054 								for(QStringList::const_iterator atn=activityTagsNames.constBegin(); atn!=activityTagsNames.constEnd(); atn++){
2055 									assert(statisticValues.hashActivityTagIDsStatistics.contains(*atn));
2056 									int id=statisticValues.hashActivityTagIDsStatistics.value(*atn, "0").toInt()-1;
2057 									assert(id>=0);
2058 									assert(id<statisticValues.hashActivityTagIDsStatistics.count());
2059 									if(gt.rules.activityTagsList[id]->printable){
2060 										switch(htmlLevel){
2061 											case 3 : tmpT+=" <span class=\"activitytag\">"+protect2(*atn)+"</span>"; break;
2062 											case 4 : tmpT+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\">"+protect2(*atn)+"</span></span>"; break;
2063 											case 5 : ;
2064 											case 6 : tmpT+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\" onmouseover=\"highlight('at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"')\">"+protect2(*atn)+"</span></span>"; break;
2065 											default: tmpT+=" "+protect2(*atn); break;
2066 										}
2067 										tmpT+=", ";
2068 									}
2069 								}
2070 								if(tmpT.endsWith(", ")){
2071 									tmpT.remove(tmpT.size()-2, 2);
2072 								}
2073 							}
2074 							if(tmpT=="")
2075 								tmpT=" ";
2076 						} else
2077 							tmpT=" ";
2078 						tmp+=tmpT;
2079 
2080 						tmp+="</td>";
2081 						it++;
2082 					}
2083 
2084 					tmp+="</tr>";
2085 					tmp+="</table></td>\n";
2086 				}
2087 			}
2088 		}
2089 		tmp+="          <th>";
2090 		tmp+=CustomFETString::number(statisticValues.studentsTotalNumberOfHours.value(students));
2091 		if(statisticValues.studentsTotalNumberOfHours.value(students)!=statisticValues.studentsTotalNumberOfHours2.value(students))
2092 			tmp+="<br />("+CustomFETString::number(statisticValues.studentsTotalNumberOfHours2.value(students))+")";
2093 		tmp+="</th>\n";
2094 		tmp+="        </tr>\n";
2095 	}
2096 
2097 	progress.setValue(qMax(statisticValues.allStudentsNames.count(), 1));
2098 
2099 	tmp+="        <tr>\n";
2100 	if(htmlLevel>=2)
2101 		tmp+="          <th class=\"xAxis\">";
2102 	else
2103 		tmp+="          <th>";
2104 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
2105 	currentCount=0;
2106 	for(int subject=0; subject<statisticValues.allSubjectsNames.count() && currentCount<maxNames; subject++){
2107 		if(!(*excludedNames).contains(subject)){
2108 			currentCount++;
2109 			tmp+="          <th>"+CustomFETString::number(statisticValues.subjectsTotalNumberOfHours.value(statisticValues.allSubjectsNames.at(subject)));
2110 			if(statisticValues.subjectsTotalNumberOfHours.value(statisticValues.allSubjectsNames.at(subject))!=statisticValues.subjectsTotalNumberOfHours4.value(statisticValues.allSubjectsNames.at(subject)))
2111 				tmp+="<br />("+CustomFETString::number(statisticValues.subjectsTotalNumberOfHours4.value(statisticValues.allSubjectsNames.at(subject)))+")";
2112 			tmp+="</th>\n";
2113 			*excludedNames<<subject;
2114 		}
2115 	}
2116 	tmp+="          <th></th>\n        </tr>\n";
2117 	//workaround begin.
2118 	tmp+="      <tr class=\"foot\"><td></td><td colspan=\""+QString::number(colspan+1)+"\">"+StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)+"</td></tr>\n";
2119 	//workaround end.
2120 	tmp+="      </tbody>\n";
2121 	tmp+="    </table>\n";
2122 	return tmp;
2123 }
2124 
exportStatisticsStudentsSubjects(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel)2125 bool StatisticsExport::exportStatisticsStudentsSubjects(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel){
2126 	assert(gt.rules.initialized); // && gt.rules.internalStructureComputed);
2127 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);	//TODO: remove s2, because too long filenames!
2128 
2129 	if(s2.right(4)==".fet")
2130 		s2=s2.left(s2.length()-4);
2131 	//else if(INPUT_FILENAME_XML!="")
2132 	//	cout<<"Minor problem - input file does not end in .fet extension - might be a problem when saving the timetables"<<" (file:"<<__FILE__<<", line:"<<__LINE__<<")"<<endl;
2133 
2134 	QString bar;
2135 	if(INPUT_FILENAME_XML=="")
2136 		bar="";
2137 	else
2138 		bar="_";
2139 
2140 	QString htmlfilename=PREFIX_STATISTICS+s2+bar+STUDENTS_SUBJECTS_STATISTICS;
2141 
2142 	QFile file(htmlfilename);
2143 	if(!file.open(QIODevice::WriteOnly)){
2144 		QMessageBox::critical(parent, tr("FET critical"),
2145 		 StatisticsExport::tr("Cannot open file %1 for writing. Please check your disk's free space. Saving of %1 aborted.").arg(htmlfilename));
2146 		return false;
2147 	}
2148 	QTextStream tos(&file);
2149 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
2150 	tos.setEncoding(QStringConverter::Utf8);
2151 #else
2152 	tos.setCodec("UTF-8");
2153 #endif
2154 	tos.setGenerateByteOrderMark(true);
2155 	tos<<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
2156 	tos<<"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n";
2157 
2158 	if(LANGUAGE_STYLE_RIGHT_TO_LEFT==false)
2159 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\">\n";
2160 	else
2161 		tos<<"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\""<<LANGUAGE_FOR_HTML<<"\" xml:lang=\""<<LANGUAGE_FOR_HTML<<"\" dir=\"rtl\">\n";
2162 	tos<<"  <head>\n";
2163 	tos<<"    <title>"<<protect2(gt.rules.institutionName)<<"</title>\n";
2164 	tos<<"    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
2165 	if(htmlLevel>=1){
2166 		QString bar;
2167 		if(INPUT_FILENAME_XML=="")
2168 			bar="";
2169 		else
2170 			bar="_";
2171 
2172 		QString cssfilename=s2+bar+STYLESHEET_STATISTICS;
2173 		tos<<"    <link rel=\"stylesheet\" media=\"all\" href=\""<<cssfilename<<"\" type=\"text/css\" />\n";
2174 	}
2175 	if(htmlLevel>=5){  // the following JavaScript code is pretty similar to an example of Les Richardson
2176 		tos<<"    <meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\" />\n";
2177 		tos<<"    <script type=\"text/javascript\">\n";
2178 		tos<<"      function highlight(classval) {\n";
2179 		tos<<"        var spans = document.getElementsByTagName('span');\n";
2180 		tos<<"        for(var i=0;spans.length>i;i++) {\n";
2181 		tos<<"          if (spans[i].className == classval) {\n";
2182 		tos<<"            spans[i].style.backgroundColor = 'lime';\n";
2183 		tos<<"          } else {\n";
2184 		tos<<"            spans[i].style.backgroundColor = 'white';\n";
2185 		tos<<"          }\n";
2186 		tos<<"        }\n";
2187 		tos<<"      }\n";
2188 		tos<<"    </script>\n";
2189 	}
2190 	tos<<"  </head>\n\n";
2191 
2192 	tos<<"  <body>\n";
2193 	QSet<int> tmpSet;
2194 	tos<<StatisticsExport::exportStatisticsStudentsSubjectsHtml(parent, saveTime, statisticValues, htmlLevel, TIMETABLE_HTML_PRINT_ACTIVITY_TAGS, statisticValues.allStudentsNames.count(), &tmpSet);
2195 	tos<<"  </body>\n</html>\n";
2196 
2197 	if(file.error()>0){
2198 		QMessageBox::critical(parent, tr("FET critical"),
2199 		 StatisticsExport::tr("Writing %1 gave error code %2, which means saving is compromised. Please check your disk's free space.").arg(htmlfilename).arg(file.error()));
2200 		return false;
2201 	}
2202 	file.close();
2203 	return true;
2204 }
2205 
exportStatisticsStudentsSubjectsHtml(QWidget * parent,QString saveTime,FetStatistics statisticValues,int htmlLevel,bool printActivityTags,int maxNames,QSet<int> * excludedNames)2206 QString StatisticsExport::exportStatisticsStudentsSubjectsHtml(QWidget* parent, QString saveTime, FetStatistics statisticValues, int htmlLevel, bool printActivityTags, int maxNames, QSet<int> *excludedNames){
2207 	int colspan=0;
2208 	for(int students=0; students<statisticValues.allStudentsNames.count() && colspan<maxNames; students++){
2209 		if(!(*excludedNames).contains(students)){
2210 			colspan++;
2211 		}
2212 	}
2213 	QString tmp;
2214 	tmp+="    <table border=\"1\">\n";
2215 	tmp+="      <caption>"+protect2(gt.rules.institutionName)+"</caption>\n";
2216 	tmp+="      <thead>\n        <tr><td rowspan=\"2\"></td><th colspan=\""+QString::number(colspan+1)+"\">"+tr("Students - Subjects Matrix")+"</th></tr>\n";
2217 	tmp+="        <tr>\n          <!-- span -->\n";
2218 	int currentCount=0;
2219 	for(int students=0; students<statisticValues.allStudentsNames.count() && currentCount<maxNames; students++){
2220 		if(!(*excludedNames).contains(students)){
2221 			currentCount++;
2222 			if(htmlLevel>=2)
2223 				tmp+="          <th class=\"xAxis\">";
2224 			else
2225 				tmp+="          <th>";
2226 			tmp+=protect2(statisticValues.allStudentsNames.at(students))+"</th>\n";
2227 		}
2228 	}
2229 	if(htmlLevel>=2)
2230 		tmp+="          <th class=\"xAxis\">";
2231 	else
2232 		tmp+="          <th>";
2233 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
2234 	tmp+="        </tr>\n";
2235 	tmp+="      </thead>\n";
2236 	tmp+="      <tbody>\n";
2237 
2238 	QProgressDialog progress(parent);
2239 	progress.setWindowTitle(tr("Exporting statistics", "Title of a progress dialog"));
2240 	progress.setLabelText(tr("Processing students with subjects...please wait"));
2241 	progress.setRange(0, qMax(statisticValues.allSubjectsNames.count(), 1));
2242 	progress.setModal(true);
2243 
2244 	int ttt=0;
2245 
2246 	for(const QString& subjects : qAsConst(statisticValues.allSubjectsNames)){
2247 		progress.setValue(ttt);
2248 		//pqapplication->processEvents();
2249 		if(progress.wasCanceled()){
2250 			progress.setValue(statisticValues.allSubjectsNames.count());
2251 			QMessageBox::warning(parent, tr("FET warning"), tr("Canceled"));
2252 			return /*false*/tmp;
2253 		}
2254 		ttt++;
2255 
2256 		QList<int> tmpSubjects;
2257 		QMultiHash<QString, int> tmpStudents;
2258 		tmpSubjects.clear();
2259 		tmpStudents.clear();
2260 		tmpSubjects=statisticValues.subjectsActivities.values(subjects);
2261 		for(int aidx : qAsConst(tmpSubjects)){
2262 			Activity* act=gt.rules.activitiesList.at(aidx);
2263 			for(const QString& students : qAsConst(act->studentsNames)){
2264 				tmpStudents.insert(students, aidx);
2265 			}
2266 		}
2267 		tmp+="        <tr>\n";
2268 		if(htmlLevel>=2)
2269 			tmp+="          <th class=\"yAxis\">";
2270 		else
2271 			tmp+="          <th>";
2272 		tmp+=protect2(subjects)+"</th>\n";
2273 		currentCount=0;
2274 		for(int students=0; students<statisticValues.allStudentsNames.count() && currentCount<maxNames; students++){
2275 			if(!(*excludedNames).contains(students)){
2276 				currentCount++;
2277 				QList<int> tmpActivities;
2278 				tmpActivities.clear();
2279 				tmpActivities=tmpStudents.values(statisticValues.allStudentsNames.at(students));
2280 				if(tmpActivities.isEmpty()){
2281 					switch(htmlLevel){
2282 						case 3 : ;
2283 						case 4 : tmp+="          <td class=\"empty\"><span class=\"empty\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
2284 						case 5 : ;
2285 						case 6 : tmp+="          <td class=\"empty\"><span class=\"empty\" onmouseover=\"highlight('empty')\">"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</span></td>\n"; break;
2286 						default: tmp+="          <td>"+protect2(STRING_EMPTY_SLOT_STATISTICS)+"</td>\n";
2287 					}
2288 				} else {
2289 					//optimized by Liviu Lalescu - 6
2290 					QMap<StringListPair, int> durationMap;
2291 					for(int tmpAct : qAsConst(tmpActivities)){
2292 						Activity* act=gt.rules.activitiesList.at(tmpAct);
2293 						StringListPair slp;
2294 						slp.list1=act->teachersNames;
2295 
2296 						slp.list2.clear();
2297 						if(printActivityTags){
2298 							for(const QString& at : qAsConst(act->activityTagsNames)){
2299 								int id=statisticValues.hashActivityTagIDsStatistics.value(at, "0").toInt()-1;
2300 								assert(id>=0);
2301 								assert(id<gt.rules.activityTagsList.count());
2302 								if(gt.rules.activityTagsList[id]->printable)
2303 									slp.list2.append(at);
2304 							}
2305 						}
2306 						//slp.list2=act->activityTagsNames;
2307 
2308 						int dur=durationMap.value(slp, 0);
2309 						dur+=act->duration;
2310 						durationMap.insert(slp, dur);
2311 					}
2312 
2313 					if(htmlLevel>=1)
2314 						tmp+="          <td><table class=\"detailed\">";
2315 					else
2316 						tmp+="          <td><table>";
2317 					if(htmlLevel>=3)
2318 						tmp+="<tr class=\"duration line1\">";
2319 					else	tmp+="<tr>";
2320 
2321 					QMap<StringListPair, int>::const_iterator it=durationMap.constBegin();
2322 					while(it!=durationMap.constEnd()){
2323 						if(htmlLevel>=1)
2324 							tmp+="<td class=\"detailed\">";
2325 						else
2326 							tmp+="<td>";
2327 						tmp+=QString::number(it.value())+"</td>";
2328 						it++;
2329 					}
2330 
2331 					tmp+="</tr>";
2332 					if(htmlLevel>=3)
2333 						tmp+="<tr class=\"teacher line2\">";
2334 					else	tmp+="<tr>";
2335 
2336 					it=durationMap.constBegin();
2337 					while(it!=durationMap.constEnd()){
2338 						if(htmlLevel>=1)
2339 							tmp+="<td class=\"detailed\">";
2340 						else
2341 							tmp+="<td>";
2342 
2343 						const StringListPair& slp=it.key();
2344 						const QStringList& teachersNames=slp.list1;
2345 						const QStringList& activityTagsNames=slp.list2;
2346 						QString tmpT=QString("");
2347 
2348 						if(teachersNames.size()>0||activityTagsNames.size()>0){
2349 							for(QStringList::const_iterator it=teachersNames.constBegin(); it!=teachersNames.constEnd(); it++){
2350 								switch(htmlLevel){
2351 									case 4 : tmpT+="<span class=\"t_"+statisticValues.hashTeacherIDsStatistics.value(*it)+"\">"+protect2(*it)+"</span>"; break;
2352 									case 5 : ;
2353 									case 6 : tmpT+="<span class=\"t_"+statisticValues.hashTeacherIDsStatistics.value(*it)+"\" onmouseover=\"highlight('t_"+statisticValues.hashTeacherIDsStatistics.value(*it)+"')\">"+protect2(*it)+"</span>"; break;
2354 									default: tmpT+=protect2(*it); break;
2355 								}
2356 								if(it!=teachersNames.constEnd()-1)
2357 									tmpT+=", ";
2358 							}
2359 							if(printActivityTags){
2360 								for(QStringList::const_iterator atn=activityTagsNames.constBegin(); atn!=activityTagsNames.constEnd(); atn++){
2361 									assert(statisticValues.hashActivityTagIDsStatistics.contains(*atn));
2362 									int id=statisticValues.hashActivityTagIDsStatistics.value(*atn, "0").toInt()-1;
2363 									assert(id>=0);
2364 									assert(id<statisticValues.hashActivityTagIDsStatistics.count());
2365 									if(gt.rules.activityTagsList[id]->printable){
2366 										switch(htmlLevel){
2367 											case 3 : tmpT+=" <span class=\"activitytag\">"+protect2(*atn)+"</span>"; break;
2368 											case 4 : tmpT+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\">"+protect2(*atn)+"</span></span>"; break;
2369 											case 5 : ;
2370 											case 6 : tmpT+=" <span class=\"activitytag\"><span class=\"at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"\" onmouseover=\"highlight('at_"+statisticValues.hashActivityTagIDsStatistics.value(*atn)+"')\">"+protect2(*atn)+"</span></span>"; break;
2371 											default: tmpT+=" "+protect2(*atn); break;
2372 										}
2373 										tmpT+=", ";
2374 									}
2375 								}
2376 								if(tmpT.endsWith(", ")){
2377 									tmpT.remove(tmpT.size()-2, 2);
2378 								}
2379 							}
2380 							if(tmpT=="")
2381 								tmpT=" ";
2382 						} else
2383 							tmpT=" ";
2384 						tmp+=tmpT;
2385 
2386 						tmp+="</td>";
2387 						it++;
2388 					}
2389 
2390 					tmp+="</tr>";
2391 					tmp+="</table></td>\n";
2392 				}
2393 			}
2394 		}
2395 		tmp+="          <th>";
2396 		tmp+=CustomFETString::number(statisticValues.subjectsTotalNumberOfHours.value(subjects));
2397 		if(statisticValues.subjectsTotalNumberOfHours.value(subjects)!=statisticValues.subjectsTotalNumberOfHours4.value(subjects))
2398 			tmp+="<br />("+CustomFETString::number(statisticValues.subjectsTotalNumberOfHours4.value(subjects))+")";
2399 		tmp+="</th>\n";
2400 		tmp+="        </tr>\n";
2401 	}
2402 
2403 	progress.setValue(qMax(statisticValues.allSubjectsNames.count(), 1));
2404 
2405 	tmp+="        <tr>\n";
2406 	if(htmlLevel>=2)
2407 		tmp+="          <th class=\"xAxis\">";
2408 	else
2409 		tmp+="          <th>";
2410 	tmp+=protect2(tr("Sum", "This means the sum of more values, the total"))+"</th>\n";
2411 	currentCount=0;
2412 	for(int students=0; students<statisticValues.allStudentsNames.count() && currentCount<maxNames; students++){
2413 		if(!(*excludedNames).contains(students)){
2414 			currentCount++;
2415 			tmp+="          <th>"+CustomFETString::number(statisticValues.studentsTotalNumberOfHours.value(statisticValues.allStudentsNames.at(students)));
2416 			if(statisticValues.studentsTotalNumberOfHours.value(statisticValues.allStudentsNames.at(students))!=statisticValues.studentsTotalNumberOfHours2.value(statisticValues.allStudentsNames.at(students)))
2417 				tmp+="<br />("+CustomFETString::number(statisticValues.studentsTotalNumberOfHours2.value(statisticValues.allStudentsNames.at(students)))+")";
2418 			tmp+="</th>\n";
2419 			*excludedNames<<students;
2420 		}
2421 	}
2422 	tmp+="          <th></th>\n        </tr>\n";
2423 	//workaround begin.
2424 	tmp+="      <tr class=\"foot\"><td></td><td colspan=\""+QString::number(colspan+1)+"\">"+StatisticsExport::tr("Timetable generated with FET %1 on %2", "%1 is FET version, %2 is the date and time of generation").arg(FET_VERSION).arg(saveTime)+"</td></tr>\n";
2425 	//workaround end.
2426 	tmp+="      </tbody>\n";
2427 	tmp+="    </table>\n";
2428 	return tmp;
2429 }
2430