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