From e8e5e499f29f0f68256de0564acea2be2f4f5086 Mon Sep 17 00:00:00 2001 From: Erik Melin <31467290+erikmelin@users.noreply.github.com> Date: Tue, 19 Mar 2019 22:32:05 +0100 Subject: [PATCH] MeOS version 3.6-1033 RC1 --- code/HTMLWriter.cpp | 702 +++++++++++++++---- code/HTMLWriter.h | 114 ++++ code/MeOSFeatures.cpp | 7 +- code/MeOSFeatures.h | 6 +- code/Printer.h | 4 +- code/RestService.cpp | 103 ++- code/RestService.h | 2 +- code/RunnerDB.cpp | 4 +- code/RunnerDB.h | 2 +- code/SportIdent.cpp | 6 +- code/SportIdent.h | 2 +- code/StdAfx.h | 2 +- code/TabAuto.cpp | 109 +-- code/TabAuto.h | 42 +- code/TabBase.cpp | 2 +- code/TabBase.h | 2 +- code/TabClass.cpp | 531 +++++++++----- code/TabClass.h | 14 +- code/TabClub.cpp | 6 +- code/TabClub.h | 2 +- code/TabCompetition.cpp | 81 ++- code/TabCompetition.h | 2 +- code/TabControl.cpp | 2 +- code/TabControl.h | 2 +- code/TabCourse.cpp | 71 +- code/TabCourse.h | 7 +- code/TabList.cpp | 929 ++++++++++++++++++++----- code/TabList.h | 22 +- code/TabMulti.cpp | 2 +- code/TabMulti.h | 2 +- code/TabRunner.cpp | 320 +++++---- code/TabRunner.h | 7 +- code/TabSI.cpp | 422 +++++++----- code/TabSI.h | 9 +- code/TabSpeaker.cpp | 158 ++++- code/TabSpeaker.h | 7 +- code/TabTeam.cpp | 88 ++- code/TabTeam.h | 4 +- code/Table.cpp | 6 +- code/Table.h | 2 +- code/TimeStamp.cpp | 2 +- code/TimeStamp.h | 2 +- code/animationdata.cpp | 18 +- code/animationdata.h | 4 +- code/autocomplete.cpp | 2 +- code/autocomplete.h | 23 + code/autocompletehandler.h | 21 + code/autotask.cpp | 2 +- code/autotask.h | 2 +- code/classconfiginfo.cpp | 54 +- code/classconfiginfo.h | 7 +- code/csvparser.cpp | 196 ++++-- code/csvparser.h | 19 +- code/czech.lng | 4 +- code/danish.lng | 4 +- code/download.cpp | 130 +++- code/download.h | 2 +- code/english.lng | 132 +++- code/french.lng | 2 +- code/gdiconstants.h | 2 +- code/gdifonts.h | 4 +- code/gdiimpl.h | 2 +- code/gdioutput.cpp | 65 +- code/gdioutput.h | 33 +- code/gdistructures.h | 4 +- code/generalresult.cpp | 10 +- code/generalresult.h | 2 +- code/german.lng | 2 +- code/guihandler.h | 2 +- code/html1.htm | 1 + code/image.cpp | 24 +- code/image.h | 28 +- code/importformats.cpp | 2 +- code/importformats.h | 2 +- code/infoserver.cpp | 58 +- code/infoserver.h | 7 +- code/inthashmap.h | 2 +- code/intkeymap.hpp | 6 +- code/intkeymapimpl.hpp | 5 +- code/iof30interface.cpp | 15 +- code/iof30interface.h | 4 +- code/license.txt | 2 +- code/listeditor.cpp | 3 +- code/listeditor.h | 2 +- code/liveresult.cpp | 5 +- code/liveresult.h | 2 +- code/localizer.cpp | 4 +- code/localizer.h | 2 +- code/meos.cpp | 69 +- code/meos.rc | 2 +- code/meos_util.cpp | 21 +- code/meos_util.h | 27 +- code/meosdb/MeosSQL.cpp | 27 +- code/meosdb/MeosSQL.h | 3 +- code/meosdb/meosdb.cpp | 22 +- code/meosdb/sqltypes.h | 7 +- code/meosdb/targetver.h | 2 +- code/meosexception.h | 2 +- code/meosversion.cpp | 38 +- code/metalist.cpp | 196 +++++- code/metalist.h | 14 +- code/methodeditor.cpp | 2 +- code/methodeditor.h | 2 +- code/mysqldaemon.cpp | 11 +- code/newcompetition.cpp | 55 +- code/oBase.cpp | 2 +- code/oBase.h | 5 +- code/oCard.cpp | 31 +- code/oCard.h | 3 +- code/oClass.cpp | 433 ++++++++---- code/oClass.h | 44 +- code/oClub.cpp | 25 +- code/oClub.h | 2 +- code/oControl.cpp | 8 +- code/oControl.h | 2 +- code/oCourse.cpp | 187 ++--- code/oCourse.h | 13 +- code/oDataContainer.cpp | 36 +- code/oDataContainer.h | 2 +- code/oEvent.cpp | 225 ++++-- code/oEvent.h | 153 +++-- code/oEventDraw.cpp | 521 ++++++++------ code/oEventDraw.h | 14 +- code/oEventResult.cpp | 195 +++++- code/oEventSQL.cpp | 114 ++-- code/oEventSpeaker.cpp | 8 +- code/oFreeImport.cpp | 2 +- code/oFreeImport.h | 2 +- code/oFreePunch.cpp | 20 +- code/oFreePunch.h | 4 +- code/oImportExport.cpp | 37 +- code/oListInfo.cpp | 1253 +++++++++++++++++++++++----------- code/oListInfo.h | 69 +- code/oPunch.cpp | 7 +- code/oPunch.h | 16 +- code/oReport.cpp | 30 +- code/oRunner.cpp | 977 +++++++++++++++----------- code/oRunner.h | 47 +- code/oTeam.cpp | 44 +- code/oTeam.h | 11 +- code/oTeamEvent.cpp | 8 +- code/onlineinput.cpp | 116 +++- code/onlineinput.h | 6 +- code/onlineresults.cpp | 27 +- code/onlineresults.h | 12 +- code/ospeaker.h | 2 +- code/parser.cpp | 2 +- code/parser.h | 2 +- code/pdfwriter.cpp | 7 +- code/pdfwriter.h | 5 +- code/prefseditor.cpp | 2 +- code/prefseditor.h | 2 +- code/printer.cpp | 104 ++- code/progress.cpp | 2 +- code/progress.h | 2 +- code/qualification_final.cpp | 273 +++++++- code/qualification_final.h | 56 +- code/random.cpp | 2 +- code/random.h | 2 +- code/recorder.cpp | 2 +- code/recorder.h | 2 +- code/restserver.cpp | 471 ++++++++++++- code/restserver.h | 41 +- code/russian.lng | 2 +- code/socket.cpp | 2 +- code/socket.h | 2 +- code/speakermonitor.cpp | 2 +- code/speakermonitor.h | 2 +- code/subcommand.h | 2 +- code/swedish.lng | 94 ++- code/testmeos.cpp | 10 +- code/testmeos.h | 2 +- code/toolbar.cpp | 2 +- code/toolbar.h | 2 +- code/xmlparser.cpp | 2 +- code/xmlparser.h | 2 +- 176 files changed, 8023 insertions(+), 3024 deletions(-) create mode 100644 code/HTMLWriter.h diff --git a/code/HTMLWriter.cpp b/code/HTMLWriter.cpp index 843e87f..02338e1 100644 --- a/code/HTMLWriter.cpp +++ b/code/HTMLWriter.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,26 +30,36 @@ #include "meos_util.h" #include "Localizer.h" #include "gdiconstants.h" +#include "HTMLWriter.h" +#include "Printer.h" +#include "oListInfo.h" +#include "meosexception.h" + +#include +#include double getLocalScale(const wstring &fontName, wstring &faceName); wstring getMeosCompectVersion(); -static void generateStyles(const gdioutput &gdi, ostream &fout, bool withTbl, const list &TL, +map > HTMLWriter::tCache; +extern wchar_t exePath[MAX_PATH]; + +static void generateStyles(const gdioutput &gdi, ostream &fout, double scale, bool withTbl, const list &TL, map< pair, pair > &styles) { fout << "\n"; } +class InterpTextInfo { +public: + static int x(const TextInfo &ti) { + return ti.xp; + } + + static int y(const TextInfo &ti) { + return ti.yp; + } + + static const TextInfo &ti(const TextInfo &in_ti) { + return in_ti; + } +}; + +template +void HTMLWriter::formatTL(ostream &fout, + const map, pair> &styles, + const T &tl, + double &yscale, double &xscale, + int &offsetY, int &offsetX) { + + + auto itt = tl.begin(); + while (itt != tl.end()) { + auto &ctr_it = *itt; + const TextInfo &it = TI::ti(ctr_it); + + if (gdioutput::skipTextRender(it.format)) { + ++itt; + continue; + } + + string yp = itos(int(yscale*TI::y(ctr_it)) + offsetY); + string xp = itos(int(xscale*TI::x(ctr_it)) + offsetX); + + string estyle; + if (it.format != 1 && it.format != boldSmall) { + if (it.format & textRight) + estyle = " style=\"position:absolute;left:" + + xp + "px;top:" + yp + "px\""; + else + estyle = " style=\"position:absolute;left:" + + xp + "px;top:" + yp + "px\""; + + } + else { + if (it.format & textRight) + estyle = " style=\"font-weight:bold;position:absolute;left:" + + xp + "px;top:" + yp + "px\""; + else + estyle = " style=\"font-weight:bold;position:absolute;left:" + + xp + "px;top:" + yp + "px\""; + } + string starttag, endtag; + getStyle(styles, it.getGdiFont(), gdioutput::narrow(it.font), estyle, starttag, endtag); + + if (!it.text.empty()) + fout << starttag << gdioutput::toUTF8(encodeXML(it.text)) << endtag << endl; + + if (it.format == boldLarge) { + auto next = itt; + ++next; + if (next == tl.end() || next->yp != it.yp) + offsetY += 7; + } + ++itt; + } +} + static void getStyle(const map< pair, pair > &styles, gdiFonts font, const string &face, const string &extraStyle, string &starttag, string &endtag) { starttag.clear(); @@ -187,94 +267,52 @@ static void getStyle(const map< pair, pair > & } } -bool gdioutput::writeHTML(const wstring &file, const wstring &title, int refreshTimeOut) const -{ +void HTMLWriter::writeHTML(gdioutput &gdi, const wstring &file, + const wstring &title, int refreshTimeOut, double scale){ checkWriteAccess(file); ofstream fout(file.c_str()); - if (fout.bad()) - return false; + throw std::exception("Bad output stream"); + + writeHTML(gdi, fout, title, refreshTimeOut, scale); +} +void HTMLWriter::writeHTML(gdioutput &gdi, ostream &fout, const wstring &title, int refreshTimeOut, double scale) { + if (scale <= 0) + scale = 1.0; - fout << "\n\n"; - + fout << "" << endl; fout << "\n\n"; - fout << "\n"; + fout << "\n"; + if (refreshTimeOut > 0) fout << "\n"; - - fout << "" << toUTF8(title) << "\n"; + fout << "" << gdioutput::toUTF8(title) << "\n"; map< pair, pair > styles; - generateStyles(*this, fout, false, TL, styles); + generateStyles(gdi, fout, scale, false, gdi.getTL(), styles); fout << "\n"; fout << "\n"; - - list::const_iterator it = TL.begin(); - - double yscale = 1.3; - double xscale = 1.2; - int offsetY = 0; - while (it!=TL.end()) { - if (skipTextRender(it->format)) { - ++it; - continue; - } - - string yp = itos(int(yscale*it->yp) + offsetY); - string xp = itos(int(xscale *it->xp)); - - string estyle; - if (it->format!=1 && it->format!=boldSmall) { - if (it->format & textRight) - estyle = " style=\"position:absolute;left:" + - xp + "px;top:" + yp + "px\""; - else - estyle = " style=\"position:absolute;left:" + - xp + "px;top:" + yp + "px\""; - - } - else { - if (it->format & textRight) - estyle = " style=\"font-weight:bold;position:absolute;left:" + - xp + "px;top:" + yp + "px\""; - else - estyle = " style=\"font-weight:bold;position:absolute;left:" + - xp + "px;top:" + yp + "px\""; - } - string starttag, endtag; - getStyle(styles, it->getGdiFont(), narrow(it->font), estyle, starttag, endtag); - - if (!it->text.empty()) - fout << starttag << toUTF8(encodeXML(it->text)) << endtag << endl; - - if (it->format == boldLarge) { - list::const_iterator next = it; - ++next; - if (next == TL.end() || next->yp != it->yp) - offsetY += 7; - } - ++it; - } - - fout << "

"; + double yscale = 1.3 * scale; + double xscale = 1.2 * scale; + int offsetY = 0, offsetX = 0; + HTMLWriter::formatTL, InterpTextInfo>(fout, styles, gdi.getTL(), yscale, xscale, offsetY, offsetX); + + fout << "

"; char bf1[256]; char bf2[256]; GetTimeFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf2, 256); GetDateFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf1, 256); //fout << "Skapad av MeOS: " << bf1 << " "<< bf2 << "\n"; - fout << toUTF8(lang.tl("Skapad av ")) + "MeOS: " << bf1 << " "<< bf2 << "\n"; + fout << gdioutput::toUTF8(lang.tl("Skapad av ")) + "MeOS: " << bf1 << " "<< bf2 << "\n"; fout << "

\n"; fout << "\n"; fout << "\n"; - - return false; } wstring html_table_code(const wstring &in) @@ -292,40 +330,45 @@ bool sortTL_X(const TextInfo *a, const TextInfo *b) } -bool gdioutput::writeTableHTML(const wstring &file, - const wstring &title, int refreshTimeOut) const -{ +void HTMLWriter::writeTableHTML(gdioutput &gdi, + const wstring &file, + const wstring &title, + int refreshTimeOut, + double scale) { checkWriteAccess(file); ofstream fout(file.c_str()); if (fout.bad()) - return false; + return throw std::exception("Bad output stream"); - return writeTableHTML(fout, title, false, refreshTimeOut); + writeTableHTML(gdi, fout, title, false, refreshTimeOut, scale); } -bool gdioutput::writeTableHTML(ostream &fout, - const wstring &title, - bool simpleFormat, - int refreshTimeOut) const { - - fout << "\n\n"; +void HTMLWriter::writeTableHTML(gdioutput &gdi, + ostream &fout, + const wstring &title, + bool simpleFormat, + int refreshTimeOut, + double scale) { + if (scale <= 0) + scale = 1.0; + fout << "" << endl; fout << "\n\n"; - fout << "\n"; + fout << "\n"; if (refreshTimeOut > 0) fout << "\n"; - fout << "" << toUTF8(title) << "\n"; + fout << "" << gdioutput::toUTF8(title) << "\n"; map< pair, pair > styles; - generateStyles(*this, fout, true, TL, styles); + generateStyles(gdi, fout, scale, true, gdi.getTL(), styles); fout << "\n"; fout << "\n"; - - list::const_iterator it = TL.begin(); + auto &TL = gdi.getTL(); + auto it = TL.begin(); + int MaxX = gdi.getPageX() - 100; map tableCoordinates; //Get x-coordinates @@ -463,39 +506,39 @@ bool gdioutput::writeTableHTML(ostream &fout, for (size_t k=0;kxp]; - if (k==0 && thisCol!=0) - fout << " "; +if (k == 0 && thisCol != 0) +fout << " "; - int nextCol; - if (row.size()==k+1) - nextCol=tableCoordinates.rbegin()->second; - else - nextCol=tableCoordinates[row[k+1]->xp]; +int nextCol; +if (row.size() == k + 1) +nextCol = tableCoordinates.rbegin()->second; +else +nextCol = tableCoordinates[row[k + 1]->xp]; - int colspan=nextCol-thisCol; +int colspan = nextCol - thisCol; - assert(colspan>0); +assert(colspan > 0); - string style; +string style; - if (row[k]->format&textRight) - style=" style=\"text-align:right\""; +if (row[k]->format&textRight) +style = " style=\"text-align:right\""; - if (colspan==1 && !sizeSet[thisCol]) { - fout << " xp - row[k]->xp) : (MaxX-row[k]->xp)) << "\">"; - sizeSet[thisCol]=true; - } - else if (colspan>1) - fout << " "; - else - fout << " "; +if (colspan == 1 && !sizeSet[thisCol]) { + fout << " xp - row[k]->xp) : (MaxX - row[k]->xp)) << "\">"; + sizeSet[thisCol] = true; +} +else if (colspan > 1) +fout << " "; +else +fout << " "; - gdiFonts font = row[k]->getGdiFont(); - string starttag, endtag; - getStyle(styles, font, narrow(row[k]->font), "", starttag, endtag); +gdiFonts font = row[k]->getGdiFont(); +string starttag, endtag; +getStyle(styles, font, gdioutput::narrow(row[k]->font), "", starttag, endtag); - fout << starttag << toUTF8(html_table_code(row[k]->text)) << endtag << "" << endl; +fout << starttag << gdioutput::toUTF8(html_table_code(row[k]->text)) << endtag << "" << endl; } fout << "\n"; @@ -512,12 +555,423 @@ bool gdioutput::writeTableHTML(ostream &fout, GetTimeFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf2, 256); GetDateFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf1, 256); wstring meos = getMeosCompectVersion(); - fout << toUTF8(lang.tl("Skapad av ")) + "MeOS " - << toUTF8(meos) << ": " << bf1 << " "<< bf2 << "\n"; + fout << gdioutput::toUTF8(lang.tl("Skapad av ")) + "MeOS " + << gdioutput::toUTF8(meos) << ": " << bf1 << " " << bf2 << "\n"; fout << "


\n"; } - fout << "\n"; - fout << "\n"; - - return true; + fout << "" << endl; + fout << "" << endl; } + +extern wchar_t programPath[MAX_PATH]; + +void HTMLWriter::enumTemplates(TemplateType type, vector &descriptionFile) { + vector res; +#ifdef _DEBUG + expandDirectory((wstring(programPath) + L".\\..\\Lists\\").c_str(), L"*.template", res); +#endif + + wchar_t listpath[MAX_PATH]; + getUserFile(listpath, L""); + expandDirectory(listpath, L"*.template", res); + + if (exePath[0]) + expandDirectory(exePath, L"*.template", res); + set tags; + + tCache.clear(); + TemplateInfo ti; + for (wstring &fn : res) { + ifstream file(fn); + string str; + if (getline(file, str)) { + if (str == "@MEOS EXPORT TEMPLATE" && getline(file, str)) { + vector tagName; + split(str, "@", tagName); + if (tagName.size() == 2) { + ti.tag = tagName[0]; + if (!tags.insert(tagName[0]).second) + continue; // Already included + + string2Wide(tagName[1], ti.name); + if (getline(file, str)) { + string2Wide(str, ti.desc); + } + ti.file = fn; + if (type == TemplateType::List) + descriptionFile.push_back(ti); + } + else { + throw meosException(L"Bad template: " + fn); + } + } + else if (str == "@MEOS PAGE" && getline(file, str)) { + vector tagName; + split(str, "@", tagName); + if (tagName.size() == 2) { + ti.tag = tagName[0]; + if (!tags.insert(tagName[0]).second) + continue; // Already included + + string2Wide(tagName[1], ti.name); + if (getline(file, str)) { + string2Wide(str, ti.desc); + } + ti.file = fn; + if (type == TemplateType::Page) + descriptionFile.push_back(ti); + } + else { + throw meosException(L"Bad template: " + fn); + } + } + } + } +} + +const HTMLWriter &HTMLWriter::getWriter(TemplateType type, const string &tag) { + auto res = tCache.find(tag); + if (res != tCache.end()) { + return *res->second; + } + else { + vector descriptionFile; + enumTemplates(type, descriptionFile); + int ix = -1; + for (size_t k = 0; k < descriptionFile.size(); k++) { + if (descriptionFile[k].tag == tag) { + ix = k; + break; + } + } + if (ix == -1) + throw std::exception("Internal error"); + + shared_ptr tmpl = make_shared(); + tmpl->read(descriptionFile[ix].file); + + vector tagName; + split(tmpl->info, "@", tagName); + if (tagName.size() == 2) + tCache[tagName[0]] = tmpl; + + return *tmpl; + } +} + +string HTMLWriter::localize(const string &in) { + string out; + size_t offset = 0; + size_t pos = in.find_first_of('$', offset); + while (pos != string::npos) { + if (out.empty()) + out.reserve(in.length() * 2); + + size_t end = in.find_first_of('$', pos+1); + if (end != string::npos && end > pos + 2) { + wstring key = gdioutput::fromUTF8(in.substr(pos + 1, end - pos - 1)); + out += in.substr(offset, pos-offset); + if (key[0] != '!') + out += gdioutput::toUTF8(lang.tl(key)); + else + out += gdioutput::toUTF8(lang.tl(key.substr(1), true)); + + offset = end + 1; + pos = in.find_first_of('$', offset); + } + else + break; + } + if (offset == 0) + return trim(in); + + out += in.substr(offset); + return trim(out); +} + +void HTMLWriter::read(const wstring &fileName) { + ifstream file(fileName); + string dmy; + string *acc = &dmy; + string str; + int ok = 0 ; + const string comment = "//"; + while (getline(file, str)) { + if (ok == 0 && str == "@MEOS EXPORT TEMPLATE") { + ok = 1; + continue; + } + else if (ok == 1) { + ok = 2; + info = str; + } + else if (ok == 0 && str == "@MEOS PAGE") { + ok = 3; + continue; + } + else if (ok == 3) { + ok = 4; + info = str; + acc = &page; + continue; + } + else if (str.length() > 1 && str[0] == '%') + continue; // Ignore comment + else if (str == "@HEAD") + acc = &head; + else if (str == "@DESCRIPRION") + acc = &description; + else if (str == "@OUTERPAGE") + acc = &outerpage; + else if (str == "@INNERPAGE") + acc = &innerpage; + else if (str == "@SEPARATOR") + acc = &separator; + else if (str == "@END") + acc = &end; + else { + size_t cix = str.rfind(comment); + if (cix != string::npos) { + if (cix == 0) + continue; // Comment line + else if (cix > 0 && str[cix - 1] != ':') + str = str.substr(0, cix); + } + + *acc += localize(str) + "\n"; + if (!(str.empty() || acc->back() == '>' || acc->back() == ';' || acc->back() == '}' || acc->back() == '{')) + *acc += " "; + } + } +} +namespace { + void replaceAll(string& str, const string& from, const string& to) { + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + } +} + +class InterpPrintTextInfo { +public: + static int x(const PrintTextInfo &ti) { + return int(ti.xp); + } + + static int y(const PrintTextInfo &ti) { + return int(ti.yp); + } + + static const TextInfo &ti(const PrintTextInfo &in_ti) { + return in_ti.ti; + } +}; + +void HTMLWriter::generate(gdioutput &gdi, + ostream &fout, + const wstring &title, + const wstring &contentDescription, + bool respectPageBreak, + const int nRows, + const int numCol, + const int interval, + const int marginPercent, + double scale) const { + int w, h; + gdi.getTargetDimension(w, h); + + string meos = "MeOS: " + gdioutput::toUTF8(getMeosCompectVersion()) + ""; + + int margin = (w * marginPercent) / 100; + int height = nRows * gdi.getLineHeight(); + bool infPage = false; + if (nRows == 0) { + infPage = true; + height = gdi.getPageY()/numCol; + } + + PageInfo pageInfo; + pageInfo.topMargin = 20; + pageInfo.scaleX = 1.0f; + pageInfo.scaleY = 1.0f; + pageInfo.leftMargin = 20; + pageInfo.bottomMargin = 30; + pageInfo.pageY = float(height - margin); + pageInfo.printHeader = false; + pageInfo.yMM2PrintC = pageInfo.xMM2PrintC = 1; + pageInfo.xMM2PrintK = 0; + pageInfo.yMM2PrintK = 0; + + list rectangles; + vector pages; + pageInfo.renderPages(gdi.getTL(), rectangles, false, respectPageBreak || infPage, pages); + + int numChapter = 0; + for (auto &pi : pages) + if (pi.startChapter) + numChapter++; + + if (infPage && pages.size() > size_t(numCol)) { + bool respectChapter = numChapter == numCol; // If the number of chapters (linked lists) equals number of columns, respec these. + vector pagesOut; + bool startPage = true; + int ydiff = 0; + for (auto &p : pages) { + if (p.text.empty()) + continue; + + if (respectChapter) + startPage = p.startChapter; + else if (!pagesOut.empty() && pagesOut.back().text.back().yp + p.text.back().yp / 4 > height) + startPage = true; + + + if (startPage && pagesOut.size() < size_t(numCol)) { + pagesOut.push_back(move(p)); + startPage = false; + ydiff = int(pagesOut.back().text.back().yp); + } + else { + for (auto &t : p.text) { + pagesOut.back().text.push_back(move(t)); + pagesOut.back().text.back().yp += ydiff; + } + ydiff = int(pagesOut.back().text.back().yp); + } + } + pages.swap(pagesOut); + } + + string output = head; + replaceAll(output, "@D", gdioutput::toUTF8(encodeXML(contentDescription))); + replaceAll(output, "@T", gdioutput::toUTF8(encodeXML(title))); + replaceAll(output, "@M", meos); + map, pair> styles; + + { + stringstream sout; + generateStyles(gdi, sout, scale, false, gdi.getTL(), styles); + replaceAll(output, "@S", sout.str()); + } + + int nPage = (pages.size() + numCol - 1) / numCol; + replaceAll(output, "@N", itos(nPage)); + + fout << output; + + int ipCounter = 1; + int opCounter = 1; + + for (size_t pix = 0; pix < pages.size();) { + string innerpageoutput; + + for (int ip = 0; ip < numCol; ip++) { + if (pages.size() == pix) + break; + + if (ip > 0) { + // Separator + output = separator; + replaceAll(output, "@P", itos(ipCounter)); + replaceAll(output, "@L", itos((ip * 100) / numCol)); + + innerpageoutput += output; + } + + auto &p = pages[pix++]; + stringstream sout; + double yscale = 1.3 * scale; + double xscale = 1.2 * scale; + int offsetY = 0, offsetX = 0; + + formatTL, InterpPrintTextInfo>(sout, styles, p.text, yscale, xscale, offsetY, offsetX); + + output = innerpage; + replaceAll(output, "@P", itos(ipCounter++)); + replaceAll(output, "@L", itos((ip * 100) / numCol)); + replaceAll(output, "@C", sout.str()); + replaceAll(output, "@M", meos); + innerpageoutput += output; + } + + string outeroutput = outerpage; + replaceAll(outeroutput, "@P", itos(opCounter++)); + replaceAll(outeroutput, "@C", innerpageoutput); + replaceAll(output, "@M", meos); + replaceAll(output, "@N", itos(nPage)); + fout << outeroutput << endl; + } + + assert(opCounter - 1 == nPage); + output = end; + replaceAll(output, "@N", itos(opCounter - 1)); + replaceAll(output, "@I", itos(numCol)); + replaceAll(output, "@T", itos(interval)); + replaceAll(output, "@M", meos); + fout << output; +} + +void HTMLWriter::write(gdioutput &gdi, const wstring &file, const wstring &title, const wstring &contentsDescription, + bool respectPageBreak, + const string &typeTag, int refresh, + int rows, int cols, int time_ms, int margin, double scale) { + + checkWriteAccess(file); + ofstream fout(file.c_str()); + + write(gdi, fout, title, contentsDescription, respectPageBreak, typeTag, refresh, + rows, cols, time_ms, margin, scale); +} + +void HTMLWriter::write(gdioutput &gdi, ostream &fout, const wstring &title, const wstring &contentsDescription, + bool respectPageBreak, const string &typeTag, int refresh, + int rows, int cols, int time_ms, int margin, double scale) { + if (typeTag == "table") + writeTableHTML(gdi, fout, title, false, refresh, scale); + else if (typeTag == "free") { + writeHTML(gdi, fout, title, refresh, scale); + } + else { + /* auto res = tCache.find(typeTag); + if (res == tCache.end()) { + vector htmlTmpl; + HTMLWriter::enumTemplates(TemplateType::List, htmlTmpl); + int ix = -1; + for (size_t k = 0; k < htmlTmpl.size(); k++) { + if (htmlTmpl[k].tag == typeTag) { + ix = k; + break; + } + } + + if (ix == -1) + throw std::exception("Internal error"); + + shared_ptr tmpl = make_shared(); + tmpl->read(htmlTmpl[ix].file); + + vector tagName; + split(tmpl->info, "@", tagName); + if (tagName.size() == 2) + tCache[tagName[0]] = tmpl; + tmpl->generate(gdi, fout, title, contentsDescription, respectPageBreak, rows, cols, time_ms, margin, scale); + } + else { + res->second->generate(gdi, fout, title, contentsDescription, respectPageBreak, rows, cols, time_ms, margin, scale); + }*/ + + getWriter(TemplateType::List, typeTag).generate(gdi, fout, title, contentsDescription, respectPageBreak, rows, cols, time_ms, margin, scale); + } +} + +void HTMLWriter::write(gdioutput &gdi, const wstring &file, const wstring &title, int refresh, oListParam ¶m, const oEvent &oe) { + write(gdi, file, title, param.getContentsDescriptor(oe), param.pageBreak, param.htmlTypeTag, + refresh != 0 ? refresh : param.timePerPage / 1000, param.htmlRows, param.nColumns, + param.timePerPage, param.margin, param.htmlScale); +} + +void HTMLWriter::getPage(const oEvent &oe, string &out) const { + out = page; +} \ No newline at end of file diff --git a/code/HTMLWriter.h b/code/HTMLWriter.h new file mode 100644 index 0000000..e85ffdd --- /dev/null +++ b/code/HTMLWriter.h @@ -0,0 +1,114 @@ +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ + +#pragma once + +struct oListParam; + +class HTMLWriter { + string info; + string description; + string head; + string outerpage; + string innerpage; + string separator; + string end; + + string page; + + static map > tCache; + + static string localize(const string &in); + +public: + + static void reset() { + tCache.clear(); + } + + enum class TemplateType { + List, + Page + }; + + static const HTMLWriter &getWriter(TemplateType type, const string &tag); + + struct TemplateInfo { + string tag; + wstring name; + wstring desc; + wstring file; + }; + + static void enumTemplates(TemplateType type, vector &descriptionFile); + + void read(const wstring &fileName); + + void generate(gdioutput &gdi, + ostream &fout, + const wstring &title, + const wstring &contentDescription, + bool respectPageBreak, + const int nRows, + const int numCol, + const int interval, + const int marginPercent, + double scal) const; + + void getPage(const oEvent &oe, string &out) const; + + static void writeHTML(gdioutput &gdi, ostream &dout, const wstring &title, int refreshTimeOut, double scale); + + static void writeTableHTML(gdioutput &gdi, ostream &fout, + const wstring &title, + bool simpleFormat, + int refreshTimeOut, + double scale); + + static void writeTableHTML(gdioutput &gdi, const wstring &file, + const wstring &title, + int refreshTimeOut, + double scale); + + static void writeHTML(gdioutput &gdi, const wstring &file, + const wstring &title, int refreshTimeOut, double scale); + + static void write(gdioutput &gdi, const wstring &file, const wstring &title, const wstring &contentsDescription, + bool respectPageBreak, const string &typeTag, int refresh, + int rows, int cols, int time_ms, int margin, double scale); + + static void write(gdioutput &gdi, ostream &fout, const wstring &title, const wstring &contentsDescription, + bool respectPageBreak, const string &typeTag, int refresh, + int rows, int cols, int time_ms, int margin, double scale); + + static void write(gdioutput &gdi, const wstring &file, const wstring &title, int refresh, oListParam ¶m, const oEvent &oe); + + template + static void formatTL(ostream &fout, + const map< pair, pair > &styles, + const T &tl, + double &yscale, + double &xscale, + int &offsetY, + int &offsetX); + +}; \ No newline at end of file diff --git a/code/MeOSFeatures.cpp b/code/MeOSFeatures.cpp index 5b40255..47b0747 100644 --- a/code/MeOSFeatures.cpp +++ b/code/MeOSFeatures.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,6 +55,9 @@ MeOSFeatures::MeOSFeatures(void) addHead("Rogaining"); add(Rogaining, L"RO", "Rogaining"); add(PointAdjust, L"PA", "Manual point reductions and adjustments").require(Rogaining); + + addHead("Timekeeping"); + add(NoCourses, L"NC", "Without courses"); } MeOSFeatures::FeatureDescriptor &MeOSFeatures::add(Feature feat, const wchar_t *code, const char *descr) { @@ -239,6 +242,8 @@ void MeOSFeatures::loadDefaults(oEvent &oe) { void MeOSFeatures::useAll(oEvent &oe) { for (size_t k = 0; k < desc.size(); k++) { + if (desc[k].feat == NoCourses) + continue; if (desc[k].feat != _Head) features.insert(desc[k].feat); } diff --git a/code/MeOSFeatures.h b/code/MeOSFeatures.h index d6bff2d..9f92607 100644 --- a/code/MeOSFeatures.h +++ b/code/MeOSFeatures.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,6 +50,7 @@ public: DrawStartList, Bib, RunnerDb, + NoCourses, }; private: @@ -97,6 +98,9 @@ public: const string &getDescription(Feature f) const; const wstring &getCode(Feature f) const; + bool withoutCourses(const oEvent &oe) const { return hasFeature(NoCourses) && oe.getNumCourses() == 0; } + bool withCourses(const oEvent *oe) const { return !withoutCourses(*oe); } + wstring serialize() const; void deserialize(const wstring &input, oEvent &oe); }; diff --git a/code/Printer.h b/code/Printer.h index b1cc09b..896e768 100644 --- a/code/Printer.h +++ b/code/Printer.h @@ -4,7 +4,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -59,6 +59,7 @@ struct PageInfo { void renderPages(const list &tl, const list &rects, bool invertHeightY, + bool respectPageBreak, vector &pages); wstring pageInfo(const RenderedPage &page) const; @@ -68,6 +69,7 @@ struct PageInfo { struct RenderedPage { int nPage; // This page number wstring info; + bool startChapter = false; vector text; vector rectangles; __int64 checkSum; diff --git a/code/RestService.cpp b/code/RestService.cpp index a4389c8..de99274 100644 --- a/code/RestService.cpp +++ b/code/RestService.cpp @@ -1,4 +1,26 @@ -#include "stdafx.h" +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ + +#include "stdafx.h" #include "RestService.h" #include "meos_util.h" #include "restserver.h" @@ -8,7 +30,7 @@ int AutomaticCB(gdioutput *gdi, int type, void *data); -RestService::RestService() : AutoMachine("Informationsserver"), port(-1) { +RestService::RestService() : AutoMachine("Informationsserver", Machines::mInfoService), port(-1) { } RestService::~RestService() { @@ -30,19 +52,62 @@ void RestService::save(oEvent &oe, gdioutput &gdi) { else throw meosException("Invalid port number"); } + + if (gdi.isChecked("AllowEntry")) { + RestServer::EntryPermissionType pt = (RestServer::EntryPermissionType)gdi.getSelectedItem("PermissionPerson").first; + RestServer::EntryPermissionClass pc = (RestServer::EntryPermissionClass)gdi.getSelectedItem("PermissionClass").first; + server->setEntryPermission(pc, pt); + } + else { + server->setEntryPermission(RestServer::EntryPermissionClass::None, + RestServer::EntryPermissionType::None); + } } +void ListIpAddresses(vector& ip); + void RestService::settings(gdioutput &gdi, oEvent &oe, bool created) { if (port == -1) port = oe.getPropertyInt("ServicePort", 2009); settingsTitle(gdi, "MeOS Informationsserver REST-API"); + + //gdi.fillRight(); + gdi.pushX(); + gdi.addCheckbox("AllowEntry", "Tillåt anmälan", 0, false).setHandler(this); + gdi.addSelection("PermissionPerson", 180, 200, 0, L"Vem får anmäla sig:"); + gdi.addItem("PermissionPerson", RestServer::getPermissionsPersons()); + gdi.autoGrow("PermissionPerson"); + gdi.selectFirstItem("PermissionPerson"); + gdi.fillDown(); + gdi.addSelection("PermissionClass", 180, 200, 0, L"Till vilka klasser:"); + gdi.addItem("PermissionClass", RestServer::getPermissionsClass()); + gdi.autoGrow("PermissionClass"); + gdi.selectFirstItem("PermissionClass"); + bool disablePermisson = true; + gdi.popX(); + startCancelInterval(gdi, "Save", created, IntervalNone, L""); if (!server) gdi.addInput("Port", itow(port), 10, 0, L"Port:", L"#http://localhost:[PORT]/meos"); - else + else { gdi.addString("", 0, "Server startad på X#" + itos(port)); + auto per = server->getEntryPermission(); + if (get(per) != RestServer::EntryPermissionType::None) + disablePermisson = false; + else { + gdi.selectItemByData("PermissionPerson", size_t(get(per))); + gdi.selectItemByData("PermissionClass", size_t(get(per))); + } + } + if (disablePermisson) { + gdi.disableInput("PermissionPerson"); + gdi.disableInput("PermissionClass"); + } + else { + gdi.check("AllowEntry", true); + } gdi.popX(); gdi.addString("", 10, "help:rest"); @@ -65,7 +130,31 @@ void RestService::status(gdioutput &gdi) { gdi.addButton("Update", "Uppdatera").setHandler(this); gdi.dropLine(0.6); gdi.addString("", 1, "Testa servern:"); - gdi.addString("link", 0, "#http://localhost:" + itos(port) + "/meos").setHandler(this); + + string sport; + if (port != 80) { + sport = ":" + itos(port); + } + gdi.addString("link", 0, "#http://localhost" + sport + "/meos").setHandler(this); + + vector adr; + ListIpAddresses(adr); + + if (adr.size() > 0) { + gdi.dropLine(); + gdi.addString("", 1, "Externa adresser:"); + for (string &ip : adr) { + gdi.addString("link", 0, "#http://" + ip + sport + "/meos").setHandler(this); + } + } + + /* + if (get(server->getEntryPermission()) != RestServer::EntryPermissionType::None) { + gdi.addString("", fontMediumPlus, "Anmälan"); + + + }*/ + } gdi.dropLine(2); @@ -87,9 +176,13 @@ void RestService::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { if (bi.id == "Update") { gdi.getTabs().get(TAutoTab)->loadPage(gdi); } + else if (bi.id == "AllowEntry") { + gdi.setInputStatus("PermissionPerson", gdi.isChecked(bi.id)); + gdi.setInputStatus("PermissionClass", gdi.isChecked(bi.id)); + } } else if (type == GUI_LINK) { - wstring url = L"http://localhost:" + itow(port) + L"/meos"; + wstring url = ((TextInfo &)info).text; ShellExecute(NULL, L"open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); } } diff --git a/code/RestService.h b/code/RestService.h index fd65e97..923a379 100644 --- a/code/RestService.h +++ b/code/RestService.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/RunnerDB.cpp b/code/RunnerDB.cpp index acbbce1..6e3890b 100644 --- a/code/RunnerDB.cpp +++ b/code/RunnerDB.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -994,7 +994,7 @@ void RunnerDB::updateAdd(const oRunner &r, map &clubIdMap) if (r.getExtIdentifier() > 0) { RunnerWDBEntry *dbe = getRunnerById(int(r.getExtIdentifier())); if (dbe) { - dbe->dbe().cardNo = r.CardNo; + dbe->dbe().cardNo = r.getCardNo(); return; // Do not change too much in runner from national database } } diff --git a/code/RunnerDB.h b/code/RunnerDB.h index 56f4ecb..c6fcc4a 100644 --- a/code/RunnerDB.h +++ b/code/RunnerDB.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/SportIdent.cpp b/code/SportIdent.cpp index 0897284..0bc0b51 100644 --- a/code/SportIdent.cpp +++ b/code/SportIdent.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1688,7 +1688,7 @@ bool SportIdent::getCard6Data(BYTE *data, SICard &card) } string2Wide(lastNameByte, lastName); - wcsncpy(card.lastName, lastName.c_str(), 20); + wcsncpy_s(card.lastName, lastName.c_str(), 20); memcpy(firstNameByte, data+32+20, 20); firstNameByte[20] = 0; @@ -1698,7 +1698,7 @@ bool SportIdent::getCard6Data(BYTE *data, SICard &card) } string2Wide(firstNameByte, firstName); - wcsncpy(card.firstName, firstName.c_str(), 20); + wcsncpy_s(card.firstName, firstName.c_str(), 20); data+=128-16; diff --git a/code/SportIdent.h b/code/SportIdent.h index 358842f..ef3819d 100644 --- a/code/SportIdent.h +++ b/code/SportIdent.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/StdAfx.h b/code/StdAfx.h index e3ab47f..1c976c3 100644 --- a/code/StdAfx.h +++ b/code/StdAfx.h @@ -36,7 +36,7 @@ bool getDesktopFile(wchar_t *fileNamePath, const wchar_t *fileName, const wchar_ bool getMeOSFile(wchar_t *FileNamePath, const wchar_t *FileName); class gdioutput; -gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x = 0, int max_y = 0); +gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x = 0, int max_y = 0, bool fixedSize = false); gdioutput *getExtraWindow(const string &tag, bool toForeGround); string uniqueTag(const char *base); diff --git a/code/TabAuto.cpp b/code/TabAuto.cpp index d44b991..7021944 100644 --- a/code/TabAuto.cpp +++ b/code/TabAuto.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,6 +44,7 @@ #include "gdiconstants.h" #include "meosexception.h" +#include "HTMLWriter.h" static TabAuto *tabAuto = 0; int AutoMachine::uniqueId = 1; @@ -212,7 +213,7 @@ void TabAuto::timerCallback(gdioutput &gdi) DWORD d=0; if (reload && !editMode && gdi.getData("AutoPage", d) && d) - loadPage(gdi); + loadPage(gdi, false); } void TabAuto::setTimer(AutoMachine *am) @@ -259,7 +260,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) if (sm) sm->saveSettings(gdi); updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id=="Result") { PrintResultMachine *sm=dynamic_cast(getMachine(bu.getExtraInt())); @@ -271,8 +272,10 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) ext.push_back(make_pair(L"Webbdokument", L"*.html;*.htm")); wstring file = gdi.browseForSave(ext, L"html", index); - if (!file.empty()) + if (!file.empty()) { gdi.setText("ExportFile", file); + oe->setProperty("LastExportTarget", file); + } } else if (bu.id == "BrowseScript") { vector< pair > ext; @@ -288,8 +291,10 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) gdi.setInputStatus("ExportScript", stat); gdi.setInputStatus("BrowseFile", stat); gdi.setInputStatus("BrowseScript", stat); - gdi.setInputStatus("HTMLRefresh", stat); - gdi.setInputStatus("StructuredExport", stat); + if (gdi.hasField("HTMLRefresh")) { + gdi.setInputStatus("HTMLRefresh", stat); + gdi.setInputStatus("StructuredExport", stat); + } } else if (bu.id == "DoPrint") { bool stat = gdi.isChecked(bu.id); @@ -339,9 +344,11 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) prm->doPrint = gdi.isChecked("DoPrint"); prm->exportFile = gdi.getText("ExportFile"); prm->exportScript = gdi.getText("ExportScript"); - prm->structuredExport = gdi.isChecked("StructuredExport"); - prm->htmlRefresh = gdi.isChecked("HTMLRefresh") ? t : 0; + if (!prm->readOnly) { + prm->structuredExport = gdi.isChecked("StructuredExport"); + prm->htmlRefresh = gdi.isChecked("HTMLRefresh") ? t : 0; + gdi.getSelection("Classes", prm->classesToPrint); ListBoxInfo lbi; @@ -350,6 +357,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) par.selection=prm->classesToPrint; par.listCode = EStdListType(lbi.data); par.pageBreak = gdi.isChecked("PageBreak"); + par.showHeader = gdi.isChecked("ShowHeader"); par.showInterTimes = gdi.isChecked("ShowInterResults"); par.splitAnalysis = gdi.isChecked("SplitAnalysis"); int legNr = gdi.getSelectedItem("LegNumber").first; @@ -363,13 +371,15 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) } prm->po.onlyChanged = gdi.isChecked("OnlyChanged"); prm->pageBreak = gdi.isChecked("PageBreak"); + prm->showHeader = gdi.isChecked("ShowHeader"); + prm->showInterResult = gdi.isChecked("ShowInterResults"); prm->splitAnalysis = gdi.isChecked("SplitAnalysis"); prm->synchronize=true; //To force continuos data sync. setTimer(prm); } updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } #endif } @@ -401,7 +411,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) setTimer(sm); } updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id=="Save") { // General save AutoMachine *sm=getMachine(bu.getExtraInt()); @@ -410,7 +420,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) setTimer(sm); } updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id=="StartPrewarning") { PrewarningMachine *pwm=dynamic_cast(getMachine(bu.getExtraInt())); @@ -419,7 +429,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) pwm->waveFolder=gdi.getText("WaveFolder"); gdi.getSelection("Controls", pwm->controls); - oe->synchronizeList(oLPunchId); + oe->synchronizeList(oListId::oLPunchId); oe->clearPrewarningSounds(); pwm->synchronizePunches=true; @@ -432,7 +442,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) } } updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id=="StartPunch") { @@ -452,17 +462,17 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) } } updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id == "Cancel") { - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id == "Stop") { if (bu.getExtraInt()) stopMachine(getMachine(bu.getExtraInt())); updateSyncInfo(); - loadPage(gdi); + loadPage(gdi, false); } else if (bu.id == "PrinterSetup") { PrintResultMachine *prm = @@ -479,7 +489,7 @@ int TabAuto::processButton(gdioutput &gdi, const ButtonInfo &bu) if (prm) { prm->process(gdi, oe, SyncNone); setTimer(prm); - loadPage(gdi); + loadPage(gdi, false); } } else if (bu.id == "SelectAll") { @@ -540,8 +550,11 @@ bool TabAuto::stopMachine(AutoMachine *am) void TabAuto::settings(gdioutput &gdi, AutoMachine *sm, Machines ms) { editMode=true; - bool createNew = (sm==0); - if (!sm) { + bool createNew = (sm==0) || (ms == Machines::Unknown); + if (sm) { + ms = sm->getType(); + } + else { sm = AutoMachine::construct(ms); machines.push_back(sm); } @@ -576,7 +589,7 @@ void TabAuto::killMachines() AutoMachine::resetGlobalId(); } -bool TabAuto::loadPage(gdioutput &gdi) +bool TabAuto::loadPage(gdioutput &gdi, bool showSettingsLast) { oe->checkDB(); tabAuto=this; @@ -587,8 +600,8 @@ bool TabAuto::loadPage(gdioutput &gdi) int storedOY = 0; int storedOX = 0; if (isAP) { - storedOY = gdi.GetOffsetY(); - storedOX = gdi.GetOffsetX(); + storedOY = gdi.getOffsetY(); + storedOX = gdi.getOffsetX(); } gdi.clearPage(false); @@ -652,7 +665,8 @@ bool TabAuto::loadPage(gdioutput &gdi) if (isAP) { gdi.setOffset(storedOY, storedOY, true); } - + if (showSettingsLast && !machines.empty()) + settings(gdi, *machines.rbegin(), Machines::Unknown); gdi.refresh(); return true; } @@ -693,7 +707,7 @@ void AutoMachine::startCancelInterval(gdioutput &gdi, char *startCommand, bool c void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, bool created) { settingsTitle(gdi, "Resultatutskrift / export"); - wstring time=created ? L"10:00" : getTimeMS(interval); + wstring time = (created && interval <= 0) ? L"10:00" : getTimeMS(interval); startCancelInterval(gdi, "StartResult", created, IntervalMinute, time); if (created) { @@ -716,9 +730,15 @@ void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, bool created) { gdi.addButton("BrowseFile", "Bläddra...", AutomaticCB); gdi.setCX(cx); gdi.dropLine(2.3); - gdi.addCheckbox("StructuredExport", "Strukturerat exportformat", 0, structuredExport); - gdi.addCheckbox("HTMLRefresh", "HTML med AutoRefresh", 0, htmlRefresh != 0); - gdi.dropLine(1.2); + if (!readOnly) { + gdi.addCheckbox("StructuredExport", "Strukturerat exportformat", 0, structuredExport); + gdi.addCheckbox("HTMLRefresh", "HTML med AutoRefresh", 0, htmlRefresh != 0); + } + else { + gdi.addString("", 0, "HTML formaterad genom listinställningar"); + } + + gdi.dropLine(1.8); gdi.setCX(cx); gdi.addInput("ExportScript", exportScript, 32, 0, L"Skript att köra efter export:"); gdi.dropLine(0.7); @@ -730,16 +750,17 @@ void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, bool created) { gdi.setInputStatus("ExportScript", doExport); gdi.setInputStatus("BrowseFile", doExport); gdi.setInputStatus("BrowseScript", doExport); - gdi.setInputStatus("StructuredExport", doExport); - gdi.setInputStatus("HTMLRefresh", doExport); gdi.setInputStatus("PrinterSetup", doPrint); if (!readOnly) { + gdi.setInputStatus("StructuredExport", doExport); + gdi.setInputStatus("HTMLRefresh", doExport); + gdi.fillDown(); - gdi.addString("", 1, "Listval"); + gdi.addString("", fontMediumPlus, "Listval"); gdi.dropLine(); gdi.fillRight(); - gdi.addListBox("Classes", 150,300,0, L"", L"", true); + gdi.addListBox("Classes", 150, 300, 0, L"", L"", true); gdi.pushX(); gdi.fillDown(); vector< pair > d; @@ -771,8 +792,10 @@ void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, bool created) { gdi.selectItemByData("LegNumber", listInfo.getLegNumberCoded()); gdi.addCheckbox("PageBreak", "Sidbrytning mellan klasser", 0, pageBreak); + gdi.addCheckbox("ShowHeader", "Visa rubrik", 0, showHeader); + gdi.addCheckbox("ShowInterResults", "Visa mellantider", 0, showInterResult, - "Mellantider visas för namngivna kontroller."); + "Mellantider visas för namngivna kontroller."); gdi.addCheckbox("SplitAnalysis", "Med sträcktidsanalys", 0, splitAnalysis); gdi.addCheckbox("OnlyChanged", "Skriv endast ut ändade sidor", 0, po.onlyChanged); @@ -784,7 +807,8 @@ void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, bool created) { } else { gdi.fillDown(); - gdi.addString("", 1, L"Lista av typ 'X'#" + listInfo.getName()); + gdi.addString("", fontMediumPlus, L"Lista av typ 'X'#" + listInfo.getName()); + gdi.dropLine(); gdi.addCheckbox("OnlyChanged", "Skriv endast ut ändade sidor", 0, po.onlyChanged); } } @@ -804,7 +828,7 @@ void PrintResultMachine::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) if (doPrint) { gdiPrint.refresh(); try { - gdiPrint.print(po, oe); + gdiPrint.print(po, oe, true, false, listInfo.getParam().pageBreak); } catch (const meosException &ex) { printError = ex.wwhat(); @@ -814,10 +838,19 @@ void PrintResultMachine::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) } if (doExport) { if (!exportFile.empty()) { - if (structuredExport) - gdiPrint.writeTableHTML(exportFile, oe->getName(), htmlRefresh); - else - gdiPrint.writeHTML(exportFile, oe->getName(), htmlRefresh); + checkWriteAccess(exportFile); + wstring tExport = exportFile + L"~"; + if (!readOnly) { + if (structuredExport) + HTMLWriter::writeTableHTML(gdiPrint, tExport, oe->getName(), htmlRefresh, 1.0); + else + HTMLWriter::writeHTML(gdiPrint, tExport, oe->getName(), htmlRefresh, 1.0); + } + else { + HTMLWriter::write(gdiPrint, tExport, oe->getName(), 0, listInfo.getParam(), *oe); + } + DeleteFile(exportFile.c_str()); + MoveFile(tExport.c_str(), exportFile.c_str()); if (!exportScript.empty()) { ShellExecute(NULL, NULL, exportScript.c_str(), exportFile.c_str(), NULL, SW_HIDE); diff --git a/code/TabAuto.h b/code/TabAuto.h index 6072d2e..d6098bd 100644 --- a/code/TabAuto.h +++ b/code/TabAuto.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,6 +44,9 @@ enum Machines { mOnlineInput, mSaveBackup, mInfoService, + + mMySQLReconnect, + Unknown = -1, }; class AutoMachine @@ -51,6 +54,8 @@ class AutoMachine private: int myid; static int uniqueId; + const Machines type; + protected: bool editMode; @@ -76,7 +81,12 @@ public: virtual void status(gdioutput &gdi) = 0; virtual bool stop() {return true;} virtual AutoMachine *clone() const = 0; - AutoMachine(const string &s) : myid(uniqueId++), name(s), interval(0), timeout(0), + + Machines getType() { + return type; + }; + + AutoMachine(const string &s, Machines type) : myid(uniqueId++), type(type), name(s), interval(0), timeout(0), synchronize(false), synchronizePunches(false), editMode(false) {} virtual ~AutoMachine() = 0 {} }; @@ -93,6 +103,7 @@ protected: PrinterObject po; set classesToPrint; bool pageBreak; + bool showHeader; bool showInterResult; bool splitAnalysis; bool notShown; @@ -112,9 +123,18 @@ public: void process(gdioutput &gdi, oEvent *oe, AutoSyncType ast); void settings(gdioutput &gdi, oEvent &oe, bool created); - PrintResultMachine(int v):AutoMachine("Resultatutskrift") { + void setHTML(const wstring &file, int timeout) { + exportFile = file; + doExport = true; + doPrint = false; + if (timeout > 0) + interval = timeout; + } + + PrintResultMachine(int v):AutoMachine("Resultatutskrift", Machines::mPrintResultsMachine) { interval=v; pageBreak = true; + showHeader = false; showInterResult = true; notShown = true; splitAnalysis = true; @@ -126,9 +146,10 @@ public: structuredExport = true; htmlRefresh = v; } - PrintResultMachine(int v, const oListInfo &li):AutoMachine("Utskrift / export"), listInfo(li) { + PrintResultMachine(int v, const oListInfo &li):AutoMachine("Utskrift / export", Machines::mPrintResultsMachine), listInfo(li) { interval=v; pageBreak = true; + showHeader = false; showInterResult = true; notShown = true; splitAnalysis = true; @@ -160,7 +181,7 @@ public: void settings(gdioutput &gdi, oEvent &oe, bool created); void saveSettings(gdioutput &gdi); - SaveMachine():AutoMachine("Säkerhetskopiera") , saveIter(0) { + SaveMachine():AutoMachine("Säkerhetskopiera", Machines::mSaveBackup) , saveIter(0) { } }; @@ -177,7 +198,7 @@ public: PrewarningMachine *clone() const {return new PrewarningMachine(*this);} void status(gdioutput &gdi); void process(gdioutput &gdi, oEvent *oe, AutoSyncType ast); - PrewarningMachine():AutoMachine("Förvarningsröst") {} + PrewarningMachine():AutoMachine("Förvarningsröst", Machines::mPrewarningMachine) {} friend class TabAuto; }; @@ -212,7 +233,7 @@ public: void settings(gdioutput &gdi, oEvent &oe, bool created); void status(gdioutput &gdi); void process(gdioutput &gdi, oEvent *oe, AutoSyncType ast); - PunchMachine():AutoMachine("Stämplingsautomat"), radio(0) {} + PunchMachine():AutoMachine("Stämplingsautomat", Machines::mPunchMachine), radio(0) {} friend class TabAuto; }; @@ -228,7 +249,7 @@ public: void settings(gdioutput &gdi, oEvent &oe, bool created); void status(gdioutput &gdi); void process(gdioutput &gdi, oEvent *oe, AutoSyncType ast); - SplitsMachine() : AutoMachine("Sträcktider/WinSplits"), leg(-1) {} + SplitsMachine() : AutoMachine("Sträcktider/WinSplits", Machines::mSplitsMachine), leg(-1) {} friend class TabAuto; }; @@ -271,7 +292,10 @@ public: int processButton(gdioutput &gdi, const ButtonInfo &bu); int processListBox(gdioutput &gdi, const ListBoxInfo &bu); - bool loadPage(gdioutput &gdi); + bool loadPage(gdioutput &gdi, bool showSettingsLast); + bool loadPage(gdioutput &gdi) { + return loadPage(gdi, false); + } const char * getTypeStr() const {return "TAutoTab";} TabType getType() const {return TAutoTab;} diff --git a/code/TabBase.cpp b/code/TabBase.cpp index 706d07e..0ae479a 100644 --- a/code/TabBase.cpp +++ b/code/TabBase.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabBase.h b/code/TabBase.h index ccb0d32..887ddcf 100644 --- a/code/TabBase.h +++ b/code/TabBase.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabClass.cpp b/code/TabClass.cpp index 74ef54d..c3cb177 100644 --- a/code/TabClass.cpp +++ b/code/TabClass.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ #include "gdifonts.h" #include "oEventDraw.h" #include "MeOSFeatures.h" +#include "qualification_final.h" extern pEvent gEvent; const char *visualDrawWindow = "visualdraw"; @@ -109,6 +110,23 @@ TabClass::~TabClass(void) { } +oEvent::DrawMethod TabClass::getDefaultMethod(const set &allowedValues) const { + oEvent::DrawMethod dm = (oEvent::DrawMethod)oe->getPropertyInt("DefaultDrawMethod", (int)oEvent::DrawMethod::MeOS); + if (allowedValues.count(dm)) + return dm; + else + return oEvent::DrawMethod::MeOS; +} + +void TabClass::createDrawMethod(gdioutput& gdi) { + gdi.addSelection("Method", 200, 200, 0, L"Metod:"); + gdi.addItem("Method", lang.tl("Lottning") + L" (MeOS)", int(oEvent::DrawMethod::MeOS)); + gdi.addItem("Method", lang.tl("Lottning"), int(oEvent::DrawMethod::Random)); + gdi.addItem("Method", lang.tl("SOFT-lottning"), int(oEvent::DrawMethod::SOFT)); + + gdi.selectItemByData("Method", (int)getDefaultMethod({ oEvent::DrawMethod::Random, oEvent::DrawMethod::SOFT, oEvent::DrawMethod::MeOS })); +} + bool ClassInfoSortStart(ClassInfo &ci1, ClassInfo &ci2) { return ci1.firstStart>ci2.firstStart; @@ -237,6 +255,8 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) } else if (bi.id == "AssignCourses") { set selectedCourses, selectedLegs; + pClass pc = oe->getClass(ClassId); + gdi.getSelection("AllCourses", selectedCourses); gdi.getSelection("AllStages", selectedLegs); for (set::iterator it = selectedLegs.begin(); it != selectedLegs.end(); ++it) { @@ -248,7 +268,7 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) bool empty = true; for (size_t k = 0; k < forkingSetup.size(); k++) { if (forkingSetup[k].empty()) { - gdi.setText("leg"+ itos(k), lang.tl("Leg X: Do not modify.#" + itos(k+1))); + gdi.setText("leg"+ itos(k), lang.tl(L"Leg X: Do not modify.#" + pc->getLegNumber(k))); } else { empty = false; @@ -262,7 +282,7 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) break; } } - gdi.setText("leg"+ itos(k), lang.tl(L"Leg X: Use Y.#" + itow(k+1) + L"#" + crs)); + gdi.setText("leg"+ itos(k), lang.tl(L"Leg X: Use Y.#" + pc->getLegNumber(k) + L"#" + crs)); } } gdi.setInputStatus("ApplyForking", !empty); @@ -374,8 +394,7 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) pc->setNumStages(0); pc->synchronize(); gdi.restore(); - gdi.enableInput("MultiCourse", true); - gdi.enableInput("Courses"); + selectClass(gdi, ClassId); } else if (bi.id=="SetNStage") { if (!checkClassSelected(gdi)) @@ -441,8 +460,10 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) save(gdi, false); //Clears and reloads - gdi.selectItemByData("Courses", -2); - gdi.disableInput("Courses"); + if (gdi.hasField("Courses")) { + gdi.selectItemByData("Courses", -2); + gdi.disableInput("Courses"); + } oe->setupRelay(*pc, newType, nstages, st); if (gdi.hasField("MAdd")) { @@ -458,7 +479,8 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) pc->synchronize(); gdi.restore(); gdi.enableInput("MultiCourse", true); - gdi.enableInput("Courses"); + if (gdi.hasField("Courses")) + gdi.enableInput("Courses"); oe->adjustTeamMultiRunners(pc); } else { @@ -837,6 +859,9 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) } if (classes.empty()) { + gdi.dropLine(); + gdi.addButton("DrawMode", L"Återgå", ClassesCB); + gdi.scrollToBottom(); throw meosException("Ingen klass vald."); } @@ -855,8 +880,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) } else if (bi.id=="DoDrawAll") { readClassSettings(gdi); - int method = gdi.getSelectedItem("Method").first; - bool soft = method == DMSOFT; + oEvent::DrawMethod method = (oEvent::DrawMethod)gdi.getSelectedItem("Method").first; int pairSize = gdi.getSelectedItem("PairSize").first; bool drawCoursebased = drawInfo.coursesTogether; @@ -885,7 +909,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) for (map >::iterator it = specs.begin(); it != specs.end(); ++it) { - oe->drawList(it->second, soft, pairSize, oEvent::drawAll); + oe->drawList(it->second, method, pairSize, oEvent::DrawType::DrawAll); } oe->addAutoBib(); @@ -944,7 +968,8 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) prepareForDrawing(gdi); } else if (bi.id == "DrawMode") { - save(gdi, false); + if (gdi.hasField("Name")) + save(gdi, false); ClassId = 0; EditChanged=false; @@ -961,10 +986,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.addInput("Vacances", L"5%", 10, 0, L"Andel vakanser:"); gdi.popX(); - gdi.addSelection("Method", 200, 200, 0, L"Metod:"); - gdi.addItem("Method", lang.tl("Lottning"), DMRandom); - gdi.addItem("Method", lang.tl("SOFT-lottning"), DMSOFT); - gdi.selectItemByData("Method", getDefaultMethod({ DMRandom, DMSOFT })); + createDrawMethod(gdi); gdi.fillDown(); gdi.addCheckbox("LateBefore", "Efteranmälda före ordinarie"); @@ -1104,11 +1126,21 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) pairSize = gdi.getSelectedItem("PairSize").first; } - int method = gdi.getSelectedItem("Method").first; - bool useSoft = method == DMSOFT; + oEvent::DrawMethod method = (oEvent::DrawMethod)gdi.getSelectedItem("Method").first; + + int baseInterval = convertAbsoluteTimeMS(minInterval) / 2; + + if (baseInterval<1 || baseInterval>60 * 60 || baseInterval == NOTIME) + throw meosException("Ogiltigt minimalt intervall."); + + int iFirstStart = oe->getRelativeTime(firstStart); + + if (iFirstStart <= 0 || iFirstStart == NOTIME) + throw meosException("Ogiltig första starttid. Måste vara efter nolltid."); + clearPage(gdi, true); oe->automaticDrawAll(gdi, firstStart, minInterval, vacances, - lateBefore, useSoft, pairSize); + lateBefore, method, pairSize); oe->addAutoBib(); gdi.scrollToBottom(); gdi.addButton("Cancel", "Återgå", ClassesCB); @@ -1262,6 +1294,9 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) readDrawInfo(gdi, drawInfo); if (drawInfo.baseInterval <= 0 || drawInfo.baseInterval == NOTIME) throw meosException("Ogiltigt basintervall."); + if (drawInfo.firstStart <= 0 || drawInfo.firstStart == NOTIME) + throw meosException("Ogiltig första starttid. Måste vara efter nolltid."); + if (drawInfo.minClassInterval <= 0 || drawInfo.minClassInterval == NOTIME) throw meosException("Ogiltigt minimalt intervall."); if (drawInfo.minClassInterval > drawInfo.maxClassInterval || drawInfo.maxClassInterval == NOTIME) @@ -1277,12 +1312,6 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) } gdi.enableEditControls(false); - - if (drawInfo.firstStart<=0) { - drawInfo.baseInterval = 0; - gdi.addString("", 0, "Raderar starttider..."); - } - oe->optimizeStartOrder(gdi, drawInfo, cInfo); showClassSettings(gdi); @@ -1340,7 +1369,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) for (set::const_iterator it = classes.begin(); it != classes.end(); ++it) { vector spec; spec.push_back(ClassDrawSpecification(*it, 0, 0, 0, 0)); - oe->drawList(spec, false, 1, oEvent::drawAll); + oe->drawList(spec, oEvent::DrawMethod::Random, 1, oEvent::DrawType::DrawAll); } if (bi.getExtraInt() == 1) @@ -1363,7 +1392,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) return classCB(gdi, type, &bi); } else if (bi.id == "DrawAllBefore" || bi.id == "DrawAllAfter") { - oe->drawRemaining(true, bi.id == "DrawAllAfter"); + oe->drawRemaining(oEvent::DrawMethod::MeOS, bi.id == "DrawAllAfter"); oe->addAutoBib(); loadPage(gdi); } @@ -1373,7 +1402,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) DWORD cid=ClassId; pClass pc = oe->getClass(cid); - DrawMethod method = DrawMethod(gdi.getSelectedItem("Method").first); + oEvent::DrawMethod method = oEvent::DrawMethod(gdi.getSelectedItem("Method").first); int interval = 0; if (gdi.hasField("Interval")) @@ -1405,11 +1434,11 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) throw std::exception("Ogiltig första starttid. Måste vara efter nolltid."); - oEvent::DrawType dtype(oEvent::drawAll); + oEvent::DrawType dtype(oEvent::DrawType::DrawAll); if (bi.id=="DoDrawAfter") - dtype = oEvent::remainingAfter; + dtype = oEvent::DrawType::RemainingAfter; else if (bi.id=="DoDrawBefore") - dtype = oEvent::remainingBefore; + dtype = oEvent::DrawType::RemainingBefore; else { if (warnDrawStartTime(gdi, t, false)) return 0; @@ -1435,24 +1464,24 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) if (gdi.hasField("ScaleFactor")) scaleFactor = _wtof(gdi.getText("ScaleFactor").c_str()); - if (method == DMRandom || method == DMSOFT) { + if (method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::MeOS) { vector spec; spec.push_back(ClassDrawSpecification(cid, leg, t, interval, vacanses)); - oe->drawList(spec, method == DMSOFT, pairSize, dtype); + oe->drawList(spec, method, pairSize, dtype); } - else if (method == DMClumped) + else if (method == oEvent::DrawMethod::Clumped) oe->drawListClumped(cid, t, interval, vacanses); - else if (method == DMPursuit || method == DMReversePursuit) { + else if (method == oEvent::DrawMethod::Pursuit || method == oEvent::DrawMethod::ReversePursuit) { oe->drawPersuitList(cid, t, restartTime, maxTime, interval, pairSize, - method == DMReversePursuit, + method == oEvent::DrawMethod::ReversePursuit, scaleFactor); } - else if (method == DMSimultaneous) { + else if (method == oEvent::DrawMethod::Simultaneous) { simultaneous(cid, time); } - else if (method == DMSeeded) { + else if (method == oEvent::DrawMethod::Seeded) { ListBoxInfo seedMethod; gdi.getSelectedItem("SeedMethod", seedMethod); wstring seedGroups = gdi.getText("SeedGroups"); @@ -1525,7 +1554,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) vector spec; spec.push_back(ClassDrawSpecification(ClassId, leg, 0, 0, 0)); - oe->drawList(spec, false, 1, oEvent::drawAll); + oe->drawList(spec, oEvent::DrawMethod::Random, 1, oEvent::DrawType::DrawAll); loadPage(gdi); } else if (bi.id=="Draw") { @@ -1567,7 +1596,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.setRestorePoint("MultiDayDraw"); - lastDrawMethod = NOMethod; + lastDrawMethod = oEvent::DrawMethod::NOMethod; drawDialog(gdi, getDefaultMethod(getSupportedDrawMethods(multiDay)), *pc); } else if (bi.id == "HandleMultiDay") { @@ -1578,15 +1607,15 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) if (!pc) throw std::exception("Class not found"); - if (!gdi.isChecked(bi.id) && (lastDrawMethod == DMReversePursuit || - lastDrawMethod == DMPursuit)) { - drawDialog(gdi, DMSOFT, *pc); + if (!gdi.isChecked(bi.id) && (lastDrawMethod == oEvent::DrawMethod::ReversePursuit || + lastDrawMethod == oEvent::DrawMethod::Pursuit)) { + drawDialog(gdi, oEvent::DrawMethod::MeOS, *pc); } else setMultiDayClass(gdi, gdi.isChecked(bi.id), lastDrawMethod); } - else if (bi.id == "QualificationFinal") { + else if (bi.id == "QualificationFinal" || bi.id == "UpdateQF") { save(gdi, true); pClass pc = oe->getClass(ClassId); if (!pc) @@ -1599,6 +1628,32 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) pc->updateFinalClasses(0, true); loadPage(gdi); } + else if (bi.id == "RemoveQF") { + pClass pc = oe->getClass(ClassId); + if (!pc) + throw std::exception("Class not found"); + if (pc->getQualificationFinal()) { + bool hasResult = false; + for (int inst = 0; inst < pc->getQualificationFinal()->getNumClasses(); inst++) { + auto vc = pc->getVirtualClass(inst); + if (vc && oe->classHasResults(vc->getId())) { + hasResult = true; + } + } + if (hasResult) { + if (!gdi.ask(L"Det finns resultat som går förlorade om du tar bort schemat. Vill du fortsätta?")) + return 0; + } + else { + if (!gdi.ask(L"Vill du ta bort schemat?")) + return 0; + } + pc->getDI().setString("Qualification", L""); + pc->clearQualificationFinal(); + pc->synchronize(true); + selectClass(gdi, pc->getId()); + } + } else if (bi.id=="Bibs") { save(gdi, true); if (!checkClassSelected(gdi)) @@ -1722,6 +1777,28 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) pClass pc=oe->getClass(ClassId); if (!pc) throw std::exception("Class not found"); + if (pc->getQualificationFinal() || (pc->getParentClass() && pc->getParentClass()->getQualificationFinal())) { + set base; + if (pc->getParentClass()) { + pClass baseClass = pc->getParentClass(); + baseClass->getQualificationFinal()->getBaseClassInstances(base); + int inst = (pc->getId() - baseClass->getId()) / MaxClassId; + + // Change to base class + pc = baseClass; + ClassId = pc->getId(); + + if (!base.count(inst)) { + throw meosException("Operationen stöds inte på en finalklass"); + } + } + else { + pc->getQualificationFinal()->getBaseClassInstances(base); + } + if (base.size() <= 1) { + throw meosException("Kval-Final-schemat har endast en basklass"); + } + } clearPage(gdi, true); gdi.addString("", boldLarge, L"Dela klass: X#" + pc->getName()); @@ -1738,18 +1815,39 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) oClass::getSplitMethods(mt); gdi.addItem("Type", mt); gdi.selectFirstItem("Type"); + int numSplitDef = 2; + if (pc->getQualificationFinal()) { + gdi.fillDown(); + gdi.popX(); + gdi.dropLine(3); + set base; + pc->getQualificationFinal()->getBaseClassInstances(base); + gdi.addString("", 1, "Kval/final-schema"); + for (int i : base) { + if (pc->getVirtualClass(i)) { + gdi.addStringUT(0, pc->getVirtualClass(i)->getName()); + int tot2 = 0; + oe->getNumClassRunners(pc->getVirtualClass(i)->getId(), 0, tot2, fin, dns); + tot += tot2; + } + } + numSplitDef = base.size(); + gdi.dropLine(1); + gdi.setData("NumCls", numSplitDef); + } + else { + gdi.addSelection("SplitInput", 100, 150, ClassesCB, L"Antal klasser:").setExtra(tot); + vector< pair > sp; + for (int k = 2; k < 10; k++) + sp.push_back(make_pair(itow(k), k)); + gdi.addItem("SplitInput", sp); + gdi.selectFirstItem("SplitInput"); + gdi.dropLine(3); + gdi.popX(); - gdi.addSelection("SplitInput", 100, 150, ClassesCB, L"Antal klasser:").setExtra(tot); - vector< pair > sp; - for (int k = 2; k < 10; k++) - sp.push_back(make_pair(itow(k), k)); - gdi.addItem("SplitInput", sp); - gdi.selectFirstItem("SplitInput"); - - gdi.dropLine(3); - gdi.popX(); + } - updateSplitDistribution(gdi, 2, tot); + updateSplitDistribution(gdi, numSplitDef, tot); } else if (bi.id=="DoSplit") { if (!checkClassSelected(gdi)) @@ -1761,9 +1859,17 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) ListBoxInfo lbi; gdi.getSelectedItem("Type", lbi); - - int number = gdi.getSelectedItem("SplitInput").first; + int number; + if (gdi.hasData("NumCls")) { + DWORD dn; + number = gdi.getData("NumCls", dn); + number = dn; + } + else { + number = gdi.getSelectedItem("SplitInput").first; + } vector parts(number); + for (int k = 0; k < number; k++) { string id = "CLS" + itos(k); parts[k] = gdi.getTextNo(id, false); @@ -1983,7 +2089,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) if (!pc) throw std::exception("Nullpointer exception"); - drawDialog(gdi, DrawMethod(bi.data), *pc); + drawDialog(gdi, oEvent::DrawMethod(bi.data), *pc); } } else if (type==GUI_INPUTCHANGE) { @@ -2251,11 +2357,7 @@ void TabClass::showClassSettings(gdioutput &gdi) gdi.setCX(rc.left + gdi.scaleLength(10)); gdi.setCY(rc.top + gdi.scaleLength(10)); - gdi.addSelection("Method", 200, 200, 0, L"Metod:"); - gdi.addItem("Method", lang.tl("Lottning"), DMRandom); - gdi.addItem("Method", lang.tl("SOFT-lottning"), DMSOFT); - - gdi.selectItemByData("Method", getDefaultMethod({DMRandom, DMSOFT})); + createDrawMethod(gdi); gdi.addSelection("PairSize", 150, 200, 0, L"Tillämpa parstart:"); gdi.addItem("PairSize", getPairOptions()); @@ -2292,11 +2394,13 @@ void TabClass::selectClass(gdioutput &gdi, int cid) if (cid==0) { gdi.restore("", true); gdi.disableInput("MultiCourse", true); - gdi.enableInput("Courses"); + if (gdi.hasField("Courses")) + gdi.enableInput("Courses"); gdi.enableEditControls(false); gdi.setText("Name", L""); gdi.selectItemByData("Courses", -2); gdi.check("AllowQuickEntry", true); + gdi.setText("NumberMaps", L""); if (gdi.hasField("FreeStart")) gdi.check("FreeStart", false); @@ -2333,6 +2437,7 @@ void TabClass::selectClass(gdioutput &gdi, int cid) pc->synchronize(); gdi.setText("Name", pc->getName()); + gdi.setTextZeroBlank("NumberMaps", pc->getNumberMaps(true)); gdi.setText("ClassType", pc->getType()); gdi.setText("StartName", pc->getStart()); @@ -2367,18 +2472,48 @@ void TabClass::selectClass(gdioutput &gdi, int cid) gdi.check("LockStartList", active && pc->lockedClassAssignment()); } ClassId=cid; + + if (pc->getQualificationFinal()) { + gdi.restore("", false); + gdi.enableInput("MultiCourse", false); - if (pc->hasTrueMultiCourse()) { + if (gdi.hasField("Courses")) { + gdi.enableInput("Courses"); + pCourse pcourse = pc->getCourse(); + gdi.selectItemByData("Courses", pcourse ? pcourse->getId() : -2); + } + + gdi.setRestorePoint(); + gdi.fillDown(); + gdi.newColumn(); + + int cx = gdi.getCX(), cy = gdi.getCY(); + gdi.setCX(cx + 10); + gdi.setCY(cy + 10); + + gdi.addString("", fontMediumPlus, "Kval/final-schema"); + gdi.pushX(); + gdi.dropLine(0.3); + gdi.fillRight(); + gdi.addButton("UpdateQF", "Uppdatera", ClassesCB); + gdi.fillDown(); + gdi.addButton("RemoveQF", "Ta bort", ClassesCB); + gdi.popX(); + pc->getQualificationFinal()->printScheme(pc, gdi); + } + else if (pc->hasTrueMultiCourse()) { gdi.restore("", false); multiCourse(gdi, pc->getNumStages()); gdi.refresh(); - gdi.addItem("Courses", lang.tl("Flera banor"), -3); - gdi.selectItemByData("Courses", -3); - gdi.disableInput("Courses"); - gdi.check("CoursePool", pc->hasCoursePool()); - + if (gdi.hasField("Courses")) { + gdi.addItem("Courses", lang.tl("Flera banor"), -3); + gdi.selectItemByData("Courses", -3); + gdi.disableInput("Courses"); + gdi.check("CoursePool", pc->hasCoursePool()); + } + if (gdi.hasField("Unordered")) gdi.check("Unordered", pc->hasUnorderedLegs()); @@ -2432,9 +2567,11 @@ void TabClass::selectClass(gdioutput &gdi, int cid) else { gdi.restore("", true); gdi.enableInput("MultiCourse", true); - gdi.enableInput("Courses"); - pCourse pcourse = pc->getCourse(); - gdi.selectItemByData("Courses", pcourse ? pcourse->getId():-2); + if (gdi.hasField("Courses")) { + gdi.enableInput("Courses"); + pCourse pcourse = pc->getCourse(); + gdi.selectItemByData("Courses", pcourse ? pcourse->getId() : -2); + } } if (gdi.hasField("QualificationFinal")) gdi.setInputStatus("QualificationFinal", pc->getParentClass() == 0); @@ -2490,14 +2627,14 @@ void TabClass::legSetup(gdioutput &gdi) void TabClass::multiCourse(gdioutput &gdi, int nLeg) { currentStage=-1; - - bool simpleView = nLeg==1; + pClass pc = oe->getClass(ClassId); + bool isQF = (pc && pc->isQualificationFinalClass()); + bool simpleView = nLeg==1 || isQF; bool showGuide = (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Relay) || - oe->getMeOSFeatures().hasFeature(MeOSFeatures::Patrol)) && nLeg==0; + oe->getMeOSFeatures().hasFeature(MeOSFeatures::Patrol)) && nLeg==0 && !isQF; if (nLeg == 0 && !showGuide) { - pClass pc = oe->getClass(ClassId); if (pc) { pc->setNumStages(1); pc->setStartType(0, STDrawn, false); @@ -2512,7 +2649,6 @@ void TabClass::multiCourse(gdioutput &gdi, int nLeg) { gdi.fillDown(); gdi.newColumn(); - int cx=gdi.getCX(), cy=gdi.getCY(); gdi.setCX(cx+10); gdi.setCY(cy+10); @@ -2551,6 +2687,9 @@ void TabClass::multiCourse(gdioutput &gdi, int nLeg) { gdi.addCheckbox("CoursePool", "Använd banpool", MultiCB, false, "Knyt löparna till banor från en pool vid målgång."); + gdi.addCheckbox("LockForking", "Lås gafflingar", MultiCB, false, + "Markera för att förhindra oavsiktlig ändring av gafflingsnycklar."); + gdi.addButton("OneCourse", "Endast en bana", MultiCB, "Använd endast en bana i klassen"); gdi.setRestorePoint("Courses"); selectCourses(gdi, 0); @@ -2627,7 +2766,8 @@ void TabClass::multiCourse(gdioutput &gdi, int nLeg) { } gdi.dropLine(-0.1); - gdi.addButton(string("@Course")+legno, "Banor...", MultiCB); + if (oe->getMeOSFeatures().withCourses(oe)) + gdi.addButton(string("@Course")+legno, "Banor...", MultiCB); gdi.fillDown(); gdi.popX(); @@ -2648,25 +2788,27 @@ void TabClass::multiCourse(gdioutput &gdi, int nLeg) { } gdi.pushX(); - gdi.fillRight(); - gdi.addCheckbox("CoursePool", "Använd banpool", MultiCB, false, + if (oe->getMeOSFeatures().withCourses(oe)) { + gdi.fillRight(); + gdi.addCheckbox("CoursePool", "Använd banpool", MultiCB, false, "Knyt löparna till banor från en pool vid målgång."); - gdi.addCheckbox("Unordered", "Oordnade parallella sträckor", MultiCB, false, + gdi.addCheckbox("Unordered", "Oordnade parallella sträckor", MultiCB, false, "Tillåt löpare inom en parallell grupp att springa gruppens banor i godtycklig ordning."); - gdi.addCheckbox("LockForking", "Lås gafflingar", MultiCB, false, + gdi.addCheckbox("LockForking", "Lås gafflingar", MultiCB, false, "Markera för att förhindra oavsiktlig ändring av gafflingsnycklar."); - gdi.popX(); - gdi.fillRight(); - gdi.dropLine(1.7); - gdi.addString("FairForking", 1, "The forking is fair."); - gdi.setCX(gdi.getCX() + gdi.getLineHeight() * 5); - gdi.dropLine(-0.3); - gdi.addButton("ShowForking", "Show forking...", MultiCB); - gdi.fillDown(); - gdi.addButton("DefineForking", "Define forking...", MultiCB); + gdi.popX(); + gdi.fillRight(); + gdi.dropLine(1.7); + gdi.addString("FairForking", 1, "The forking is fair."); + gdi.setCX(gdi.getCX() + gdi.getLineHeight() * 5); + gdi.dropLine(-0.3); + gdi.addButton("ShowForking", "Show forking...", MultiCB); + gdi.fillDown(); + gdi.addButton("DefineForking", "Define forking...", MultiCB); - gdi.popX(); + gdi.popX(); + } RECT rc; rc.left = cx; rc.right = gdi.getWidth()+10; @@ -2676,7 +2818,7 @@ void TabClass::multiCourse(gdioutput &gdi, int nLeg) { gdi.setRestorePoint("Courses"); - if (nLeg==1) + if (nLeg==1 && oe->getMeOSFeatures().withCourses(oe)) gdi.sendCtrlMessage("@Course0"); } @@ -2721,6 +2863,10 @@ void TabClass::save(gdioutput &gdi, bool skipReload) ClassId=pc->getId(); pc->setName(name); + if (gdi.hasField("NumberMaps")) { + pc->setNumberMaps(gdi.getTextNo("NumberMaps")); + } + if (gdi.hasField("StartName")) pc->setStart(gdi.getText("StartName")); @@ -2771,19 +2917,21 @@ void TabClass::save(gdioutput &gdi, bool skipReload) pc->lockedClassAssignment(locked); } - int crs = gdi.getSelectedItem("Courses").first; + if (gdi.hasField("Courses")) { + int crs = gdi.getSelectedItem("Courses").first; - if (crs==0) { - //Skapa ny bana... - pCourse pcourse=oe->addCourse(L"Bana " + name); - pc->setCourse(pcourse); - pc->synchronize(); - return; + if (crs == 0) { + //Skapa ny bana... + pCourse pcourse = oe->addCourse(L"Bana " + name); + pc->setCourse(pcourse); + pc->synchronize(); + return; + } + else if (crs == -2) + pc->setCourse(0); + else if (crs > 0) + pc->setCourse(oe->getCourse(crs)); } - else if (crs==-2) - pc->setCourse(0); - else if (crs > 0) - pc->setCourse(oe->getCourse(crs)); if (pc->hasMultiCourse()) { @@ -2890,16 +3038,16 @@ bool TabClass::loadPage(gdioutput &gdi) oe->checkNecessaryFeatures(); gdi.selectTab(tabId); clearPage(gdi, false); - int xp=gdi.getCX(); - - const int button_w=gdi.scaleLength(90); + int xp = gdi.getCX(); + + const int button_w = gdi.scaleLength(90); string switchMode; - switchMode=tableMode ? "Formulärläge" : "Tabelläge"; + switchMode = tableMode ? "Formulärläge" : "Tabelläge"; gdi.addButton(2, 2, button_w, "SwitchMode", switchMode, - ClassesCB, "Välj vy", false, false).fixedCorner(); + ClassesCB, "Välj vy", false, false).fixedCorner(); if (tableMode) { - Table *tbl=oe->getClassTB(); + Table *tbl = oe->getClassTB(); gdi.addTable(tbl, xp, 30); return true; } @@ -2908,7 +3056,7 @@ bool TabClass::loadPage(gdioutput &gdi) try { defineForking(gdi, false); } - catch(...) { + catch (...) { showForkingGuide = false; throw; } @@ -2933,23 +3081,28 @@ bool TabClass::loadPage(gdioutput &gdi) gdi.addInput("Name", L"", 14, ClassesCB, L"Klassnamn:"); bool sameLineNameCourse = true; if (showAdvanced) { - gdi.addCombo("ClassType", 100, 300, 0, L"Typ:"); + gdi.addCombo("ClassType", 80, 300, 0, L"Typ:"); oe->fillClassTypes(gdi, "ClassType"); sameLineNameCourse = false; } + bool useCourse = oe->getMeOSFeatures().withoutCourses(*oe) == false; - if (showMulti(false) || !sameLineNameCourse) { + if (showMulti(false) && useCourse) { + gdi.addInput("NumberMaps", L"", 6, ClassesCB, L"Antal kartor:"); + } + + if (useCourse && (showMulti(false) || !sameLineNameCourse)) { gdi.dropLine(3); gdi.popX(); } - - gdi.addSelection("Courses", 120, 400, ClassesCB, L"Bana:"); - oe->fillCourses(gdi, "Courses", true); - gdi.addItem("Courses", lang.tl("Ingen bana"), -2); - + if (useCourse) { + gdi.addSelection("Courses", 120, 400, ClassesCB, L"Bana:"); + oe->fillCourses(gdi, "Courses", true); + gdi.addItem("Courses", lang.tl("Ingen bana"), -2); + } if (showMulti(false)) { gdi.dropLine(0.9); - if (showMulti(true)) { + if (showMulti(true) || !useCourse) { gdi.addButton("MultiCourse", "Flera banor/stafett...", ClassesCB); } else { @@ -2957,6 +3110,9 @@ bool TabClass::loadPage(gdioutput &gdi) } gdi.disableInput("MultiCourse"); } + else if (useCourse) { + gdi.addInput("NumberMaps", L"", 6, ClassesCB, L"Antal kartor:"); + } gdi.popX(); if (showAdvanced) { @@ -3077,7 +3233,7 @@ bool TabClass::loadPage(gdioutput &gdi) func.push_back(ButtonData("QuickSettings", "Snabbinställningar", true)); if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::MultipleRaces)) - func.push_back(ButtonData("QualificationFinal", "Kval-Final-Schema", false)); + func.push_back(ButtonData("QualificationFinal", "Kval/final-schema", false)); RECT funRect; funRect.right = gdi.getCX() - 7; @@ -3107,7 +3263,7 @@ bool TabClass::loadPage(gdioutput &gdi) gdi.dropLine(2.5); funRect.bottom = gdi.getCY(); - gdi.addRectangle(funRect, colorLightGreen); + gdi.addRectangle(funRect, colorLightBlue); gdi.popX(); gdi.dropLine(0.5); @@ -3246,32 +3402,35 @@ void TabClass::prepareForDrawing(gdioutput &gdi) { gdi.refresh(); } -void TabClass::drawDialog(gdioutput &gdi, DrawMethod method, const oClass &pc) { - oe->setProperty("DefaultDrawMethod", method); +bool isInSameClass(oEvent::DrawMethod m1, oEvent::DrawMethod m2, const set &cls) { + return cls.count(m1) && cls.count(m2); +} + +void TabClass::drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClass &pc) { + oe->setProperty("DefaultDrawMethod", (int)method); if (lastDrawMethod == method) return; bool noUpdate = false; - if (lastDrawMethod == DMPursuit && method == DMReversePursuit) - noUpdate = true; - if (lastDrawMethod == DMReversePursuit && method == DMPursuit) + if (isInSameClass(lastDrawMethod, method, { oEvent::DrawMethod::Pursuit, + oEvent::DrawMethod::ReversePursuit })) noUpdate = true; - if (lastDrawMethod == DMRandom && method == DMSOFT) + if (isInSameClass(lastDrawMethod, method, { oEvent::DrawMethod::Random, + oEvent::DrawMethod::SOFT, + oEvent::DrawMethod::MeOS })) noUpdate = true; - if (lastDrawMethod == DMSOFT && method == DMRandom) - noUpdate = true; - + if (noUpdate) { lastDrawMethod = method; return; } int firstStart = 3600, - interval = 120, - vac = _wtoi(lastNumVac.c_str()); + interval = 120, + vac = _wtoi(lastNumVac.c_str()); int pairSize = lastPairSize; @@ -3292,7 +3451,7 @@ void TabClass::drawDialog(gdioutput &gdi, DrawMethod method, const oClass &pc) { const bool multiDay = oe->hasPrevStage() && gdi.isChecked("HandleMultiDay"); - if (method == DMSeeded) { + if (method == oEvent::DrawMethod::Seeded) { gdi.addString("", 10, "help:seeding_info"); gdi.dropLine(1); gdi.pushX(); @@ -3306,16 +3465,16 @@ void TabClass::drawDialog(gdioutput &gdi, DrawMethod method, const oClass &pc) { else gdi.selectItemByData("SeedMethod", lastSeedMethod); seedmethod.setSynchData(&lastSeedMethod); - gdi.addInput("SeedGroups", lastSeedGroups, 32, 0, L"Seedningsgrupper:", + gdi.addInput("SeedGroups", lastSeedGroups, 32, 0, L"Seedningsgrupper:", L"Ange en gruppstorlek (som repeteras) eller flera kommaseparerade gruppstorlekar"). - setSynchData(&lastSeedGroups); + setSynchData(&lastSeedGroups); gdi.fillDown(); gdi.popX(); gdi.dropLine(3); - gdi.addCheckbox("PreventClubNb", "Hindra att deltagare från samma klubb startar på angränsande tider", + gdi.addCheckbox("PreventClubNb", "Hindra att deltagare från samma klubb startar på angränsande tider", 0, lastSeedPreventClubNb).setSynchData(&lastSeedPreventClubNb); gdi.addCheckbox("ReverseSeedning", "Låt de bästa start först", 0, lastSeedReverse). - setSynchData(&lastSeedReverse); + setSynchData(&lastSeedReverse); } else { gdi.popX(); @@ -3323,8 +3482,8 @@ void TabClass::drawDialog(gdioutput &gdi, DrawMethod method, const oClass &pc) { gdi.dropLine(1); } - if (method == DMRandom || method == DMSOFT || method == DMPursuit - || method == DMReversePursuit || method == DMSeeded) { + if (method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::Pursuit + || method == oEvent::DrawMethod::ReversePursuit || method == oEvent::DrawMethod::Seeded || method == oEvent::DrawMethod::MeOS) { gdi.addSelection("PairSize", 150, 200, 0, L"Tillämpa parstart:").setSynchData(&lastPairSize); gdi.addItem("PairSize", getPairOptions()); gdi.selectItemByData("PairSize", pairSize); @@ -3333,22 +3492,22 @@ void TabClass::drawDialog(gdioutput &gdi, DrawMethod method, const oClass &pc) { gdi.addInput("FirstStart", oe->getAbsTime(firstStart), 10, 0, L"Första start:").setSynchData(&lastFirstStart); - if (method == DMPursuit || method == DMReversePursuit) { + if (method == oEvent::DrawMethod::Pursuit || method == oEvent::DrawMethod::ReversePursuit) { gdi.addInput("MaxAfter", lastMaxAfter, 10, 0, L"Maxtid efter:", L"Maximal tid efter ledaren för att delta i jaktstart").setSynchData(&lastMaxAfter); - gdi.addInput("TimeRestart", oe->getAbsTime(firstStart + 3600), 8, 0, L"Första omstartstid:"); - gdi.addInput("ScaleFactor", lastScaleFactor, 8, 0, L"Tidsskalning:").setSynchData(&lastScaleFactor); + gdi.addInput("TimeRestart", oe->getAbsTime(firstStart + 3600), 8, 0, L"Första omstartstid:"); + gdi.addInput("ScaleFactor", lastScaleFactor, 8, 0, L"Tidsskalning:").setSynchData(&lastScaleFactor); } - if (method != DMSimultaneous) + if (method != oEvent::DrawMethod::Simultaneous) gdi.addInput("Interval", formatTime(interval), 10, 0, L"Startintervall (min):").setSynchData(&lastInterval); - if (method == DMRandom || method == DMSOFT || method == DMClumped && pc.getParentClass() == 0) + if ((method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::Clumped || method == oEvent::DrawMethod::MeOS) && pc.getParentClass() == 0) gdi.addInput("Vacanses", itow(vac), 10, 0, L"Antal vakanser:").setSynchData(&lastNumVac); - if ((method == DMRandom || method == DMSOFT || method == DMSeeded) && pc.getNumStages() > 1 && pc.getClassType() != oClassPatrol) { + if ((method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::Seeded || method == oEvent::DrawMethod::MeOS) && pc.getNumStages() > 1 && pc.getClassType() != oClassPatrol) { gdi.addSelection("Leg", 90, 100, 0, L"Sträcka:", L"Sträcka att lotta"); - for (unsigned k = 0; k TabClass::getSupportedDrawMethods(bool hasMulti) const { - set base = { DMRandom, DMSOFT, DMClumped, DMSimultaneous, DMSeeded }; +set TabClass::getSupportedDrawMethods(bool hasMulti) const { + set base = { oEvent::DrawMethod::Random, oEvent::DrawMethod::SOFT, oEvent::DrawMethod::Clumped, + oEvent::DrawMethod::MeOS, oEvent::DrawMethod::Simultaneous, oEvent::DrawMethod::Seeded }; if (hasMulti) { - base.insert(DMPursuit); - base.insert(DMReversePursuit); + base.insert(oEvent::DrawMethod::Pursuit); + base.insert(oEvent::DrawMethod::ReversePursuit); } return base; } -void TabClass::setMultiDayClass(gdioutput &gdi, bool hasMulti, DrawMethod defaultMethod) { +void TabClass::setMultiDayClass(gdioutput &gdi, bool hasMulti, oEvent::DrawMethod defaultMethod) { gdi.clearList("Method"); - gdi.addItem("Method", lang.tl("Lottning"), DMRandom); - gdi.addItem("Method", lang.tl("SOFT-lottning"), DMSOFT); - gdi.addItem("Method", lang.tl("Klungstart"), DMClumped); - gdi.addItem("Method", lang.tl("Gemensam start"), DMSimultaneous); - gdi.addItem("Method", lang.tl("Seedad lottning"), DMSeeded); + gdi.addItem("Method", lang.tl("Lottning") + L" (MeOS)" , int(oEvent::DrawMethod::MeOS)); + gdi.addItem("Method", lang.tl("Lottning"), int(oEvent::DrawMethod::Random)); + gdi.addItem("Method", lang.tl("SOFT-lottning"), int(oEvent::DrawMethod::SOFT)); + gdi.addItem("Method", lang.tl("Klungstart"), int(oEvent::DrawMethod::Clumped)); + gdi.addItem("Method", lang.tl("Gemensam start"), int(oEvent::DrawMethod::Simultaneous)); + gdi.addItem("Method", lang.tl("Seedad lottning"), int(oEvent::DrawMethod::Seeded)); if (hasMulti) { - gdi.addItem("Method", lang.tl("Jaktstart"), DMPursuit); - gdi.addItem("Method", lang.tl("Omvänd jaktstart"), DMReversePursuit); + gdi.addItem("Method", lang.tl("Jaktstart"), int(oEvent::DrawMethod::Pursuit)); + gdi.addItem("Method", lang.tl("Omvänd jaktstart"), int(oEvent::DrawMethod::ReversePursuit)); } - else if (defaultMethod > 10) - defaultMethod = DMSOFT; + else if (int(defaultMethod) > 10) + defaultMethod = oEvent::DrawMethod::MeOS; - gdi.selectItemByData("Method", defaultMethod); + gdi.selectItemByData("Method", int(defaultMethod)); if (gdi.hasField("Vacanses")) { gdi.setInputStatus("Vacanses", !hasMulti); @@ -3706,6 +3867,8 @@ void TabClass::selectCourses(gdioutput &gdi, int legNo) { } void TabClass::updateFairForking(gdioutput &gdi, pClass pc) const { + if (!gdi.hasField("FairForking")) + return; vector< vector > forks; vector< vector > forksC; set< pair > unfairLegs; @@ -3762,9 +3925,15 @@ void TabClass::defineForking(gdioutput &gdi, bool clearSettings) { gdi.dropLine(0.5); for (int k = 0; k < ns; k++) { LegTypes lt = pc->getLegType(k); - if (lt != LTIgnore) { - gdi.addString("leg"+ itos(k), 0, "Leg X: Do not modify.#" + itos(k+1)); - gdi.addItem("AllStages", lang.tl("Leg X#" + itos(k+1)), k); + if (lt != LTIgnore && lt != LTExtra) { + wstring lnum = pc->getLegNumber(k); + int k2 = k + 1; + while (k2 < ns && (pc->getLegType(k2) == LTExtra || pc->getLegType(k2) == LTIgnore)) { + lnum += L"/" + pc->getLegNumber(k2); + k2++; + } + gdi.addString("leg"+ itos(k), 0, L"Leg X: Do not modify.#" + lnum); + gdi.addItem("AllStages", lang.tl(L"Leg X#" + lnum), k); } } @@ -4099,13 +4268,7 @@ void TabClass::updateSplitDistribution(gdioutput &gdi, int num, int tot) const { gdi.refresh(); } -DrawMethod TabClass::getDefaultMethod(const set &allowedValues) const { - DrawMethod dm = (DrawMethod)oe->getPropertyInt("DefaultDrawMethod", DMSOFT); - if (allowedValues.count(dm)) - return dm; - else - return DMSOFT; -} + vector< pair > TabClass::getPairOptions() { vector< pair > res; @@ -4179,6 +4342,12 @@ void TabClass::setLockForkingState(gdioutput &gdi, bool poolState, bool lockStat while (gdi.hasField("@Course" + itos(legno))) { gdi.setInputStatus("@Course" + itos(legno++), !lockState || poolState); } + + for (string s : {"MCourses", "StageCourses", "MAdd", "MRemove"}) { + if (gdi.hasField(s)) { + gdi.setInputStatus(s, !lockState || poolState); + } + } } bool TabClass::warnDrawStartTime(gdioutput &gdi, const wstring &firstStart) { @@ -4275,6 +4444,7 @@ vector DrawSettingsCSV::read(gdioutput &gdi, const oEvent &oe, set usedId; // Save settings with class int lineNo = 0; + bool anyError = false; for (auto &row : data) { lineNo++; if (row.empty()) @@ -4285,8 +4455,8 @@ vector DrawSettingsCSV::read(gdioutput &gdi, const oEvent &oe, continue; DrawSettingsCSV dl; - try { + try { if (row.size() <= 7) throw wstring(L"Rad X är ogiltig#" + itow(lineNo) + L": " + row[0] + L"..."); @@ -4319,9 +4489,16 @@ vector DrawSettingsCSV::read(gdioutput &gdi, const oEvent &oe, } catch (const wstring &exmsg) { gdi.addString("", 0, exmsg).setColor(colorRed); + anyError = true; } } + if (anyError && !output.empty()) { + gdi.dropLine(); + gdi.refresh(); + Sleep(3000); + } + return output; } diff --git a/code/TabClass.h b/code/TabClass.h index a7a3ef9..e43c67d 100644 --- a/code/TabClass.h +++ b/code/TabClass.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -75,10 +75,10 @@ class TabClass : map cInfoCache; DrawInfo drawInfo; - void setMultiDayClass(gdioutput &gdi, bool hasMulti, DrawMethod defaultMethod); - set getSupportedDrawMethods(bool multiDay) const; + void setMultiDayClass(gdioutput &gdi, bool hasMulti, oEvent::DrawMethod defaultMethod); + set getSupportedDrawMethods(bool multiDay) const; - void drawDialog(gdioutput &gdi, DrawMethod method, const oClass &cls); + void drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClass &cls); void pursuitDialog(gdioutput &gdi); @@ -90,7 +90,7 @@ class TabClass : bool hasWarnedStartTime; bool hasWarnedDirect; bool tableMode; - DrawMethod lastDrawMethod; + oEvent::DrawMethod lastDrawMethod; int lastSeedMethod; bool lastSeedPreventClubNb; bool lastSeedReverse; @@ -138,7 +138,9 @@ class TabClass : void updateSplitDistribution(gdioutput &gdi, int numInClass, int tot) const; - DrawMethod getDefaultMethod(const set &allowedValues) const; + oEvent::DrawMethod getDefaultMethod(const set &allowedValues) const; + + void createDrawMethod(gdioutput& gdi); void enableLoadSettings(gdioutput &gdi); diff --git a/code/TabClub.cpp b/code/TabClub.cpp index 3a06979..5210669 100644 --- a/code/TabClub.cpp +++ b/code/TabClub.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -134,7 +134,7 @@ int TabClub::clubCB(gdioutput &gdi, int type, void *data) gdi.clearPage(true); oe->calculateTeamResults(false); oe->sortTeams(ClassStartTime, 0, true); - oe->calculateResults(oEvent::RTClassResult); + oe->calculateResults({}, oEvent::ResultType::ClassResult); oe->sortRunners(ClassStartTime); int pay, paid; { @@ -561,7 +561,7 @@ int TabClub::clubCB(gdioutput &gdi, int type, void *data) if (!file.empty()) { pdfwriter pdf; - pdf.generatePDF(gdi, file, lang.tl("Faktura"), oe->getDCI().getString("Organizer"), gdi.getTL()); + pdf.generatePDF(gdi, file, lang.tl("Faktura"), oe->getDCI().getString("Organizer"), gdi.getTL(), true); gdi.openDoc(file.c_str()); } } diff --git a/code/TabClub.h b/code/TabClub.h index cbe9c23..6657102 100644 --- a/code/TabClub.h +++ b/code/TabClub.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabCompetition.cpp b/code/TabCompetition.cpp index 92f5450..f8a636f 100644 --- a/code/TabCompetition.cpp +++ b/code/TabCompetition.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,6 +52,8 @@ #include "recorder.h" #include "testmeos.h" #include "importformats.h" +#include "HTMLWriter.h" +#include "metalist.h" #include #include @@ -99,6 +101,9 @@ bool TabCompetition::save(gdioutput &gdi, bool write) bool longTimes = gdi.isChecked("LongTimes"); wstring date = gdi.getText("Date"); + if (!checkValidDate(date)) + throw meosException(L"Felaktigt datum 'X' (Använd YYYY-MM-DD)#" + date); + if (longTimes) zt = L"00:00:00"; @@ -674,9 +679,8 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) if (club == cmp) club = L""; - csvparser csv; - bool clubCsv = !club.empty() && csv.iscsv(club.c_str()) != 0; - bool cmpCsv = !cmp.empty() && csv.iscsv(cmp.c_str()) != 0; + bool clubCsv = !club.empty() && csvparser::iscsv(club) != csvparser::CSV::NoCSV; + bool cmpCsv = !cmp.empty() && csvparser::iscsv(cmp) != csvparser::CSV::NoCSV; if (cmpCsv) { if (!club.empty()) @@ -964,8 +968,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) vector newEntriesT, notTransferedT, failedTargetT; oe->transferResult(nextStage, method, newEntriesT, notTransferedT, failedTargetT); - - nextStage.save(); + nextStage.transferListsAndSave(*oe); oe->updateTabs(true); gdi.dropLine(); @@ -1549,7 +1552,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) switch (startType) { case SMCommon: - oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"0", L"0", false, false, 1); + oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"0", L"0", false, oEvent::DrawMethod::Random, 1); drawn = true; break; @@ -1569,12 +1572,12 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) cls += cnf.classWithoutCourse[k]; } if (!gdi.ask(L"ask:missingcourse#" + cls)) { - gdi.addString("", 0, "Skipper lottning"); + gdi.addString("", 0, "Skippar lottning"); skip = true; } } if (!skip) - oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"2:00", L"2", true, true, 1); + oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"2:00", L"2", true, oEvent::DrawMethod::MeOS, 1); drawn = true; break; } @@ -1773,7 +1776,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) oe->generateListInfo(par, gdi.getLineHeight(), li); gdioutput tGdi("temp", gdi.getScale()); oe->generateList(tGdi, true, li, false); - tGdi.writeTableHTML(save, oe->getName(), 0); + HTMLWriter::writeTableHTML(tGdi, save, oe->getName(), 0, 1.0); tGdi.openDoc(save.c_str()); } loadPage(gdi); @@ -1858,7 +1861,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) oe->generateListInfo(par, gdi.getLineHeight(), li); gdioutput tGdi("temp", gdi.getScale()); oe->generateList(tGdi, true, li, false); - tGdi.writeTableHTML(save, oe->getName(), 0); + HTMLWriter::writeTableHTML(tGdi, save, oe->getName(), 0, 1.0); tGdi.openDoc(save.c_str()); } @@ -2337,7 +2340,7 @@ int TabCompetition::restoreCB(gdioutput &gdi, int type, void *data) { void TabCompetition::listBackups(gdioutput &gdi) { wchar_t bf[260]; getUserFile(bf, L""); - int yo = gdi.GetOffsetY(); + int yo = gdi.getOffsetY(); gdi.clearPage(false); oe->enumerateBackups(bf); @@ -2393,10 +2396,17 @@ void TabCompetition::loadAboutPage(gdioutput &gdi) const gdi.addString("", 0, "se license.txt som levereras med programmet."); gdi.dropLine(); - gdi.addString("", 1, "Vi stöder MeOS"); vector supp; vector developSupp; getSupporters(supp, developSupp); + + gdi.addString("", fontMediumPlus, "MeOS utvecklinsstöd"); + for (size_t k = 0; k problems; + csvparser csv; + int count = csv.importRanking(*oe, File, problems); + if (count > 0) { + gdi.addString("", 0, "Klart. X värden tilldelade.#" + itos(count)); + if (!problems.empty()) { + gdi.dropLine(); + gdi.addString("", 0, "Varning: Följande deltagare har ett osäkert resultat:"); + for (auto &p : problems) + gdi.addStringUT(0, p).setColor(colorDarkRed); + } + } + else gdi.addString("", 0, "Försöket misslyckades."); + } + else if (type != csvparser::CSV::NoCSV) { + const wchar_t *File = filename[i].c_str(); + csvparser csv; + if (type == csvparser::CSV::OE) { gdi.addString("", 0, "Importerar OE2003 csv-fil..."); gdi.refresh(); gdi.setWaitCursor(true); @@ -3467,7 +3498,7 @@ TabCompetition::FlowOperation TabCompetition::saveEntries(gdioutput &gdi, bool r } else gdi.addString("", 0, "Försöket misslyckades."); } - else if (type==2) { + else if (type == csvparser::CSV::OS) { gdi.addString("", 0, "Importerar OS2003 csv-fil..."); gdi.refresh(); gdi.setWaitCursor(true); @@ -3476,14 +3507,16 @@ TabCompetition::FlowOperation TabCompetition::saveEntries(gdioutput &gdi, bool r } else gdi.addString("", 0, "Försöket misslyckades."); } - else if (type==3) { + else if (type == csvparser::CSV::RAID) { gdi.addString("", 0, "Importerar RAID patrull csv-fil..."); gdi.setWaitCursor(true); if (csv.importRAID(*oe, File)) { gdi.addString("", 0, "Klart. X patruller importerade.#" + itos(csv.nimport)); } else gdi.addString("", 0, "Försöket misslyckades."); - + } + else { + gdi.addString("", 0, "Försöket misslyckades."); } } else { diff --git a/code/TabCompetition.h b/code/TabCompetition.h index a170f5c..c0adec9 100644 --- a/code/TabCompetition.h +++ b/code/TabCompetition.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabControl.cpp b/code/TabControl.cpp index ad4223e..4b2a485 100644 --- a/code/TabControl.cpp +++ b/code/TabControl.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabControl.h b/code/TabControl.h index db56c31..8a92d2e 100644 --- a/code/TabControl.h +++ b/code/TabControl.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabCourse.cpp b/code/TabCourse.cpp index 7395097..141e487 100644 --- a/code/TabCourse.cpp +++ b/code/TabCourse.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -179,13 +179,11 @@ void TabCourse::selectCourse(gdioutput &gdi, pCourse pc) gdi.selectItemByData("CommonControl", cc); } - fillOtherCourses(gdi, *pc); - pCourse sh = pc->getShorterVersion(); - gdi.check("Shorten", sh != 0); - gdi.setInputStatus("ShortCourse", sh != 0); - if (sh) { - gdi.selectItemByData("ShortCourse", sh->getId()); - } + fillOtherCourses(gdi, *pc, cc != 0); + auto sh = pc->getShorterVersion(); + gdi.check("Shorten", sh.first); + gdi.setInputStatus("ShortCourse", sh.first); + gdi.selectItemByData("ShortCourse", sh.second ? sh.second->getId() : 0); } else { gdi.setText("Name", L""); @@ -283,13 +281,16 @@ void TabCourse::save(gdioutput &gdi, int canSwitchViewMode) if (gdi.isChecked("Shorten")) { ListBoxInfo ci; if (gdi.getSelectedItem("ShortCourse", ci) && oe->getCourse(ci.data)) { - pc->setShorterVersion(oe->getCourse(ci.data)); + pc->setShorterVersion(true, oe->getCourse(ci.data)); + } + else if (gdi.isChecked("WithLoops")) { + pc->setShorterVersion(true, nullptr); } else throw meosException("Ange en avkortad banvariant"); } else - pc->setShorterVersion(0); + pc->setShorterVersion(false, 0); if (gdi.hasField("Rogaining")) { string t; @@ -411,7 +412,7 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) if (!file.empty()) { pdfwriter pdf; - pdf.generatePDF(gdi, file, L"Report", L"MeOS", gdi.getTL()); + pdf.generatePDF(gdi, file, L"Report", L"MeOS", gdi.getTL(), true); gdi.openDoc(file.c_str()); } } @@ -420,6 +421,21 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) gdi.setInputStatus("CommonControl", w); if (w && gdi.getTextNo("CommonControl") == 0) gdi.selectFirstItem("CommonControl"); + + pCourse pc = oe->getCourse(courseId); + if (pc) { + pair sel = gdi.getSelectedItem("ShortCourse"); + fillOtherCourses(gdi, *pc, w); + if (!w && sel.first == 0) + sel.second = false; + + if (sel.second) { + gdi.selectItemByData("ShortCourse", sel.first); + } + else if (w) { + gdi.selectItemByData("ShortCourse", 0); + } + } } else if (bi.id == "Shorten") { bool w = gdi.isChecked(bi.id); @@ -512,10 +528,11 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) gdi.popX(); gdi.dropLine(3); gdi.addSelection("Method", 200, 200, 0, L"Metod:"); - gdi.addItem("Method", lang.tl("Lottning"), DMRandom); - gdi.addItem("Method", lang.tl("SOFT-lottning"), DMSOFT); + gdi.addItem("Method", lang.tl("Lottning") + L" (MeOS)", int(oEvent::DrawMethod::MeOS)); + gdi.addItem("Method", lang.tl("Lottning"), int(oEvent::DrawMethod::Random)); + gdi.addItem("Method", lang.tl("SOFT-lottning"), int(oEvent::DrawMethod::SOFT)); - gdi.selectItemByData("Method", getDefaultMethod()); + gdi.selectItemByData("Method", (int)getDefaultMethod()); gdi.dropLine(0.9); gdi.fillRight(); gdi.addButton("DoDrawCourse", "Lotta", CourseCB).setDefault(); @@ -530,7 +547,7 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) int vacances = gdi.getTextNo("Vacances"); int fs = oe->getRelativeTime(firstStart); int iv = convertAbsoluteTimeMS(minInterval); - DrawMethod method = DrawMethod(gdi.getSelectedItem("Method").first); + oEvent::DrawMethod method = oEvent::DrawMethod(gdi.getSelectedItem("Method").first); courseDrawClasses[0].firstStart = fs; courseDrawClasses[0].vacances = vacances; courseDrawClasses[0].interval = iv; @@ -549,7 +566,7 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) courseDrawClasses[k].interval = iv; } - oe->drawList(courseDrawClasses, method == DMSOFT, 1, oEvent::drawAll); + oe->drawList(courseDrawClasses, method, 1, oEvent::DrawType::DrawAll); oe->addAutoBib(); @@ -852,13 +869,12 @@ bool TabCourse::loadPage(gdioutput &gdi) { void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename, oEvent *oe, bool addClasses) { - csvparser csv; - if (csv.iscsv(filename)) { + if (csvparser::iscsv(filename) != csvparser::CSV::NoCSV) { gdi.fillRight(); gdi.pushX(); gdi.addString("", 0, "Importerar OCAD csv-fil..."); gdi.refreshFast(); - + csvparser csv; if (csv.importOCAD_CSV(*oe, filename, addClasses)) { gdi.addString("", 1, "Klart.").setColor(colorGreen); } @@ -1044,7 +1060,7 @@ void TabCourse::fillCourseControls(gdioutput &gdi, const wstring &ctrl) { gdi.addItem("CommonControl", item); } -void TabCourse::fillOtherCourses(gdioutput &gdi, oCourse &crs) { +void TabCourse::fillOtherCourses(gdioutput &gdi, oCourse &crs, bool withLoops) { vector< pair > ac; oe->fillCourses(ac, true); set skipped; @@ -1057,6 +1073,9 @@ void TabCourse::fillOtherCourses(gdioutput &gdi, oCourse &crs) { } vector< pair > out; + if (withLoops) + out.emplace_back(lang.tl("Färre slingor"), 0); + for (size_t k = 0; k < ac.size(); k++) { if (!skipped.count(ac[k].second)) out.push_back(ac[k]); @@ -1093,12 +1112,14 @@ void TabCourse::saveLegLengths(gdioutput &gdi) { pc->synchronize(true); } -DrawMethod TabCourse::getDefaultMethod() const { - int dm = oe->getPropertyInt("DefaultDrawMethod", DMSOFT); - if (dm == DMRandom) - return DMRandom; +oEvent::DrawMethod TabCourse::getDefaultMethod() const { + int dm = oe->getPropertyInt("DefaultDrawMethod", int(oEvent::DrawMethod::MeOS)); + if (dm == (int)oEvent::DrawMethod::Random) + return oEvent::DrawMethod::Random; + if (dm == (int)oEvent::DrawMethod::MeOS) + return oEvent::DrawMethod::MeOS; else - return DMSOFT; + return oEvent::DrawMethod::SOFT; } void TabCourse::clearCompetitionData() { diff --git a/code/TabCourse.h b/code/TabCourse.h index faa8b0e..1339ec5 100644 --- a/code/TabCourse.h +++ b/code/TabCourse.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,6 @@ #include "tabbase.h" struct ClassDrawSpecification; -enum DrawMethod; class TabCourse : public TabBase @@ -40,13 +39,13 @@ class TabCourse : wstring point_reduction; void fillCourseControls(gdioutput &gdi, const wstring &ctrl); - void fillOtherCourses(gdioutput &gdi, oCourse &crs); + void fillOtherCourses(gdioutput &gdi, oCourse &crs, bool withLoops); void saveLegLengths(gdioutput &gdi); vector courseDrawClasses; - DrawMethod getDefaultMethod() const; + oEvent::DrawMethod getDefaultMethod() const; wstring encodeCourse(const wstring &in, bool firstStart, bool lastFinish); void refreshCourse(const wstring &text, gdioutput &gdi); diff --git a/code/TabList.cpp b/code/TabList.cpp index 36ddf0a..ec18501 100644 --- a/code/TabList.cpp +++ b/code/TabList.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,6 +51,7 @@ #include "liveresult.h" #include "animationdata.h" #include +#include "HTMLWriter.h" const static int CUSTOM_OFFSET = 10; const static int NUMTEXTSAMPLE = 13; @@ -59,6 +60,8 @@ TabList::TabList(oEvent *poe):TabBase(poe) { listEditor = 0; methodEditor = 0; + + lastHtmlTarget = poe->getPropertyString("LastExportTarget", L""); clearCompetitionData(); } @@ -155,8 +158,10 @@ int TabList::baseButtons(gdioutput &gdi, int extraButtons) { gdi.addButton(gdi.getWidth()+20, 18+gdi.getButtonHeight(), gdi.scaleLength(120), "Print", "Skriv ut...", ListsCB, "Skriv ut listan.", true, false); - gdi.addButton(gdi.getWidth()+20, 21+2*gdi.getButtonHeight(), gdi.scaleLength(120), - "HTML", "Webb...", ListsCB, "Spara för webben.", true, false); + if (!ownWindow) { + gdi.addButton(gdi.getWidth() + 20, 21 + 2 * gdi.getButtonHeight(), gdi.scaleLength(120), + "HTMLDesign", "Webb...", ListsCB, "Spara för webben.", true, false); + } gdi.addButton(gdi.getWidth()+20, 24+3*gdi.getButtonHeight(), gdi.scaleLength(120), "PDF", "PDF...", ListsCB, "Spara som PDF.", true, false); gdi.addButton(gdi.getWidth()+20, 27+4*gdi.getButtonHeight(), gdi.scaleLength(120), @@ -199,8 +204,8 @@ void TabList::generateList(gdioutput &gdi, bool forceUpdate) if (!forceUpdate && !currentList.needRegenerate(*oe)) return; gdi.takeShownStringsSnapshot(); - oX = gdi.GetOffsetX(); - oY = gdi.GetOffsetY(); + oX = gdi.getOffsetX(); + oY = gdi.getOffsetY(); gdi.getData("GeneralList", storedWidth); gdi.restoreNoUpdate("GeneralList"); } @@ -256,14 +261,12 @@ void TabList::generateList(gdioutput &gdi, bool forceUpdate) baseY += 2*(3+gdi.getButtonHeight()); } - /*baseY += 3 + gdi.getButtonHeight(); - gdi.addButton(gdi.getWidth()+20, baseY, gdi.scaleLength(120), - "AutoScroll", "Automatisk skroll", ListsCB, "Rulla upp och ner automatiskt", true, false); - - baseY += 3 + gdi.getButtonHeight(); - gdi.addButton(gdi.getWidth()+20, baseY, gdi.scaleLength(120), - "FullScreen", "Fullskärm", ListsCB, "Visa listan i fullskärm", true, false); - */ + + if (currentList.supportUpdateClasses()) { + baseY += 3 + gdi.getButtonHeight(); + gdi.addButton(gdi.getWidth() + 20, baseY, gdi.scaleLength(120), + "ClassSelection", "Klassval...", ListsCB, "Välj klasser", true, false); + } baseY += 3 + gdi.getButtonHeight(); gdi.addButton(gdi.getWidth() + 20, baseY, gdi.scaleLength(120), "ListDesign", "Utseende...", ListsCB, "Justera visningsinställningar", true, false); @@ -271,13 +274,13 @@ void TabList::generateList(gdioutput &gdi, bool forceUpdate) if (!currentList.getParam().saved && !oe->isReadOnly()) { baseY += 3 + gdi.getButtonHeight(); gdi.addButton(gdi.getWidth()+20, baseY, gdi.scaleLength(120), - "Remember", "Kom ihåg listan", ListsCB, "Spara den här listan som en favoritlista", true, false); + "Remember", "Kom ihåg listan...", ListsCB, "Spara den här listan som en favoritlista", true, false); } } gdi.registerEvent("DataUpdate", ListsEventCB); gdi.setData("DataSync", 1); - if (currentList.needPunchCheck()) + if (currentList.needPunchCheck() != oListInfo::PunchMode::NoPunch) gdi.setData("PunchSync", 1); gdi.registerEvent("GeneralList", ListsCB); gdi.setOnClearCb(ListsCB); @@ -316,34 +319,25 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) if (ownWindow) gdi.closeWindow(); else { + gdioutput *gdi_settings = getExtraWindow("html_settings", false); + if (gdi_settings) { + gdi_settings->closeWindow(); + } + gdi_settings = getExtraWindow("save_list", false); + if (gdi_settings) { + gdi_settings->closeWindow(); + } SelectedList = ""; currentListType = EStdNone; loadPage(gdi); } } else if (bi.id=="Print") { - gdi.print(oe); - } - else if (bi.id=="HTML") { - vector< pair > ext; - ext.push_back(make_pair(L"Strukturerat webbdokument (html)", L"*.html;*.htm")); - ext.push_back(make_pair(L"Formaterat webbdokument (html)", L"*.html;*.htm")); - - wstring file=gdi.browseForSave(ext, L"html", index); - - if (!file.empty()) { - if (index == 1) - gdi.writeTableHTML(file, oe->getName(), 0); - else { - assert(index == 2); - gdi.writeHTML(file, oe->getName(), 0); - } - gdi.openDoc(file.c_str()); - } + gdi.print(oe, 0, true, false, currentList.getParam().pageBreak); } else if (bi.id=="Copy") { ostringstream fout; - gdi.writeTableHTML(fout, L"MeOS", true, 0); + HTMLWriter::writeTableHTML(gdi, fout, L"MeOS", true, 0, 1.0); string res = fout.str(); gdi.copyToClipboard(res, L""); } @@ -356,11 +350,26 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) if (!file.empty()) { pdfwriter pdf; pdf.generatePDF(gdi, file, oe->getName() + L", " + currentList.getName(), - oe->getDCI().getString("Organizer"), gdi.getTL()); + oe->getDCI().getString("Organizer"), gdi.getTL(), + currentList.getParam().pageBreak); gdi.openDoc(file.c_str()); } } + else if (bi.id == "ClassSelection") { + gdioutput *gdi_settings = getExtraWindow("list_class", true); + if (!gdi_settings) { + gdi_settings = createExtraWindow("list_class", lang.tl("Klassval"), gdi.scaleLength(350), gdi.scaleLength(600), true); + } + if (gdi_settings) { + loadClassSettings(*gdi_settings, gdi.getTag()); + } + } else if (bi.id == "ListDesign") { + gdioutput *html_settings = getExtraWindow("html_settings", false); + if (html_settings) { + html_settings->closeWindow(); + } + gdioutput *gdi_settings = getExtraWindow("list_settings", true); if (!gdi_settings) { gdi_settings = createExtraWindow("list_settings", lang.tl("Inställningar"), gdi.scaleLength(600), gdi.scaleLength(400)); @@ -369,6 +378,20 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) loadSettings(*gdi_settings, gdi.getTag()); } } + else if (bi.id == "HTMLDesign") { + gdioutput *design_settings = getExtraWindow("list_settings", false); + if (design_settings) { + design_settings->closeWindow(); + } + + gdioutput *gdi_settings = getExtraWindow("html_settings", true); + if (!gdi_settings) { + gdi_settings = createExtraWindow("html_settings", lang.tl("HTML Export"), gdi.scaleLength(600), gdi.scaleLength(500), true); + } + if (gdi_settings) { + htmlSettings(*gdi_settings, gdi.getTag()); + } + } else if (bi.id == "Window" || bi.id == "AutoScroll" || bi.id == "FullScreen" || bi.id == "FullScreenLive") { gdioutput *gdi_new; @@ -383,6 +406,11 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else gdi_new = &gdi; + gdioutput *gdi_settings = getExtraWindow("html_settings", false); + if (gdi_settings) { + gdi_settings->closeWindow(); + } + if (gdi_new && bi.id == "AutoScroll" || bi.id == "FullScreen") { tl_new->hideButtons = true; @@ -404,14 +432,13 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) } } else if (bi.id == "Remember") { - oListParam &par = currentList.getParam(); - wstring baseName = par.getDefaultName(); - baseName = oe->getListContainer().makeUniqueParamName(baseName); - par.setName(baseName); - oe->synchronize(false); - oe->getListContainer().addListParam(currentList.getParam()); - oe->synchronize(true); - gdi.removeControl(bi.id); + gdioutput *gdi_settings = getExtraWindow("save_list", true); + if (!gdi_settings) { + gdi_settings = createExtraWindow("save_list", lang.tl("Kom ihåg listan"), gdi.scaleLength(450), gdi.scaleLength(500), true); + } + if (gdi_settings) { + loadRememberList(*gdi_settings, gdi.getTag()); + } } else if (bi.id == "ShowSaved") { ListBoxInfo lbi; @@ -419,6 +446,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oListParam &par = oe->getListContainer().getParam(lbi.data); oe->generateListInfo(par, gdi.getLineHeight(), currentList); + currentList.getParam().sourceParam = lbi.data; generateList(gdi); } } @@ -433,8 +461,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) gdi.fillRight(); gdi.addInput("Name", par.getName(), 36); gdi.setInputFocus("Name", true); - gdi.addButton("DoRenameSaved", "Döp om", ListsCB); - gdi.addButton("Cancel", "Avbryt", ListsCB); + gdi.addButton("DoRenameSaved", "Döp om", ListsCB).setDefault(); + gdi.addButton("Cancel", "Avbryt", ListsCB).setCancel(); gdi.dropLine(3); } } @@ -460,8 +488,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) gdi.addItem("Merge", cand); gdi.addCheckbox("ShowTitle", "Visa rubrik mellan listorna", 0, false); gdi.fillRight(); - gdi.addButton("DoMerge", "Slå ihop", ListsCB); - gdi.addButton("Cancel", "Avbryt", ListsCB); + gdi.addButton("DoMerge", "Slå ihop", ListsCB).setDefault(); + gdi.addButton("Cancel", "Avbryt", ListsCB).setCancel(); gdi.dropLine(3); } } @@ -499,9 +527,18 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) return 0; } else if (bi.id == "Automatic") { + gdioutput *gdi_settings = getExtraWindow("html_settings", false); + wstring htmlTarget; + if (gdi_settings) { + htmlTarget = lastHtmlTarget; + gdi_settings->closeWindow(); + } PrintResultMachine prm(60*10, currentList); + if (!htmlTarget.empty()) { + prm.setHTML(htmlTarget, currentList.getParam().timePerPage / 1000); + } tabAutoAddMachinge(prm); - gdi.getTabs().get(TAutoTab)->loadPage(gdi); + dynamic_cast(gdi.getTabs().get(TAutoTab))->loadPage(gdi, true); } else if (bi.id == "WideFormat") { enableWideFormat(gdi, gdi.isChecked(bi.id)); @@ -589,6 +626,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) lastInputNumber = itow(par.inputNumber); par.pageBreak = gdi.isChecked("PageBreak"); + par.showHeader = gdi.isChecked("ShowHeader"); + par.listCode = (EStdListType)currentListType; par.showInterTimes = gdi.isChecked("ShowInterResults"); par.showSplitTimes = gdi.isChecked("ShowSplits"); @@ -636,12 +675,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oe->getClassConfigurationInfo(cnf); getResultIndividual(par, cnf); cnf.getIndividual(par.selection); - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); - - ListBoxInfo lbi; - gdi.getSelectedItem("ClassLimit", lbi); - par.filterMaxPer = lbi.data; + readSettings(gdi, par, true); oe->generateListInfo(par, gdi.getLineHeight(), currentList); generateList(gdi); @@ -654,9 +688,11 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oe->getClassConfigurationInfo(cnf); cnf.getIndividual(par.selection); par.listCode = EStdResultList; + + readSettings(gdi, par, true); par.showSplitTimes = true; par.setLegNumberCoded(-1); - par.pageBreak = gdi.isChecked("PageBreak"); + oe->generateListInfo(par, gdi.getLineHeight(), currentList); generateList(gdi); gdi.refresh(); @@ -667,7 +703,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); getStartIndividual(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); + readSettings(gdi, par, false); + oe->generateListInfo(par, gdi.getLineHeight(), currentList); currentList.setCallback(openRunnerTeamCB); generateList(gdi); @@ -677,7 +714,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oe->sanityCheck(gdi, false); oListParam par; getStartClub(par); - par.pageBreak = gdi.isChecked("PageBreak"); + readSettings(gdi, par, false); + oe->generateListInfo(par, gdi.getLineHeight(), currentList); currentList.setCallback(openRunnerTeamCB); generateList(gdi); @@ -690,8 +728,9 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oe->getClassConfigurationInfo(cnf); getResultClub(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); + readSettings(gdi, par, false); par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + oe->generateListInfo(par, gdi.getLineHeight(), currentList); currentList.setCallback(openRunnerTeamCB); generateList(gdi); @@ -722,7 +761,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); getStartTeam(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); + readSettings(gdi, par, false); + oe->generateListInfo(par, gdi.getLineHeight(), currentList); currentList.setCallback(openRunnerTeamCB); generateList(gdi); @@ -731,10 +771,11 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id=="RaceNStart") { oe->sanityCheck(gdi, false); oListParam par; - int race = int(bi.getExtra()); + int race = bi.getExtraInt(); par.setLegNumberCoded(race); par.listCode = EStdIndMultiStartListLeg; - par.pageBreak = gdi.isChecked("PageBreak"); + readSettings(gdi, par, false); + ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); cnf.getRaceNStart(race, par.selection); @@ -746,7 +787,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id=="LegNStart") { oe->sanityCheck(gdi, false); oListParam par; - par.pageBreak = gdi.isChecked("PageBreak"); + readSettings(gdi, par, false); + int race = bi.getExtraInt(); par.setLegNumberCoded(race); par.listCode = EStdTeamStartListLeg; @@ -758,27 +800,13 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) generateList(gdi); gdi.refresh(); } - else if (bi.id=="PatrolStartList") { - oe->sanityCheck(gdi, false); - oListParam par; - ClassConfigInfo cnf; - oe->getClassConfigurationInfo(cnf); - getStartPatrol(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); - oe->generateListInfo(par, gdi.getLineHeight(), currentList); - currentList.setCallback(openRunnerTeamCB); - generateList(gdi); - gdi.refresh(); - } else if (bi.id=="TeamResults") { oe->sanityCheck(gdi, true); oListParam par; ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); getResultTeam(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); - par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; + readSettings(gdi, par, true); oe->generateListInfo(par, gdi.getLineHeight(), currentList); generateList(gdi); gdi.refresh(); @@ -786,8 +814,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id=="MultiResults") { oe->sanityCheck(gdi, true); oListParam par; - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + readSettings(gdi, par, true); par.listCode = EStdIndMultiResultListAll; ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); @@ -799,8 +826,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id=="RaceNRes") { oe->sanityCheck(gdi, true); oListParam par; - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + readSettings(gdi, par, true); + int race = bi.getExtraInt(); par.setLegNumberCoded(race); par.listCode = EStdIndMultiResultListLeg; @@ -815,11 +842,9 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id=="LegNResult") { oe->sanityCheck(gdi, true); oListParam par; - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + readSettings(gdi, par, true); int race = bi.getExtraInt(); par.setLegNumberCoded(race); - par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; par.listCode = oe->getListContainer().getType("legresult"); ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); @@ -829,30 +854,13 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) generateList(gdi); gdi.refresh(); } - else if (bi.id=="PatrolResultList") { - oe->sanityCheck(gdi, false); - oListParam par; - ClassConfigInfo cnf; - oe->getClassConfigurationInfo(cnf); - getResultPatrol(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); - par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; - - oe->generateListInfo(par, gdi.getLineHeight(), currentList); - currentList.setCallback(openRunnerTeamCB); - generateList(gdi); - gdi.refresh(); - } else if (bi.id=="RogainingResultList") { oe->sanityCheck(gdi, true); oListParam par; ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); getResultRogaining(par, cnf); - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); - par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; + readSettings(gdi, par, true); oe->generateListInfo(par, gdi.getLineHeight(), currentList); currentList.setCallback(openRunnerTeamCB); @@ -867,15 +875,17 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) vector par; if (cnf.hasTeamClass()) { - par.push_back(oListParam()); - par.back().pageBreak = gdi.isChecked("PageBreak"); + par.emplace_back(); + readSettings(gdi, par.back(), false); + par.back().listCode = ETeamCourseList; cnf.getTeamClass(par.back().selection); } if (cnf.hasIndividual()) { - par.push_back(oListParam()); - par.back().pageBreak = gdi.isChecked("PageBreak"); + par.emplace_back(); + readSettings(gdi, par.back(), false); + par.back().listCode = EIndCourseList; par.back().showInterTitle = false; cnf.getIndividual(par.back().selection); @@ -885,11 +895,12 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) generateList(gdi); gdi.refresh(); } - else if (bi.id=="RentedCards") { + else if (bi.id=="HiredCards") { ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); oListParam par; par.listCode = EStdRentedCard; + par.showHeader = gdi.isChecked("ShowHeader"); par.setLegNumberCoded(-1); oe->generateListInfo(par, gdi.getLineHeight(), currentList); generateList(gdi); @@ -901,6 +912,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oListParam par; cnf.getIndividual(par.selection); par.listCode = EIndPriceList; + par.showHeader = gdi.isChecked("ShowHeader"); par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; oe->generateListInfo(par, gdi.getLineHeight(), currentList); generateList(gdi); @@ -928,17 +940,19 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) bool isReport = bi.id.substr(0, 7) == "GenLst:"; bool allClasses = bi.getExtraInt() == 1; bool rogaining = bi.getExtraInt() == 2; + bool patrol = bi.getExtraInt() == 3; oe->sanityCheck(gdi, bi.id.substr(0, 7) == "Result:"); oListParam par; par.listCode = oe->getListContainer().getType(bi.id.substr(7)); - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + readSettings(gdi, par, true); - par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; - par.setLegNumberCoded(-1); - - if (rogaining) { + if (patrol) { + ClassConfigInfo cnf; + oe->getClassConfigurationInfo(cnf); + cnf.getPatrol(par.selection); + } + else if (rogaining) { ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); cnf.getRogaining(par.selection); @@ -955,14 +969,48 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) generateList(gdi); gdi.refresh(); } + else if (bi.id == "KnockoutTotal") { + oe->sanityCheck(gdi, true); + oListParam par; + par.listCode = oe->getListContainer().getType("knockout-total"); + readSettings(gdi, par, true); + par.setLegNumberCoded(-1); + + ClassConfigInfo cnf; + oe->getClassConfigurationInfo(cnf); + par.selection = set(cnf.knockout.begin(), cnf.knockout.end()); + oe->generateListInfo(par, gdi.getLineHeight(), currentList); + currentList.setCallback(openRunnerTeamCB); + generateList(gdi); + gdi.refresh(); + } + else if (bi.id == "LapCount" || bi.id == "LapCountExtra") { + oe->sanityCheck(gdi, true); + bool extra = bi.id == "LapCountExtra"; + oListParam par; + par.listCode = oe->getListContainer().getType(extra ? "lapcountextra" : "lapcount"); + readSettings(gdi, par, true); + par.setLegNumberCoded(-1); + + ClassConfigInfo cnf; + oe->getClassConfigurationInfo(cnf); + if (extra) + par.selection = set(cnf.lapcountextra.begin(), cnf.lapcountextra.end()); + else + par.selection = set(cnf.lapcountsingle.begin(), cnf.lapcountsingle.end()); + + oe->generateListInfo(par, gdi.getLineHeight(), currentList); + currentList.setCallback(openRunnerTeamCB); + generateList(gdi); + gdi.refresh(); + } else if (bi.id == "CustomList") { oe->synchronize(); oe->sanityCheck(gdi, false); oListParam par; int index = bi.getExtraInt(); par.listCode = oe->getListContainer().getType(index); - par.pageBreak = gdi.isChecked("PageBreak"); - par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + readSettings(gdi, par, true); par.setLegNumberCoded(-1); ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); @@ -1087,6 +1135,9 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id == "PageBreak") { oe->setProperty("pagebreak", gdi.isChecked(bi.id)); } + else if (bi.id == "ShowHeader") { + oe->setProperty("showheader", gdi.isChecked(bi.id)); + } else if (bi.id == "ShowInterResults"){ oe->setProperty("intertime", gdi.isChecked(bi.id)); } @@ -1128,8 +1179,8 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) } } else if (type==GUI_CLEAR) { - offsetY=gdi.GetOffsetY(); - offsetX=gdi.GetOffsetX(); + offsetY=gdi.getOffsetY(); + offsetX=gdi.getOffsetX(); leavingList(gdi.getTag()); return true; } @@ -1319,6 +1370,7 @@ void TabList::loadGeneralList(gdioutput &gdi) gdi.popY(); gdi.fillDown(); gdi.addCheckbox("PageBreak", "Sidbrytning mellan klasser / klubbar", ListsCB, oe->getPropertyInt("pagebreak", 0)!=0); + gdi.addCheckbox("ShowHeader", "Visa rubrik", ListsCB, oe->getPropertyInt("showheader", 0) != 0); gdi.addCheckbox("ShowInterResults", "Visa mellantider", ListsCB, oe->getPropertyInt("intertime", 1)!=0, "Mellantider visas för namngivna kontroller."); gdi.addCheckbox("SplitAnalysis", "Med sträcktidsanalys", ListsCB, oe->getPropertyInt("splitanalysis", 1)!=0); @@ -1365,8 +1417,8 @@ void TabList::loadGeneralList(gdioutput &gdi) gdi.dropLine(); gdi.fillRight(); - gdi.addButton("Generate", "Generera", ListsCB); - gdi.addButton("Cancel", "Avbryt", ListsCB); + gdi.addButton("Generate", "Generera", ListsCB).setDefault(); + gdi.addButton("Cancel", "Avbryt", ListsCB).setCancel(); gdi.refresh(); } @@ -1396,8 +1448,8 @@ void TabList::makeFromTo(gdioutput &gdi) { gdi.dropLine(3); } -class ListSettings : public GuiHandler { - void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { +class TargetSettings : public GuiHandler { + void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) final { string target; if (!gdi.getData("target", target)) return; @@ -1406,14 +1458,48 @@ class ListSettings : public GuiHandler { return; TabBase *tb = dest_gdi->getTabs().get(TabType::TListTab); - if (tb) { - TabList *list = dynamic_cast(tb); - list->handleListSettings(gdi, info, type, *dest_gdi); + TabList *list = dynamic_cast(tb); + + if (list) { + handle(list, gdi, info, type, *dest_gdi); } } +public: + virtual void handle(TabList *list, gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) = 0; + virtual ~TargetSettings() {} +}; + +class HTMLSettings : public TargetSettings { + void handle(TabList *list, gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) override { + list->handleHTMLSettings(gdi, info, type, dest_gdi); + } }; + +class ListSettings : public TargetSettings { + void handle(TabList *list, gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) override { + list->handleListSettings(gdi, info, type, dest_gdi); + } +}; + +class ClassSettings : public TargetSettings { +public: + void handle(TabList *list, gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) override { + list->handleClassSettings(gdi, info, type, dest_gdi); + } +}; + +class RememberList : public TargetSettings { +public: + void handle(TabList *list, gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) override { + list->handleRememberSettings(gdi, info, type, dest_gdi); + } +}; + +HTMLSettings htmlClass; ListSettings settingsClass; +ClassSettings settingsClassSelection; +RememberList settingsRememberList; void TabList::changeListSettingsTarget(gdioutput &oldWindow, gdioutput &newWindow) { gdioutput *gdi_settings = getExtraWindow("list_settings", true); @@ -1426,7 +1512,23 @@ void TabList::changeListSettingsTarget(gdioutput &oldWindow, gdioutput &newWindo } void TabList::leavingList(const string &wnd) { - gdioutput *gdi_settings = getExtraWindow("list_settings", true); + gdioutput *gdi_settings = getExtraWindow("list_settings", false); + if (gdi_settings) { + string oldTag; + gdi_settings->getData("target", oldTag); + if (wnd == oldTag) + gdi_settings->closeWindow(); + } + + gdi_settings = getExtraWindow("html_settings", false); + if (gdi_settings) { + string oldTag; + gdi_settings->getData("target", oldTag); + if (wnd == oldTag) + gdi_settings->closeWindow(); + } + + gdi_settings = getExtraWindow("save_list", false); if (gdi_settings) { string oldTag; gdi_settings->getData("target", oldTag); @@ -1435,6 +1537,104 @@ void TabList::leavingList(const string &wnd) { } } +void TabList::loadRememberList(gdioutput &gdi, string targetTag) { + gdi.clearPage(false); + gdi.setCX(10); + gdi.setCY(15); + gdi.setColorMode(RGB(242, 240, 250)); + gdi.setData("target", targetTag); + settingsTarget = targetTag; + gdi.addString("", fontMediumPlus, L"Spara 'X'#" + currentList.getName()); + gdi.dropLine(0.5); + oListParam &par = currentList.getParam(); + wstring baseName = par.getDefaultName(); + + if (!par.selection.empty()) { + vector classes; + oe->getClasses(classes, false); + if (par.selection.size() != classes.size()) { + wstring cls; + for (pClass c : classes) { + if (par.selection.count(c->getId())) { + if (cls.length() > 16) { + cls += L"..."; + break; + } + if (!cls.empty()) + cls += L", "; + cls += c->getName(); + } + } + baseName += L"; " + cls; + } + } + if (!par.getLegName().empty()) + baseName += L"; " + lang.tl(L"Sträcka X#" + par.getLegName()); + + gdi.addInput("Name", baseName, 40, 0, L"Namn:"); + gdi.dropLine(); + + gdi.addCheckbox("DoMerge", "Slå ihop med befintlig lista", nullptr, false).setHandler(&settingsRememberList); + gdi.addListBox("Merge", 350, 250); + vector < pair > cand; + oe->getListContainer().getMergeCandidates(-1, cand); + gdi.addItem("Merge", cand); + gdi.dropLine(-0.2); + gdi.addCheckbox("ShowTitle", "Visa rubrik mellan listorna", 0, false); + gdi.dropLine(0.5); + gdi.disableInput("Merge"); + gdi.disableInput("ShowTitle"); + gdi.fillRight(); + + gdi.pushX(); + gdi.addButton("SaveList", "OK").setDefault().setHandler(&settingsRememberList); + gdi.addButton("Cancel", "Avbryt").setCancel().setHandler(&settingsRememberList); + gdi.dropLine(3); + gdi.popX(); + + gdi.refresh(); +} + +void TabList::handleRememberSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) { + if (type == GUI_BUTTON) { + ButtonInfo bi = static_cast(info); + if (bi.id == "Cancel") + gdi.closeWindow(); + else if (bi.id == "SaveList") { + oListParam &par = currentList.getParam(); + //wstring baseName = par.getDefaultName(); + wstring baseName = gdi.getText("Name"); + baseName = oe->getListContainer().makeUniqueParamName(baseName); + par.setName(baseName); + oe->synchronize(false); + par.sourceParam = oe->getListContainer().addListParam(par); + + if (gdi.isChecked("DoMerge")) { + ListBoxInfo lbi; + if (gdi.getSelectedItem("Merge", lbi)) { + int mergeWidth = lbi.data; + bool showTitle = gdi.isChecked("ShowTitle"); + oe->getListContainer().mergeParam(mergeWidth, par.sourceParam, showTitle); + } + } + oe->synchronize(true); + + dest_gdi.removeControl("Remember"); + gdi.closeWindow(); + } + else if (bi.id == "DoMerge") { + bool merge = gdi.isChecked(bi.id); + gdi.setInputStatus("Merge", merge); + gdi.setInputStatus("ShowTitle", merge); + } + } + else if (type == GUI_LISTBOX) { + ListBoxInfo lbi = static_cast(info); + } +} + + + static void addAnimationSettings(gdioutput &gdi, oListParam &dst) { DWORD cx, cy; gdi.getData("xmode", cx); @@ -1662,6 +1862,289 @@ void TabList::handleListSettings(gdioutput &gdi, BaseInfo &info, GuiEventType ty } } +namespace { + void htmlDetails(gdioutput &gdi, oListParam &tmpSettingsParam, wstring &info, bool withExtra) { + gdi.restoreNoUpdate("htmlDetails"); + gdi.setRestorePoint("htmlDetails"); + gdi.pushX(); + if (withExtra) { + gdi.fillDown(); + gdi.addString("", 0, info); + gdi.dropLine(0.3); + gdi.fillRight(); + gdi.addInput("Margin", itow(tmpSettingsParam.margin) + L" %", 5, 0, L"Marginal:"); + gdi.addInput("Scale", itow(int(tmpSettingsParam.htmlScale*100)) + L" %", 5, 0, L"Skalfaktor:"); + if (tmpSettingsParam.nColumns <= 0) + tmpSettingsParam.nColumns = 1; + + gdi.addInput("Columns", itow(tmpSettingsParam.nColumns), 5, 0, L"Kolumner:"); + gdi.addInput("Time", itow(tmpSettingsParam.timePerPage) + L"ms", 5, 0, L"Visningstid:"); + + gdi.popX(); + gdi.dropLine(3.4); + + gdi.addCheckbox("UseRows", "Begränsa antal rader per sida", 0, tmpSettingsParam.htmlRows>0).setHandler(&htmlClass); + gdi.dropLine(-0.4); + gdi.addInput("Rows", itow(tmpSettingsParam.htmlRows), 5); + gdi.setInputStatus("Rows", tmpSettingsParam.htmlRows > 0); + + gdi.popX(); + gdi.dropLine(3.0); + } + else { + gdi.fillRight(); + gdi.addInput("Scale", itow(int(tmpSettingsParam.htmlScale * 100)) + L" %", 5, 0, L"Skalfaktor:"); + gdi.popX(); + gdi.dropLine(3.4); + + gdi.addCheckbox("Reload", "Automatisk omladdning", 0, tmpSettingsParam.timePerPage>999).setHandler(&htmlClass); + gdi.dropLine(-0.4); + gdi.addInput("ReloadTime", itow(tmpSettingsParam.timePerPage/1000) + L" s", 5); + gdi.setInputStatus("Reload", tmpSettingsParam.timePerPage>999); + + gdi.popX(); + gdi.dropLine(3.0); + } + gdi.fillRight(); + gdi.addButton("ApplyList", "Lagra inställningar").setHandler(&htmlClass); + gdi.addButton("Automatic", "Automatisera", 0, "Skriv ut eller exportera listan automatiskt.").setHandler(&htmlClass); + gdi.addButton("HTML", "Exportera").setHandler(&htmlClass); + } +} + +void TabList::htmlSettings(gdioutput &gdi, string targetTag) { + gdi.clearPage(false); + gdi.setCX(10); + gdi.setCY(15); + gdi.setColorMode(RGB(242, 240, 250)); + gdi.setData("target", targetTag); + settingsTarget = targetTag; + gdi.addString("", fontMediumPlus, L"HTML Export för 'X'#" + currentList.getName()); + tmpSettingsParam = currentList.getParam(); + + vector htmlTmpl; + HTMLWriter::enumTemplates(HTMLWriter::TemplateType::List, htmlTmpl); + vector > items; + gdi.dropLine(0.5); + gdi.addString("", 10, "htmlhelp"); + + items.emplace_back(lang.tl("Strukturerat webbdokument (html)"), 0); + items.emplace_back(lang.tl("Formaterat webbdokument (html)"), 1); + + int id = 10; + htmlTemplateTag2Id.clear(); + html2IdToInfo.clear(); + htmlTemplateTag2Id["free"] = 1; + htmlTemplateTag2Id["table"] = 0; + + for (auto &t : htmlTmpl) { + items.emplace_back(lang.tl(t.name), ++id); + htmlTemplateTag2Id[t.tag] = id; + html2IdToInfo[id] = t.desc; + } + gdi.addSelection("Format", 200, 100, 0, L"Format:").setHandler(&htmlClass); + if (!htmlTemplateTag2Id.count(tmpSettingsParam.htmlTypeTag)) + tmpSettingsParam.htmlTypeTag = "free"; + + gdi.addItem("Format", items); + int tid = htmlTemplateTag2Id[tmpSettingsParam.htmlTypeTag]; + gdi.selectItemByData("Format", tid); + + htmlDetails(gdi, tmpSettingsParam, html2IdToInfo[tid], tid > 5); + + gdi.refresh(); +} + +void TabList::handleHTMLSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) { + if (type == GUI_BUTTON) { + int typeIx = gdi.getSelectedItem("Format").first; + string typeTag = "free"; + for (auto &m : htmlTemplateTag2Id) { + if (m.second == typeIx) { + typeTag = m.first; + break; + } + } + int margin = -1; + int rows = 0; + int cols = 0; + int time_ms = 0; + double scale = 0; + + if (gdi.hasField("Margin")) { + margin = gdi.getTextNo("Margin"); + if (gdi.isChecked("UseRows")) + rows = gdi.getTextNo("Rows"); + cols = gdi.getTextNo("Columns"); + time_ms = gdi.getTextNo("Time"); + scale = _wtoi(gdi.getText("Scale").c_str())*0.01; + } + else { + scale = _wtoi(gdi.getText("Scale").c_str())*0.01; + if (gdi.isChecked("Reload")) + time_ms = 1000 * _wtoi(gdi.getText("ReloadTime").c_str()); + } + ButtonInfo bi = static_cast(info); + if (bi.id == "ApplyList" || bi.id =="Automatic") { + oListParam ¶m = currentList.getParam(); + param.htmlTypeTag = typeTag; + + if (margin > 0) { + param.htmlRows = rows; + param.htmlScale = scale; + param.timePerPage = time_ms; + param.margin = margin; + param.nColumns = cols; + } + else { + param.htmlScale = scale; + param.timePerPage = time_ms; + } + + if (bi.id == "ApplyList") { + if (param.sourceParam != -1) { + auto &dest = oe->getListContainer().getParam(param.sourceParam); + if (margin > 0) { + dest.htmlRows = rows; + dest.htmlScale = scale; + dest.timePerPage = time_ms; + dest.margin = margin; + dest.nColumns = cols; + } + else { + dest.htmlScale = scale; + dest.timePerPage = time_ms; + } + + dest.htmlTypeTag = typeTag; + } + else { + dest_gdi.sendCtrlMessage("Remember"); + } + } + else { + if (lastHtmlTarget.empty()) { + WCHAR bf[260]; + bf[0] = 0; + GetCurrentDirectory(260, bf); + lastHtmlTarget = bf; + lastHtmlTarget += L"\\exported.html"; + } + dest_gdi.sendCtrlMessage("Automatic"); + } + } + else if (bi.id == "HTML") { + oListParam ¶m = currentList.getParam(); + + int index = 0; + vector< pair > ext; + ext.push_back(make_pair(L"Webbdokument", L"*.html;*.htm")); + + wstring file = gdi.browseForSave(ext, L"html", index); + + if (!file.empty()) { + HTMLWriter::reset(); // Force template reload + HTMLWriter::write(dest_gdi, file, oe->getName(), + param.getContentsDescriptor(*oe), + param.pageBreak, + typeTag, 0, rows, cols, time_ms, margin, scale); + gdi.openDoc(file.c_str()); + lastHtmlTarget = file; + oe->setProperty("LastExportTarget", file); + } + } + else if (bi.id == "UseRows") { + gdi.setInputStatus("Rows", gdi.isChecked(bi.id)); + } + else if (bi.id == "Reload") { + gdi.setInputStatus("ReloadTime", gdi.isChecked(bi.id)); + } + } + else if (type == GUI_LISTBOX) { + ListBoxInfo lbi = dynamic_cast(info); + if (lbi.id == "Format") { + htmlDetails(gdi, tmpSettingsParam, html2IdToInfo[lbi.data], lbi.data > 5); + gdi.refresh(); + } + } +} + +void TabList::loadClassSettings(gdioutput &gdi, string targetTag) { + gdi.clearPage(false); + gdi.setCX(gdi.scaleLength(10)); + gdi.setCY(gdi.scaleLength(15)); + gdi.setColorMode(RGB(242, 240, 250)); + gdi.setData("target", targetTag); + settingsTarget = targetTag; + gdi.addString("", fontMediumPlus, L"Klassval för 'X'#" + currentList.getName()); + gdi.dropLine(0.5); + gdi.pushX(); + +// int sx = gdi.getCX() - gdi.scaleLength(5); +// int sy = gdi.getCY(); +// gdi.dropLine(0.7); + + makeClassSelection(gdi); + + oEvent::ClassFilter ct = currentList.isTeamList() ? oEvent::filterOnlyMulti : oEvent::filterNone; + + oe->fillClasses(gdi, "ListSelection", oEvent::extraNone, ct); + gdi.setSelection("ListSelection", currentList.getParam().selection); + gdi.dropLine(2.5); + + gdi.fillDown(); + gdi.addCheckbox("PageBreak", "Sidbrytning mellan klasser", 0, currentList.getParam().pageBreak).setHandler(&settingsClassSelection); + gdi.addCheckbox("ShowHeader", "Visa rubrik", 0, currentList.getParam().showHeader).setHandler(&settingsClassSelection); + + gdi.dropLine(-0.3); + gdi.addInput("Heading", currentList.getParam().getCustomTitle(wstring(L"")), 28, 0, L"Egen listrubrik:"); + gdi.setInputStatus("Heading", currentList.getParam().showHeader); + gdi.dropLine(0.5); + gdi.fillRight(); + gdi.addButton("UpdateClass", "Uppdatera").setDefault().setHandler(&settingsClassSelection); + gdi.addButton("Cancel", "Avbryt").setCancel().setHandler(&settingsClassSelection); + gdi.dropLine(2.5); + gdi.popX(); + // + // int ey = gdi.getCY() + gdi.scaleLength(4); +// int ex = gdi.getWidth(); +// RECT rc = { sx, sy, ex, ey }; +// gdi.addRectangle(rc, colorLightBlue); + + gdi.refresh(); +} + +void TabList::handleClassSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi) { + if (type == GUI_BUTTON) { + ButtonInfo bi = static_cast(info); + if (bi.id == "Cancel") + gdi.closeWindow(); + else if (bi.id == "UpdateClass") { + oListParam ¶m = currentList.getParam(); + param.lockUpdate = true; + gdi.getSelection("ListSelection", param.selection); + bool pb = gdi.isChecked("PageBreak"); + param.pageBreak = pb; + + bool sh = gdi.isChecked("ShowHeader"); + param.showHeader = sh; + + param.setCustomTitle(makeDash(trim(gdi.getText("Heading")))); + + loadPage(dest_gdi); + param.lockUpdate = false; + gdi.closeWindow(); + } + else if (bi.id == "PageBreak" || bi.id == "ShowHeader") { + gdi.setInputStatus("Heading", gdi.isChecked("ShowHeader")); + } + } + else if (type == GUI_LISTBOX) { + ListBoxInfo lbi = static_cast(info); + } +} + + void TabList::settingsResultList(gdioutput &gdi) { lastFilledResultClassType = -1; @@ -1746,6 +2229,8 @@ void TabList::settingsResultList(gdioutput &gdi) gdi.fillDown(); gdi.pushX(); gdi.addCheckbox("PageBreak", "Sidbrytning mellan klasser", ListsCB, oe->getPropertyInt("pagebreak", 0)!=0); + gdi.addCheckbox("ShowHeader", "Visa rubrik", ListsCB, oe->getPropertyInt("showheader", 1) != 0); + gdi.addCheckbox("ShowInterResults", "Visa mellantider", 0, lastInterResult, "Mellantider visas för namngivna kontroller."); gdi.addCheckbox("ShowSplits", "Lista med sträcktider", 0, lastSplitState); @@ -1808,7 +2293,7 @@ void checkWidth(gdioutput &gdi) { int h,w; gdi.getTargetDimension(w, h); w = max (w, gdi.scaleLength(300)); - if (gdi.getCX() + gdi.scaleLength(100) > w) { + if (gdi.getCX() + gdi.scaleLength(110) > w) { gdi.popX(); gdi.dropLine(2.5); } @@ -1850,7 +2335,7 @@ bool TabList::loadPage(gdioutput &gdi) gdi.pushX(); if (!cnf.empty()) { gdi.dropLine(1); - gdi.addString("", 1, "Startlistor"); + gdi.addString("", fontMediumPlus, "Startlistor").setColor(colorDarkGrey); gdi.fillRight(); if (cnf.hasIndividual()) { gdi.addButton("StartIndividual", "Individuell", ListsCB); @@ -1858,11 +2343,14 @@ bool TabList::loadPage(gdioutput &gdi) if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) gdi.addButton("StartClub", "Klubbstartlista", ListsCB); } - - for (size_t k = 0; k 0) { checkWidth(gdi); - gdi.addButton("RaceNStart", "Lopp X#" + itos(k+1), ListsCB, + gdi.addButton("RaceNStart", "Lopp X#" + itos(k + 1), ListsCB, "Startlista ett visst lopp.").setExtra(k); } } @@ -1870,21 +2358,18 @@ bool TabList::loadPage(gdioutput &gdi) checkWidth(gdi); gdi.addButton("TeamStartList", "Stafett (sammanställning)", ListsCB); } - if (cnf.hasPatrol()) { - checkWidth(gdi); - gdi.addButton("PatrolStartList", "Patrull", ListsCB); - } - for (size_t k = 0; k 0) { checkWidth(gdi); - gdi.addButton("LegNStart", "Sträcka X#" + itos(k+1), ListsCB).setExtra(k); + gdi.addButton("LegNStart", "Sträcka X#" + itos(k + 1), ListsCB).setExtra(k); } } checkWidth(gdi); gdi.addButton("MinuteStartList", "Minutstartlista", ListsCB); - if (cnf.isMultiStageEvent()) { + if (cnf.isMultiStageEvent()) { checkWidth(gdi); gdi.addButton("StartL:inputresult", "Input Results", ListsCB); } @@ -1892,21 +2377,34 @@ bool TabList::loadPage(gdioutput &gdi) gdi.dropLine(3); gdi.fillDown(); gdi.popX(); - gdi.addString("", 1, "Resultatlistor"); + gdi.addString("", fontMediumPlus, "Resultatlistor").setColor(colorDarkGrey); gdi.fillRight(); if (cnf.hasIndividual()) { gdi.addButton("ResultIndividual", "Individuell", ListsCB); checkWidth(gdi); + } + + if (cnf.hasPatrol()) { + gdi.addButton("Result:patrolresult", "Patrull", ListsCB).setExtra(3); + checkWidth(gdi); + } + + if (cnf.hasIndividual()) { + + gdi.addButton("Result:liveresultradio", "Liveresultat", ListsCB); + checkWidth(gdi); + + gdi.addButton("Result:latestresult", "Latest Results", ListsCB).setExtra(1); + checkWidth(gdi); + if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { gdi.addButton("ResultClub", "Klubbresultat", ListsCB); - checkWidth(gdi); + checkWidth(gdi); } gdi.addButton("ResultIndSplit", "Sträcktider", ListsCB); - - checkWidth(gdi); - gdi.addButton("Result:latestresult", "Latest Results", ListsCB).setExtra(1); + if (cnf.isMultiStageEvent()) { checkWidth(gdi); @@ -1916,11 +2414,28 @@ bool TabList::loadPage(gdioutput &gdi) gdi.addButton("Result:finalresult", "Slutresultat", ListsCB); } } + + if (!cnf.knockout.empty()) { + checkWidth(gdi); + gdi.addButton("KnockoutTotal", "Knockout sammanställning", ListsCB); + } + + + if (!cnf.lapcountsingle.empty()) { + checkWidth(gdi); + gdi.addButton("LapCount", "Varvräkning", ListsCB); + } + + if (!cnf.lapcountextra.empty()) { + checkWidth(gdi); + gdi.addButton("LapCountExtra", "Varvräkning med mellantid", ListsCB); + } + bool hasMulti = false; - for (size_t k = 0; k 0) { checkWidth(gdi); - gdi.addButton("RaceNRes", "Lopp X#" + itos(k+1), ListsCB, + gdi.addButton("RaceNRes", "Lopp X#" + itos(k + 1), ListsCB, "Resultat för ett visst lopp.").setExtra(k); hasMulti = true; } @@ -1934,13 +2449,10 @@ bool TabList::loadPage(gdioutput &gdi) checkWidth(gdi); gdi.addButton("TeamResults", "Stafett (sammanställning)", ListsCB); } - if (cnf.hasPatrol()) { - checkWidth(gdi); - gdi.addButton("PatrolResultList", "Patrull", ListsCB); - } + for (map >::const_iterator it = cnf.legResult.begin(); it != cnf.legResult.end(); ++it) { checkWidth(gdi); - gdi.addButton("LegNResult", "Sträcka X#" + itos(it->first+1), ListsCB).setExtra(it->first); + gdi.addButton("LegNResult", "Sträcka X#" + itos(it->first + 1), ListsCB).setExtra(it->first); } if (cnf.hasRogaining()) { @@ -1957,10 +2469,9 @@ bool TabList::loadPage(gdioutput &gdi) gdi.dropLine(3); } - MetaListContainer &lc = oe->getListContainer(); if (lc.getNumLists(MetaListContainer::ExternalList) > 0) { - gdi.addString("", 1, "Egna listor"); + gdi.addString("", fontMediumPlus, "Egna listor").setColor(colorDarkGrey); gdi.fillRight(); gdi.pushX(); @@ -1972,16 +2483,16 @@ bool TabList::loadPage(gdioutput &gdi) gdi.addButton("CustomList", mc.getListName(), ListsCB).setExtra(k); } } - } - gdi.popX(); - gdi.dropLine(3); - gdi.fillDown(); + gdi.popX(); + gdi.dropLine(3); + gdi.fillDown(); + } vector< pair > savedParams; lc.getListParam(savedParams); if (savedParams.size() > 0) { - gdi.addString("", 1, "Sparade listval"); + gdi.addString("", fontMediumPlus, "Sparade listval").setColor(colorDarkGrey); gdi.fillRight(); gdi.pushX(); @@ -2003,7 +2514,7 @@ bool TabList::loadPage(gdioutput &gdi) gdi.fillDown(); } - gdi.addString("", 1, "Rapporter"); + gdi.addString("", fontMediumPlus, "Rapporter").setColor(colorDarkGrey); gdi.fillRight(); gdi.pushX(); @@ -2027,15 +2538,23 @@ bool TabList::loadPage(gdioutput &gdi) } bool hasVac = false; + bool hasAPIEntry = false; { vector rr; oe->getRunners(0, 0, rr, false); - for (size_t k = 0; k < rr.size(); k++) { - if (rr[k]->isVacant()) { + for (pRunner r : rr) { + if (r->isVacant()) { hasVac = true; break; } } + + for (pRunner r : rr) { + if (r->hasFlag(oRunner::FlagAddedViaAPI)) { + hasAPIEntry = true; + break; + } + } } if (hasVac) { @@ -2043,26 +2562,42 @@ bool TabList::loadPage(gdioutput &gdi) checkWidth(gdi); } - gdi.addButton("GenLst:courseusage", "Bananvändning", ListsCB); - checkWidth(gdi); + if (hasAPIEntry) { + gdi.addButton("GenLst:apientry", "EFilterAPIEntry", ListsCB); + checkWidth(gdi); + } - gdi.addButton("GenLst:controloverview", "Kontroller", ListsCB); - checkWidth(gdi); + if (oe->getMeOSFeatures().withCourses(oe)) { + gdi.addButton("GenLst:courseusage", "Bananvändning", ListsCB); + checkWidth(gdi); - gdi.addButton("GenLst:controlstatistics", "Control Statistics", ListsCB); - checkWidth(gdi); + gdi.addButton("GenLst:controloverview", "Kontroller", ListsCB); + checkWidth(gdi); + + gdi.addButton("GenLst:controlstatistics", "Control Statistics", ListsCB); + checkWidth(gdi); + } if (cnf.hasRentedCard) - gdi.addButton("RentedCards", "Hyrbricksrapport", ListsCB); + gdi.addButton("HiredCards", "Hyrbricksrapport", ListsCB); gdi.popX(); gdi.dropLine(3); + RECT rc; + rc.left = gdi.getCX(); + gdi.setCX(rc.left + gdi.scaleLength(10)); + rc.top = gdi.getCY(); + gdi.dropLine(0.5); + gdi.addString("", fontMediumPlus, "Inställningar").setColor(colorDarkGrey); + gdi.setCX(rc.left + gdi.scaleLength(10)); + gdi.dropLine(2); + gdi.addCheckbox("PageBreak", "Sidbrytning mellan klasser / klubbar", ListsCB, oe->getPropertyInt("pagebreak", 0)!=0); + gdi.addCheckbox("ShowHeader", "Visa rubrik", ListsCB, oe->getPropertyInt("showheader", 1) != 0); gdi.addCheckbox("SplitAnalysis", "Med sträcktidsanalys", ListsCB, oe->getPropertyInt("splitanalysis", 1)!=0); - gdi.popX(); - gdi.fillRight(); + gdi.setCX(rc.left + gdi.scaleLength(10)); gdi.dropLine(2); gdi.addString("", 0, "Begränsning, antal visade per klass: "); gdi.dropLine(-0.2); @@ -2075,11 +2610,13 @@ bool TabList::loadPage(gdioutput &gdi) gdi.addItem("ClassLimit", itow(v), v); } gdi.selectItemByData("ClassLimit", oe->getPropertyInt("classlimit", 0)); - + + gdi.dropLine(2); + rc.bottom = gdi.getCY(); + rc.right = gdi.getWidth() - gdi.scaleLength(5); + gdi.addRectangle(rc, colorLightBlue); + gdi.dropLine(1.5); gdi.popX(); - - gdi.dropLine(3); - gdi.addButton("GeneralList", "Alla listor...", ListsCB); gdi.addButton("EditList", "Redigera lista...", ListsCB); @@ -2189,14 +2726,18 @@ void TabList::saveExtraLines(oEvent &oe, const char *dataField, gdioutput &gdi) void TabList::customTextLines(oEvent &oe, const char *dataField, gdioutput &gdi) { gdi.dropLine(2.5); - gdi.addString("", boldText, "Egna textrader"); + gdi.addString("", fontMediumPlus, "Egna textrader"); + gdi.dropLine(0.3); + gdi.addString("", 10, "help:custom_text_lines"); + gdi.dropLine(0.8); + int yp = gdi.getCY(); vector< pair > fonts; vector< pair > lines; MetaListPost::getAllFonts(fonts); oe.getExtraLines(dataField, lines); - + int xp = gdi.getCX(); for (int k = 0; k < 5; k++) { gdi.fillRight(); gdi.pushX(); @@ -2211,10 +2752,24 @@ void TabList::customTextLines(oEvent &oe, const char *dataField, gdioutput &gdi) } else gdi.selectFirstItem(key); + + xp = max(xp, gdi.getCX()); gdi.popX(); gdi.fillDown(); gdi.dropLine(2); } + gdi.pushX(); + gdi.pushY(); + + gdi.setCX(xp); + gdi.setCY(yp); + gdi.addListBox("Symbols", 500, 160); + gdi.setTabStops("Symbols", 300); + vector < pair> symb; + MetaList::fillSymbols(symb); + gdi.addItem("Symbols", symb); + gdi.popX(); + gdi.popY(); } void TabList::liveResult(gdioutput &gdi, oListInfo &li) { @@ -2231,7 +2786,7 @@ EStdListType TabList::getTypeFromResultIndex(int ix) const { case 1: return EStdResultList; case 2: - return EStdPatrolResultList; + return oe->getListContainer().getType("patrolresult"); case 3: return EStdTeamResultListAll; case 4: @@ -2392,7 +2947,7 @@ void TabList::clearCompetitionData() { void TabList::setAnimationMode(gdioutput &gdi) { auto par = currentList.getParam(); gdi.setAnimationMode(make_shared(gdi, par.timePerPage, par.nColumns, - par.margin, par.animate)); + par.margin, par.animate, par.pageBreak)); } void TabList::getStartIndividual(oListParam &par, ClassConfigInfo &cnf){ @@ -2420,13 +2975,13 @@ void TabList::getResultClub(oListParam &par, ClassConfigInfo &cnf) { cnf.getPatrol(par.selection); } -void TabList::getStartPatrol(oListParam &par, ClassConfigInfo &cnf) { - par.listCode = EStdPatrolStartList; +void TabList::getStartPatrol(oEvent &oe, oListParam &par, ClassConfigInfo &cnf) { + par.listCode = oe.getListContainer().getType("patrolstart"); cnf.getPatrol(par.selection); } -void TabList::getResultPatrol(oListParam &par, ClassConfigInfo &cnf) { - par.listCode = EStdPatrolResultList; +void TabList::getResultPatrol(oEvent &oe, oListParam &par, ClassConfigInfo &cnf) { + par.listCode = oe.getListContainer().getType("patrolresult"); cnf.getPatrol(par.selection); } @@ -2468,7 +3023,7 @@ void TabList::getPublicLists(oEvent &oe, vector &lists) { } if (cnf.hasPatrol()) { lists.push_back(oListParam()); - getStartPatrol(lists.back(), cnf); + getStartPatrol(oe, lists.back(), cnf); } if (cnf.isMultiStageEvent()) { @@ -2497,7 +3052,7 @@ void TabList::getPublicLists(oEvent &oe, vector &lists) { } if (cnf.hasPatrol()) { lists.push_back(oListParam()); - getResultPatrol(lists.back(), cnf); + getResultPatrol(oe, lists.back(), cnf); } if (cnf.hasRogaining()) { @@ -2518,3 +3073,13 @@ void TabList::getPublicLists(oEvent &oe, vector &lists) { //gdi.addButton("PriceList", "Prisutdelningslista", ListsCB); } } + +void TabList::readSettings(gdioutput &gdi, oListParam &par, bool forResult) { + par.pageBreak = gdi.isChecked("PageBreak"); + par.showHeader = gdi.isChecked("ShowHeader"); + + if (forResult) { + par.splitAnalysis = gdi.isChecked("SplitAnalysis"); + par.filterMaxPer = gdi.getSelectedItem("ClassLimit").first; + } +} diff --git a/code/TabList.h b/code/TabList.h index dd48f0d..835980e 100644 --- a/code/TabList.h +++ b/code/TabList.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,6 +40,7 @@ protected: bool lastSplitState; bool lastLargeSize; + wstring lastHtmlTarget; EStdListType getTypeFromResultIndex(int ix) const; @@ -78,6 +79,9 @@ private: TabList(const TabList &); const TabList &operator = (const TabList &); + map htmlTemplateTag2Id; + map html2IdToInfo; + string settingsTarget; oListParam tmpSettingsParam; void changeListSettingsTarget(gdioutput &oldWindow, gdioutput &newWindow); @@ -93,8 +97,8 @@ private: static void getResultIndividual(oListParam &par, ClassConfigInfo &cnf); static void getResultClub(oListParam &par, ClassConfigInfo &cnf); - static void getStartPatrol(oListParam &par, ClassConfigInfo &cnf); - static void getResultPatrol(oListParam &par, ClassConfigInfo &cnf); + static void getStartPatrol(oEvent &oe, oListParam &par, ClassConfigInfo &cnf); + static void getResultPatrol(oEvent &oe, oListParam &par, ClassConfigInfo &cnf); static void getStartTeam(oListParam &par, ClassConfigInfo &cnf); static void getResultTeam(oListParam &par, ClassConfigInfo &cnf); @@ -102,6 +106,8 @@ private: static void getResultRogaining(oListParam &par, ClassConfigInfo &cnf); + static void readSettings(gdioutput &gdi, oListParam &par, bool forResult); + public: /** Returns a collection of public lists. */ void static getPublicLists(oEvent &oe, vector &lists); @@ -121,8 +127,18 @@ public: void rebuildList(gdioutput &gdi); void settingsResultList(gdioutput &gdi); + void loadClassSettings(gdioutput &gdi, string targetTag); + void handleClassSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi); + void loadSettings(gdioutput &gdi, string targetTag); void handleListSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi); + + void htmlSettings(gdioutput &gdi, string targetTag); + void handleHTMLSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi); + + void loadRememberList(gdioutput &gdi, string targetTag); + void handleRememberSettings(gdioutput &gdi, BaseInfo &info, GuiEventType type, gdioutput &dest_gdi); + enum PrintSettingsSelection { Splits = 0, StartInfo = 1, diff --git a/code/TabMulti.cpp b/code/TabMulti.cpp index ff78fdd..95d9a6b 100644 --- a/code/TabMulti.cpp +++ b/code/TabMulti.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabMulti.h b/code/TabMulti.h index 3f0a836..f9270ca 100644 --- a/code/TabMulti.h +++ b/code/TabMulti.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TabRunner.cpp b/code/TabRunner.cpp index 6da43ce..9aeab3b 100644 --- a/code/TabRunner.cpp +++ b/code/TabRunner.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -473,7 +473,7 @@ int TabRunner::searchCB(gdioutput &gdi, int type, void *data) { } pRunner TabRunner::save(gdioutput &gdi, int runnerId, bool willExit) { - oe->synchronizeList(oLCardId, true, true); + oe->synchronizeList(oListId::oLCardId); TabSI &tsi = dynamic_cast(*gdi.getTabs().get(TSITab)); tsi.storedInfo.clear(); @@ -545,11 +545,8 @@ pRunner TabRunner::save(gdioutput &gdi, int runnerId, bool willExit) { RunnerStatus originalStatus = r->getStatus(); r->setName(name, true); - if (gdi.hasField("Bib")) { - const wstring &bib = gdi.getText("Bib"); - wchar_t pat[32]; - int num = oClass::extractBibPattern(bib, pat); - r->setBib(bib, num, num>0, false); + if (dynamic_cast(gdi.getBaseInfo("PTime")).changed()) { + savePunchTime(r, gdi); } bool noSetStatus = false; @@ -578,7 +575,7 @@ pRunner TabRunner::save(gdioutput &gdi, int runnerId, bool willExit) { const bool hireChecked = gdi.isChecked("RentCard"); const bool hireState = r->isHiredCard(); if (hireChecked && !hireState) { - r->getDI().setInt("CardFee", oe->getDI().getInt("CardFee")); + r->getDI().setInt("CardFee", oe->getBaseCardFee()); } else if (!hireChecked && hireState) { r->getDI().setInt("CardFee", 0); @@ -638,6 +635,15 @@ pRunner TabRunner::save(gdioutput &gdi, int runnerId, bool willExit) { r->setClassId(classId, true); + if (gdi.hasField("Bib")) { + const wstring &bib = gdi.getText("Bib"); + wchar_t pat[32]; + int num = oClass::extractBibPattern(bib, pat); + bool lockedForking = r->getClassRef(true) && r->getClassRef(true)->lockedForking(); + + r->setBib(bib, num, num>0 && !lockedForking, false); + } + r->setCourseId(gdi.getSelectedItem("RCourse").first); RunnerStatus sIn = (RunnerStatus)gdi.getSelectedItem("Status").first; @@ -649,7 +655,7 @@ pRunner TabRunner::save(gdioutput &gdi, int runnerId, bool willExit) { vector mp; r->evaluateCard(true, mp, 0, true); - if (r->getClassId(true) != classId) { + if (r->getClassId(true) != classId && r->getClassId(false) != classId) { gdi.alert("Deltagarens klass styrs av laget."); } @@ -963,7 +969,7 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) return 0; pRunner r = oe->getRunner(runnerId, 0); if (getExtraWindow("ecosettings", true) == 0) { - gdioutput *settings = createExtraWindow("ecosettings", L"Economy", 550, 350); + gdioutput *settings = createExtraWindow("ecosettings", L"Economy", gdi.scaleLength(550), gdi.scaleLength(350), true); TabRunner &dst = dynamic_cast(*settings->getTabs().get(TabType::TRunnerTab)); dst.loadEconomy(*settings, *r); } @@ -1236,7 +1242,7 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) (listenToPunches || oe->isReadOnly()) && currentMode == 5) { if (ei.getData() > 0) { vector rs; - oe->getRunnersByCard(ei.getData(), rs); + oe->getRunnersByCardNo(ei.getData(), true, oEvent::CardLookupProperty::Any, rs); if (!rs.empty()) { runnersToReport.resize(rs.size()); for (size_t k = 0; kgetDI().getInt("CardFee"); + cardFee = oe->getBaseCardFee(); r->getDI().setInt("CardFee", cardFee); } else @@ -1513,8 +1519,7 @@ void TabRunner::setCardNo(gdioutput &gdi, int cardNo) } } -void TabRunner::showRunnerReport(gdioutput &gdi) -{ +void TabRunner::showRunnerReport(gdioutput &gdi) { gdi.clearPage(true); currentMode = 5; @@ -1523,13 +1528,13 @@ void TabRunner::showRunnerReport(gdioutput &gdi) else if (oe->isReadOnly()) gdi.addString("", fontLarge, makeDash(L"MeOS - Resultatkiosk")).setColor(colorDarkBlue); - gdi.dropLine(); + gdi.dropLine(); gdi.pushX(); gdi.fillRight(); gdi.addSelection("ReportRunner", 300, 300, RunnerCB); - oe->fillRunners(gdi, "ReportRunner", true, oEvent::RunnerFilterShowAll|oEvent::RunnerCompactMode); + oe->fillRunners(gdi, "ReportRunner", true, oEvent::RunnerFilterShowAll | oEvent::RunnerCompactMode); gdi.selectItemByData("ReportRunner", runnerId); if (!oe->isReadOnly()) { @@ -1545,44 +1550,51 @@ void TabRunner::showRunnerReport(gdioutput &gdi) gdi.popX(); gdi.registerEvent("DataUpdate", RunnerCB); gdi.registerEvent("ReadCard", RunnerCB); - - gdi.fillDown(); - oe->calculateResults(oEvent::RTClassResult); - + if (runnerId > 0) { runnersToReport.resize(1); runnersToReport[0] = make_pair(runnerId, false); } + generateRunnerReport(*oe, gdi, runnersToReport); + + if (runnersToReport.size() == 1) + runnerId = runnersToReport[0].first; +} + + void TabRunner::generateRunnerReport(oEvent &oe, gdioutput &gdi, vector> &runnersToReport) { + + gdi.fillDown(); cTeam t = 0; + set clsSet; for (size_t k = 0; k < runnersToReport.size(); k++) { - pRunner r = oe->getRunner(runnersToReport[k].first, 0); + pRunner r = oe.getRunner(runnersToReport[k].first, 0); + clsSet.insert(r->getClassId(true)); if (r && r->getTeam()) { pClass cls = r->getClassRef(true); - if (cls && cls->getClassType() == oClassPatrol) + if (cls && cls->getClassType() != oClassRelay) continue; if (t == 0) t = r->getTeam(); } } - - if (runnersToReport.size() == 1) - runnerId = runnersToReport[0].first; + oe.calculateResults(clsSet, oEvent::ResultType::PreliminarySplitResults, true); + oe.calculateResults(clsSet, oEvent::ResultType::ClassResult); if (t == 0) { for (size_t k = 0; k < runnersToReport.size(); k++) - runnerReport(gdi, runnersToReport[k].first, runnersToReport[k].second); + runnerReport(oe, gdi, runnersToReport[k].first, runnersToReport[k].second); } else { - oe->calculateTeamResults(false); + oe.calculateTeamResults(false); set selectedRunners; bool selHasRes = false; for (size_t k = 0; k < runnersToReport.size(); k++) { selectedRunners.insert(runnersToReport[k].first); - pRunner r = oe->getRunner(runnersToReport[k].first, 0); - if (r->getStatus() != StatusUnknown) + pRunner r = oe.getRunner(runnersToReport[k].first, 0); + if (r && r->hasOnCourseResult()) selHasRes = true; } @@ -1593,7 +1605,7 @@ void TabRunner::showRunnerReport(gdioutput &gdi) tInfo += L", +" + formatTime(t->getTimeAfter(-1)); } else if (t->getStatus() != StatusUnknown) { - tInfo += L" " + t->getStatusS(); + tInfo += L" " + t->getStatusS(true); } gdi.addStringUT(fontMediumPlus, t->getClass(true)); @@ -1613,10 +1625,10 @@ void TabRunner::showRunnerReport(gdioutput &gdi) bool selected = selectedRunners.count(r->getId()) > 0; if (selHasRes) { - runnerReport(gdi, r->getId(), !selected); + runnerReport(oe, gdi, r->getId(), !selected); } else { - runnerReport(gdi, r->getId(), !nextSelected); + runnerReport(oe, gdi, r->getId(), !nextSelected); } visitedSelected |= selected; @@ -1625,11 +1637,11 @@ void TabRunner::showRunnerReport(gdioutput &gdi) } } -void TabRunner::runnerReport(gdioutput &gdi, int id, bool compact) { - pRunner r = oe->getRunner(id, 0); +void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { + pRunner r = oe.getRunner(id, 0); if (!r || ! r->getClassRef(false)) return; - + gdi.pushX(); gdi.fillDown(); if (r->getTeam() == 0) { @@ -1647,18 +1659,18 @@ void TabRunner::runnerReport(gdioutput &gdi, int id, bool compact) { wstring str; if (r->getTeam() == 0) { - str = oe->formatListString(lRunnerTimeStatus, r); + str = oe.formatListString(lRunnerTimeStatus, r); } else { - str = oe->formatListString(lTeamLegTimeStatus, r); - str += L" (" + oe->formatListString(lRunnerTimeStatus, r) + L")"; + str = oe.formatListString(lTeamLegTimeStatus, r); + str += L" (" + oe.formatListString(lRunnerTimeStatus, r) + L")"; } gdi.dropLine(0.3); if (r->statusOK()) { int total, finished, dns; - oe->getNumClassRunners(r->getClassId(true), r->getLegNumber(), total, finished, dns); + oe.getNumClassRunners(r->getClassId(true), r->getLegNumber(), total, finished, dns); if (r->getTeam() == 0) { gdi.addString("", fontMediumPlus, L"Tid: X, nuvarande placering Y/Z.#" + str + L"#" + r->getPlaceS() + L"#" + itow(finished)); @@ -1686,12 +1698,12 @@ void TabRunner::runnerReport(gdioutput &gdi, int id, bool compact) { if (r->getFinishTime() > 0) gdi.addString("", fontMedium, L"Måltid: X #" + r->getFinishTimeS()); - const wstring &after = oe->formatListString(lRunnerTimeAfter, r); + const wstring &after = oe.formatListString(lRunnerTimeAfter, r); if (!after.empty()) { gdi.addString("", fontMedium, L"Tid efter: X #" + after); } - const wstring &lost = oe->formatListString(lRunnerMissedTime, r); + const wstring &lost = oe.formatListString(lRunnerLostTime, r); if (!lost.empty()) { gdi.addString("", fontMedium, L"Bomtid: X #" + lost).setColor(colorDarkRed); } @@ -1798,7 +1810,7 @@ void TabRunner::runnerReport(gdioutput &gdi, int id, bool compact) { } else { vector punches; - oe->getPunchesForRunner(r->getId(), punches); + oe.getPunchesForRunner(r->getId(), punches); int lastT = r->getStartTime(); for (size_t k = 0; k < punches.size(); k++) { @@ -1830,7 +1842,7 @@ void TabRunner::runnerReport(gdioutput &gdi, int id, bool compact) { int t = punches[k]->getAdjustedTime(); if (t>0) { int st = r->getStartTime(); - gdi.addString("", yp + lh, cx, normalText, L"Klocktid: X#" + oe->getAbsTime(t), limit); + gdi.addString("", yp + lh, cx, normalText, L"Klocktid: X#" + oe.getAbsTime(t), limit); if (st > 0 && t > st) { wstring split = formatTimeHMS(t-st); if (lastT>0 && st != lastT && lastT < t) @@ -2036,7 +2048,6 @@ void TabRunner::showInForestList(gdioutput &gdi) clearInForestData(); bool hasDNS; oe->analyseDNS(unknown_dns, known_dns, known, unknown, hasDNS); - oe->setupCardHash(false); if (!unknown.empty()) { gdi.dropLine(); gdi.dropLine(0.5); @@ -2070,8 +2081,6 @@ void TabRunner::showInForestList(gdioutput &gdi) else gdi.disableInput("SetUnknown"); - oe->setupCardHash(true); - if (known.empty() && unknown.empty() && known_dns.empty()) { gdi.addString("", 10, "inforestwarning"); } @@ -2095,7 +2104,14 @@ void TabRunner::listRunners(gdioutput &gdi, const vector &r, bool filte gdi.addStringUT(yp, xp+350, 0, r[k]->getClub(), 190); int c = r[k]->getCardNo(); if (c>0) { - oe->getRunnersByCardNo(c, true, true, out); + { + vector o2; + oe->getRunnersByCardNo(c, false, oEvent::CardLookupProperty::SkipNoStart, o2); + for (pRunner r : o2) { + if (!r->skip()) + out.push_back(r); + } + } if (out.size() <= 1) { gdi.addStringUT(yp, xp+550, 0, "(" + itos(c) + ")", 190); } @@ -2186,7 +2202,6 @@ void disablePunchCourseChange(gdioutput &gdi) gdi.disableInput("PTime"); gdi.setText("PTime", L""); gdi.selectItemByData("Punches", -1); - } void disablePunchCourse(gdioutput &gdi) @@ -2206,37 +2221,47 @@ void UpdateStatus(gdioutput &gdi, pRunner r) gdi.setText("RunnerInfo", lang.tl(r->getProblemDescription()), true); } -int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) -{ - DWORD rid=runnerId; +int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) { + DWORD rid = runnerId; if (!rid) return 0; - pRunner r=oe->getRunner(rid, 0); + pRunner r = oe->getRunner(rid, 0); - if (!r){ + if (!r) { gdi.alert("Deltagaren måste sparas innan stämplingar kan hanteras."); return 0; } + if (type == GUI_LISTBOXSELECT) { + ListBoxInfo bi = *(ListBoxInfo *)data; + if (bi.id == "Course") { + if (signed(bi.data) >= 0) { + pCourse pc = r->getCourse(true); + if (!pc) return 0; + return gdi.sendCtrlMessage("AddC"); + } + } + } + else if (type == GUI_LISTBOX) { + ListBoxInfo bi = *(ListBoxInfo *)data; - if (type==GUI_LISTBOX){ - ListBoxInfo bi=*(ListBoxInfo *)data; - - if (bi.id=="Punches") { + if (bi.id == "Punches") { if (bi.data != -1) { - pCard card=r->getCard(); + pCard card = r->getCard(); if (!card) return 0; pPunch punch = card->getPunchByIndex(bi.data); - if (!punch) + if (!punch) throw meosException("Punch not found."); - wstring ptime=punch->getTime(); + wstring ptime; + if (punch->getTimeInt() > 0) + ptime = punch->getTime(); - if (!ptime.empty()) { - gdi.enableInput("SaveC"); - gdi.setText("PTime", ptime); - } + gdi.setText("PTime", ptime); + + gdi.enableInput("SaveC"); + gdi.enableInput("RemoveC"); gdi.enableInput("PTime"); } @@ -2244,46 +2269,44 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) gdi.disableInput("SaveC"); gdi.disableInput("RemoveC"); gdi.setText("PTime", L""); + gdi.disableInput("PTime"); } disablePunchCourseAdd(gdi); } - else if (bi.id=="Course") { - if (signed(bi.data)>=0) { - pCourse pc=r->getCourse(true); - + else if (bi.id == "Course") { + if (signed(bi.data) >= 0) { + pCourse pc = r->getCourse(true); if (!pc) return 0; - gdi.enableInput("AddC"); gdi.enableInput("AddAllC"); } - else{ + else { gdi.disableInput("AddC"); gdi.disableInput("AddAllC"); } disablePunchCourseChange(gdi); } } - else if (type==GUI_BUTTON){ - ButtonInfo bi=*(ButtonInfo *)data; - pCard card=r->getCard(); + else if (type == GUI_BUTTON) { + ButtonInfo bi = *(ButtonInfo *)data; + pCard card = r->getCard(); - if (!card){ + if (!card) { if (!gdi.ask(L"ask:addpunches")) return 0; - card=oe->allocateCard(r); + card = oe->allocateCard(r); card->setCardNo(r->getCardNo()); vector mp; r->addPunches(card, mp); - } - if (bi.id=="AddC"){ + if (bi.id == "AddC") { vector mp; r->evaluateCard(true, mp); - pCourse pc=r->getCourse(true); + pCourse pc = r->getCourse(true); if (!pc) return 0; @@ -2292,7 +2315,7 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) if (!gdi.getSelectedItem("Course", lbi)) return 0; - oControl *oc=pc->getControl(lbi.data); + oControl *oc = pc->getControl(lbi.data); if (!oc) return 0; vector nmp; @@ -2301,7 +2324,7 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) r->evaluateCard(true, nmp, oc->getFirstNumber()); //Add this punch } else { - for (size_t k = 0; khasNumber(mp[k])) r->evaluateCard(true, nmp, mp[k]); //Add this punch } @@ -2314,13 +2337,12 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) card->fillPunches(gdi, "Punches", pc); UpdateStatus(gdi, r); } - else if (bi.id=="AddAllC"){ + else if (bi.id == "AddAllC") { vector mp; r->evaluateCard(true, mp); - vector::iterator it=mp.begin(); + vector::iterator it = mp.begin(); - - while(it!=mp.end()){ + while (it != mp.end()) { vector nmp; r->evaluateCard(true, nmp, *it); //Add this punch ++it; @@ -2336,30 +2358,30 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) r->hasManuallyUpdatedTimeStatus(); UpdateStatus(gdi, r); } - else if (bi.id=="SaveC"){ - //int time=oe->GetRelTime(); - + else if (bi.id == "SaveC") { + if (!savePunchTime(r, gdi)) + return false; ListBoxInfo lbi; if (!gdi.getSelectedItem("Punches", lbi)) return 0; + /* + pCard pc=r->getCard(); - pCard pc=r->getCard(); + if (!pc) return 0; - if (!pc) return 0; + pPunch pp = pc->getPunchByIndex(lbi.data); - pPunch pp = pc->getPunchByIndex(lbi.data); - - if (!pp) - throw meosException("Punch not found."); + if (!pp) + throw meosException("Punch not found."); - pc->setPunchTime(pp, gdi.getText("PTime")); + pc->setPunchTime(pp, gdi.getText("PTime")); + r->evaluateCard(true, mp); + + //synchronize SQL + card->synchronize();*/ vector mp; - r->evaluateCard(true, mp); - - //synchronize SQL - card->synchronize(); r->synchronize(); r->evaluateCard(true, mp); r->hasManuallyUpdatedTimeStatus(); @@ -2371,6 +2393,30 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) return 0; } +bool TabRunner::savePunchTime(pRunner r, gdioutput &gdi) { + ListBoxInfo lbi; + pCard card = r->getCard(); + if (!card) + return false; + + if (!gdi.getSelectedItem("Punches", lbi)) + return 0; + + pPunch pp = card->getPunchByIndex(lbi.data); + + if (!pp) + return false; + + card->setPunchTime(pp, gdi.getText("PTime")); + + vector mp; + r->evaluateCard(true, mp); + + //synchronize SQL + card->synchronize(); + return true; +} + bool TabRunner::loadPage(gdioutput &gdi) { oe->reEvaluateAll(set(), true); @@ -2503,26 +2549,28 @@ bool TabRunner::loadPage(gdioutput &gdi) if (numSL > 0) gdi.fillRight(); - gdi.addSelection("RCourse", numSL == 0 ? 220 : 180, 300, RunnerCB, L"Bana:"); - oe->fillCourses(gdi, "RCourse", true); - gdi.addItem("RCourse", lang.tl("[Klassens bana]"), 0); + if (!oe->getMeOSFeatures().withoutCourses(*oe)) { + gdi.addSelection("RCourse", numSL == 0 ? 220 : 180, 300, RunnerCB, L"Bana:"); + oe->fillCourses(gdi, "RCourse", true); + gdi.addItem("RCourse", lang.tl("[Klassens bana]"), 0); - if (numSL > 0) { - gdi.fillDown(); - gdi.addSelection("NumShort", 60, 300, RunnerCB, L"Avkortning:"); - vector< pair > data; - if (numSL == 1) { - data.push_back(make_pair(lang.tl("Nej"), 0)); - data.push_back(make_pair(lang.tl("Ja"), 1)); - } - else { - data.push_back(make_pair(lang.tl("Nej"), 0)); - for (int i = 1; i <= numSL; i++) { - data.push_back(make_pair(itow(i), i)); + if (numSL > 0) { + gdi.fillDown(); + gdi.addSelection("NumShort", 60, 300, RunnerCB, L"Avkortning:"); + vector< pair > data; + if (numSL == 1) { + data.push_back(make_pair(lang.tl("Nej"), 0)); + data.push_back(make_pair(lang.tl("Ja"), 1)); } + else { + data.push_back(make_pair(lang.tl("Nej"), 0)); + for (int i = 1; i <= numSL; i++) { + data.push_back(make_pair(itow(i), i)); + } + } + gdi.addItem("NumShort", data); + gdi.popX(); } - gdi.addItem("NumShort", data); - gdi.popX(); } gdi.pushX(); @@ -2777,9 +2825,9 @@ bool TabRunner::canSetFinish(pRunner r) const { pRunner TabRunner::warnDuplicateCard(int cno, pRunner r) { pRunner warnCardDupl = 0; - if (!r->getCard()) { + if (!r->getCard() && cno != 0) { vector allR; - oe->getRunners(0, 0, allR, false); + oe->getRunnersByCardNo(cno, false, oEvent::CardLookupProperty::Any, allR); for (size_t k = 0; k < allR.size(); k++) { if (!r->canShareCard(allR[k], cno)) { warnCardDupl = allR[k]; @@ -2815,19 +2863,33 @@ int TabRunner::numShorteningLevels() const { map known; int res = 0; for (size_t k = 0; k < allCrs.size(); k++) { - pCourse sh = allCrs[k]->getShorterVersion(); - touch.clear(); + auto shInfo = allCrs[k]->getShorterVersion(); + pCourse cCourse = allCrs[k]; int count = 0; - while (sh && !touch.count(sh->getId())) { - count++; - map::iterator r = known.find(sh->getId()); - if (r != known.end()) { - count += r->second; - break; + if (shInfo.second) { + pCourse sh = shInfo.second; + touch.clear(); + while (sh && !touch.count(sh->getId())) { + cCourse = sh; + count++; + map::iterator r = known.find(sh->getId()); + if (r != known.end()) { + count += r->second; + break; + } + touch.insert(sh->getId()); + shInfo = sh->getShorterVersion(); + sh = shInfo.second; } - touch.insert(sh->getId()); - sh = sh->getShorterVersion(); } + + if (shInfo.first && !shInfo.second) { + // Course with loops + int nl = cCourse->getNumLoops(); + if (nl > 0) + count += nl - 1; + } + known[allCrs[k]->getId()] = count; res = max(res, count); } @@ -2836,7 +2898,7 @@ int TabRunner::numShorteningLevels() const { void TabRunner::updateNumShort(gdioutput &gdi, pCourse crs, pRunner r) { if (gdi.hasField("NumShort")) { - if (crs && crs->getShorterVersion()) { + if (crs && crs->getShorterVersion().first) { gdi.enableInput("NumShort"); if (r) gdi.selectItemByData("NumShort", r->getNumShortening()); @@ -2871,7 +2933,6 @@ void TabRunner::autoGrowCourse(gdioutput &gdi) { } } - void TabRunner::EconomyHandler::init(oRunner &r) { oe = r.getEvent(); runnerId = r.getId(); @@ -2938,7 +2999,6 @@ void TabRunner::EconomyHandler::save(gdioutput &gdi) { int paid = oe->interpretCurrency(gdi.getText("PaidAmount")); r.getDI().setInt("Paid", paid); - if (paid != 0) { int m = gdi.getSelectedItem("").first; if (m != 1000) diff --git a/code/TabRunner.h b/code/TabRunner.h index cd8af21..309d721 100644 --- a/code/TabRunner.h +++ b/code/TabRunner.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -71,11 +71,12 @@ private: vector known; vector unknown; void clearInForestData(); + bool savePunchTime(pRunner r, gdioutput &gdi); PrinterObject splitPrinter; void showRunnerReport(gdioutput &gdi); - void runnerReport(gdioutput &gdi, int id, bool compactReport); + static void runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compactReport); void showVacancyList(gdioutput &gdi, const string &method="", int classId=0); void showCardsList(gdioutput &gdi); @@ -122,7 +123,7 @@ public: bool loadPage(gdioutput &gdi); bool loadPage(gdioutput &gdi, int runnerId); - + static void generateRunnerReport(oEvent &oe, gdioutput &gdi, vector> &runnersToReport); TabRunner(oEvent *oe); ~TabRunner(void); diff --git a/code/TabSI.cpp b/code/TabSI.cpp index 4579379..bdab253 100644 --- a/code/TabSI.cpp +++ b/code/TabSI.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -119,8 +119,8 @@ int SportIdentCB(gdioutput *gdi, int type, void *data) { int TabSI::siCB(gdioutput &gdi, int type, void *data) { - if (type==GUI_BUTTON) { - ButtonInfo bi=*(ButtonInfo *)data; + if (type == GUI_BUTTON) { + ButtonInfo bi = *(ButtonInfo *)data; if (bi.id == "ClearMemory") { if (gdi.ask(L"Do you want to clear the card memory?")) { @@ -149,41 +149,41 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) else if (bi.id == "CreateCompetition") { createCompetitionFromCards(gdi); } - else if (bi.id=="SIPassive") { - wstring port=gdi.getText("ComPortName"); + else if (bi.id == "SIPassive") { + wstring port = gdi.getText("ComPortName"); if (gSI->openComListen(port.c_str(), gdi.getTextNo("BaudRate"))) { gSI->startMonitorThread(port.c_str()); loadPage(gdi); - gdi.addString("", 1, L"Lyssnar på X.#"+port).setColor(colorDarkGreen); + gdi.addString("", 1, L"Lyssnar på X.#" + port).setColor(colorDarkGreen); } else gdi.addString("", 1, "FEL: Porten kunde inte öppnas").setColor(colorRed); gdi.dropLine(); gdi.refresh(); } - else if (bi.id=="CancelTCP") + else if (bi.id == "CancelTCP") gdi.restore("TCP"); - else if (bi.id=="StartTCP") { + else if (bi.id == "StartTCP") { gSI->tcpAddPort(gdi.getTextNo("tcpPortNo"), 0); gdi.restore("TCP"); gSI->startMonitorThread(L"TCP"); printSIInfo(gdi, L"TCP"); - + gdi.dropLine(0.5); refillComPorts(gdi); gdi.refresh(); } - else if (bi.id=="StartSI") { + else if (bi.id == "StartSI") { wchar_t bf[64]; ListBoxInfo lbi; if (gdi.getSelectedItem("ComPort", lbi)) { swprintf_s(bf, 64, L"COM%d", lbi.data); - wstring port=bf; + wstring port = bf; - if (lbi.text.substr(0, 3)==L"TCP") - port=L"TCP"; + if (lbi.text.substr(0, 3) == L"TCP") + port = L"TCP"; if (gSI->isPortOpen(port)) { gSI->closeCom(port.c_str()); @@ -194,12 +194,12 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } else { gdi.fillDown(); - if (port==L"TCP") { + if (port == L"TCP") { gdi.setRestorePoint("TCP"); gdi.dropLine(); gdi.pushX(); gdi.fillRight(); - gdi.addInput("tcpPortNo", L"10000", 8,0, L"Port för TCP:"); + gdi.addInput("tcpPortNo", L"10000", 8, 0, L"Port för TCP:"); gdi.dropLine(); gdi.addButton("StartTCP", "Starta", SportIdentCB); gdi.addButton("CancelTCP", "Avbryt", SportIdentCB); @@ -214,21 +214,21 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addStringUT(0, lang.tl(L"Startar SI på ") + port + L"..."); gdi.refresh(); - if (gSI->openCom(port.c_str())){ + if (gSI->openCom(port.c_str())) { gSI->startMonitorThread(port.c_str()); - gdi.addStringUT(0, lang.tl(L"SI på ")+ port + L": "+lang.tl(L"OK")); + gdi.addStringUT(0, lang.tl(L"SI på ") + port + L": " + lang.tl(L"OK")); printSIInfo(gdi, port); - + SI_StationInfo *si = gSI->findStation(port); if (si && !si->extended()) gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } - else{ + else { //Retry... Sleep(300); if (gSI->openCom(port.c_str())) { gSI->startMonitorThread(port.c_str()); - gdi.addStringUT(0, lang.tl(L"SI på ") + port + L": " + lang.tl(L"OK")); + gdi.addStringUT(0, lang.tl(L"SI på ") + port + L": " + lang.tl(L"OK")); printSIInfo(gdi, port); SI_StationInfo *si = gSI->findStation(port); @@ -270,22 +270,22 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.refresh(); } } - else if (bi.id=="SIInfo") { + else if (bi.id == "SIInfo") { wchar_t bf[64]; ListBoxInfo lbi; if (gdi.getSelectedItem("ComPort", lbi)) { - if (lbi.text.substr(0,3)==L"TCP") + if (lbi.text.substr(0, 3) == L"TCP") swprintf_s(bf, 64, L"TCP"); else swprintf_s(bf, 64, L"COM%d", lbi.data); gdi.fillDown(); - gdi.addStringUT(0, lang.tl(L"Hämtar information om ") + wstring(bf)+ L"."); + gdi.addStringUT(0, lang.tl(L"Hämtar information om ") + wstring(bf) + L"."); printSIInfo(gdi, bf); gdi.refresh(); } } - else if (bi.id=="AutoDetect") + else if (bi.id == "AutoDetect") { gdi.fillDown(); gdi.addString("", 0, "Söker efter SI-enheter... "); @@ -299,13 +299,13 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) wchar_t bf[128]; gSI->closeCom(0); - while(!ports.empty()) { - int p=ports.front(); + while (!ports.empty()) { + int p = ports.front(); swprintf_s(bf, 128, L"COM%d", p); char bfn[128]; sprintf_s(bfn, 128, "COM%d", p); - gdi.addString((string("SIInfo")+bfn).c_str(), 0, L"#" + lang.tl(L"Startar SI på ") + wstring(bf) + L"..."); + gdi.addString((string("SIInfo") + bfn).c_str(), 0, L"#" + lang.tl(L"Startar SI på ") + wstring(bf) + L"..."); gdi.refresh(); if (gSI->openCom(bf)) { gSI->startMonitorThread(bf); @@ -314,7 +314,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) SI_StationInfo *si = gSI->findStation(bf); if (si && !si->extended()) - gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); + gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } else if (gSI->openCom(bf)) { gSI->startMonitorThread(bf); @@ -325,7 +325,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (si && !si->extended()) gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } - else gdi.addStringUT(0, lang.tl(L"SI på ") + wstring(bf) + L": " +lang.tl(L"FEL, inget svar")); + else gdi.addStringUT(0, lang.tl(L"SI på ") + wstring(bf) + L": " + lang.tl(L"FEL, inget svar")); gdi.refresh(); gdi.popX(); @@ -352,8 +352,8 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) else if (bi.id == "TieOK") { tieCard(gdi); } - else if (bi.id=="Interactive") { - interactiveReadout=gdi.isChecked(bi.id); + else if (bi.id == "Interactive") { + interactiveReadout = gdi.isChecked(bi.id); gEvent->setProperty("Interactive", interactiveReadout); if (mode == ModeAssignCards) { @@ -361,14 +361,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) showAssignCard(gdi, false); } } - else if (bi.id=="Database") { - useDatabase=gdi.isChecked(bi.id); + else if (bi.id == "Database") { + useDatabase = gdi.isChecked(bi.id); gEvent->setProperty("Database", useDatabase); } - else if (bi.id=="PrintSplits") { - printSplits=gdi.isChecked(bi.id); + else if (bi.id == "PrintSplits") { + printSplits = gdi.isChecked(bi.id); } - else if (bi.id=="StartInfo") { + else if (bi.id == "StartInfo") { printStartInfo = gdi.isChecked(bi.id); } else if (bi.id == "UseManualInput") { @@ -378,7 +378,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (manualInput) showManualInput(gdi); } - else if (bi.id=="Import") { + else if (bi.id == "Import") { int origin = bi.getExtraInt(); vector< pair > ext; @@ -399,7 +399,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) filterDate.clear(); filterDate.push_back(lang.tl("Inget filter")); - for (set::iterator it = dates.begin(); it!=dates.end(); ++it) + for (set::iterator it = dates.begin(); it != dates.end(); ++it) filterDate.push_back(gdi.widen(*it)); gdi.dropLine(2); @@ -411,11 +411,11 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) vector< pair > d; oe->fillControlTypes(d); gdi.addItem("ControlType", d); - // oe->fillControlTypes(gdi, "ControlType"); + // oe->fillControlTypes(gdi, "ControlType"); gdi.selectItemByData("ControlType", oPunch::PunchCheck); gdi.addSelection("Filter", 150, 300, 0, L"Datumfilter:"); - for (size_t k = 0; k0; + bool dofilter = signed(lbi.data) > 0; string filter = lbi.data < filterDate.size() ? gdi.narrow(filterDate[lbi.data]) : ""; gdi.restore("Help"); - for (size_t k=0;kaddFreePunch(punches[k].time, type, punches[k].card, true); } punches.clear(); - if (origin==1) { + if (origin == 1) { TabRunner &tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); tc.showInForestList(gdi); } } - else if (bi.id=="SaveCards") { + else if (bi.id == "SaveCards") { int origin = bi.getExtraInt(); gdi.restore("Help"); - oe->synchronizeList(oLCardId, true, false); - oe->synchronizeList(oLRunnerId, false, true); - for (size_t k=0;ksynchronizeList({ oListId::oLCardId, oListId::oLRunnerId }); + for (size_t k = 0; k < cards.size(); k++) insertSICard(gdi, cards[k]); oe->reEvaluateAll(set(), true); cards.clear(); - if (origin==1) { + if (origin == 1) { TabRunner &tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); tc.showInForestList(gdi); } } - else if (bi.id=="Save") { + else if (bi.id == "Save") { SICard sic(ConvertedTimeStatus::Hour24); sic.CheckPunch.Code = -1; - sic.CardNumber=gdi.getTextNo("SI"); + sic.CardNumber = gdi.getTextNo("SI"); int f = convertAbsoluteTimeHMS(gdi.getText("Finish"), oe->getZeroTimeNum()); int s = convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum()); if (f < s) { f += 24 * 3600; } - sic.FinishPunch.Time= f % (24*3600); - sic.StartPunch.Time = s % (24*3600); + sic.FinishPunch.Time = f % (24 * 3600); + sic.StartPunch.Time = s % (24 * 3600); if (!gdi.isChecked("HasFinish")) { sic.FinishPunch.Code = -1; sic.FinishPunch.Time = 0; @@ -511,11 +510,11 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) double t = 0.1; for (sic.nPunch = 0; sic.nPunchaddCard(sic); } - else if (bi.id=="SaveP") { + else if (bi.id == "SaveP") { SICard sic(ConvertedTimeStatus::Hour24); sic.clear(0); sic.FinishPunch.Code = -1; sic.CheckPunch.Code = -1; sic.StartPunch.Code = -1; - sic.CardNumber=gdi.getTextNo("SI"); - int f=convertAbsoluteTimeHMS(gdi.getText("Finish"), oe->getZeroTimeNum()); + sic.CardNumber = gdi.getTextNo("SI"); + int f = convertAbsoluteTimeHMS(gdi.getText("Finish"), oe->getZeroTimeNum()); if (f > 0) { sic.FinishPunch.Time = f; sic.FinishPunch.Code = 1; @@ -549,7 +548,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) return 0; } - int s=convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum()); + int s = convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum()); if (s > 0) { sic.StartPunch.Time = s; sic.StartPunch.Code = 1; @@ -558,17 +557,17 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) return 0; } - sic.Punch[sic.nPunch].Code=gdi.getTextNo("C1"); - sic.Punch[sic.nPunch].Time=convertAbsoluteTimeHMS(gdi.getText("C2"), oe->getZeroTimeNum()); + sic.Punch[sic.nPunch].Code = gdi.getTextNo("C1"); + sic.Punch[sic.nPunch].Time = convertAbsoluteTimeHMS(gdi.getText("C2"), oe->getZeroTimeNum()); sic.nPunch = 1; sic.punchOnly = true; gSI->addCard(sic); } - else if (bi.id=="Cancel") { + else if (bi.id == "Cancel") { int origin = bi.getExtraInt(); activeSIC.clear(0); punches.clear(); - if (origin==1) { + if (origin == 1) { TabRunner &tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); tc.showInForestList(gdi); return 0; @@ -578,20 +577,20 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) checkMoreCardsInQueue(gdi); return 0; } - else if (bi.id=="OK1") { - wstring name=gdi.getText("Runners"); - wstring club=gdi.getText("Club", true); + else if (bi.id == "OK1") { + wstring name = gdi.getText("Runners"); + wstring club = gdi.getText("Club", true); - if (name.length()==0){ + if (name.length() == 0) { gdi.alert("Alla deltagare måste ha ett namn."); return 0; } - pRunner r=0; + pRunner r = 0; DWORD rid; bool lookup = true; - if (gdi.getData("RunnerId", rid) && rid>0) { + if (gdi.getData("RunnerId", rid) && rid > 0) { r = gEvent->getRunner(rid, 0); if (r && r->getCard()) { @@ -634,30 +633,30 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.fillRight(); gdi.pushX(); - SICard si_copy=activeSIC; + SICard si_copy = activeSIC; gEvent->convertTimes(nullptr, si_copy); //Find matching class... vector classes; int dist = gEvent->findBestClass(activeSIC, classes); - if (classes.size()==1 && dist == 0 && si_copy.StartPunch.Time>0 && classes[0]->getType()!=L"tmp") { + if (classes.size() == 1 && dist == 0 && si_copy.StartPunch.Time > 0 && classes[0]->getType() != L"tmp") { //We have a match! wstring club = gdi.getText("Club", true); - if (club.length()==0 && oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { + if (club.length() == 0 && oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { pClub noClub = oe->getClub(oe->getVacantClub(true)); if (noClub) { noClub->synchronize(); club = noClub->getName(); } else - club=lang.tl("Klubblös"); + club = lang.tl("Klubblös"); } int year = 0; - pRunner r=gEvent->addRunner(gdi.getText("Runners"), club, - classes[0]->getId(), activeSIC.CardNumber, year, true); + pRunner r = gEvent->addRunner(gdi.getText("Runners"), club, + classes[0]->getId(), activeSIC.CardNumber, year, true); gdi.setData("RunnerId", r->getId()); @@ -680,7 +679,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gEvent->fillClasses(gdi, "Classes", oEvent::extraNone, oEvent::filterNone); gdi.setInputFocus("Classes"); - if (classes.size()>0) + if (classes.size() > 0) gdi.selectItemByData("Classes", classes[0]->getId()); gdi.dropLine(); @@ -695,21 +694,21 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addButton("NewClass", "Skapa ny klass", SportIdentCB); gdi.popX(); - if (classes.size()>0) + if (classes.size() > 0) gdi.addString("FindMatch", 0, "Press Enter to continue").setColor(colorGreen); gdi.dropLine(); gdi.refresh(); return 0; } - else if (bi.id=="OK2") + else if (bi.id == "OK2") { //New runner in existing class... ListBoxInfo lbi; gdi.getSelectedItem("Classes", lbi); - if (lbi.data==0 || lbi.data==-1) { + if (lbi.data == 0 || lbi.data == -1) { gdi.alert("Du måste välja en klass"); return 0; } @@ -723,8 +722,8 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) club = lang.tl("Klubblös"); int year = 0; - pRunner r=gEvent->addRunner(gdi.getText("Runners"), club, - lbi.data, activeSIC.CardNumber, year, true); + pRunner r = gEvent->addRunner(gdi.getText("Runners"), club, + lbi.data, activeSIC.CardNumber, year, true); r->setStartTimeS(gdi.getText("StartTime")); r->setCardNo(activeSIC.CardNumber, false); @@ -734,14 +733,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) activeSIC.clear(&activeSIC); processCard(gdi, r, copy); } - else if (bi.id=="NewClass") { + else if (bi.id == "NewClass") { gdi.restore("restOK2", false); gdi.popX(); gdi.dropLine(2); gdi.fillRight(); gdi.pushX(); - gdi.addInput("ClassName", gEvent->getAutoClassName(), 10,0, L"Klassnamn:"); + gdi.addInput("ClassName", gEvent->getAutoClassName(), 10, 0, L"Klassnamn:"); gdi.dropLine(); gdi.addButton("Cancel", "Avbryt", SportIdentCB).setCancel(); @@ -751,13 +750,13 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.refresh(); gdi.popX(); } - else if (bi.id=="OK3") { + else if (bi.id == "OK3") { pCourse pc = 0; pClass pclass = 0; if (oe->getNumClasses() == 1 && oe->getClass(1) != 0 && - oe->getClass(1)->getType() == L"tmp" && - oe->getClass(1)->getNumRunners(false, false, false) == 0) { + oe->getClass(1)->getType() == L"tmp" && + oe->getClass(1)->getNumRunners(false, false, false) == 0) { pclass = oe->getClass(1); pclass->setType(L""); pclass->setName(gdi.getText("ClassName")); @@ -766,19 +765,20 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) pc->setName(gdi.getText("ClassName")); } - if (pc == 0) { - pc=gEvent->addCourse(gdi.getText("ClassName")); - for(unsigned i=0;igetMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { + pc = gEvent->addCourse(gdi.getText("ClassName")); + for (unsigned i = 0; i < activeSIC.nPunch; i++) pc->addControl(activeSIC.Punch[i].Code); } if (pclass == 0) { - pclass=gEvent->addClass(gdi.getText("ClassName"), pc->getId()); + pclass = gEvent->addClass(gdi.getText("ClassName"), pc ? pc->getId(): 0); } - else + else if (pc) pclass->setCourse(pc); + int year = 0; - pRunner r=gEvent->addRunner(gdi.getText("Runners"), gdi.getText("Club", true), - pclass->getId(), activeSIC.CardNumber, year, true); + pRunner r = gEvent->addRunner(gdi.getText("Runners"), gdi.getText("Club", true), + pclass->getId(), activeSIC.CardNumber, year, true); r->setStartTimeS(gdi.getText("StartTime")); r->setCardNo(activeSIC.CardNumber, false); @@ -787,13 +787,13 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) activeSIC.clear(&activeSIC); processCard(gdi, r, copy_sic); } - else if (bi.id=="OK4") { + else if (bi.id == "OK4") { //Existing runner in existing class... ListBoxInfo lbi; gdi.getSelectedItem("Classes", lbi); - if (lbi.data==0 || lbi.data==-1) + if (lbi.data == 0 || lbi.data == -1) { gdi.alert("Du måste välja en klass"); return 0; @@ -802,7 +802,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) DWORD rid; pRunner r; - if (gdi.getData("RunnerId", rid) && rid>0) + if (gdi.getData("RunnerId", rid) && rid > 0) r = gEvent->getRunner(rid, 0); else r = gEvent->addRunner(lang.tl(L"Oparad bricka"), lang.tl("Okänd"), 0, 0, 0, false); @@ -813,12 +813,11 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) activeSIC.clear(&activeSIC); processCard(gdi, r, copy); } - else if (bi.id=="EntryOK") { + else if (bi.id == "EntryOK") { storedInfo.clear(); - oe->synchronizeList(oLRunnerId, true, false); - oe->synchronizeList(oLCardId, false, true); + oe->synchronizeList({ oListId::oLRunnerId, oListId::oLCardId }); - wstring name=gdi.getText("Name"); + wstring name = gdi.getText("Name"); if (name.empty()) { gdi.alert("Alla deltagare måste ha ett namn."); return 0; @@ -827,29 +826,46 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) pRunner r = oe->getRunner(rid, 0); int cardNo = gdi.getTextNo("CardNo"); - pRunner cardRunner = oe->getRunnerByCardNo(cardNo, 0, true); - if (cardNo>0 && cardRunner!=0 && cardRunner!=r) { + pRunner cardRunner = oe->getRunnerByCardNo(cardNo, 0, oEvent::CardLookupProperty::ForReadout); + if (cardNo > 0 && cardRunner != 0 && cardRunner != r) { gdi.alert(L"Bricknummret är upptaget (X).#" + cardRunner->getName() + L", " + cardRunner->getClass(true)); return 0; } ListBoxInfo lbi; gdi.getSelectedItem("Class", lbi); - - if (signed(lbi.data)<=0) { + pClass clz = oe->getClass(lbi.data); + if (!clz) { if (oe->getNumClasses() > 0) { gdi.alert(L"Ingen klass vald"); return 0; } - pClass pc = oe->getClassCreate(0, lang.tl(L"Öppen klass")); - lbi.data = pc->getId(); - pc->setAllowQuickEntry(true); - pc->synchronize(); + set dmy; + clz = oe->getClassCreate(0, lang.tl(L"Öppen klass"), dmy); + lbi.data = clz->getId(); + clz->setAllowQuickEntry(true); + clz->synchronize(); } bool updated = false; int year = 0; + bool warnClassFull = false; + if (!r || r->getClassRef(false) != clz) { + int numRemMaps = clz->getNumRemainingMaps(true); + if (numRemMaps != numeric_limits::min()) { + if (clz->getNumRemainingMaps(true) > 0) + warnedClassOutOfMaps.erase(clz->getId()); + else { + warnClassFull = true; + if (!warnedClassOutOfMaps.count(clz->getId())) { + warnedClassOutOfMaps.insert(clz->getId()); + if (!gdi.ask(L"ask:outofmaps")) + return 0; + } + } + } + } - if (r==0) { + if (r == 0) { r = oe->addRunner(name, gdi.getText("Club", true), lbi.data, cardNo, year, true); r->setCardNo(0, false, false); // Clear to match below } @@ -871,37 +887,46 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) updated = true; } - lastClubId=r->getClubId(); - lastClassId=r->getClassId(true); + lastClubId = r->getClubId(); + lastClassId = r->getClassId(true); lastFee = gdi.getText("Fee", true); int lastFeeNum = oe->interpretCurrency(lastFee); r->setCardNo(cardNo, true);//XXX - oDataInterface di=r->getDI(); + oDataInterface di = r->getDI(); - int cardFee = gdi.isChecked("RentCard") ? oe->getDI().getInt("CardFee") : 0; + int cardFee = gdi.isChecked("RentCard") ? oe->getBaseCardFee() : 0; + di.setInt("CardFee", cardFee); di.setInt("Fee", lastFeeNum); r->setFlag(oRunner::FlagFeeSpecified, true); - + int totFee = lastFeeNum + (cardFee > 0 ? cardFee : 0); writePayMode(gdi, totFee, *r); di.setString("Phone", gdi.getText("Phone")); + r->setFlag(oRunner::FlagTransferSpecified, gdi.hasField("AllStages")); r->setFlag(oRunner::FlagTransferNew, gdi.isChecked("AllStages")); - + r->setStartTimeS(gdi.getText("StartTime")); + wstring bibIn = gdi.getText("Bib"); wstring bib; - switch (r->autoAssignBib()) { - case oRunner::BibAssignResult::Assigned: + if (bibIn.empty()) { + switch (r->autoAssignBib()) { + case oRunner::BibAssignResult::Assigned: + bib = L", " + lang.tl(L"Nummerlapp: ") + r->getBib(); + break; + case oRunner::BibAssignResult::Failed: + bib = L", " + lang.tl(L"Ingen nummerlapp"); + break; + } + } + else { + r->setBib(bibIn, 0, false, false); bib = L", " + lang.tl(L"Nummerlapp: ") + r->getBib(); - break; - case oRunner::BibAssignResult::Failed: - bib = L", " + lang.tl(L"Ingen nummerlapp"); - break; } r->synchronize(); @@ -910,7 +935,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) wchar_t bf[256]; if (r->getClubId() != 0) { swprintf_s(bf, L"(%d), %s, %s", r->getCardNo(), r->getClub().c_str(), - r->getClass(true).c_str()); + r->getClass(true).c_str()); } else { swprintf_s(bf, L"(%d), %s", r->getCardNo(), r->getClass(true).c_str()); @@ -918,23 +943,23 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) wstring info(bf); if (r->getDI().getInt("CardFee") != 0) - info+=lang.tl(L", Hyrbricka"); + info += lang.tl(L", Hyrbricka"); vector< pair > modes; oe->getPayModes(modes); wstring pm; - if (modes.size() > 1 && size_t(r->getPaymentMode()) < modes.size()) + if (modes.size() > 1 && size_t(r->getPaymentMode()) < modes.size()) pm = L" (" + modes[r->getPaymentMode()].first + L")"; - if (r->getDI().getInt("Paid")>0) + if (r->getDI().getInt("Paid") > 0) info += lang.tl(L", Betalat") + pm; bool warnPayment = r->getDI().getInt("Paid") < totFee && ( - r->getClubRef() == 0 || - r->getClubId() == oe->getVacantClubIfExist(true) || - r->getClubId() == oe->getVacantClubIfExist(false)); + r->getClubRef() == 0 || + r->getClubId() == oe->getVacantClubIfExist(true) || + r->getClubId() == oe->getVacantClubIfExist(false)); - if (bib.length()>0) - info+=bib; + if (bib.length() > 0) + info += bib; if (updated) info += lang.tl(L" [Uppdaterad anmälan]"); @@ -949,18 +974,21 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (warnPayment) { gdi.addString("", fontMediumPlus, "Varning: avgiften kan ej faktureras").setColor(colorRed); } + if (warnClassFull) { + gdi.addString("", fontMediumPlus, "Varning: Kartorna är slut").setColor(colorRed); + } generateStartInfo(gdi, *r); gdi.setRestorePoint("EntryLine"); generateEntryLine(gdi, 0); } - else if (bi.id=="EntryCancel") { + else if (bi.id == "EntryCancel") { gdi.restore("EntryLine"); storedInfo.clear(); generateEntryLine(gdi, 0); } - else if (bi.id=="RentCard" || bi.id=="Paid" || bi.id == "AllStages") { + else if (bi.id == "RentCard" || bi.id == "Paid" || bi.id == "AllStages") { updateEntryInfo(gdi); } else if (bi.id == "ManualOK") { @@ -978,7 +1006,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) bool dnf = gdi.isChecked("StatusDNF"); pRunner r = oe->getRunner(runnerMatchedId, 0); - if (r==0) + if (r == 0) throw meosException("Löparen hittades inte"); if (r->getStatus() != StatusUnknown) { @@ -999,7 +1027,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) else if (bi.id == "StatusOK") { bool ok = gdi.isChecked(bi.id); if (ok) { - gdi.check("StatusDNF", false); + gdi.check("StatusDNF", false); } } else if (bi.id == "StatusDNF") { @@ -1026,7 +1054,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) //gdi.print(oe); gdioutput gdiPrint("print", gdi.getScale()); gdiPrint.clearPage(false); - + int tCardPosX = cardPosX; int tCardPosY = cardPosY; int tCardOffsetX = cardOffsetX; @@ -1035,7 +1063,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) showCheckCardStatus(gdiPrint, "stat"); showCheckCardStatus(gdiPrint, "report"); showCheckCardStatus(gdiPrint, "tickoff"); - + cardPosX = tCardPosX; cardPosY = tCardPosY; cardOffsetX = tCardOffsetX; @@ -1270,7 +1298,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (nr > 0) { r = oe->getRunnerByBibOrStartNo(text, false); if (r == 0) - r = oe->getRunnerByCardNo(nr, 0, true, true); + r = oe->getRunnerByCardNo(nr, 0, oEvent::CardLookupProperty::ForReadout); } if (nr == 0 && text.size() > 2) { @@ -1324,7 +1352,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.setInputStatus("TieOK", runnerMatchedId != -1); } else if (ii.id == "SI") { - pRunner r = oe->getRunnerByCardNo(_wtoi(ii.text.c_str()), 0, true, false); + pRunner r = oe->getRunnerByCardNo(_wtoi(ii.text.c_str()), 0, oEvent::CardLookupProperty::ForReadout); if (r && r->getStartTime() > 0) { gdi.setText("Start", r->getStartTimeS()); gdi.check("HasStart", false); @@ -1371,10 +1399,11 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) pRunner r=oe->getRunner(ii.getExtraInt(), 0); r->synchronize(); - if (r && r->getCardNo()!=si) { - if (si==0 || !oe->checkCardUsed(gdi,*r, si)) { + if (r && r->getCardNo() != si) { + if (si == 0 || !oe->checkCardUsed(gdi, *r, si)) { r->setCardNo(si, false); - r->getDI().setInt("CardFee", oe->getDI().getInt("CardFee")); + + r->getDI().setInt("CardFee", oe->getBaseCardFee()); r->synchronize(); } @@ -1470,7 +1499,7 @@ void TabSI::showReadPunches(gdioutput &gdi, vector &punches, setgetRunnerByCardNo(punches[k].card, punches[k].time); + pRunner r = oe->getRunnerByCardNo(punches[k].card, punches[k].time, oEvent::CardLookupProperty::Any); sprintf_s(bf, "%d", punches[k].card); gdi.addStringUT(yp, xp+40, 0, bf, 240); @@ -1499,7 +1528,7 @@ void TabSI::showReadCards(gdioutput &gdi, vector &cards) sprintf_s(bf, "%d.", k+1); gdi.addStringUT(yp, xp, 0, bf); - pRunner r = oe->getRunnerByCardNo(cards[k].CardNumber, 0); + pRunner r = oe->getRunnerByCardNo(cards[k].CardNumber, 0, oEvent::CardLookupProperty::Any); sprintf_s(bf, "%d", cards[k].CardNumber); gdi.addStringUT(yp, xp+40, 0, bf, 240); @@ -1836,8 +1865,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } return; } - gEvent->synchronizeList(oLCardId, true, false); - gEvent->synchronizeList(oLRunnerId, false, true); + gEvent->synchronizeList({ oListId::oLCardId, oListId::oLRunnerId }); if (sic.punchOnly) { processPunchOnly(gdi, sic); @@ -1845,7 +1873,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } pRunner r; if (sic.runnerId == 0) - r = gEvent->getRunnerByCardNo(sic.CardNumber, 0, false); + r = gEvent->getRunnerByCardNo(sic.CardNumber, 0, oEvent::CardLookupProperty::ForReadout); else { r = gEvent->getRunner(sic.runnerId, 0); sic.CardNumber = r->getCardNo(); @@ -1923,7 +1951,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) if (!gdi.ask(bf)) { if (printSplits) { - pRunner runner = oe->getRunnerByCardNo(sic.CardNumber, 0); + pRunner runner = getRunnerForCardSplitPrint(sic); if (runner) generateSplits(runner, gdi); } @@ -1936,7 +1964,8 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } else { if (printSplits) { - pRunner runner = oe->getRunnerByCardNo(sic.CardNumber, 0); + pRunner runner = getRunnerForCardSplitPrint(sic); + if (runner) generateSplits(runner, gdi); } @@ -1955,7 +1984,10 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) pRunner db_r = 0; if (sic.runnerId == 0) { - r = gEvent->getRunnerByCardNo(sic.CardNumber, 0, !readBefore); + if (!readBefore) + r = gEvent->getRunnerByCardNo(sic.CardNumber, 0, oEvent::CardLookupProperty::ForReadout); + else + r = getRunnerForCardSplitPrint(sic); if (!r && showDatabase()) { //Look up in database. @@ -1966,15 +1998,18 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } // If there is no class, auto create - if (interactiveReadout && oe->getNumClasses()==0) { + if (interactiveReadout && oe->getNumClasses() == 0) { gdi.fillDown(); gdi.dropLine(); gdi.addString("", 1, "Skapar saknad klass").setColor(colorGreen); gdi.dropLine(); - pCourse pc=gEvent->addCourse(lang.tl("Okänd klass")); - for(unsigned i=0;iaddControl(sic.Punch[i].Code); - gEvent->addClass(lang.tl(L"Okänd klass"), pc->getId())->setType(L"tmp"); + pCourse pc = nullptr; + if (!oe->getMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { + pc = gEvent->addCourse(lang.tl("Okänd klass")); + for (unsigned i = 0; i < sic.nPunch; i++) + pc->addControl(sic.Punch[i].Code); + } + gEvent->addClass(lang.tl(L"Okänd klass"), pc ? pc->getId() : 0)->setType(L"tmp"); } // Assign a class if not already done @@ -1999,6 +2034,28 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } } +pRunner TabSI::getRunnerForCardSplitPrint(const SICard &sic) const { + pRunner runner = 0; + vector out; + oe->getRunnersByCardNo(sic.CardNumber, false, oEvent::CardLookupProperty::SkipNoStart, out); + for (pRunner r : out) { + if (!r->getCard()) + continue; + if (runner == 0) + runner = r; + else { + if (runner->getFinishTime() < r->getFinishTime()) + runner = r; // Take the last finisher +// int nPunchBest = runner->getCard()->getNumControlPunches(oPunch::PunchStart, oPunch::PunchFinish); +// int nPunchCurrent = r->getCard()->getNumControlPunches(oPunch::PunchStart, oPunch::PunchFinish); + +// if (abs(int(nPunchCurrent - activeSIC.nPunch)) < abs(int(nPunchBest - activeSIC.nPunch))) + // runner = r; + } + } + return runner; +} + void TabSI::startInteractive(gdioutput &gdi, const SICard &sic, pRunner r, pRunner db_r) { if (!r) { @@ -2093,7 +2150,7 @@ void TabSI::processInsertCard(const SICard &sic) if (oe->isCardRead(sic)) return; - pRunner runner = oe->getRunnerByCardNo(sic.CardNumber, 0, true); + pRunner runner = oe->getRunnerByCardNo(sic.CardNumber, 0, oEvent::CardLookupProperty::ForReadout); pCard card = oe->allocateCard(runner); card->setReadId(sic); card->setCardNo(sic.CardNumber); @@ -2229,7 +2286,7 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool } pClass pclass = runner->getClassRef(true); - if (!runner->getCourse(false) && !csic.isManualInput()) { + if (!runner->getCourse(false) && !csic.isManualInput() && !oe->getMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { if (pclass && !pclass->hasMultiCourse() && !pclass->hasDirectResult()) { pCourse pcourse=gEvent->addCourse(pclass->getName()); @@ -2353,7 +2410,10 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool rc.bottom+=gdi.getLineHeight(); if (runner->getStatus()==StatusOK) { - gEvent->calculateResults(oEvent::RTClassResult); + set clsSet; + if (runner->getClassId(false)) + clsSet.insert(runner->getClassId(true)); + gEvent->calculateResults(clsSet, oEvent::ResultType::ClassResult); if (runner->getTeam()) gEvent->calculateTeamResults(runner->getLegNumber(), false); bool qfClass = runner->getClassId(false) != runner->getClassId(true); @@ -2391,7 +2451,7 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool } } else { - wstring msg=lang.tl(L"Status: ") + lang.tl(runner->getStatusS()); + wstring msg=lang.tl(L"Status: ") + runner->getStatusS(true); if (!MP.empty()) { msg=msg + L", ("; @@ -2507,7 +2567,7 @@ void TabSI::entryCard(gdioutput &gdi, const SICard &sic) name=wstring(sic.lastName) + L", " + wstring(sic.firstName); gdi.setText("Name", name); - if (gdi.hasField("Club")) + if (gdi.hasField("Club") && !club.empty()) gdi.setText("Club", club); if (club.empty() && gdi.hasField("Club")) @@ -2567,11 +2627,11 @@ void TabSI::assignCard(gdioutput &gdi, const SICard &sic) currentAssignIndex = storedAssigneIndex; return; } - if (r->getCardNo()==0 || - gdi.ask(L"Skriv över existerande bricknummer?")) { + if (r->getCardNo() == 0 || + gdi.ask(L"Skriv över existerande bricknummer?")) { r->setCardNo(sic.CardNumber, false); - r->getDI().setInt("CardFee", oe->getDI().getInt("CardFee")); + r->getDI().setInt("CardFee", oe->getBaseCardFee()); r->synchronize(); gdi.setText(ii->id, sicode); } @@ -2582,10 +2642,8 @@ void TabSI::assignCard(gdioutput &gdi, const SICard &sic) checkMoreCardsInQueue(gdi); } -void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) -{ - oe->synchronizeList(oLRunnerId, true, false); - oe->synchronizeList(oLCardId, false, true); +void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { + oe->synchronizeList({ oListId::oLRunnerId, oListId::oLCardId }); gdi.restore("EntryLine", false); gdi.setRestorePoint("EntryLine"); @@ -2651,16 +2709,22 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) gdi.dropLine(-0.2); gdi.addInput("StartTime", storedInfo.storedStartTime, 5, 0, L""); - gdi.setCX(gdi.getCX()+gdi.scaleLength(50)); + gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); gdi.dropLine(0.2); - gdi.setCX(gdi.getCX()+gdi.scaleLength(5)); + gdi.addString("", 0, "Nummerlapp:"); + gdi.dropLine(-0.2); + gdi.addInput("Bib", L"", 5, 0, L""); + + gdi.setCX(gdi.getCX()+gdi.scaleLength(20)); + gdi.dropLine(0.2); + gdi.addString("", 0, "Telefon:"); gdi.dropLine(-0.2); gdi.addInput("Phone", storedInfo.storedPhone, 12, 0, L""); gdi.dropLine(0.2); - gdi.setCX(gdi.getCX()+gdi.scaleLength(50)); + gdi.setCX(gdi.getCX()+gdi.scaleLength(20)); gdi.addCheckbox("RentCard", "Hyrbricka", SportIdentCB, storedInfo.rentState); if (oe->hasNextStage()) @@ -2681,6 +2745,7 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) gdi.setText("Fee", oe->formatCurrency(dci.getInt("Fee"))); gdi.setText("Phone", dci.getString("Phone")); + gdi.setText("Bib", r->getBib()); gdi.check("RentCard", dci.getInt("CardFee") != 0); if (gdi.hasField("Paid")) @@ -2876,7 +2941,8 @@ void TabSI::tieCard(gdioutput &gdi) { bool rent = gdi.isChecked("RentCardTie"); r->setCardNo(card, true, false); - r->getDI().setInt("CardFee", rent ? oe->getDI().getInt("CardFee") : 0); + + r->getDI().setInt("CardFee", rent ? oe->getBaseCardFee() : 0); r->synchronize(true); gdi.restore("ManualTie"); @@ -3373,9 +3439,17 @@ void TabSI::StoredStartInfo::clear() { } void TabSI::clearCompetitionData() { + printSplits = false; + interactiveReadout = oe->getPropertyInt("Interactive", 1) != 0; + useDatabase = oe->getPropertyInt("Database", 1) != 0; + printSplits = false; + printStartInfo = false; + manualInput = oe->getPropertyInt("ManualInput", 0) == 1; + savedCardUniqueId = 1; checkedCardFlags.clear(); currentAssignIndex = 0; + warnedClassOutOfMaps.clear(); } SICard &TabSI::getCard(int id) const { @@ -3529,7 +3603,7 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { wstring cp = r[k]->getCompleteIdentification(); if (r[k]->getStatus() != StatusUnknown) - cp += L" " + r[k]->getStatusS(); + cp += L" " + r[k]->getStatusS(true); else cp += makeDash(L" -"); diff --git a/code/TabSI.h b/code/TabSI.h index e8efacf..f67cf38 100644 --- a/code/TabSI.h +++ b/code/TabSI.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -71,6 +71,8 @@ private: vector cards; vector filterDate; + set warnedClassOutOfMaps; + int runnerMatchedId; bool printErrorShown; void printProtected(gdioutput &gdi, gdioutput &gdiprint); @@ -132,7 +134,6 @@ private: // Insert card without converting times and with/without runner void processInsertCard(const SICard &csic); - void generateSplits(const pRunner r, gdioutput &gdi); int logcounter; csvparser *logger; @@ -141,6 +142,8 @@ private: void insertSICardAux(gdioutput &gdi, SICard &sic); + pRunner getRunnerForCardSplitPrint(const SICard &sic) const; + // Ask if card is to be overwritten bool askOverwriteCard(gdioutput &gdi, pRunner r) const; @@ -246,7 +249,7 @@ public: TabType getType() const {return TSITab;} void insertSICard(gdioutput &gdi, SICard &sic); - + void clearQueue() { CardQueue.clear(); } void refillComPorts(gdioutput &gdi); bool loadPage(gdioutput &gdi); diff --git a/code/TabSpeaker.cpp b/code/TabSpeaker.cpp index 9820b00..26f5a78 100644 --- a/code/TabSpeaker.cpp +++ b/code/TabSpeaker.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,7 @@ #include "TabSpeaker.h" #include "TabList.h" +#include "TabRunner.h" #include "speakermonitor.h" #include "meosexception.h" @@ -100,6 +101,34 @@ int TabSpeaker::handleEvent(gdioutput &gdi, const EventInfo &ei) return 0; } +namespace { + class ReportMode : public GuiHandler { + void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) final { + TabBase *tb = gdi.getTabs().get(TabType::TSpeakerTab); + TabSpeaker *list = dynamic_cast(tb); + if (list) { + auto oe = list->getEvent(); + if (type == GUI_INPUTCHANGE) { + InputInfo &ii = dynamic_cast(info); + int nr = _wtoi(ii.text.c_str()); + if (nr > 0) { + pRunner r = oe->getRunnerByBibOrStartNo(ii.text, false); + if (r) { + list->setSelectedRunner(*r); + gdi.sendCtrlMessage("Report"); + } + } + + } + } + } + public: + virtual ~ReportMode() {} + }; + + ReportMode reportHandler; +} + int TabSpeaker::processButton(gdioutput &gdi, const ButtonInfo &bu) { if (bu.id=="Settings") { @@ -188,6 +217,37 @@ int TabSpeaker::processButton(gdioutput &gdi, const ButtonInfo &bu) gdi.addTable(oe->getPunchesTB(), gdi.getCX(), gdi.getCY()); gdi.refresh(); } + else if (bu.id == "Report") { + classId = -2; + if (gdi.hasData("ReportMode")) { + gdi.restore("ReportMode", false); + } + else { + gdi.restore("speaker", false); + gdi.pushX(); + gdi.fillRight(); + gdi.addSelection("ReportRunner", 300, 300, tabSpeakerCB); + gdi.dropLine(0.2); + gdi.addString("", 0, "Nummerlapp:"); + gdi.dropLine(-0.2); + gdi.addInput("FindRunner", L"", 6, 0, L"", L"Nummerlapp").setHandler(&reportHandler); + gdi.setRestorePoint("ReportMode"); + } + + gdi.setData("ReportMode", 1); + oe->fillRunners(gdi, "ReportRunner", true, oEvent::RunnerFilterShowAll | oEvent::RunnerCompactMode); + gdi.selectItemByData("ReportRunner", runnerId); + + gdi.dropLine(3); + gdi.popX(); + gdi.registerEvent("DataUpdate", tabSpeakerCB); + vector> runnersToReport; + if (runnerId > 0) { + runnersToReport.emplace_back(runnerId, false); + } + TabRunner::generateRunnerReport(*oe, gdi, runnersToReport); + gdi.refresh(); + } else if (bu.id == "Priority") { gdi.clearPage(false); gdi.addString("", boldLarge, "Bevakningsprioritering"); @@ -424,7 +484,7 @@ void TabSpeaker::drawTimeLine(gdioutput &gdi) { } void TabSpeaker::updateTimeLine(gdioutput &gdi) { - int storedY = gdi.GetOffsetY(); + int storedY = gdi.getOffsetY(); int storedHeight = gdi.getHeight(); bool refresh = gdi.hasData("TimeLineLoaded"); @@ -442,7 +502,7 @@ void TabSpeaker::updateTimeLine(gdioutput &gdi) { gdi.pushX(); gdi.pushY(); gdi.updatePos(0,0,0, storedHeight); gdi.popX(); gdi.popY(); - gdi.SetOffsetY(storedY); + gdi.setOffsetY(storedY); SpeakerMonitor &sm = *getSpeakerMonitor(); int limit = 1000; @@ -864,14 +924,14 @@ int TabSpeaker::deducePreviousControl(int classId, int leg, int control) { int TabSpeaker::processListBox(gdioutput &gdi, const ListBoxInfo &bu) { - if (bu.id=="Leg") { - if (classId>0) { - selectedControl[classId].setLeg(bu.data>=1000, bu.data%1000); + if (bu.id == "Leg") { + if (classId > 0) { + selectedControl[classId].setLeg(bu.data >= 1000, bu.data % 1000); generateControlList(gdi, classId); gdi.setRestorePoint("speaker"); gdi.setRestorePoint("SpeakerList"); - bool shortNames = oe->getPropertyInt("SpeakerShortNames", false) != 0; + bool shortNames = oe->getPropertyInt("SpeakerShortNames", false) != 0; oe->speakerList(gdi, classId, selectedControl[classId].getLeg(), selectedControl[classId].getControl(), selectedControl[classId].getPreviousControl(), @@ -899,11 +959,14 @@ int TabSpeaker::processListBox(gdioutput &gdi, const ListBoxInfo &bu) int classId = int(bu.data); loadPriorityClass(gdi, classId); } + else if (bu.id == "ReportRunner") { + runnerId = bu.data; + gdi.sendCtrlMessage("Report"); + } return 0; } -bool TabSpeaker::loadPage(gdioutput &gdi) -{ +bool TabSpeaker::loadPage(gdioutput &gdi) { oe->checkDB(); gdi.clearPage(false); @@ -925,24 +988,31 @@ bool TabSpeaker::loadPage(gdioutput &gdi) int cx=basex; int cy=basey; int cb=1; - for (set::iterator it=classesToWatch.begin();it!=classesToWatch.end();++it) { - char classid[32]; - sprintf_s(classid, "cid%d", *it); - pClass pc=oe->getClass(*it); - + vector clsToWatch; + for (int cid : classesToWatch) { + pClass pc = oe->getClass(cid); if (pc) { - gdi.addButton(cx, cy, bw, classid, L"#" + pc->getName(), tabSpeakerCB, L"", false, false); - cx+=bw; - cb++; - - if (cb>nbtn) { - cb=1; - cx=basex; - cy+=gdi.getButtonHeight()+4; - } + clsToWatch.push_back(pc); } } + sort(clsToWatch.begin(), clsToWatch.end(), [](const pClass &a, const pClass &b) {return *a < *b; }); + + for (auto pc : clsToWatch) { + char classid[32]; + sprintf_s(classid, "cid%d", pc->getId()); + gdi.addButton(cx, cy, bw, classid, L"#" + pc->getName(), tabSpeakerCB, L"", false, false); + cx+=bw; + cb++; + + if (cb>nbtn) { + cb=1; + cx=basex; + cy+=gdi.getButtonHeight()+4; + } + } + + bool pm = false; int db = 0; if (classesToWatch.empty()) { gdi.addString("", boldLarge, "Speakerstöd"); @@ -956,11 +1026,23 @@ bool TabSpeaker::loadPage(gdioutput &gdi) cb = 1, cx = basex, db = 0; cy += gdi.getButtonHeight()+4; } else db += bw; - gdi.addButton(cx+db, cy, bw/5, "ZoomIn", "+", tabSpeakerCB, "Zooma in (Ctrl + '+')", false, false); - db += bw/5+2; - gdi.addButton(cx+db, cy, bw/5, "ZoomOut", makeDash(L"-"), tabSpeakerCB, L"Zooma ut (Ctrl + '-')", false, false); - db += bw/5+2; + pm = true; } + + gdi.addButton(cx + db, cy, bw - 2, "Report", "Rapportläge", tabSpeakerCB, "Visa detaljerad rapport för viss deltagare", false, false); + if (++cb>nbtn) { + cb = 1, cx = basex, db = 0; + cy += gdi.getButtonHeight() + 4; + } + else db += bw; + + if (pm) { + gdi.addButton(cx + db, cy, bw / 5, "ZoomIn", "+", tabSpeakerCB, "Zooma in (Ctrl + '+')", false, false); + db += bw / 5 + 2; + gdi.addButton(cx + db, cy, bw / 5, "ZoomOut", makeDash(L"-"), tabSpeakerCB, L"Zooma ut (Ctrl + '-')", false, false); + db += bw / 5 + 2; + } + gdi.addButton(cx+db, cy, bw-2, "Settings", "Inställningar...", tabSpeakerCB, "Välj vilka klasser och kontroller som bevakas", false, false); if (++cb>nbtn) { cb = 1, cx = basex, db = 0; @@ -978,13 +1060,12 @@ bool TabSpeaker::loadPage(gdioutput &gdi) cy += gdi.getButtonHeight()+4; } else db += bw; - gdi.addButton(cx+db, cy, bw-2, "LiveResult", "Liveresultat", tabSpeakerCB, "Visa rullande tider mellan kontroller i helskärmsläge", false, false); + gdi.addButton(cx+db, cy, bw-2, "LiveResult", "Direkt tidtagning", tabSpeakerCB, "Visa rullande tider mellan kontroller i helskärmsläge", false, false); if (++cb>nbtn) { cb = 1, cx = basex, db = 0; cy += gdi.getButtonHeight()+4; } else db += bw; - if (!ownWindow) { gdi.addButton(cx+db, cy, bw-2, "Priority", "Prioritering", tabSpeakerCB, "Välj löpare att prioritera bevakning för", false, false); if (++cb>nbtn) { @@ -1028,6 +1109,11 @@ bool TabSpeaker::loadPage(gdioutput &gdi) if (gdi.hasField(btn)) gdi.sendCtrlMessage(btn); } + else if (classId == -2) { + string btn = "Report"; + if (gdi.hasField(btn)) + gdi.sendCtrlMessage(btn); + } else if (classId > 0) { string btn = "cid" + itos(classId); if (gdi.hasField(btn)) @@ -1054,6 +1140,7 @@ void TabSpeaker::clearCompetitionData() delete speakerMonitor; speakerMonitor = 0; + runnerId = -1; } void TabSpeaker::manualTimePage(gdioutput &gdi) const @@ -1101,7 +1188,7 @@ void TabSpeaker::storeManualTime(gdioutput &gdi) pRunner r=oe->getRunnerByBibOrStartNo(r_str, false); int r_no = _wtoi(r_str.c_str()); if (!r) - r=oe->getRunnerByCardNo(r_no, itime); + r=oe->getRunnerByCardNo(r_no, itime, oEvent::CardLookupProperty::Any); wstring Name; int sino=r_no; @@ -1153,8 +1240,7 @@ void TabSpeaker::loadPriorityClass(gdioutput &gdi, int classId) { } void TabSpeaker::savePriorityClass(gdioutput &gdi) { - oe->synchronizeList(oLRunnerId, true, false); - oe->synchronizeList(oLTeamId, false, true); + oe->synchronizeList({ oListId::oLRunnerId,oListId::oLTeamId }); for (size_t k = 0; kgetRunner(runnersToSet[k], 0); @@ -1223,6 +1309,9 @@ void TabSpeaker::getSettings(gdioutput &gdi, multimap &settings if (classId == -1) { settings.insert(make_pair("currentClass", L"@Events")); } + else if (classId == -2) { + settings.insert(make_pair("currentClass", L"@Report")); + } for (auto ctrl : controlsToWatch) { settings.insert(make_pair("control", itow(ctrl))); @@ -1249,6 +1338,9 @@ void TabSpeaker::importSettings(gdioutput &gdi, multimap &setti if (s.second == L"@Events") { classId = -1; } + else if (s.second == L"@Report") { + classId = -2; + } else { pClass cls = oe->getClass(s.second); classId = cls ? cls->getId() : 0; @@ -1296,7 +1388,7 @@ void TabSpeaker::importSettings(gdioutput &gdi, multimap &setti if (rc.right > rc.left && rc.bottom > rc.top && rc.right > 50 && rc.left < (desktop.right - 50) && rc.bottom > 50 && rc.top < (desktop.bottom - 50)) - gdi.setWindowsPosition(rc); + gdi.setWindowsPosition(rc); } wstring TabSpeaker::getSpeakerSettingsFile() { diff --git a/code/TabSpeaker.h b/code/TabSpeaker.h index 82262ff..433d9bc 100644 --- a/code/TabSpeaker.h +++ b/code/TabSpeaker.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -58,6 +58,9 @@ private: set controlsToWatch; set classesToWatch; + // For runner report + int runnerId = -1; + int lastControlToWatch; int lastClassToWatch; @@ -100,6 +103,8 @@ private: static wstring getSpeakerSettingsFile(); public: + void setSelectedRunner(const oRunner &r) { runnerId = r.getId(); } + bool onClear(gdioutput &gdi); void loadPriorityClass(gdioutput &gdi, int classId); void savePriorityClass(gdioutput &gdi); diff --git a/code/TabTeam.cpp b/code/TabTeam.cpp index 13259e8..76cc3d0 100644 --- a/code/TabTeam.cpp +++ b/code/TabTeam.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -188,7 +188,7 @@ void TabTeam::selectTeam(gdioutput &gdi, pTeam t) gdi.enableInput("Undo"); gdi.enableInput("Remove"); - oe->fillClasses(gdi, "RClass", oEvent::extraNone, oEvent::filterNone); + oe->fillClasses(gdi, "RClass", oEvent::extraNone, oEvent::filterOnlyMulti); gdi.selectItemByData("RClass", t->getClassId(false)); gdi.selectItemByData("Teams", t->getId()); @@ -205,6 +205,10 @@ void TabTeam::selectTeam(gdioutput &gdi, pTeam t) gdi.setText("PointIn", t->getInputPoints()); } + if (gdi.hasField("NoRestart")) { + gdi.check("NoRestart", t->preventRestart()); + } + loadTeamMembers(gdi, 0, 0, t); } else { @@ -320,6 +324,10 @@ bool TabTeam::save(gdioutput &gdi, bool dontReloadTeams) { if (gdi.hasField("Fee")) t->getDI().setInt("Fee", oe->interpretCurrency(gdi.getText("Fee"))); + + if (gdi.hasField("NoRestart")) + t->preventRestart(gdi.isChecked("NoRestart")); + t->apply(false, 0, false); if (gdi.hasField("Club")) { @@ -436,7 +444,7 @@ bool TabTeam::save(gdioutput &gdi, bool dontReloadTeams) { r->setCardNo(cardNo, true); if (gdi.isChecked("RENT" + itos(i))) - r->getDI().setInt("CardFee", oe->getDI().getInt("CardFee")); + r->getDI().setInt("CardFee", oe->getBaseCardFee()); else r->getDI().setInt("CardFee", 0); @@ -617,15 +625,14 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) bool rExists = r != 0; - pRunner old = oe->getRunnerByCardNo(card, 0, true, true); + pRunner old = oe->getRunnerByCardNo(card, 0, oEvent::CardLookupProperty::CardInUse); if (old && r != old) { throw meosException(L"Brickan används av X.#" + old->getName() ); } - pClub clb = 0; if (!rExists) { - pRunner rOrig = oe->getRunnerByCardNo(card, 0, false, false); + pRunner rOrig = oe->getRunnerByCardNo(card, 0, oEvent::CardLookupProperty::Any); if (rOrig) clb = rOrig->getClubRef(); } @@ -636,7 +643,7 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) r = oe->addRunner(name, clb ? clb->getId() : t->getClubId(), t->getClassId(false), card, 0, false); } if (rent) - r->getDI().setInt("CardFee", oe->getDI().getInt("CardFee")); + r->getDI().setInt("CardFee", oe->getBaseCardFee()); t->synchronize(); pRunner oldR = t->getRunner(leg); @@ -653,13 +660,17 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) oldR->synchronize(true); t->setRunner(leg, r, true); t->checkValdParSetup(); + + if (oldR->isAnnonumousTeamMember()) + oe->removeRunner({ oldR->getId() }); + t->synchronize(true); } } else { t->setRunner(leg, r, true); t->checkValdParSetup(); + t->synchronize(true); } - selectTeam(gdi, t); } else if (bi.id == "Browse") { @@ -1017,7 +1028,6 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) pClass pc=oe->getClass(lbi.data); if (pc) { - vector rCache; for(unsigned i=0;igetNumStages();i++){ char bf[16]; sprintf_s(bf, "R%d", i); @@ -1027,7 +1037,7 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) sprintf_s(bf, "SI%d", i); int cno = r->getCardNo(); gdi.setText(bf, cno > 0 ? itow(cno) : L""); - warnDuplicateCard(gdi, bf, cno, r, rCache); + warnDuplicateCard(gdi, bf, cno, r); } } } @@ -1094,14 +1104,14 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) int cno = gdi.getTextNo("DirCard"); if (cno > 0 && gdi.getText("DirName").empty()) { bool matched = false; - pRunner r = oe->getRunnerByCardNo(cno, 0, true, false); + pRunner r = oe->getRunnerByCardNo(cno, 0, oEvent::CardLookupProperty::ForReadout); if (r && (r->getStatus() == StatusUnknown || r->getStatus() == StatusDNS) ) { // Switch to exactly this runner. Has not run before gdi.setText("DirName", r->getName())->setExtra(r->getId()); matched = true; } else { - r = oe->getRunnerByCardNo(cno, 0, false, false); + r = oe->getRunnerByCardNo(cno, 0, oEvent::CardLookupProperty::Any); if (r) { // Copy only the name. gdi.setText("DirName", r->getName())->setExtra(0); @@ -1130,8 +1140,7 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) int cardNo = _wtoi(ii.text.c_str()); pTeam t = oe->getTeam(teamId); if (t) { - vector rc; - warnDuplicateCard(gdi, ii.id, cardNo, t->getRunner(i), rc); + warnDuplicateCard(gdi, ii.id, cardNo, t->getRunner(i)); } break; } @@ -1209,7 +1218,6 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) gdi.addString("", yp, xp + dx[3], 0, "Hyrd:"); gdi.addString("", yp, xp + dx[5], 0, "Status:"); gdi.dropLine(0.5); - vector rCache; for (unsigned i=0;igetNumStages();i++) { yp = gdi.getCY(); @@ -1249,7 +1257,7 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) if (hasSI) { int cno = r->getCardNo(); gdi.setText(bf_si, cno > 0 ? itow(cno) : L""); - warnDuplicateCard(gdi, bf_si, cno, r, rCache); + warnDuplicateCard(gdi, bf_si, cno, r); gdi.check("RENT" + itos(i), r->getDCI().getInt("CardFee") != 0); } string sid = "STATUS"+itos(i); @@ -1259,7 +1267,7 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) ti->setColor(colorGreen); } else if (r->getStatus() != StatusUnknown) { - TextInfo * ti = (TextInfo *)gdi.setText(sid, r->getStatusS() + L", " + r->getRunningTimeS(), false); + TextInfo * ti = (TextInfo *)gdi.setText(sid, r->getStatusS(false) + L", " + r->getRunningTimeS(), false); if (ti) ti->setColor(colorRed); } @@ -1378,7 +1386,7 @@ bool TabTeam::loadPage(gdioutput &gdi) } gdi.addSelection("RClass", 170, 300, TeamCB, L"Klass:"); - oe->fillClasses(gdi, "RClass", oEvent::extraNone, oEvent::filterNone); + oe->fillClasses(gdi, "RClass", oEvent::extraNone, oEvent::filterOnlyMulti); gdi.addItem("RClass", lang.tl("Ny klass"), 0); if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy)) @@ -1431,9 +1439,12 @@ bool TabTeam::loadPage(gdioutput &gdi) gdi.popX(); gdi.selectItemByData("Status", 0); - - gdi.dropLine(1.5); + if (oe->hasAnyRestartTime()) { + gdi.addCheckbox("NoRestart", "Förhindra omstart", 0, false, "Förhindra att laget deltar i någon omstart"); + } + gdi.dropLine(1.5); + const bool multiDay = oe->hasPrevStage(); if (multiDay) { @@ -1491,18 +1502,18 @@ bool TabTeam::loadPage(gdioutput &gdi) rc.left = posXForButtons; rc.top = posYForButtons; - gdi.setCY(posYForButtons + gdi.scaleLength(4)); + gdi.setCY(posYForButtons + gdi.scaleLength(10)); gdi.setCX(posXForButtons + gdi.getLineHeight()); gdi.fillDown(); - gdi.addString("", 1, "Verktyg"); + gdi.addString("", fontMediumPlus, "Verktyg"); gdi.dropLine(0.3); gdi.fillRight(); gdi.addButton("ImportTeams", "Importera laguppställningar", TeamCB); gdi.addButton("AddTeamMembers", "Skapa anonyma lagmedlemmar", TeamCB, "Fyll obesatta sträckor i alla lag med anonyma tillfälliga lagmedlemmar (N.N.)"); rc.right = gdi.getCX() + gdi.getLineHeight(); - gdi.dropLine(1.5); + gdi.dropLine(2); rc.bottom = gdi.getHeight(); - gdi.addRectangle(rc, colorLightCyan); + gdi.addRectangle(rc, colorLightBlue); gdi.setRestorePoint(); @@ -1692,7 +1703,7 @@ pRunner TabTeam::findRunner(const wstring &name, int cardNo) const { if (cardNo != 0) { vector pr; - oe->getRunnersByCard(cardNo, pr); + oe->getRunnersByCardNo(cardNo, true, oEvent::CardLookupProperty::Any, pr); for (size_t k = 0; k < pr.size(); k++) { wstring a = canonizeName(pr[k]->getName().c_str()); if (a == n) @@ -1876,7 +1887,8 @@ void TabTeam::processChangeRunner(gdioutput &gdi, pTeam t, int leg, pRunner r) { void TabTeam::switchRunners(pTeam t, int leg, pRunner r, pRunner oldR) { vector mp; - + bool removeAnnonumousTeamMember = false; + if (r->getTeam()) { pTeam otherTeam = r->getTeam(); int otherLeg = r->getLegNumber(); @@ -1890,7 +1902,11 @@ void TabTeam::switchRunners(pTeam t, int leg, pRunner r, pRunner oldR) { else if (oldR) { t->setRunner(leg, 0, false); t->synchronize(true); - oldR->setClassId(r->getClassId(false), true); + if (r->getClassRef(false) && r->getClassRef(false)->isTeamClass()) + oldR->setClassId(0, false); + else + oldR->setClassId(r->getClassId(false), true); + removeAnnonumousTeamMember = oldR->isAnnonumousTeamMember(); oldR->evaluateCard(true, mp, 0, true); oldR->synchronize(true); } @@ -1900,6 +1916,9 @@ void TabTeam::switchRunners(pTeam t, int leg, pRunner r, pRunner oldR) { t->checkValdParSetup(); t->apply(true, 0, false); t->synchronize(true); + + if (removeAnnonumousTeamMember) + oe->removeRunner({ oldR->getId() }); } void TabTeam::clearCompetitionData() { @@ -1911,16 +1930,15 @@ void TabTeam::clearCompetitionData() { currentMode = 0; } -bool TabTeam::warnDuplicateCard(gdioutput &gdi, string id, int cno, pRunner r, vector &allRCache) { +bool TabTeam::warnDuplicateCard(gdioutput &gdi, string id, int cno, pRunner r) { pRunner warnCardDupl = 0; - if (r && !r->getCard()) { - if (allRCache.empty()) // Fill cache if not initialized - oe->getRunners(0, 0, allRCache, false); - - for (size_t k = 0; k < allRCache.size(); k++) { - if (!r->canShareCard(allRCache[k], cno)) { - warnCardDupl = allRCache[k]; + if (r && !r->getCard() && cno != 0) { + vector allR; + oe->getRunnersByCardNo(cno, true, oEvent::CardLookupProperty::Any, allR); + for (pRunner ar : allR) { + if (!r->canShareCard(ar, cno)) { + warnCardDupl = ar; break; } } diff --git a/code/TabTeam.h b/code/TabTeam.h index bb07ace..231ccaa 100644 --- a/code/TabTeam.h +++ b/code/TabTeam.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -70,7 +70,7 @@ private: vector teamLineup; // Returns true if the warning concerns the same team - bool warnDuplicateCard(gdioutput &gdi, string id, int cno, pRunner r, vector &allRCache); + bool warnDuplicateCard(gdioutput &gdi, string id, int cno, pRunner r); void switchRunners(pTeam team, int leg, pRunner r, pRunner oldR); diff --git a/code/Table.cpp b/code/Table.cpp index c8fecc2..e4ebed5 100644 --- a/code/Table.cpp +++ b/code/Table.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1023,8 +1023,8 @@ void Table::initEmpty() { void drawSymbol(gdioutput &gdi, HDC hDC, int height, const TextInfo &ti, const wstring &symbol, bool highLight) { - int cx = ti.xp - gdi.GetOffsetX() + ti.xlimit/2; - int cy = ti.yp - gdi.GetOffsetY() + height/2 - 2; + int cx = ti.xp - gdi.getOffsetX() + ti.xlimit/2; + int cy = ti.yp - gdi.getOffsetY() + height/2 - 2; int h = int(height * 0.4); int w = h/3; h-=2; diff --git a/code/Table.h b/code/Table.h index e32fc3a..6c5d98f 100644 --- a/code/Table.h +++ b/code/Table.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TimeStamp.cpp b/code/TimeStamp.cpp index e65ec82..029cd12 100644 --- a/code/TimeStamp.cpp +++ b/code/TimeStamp.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/TimeStamp.h b/code/TimeStamp.h index 48a4ac1..9889e2b 100644 --- a/code/TimeStamp.h +++ b/code/TimeStamp.h @@ -10,7 +10,7 @@ #endif // _MSC_VER > 1000 /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/animationdata.cpp b/code/animationdata.cpp index 37107ab..2958d3e 100644 --- a/code/animationdata.cpp +++ b/code/animationdata.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ Eksoppsvägen 16, SE-75646 UPPSALA, Sweden #include "Printer.h" AnimationData::AnimationData(gdioutput &gdi, int timePerPage, int nCol, - int marginPercent, bool animate) : + int marginPercent, bool animate, bool respectPageBreak) : nCol(nCol), animate(animate), page(-1), gdiRef(0) { lastTime = 0; @@ -56,7 +56,7 @@ AnimationData::AnimationData(gdioutput &gdi, int timePerPage, int nCol, pageInfo.yMM2PrintK = 0; list rectangles; - pageInfo.renderPages(gdi.getTL(), rectangles, false, pages); + pageInfo.renderPages(gdi.getTL(), rectangles, false, respectPageBreak, pages); } AnimationData::~AnimationData() { @@ -194,8 +194,8 @@ void AnimationData::threadRender(gdioutput *gdi, size_t sp, int delay) { } void AnimationData::renderSubPage(HDC hDC, gdioutput &gdi, RenderedPage &page, int x, int y, int animateDelay) { - int ox = gdi.GetOffsetX(); - int oy = gdi.GetOffsetY(); + int ox = gdi.getOffsetX(); + int oy = gdi.getOffsetY(); int top = 10000; for (const auto &text : page.text) { @@ -203,8 +203,8 @@ void AnimationData::renderSubPage(HDC hDC, gdioutput &gdi, RenderedPage &page, i top = min(top, text.ti.yp); } } - gdi.SetOffsetY(-y+top-margin/2); - gdi.SetOffsetX(-x); + gdi.setOffsetY(-y+top-margin/2); + gdi.setOffsetX(-x); int currentRow = 0; for (auto &text : page.text) { @@ -214,8 +214,8 @@ void AnimationData::renderSubPage(HDC hDC, gdioutput &gdi, RenderedPage &page, i } gdi.RenderString(text.ti, hDC); } - gdi.SetOffsetY(ox); - gdi.SetOffsetX(oy); + gdi.setOffsetY(ox); + gdi.setOffsetX(oy); } void AnimationData::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { diff --git a/code/animationdata.h b/code/animationdata.h index 198ce73..fc8bba9 100644 --- a/code/animationdata.h +++ b/code/animationdata.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -54,7 +54,7 @@ class AnimationData : public GuiHandler { public: AnimationData(gdioutput &gdi, int timePerPage, int nCol, - int marginPercent, bool animate); + int marginPercent, bool animate, bool respectPageBreak); ~AnimationData(); void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type); diff --git a/code/autocomplete.cpp b/code/autocomplete.cpp index 72fb40c..a4625d4 100644 --- a/code/autocomplete.cpp +++ b/code/autocomplete.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/autocomplete.h b/code/autocomplete.h index a821ecd..0d2adae 100644 --- a/code/autocomplete.h +++ b/code/autocomplete.h @@ -1,4 +1,27 @@ #pragma once + +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ + #include #include "gdioutput.h" diff --git a/code/autocompletehandler.h b/code/autocompletehandler.h index 7363a40..8a30ae4 100644 --- a/code/autocompletehandler.h +++ b/code/autocompletehandler.h @@ -1,4 +1,25 @@ #pragma once +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ class AutoCompleteInfo; class gdioutput; diff --git a/code/autotask.cpp b/code/autotask.cpp index 27177e8..5349390 100644 --- a/code/autotask.cpp +++ b/code/autotask.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/autotask.h b/code/autotask.h index 1621d38..610f7b1 100644 --- a/code/autotask.h +++ b/code/autotask.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/classconfiginfo.cpp b/code/classconfiginfo.cpp index c135008..43f220f 100644 --- a/code/classconfiginfo.cpp +++ b/code/classconfiginfo.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -115,6 +115,7 @@ void oEvent::getClassConfigurationInfo(ClassConfigInfo &cnf) const cnf.clear(); cnf.hasMultiEvent = hasPrevStage() || hasNextStage(); + map> runnerPerClass; for (it = Classes.begin(); it != Classes.end(); ++it) { if (it->isRemoved()) @@ -128,7 +129,58 @@ void oEvent::getClassConfigurationInfo(ClassConfigInfo &cnf) const if (it->getCourse() == 0) cnf.classWithoutCourse.push_back(it->getName()); //MultiCourse not analysed... + + if (it->isQualificationFinalBaseClass()) + cnf.knockout.push_back(it->getId()); + if (Courses.empty() || (it->getCourse(false) == nullptr && it->getCourse(0,0, false) == nullptr) || + (it->getCourse(false) && it->getCourse(false)->getNumControls() == 0)) { + + if (!it->isQualificationFinalBaseClass()) { + // No course. + if (runnerPerClass.empty()) { + for (auto &r : Runners) { + if (!r.skip() && r.getClassRef(false) != nullptr) + runnerPerClass[r.getClassId(true)].push_back(&r); + } + } + + map punches; + int cntSample = 0; + for (auto r : runnerPerClass[it->getId()]) { + if (r->getCard()) { + for (auto &p : r->getCard()->punches) { + int tc = p.getTypeCode(); + if (tc > 30) + ++punches[tc]; + } + if (++cntSample > 10) + break; + } + } + + bool single = true, extra = true; + + if (cntSample > 3) { + int usedControlCodes = 0; + for (auto &p : punches) { + if (p.second >= cntSample / 2) + usedControlCodes++; + } + + if (usedControlCodes == 1) + extra = false; + else if (usedControlCodes > 1) + single = false; + } + + if (single) + cnf.lapcountsingle.push_back(it->getId()); + if (extra) + cnf.lapcountextra.push_back(it->getId()); + } + } + if ( !it->hasCoursePool() ) { for (size_t k = 0; k< it->MultiCourse.size(); k++) { if (it->MultiCourse[k].size() > 1) diff --git a/code/classconfiginfo.h b/code/classconfiginfo.h index e25f08e..96cfdad 100644 --- a/code/classconfiginfo.h +++ b/code/classconfiginfo.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,6 +44,11 @@ public: vector rogainingClasses; + vector knockout; + + vector lapcountsingle; + vector lapcountextra; + // True if predefined forking bool hasMultiCourse; diff --git a/code/csvparser.cpp b/code/csvparser.cpp index 769b1ec..5424d3a 100644 --- a/code/csvparser.cpp +++ b/code/csvparser.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -63,35 +63,40 @@ csvparser::~csvparser() } -int csvparser::iscsv(const wstring &file) -{ - fin.open(file); +csvparser::CSV csvparser::iscsv(const wstring &file) { + ifstream fin(file); if (!fin.good()) - return false; + return CSV::NoCSV; char bf[2048]; - fin.getline(bf, 2048); - - while(fin.good() && strlen(bf)<3) + bool isCSVType = false; + while (fin.good() && !isCSVType) { fin.getline(bf, 2048); + isCSVType = strlen(bf) >= 3; + } fin.close(); vector sp; split(bf, sp); + if (sp.size() > 0) { + string sp0 = sp[0]; + if (sp0.find(" > allLines; parse(file, allLines); - //vector sp; list< vector >::iterator it = allLines.begin(); + + set matchedClasses; // Skip first line while (++it != allLines.end()) { //fin.getline(bf, 1024); @@ -161,7 +159,7 @@ bool csvparser::importOS_CSV(oEvent &event, const wstring &file) //Create class with this class number... int ClassId=wtoi(sp[OSclassno]); - event.getClassCreate(ClassId, sp[OSclass]); + event.getClassCreate(ClassId, sp[OSclass], matchedClasses); //Club is autocreated... pTeam team=event.addTeam(sp[OSclub] + L" " + sp[OSdesc], ClubId, ClassId); @@ -210,10 +208,10 @@ bool csvparser::importOS_CSV(oEvent &event, const wstring &file) r->setFinishTime( event.convertAbsoluteTime(sp[rindex+OSRfinish]) ); if (sp[rindex+OSRstatus].length()>0) - r->setStatus( ConvertOEStatus( wtoi(sp[rindex+OSRstatus]) ), true, false); + r->setStatus( ConvertOEStatus( wtoi(sp[rindex+OSRstatus]) ), true, false, false); if (r->getStatus()==StatusOK && r->getRunningTime()==0) - r->setStatus(StatusUnknown, true, false); + r->setStatus(StatusUnknown, true, false, false); r->addClassDefaultFee(false); @@ -244,24 +242,14 @@ bool csvparser::importOE_CSV(oEvent &event, const wstring &file) { OErent=35, OEfee=36, OEpaid=37, OEcourseno=38, OEcourse=39, OElength=40}; -/* fin.open(file); - - if (!fin.good()) - return false; - - char bf[1024]; - fin.getline(bf, 1024); - */ list< vector > allLines; parse(file, allLines); list< vector >::iterator it = allLines.begin(); + + set matchedClasses; // Skip first line - - nimport=0; while (++it != allLines.end()) { - //fin.getline(bf, 1024); - //split(bf, sp); const vector &sp = *it; if (sp.size()>20) { nimport++; @@ -348,7 +336,7 @@ bool csvparser::importOE_CSV(oEvent &event, const wstring &file) { //Autocreate class if it does not exist... int classId=wtoi(sp[OEclassno]); if (classId>0 && !pr->hasFlag(oAbstractRunner::FlagUpdateClass)) { - pClass pc=event.getClassCreate(classId, sp[OEclass]); + pClass pc=event.getClassCreate(classId, sp[OEclass], matchedClasses); if (pc) { pc->synchronize(); @@ -675,6 +663,8 @@ bool csvparser::importRAID(oEvent &event, const wstring &file) list< vector > allLines; parse(file, allLines); + + set matchedClasses; list< vector >::iterator it = allLines.begin(); nimport=0; while (++it != allLines.end()) { @@ -686,7 +676,7 @@ bool csvparser::importRAID(oEvent &event, const wstring &file) int ClubId=0; //Create class with this class number... int ClassId=wtoi(sp[RAIDclassid]); - pClass pc = event.getClassCreate(ClassId, sp[RAIDclass]); + pClass pc = event.getClassCreate(ClassId, sp[RAIDclass], matchedClasses); ClassId = pc->getId(); //Club is autocreated... @@ -1357,3 +1347,127 @@ void csvparser::importTeamLineup(const wstring &file, data.pop_front(); } } + +int csvparser::importRanking(oEvent &oe, const wstring &file, vector &problems) { + list< vector > data; + parse(file, data); + + size_t idIx = -1; + size_t nameIx = 1; + size_t lastNameIx = -1; + size_t rankIx = -1; + map > id2Rank; + map > name2RankDup; + bool first = true; + for (auto &rank : data) { + if (first) { + first = false; + bool any = false; + + for (size_t i = 0; i < rank.size(); i++) { + wstring s = canonizeName(rank[i].c_str()); + + if (s.find(L"name") != wstring::npos && (s.find(L"last") != wstring::npos || s.find(L"family") != wstring::npos) && lastNameIx == -1) { + lastNameIx = i; + any = true; + } + else if (s.find(L"name") != wstring::npos && (s.find(L"first") != wstring::npos || s.find(L"given") != wstring::npos) && nameIx == -1) { + nameIx = i; + any = true; + } + else if (s.find(L" id") != wstring::npos && idIx == -1) { + idIx = i; + any = true; + } + else if (s.find(L"position") != wstring::npos && rankIx == -1) { + rankIx = i; + any = true; + } + } + + if (idIx == -1) + idIx = 0; + + if (nameIx == -1) + nameIx = 1; + + if (lastNameIx == -1) + lastNameIx = 2; + + if (rankIx == -1) + rankIx = 4; + + if (any) + continue; + } + + if (rank.size() <= rankIx) + continue; + + int rpos = _wtoi(rank[rankIx].c_str()); + if (rpos <= 0) + continue; + + wstring name; + if (nameIx < rank.size()) { + name = rank[nameIx]; + + if (lastNameIx < rank.size()) { + name += L" " + rank[lastNameIx]; + } + } + if (name.empty()) + continue; + + auto res = name2RankDup.emplace(name, make_pair(rpos, false)); + + if (!res.second) + res.first->second.second = true; // Duplicate names + + if (idIx < rank.size()) { + int64_t id = oBase::converExtIdentifierString(rank[idIx]); + if (id != 0) + id2Rank[id] = make_pair(name, rpos); + } + } + + if ((name2RankDup.size() < data.size() / 2 || name2RankDup.empty()) && + (id2Rank.size() < data.size() / 2 || id2Rank.empty())) { + throw meosException(L"Felaktigt rankingformat i X. Förväntat: Y#" + file + L"#ID; First name; Last name; Rank"); + } + + vector runners; + oe.getRunners(-1, -1, runners); + + int count = 0; + vector remRunners; + for (pRunner r : runners) { + int64_t id = r->getExtIdentifier(); + auto res = id2Rank.find(id); + if (res != id2Rank.end() && r->matchName(res->second.first)) { + r->getDI().setInt("Rank", res->second.second); + r->synchronize(true); + count++; + } + else + remRunners.push_back(r); + } + + for (pRunner r : remRunners) { + auto res = name2RankDup.find(r->getName()); + if (r->getRaceNo() > 0) + continue; + if (res != name2RankDup.end()) { + if (res->second.second) + problems.push_back(r->getCompleteIdentification()); + else { + res->second.second = true; + r->getDI().setInt("Rank", res->second.first); + r->synchronize(true); + count++; + } + } + } + + return count; +} diff --git a/code/csvparser.h b/code/csvparser.h index 9caba9c..8d2805c 100644 --- a/code/csvparser.h +++ b/code/csvparser.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -134,10 +134,21 @@ public: bool importCards(const oEvent &oe, const wstring &file, vector &punches); - int split(char *line, vector &split); - int split(wchar_t *line, vector &split); + int importRanking(oEvent &oe, const wstring &file, vector & problems); + + static int split(char *line, vector &split); + static int split(wchar_t *line, vector &split); + + enum class CSV { + NoCSV, + Unknown, + RAID, + OE, + OS, + }; + + static CSV iscsv(const wstring &file); - int iscsv(const wstring &file); csvparser(); virtual ~csvparser(); diff --git a/code/czech.lng b/code/czech.lng index 4bbcd6a..a45b6ab 100644 --- a/code/czech.lng +++ b/code/czech.lng @@ -752,7 +752,7 @@ Radera tävlingen = Smaž závod Radera vakanser = Odstraň vakanty Radiotider, kontroll = Radio-čas, kontrola Ranking = Ranking -Ranking (IOF, xml) = Ranking (IOF, xml) +Ranking (IOF, xml, csv) = Ranking (IOF, xml, csv) Rapport inför = Protokol pro Rapporter = Protokoly Rapportläge = Režim protokolu @@ -2295,7 +2295,7 @@ help:rest = MeOS REST API ti umožní přístup k datům závodu skrze webové s Server startad på X = Server běžící na portu X Inconsistent qualification rule, X = Nekonzistentní kvalifikační pravidlo, X help:LockStartList = MeOS neprovede aktualizaci zamknuté kategorie přestože kvalifikační výsledky jsou změněny. -Kval-Final-Schema = Načti kvalifikační schéma +Kval/final-schema = Načti kvalifikační schéma Lås startlista = Zamkni startovku FilterNoCancel = Není zrušeno CourseStartTime = Trať, čas startu diff --git a/code/danish.lng b/code/danish.lng index d5872a6..5b33625 100644 --- a/code/danish.lng +++ b/code/danish.lng @@ -1014,7 +1014,7 @@ Kunde inte öppna tävlingen = Kan ikke åbne løbet Kunde inte ansluta till Eventor = Kunne ikke forbinde til Eventor Kunde inte ladda upp tävlingen (X) = Kunne ikke uploade løbet (X) Kunde inte ladda X\n\n(Y) = Kunne ikke indlæse X\n\n(Y) -Kval-Final-Schema = Kval-Finale-Skema +Kval/final-schema = Kval/Finale-Skema Kvar-i-skogen = Løbere i skoven Kvinna = Kvinde Kvinnor = Kvinder @@ -1468,7 +1468,7 @@ Radera tävlingen = Slet løbet Radera vakanser = Slet vakante radio X = radio X Radiotider, kontroll = Radiotider, post -Ranking (IOF, xml) = Rangliste (IOF, XML) +Ranking (IOF, xml, csv) = Rangliste (IOF, xml, csv) Ranking = Rangliste Rapport = Rapport Rapport inför = Rapport før diff --git a/code/download.cpp b/code/download.cpp index d234c5d..833abd0 100644 --- a/code/download.cpp +++ b/code/download.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,13 +27,18 @@ #include "meos_util.h" #include "progress.h" #include "meosexception.h" - +#include +#include +#include #include #include #include #include +#pragma comment(lib, "IPHLPAPI.lib") +#define INET_ADDRSTRLEN 16 + ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// @@ -472,3 +477,124 @@ bool Download::httpSendReqEx(HINTERNET hConnect, bool https, const wstring &dest InternetCloseHandle(hRequest); return true; } + + +void ListIpAddresses(vector& ipAddrs) +{ + ipAddrs.clear(); + IP_ADAPTER_ADDRESSES* adapter_addresses(NULL); + IP_ADAPTER_ADDRESSES* adapter(NULL); + const int KB = 1024; + // Start with a 16 KB buffer and resize if needed - + // multiple attempts in case interfaces change while + // we are in the middle of querying them. + DWORD adapter_addresses_buffer_size = 16 * 1024; + for (int attempts = 0; attempts != 3; ++attempts) + { + adapter_addresses = (IP_ADAPTER_ADDRESSES*)malloc(adapter_addresses_buffer_size); + assert(adapter_addresses); + + DWORD error = ::GetAdaptersAddresses( + AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME, + NULL, + adapter_addresses, + &adapter_addresses_buffer_size); + + if (ERROR_SUCCESS == error) { + // We're done here, people! + break; + } + else if (ERROR_BUFFER_OVERFLOW == error) + { + // Try again with the new size + free(adapter_addresses); + adapter_addresses = NULL; + + continue; + } + else { + // Unexpected error code - log and throw + free(adapter_addresses); + adapter_addresses = NULL; + + return; + } + } + + // Iterate through all of the adapters + for (adapter = adapter_addresses; NULL != adapter; adapter = adapter->Next) + { + // Skip loopback adapters + if (IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) + { + continue; + } + + // Parse all IPv4 and IPv6 addresses + for ( + IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; + NULL != address; + address = address->Next) + { + auto family = address->Address.lpSockaddr->sa_family; + if (AF_INET == family) + { + // IPv4 + SOCKADDR_IN* ipv4 = reinterpret_cast(address->Address.lpSockaddr); + + char str_buffer[INET_ADDRSTRLEN] = { 0 }; + //inet_ntop(AF_INET, &(ipv4->sin_addr), str_buffer, INET_ADDRSTRLEN); + auto &id = ipv4->sin_addr.S_un.S_un_b; + if (id.s_b1 == 169 && id.s_b2 == 254) + continue; // Not usable + sprintf_s(str_buffer, "%u.%u.%u.%u", id.s_b1, id.s_b2, id.s_b3, id.s_b4); + ipAddrs.emplace_back(str_buffer); + } + else if (AF_INET6 == family) + {/* + // IPv6 + SOCKADDR_IN6* ipv6 = reinterpret_cast(address->Address.lpSockaddr); + + char str_buffer[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN); + + std::string ipv6_str(str_buffer); + + // Detect and skip non-external addresses + bool is_link_local(false); + bool is_special_use(false); + + if (0 == ipv6_str.find("fe")) + { + char c = ipv6_str[2]; + if (c == '8' || c == '9' || c == 'a' || c == 'b') + { + is_link_local = true; + } + } + else if (0 == ipv6_str.find("2001:0:")) + { + is_special_use = true; + } + + if (!(is_link_local || is_special_use)) + { + ipAddrs.mIpv6.push_back(ipv6_str); + }*/ + } + else + { + // Skip all other types of addresses + continue; + } + } + } + + // Cleanup + free(adapter_addresses); + adapter_addresses = NULL; +} diff --git a/code/download.h b/code/download.h index a09d9c5..630c5bc 100644 --- a/code/download.h +++ b/code/download.h @@ -7,7 +7,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/english.lng b/code/english.lng index a2112ac..c8ea89f 100644 --- a/code/english.lng +++ b/code/english.lng @@ -619,9 +619,9 @@ Mata in radiotider manuellt = Enter radio times by hand Max antal gemensamma kontroller = Max number common controls Max parallellt startande = Max. number parallel start Max. vakanser (per klass) = Max. vacancy (per class) -Maximal tid efter ledaren för att delta i jaktstart = Maximal time after leader to participate in pursuit. +Maximal tid efter ledaren för att delta i jaktstart = Maximal time behind leader to participate in pursuit. Maxtid = OMT -Maxtid efter = Maximum time after +Maxtid efter = Maximum time behind MeOS = MeOS MeOS lokala datakatalog är = MeOS local data folder is MeOS – Resultatkiosk = MeOS – Result Kiosk @@ -685,7 +685,7 @@ Ogiltig föregående/efterföljande etapp = Invalid previous/next stage Ogiltig första starttid. Måste vara efter nolltid = Invalid first start time. Must be after zero time Ogiltig omstartstid = Invalid restart time Ogiltig repdragningstid = Invalid rope time -Ogiltig starttid i 'X' på sträcka Y = Invalid start time in 'X' on leg Y +Ogiltig starttid i 'X' på sträcka Y = Invalid start time for 'X' on leg Y Ogiltig starttid: X = Invalid start time: X Ogiltig tid = Invalid time Ogiltigt bricknummer X = Invalid card number X @@ -753,7 +753,7 @@ Radera tävlingen = Remove Competition Radera vakanser = Delete Vacancies Radiotider, kontroll = Radio times, control Ranking = Ranking -Ranking (IOF, xml) = Ranking (IOF, xml) +Ranking (IOF, xml, csv) = Ranking (IOF, xml, csv) Rapport inför = Report for Rapporter = Reports Rapportläge = Report Mode @@ -804,7 +804,7 @@ Runner = Competitor RunnerBib = Competitor's bib RunnerCard = Card number RunnerClassCoursePlace = Position on course within class -RunnerClassCourseTimeAfter = Time after on course within class +RunnerClassCourseTimeAfter = Time behind on course within class RunnerClub = Competitor's club RunnerCompleteName = Complete name RunnerCourse = Competitor's course @@ -820,17 +820,17 @@ RunnerRank = Ranking RunnerRogainingPoint = Rogaining points RunnerStart = Competitor's start time RunnerStartNo = Competitor's start number -RunnerTempTimeAfter = Competitor's time after at selected control +RunnerTempTimeAfter = Competitor's time behind at selected control RunnerTempTimeStatus = Competitor's time / status at selected control RunnerTime = Competitor's time -RunnerTimeAfter = Competitor's time after -RunnerTimeAfterDiff = Competitor's time after difference +RunnerTimeAfter = Competitor's time behind +RunnerTimeAfterDiff = Competitor's time behind difference RunnerTimeLost = Competitor's lost time RunnerTimePlaceFixed = Time when Competitor's place is set RunnerTimeStatus = Competitor's time / status RunnerTotalPlace = Competitor's total place RunnerTotalTime = Competitor's total time -RunnerTotalTimeAfter = Competitor's total time after +RunnerTotalTimeAfter = Competitor's total time behind RunnerTotalTimeStatus = Competitor's total time / status RunnerUMMasterPoint = Uppsala möte, master points SI X inläst. Brickan tillhör Y som saknar klass = SI X was read out. The card belongs to Y, who has no class @@ -1038,7 +1038,7 @@ Tabelläge = Table Mode Team = Team TeamBib = Team's bib TeamClub = Team's club -TeamLegTimeAfter = Team's time after on leg +TeamLegTimeAfter = Team's time behind on leg TeamLegTimeStatus = Team's time / status on leg TeamName = Team name TeamPlace = Team's place @@ -1049,7 +1049,7 @@ TeamStart = Team's start time TeamStartNo = Team's start number TeamStatus = Team status TeamTime = Team's time -TeamTimeAfter = Team's time after +TeamTimeAfter = Team's time behind TeamTimeStatus = Team's time / status Telefon = Phone Test = Test @@ -1060,9 +1060,9 @@ Text: X = Text: X Textfiler = Text files Textstorlek = Text size Tid = Time -Tid efter: X = Time after: X -Tid efter: X; har tagit in Y = Time after: X; gained Y -Tid efter: X; har tappat Y = Time after: X; lost Y +Tid efter: X = Time behind: X +Tid efter: X; har tagit in Y = Time behind: X; gained Y +Tid efter: X; har tappat Y = Time behind: X; lost Y Tid in = Time in Tid: X, nuvarande placering Y/Z = Time: X, current place Y/Z Tidsavdrag: X poäng = Point reduction: X points @@ -1174,7 +1174,7 @@ Varning: deltagare med blankt namn påträffad. MeOS kräver att alla deltagare Varning: lag utan namn påträffat. MeOS kräver att alla lag har ett namn, och tilldelar namnet 'N.N.' = Warning: A team without name was found. MeOS requires a name and has assigned the name 'N.N.' Verkställ = Apply Version X = Version X -Vi stöder MeOS = We support MeOS +Vi stöder MeOS = We Support MeOS Viktiga händelser = Important events Vill du flytta löpare från X till Y och ta bort Z? = Do you want to move runners from X to Y and remove Z? Vill du klistra in X nya rader i tabellen? = Do you want to paste X new rows into the table? @@ -1401,7 +1401,7 @@ väntas till X om någon minut, och kan i så fall ta en Y plats = is expected t väntas till X om någon minut, och kan i så fall ta ledningen = is expected to X in a minute, and can take the lead växeln = the changeover växlar på X plats med tiden Y = changes over as X with time Y -växlar på X plats, efter Y, på tiden Z = changes over at a X place, after Y, with time Z +växlar på X plats, efter Y, på tiden Z = changes over at a X place, behind Y, with time Z växlar på delad X plats med tiden Y = changes over as X with time Y warn:changedtimezero = Changing the zero time for a competition with results is not recommended.\n\nDo you wish to proceed anyway? warn:olddbversion = The database is in use by a later version of MeOS. Upgrading is recommended. @@ -1506,8 +1506,8 @@ Rogainingresultat - %s = Rogaining results - %s TeamPlaceDiff = Team's place difference (this stages) TeamTotalPlace = Team's summed place (all stages) TeamTotalTime = Team's summed time (all stages) -TeamTotalTimeAfter = Team's summed time after (all stages) -TeamTotalTimeDiff = Team's summed time after difference (this stage) +TeamTotalTimeAfter = Team's summed time behind (all stages) +TeamTotalTimeDiff = Team's summed time behind difference (this stage) TeamTotalTimeStatus = Team's summed time or status (all stages) Vill du dumpa aktuellt tävling och skapa en testtävling? = Do you want to dump the current competition and create a test competition? Radera alla klubbar = Delete All Clubs @@ -1614,7 +1614,7 @@ Höger = Right PunchControlCode = Control code PunchControlNumber = Punch code PunchControlPlace = Place, leg to control -PunchControlPlaceAcc = Place, total after control +PunchControlPlaceAcc = Place, total at control PunchLostTime = Time lost at control Slå ihop text med föregående = Merge with previous Textjustering = Text adjustment @@ -1825,7 +1825,7 @@ Status code for not finishing = Status code for not finishing Status code for not starting = Status code for not starting Points as computed by your point method = Points as computed by your point method Time as computed by your time method = Time as computed by your time method -Time after leg winner = Time after leg winner +Time after leg winner = Time behind leg winner Finish time for each team member = Finish time for each team member Matched control ids (-1 for unmatched) for each team member = Matched control ids (-1 for unmatched) for each team member Punch codes for each team member = Punch codes for each team member @@ -1952,7 +1952,7 @@ Hela banan = Entire course Ogiltigt maximalt intervall = Invalid maximum interval Startintervallet får inte vara kortare än basintervallet = A start interval may not be shorter than the base interval Ett startintervall måste vara en multipel av basintervallet = A start interval must be a multiple of the base interval -Ogiltigt minimalt intervall = Invalid minimal interval +Ogiltigt minimalt intervall = Invalid shortest interval Ogiltigt basintervall = Invalid base interval Country = Country CourseShortening = Course shortening @@ -2010,7 +2010,7 @@ Tabellverktyg = Table tools Antal reserverade nummerlappsnummer mellan klasser = Number of reserved bib numbers between classes help:bibs = You can handle bibs automatically or manually. Here you can assign bibs manually for a certain class by specifying the method Manual and provide the first number in the class.\n\nThe method automatic works in the same way, with the difference that MeOS will update the bibs of all classes at once. Although it is possible to make this setting here, it is better to use the Quick settings for classes to get an overview over all classes.\n\nUse the method Automatic together with the methods None or Consecutive, which means that the last number in the preceding class is used as first number. The number of reserved bibs specifies the jump made in the numbering between classes.\n\nFor team classes you can specify how the competitors´ bibs relate to the team´s bib. It can be the Same, Independent, Increasing (Team 1: 101, 102, 103, 104, Team 2: 111, 112, 113, 114 etc) or Leg (100-1, 100-2, 100-3 etc). RunnerGeneralPlace = Competitor's team's or individual place -RunnerGeneralTimeAfter = Competitor's team's or individual time after +RunnerGeneralTimeAfter = Competitor's team's or individual time behind RunnerGeneralTimeStatus = Competitor's team's or individual time / status open_error = Failed to open X.\n\nY. open_error_locked = This competition is already open in MeOS.\n\nYou have to use a database to open more than one instance of the competition. @@ -2296,7 +2296,7 @@ help:rest = MeOS REST API lets you access competition data via a web connection. Server startad på X = Server running on port X Inconsistent qualification rule, X = Inconsistent qualification rule, X help:LockStartList = MeOS will not update assignement to a locked class even if qualification results are altered. -Kval-Final-Schema = Load qualification scheme +Kval/final-schema = Qualification/final scheme Lås startlista = Lock start list FilterNoCancel = Not cancelled CourseStartTime = Course, start time @@ -2321,3 +2321,89 @@ Finish order = Finish order First to finish = First to finish Individual result by finish time = Individual result by finish time Endast tidtagning = Only timing +AllPunches = All punches +CoursePunches = Punches (on course) +FilterNamedControl = Named controls +FilterNotFinish = Exclude finish +LineBreak = Line break +PunchAbsTime = Punch, real time +PunchTimeSinceLast = Time between controls +PunchTotalTime = Time to control +PunchName = Punch, control name +PunchNamedSplit = Time since last named control +PunchSplitTime = Time since last control (split time) +ClassLiveResult = Live results (radio times), class-wise +Felaktigt datum 'X' (Använd YYYY-MM-DD) = Incorrect date 'X' (Use YYYY-MM-DD) +FilterAnyResult = With radio time/result +Liveresultat, radiotider = Live results with radio times +PunchTotalTimeAfter = Time behind at control +RunnerCheck = Time for check punch +RunnerId = Competitor's external ID +StartTimeClass = Start time, class +ask:outofmaps = Out of maps. Do you want to add this competitor anyway? +Varning: Kartorna är slut = Warning: Out of maps +X går vidare, klass enligt ranking = X qualified, class by ranking +Vill du ta bort schemat? = Do you want to remove the scheme? +ask:removescheme = Results are lost if you remove the scheme. Do you wish to continue? +ClassKnockoutTotalResult = Class, knock-out total result +Support intermediate legs = Support specified relay leg +help:custom_text_lines = You can insert custom specific data by typing [Symbol Name]. Available symbols can be seen in the list to the right.\n\n Example: Well done [RunnerName]! +Importerar ranking = Importing ranking +Klart. X värden tilldelade = Complete. Assigned X values. +Felaktigt rankingformat i X. Förväntat: Y = Incorrect ranking format in X.\nExpected: Y +Importerar RAID patrull csv-fil = Importing RAID patrol data +Varning: Följande deltagare har ett osäkert resultat = Warning: The assignments for the following competitors is unclear +Direkt tidtagning = Timekeeping Live +Klassval för 'X' = Class selection for 'X' +Endast tidtagning (utan banor) = Only timekeeping (no courses) +Knockout total = Knock-out summary +Varvräkning = Count laps +Varvräkning med mellantid = Count laps with extra time +Without courses = Without courses +Timekeeping = Timekeeping +Endast grundläggande (enklast möjligt) = Basic functionality only +Endast tidtagning (utan banor), stafett = Only timekeeping (no courses), relay +Individuellt = Individual +Lag och stafett = Team and relay +Övrigt = Miscellaneous +htmlhelp = HTML can be exported as a structured table or as a freely formatted document (more similar to the MeOS lists). You can also use export templates for custom formatting: columns, JavaScript base page flips, automatic scrolling, etc. It is possible to add custom templates by adding '.template' files in MeOS data folder. If you use a template there is a number of parameters to set below. The exact interpretation depends on the template..\n\nIf you select the list and its settings is stored permanently in the competition. You can then access the list by using MeOS as a web server (The service 'Information Server') or export the list automatically at regular intervals. +HTML Export = HTML Export +HTML Export för 'X' = HTML Export of 'X' +Lagra inställningar = Store settings +Kolumner = Columns +Rader = Rows +HTML formaterad genom listinställningar = HTML formatted by list settings +Begränsa antal rader per sida = Limit rows per page +Färre slingor = Fewer loops +RunnerGrossTime = Competitor's time before adjustment +TeamGrossTime = Team's time before adjustment +Visa detaljerad rapport för viss deltagare = Show a detailed report for a specific competor +Förhindra att laget deltar i någon omstart = Prevent that the team takes part in a restart +Förhindra omstart = Prevent restart +Ej omstart = No restart +Visa rubrik mellan listorna = Show heading between lists +Slå ihop med befintlig lista = Merge with existing list +Från löpardatabasen = From runner database +Från löpardatabasen i befintliga klubbar = From runner database in existing clubs +Med direktanmälan = With direct entry +Tillåt anmälan = Allow Entry +Anyone = Anyone +Bricknummer = Card number +Anmäl andra = New entry +Anmälan mottagen = Accepted entry +Automatisk omladdning = Automatic update +Till vilka klasser = To which classes +Vem får anmäla sig = Who may enter +Anmälan måste hanteras manuellt = Your entry requires manual processing. +EFilterAPIEntry = Entries via API +Visa rubrik = Show title +Rad X är ogiltig = Row X is invalid +Klassen X är listad flera gånger = The class X is listed several times +Ogiltig starttid X = Invalid start time X +Ogiltigt startintervall X = Invalid start interval X +Hittar inte klass X = Cannot X not found +MeOS utvecklinsstöd = MeOS Development Support +info:pageswithcolumns = Show the list one page at the time, with the specified number of columns. Automatically reload data for each round. +Pages with columns = Pages with columns +Pages with columns, no header = Pages with columns, no header +Externa adresser = External links diff --git a/code/french.lng b/code/french.lng index a9d3d33..e02ebdf 100644 --- a/code/french.lng +++ b/code/french.lng @@ -753,7 +753,7 @@ Radera tävlingen = Supprimer la compétition Radera vakanser = Supprimer les vacants Radiotider, kontroll = Heures radio, poste Ranking = Classement -Ranking (IOF, xml) = Classement (IOF, xml) +Ranking (IOF, xml, csv) = Classement (IOF, xml, csv) Rapport inför = Rapport pour Rapporter = Rapports Rapportläge = Mode rapport diff --git a/code/gdiconstants.h b/code/gdiconstants.h index 44ab1fc..7ded93a 100644 --- a/code/gdiconstants.h +++ b/code/gdiconstants.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/gdifonts.h b/code/gdifonts.h index e3c3b73..4954057 100644 --- a/code/gdifonts.h +++ b/code/gdifonts.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2012 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ ************************************************************************/ + #pragma once enum gdiFonts { @@ -46,6 +47,7 @@ enum gdiFonts { const int pageNewPage=100; //const int pageReserveHeight=101; const int pagePageInfo=102; +const int pageNewChapter = 103; const int textRight=256; const int textCenter=512; diff --git a/code/gdiimpl.h b/code/gdiimpl.h index 576b60a..8405472 100644 --- a/code/gdiimpl.h +++ b/code/gdiimpl.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/gdioutput.cpp b/code/gdioutput.cpp index 1a0b9cf..c1d3ef6 100644 --- a/code/gdioutput.cpp +++ b/code/gdioutput.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -102,7 +102,8 @@ EventInfo::EventInfo() : callBack(0), keyEvent(KC_NONE) {} bool gdioutput::skipTextRender(int format) { format &= 0xFF; return format == pageNewPage || - format == pagePageInfo; + format == pagePageInfo || + format == pageNewChapter; } #ifndef MEOSDB @@ -117,6 +118,7 @@ gdioutput::gdioutput(const string &_tag, double _scale) : isTestMode = false; } +extern gdioutput *gdi_main; gdioutput::gdioutput(double _scale, HWND hWnd, const PrinterObject &prndef) : recorder((Recorder *)0, false) { @@ -125,8 +127,12 @@ gdioutput::gdioutput(double _scale, HWND hWnd, const PrinterObject &prndef) : tabs = 0; setWindow(hWnd); constructor(_scale); - - isTestMode = false; + if (gdi_main) { + isTestMode = gdi_main->isTestMode; + if (isTestMode) + cmdAnswers.swap(gdi_main->cmdAnswers); + } + else isTestMode = false; } void gdioutput::constructor(double _scale) @@ -1559,7 +1565,7 @@ ListBoxInfo &gdioutput::addSelection(int x, int y, const string &id, int width, lbi.xp=x; lbi.yp=y; lbi.width = scale*width; - lbi.height = scale*height; + lbi.height = scale*30; lbi.id=id; lbi.callBack=cb; @@ -3098,6 +3104,11 @@ bool gdioutput::hasField(const string &id) const return true; } + for (auto &tl : TL) { + if (tl.id == id) + return true; + } + return false; } @@ -3651,8 +3662,8 @@ void gdioutput::refreshSmartFromSnapshot(bool allowMoveOffset) { } } - int maxOffsetY=max(GetPageY()-clientRC.bottom, 0); - int maxOffsetX=max(GetPageX()-clientRC.right, 0); + int maxOffsetY=max(getPageY()-clientRC.bottom, 0); + int maxOffsetX=max(getPageX()-clientRC.right, 0); int noy = OffsetY - offset.second; int nox = OffsetX - offset.first; if ((offset.first != 0 && nox>0 && nox0 && noyMaxOffsetY) @@ -5856,7 +5867,7 @@ int gdioutput::setHighContrastMaxWidth() { OutputDebugString("Set high contrast\n"); #endif - double w = GetPageX(); + double w = getPageX(); double s = rc.right / w; if (!highContrast || (fabs(s-1.0) > 1e-3 && (s * scale) >= 1.0) ) { lockRefresh = true; @@ -6441,12 +6452,18 @@ const string &gdioutput::recodeToNarrow(const wstring &input) { return output; } -/* -const string &gdioutput::toUTF8(const string &input) const { - return toUTF8(toWide(input)); -}*/ -const string &gdioutput::toUTF8(const wstring &winput) const { +const wstring &gdioutput::fromUTF8(const string &input) { + wstring &output = StringCache::getInstance().wget(); + size_t alloc = input.length() + 1; + output.resize(alloc); + wchar_t *ptr = &output[0]; + int wlen = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.length(), ptr, alloc); + ptr[wlen] = 0; + output.resize(wlen); + return output; +} +const string &gdioutput::toUTF8(const wstring &winput) { string &output = StringCache::getInstance().get(); size_t alloc = winput.length()*4+32; output.resize(alloc); @@ -6957,3 +6974,21 @@ AutoCompleteInfo &gdioutput::addAutoComplete(const string &key) { void gdioutput::clearAutoComplete(const string &key) { autoCompleteInfo.reset(); } + +int gdioutput::getPageY() const { + if (hideBG || backgroundColor1 != -1) + return max(MaxY, 100); + else + return max(MaxY, 100) + scaleLength(60); +} + +int gdioutput::getPageX() const { + int xlimit = 100; + for (auto &b : BI) + xlimit = max(b.xp + b.width, xlimit); + + if (hideBG || backgroundColor1 != -1 || xlimit >= MaxX) + return max(MaxX, xlimit); + else + return max(MaxX, xlimit) + scaleLength(60); +} diff --git a/code/gdioutput.h b/code/gdioutput.h index 2cca323..4a3edc1 100644 --- a/code/gdioutput.h +++ b/code/gdioutput.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -122,7 +122,7 @@ protected: bool startDoc(PrinterObject &po); bool getSelectedItem(ListBoxInfo &lbi); - bool doPrint(PrinterObject &po, PageInfo &pageInfo, pEvent oe); + bool doPrint(PrinterObject &po, PageInfo &pageInfo, pEvent oe, bool respectPageBreak); PrinterObject *po_default; @@ -329,7 +329,8 @@ public: static const wstring &widen(const string &input); static const string &narrow(const wstring &input); - const string &toUTF8(const wstring &input) const; + static const string &toUTF8(const wstring &input); + static const wstring &fromUTF8(const string &input); //void setEncoding(FontEncoding encoding); //FontEncoding getEncoding() const; @@ -400,16 +401,8 @@ public: void disableTables(); void pasteText(const char *id); - - bool writeHTML(const wstring &file, const wstring &title, int refreshTimeOut) const; - bool writeTableHTML(const wstring &file, const wstring &title, int refreshTimeOut) const; - bool writeTableHTML(ostream &fout, - const wstring &title, - bool simpleFormat, - int refreshTimeOut) const; - - void print(pEvent oe, Table *t=0, bool printMeOSHeader=true, bool noMargin=false); - void print(PrinterObject &po, pEvent oe, bool printMeOSHeader=true, bool noMargin=false); + void print(pEvent oe, Table *t = 0, bool printMeOSHeader = true, bool noMargin = false, bool respectPageBreak = true); + void print(PrinterObject &po, pEvent oe, bool printMeOSHeader = true, bool noMargin=false, bool respectPageBreak = true); void printSetup(PrinterObject &po); void destroyPrinterDC(PrinterObject &po); @@ -488,12 +481,12 @@ public: void updateScrollbars() const; - void SetOffsetY(int oy){OffsetY=oy;} - void SetOffsetX(int ox){OffsetX=ox;} - int GetPageY(){return max(MaxY, 100)+60;} - int GetPageX(){return max(MaxX, 100)+100;} - int GetOffsetY(){return OffsetY;} - int GetOffsetX(){return OffsetX;} + void setOffsetY(int oy) {OffsetY=oy;} + void setOffsetX(int ox) {OffsetX=ox;} + int getPageY() const; + int getPageX() const; + int getOffsetY() const {return OffsetY;} + int getOffsetX() const {return OffsetX;} void RenderString(TextInfo &ti, const wstring &text, HDC hDC); void RenderString(TextInfo &ti, HDC hDC=0); @@ -753,7 +746,7 @@ public: void setDBErrorState(bool state); friend int TablesCB(gdioutput *gdi, int type, void *data); friend class Table; - friend gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x, int max_y); + friend gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x, int max_y, bool fixedSize); gdioutput(const string &tag, double _scale); gdioutput(double _scale, HWND hWndTarget, const PrinterObject &defprn); diff --git a/code/gdistructures.h b/code/gdistructures.h index 74d47d9..9064fa2 100644 --- a/code/gdistructures.h +++ b/code/gdistructures.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -154,7 +154,7 @@ public: TextInfo &changeFont(const wstring &fnt) {font = fnt; return *this;} //Note: size not updated - bool isFormatInfo() const { return format == pageNewPage || format == pagePageInfo; } + bool isFormatInfo() const { return format == pageNewPage || format == pagePageInfo || format == pageNewChapter; } int getHeight() {return int(textRect.bottom-textRect.top);} gdiFonts getGdiFont() const {return gdiFonts(format & 0xFF);} diff --git a/code/generalresult.cpp b/code/generalresult.cpp index 1fddd35..a5ba25c 100644 --- a/code/generalresult.cpp +++ b/code/generalresult.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1017,8 +1017,8 @@ void DynamicResult::getSymbolInfo(int ix, wstring &name, wstring &desc) const { void DynamicResult::prepareCalculations(oEvent &oe, bool prepareForTeam, int inputNumber) const { compile(false); - oe.calculateResults(oEvent::RTClassResult); - oe.calculateResults(oEvent::RTTotalResult); + oe.calculateResults(set(), oEvent::ResultType::ClassResult); + oe.calculateResults(set(), oEvent::ResultType::TotalResult); declareSymbols(MRScore, true); if (prepareForTeam) { @@ -1344,7 +1344,7 @@ void GeneralResult::calculateIndividualResults(vector &runners, controlId.first == oPunch::PunchStart) { if (!totalResults) { - oe.calculateResults(oEvent::RTClassResult, true); + oe.calculateResults(set(), oEvent::ResultType::ClassResult, true); for (pRunner r : runners) { ri.status = r->getStatus(); if (ri.status == StatusUnknown) { @@ -1373,7 +1373,7 @@ void GeneralResult::calculateIndividualResults(vector &runners, } } else { - oe.calculateResults(oEvent::RTTotalResult, true); + oe.calculateResults(set(), oEvent::ResultType::TotalResult, true); for (pRunner r : runners) { ri.status = r->getTotalStatus(); if (ri.status == StatusUnknown && r->getInputStatus() == StatusOK) { diff --git a/code/generalresult.h b/code/generalresult.h index 4c79c7e..7e80ca1 100644 --- a/code/generalresult.h +++ b/code/generalresult.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/german.lng b/code/german.lng index f59ff29..76ab55f 100644 --- a/code/german.lng +++ b/code/german.lng @@ -539,7 +539,7 @@ Radera tävlingen = Wettkampf löschen Radera vakanser = Vakantplätze löschen Radiotider, kontroll = Funkposten-Zeiten, Posten Ranking = Ranking -Ranking (IOF, xml) = Ranking (IOF, xml) +Ranking (IOF, xml, csv) = Ranking (IOF, xml, csv) Rapport inför = Bericht für Rapporter = Berichte Redigera deltagaren = Teilnehmer bearbeiten diff --git a/code/guihandler.h b/code/guihandler.h index 3d23e25..55bec24 100644 --- a/code/guihandler.h +++ b/code/guihandler.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/html1.htm b/code/html1.htm index 74b8a94..d12cc6b 100644 --- a/code/html1.htm +++ b/code/html1.htm @@ -1,3 +1,4 @@ +

Documentation of MeOS REST API

Competition

diff --git a/code/image.cpp b/code/image.cpp index 29548bc..2472aef 100644 --- a/code/image.cpp +++ b/code/image.cpp @@ -1,4 +1,26 @@ -#include "stdafx.h" +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ + +#include "stdafx.h" #include "image.h" #include "png/png.h" #include diff --git a/code/image.h b/code/image.h index 9f2bae9..c390f02 100644 --- a/code/image.h +++ b/code/image.h @@ -1,4 +1,26 @@ -#pragma once +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ + +#pragma once #include #include @@ -12,8 +34,6 @@ public: private: - - static vector loadResourceToMemory(LPCTSTR lpName, LPCTSTR lpType); static HBITMAP read_png_file(const wstring &filename, int &width, int &height, ImageMethod method); static HBITMAP read_png_resource(LPCTSTR lpName, LPCTSTR lpType, int &width, int &height, ImageMethod method); static HBITMAP read_png(vector &data, int &width, int &height, ImageMethod method); @@ -30,6 +50,8 @@ private: public: HBITMAP loadImage(int resource, ImageMethod method); + + static vector loadResourceToMemory(LPCTSTR lpName, LPCTSTR lpType); int getWidth(int resource); int getHeight(int resource); diff --git a/code/importformats.cpp b/code/importformats.cpp index 7893ce2..7ef0b26 100644 --- a/code/importformats.cpp +++ b/code/importformats.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/importformats.h b/code/importformats.h index 7b1fa82..ec4fb84 100644 --- a/code/importformats.h +++ b/code/importformats.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2017 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/infoserver.cpp b/code/infoserver.cpp index 416a4c6..3f82969 100644 --- a/code/infoserver.cpp +++ b/code/infoserver.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -109,7 +109,7 @@ InfoTeam::InfoTeam(int id) : InfoBaseCompetitor(id) { } -bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &includeCls, const set &ctrls) { +bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &includeCls, const set &ctrls, bool allowDeletion) { bool changed = false; if (oe.getName() != name) { name = oe.getName(); @@ -170,6 +170,8 @@ bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &incl vector cls; oe.getClasses(cls, false); for (size_t k = 0; k < cls.size(); k++) { + if (cls[k]->getQualificationFinal()) + continue; int wid = cls[k]->getId(); if (!includeCls.count(wid)) continue; @@ -207,8 +209,13 @@ bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &incl // Check if something was deleted for (map::iterator it = organizations.begin(); it != organizations.end();) { if (!knownId.count(it->first)) { + int oid = it->first; organizations.erase(it++); - forceComplete = true; + + if (allowDeletion) + deleteMap.emplace_back("org", oid); + else + forceComplete = true; } else ++it; @@ -218,8 +225,12 @@ bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &incl vector t; oe.getTeams(0, t, false); for (size_t k = 0; k < t.size(); k++) { - if (!includeCls.count(t[k]->getClassId(true))) + int cid = t[k]->getClassId(true); + if (!includeCls.count(cid)) continue; + if (cid != 0 && t[k]->getClassRef(false)->getQualificationFinal() != nullptr) + continue; + int wid = t[k]->getId(); knownId.insert(wid); map::iterator res = teams.find(wid); @@ -232,8 +243,13 @@ bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &incl // Check if something was deleted for (map::iterator it = teams.begin(); it != teams.end();) { if (!knownId.count(it->first)) { + int tid = it->first; teams.erase(it++); - forceComplete = true; + + if (allowDeletion) + deleteMap.emplace_back("tm", tid); + else + forceComplete = true; } else ++it; @@ -243,8 +259,12 @@ bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &incl vector r; oe.getRunners(0, 0, r, false); for (size_t k = 0; k < r.size(); k++) { - if (!includeCls.count(r[k]->getClassId(true))) + int cid = r[k]->getClassId(true); + if (!includeCls.count(cid)) continue; + if (cid != 0 && r[k]->getClassRef(true)->getQualificationFinal() != nullptr) + continue; + int wid = r[k]->getId(); knownId.insert(wid); map::iterator res = competitors.find(wid); @@ -257,15 +277,19 @@ bool InfoCompetition::synchronize(oEvent &oe, bool onlyCmp, const set &incl // Check if something was deleted for (map::iterator it = competitors.begin(); it != competitors.end();) { if (!knownId.count(it->first)) { + int rid = it->first; competitors.erase(it++); - forceComplete = true; + if (allowDeletion) + deleteMap.emplace_back("cmp", rid); + else + forceComplete = true; } else ++it; } knownId.clear(); - return !toCommit.empty() || forceComplete; + return !toCommit.empty() || forceComplete || !deleteMap.empty(); } void InfoCompetition::needCommit(InfoBase &obj) { @@ -499,7 +523,8 @@ bool InfoBaseCompetitor::synchronizeBase(oAbstractRunner &bc) { bool InfoCompetitor::synchronize(bool useTotalResults, bool useCourse, oRunner &r) { bool ch = synchronizeBase(r); - changeTotalSt = r.getEvent()->hasPrevStage() || r.getLegNumber()>0; // Always write full attributes + bool isQF = r.getClassRef(false) && r.getClassRef(false)->getQualificationFinal() != nullptr; + changeTotalSt = r.getEvent()->hasPrevStage() || (r.getLegNumber()>0 && !isQF); // Always write full attributes int s = StatusOK; int legInput = 0; @@ -521,7 +546,7 @@ bool InfoCompetitor::synchronize(bool useTotalResults, bool useCourse, oRunner & legInput = r.getTotalTimeInput() * 10; s = r.getTotalStatusInput(); } - else if (t && r.getLegNumber() > 0) { + else if (t && !isQF && r.getLegNumber() > 0) { legInput = t->getLegRunningTime(r.getLegNumber() - 1, false) * 10; s = t->getLegStatus(r.getLegNumber() - 1, false); } @@ -549,7 +574,7 @@ bool InfoCompetitor::synchronize(const InfoCompetition &cmp, oRunner &r) { vector newRT; if (r.getClassId(false) > 0) { - const vector &radios = cmp.getControls(r.getClassId(false), r.getLegNumber()); + const vector &radios = cmp.getControls(r.getClassId(true), r.getLegNumber()); for (size_t k = 0; k < radios.size(); k++) { RadioTime radioTime; RunnerStatus s_split; @@ -693,12 +718,21 @@ void InfoCompetition::getDiffXML(xmlbuffer &xml) { return; } xml.setComplete(false); + + vector> prop = { make_pair("id", L""), make_pair("delete", L"true") }; + for (auto &dm : deleteMap) { + prop[0].second = itow(dm.second); + xml.startTag(dm.first.c_str(), prop); + xml.endTag(); + } + for (list::iterator it = toCommit.begin(); it != toCommit.end(); ++it) { (*it)->serialize(xml, true); } } void InfoCompetition::commitComplete() { + deleteMap.clear(); toCommit.clear(); forceComplete = false; } @@ -767,8 +801,6 @@ void xmlbuffer::write(const char *tag, blocks.back().value = value; } - - void xmlbuffer::startXML(xmlparser &xml, const wstring &dest) { xml.openOutput(dest.c_str(), false); if (complete) { diff --git a/code/infoserver.h b/code/infoserver.h index fd1742b..5392176 100644 --- a/code/infoserver.h +++ b/code/infoserver.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -227,6 +227,7 @@ protected: map organizations; map competitors; map teams; + vector> deleteMap; void needCommit(InfoBase &obj); @@ -240,10 +241,10 @@ protected: void includeCourse(bool inc) { withCourse = inc; } const vector &getControls(int classId, int legNumber) const; - bool synchronize(oEvent &oe, bool onlyCmp, const set &classes, const set &ctrls); + bool synchronize(oEvent &oe, bool onlyCmp, const set &classes, const set &ctrls, bool allowDeletion); bool synchronize(oEvent &oe) { set dmy; - return synchronize(oe, true, dmy, dmy); + return synchronize(oe, true, dmy, dmy, false); } void getCompleteXML(xmlbuffer &xml); void getDiffXML(xmlbuffer &xml); diff --git a/code/inthashmap.h b/code/inthashmap.h index 4c4be99..5b01b25 100644 --- a/code/inthashmap.h +++ b/code/inthashmap.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/intkeymap.hpp b/code/intkeymap.hpp index 197e8e3..1db8275 100644 --- a/code/intkeymap.hpp +++ b/code/intkeymap.hpp @@ -1,8 +1,7 @@ #pragma once - /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2015 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,10 +17,11 @@ along with this program. If not, see . Melin Software HB - software@melin.nu - www.melin.nu - Stigbergsvgen 7, SE-75242 UPPSALA, Sweden + Eksoppsvgen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ + template class intkeymap { private: const static KEY NoKey = -1013; diff --git a/code/intkeymapimpl.hpp b/code/intkeymapimpl.hpp index f73d88f..73c0156 100644 --- a/code/intkeymapimpl.hpp +++ b/code/intkeymapimpl.hpp @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2015 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,10 +18,11 @@ along with this program. If not, see . Melin Software HB - software@melin.nu - www.melin.nu - Stigbergsvgen 7, SE-75242 UPPSALA, Sweden + Eksoppsvgen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ + #include "stdafx.h" #include "intkeymap.hpp" diff --git a/code/iof30interface.cpp b/code/iof30interface.cpp index 5c08d0c..19127f6 100644 --- a/code/iof30interface.cpp +++ b/code/iof30interface.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -156,7 +156,7 @@ void IOF30Interface::classCourseAssignment(gdioutput &gdi, xmlList &xAssignment, wstring cname; xClsAssignment.getObjectString("ClassName", cname); if (cname.length() > 0) { - pClass pc = oe.getClassCreate(0, cname); + pClass pc = oe.getClassCreate(0, cname, matchedClasses); if (pc) cls2Stages.insert(make_pair(pc->getId(), vector()) ); } @@ -510,7 +510,7 @@ void IOF30Interface::classAssignmentObsolete(gdioutput &gdi, xmlList &xAssignmen wstring cName; xCls[j].getObjectString("Name", cName); int id = xCls[j].getObjectInt("Id"); - pClass cls = oe.getClassCreate(id, cName); + pClass cls = oe.getClassCreate(id, cName, matchedClasses); if (cls) { class2Courses[cls->getId()].push_back(pc); @@ -2314,7 +2314,8 @@ void IOF30Interface::setupRelayClasses(const map > &teamCla if (classId > 0) { pClass pc = oe.getClass(classId); if (!pc) { - pc = oe.getClassCreate(classId, L"tmp" + itow(classId)); + set dmy; + pc = oe.getClassCreate(classId, L"tmp" + itow(classId), dmy); } setupRelayClass(pc, legs); } @@ -2732,13 +2733,13 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o xml.endTag(); } - - pCourse crs = r.getCourse(!unrollLoops); + bool doUnroll = unrollLoops && r.getNumShortening() == 0; + pCourse crs = r.getCourse(!doUnroll); if (crs) { if (includeCourse) writeCourse(xml, *crs); - const vector &sp = r.getSplitTimes(unrollLoops); + const vector &sp = r.getSplitTimes(doUnroll); if (r.getStatus()>0 && r.getStatus() != StatusDNS && r.getStatus() != StatusCANCEL && r.getStatus() != StatusNotCompetiting) { diff --git a/code/iof30interface.h b/code/iof30interface.h index 0396cc7..ef66d59 100644 --- a/code/iof30interface.h +++ b/code/iof30interface.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -65,6 +65,8 @@ class IOF30Interface { bool includeStageRaceInfo; void operator=(const IOF30Interface &); + set matchedClasses; + struct LegInfo { int maxRunners; int minRunners; diff --git a/code/license.txt b/code/license.txt index dfcb5b3..5f4e616 100644 --- a/code/license.txt +++ b/code/license.txt @@ -7,7 +7,7 @@ Third Party Code. Additional copyright notices and license terms applicable to p All trademarks and registered trademarks mentioned herein are the property of their respective owners. ------------------------------------ -Copyright 2007-2017 Melin Software HB. +Copyright 2007-2019 Melin Software HB. ------------------------------------ diff --git a/code/listeditor.cpp b/code/listeditor.cpp index 695c3f9..381c0f1 100644 --- a/code/listeditor.cpp +++ b/code/listeditor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1069,6 +1069,7 @@ void ListEditor::editListProp(gdioutput &gdi, bool newList) { xp = x1 + margin; gdi.setCX(xp); yp += int(1.3 * gdi.getLineHeight()); + gdi.dropLine(1.3); } } diff --git a/code/listeditor.h b/code/listeditor.h index 8e20c4e..d004258 100644 --- a/code/listeditor.h +++ b/code/listeditor.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/liveresult.cpp b/code/liveresult.cpp index ac57f5d..50fab00 100644 --- a/code/liveresult.cpp +++ b/code/liveresult.cpp @@ -1,6 +1,6 @@ /********************i**************************************************** MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -91,8 +91,7 @@ void LiveResult::showTimer(gdioutput &gdi, const oListInfo &liIn) { lastTime = 0; vector pp; - oe->synchronizeList(oLRunnerId, true, false); - oe->synchronizeList(oLPunchId, false, true); + oe->synchronizeList({ oListId::oLRunnerId, oListId::oLPunchId }); oe->getLatestPunches(lastTime, pp); processedPunches.clear(); diff --git a/code/liveresult.h b/code/liveresult.h index a5dafc6..f20d847 100644 --- a/code/liveresult.h +++ b/code/liveresult.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/localizer.cpp b/code/localizer.cpp index 2e84ba9..a344784 100644 --- a/code/localizer.cpp +++ b/code/localizer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -249,7 +249,7 @@ const wstring &LocalizerImpl::translate(const wstring &str, bool &found) return value[i]; } #ifdef _DEBUG - if (key.length() > 1) + if (key.length() > 1 && _wtoi(key.c_str()) == 0) unknown[key] = L""; #endif diff --git a/code/localizer.h b/code/localizer.h index ced2f4e..5b00ad8 100644 --- a/code/localizer.h +++ b/code/localizer.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/meos.cpp b/code/meos.cpp index f61586c..a51e4fb 100644 --- a/code/meos.cpp +++ b/code/meos.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -154,9 +154,9 @@ void LoadPage(gdioutput &gdi, TabType type) { // Path to settings file static wchar_t settings[260]; // Startup path -static wchar_t programPath[MAX_PATH]; +wchar_t programPath[MAX_PATH]; // Exe path -static wchar_t exePath[MAX_PATH]; +wchar_t exePath[MAX_PATH]; void mainMessageLoop(HACCEL hAccelTable, DWORD time) { @@ -830,7 +830,7 @@ gdioutput *getExtraWindow(const string &tag, bool toForeGround) { return 0; } -gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x, int max_y) { +gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x, int max_y, bool fixedSize) { if (getExtraWindow(tag, false) != 0) throw meosException("Window already exists"); @@ -847,22 +847,31 @@ gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x, for (size_t k = 0; kgetHWNDTarget(); - RECT rc; - if (GetWindowRect(hWnd, &rc)) { - xp = max(rc.left + 16, xp); - yp = max(rc.top + 32, yp); + RECT rcc; + if (GetWindowRect(hWnd, &rcc)) { + xp = max(rcc.left + 16, xp); + yp = max(rcc.top + 32, yp); } } } - int xs = gEvent->getPropertyInt("xsize", max(850, min(int(rc.right)-yp, 1124))); - int ys = gEvent->getPropertyInt("ysize", max(650, min(int(rc.bottom)-yp-40, 800))); + if (xp > rc.right - 100) + xp = rc.right - 100; - if (max_x>0) - xs = min(max_x, xs); - if (max_y>0) - ys = min(max_y, ys); + if (yp > rc.bottom - 100) + yp = rc.bottom - 100; + int xs = max_x, ys = max_y; + if (!fixedSize) { + xs = gEvent->getPropertyInt("xsize", max(850, min(int(rc.right) - yp, 1124))); + ys = gEvent->getPropertyInt("ysize", max(650, min(int(rc.bottom) - yp - 40, 800))); + + if (max_x > 0) + xs = min(max_x, xs); + if (max_y > 0) + ys = min(max_y, ys); + } + hWnd = CreateWindowEx(0, szWorkSpaceClass, title.c_str(), WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS, xp, yp, max(xs, 200), max(ys, 100), 0, NULL, hInst, NULL); @@ -944,7 +953,8 @@ void InsertSICard(gdioutput &gdi, SICard &sic); //static int xPos=0, yPos=0; void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker, - bool skipEconomy, bool skipLists, bool skipRunners, bool skipControls) + bool skipEconomy, bool skipLists, + bool skipRunners, bool skipCourses, bool skipControls) { static bool onlyMainP = false; static bool skipTeamP = false; @@ -952,11 +962,12 @@ void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker, static bool skipEconomyP = false; static bool skipListsP = false; static bool skipRunnersP = false; + static bool skipCoursesP = false; static bool skipControlsP = false; if (!force && onlyMain==onlyMainP && skipTeam==skipTeamP && skipSpeaker==skipSpeakerP && skipEconomy==skipEconomyP && skipLists==skipListsP && - skipRunners==skipRunnersP && skipControls==skipControlsP) + skipRunners==skipRunnersP && skipControls==skipControlsP && skipCourses == skipCoursesP) return; onlyMainP = onlyMain; @@ -965,6 +976,7 @@ void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker, skipEconomyP = skipEconomy; skipListsP = skipLists; skipRunnersP = skipRunners; + skipCoursesP = skipCourses; skipControlsP = skipControls; int oldid=TabCtrl_GetCurSel(hMainTab); @@ -996,6 +1008,9 @@ void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker, if (skipRunners && it->getType() == typeid(TabRunner)) continue; + if (skipCourses && it->getType() == typeid(TabCourse)) + continue; + if (skipControls && it->getType() == typeid(TabControl)) continue; @@ -1055,7 +1070,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ic.dwICC=ICC_TAB_CLASSES ; InitCommonControlsEx(&ic); hMainTab=CreateWindowEx(0, WC_TABCONTROL, L"tabs", WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 300, 20, hWnd, 0, hInst, 0); - createTabs(true, true, false, false, false, false, false, false); + createTabs(true, true, false, false, false, false, false, false, false); SetTimer(hWnd, 4, 10000, 0); //Connection check break; @@ -1278,7 +1293,7 @@ void scrollVertical(gdioutput *gdi, int yInc, HWND hWnd) { if (si.nPage==0) yInc = 0; - int yPos=gdi->GetOffsetY(); + int yPos=gdi->getOffsetY(); int a=si.nMax-signed(si.nPage-1) - yPos; if ( (yInc = max( -yPos, min(yInc, a)))!=0 ) { @@ -1289,9 +1304,9 @@ void scrollVertical(gdioutput *gdi, int yInc, HWND hWnd) { ScrollArea.top=-gdi->getHeight()-100; ScrollArea.bottom+=gdi->getHeight(); - ScrollArea.right=gdi->getWidth()-gdi->GetOffsetX()+15; + ScrollArea.right=gdi->getWidth()-gdi->getOffsetX()+15; ScrollArea.left = -2000; - gdi->SetOffsetY(yPos); + gdi->setOffsetY(yPos); bool inv = true; //Inv = false works only for lists etc. where there are not controls in the scroll area. @@ -1331,7 +1346,7 @@ void updateScrollInfo(HWND hWnd, gdioutput &gdi, int nHeight, int nWidth) { if (maxy>0) { si.nMax=maxy+nHeight; - si.nPos=gdi.GetOffsetY(); + si.nPos=gdi.getOffsetY(); si.nPage=nHeight; } else { @@ -1344,7 +1359,7 @@ void updateScrollInfo(HWND hWnd, gdioutput &gdi, int nHeight, int nWidth) { si.nMin=0; if (maxx>0) { si.nMax=maxx+nWidth; - si.nPos=gdi.GetOffsetX(); + si.nPos=gdi.getOffsetX(); si.nPage=nWidth; } else { @@ -1431,7 +1446,7 @@ LRESULT CALLBACK WorkSpaceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM //int hwndScrollBar = (HWND) lParam; // handle to scroll bar int yInc; - int yPos=gdi->GetOffsetY(); + int yPos=gdi->getOffsetY(); RECT rc; GetClientRect(hWnd, &rc); int pagestep = max(50, int(0.9*rc.bottom)); @@ -1478,7 +1493,7 @@ LRESULT CALLBACK WorkSpaceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM } scrollVertical(gdi, yInc, hWnd); - gdi->storeAutoPos(gdi->GetOffsetY()); + gdi->storeAutoPos(gdi->getOffsetY()); break; } @@ -1488,7 +1503,7 @@ LRESULT CALLBACK WorkSpaceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM //int hwndScrollBar = (HWND) lParam; // handle to scroll bar int xInc; - int xPos=gdi->GetOffsetX(); + int xPos=gdi->getOffsetX(); switch(nScrollCode) { @@ -1552,7 +1567,7 @@ LRESULT CALLBACK WorkSpaceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM GetClientRect(hWnd, &ScrollArea); ClipArea=ScrollArea; - gdi->SetOffsetX(xPos); + gdi->setOffsetX(xPos); ScrollWindowEx (hWnd, -xInc, 0, 0, &ClipArea, @@ -1571,7 +1586,7 @@ LRESULT CALLBACK WorkSpaceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM case WM_MOUSEWHEEL: { int dz = GET_WHEEL_DELTA_WPARAM(wParam); scrollVertical(gdi, -dz, hWnd); - gdi->storeAutoPos(gdi->GetOffsetY()); + gdi->storeAutoPos(gdi->getOffsetY()); } break; diff --git a/code/meos.rc b/code/meos.rc index 7666a39..d0cc44d 100644 --- a/code/meos.rc +++ b/code/meos.rc @@ -169,7 +169,7 @@ BEGIN VALUE "FileDescription", "meos" VALUE "FileVersion", "3.3.0.1" VALUE "InternalName", "meos" - VALUE "LegalCopyright", "Copyright 2007-2018" + VALUE "LegalCopyright", "Copyright 2007-2019" VALUE "OriginalFilename", "meos.exe" VALUE "ProductName", " meos" VALUE "ProductVersion", "3.4.0.1" diff --git a/code/meos_util.cpp b/code/meos_util.cpp index 2d2358b..7b28af5 100644 --- a/code/meos_util.cpp +++ b/code/meos_util.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1825,6 +1825,20 @@ void capitalize(wstring &str) { } } +bool checkValidDate(const wstring &date) { + SYSTEMTIME st; + if (convertDateYMS(date, st, false) <= 0) + return false; + + st.wHour = 12; + SYSTEMTIME utc; + if (!TzSpecificLocalTimeToSystemTime(0, &st, &utc)) { + return false; + } + + return true; +} + /** Return bias in seconds. UTC = local time + bias. */ int getTimeZoneInfo(const wstring &date) { static wchar_t lastDate[16] = {0}; @@ -1839,7 +1853,10 @@ int getTimeZoneInfo(const wstring &date) { convertDateYMS(date, st, false); st.wHour = 12; SYSTEMTIME utc; - TzSpecificLocalTimeToSystemTime(0, &st, &utc); + if (!TzSpecificLocalTimeToSystemTime(0, &st, &utc)) { + lastValue = 0; + return 0; + } int datecode = ((st.wYear * 12 + st.wMonth) * 31) + st.wDay; int datecodeUTC = ((utc.wYear * 12 + utc.wMonth) * 31) + utc.wDay; diff --git a/code/meos_util.h b/code/meos_util.h index 39950a7..9b58a2d 100644 --- a/code/meos_util.h +++ b/code/meos_util.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,17 +53,10 @@ public: }; string convertSystemTimeN(const SYSTEMTIME &st); - -/* -string convertSystemTime(const SYSTEMTIME &st); -string convertSystemTimeOnly(const SYSTEMTIME &st); -string convertSystemDate(const SYSTEMTIME &st); -string getLocalTime(); -string getLocalDate(); -string getLocalTimeOnly(); -*/ string getLocalTimeN(); +bool checkValidDate(const wstring &date); + wstring convertSystemTime(const SYSTEMTIME &st); wstring convertSystemTimeOnly(const SYSTEMTIME &st); wstring convertSystemDate(const SYSTEMTIME &st); @@ -83,7 +76,6 @@ const wstring &getTimeMS(int m); const wstring &formatTime(int rt); const wstring &formatTimeHMS(int rt); -//const string &formatTimeN(int rt); wstring formatTimeIOF(int rt, int zeroTime); int convertDateYMS(const string &m, bool checkValid); @@ -92,7 +84,6 @@ int convertDateYMS(const string &m, SYSTEMTIME &st, bool checkValid); int convertDateYMS(const wstring &m, bool checkValid); int convertDateYMS(const wstring &m, SYSTEMTIME &st, bool checkValid); - // Convert a "general" time string to a MeOS compatible time string void processGeneralTime(const wstring &generalTime, wstring &meosTime, wstring &meosDate); @@ -110,11 +101,6 @@ int convertAbsoluteTimeMS(const wstring &m); // Parses a time on format HH:MM:SS+01:00Z or HHMMSS+0100Z (but ignores time zone) int convertAbsoluteTimeISO(const wstring &m); -//Returns a time converted from +/-MM:SS or NOTIME, in seconds -//int convertAbsoluteTimeMS(const string &m); -// Parses a time on format HH:MM:SS+01:00Z or HHMMSS+0100Z (but ignores time zone) -//int convertAbsoluteTimeISO(const string &m); - /** Returns a time converted from HH:MM:SS or -1, in seconds @param m time to convert @param daysZeroTime -1 do not support days syntax, positive interpret days w.r.t the specified zero time. @@ -160,7 +146,7 @@ wstring getMeosFullVersion(); wstring getMajorVersion(); wstring getMeosCompectVersion(); -void getSupporters(vector &supp, vector developSupp); +void getSupporters(vector &supp, vector &developSupp); int countWords(const wchar_t *p); @@ -255,11 +241,6 @@ void capitalize(wstring &str); /** Initial capital letter for each word. */ void capitalizeWords(wstring &str); -/* -void capitalize(string &str); -void capitalizeWords(string &str);*/ - - wstring getTimeZoneString(const wstring &date); /** Return bias in seconds. UTC = local time + bias. */ diff --git a/code/meosdb/MeosSQL.cpp b/code/meosdb/MeosSQL.cpp index ab6aa7d..e6ee3ef 100644 --- a/code/meosdb/MeosSQL.cpp +++ b/code/meosdb/MeosSQL.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1902,7 +1902,7 @@ OpFailStatus MeosSQL::syncUpdate(oRunner *r, bool forceWriteAll) mysqlpp::Query queryset = con.query(); queryset << " Name=" << quote << toString(r->sName) << ", " - << " CardNo=" << r->CardNo << ", " + << " CardNo=" << r->cardNumber << ", " << " StartNo=" << r->StartNo << ", " << " StartTime=" << r->startTime << ", " << " FinishTime=" << r->FinishTime << ", " @@ -3680,6 +3680,11 @@ bool MeosSQL::skipSynchronize(const oBase &entity) const { notskipped++; return false; } +namespace { + int encode(oListId id) { + return int(id); + } +} int MeosSQL::getModifiedMask(oEvent &oe) { try { @@ -3699,23 +3704,23 @@ int MeosSQL::getModifiedMask(oEvent &oe) { int e = r["oEvent"]; if (ctrl > oe.sqlCounterControls) - res |= oLControlId; + res |= encode(oListId::oLControlId); if (crs > oe.sqlCounterCourses) - res |= oLCourseId; + res |= encode(oListId::oLCourseId); if (cls > oe.sqlCounterClasses) - res |= oLClassId; + res |= encode(oListId::oLClassId); if (card > oe.sqlCounterCards) - res |= oLCardId; + res |= encode(oListId::oLCardId); if (club > oe.sqlCounterClubs) - res |= oLClubId; + res |= encode(oListId::oLClubId); if (punch > oe.sqlCounterPunches) - res |= oLPunchId; + res |= encode(oListId::oLPunchId); if (runner > oe.sqlCounterRunners) - res |= oLRunnerId; + res |= encode(oListId::oLRunnerId); if (t > oe.sqlCounterTeams) - res |= oLTeamId; + res |= encode(oListId::oLTeamId); if (e > oe.counter) - res |= oLEventId; + res |= encode(oListId::oLEventId); return res; } diff --git a/code/meosdb/MeosSQL.h b/code/meosdb/MeosSQL.h index 9dee2e2..ef90b9d 100644 --- a/code/meosdb/MeosSQL.h +++ b/code/meosdb/MeosSQL.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2012 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ Eksoppsvägen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ + #pragma warning( disable : 4251) #include diff --git a/code/meosdb/meosdb.cpp b/code/meosdb/meosdb.cpp index 918ad6c..1e0dc2f 100644 --- a/code/meosdb/meosdb.cpp +++ b/code/meosdb/meosdb.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,7 +55,6 @@ using namespace std; Localizer lang; #endif -extern "C"{ int MEOSDB_API getMeosVersion() { @@ -70,27 +69,27 @@ int getListMask(oEvent &oe) { return msql.getModifiedMask(oe); } -bool MEOSDB_API msSynchronizeList(oEvent *oe, int lid) +bool MEOSDB_API msSynchronizeList(oEvent *oe, oListId lid) { nSynchList++; if (nSynchList % 100 == 99) OutputDebugString(L"Synchronized 100 lists\n"); - if (lid==oLRunnerId) + if (lid == oListId::oLRunnerId) return msql.syncListRunner(oe); - else if (lid==oLClassId) + else if (lid == oListId::oLClassId) return msql.syncListClass(oe); - else if (lid==oLCourseId) + else if (lid == oListId::oLCourseId) return msql.syncListCourse(oe); - else if (lid==oLControlId) + else if (lid == oListId::oLControlId) return msql.syncListControl(oe); - else if (lid==oLClubId) + else if (lid == oListId::oLClubId) return msql.syncListClub(oe); - else if (lid==oLCardId) + else if (lid == oListId::oLCardId) return msql.syncListCard(oe); - else if (lid==oLPunchId) + else if (lid == oListId::oLPunchId) return msql.syncListPunch(oe); - else if (lid==oLTeamId) + else if (lid == oListId::oLTeamId) return msql.syncListTeam(oe); return false; @@ -227,7 +226,6 @@ bool MEOSDB_API msReConnect() } -} //Extern "C" bool repairTables(const string &db, vector &output) { return msql.repairTables(db, output); diff --git a/code/meosdb/sqltypes.h b/code/meosdb/sqltypes.h index 34f9c63..3188a0c 100644 --- a/code/meosdb/sqltypes.h +++ b/code/meosdb/sqltypes.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,11 +33,10 @@ enum OpFailStatus { class oEvent; class oBase; -extern "C"{ #define MEOSDB_API int MEOSDB_API getMeosVersion(); - bool MEOSDB_API msSynchronizeList(oEvent *, int lid); +// bool MEOSDB_API msSynchronizeList(oEvent *, oListId lid); int MEOSDB_API msSynchronizeUpdate(oBase *); int MEOSDB_API msSynchronizeRead(oBase *obj); int MEOSDB_API msRemove(oBase *obj); @@ -52,7 +51,7 @@ extern "C"{ int MEOSDB_API msListCompetitions(oEvent *oe); int getListMask(oEvent &oe); -} + bool repairTables(const string &db, vector &output); diff --git a/code/meosdb/targetver.h b/code/meosdb/targetver.h index 22ac54f..55afdfb 100644 --- a/code/meosdb/targetver.h +++ b/code/meosdb/targetver.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/meosexception.h b/code/meosexception.h index 8ecb9a8..b6cddcd 100644 --- a/code/meosexception.h +++ b/code/meosexception.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/meosversion.cpp b/code/meosversion.cpp index 7bfdab8..255dd37 100644 --- a/code/meosversion.cpp +++ b/code/meosversion.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,9 +27,10 @@ //V2: ABCDEFGHIHJKMN //V31: a //V33: abcde -//V35: abcdefgh +//V35: abcdef +//V36: abcdef int getMeosBuild() { - string revision("$Rev: 810 $"); + string revision("$Rev: 859 $"); return 174 + atoi(revision.substr(5, string::npos).c_str()); } @@ -41,16 +42,16 @@ int getMeosBuild() { //V33: abcdefghij //V34: abcdfge wstring getMeosDate() { - wstring date(L"$Date: 2019-01-10 22:42:30 +0100 (to, 10 jan 2019) $"); + wstring date(L"$Date: 2019-03-19 21:52:12 +0100 (ti, 19 mar 2019) $"); return date.substr(7,10); } wstring getBuildType() { - return L"U3"; // No parantheses (...) + return L"RC1"; // No parantheses (...) } wstring getMajorVersion() { - return L"3.5"; + return L"3.6"; } wstring getMeosFullVersion() { @@ -70,24 +71,8 @@ wstring getMeosCompectVersion() { return getMajorVersion() + L"." + itow(getMeosBuild()) + L" (" + getBuildType() + L")"; } -void getSupporters(vector &supp, vector developSupp) +void getSupporters(vector &supp, vector &developSupp) { - supp.emplace_back(L"Järfälla OK"); - supp.emplace_back(L"Anders Larsson, OK Nackhe"); - supp.emplace_back(L"Hans Wilhelmsson"); - supp.emplace_back(L"Patrice Lavallee, Noyon Course d'Orientation"); - supp.emplace_back(L"IFK Linköpings OS"); - supp.emplace_back(L"Lars Ove Karlsson, Västerås SOK"); - supp.emplace_back(L"OK Djerf"); - supp.emplace_back(L"OK Vivill"); - supp.emplace_back(L"Sonny Andersson, Huskvarna"); - supp.emplace_back(L"Hässleholms OK Skolorientering"); - supp.emplace_back(L"IBM-klubben Orientering"); - supp.emplace_back(L"OK Øst, Birkerød"); - supp.emplace_back(L"OK Klemmingen"); - supp.emplace_back(L"Hans Johansson"); - supp.emplace_back(L"KOB Kysak"); - supp.emplace_back(L"Per Ivarsson, Trollhättans SOK"); supp.emplace_back(L"Sergio Yañez, ABC TRAIL"); supp.emplace_back(L"Western Race Services"); supp.emplace_back(L"IK Gandvik, Skara"); @@ -113,6 +98,7 @@ void getSupporters(vector &supp, vector developSupp) supp.emplace_back(L"SOS Jindřichův Hradec"); supp.emplace_back(L"Mats Holmberg, OK Gränsen"); supp.emplace_back(L"Christoffer Ohlsson, Uddevalla OK"); + supp.emplace_back(L"KOB ATU Košice"); supp.emplace_back(L"O-Ringen AB"); supp.emplace_back(L"Hans Carlstedt, Sävedalens AIK"); supp.emplace_back(L"IFK Mora OK"); @@ -129,6 +115,7 @@ void getSupporters(vector &supp, vector developSupp) supp.emplace_back(L"Leksands OK"); supp.emplace_back(L"O-Travel"); supp.emplace_back(L"Kamil Pipek, OK Lokomotiva Pardubice"); + developSupp.emplace_back(L"KOB Kysak"); supp.emplace_back(L"Richard HEYRIES"); supp.emplace_back(L"Ingemar Carlsson"); supp.emplace_back(L"Tolereds AIK"); @@ -137,8 +124,9 @@ void getSupporters(vector &supp, vector developSupp) supp.emplace_back(L"Helsingborgs SOK"); supp.emplace_back(L"Sala OK"); supp.emplace_back(L"OK Roskilde"); - supp.emplace_back(L"Almby IK, Örebro"); + developSupp.emplace_back(L"Almby IK, Örebro"); supp.emplace_back(L"Ligue PACA"); - + supp.emplace_back(L"SC vebr-sport"); + supp.emplace_back(L"IP Skogen Göteborg"); reverse(supp.begin(), supp.end()); } diff --git a/code/metalist.cpp b/code/metalist.cpp index 55aaaf6..3dae1e5 100644 --- a/code/metalist.cpp +++ b/code/metalist.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -69,6 +69,10 @@ oListParam::oListParam() { timePerPage = 8000; margin = 5; screenMode = 0; + + htmlScale = 1.0; + htmlRows = 60; + htmlTypeTag = "free"; } void oListParam::serialize(xmlparser &xml, @@ -77,10 +81,10 @@ void oListParam::serialize(xmlparser &xml, xml.startTag("ListParam", "Name", name); xml.write("ListId", container.getUniqueId(listCode)); string sel; - for (set::const_iterator it = selection.begin(); it != selection.end(); ++it) { + for (int s : selection) { if (!sel.empty()) sel += ";"; - sel += itos(*it); + sel += itos(s); } xml.write("ClassId", sel); xml.write("LegNumber", legNumber); @@ -90,13 +94,15 @@ void oListParam::serialize(xmlparser &xml, xml.write("Title", title); xml.writeBool("Large", useLargeSize); xml.writeBool("PageBreak", pageBreak); + xml.writeBool("HideHeader", !showHeader); + xml.writeBool("ShowNamedSplits", showInterTimes); xml.writeBool("ShowInterTitle", showInterTitle); xml.writeBool("ShowSplits", showSplitTimes); xml.writeBool("ShowAnalysis", splitAnalysis); xml.write("InputNumber", inputNumber); if (nextList != 0) { - map::const_iterator res = idToIndex.find(nextList); + auto res = idToIndex.find(nextList); if (res != idToIndex.end()) xml.write("NextList", res->second); } @@ -111,13 +117,19 @@ void oListParam::serialize(xmlparser &xml, xml.write("ScreenMode", screenMode); - if (nColumns != 0) { + if (nColumns != 0 || htmlTypeTag != "free") { xml.write("NumColumns", itos(nColumns)); xml.writeBool("Animate", animate); xml.write("TimePerPage", timePerPage); xml.write("Margin", margin); } + if (htmlTypeTag != "free") { + xml.write("HTML", htmlTypeTag); + xml.write("PageRows", htmlRows); + xml.write("Scale", to_string(htmlScale)); + } + xml.endTag(); } @@ -142,6 +154,8 @@ void oListParam::deserialize(const xmlobject &xml, const MetaListContainer &cont useLargeSize = xml.getObjectBool("Large"); pageBreak = xml.getObjectBool("PageBreak"); + showHeader = !xml.getObjectBool("HideHeader"); + showInterTimes = xml.getObjectBool("ShowNamedSplits"); showSplitTimes = xml.getObjectBool("ShowSplits"); splitAnalysis = xml.getObjectBool("ShowAnalysis"); @@ -163,7 +177,7 @@ void oListParam::deserialize(const xmlobject &xml, const MetaListContainer &cont xml.getObjectString("Image", bgImage); - int nColumns = xml.getObjectInt("NumColumns"); + nColumns = xml.getObjectInt("NumColumns"); screenMode = xml.getObjectInt("ScreenMode"); animate = xml.getObjectBool("Animate"); @@ -172,7 +186,22 @@ void oListParam::deserialize(const xmlobject &xml, const MetaListContainer &cont timePerPage = xml.getObjectInt("TimePerPage"); if (xml.got("Margin")) - timePerPage = xml.getObjectInt("Margin"); + margin = xml.getObjectInt("Margin"); + + if (xml.got("HTML")) + xml.getObjectString("HTML", htmlTypeTag); + + if (xml.got("Scale")) { + string tmp; + xml.getObjectString("Scale", tmp); + htmlScale = atof(tmp.c_str()); + if (!(htmlScale > 0.1 && htmlScale < 1000)) + htmlScale = 1.0; + } + + if (xml.got("PageRows")) { + htmlRows = xml.getObjectInt("PageRows"); + } saved = true; } @@ -374,10 +403,18 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par li.calcResults = false; li.calcTotalResults = false; li.rogainingResults = false; + li.calculateLiveResults = false; li.calcCourseClassResults = false; if (par.useControlIdResultFrom > 0 || par.useControlIdResultTo > 0) - li.needPunches = true; - const bool isPunchList = mList.listSubType == oListInfo::EBaseTypePunches; + li.needPunches = oListInfo::PunchMode::SpecificPunch; + const bool isPunchList = mList.listSubType == oListInfo::EBaseTypeCoursePunches || + mList.listSubType == oListInfo::EBaseTypeAllPunches; + if (isPunchList) { + if ((!li.filter(EFilterList::EFilterHasResult) && !li.filter(EFilterList::EFilterHasPrelResult)) + || li.sortOrder == SortOrder::ClassLiveResult) + li.needPunches = oListInfo::PunchMode::AnyPunch; + } + map, int> indexPosToWidth; map< pair, int> linePostCount; for (int i = 0; i<4; i++) { @@ -411,7 +448,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par // Automatically determine what needs to be calculated if (mp.type == lTeamPlace || mp.type == lRunnerPlace || mp.type == lRunnerGeneralPlace) { if (!li.calcResults) { - oe->calculateResults(oEvent::RTClassResult); + oe->calculateResults(set(), oEvent::ResultType::ClassResult); oe->calculateTeamResults(false); } li.calcResults = true; @@ -420,7 +457,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par || mp.type == lTeamTotalPlace || mp.type == lTeamPlaceDiff || mp.type == lRunnerGeneralPlace) { if (!li.calcTotalResults) { - oe->calculateResults(oEvent::RTTotalResult); + oe->calculateResults(set(), oEvent::ResultType::TotalResult); oe->calculateTeamResults(true); } li.calcTotalResults = true; @@ -431,12 +468,13 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par } else if (mp.type == lRunnerClassCoursePlace || mp.type == lRunnerClassCourseTimeAfter) { if (!li.calcCourseClassResults) - oe->calculateResults(oEvent::RTClassCourseResult); + oe->calculateResults(set(), oEvent::ResultType::ClassCourseResult); li.calcCourseClassResults = true; } else if (mp.type == lRunnerTempTimeAfter || mp.type == lRunnerTempTimeStatus) { - li.needPunches = true; + if (li.needPunches == oListInfo::PunchMode::NoPunch) + li.needPunches = oListInfo::PunchMode::SpecificPunch; } string label = "P" + itos(i*1000 + j*100 + k); @@ -515,7 +553,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par for (map, int>::iterator it = linePostCount.begin(); it != linePostCount.end(); ++it) { if (it->second == 1) { int base = it->first.first; - if (base == MLSubList && listSubType == oListInfo::EBaseTypePunches) + if (base == MLSubList && listSubType == oListInfo::EBaseTypeCoursePunches) continue; // This type of list requires actual width indexPosToWidth[tuple(base, it->first.second, 0)] = totalWidth; } @@ -738,6 +776,9 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par it != filter.end(); ++it) { li.setFilter(*it); } + if (li.filter(EFilterList::EFilterAnyResult) || sortOrder == SortOrder::ClassLiveResult) + li.calculateLiveResults = true; + for (set::const_iterator it = subFilter.begin(); it != subFilter.end(); ++it) { li.setSubFilter(*it); @@ -750,7 +791,6 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par hasResults_ = true; } - void Position::indent(int ind) { int end = pos.size() - 1; if (end < 1) @@ -1629,15 +1669,17 @@ void MetaList::initSymbols() { typeToSymbol[lPatrolClubNameNames] = L"PatrolClubNameNames"; typeToSymbol[lRunnerFinish] = L"RunnerFinish"; typeToSymbol[lRunnerTime] = L"RunnerTime"; + typeToSymbol[lRunnerGrossTime] = L"RunnerGrossTime"; typeToSymbol[lRunnerTimeStatus] = L"RunnerTimeStatus"; typeToSymbol[lRunnerTempTimeStatus] = L"RunnerTempTimeStatus"; typeToSymbol[lRunnerTempTimeAfter] = L"RunnerTempTimeAfter"; typeToSymbol[lRunnerTimeAfter] = L"RunnerTimeAfter"; typeToSymbol[lRunnerClassCourseTimeAfter] = L"RunnerClassCourseTimeAfter"; - typeToSymbol[lRunnerMissedTime] = L"RunnerTimeLost"; + typeToSymbol[lRunnerLostTime] = L"RunnerTimeLost"; typeToSymbol[lRunnerPlace] = L"RunnerPlace"; typeToSymbol[lRunnerClassCoursePlace] = L"RunnerClassCoursePlace"; typeToSymbol[lRunnerStart] = L"RunnerStart"; + typeToSymbol[lRunnerCheck] = L"RunnerCheck"; typeToSymbol[lRunnerStartCond] = L"RunnerStartCond"; typeToSymbol[lRunnerStartZero] = L"RunnerStartZero"; typeToSymbol[lRunnerClub] = L"RunnerClub"; @@ -1674,6 +1716,7 @@ void MetaList::initSymbols() { typeToSymbol[lRunnerPayMethod] = L"RunnerPayMethod"; typeToSymbol[lRunnerEntryDate] = L"RunnerEntryDate"; typeToSymbol[lRunnerEntryTime] = L"RunnerEntryTime"; + typeToSymbol[lRunnerId] = L"RunnerId"; typeToSymbol[lTeamName] = L"TeamName"; typeToSymbol[lTeamStart] = L"TeamStart"; @@ -1693,13 +1736,18 @@ void MetaList::initSymbols() { typeToSymbol[lTeamPointAdjustment] = L"TeamPointAdjustment"; typeToSymbol[lTeamTime] = L"TeamTime"; + typeToSymbol[lTeamGrossTime] = L"TeamGrossTime"; typeToSymbol[lTeamStatus] = L"TeamStatus"; typeToSymbol[lTeamClub] = L"TeamClub"; typeToSymbol[lTeamRunner] = L"TeamRunner"; typeToSymbol[lTeamRunnerCard] = L"TeamRunnerCard"; typeToSymbol[lTeamBib] = L"TeamBib"; typeToSymbol[lTeamStartNo] = L"TeamStartNo"; + typeToSymbol[lPunchNamedTime] = L"PunchNamedTime"; + typeToSymbol[lPunchName] = L"PunchName"; + typeToSymbol[lPunchNamedSplit] = L"PunchNamedSplit"; + typeToSymbol[lPunchTime] = L"PunchTime"; typeToSymbol[lPunchControlNumber] = L"PunchControlNumber"; typeToSymbol[lPunchControlCode] = L"PunchControlCode"; @@ -1707,6 +1755,12 @@ void MetaList::initSymbols() { typeToSymbol[lPunchControlPlace] = L"PunchControlPlace"; typeToSymbol[lPunchControlPlaceAcc] = L"PunchControlPlaceAcc"; + typeToSymbol[lPunchSplitTime] = L"PunchSplitTime"; + typeToSymbol[lPunchTotalTime] = L"PunchTotalTime"; + typeToSymbol[lPunchAbsTime] = L"PunchAbsTime"; + typeToSymbol[lPunchTotalTimeAfter] = L"PunchTotalTimeAfter"; + typeToSymbol[lPunchTimeSinceLast] = L"PunchTimeSinceLast"; + typeToSymbol[lRogainingPunch] = L"RogainingPunch"; typeToSymbol[lTotalCounter] = L"TotalCounter"; typeToSymbol[lSubCounter] = L"SubCounter"; @@ -1746,6 +1800,8 @@ void MetaList::initSymbols() { typeToSymbol[lControlRunnersLeft] = L"ControlRunnersLeft"; typeToSymbol[lControlCodes] = L"ControlCodes"; + typeToSymbol[lLineBreak] = L"LineBreak"; + for (map::iterator it = typeToSymbol.begin(); it != typeToSymbol.end(); ++it) { symbolToType[it->second] = it->first; @@ -1760,7 +1816,8 @@ void MetaList::initSymbols() { baseTypeToSymbol[oListInfo::EBaseTypeRunner] = "Runner"; baseTypeToSymbol[oListInfo::EBaseTypeTeam] = "Team"; baseTypeToSymbol[oListInfo::EBaseTypeClub] = "ClubRunner"; - baseTypeToSymbol[oListInfo::EBaseTypePunches] = "Punches"; + baseTypeToSymbol[oListInfo::EBaseTypeCoursePunches] = "CoursePunches"; + baseTypeToSymbol[oListInfo::EBaseTypeAllPunches] = "AllPunches"; baseTypeToSymbol[oListInfo::EBaseTypeNone] = "None"; baseTypeToSymbol[oListInfo::EBaseTypeRunnerGlobal] = "RunnerGlobal"; baseTypeToSymbol[oListInfo::EBaseTypeRunnerLeg] = "RunnerLeg"; @@ -1778,6 +1835,7 @@ void MetaList::initSymbols() { if (symbolToBaseType.size() != oListInfo::EBasedTypeLast_) throw std::exception("Bad symbol setup"); + symbolToBaseType["Punches"] = oListInfo::EBaseTypeCoursePunches; // Back comp MeOS --3.5 orderToSymbol[ClassStartTime] = "ClassStartTime"; orderToSymbol[ClassStartTimeClub] = "ClassStartTimeClub"; @@ -1789,6 +1847,7 @@ void MetaList::initSymbols() { orderToSymbol[SortByFinishTimeReverse] = "FinishTimeReverse"; orderToSymbol[ClassFinishTime] = "ClassFinishTime"; orderToSymbol[SortByStartTime] = "StartTime"; + orderToSymbol[SortByStartTimeClass] = "StartTimeClass"; orderToSymbol[SortByEntryTime] = "EntryTime"; orderToSymbol[ClassPoints] = "ClassPoints"; orderToSymbol[ClassTotalResult] = "ClassTotalResult"; @@ -1796,6 +1855,8 @@ void MetaList::initSymbols() { orderToSymbol[CourseResult] = "CourseResult"; orderToSymbol[CourseStartTime] = "CourseStartTime"; orderToSymbol[ClassTeamLeg] = "ClassTeamLeg"; + orderToSymbol[ClassLiveResult] = "ClassLiveResult"; + orderToSymbol[ClassKnockoutTotalResult] = "ClassKnockoutTotalResult"; orderToSymbol[Custom] = "CustomSort"; for (map::iterator it = orderToSymbol.begin(); @@ -1818,6 +1879,8 @@ void MetaList::initSymbols() { filterToSymbol[EFilterVacant] = "FilterNotVacant"; filterToSymbol[EFilterOnlyVacant] = "FilterOnlyVacant"; filterToSymbol[EFilterHasNoCard] = "FilterNoCard"; + filterToSymbol[EFilterAnyResult] = "FilterAnyResult"; + filterToSymbol[EFilterAPIEntry] = "EFilterAPIEntry"; for (map::iterator it = filterToSymbol.begin(); it != filterToSymbol.end(); ++it) { @@ -1836,6 +1899,8 @@ void MetaList::initSymbols() { subFilterToSymbol[ESubFilterVacant] = "FilterNotVacant"; subFilterToSymbol[ESubFilterSameParallel] = "FilterSameParallel"; subFilterToSymbol[ESubFilterSameParallelNotFirst] = "FilterSameParallelNotFirst"; + subFilterToSymbol[ESubFilterNotFinish] = "FilterNotFinish"; + subFilterToSymbol[ESubFilterNamedControl] = "FilterNamedControl"; for (map::iterator it = subFilterToSymbol.begin(); it != subFilterToSymbol.end(); ++it) { @@ -1875,7 +1940,14 @@ void MetaList::initSymbols() { } } -MetaListContainer::MetaListContainer(oEvent *ownerIn): owner(ownerIn) {} +MetaListContainer::MetaListContainer(oEvent *owner): owner(owner) {} + + +MetaListContainer::MetaListContainer(oEvent *owner, const MetaListContainer &src) { + *this = src; + this->owner = owner; +} + MetaListContainer::~MetaListContainer() {} @@ -2426,7 +2498,8 @@ void MetaList::getBaseType(vector< pair > &types, int ¤tT types.clear(); for(map::const_iterator it = baseTypeToSymbol.begin(); it != baseTypeToSymbol.end(); ++it) { - if (it->first == oListInfo::EBaseTypeNone || it->first == oListInfo::EBaseTypePunches) + if (it->first == oListInfo::EBaseTypeNone || it->first == oListInfo::EBaseTypeCoursePunches + || it->first == oListInfo::EBaseTypeAllPunches) continue; types.push_back(make_pair(lang.tl(it->second), it->first)); } @@ -2444,7 +2517,11 @@ void MetaList::getSubType(vector< pair > &types, int ¤tTy tt.insert(t); types.push_back(make_pair(lang.tl(baseTypeToSymbol[t]), t)); - t = oListInfo::EBaseTypePunches; + t = oListInfo::EBaseTypeCoursePunches; + tt.insert(t); + types.push_back(make_pair(lang.tl(baseTypeToSymbol[t]), t)); + + t = oListInfo::EBaseTypeAllPunches; tt.insert(t); types.push_back(make_pair(lang.tl(baseTypeToSymbol[t]), t)); @@ -2493,7 +2570,7 @@ void MetaListContainer::removeParam(int index) { throw meosException("No such parameters exist"); } -void MetaListContainer::addListParam(oListParam ¶m) { +int MetaListContainer::addListParam(oListParam ¶m) { param.saved = true; int ix = 0; if (!listParam.empty()) @@ -2502,6 +2579,7 @@ void MetaListContainer::addListParam(oListParam ¶m) { listParam[ix] = param; if (owner) owner->updateChanged(); + return ix; } const oListParam &MetaListContainer::getParam(int index) const { @@ -2606,3 +2684,79 @@ bool MetaList::supportClasses() const { else return true; } + +EPostType MetaList::getTypeFromSymbol(wstring &symb) noexcept { + auto res = symbolToType.find(symb); + if (res == symbolToType.end()) + return EPostType::lNone; // Error code + + return res->second; +} + +void MetaList::fillSymbols(vector < pair> &symb) { + for (auto s : symbolToType) { + if (s.second == lAlignNext || s.second == lNone || s.second == lLineBreak) + continue; + wstring desc = L"[" + s.first + L"]\t" + lang.tl(s.first); + symb.emplace_back(desc, size_t(s.second)); + } +} + +void MetaListContainer::synchronizeTo(MetaListContainer &dst) const { + auto &dstData = dst.data; + map dstLst; + map dstLstId; + + for (size_t k = 0; k < dstData.size(); k++) { + auto &d = dstData[k]; + if (d.first == MetaListType::ExternalList) { + wstring n = d.second.getListName(); + dstLst[n] = k; + + string id = d.second.getUniqueId(); + dstLstId[id] = k; + } + } + + for (auto &d : data) { + if (d.first == MetaListType::ExternalList) { + wstring n = d.second.getListName(); + string id = d.second.getUniqueId(); + + if (dstLst.count(n)) + dstData[dstLst[n]].second = d.second; + else if (!dstLstId.count(id)) { + dstData.push_back(d); + } + } + else if (d.first != MetaListType::RemovedList) { + dstData.push_back(d); // All internal lists + } + } + + dst.setupIndex(EFirstLoadedList); + + map dstLstParam; + + for (auto &lp : dst.listParam) { + const wstring &n = lp.second.getName(); + dstLstParam[n] = lp.first; + } + + for (auto &lp : listParam) { + EStdListType dstCode = dst.getCodeFromUnqiueId(getUniqueId(lp.second.listCode)); + const wstring &n = lp.second.getName(); + if (dstLstParam.count(n)) { + dst.listParam[dstLstParam[n]] = lp.second; + dst.listParam[dstLstParam[n]].listCode = dstCode; + } + else { + int ix = 0; + if (!listParam.empty()) + ix = listParam.rbegin()->first + 1; + + dst.listParam[ix] = lp.second; + dst.listParam[ix].listCode = dstCode; + } + } +} diff --git a/code/metalist.h b/code/metalist.h index ef1714f..c4bc0ca 100644 --- a/code/metalist.h +++ b/code/metalist.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -337,6 +337,11 @@ public: void newHead() {addRow(MLHead);} void newSubHead() {addRow(MLSubHead);} + /** Lookup type from symbol. Return lNone if not found, no exception*/ + static EPostType getTypeFromSymbol(wstring &symb) noexcept; + + static void fillSymbols(vector < pair> &symb); + friend class MetaListPost; }; @@ -354,6 +359,8 @@ private: public: MetaListContainer(oEvent *owner); + MetaListContainer(oEvent *owner, const MetaListContainer &src); + virtual ~MetaListContainer(); string getUniqueId(EStdListType code) const; @@ -402,13 +409,16 @@ public: void getListParam( vector< pair > ¶m) const; void removeParam(int index); - void addListParam(oListParam &listParam); + /** Return the list index.*/ + int addListParam(oListParam &listParam); void mergeParam(int toInsertAfter, int toMerge, bool showTitleBetween); void getMergeCandidates(int toMerge, vector< pair > ¶m) const; bool canSplit(int index) const; void split(int index); + void synchronizeTo(MetaListContainer &dst) const; + bool interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par, int lineHeight, oListInfo &li) const; diff --git a/code/methodeditor.cpp b/code/methodeditor.cpp index 1c71ac0..068862b 100644 --- a/code/methodeditor.cpp +++ b/code/methodeditor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/methodeditor.h b/code/methodeditor.h index 5b3e9d7..a8a91d5 100644 --- a/code/methodeditor.h +++ b/code/methodeditor.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/mysqldaemon.cpp b/code/mysqldaemon.cpp index 79b998a..bac1c8b 100644 --- a/code/mysqldaemon.cpp +++ b/code/mysqldaemon.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,20 +29,17 @@ #include "meosdb/sqltypes.h" #include -MySQLReconnect::MySQLReconnect(const wstring &errorIn) : AutoMachine("MySQL-daemon"), error(errorIn) -{ +MySQLReconnect::MySQLReconnect(const wstring &errorIn) : AutoMachine("MySQL-daemon", Machines::mMySQLReconnect), error(errorIn) { timeError = getLocalTime(); hThread=0; } -MySQLReconnect::~MySQLReconnect() -{ +MySQLReconnect::~MySQLReconnect() { CloseHandle(hThread); hThread=0; } -bool MySQLReconnect::stop() -{ +bool MySQLReconnect::stop() { if (interval==0) return true; diff --git a/code/newcompetition.cpp b/code/newcompetition.cpp index 4229d22..edecc3b 100644 --- a/code/newcompetition.cpp +++ b/code/newcompetition.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -156,6 +156,30 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) oe->updateTabs(true, false); loadPage(gdi); } + else if (bi.id == "FNoCourses" || bi.id == "FNoCoursesRelay") { + if (gdi.hasField("Name")) + createCompetition(gdi); + oe->getMeOSFeatures().useFeature(MeOSFeatures::Speaker, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::Economy, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::EditClub, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::Network, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::Vacancy, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::DrawStartList, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::Bib, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::RunnerDb, true, *oe); + oe->getMeOSFeatures().useFeature(MeOSFeatures::NoCourses, true, *oe); + + if (bi.id == "FNoCoursesRelay") + oe->getMeOSFeatures().useFeature(MeOSFeatures::Relay, true, *oe); + + gdi.clearPage(true); + gdi.fillRight(); + gdi.addString("", fontMediumPlus, "Skapar tävling..."); + gdi.refresh(); + Sleep(400); + oe->updateTabs(true, false); + loadPage(gdi); + } else if (bi.id == "FForked") { if (gdi.hasField("Name")) createCompetition(gdi); @@ -311,12 +335,36 @@ void TabCompetition::newCompetitionGuide(gdioutput &gdi, int step) { gdi.addString("", 1, "Välj vilka funktioner du vill använda"); gdi.dropLine(0.5); gdi.fillRight(); + gdi.addString("", 1, "Individuellt").setColor(colorDarkBlue); + gdi.popX(); + gdi.dropLine(1.2); + gdi.addButton("FIndividual", "Individuell tävling", NewGuideCB); gdi.addButton("FForked", "Individuellt, gafflat", NewGuideCB); - gdi.addButton("FTeam", "Tävling med lag", NewGuideCB); + gdi.popX(); gdi.dropLine(2); - gdi.addButton("FBasic", "Endast grundläggande", NewGuideCB); + + gdi.addButton("FBasic", "Endast grundläggande (enklast möjligt)", NewGuideCB); + gdi.addButton("FNoCourses", "Endast tidtagning (utan banor)", NewGuideCB); + + gdi.popX(); + gdi.dropLine(3); + + gdi.addString("", 1, "Lag och stafett").setColor(colorDarkBlue); + gdi.popX(); + gdi.dropLine(1.2); + + gdi.addButton("FTeam", "Tävling med lag", NewGuideCB); + gdi.addButton("FNoCoursesRelay", "Endast tidtagning (utan banor), stafett", NewGuideCB); + + gdi.popX(); + gdi.dropLine(3); + + gdi.addString("", 1, "Övrigt").setColor(colorDarkBlue); + gdi.popX(); + gdi.dropLine(1.2); + gdi.addButton("FAll", "Alla funktioner", NewGuideCB); gdi.addButton("FSelect", "Välj från lista...", NewGuideCB); gdi.addButton("Cancel", "Avbryt", NewGuideCB).setCancel(); @@ -325,6 +373,7 @@ void TabCompetition::newCompetitionGuide(gdioutput &gdi, int step) { gdi.disableInput("FIndividual"); gdi.disableInput("FForked"); gdi.disableInput("FBasic"); + gdi.disableInput("FNoCourses"); } gdi.popX(); diff --git a/code/oBase.cpp b/code/oBase.cpp index ac87d25..2468994 100644 --- a/code/oBase.cpp +++ b/code/oBase.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/oBase.h b/code/oBase.h index 220a50f..1aa604c 100644 --- a/code/oBase.h +++ b/code/oBase.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -58,11 +58,14 @@ enum SortOrder {ClassStartTime, ClassFinishTime, ClassStartTimeClub, ClassPoints, + ClassLiveResult, + ClassKnockoutTotalResult, SortByName, SortByLastName, SortByFinishTime, SortByFinishTimeReverse, SortByStartTime, + SortByStartTimeClass, CourseResult, CourseStartTime, SortByEntryTime, diff --git a/code/oCard.cpp b/code/oCard.cpp index 02bafcc..0a61277 100644 --- a/code/oCard.cpp +++ b/code/oCard.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -167,7 +167,7 @@ void oCard::importPunches(const string &s) { return; } -bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { +bool oCard::fillPunches(gdioutput &gdi, const string &name, oCourse *crs) { oPunchList::iterator it; synchronize(true); int ix = 0; @@ -212,7 +212,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { if (!hasStart && !it->isStart()){ if (it->isUsed){ if (showStart) - gdi.addItem(name, lang.tl("Start")+L"\t-", -1); + gdi.addItem(name, lang.tl("Start")+L"\t\u2013", -1); hasStart=true; } } @@ -230,11 +230,11 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { gdi.addItem(name, rogainingIndex[matchPunch].second->getString(), rogainingIndex[matchPunch].first); else - gdi.addItem(name, L"-\t-", -1); + gdi.addItem(name, L"\u2013\t\u2013", -1); } else { while(0getString(), rogainingIndex[matchPunch].first); else - gdi.addItem(name, L"-\t-", -1); + gdi.addItem(name, L"\u2013\t\u2013", -1); ctrl = crs->getControl(++matchPunch); } punchRemain = ctrl ? ctrl->getNumMulti() : 1; } else { - gdi.addItem(name, L"-\t-", -1); + gdi.addItem(name, L"\u2013\t\u2013", -1); ctrl = crs->getControl(++matchPunch); } } @@ -268,7 +268,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { if (it->isFinish() && crs) { //Add missing punches before the finish while(ctrl) { - gdi.addItem(name, L"-\t-", -1); + gdi.addItem(name, L"\u2013\t\u2013", -1); ctrl = crs->getControl(++matchPunch); } } @@ -287,7 +287,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { gdi.addItem(name, rogainingIndex[matchPunch].second->getString(), rogainingIndex[matchPunch].first); else - gdi.addItem(name, L"-\t-", -1); + gdi.addItem(name, L"\u2013\t\u2013", -1); ctrl = crs->getControl(++matchPunch); } punchRemain = ctrl ? ctrl->getNumMulti() : 1; @@ -305,7 +305,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { } if (!hasStart && showStart) - gdi.addItem(name, lang.tl("Start")+L"\t-", -1); + gdi.addItem(name, lang.tl("Start")+L"\t\u2013", -1); if (!hasFinish && showFinish) { @@ -317,7 +317,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { gdi.addItem(name, rogainingIndex[matchPunch].second->getString(), rogainingIndex[matchPunch].first); else - gdi.addItem(name, L"-\t-", -1); + gdi.addItem(name, L"\u2013\t\u2013", -1); ctrl = crs->getControl(++matchPunch); } punchRemain = ctrl ? ctrl->getNumMulti() : 1; @@ -328,7 +328,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, pCourse crs) { } } - gdi.addItem(name, lang.tl("Mål")+L"\t-", -1); + gdi.addItem(name, lang.tl("Mål")+L"\t\u2013", -1); } if (extra) { @@ -509,7 +509,7 @@ pCard oEvent::getCard(int Id) const } void oEvent::getCards(vector &c) { - synchronizeList(oLCardId); + synchronizeList(oListId::oLCardId); c.clear(); c.reserve(Cards.size()); @@ -592,8 +592,7 @@ void oEvent::generateCardTableData(Table &table, oCard *addCard) } oCardList::iterator it; - synchronizeList(oLCardId, true, false); - synchronizeList(oLRunnerId, false, true); + synchronizeList({ oListId::oLCardId, oListId::oLRunnerId }); for (it=Cards.begin(); it!=Cards.end(); ++it) { if (!it->isRemoved()) { @@ -712,7 +711,7 @@ bool oCard::comparePunchTime(oPunch *p1, oPunch *p2) { } void oCard::setupFromRadioPunches(oRunner &r) { - oe->synchronizeList(oLPunchId, true, true); + oe->synchronizeList(oListId::oLPunchId); vector p; oe->getPunchesForRunner(r.getId(), p); diff --git a/code/oCard.h b/code/oCard.h index 5544285..d5e57dd 100644 --- a/code/oCard.h +++ b/code/oCard.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -140,6 +140,7 @@ public: friend class oRunner; friend class oTeam; friend class MeosSQL; + friend class oListInfo; }; #endif // !defined(AFX_OCARD_H__674EAB76_A232_4E44_A9B4_C52F6A04D7CF__INCLUDED_) diff --git a/code/oClass.cpp b/code/oClass.cpp index 9aeb1db..d2efaa3 100644 --- a/code/oClass.cpp +++ b/code/oClass.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -421,19 +421,16 @@ oDataContainer &oClass::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr & return *oe->oClassData; } -pClass oEvent::getClassCreate(int Id, const wstring &createName) -{ - oClassList::iterator it; - +pClass oEvent::getClassCreate(int Id, const wstring &createName, set &exactMatch) { if (Id>0) { + oClassList::iterator it; for (it=Classes.begin(); it != Classes.end(); ++it) { if (it->Id==Id && !it->isRemoved()) { if (compareClassName(createName, it->getName())) { if (it!=Classes.begin()) Classes.splice(Classes.begin(), Classes, it, Classes.end()); - - return &Classes.front(); + return &Classes.front(); } else { Id=0; //Bad Id @@ -449,10 +446,19 @@ pClass oEvent::getClassCreate(int Id, const wstring &createName) return addClass(c); } else { + bool exact = exactMatch.count(createName) > 0; + //Check if class exist under different id - for (it=Classes.begin(); it != Classes.end(); ++it) { - if (!it->isRemoved() && compareClassName(it->Name, createName)) - return &*it; + for (auto &c : Classes) { + if (c.isRemoved()) + continue; + + if (!exact && exactMatch.count(c.Name) == 0 && compareClassName(c.Name, createName)) { + return &c; + } + if (exact && c.Name == createName) { + return &c; + } } if (Id<=0) @@ -460,7 +466,7 @@ pClass oEvent::getClassCreate(int Id, const wstring &createName) oClass c(this, Id); c.Name = createName; - + exactMatch.insert(createName); //No! Create class with this Id pClass pc=addClass(c); @@ -534,7 +540,7 @@ static bool clsSortFunction (pClass i, pClass j) { void oEvent::getClasses(vector &classes, bool sync) const { if (sync) { - oe->synchronizeList(oLCourseId); + oe->synchronizeList(oListId::oLCourseId); oe->reinitializeClasses(); } @@ -563,10 +569,9 @@ pClass oEvent::getClass(const wstring &cname) const return 0; } -pClass oEvent::getClass(int Id) const -{ +pClass oEvent::getClass(int Id) const { if (Id<=0) - return 0; + return nullptr; oClassList::const_iterator it; @@ -575,7 +580,7 @@ pClass oEvent::getClass(int Id) const return pClass(&*it); } } - return 0; + return nullptr; } pClass oEvent::addClass(const wstring &pname, int CourseId, int classId) @@ -768,6 +773,7 @@ void oClass::fillStartTypes(gdioutput &gdi, const string &name, bool firstLeg) StartTypes oClass::getStartType(int leg) const { + leg = mapLeg(leg); if (unsigned(leg) 0 && (isParallel(leg) || isOptional(leg)) ) return getRestartTime(leg-1); @@ -798,8 +809,9 @@ int oClass::getRestartTime(int leg) const } -int oClass::getRopeTime(int leg) const -{ +int oClass::getRopeTime(int leg) const { + leg = mapLeg(leg); + if (leg > 0 && (isParallel(leg) || isOptional(leg)) ) return getRopeTime(leg-1); if (unsigned(leg) > &oEvent::fillClasses(vector< pair &classes, bool someMissing) oRunnerList::iterator rit; - synchronizeList(oLRunnerId); + synchronizeList(oListId::oLRunnerId); for (rit=Runners.begin(); rit != Runners.end(); ++rit) { if (rit->tStartTime>0) drawn.insert(rit->getClassId(true)); @@ -1153,7 +1172,7 @@ void oEvent::getNotDrawnClasses(set &classes, bool someMissing) return; oClassList::iterator it; - synchronizeList(oLClassId); + synchronizeList(oListId::oLClassId); // Return classes where no runner has a start time for (it=Classes.begin(); it != Classes.end(); ++it) { @@ -1168,7 +1187,7 @@ void oEvent::getAllClasses(set &classes) classes.clear(); oClassList::const_iterator it; - synchronizeList(oLClassId); + synchronizeList(oListId::oLClassId); for (it=Classes.begin(); it != Classes.end(); ++it){ if (!it->Removed){ @@ -1180,7 +1199,7 @@ void oEvent::getAllClasses(set &classes) bool oEvent::fillClassesTB(gdioutput &gdi)//Table mode { oClassList::iterator it; - synchronizeList(oLClassId); + synchronizeList(oListId::oLClassId); reinitializeClasses(); Classes.sort();//Sort by Id @@ -1246,6 +1265,8 @@ bool oClass::hasTrueMultiCourse() const { wstring oClass::getLength(int leg) const { + leg = mapLeg(leg); + wchar_t bf[64]; if (hasMultiCourse()){ int minlen=1000000; @@ -1398,6 +1419,8 @@ pCourse oClass::selectParallelCourse(const oRunner &r, const SICard &sic) { pCourse oClass::getCourse(int leg, unsigned fork, bool getSampleFromRunner) const { + leg = mapLeg(leg); + if (size_t(leg) < MultiCourse.size()) { const vector &courses=MultiCourse[leg]; if (courses.size()>0) { @@ -1414,7 +1437,7 @@ pCourse oClass::getCourse(int leg, unsigned fork, bool getSampleFromRunner) cons else { pCourse res = 0; for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { - if (it->Class == this && it->Course) { + if (it->getClassRef(true) == this && it->Course) { if (it->tLeg == leg) return it->Course; else @@ -1439,6 +1462,8 @@ pCourse oClass::getCourse(bool getSampleFromRunner) const { } void oClass::getCourses(int leg, vector &courses) const { + leg = mapLeg(leg); + //leg == -1 -> all courses courses.clear(); if (leg <= 0 && Course) @@ -1455,12 +1480,12 @@ void oClass::getCourses(int leg, vector &courses) const { // Add shortened versions for (size_t k = 0; k < courses.size(); k++) { - pCourse sht = courses[k]->getShorterVersion(); + pCourse sht = courses[k]->getShorterVersion().second; int maxIter = 10; while (sht && --maxIter >= 0 ) { if (find(courses.begin(), courses.end(), sht) == courses.end()) courses.push_back(sht); - sht = sht->getShorterVersion(); + sht = sht->getShorterVersion().second; } } } @@ -1471,6 +1496,8 @@ ClassType oClass::getClassType() const legInfo[1].legMethod==LTIgnore) ) return oClassPatrol; else if (legInfo.size()>=2) { + if (isQualificationFinalBaseClass()) + return oClassKnockout; for(size_t k=1;kgetClassType(); - if (ct==oClassIndividual || ct==oClassIndividRelay) { + if (ct == oClassIndividual || ct == oClassIndividRelay || ct == oClassKnockout) { oRunnerList::const_iterator it; int maxleg = pc->getLastStageIndex(); - for (it=Runners.begin(); it != Runners.end(); ++it){ - if (!it->skip() && it->getClassId(true)==id && it->getStatus() != StatusNotCompetiting) { - if (leg==0) { + for (it = Runners.begin(); it != Runners.end(); ++it) { + if (!it->skip() && it->getClassId(true) == id && it->getStatus() != StatusNotCompetiting) { + if (leg == 0) { total++; if (it->tStatus != StatusUnknown) finished++; - else if (it->tStatus==StatusDNS || it->tStatus == StatusCANCEL) + else if (it->tStatus == StatusDNS || it->tStatus == StatusCANCEL) dns++; } else { - int tleg = leg>0 ? leg : maxleg; - const pRunner r=it->getMultiRunner(tleg); + int tleg = leg > 0 ? leg : maxleg; + const pRunner r = it->getMultiRunner(tleg); if (r) { total++; - if (r->tStatus!=StatusUnknown) + if (r->tStatus != StatusUnknown) finished++; - else if (it->tStatus==StatusDNS || it->tStatus == StatusCANCEL) + else if (it->tStatus == StatusDNS || it->tStatus == StatusCANCEL) dns++; } } @@ -1616,6 +1643,8 @@ void oClass::setCoursePool(bool p) } pCourse oClass::selectCourseFromPool(int leg, const SICard &card) const { + leg = mapLeg(leg); + int Distance=-1000; const oCourse *rc=0; //Best match course @@ -1662,7 +1691,7 @@ pCourse oClass::selectCourseFromPool(int leg, const SICard &card) const { vector< pair > shortenedLayer; for (size_t k=0;k < layer.size(); k++) { if (layer[k].first) { - pCourse sw = layer[k].first->getShorterVersion(); + pCourse sw = layer[k].first->getShorterVersion().second; if (sw) shortenedLayer.push_back(make_pair(sw, layer[k].second)); } @@ -1691,7 +1720,7 @@ void oClass::updateChangedCoursePool() { SICard card(ConvertedTimeStatus::Unknown); oRunnerList::iterator it; for (it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { - if (it->isRemoved() || it->Class != this) + if (it->isRemoved() || it->getClassRef(true) != this) continue; if (size_t(it->tLeg) >= crs.size() || crs[it->tLeg].empty()) @@ -1718,10 +1747,8 @@ void oClass::updateChangedCoursePool() { tCoursesChanged = false; } -int oClass::getBestLegTime(int leg) const -{ - if (leg > 0 && tLeaderTime.size() == 1) - leg = 0; // The case with different class for team/runner. Leg is an index in another class. +int oClass::getBestLegTime(int leg) const { + leg = mapLeg(leg); if (unsigned(leg)>=tLeaderTime.size()) return 0; @@ -1738,18 +1765,20 @@ int oClass::getBestTimeCourse(int courseId) const return res->second; } - int oClass::getBestInputTime(int leg) const { + leg = mapLeg(leg); + if (unsigned(leg)>=tLeaderTime.size()) return 0; - else return - tLeaderTime[leg].inputTime; + else + return tLeaderTime[leg].inputTime; } - int oClass::getTotalLegLeaderTime(int leg, bool includeInput) const { + leg = mapLeg(leg); + if (unsigned(leg)>=tLeaderTime.size()) return 0; else { @@ -1757,7 +1786,6 @@ int oClass::getTotalLegLeaderTime(int leg, bool includeInput) const return tLeaderTime[leg].totalLeaderTimeInput; else return tLeaderTime[leg].totalLeaderTime; - } } @@ -2118,6 +2146,19 @@ void ClassSplit::valueEvenSplit(const vector &parts, vector< pair void oClass::splitClass(ClassSplitMethod method, const vector &parts, vector &outClassId) { if (parts.size() <= 1) return; + bool qf = false; + set clsIdSrc; + clsIdSrc.insert(getId()); + + if (getQualificationFinal()) { + // Works for base classes + set base; + getQualificationFinal()->getBaseClassInstances(base); + assert(base.size() == parts.size()); + qf = true; + for (int inst : base) + clsIdSrc.insert(getVirtualClass(inst)->getId()); + } bool defineHeats = method == SplitRankEven || method == SplitResultEven; @@ -2125,48 +2166,73 @@ void oClass::splitClass(ClassSplitMethod method, const vector &parts, vecto vector t; vector r; - if ( oe->classHasTeams(getId()) ) { - oe->getTeams(getId(), t, true); - for (size_t k = 0; k < t.size(); k++) - cc.addMember(*t[k]); + if (!qf && oe->classHasTeams(getId()) ) { + for (int clsId : clsIdSrc) { + vector tTmp; + oe->getTeams(clsId, tTmp, true); + for (auto tk : tTmp) { + t.push_back(tk); + cc.addMember(*tk); + } + } } else { - oe->getRunners(getId(), 0, r, true); - for (size_t k = 0; k < r.size(); k++) - cc.addMember(*r[k]); + for (int clsId : clsIdSrc) { + vector rTmp; + oe->getRunners(clsId, 0, rTmp, true); + for (auto rk : rTmp) { + if (qf && rk->getLegNumber() != 0) + continue; + + r.push_back(rk); + cc.addMember(*rk); + } + } } - // Split teams. - + + // Split teams. cc.split(parts, method); vector pcv(parts.size()); outClassId.resize(parts.size()); - pcv[0] = this; - outClassId[0] = getId(); - - pcv[0]->getDI().setInt("Heat", defineHeats ? 1 : 0); - pcv[0]->synchronize(true); - - int lastSI = getDI().getInt("SortIndex"); - for (size_t k=1; kaddClass(getName() + makeDash(L"-") + itow(k+1), getCourseId()); - if (pcv[k]) { - // Find suitable sort index - lastSI = pcv[k]->getSortIndex(lastSI + 1); - - memcpy(pcv[k]->oData, oData, sizeof(oData)); - - pcv[k]->getDI().setInt("SortIndex", lastSI); - pcv[k]->getDI().setInt("Heat", defineHeats ? k+1 : 0); - pcv[k]->synchronize(); + if (qf) { + set base; + getQualificationFinal()->getBaseClassInstances(base); + int ix = 0; + for (int inst : base) { + pcv[ix] = getVirtualClass(inst); + outClassId[ix] = pcv[ix]->getId(); + ix++; + } + } + else { + pcv[0] = this; + outClassId[0] = getId(); + + pcv[0]->getDI().setInt("Heat", defineHeats ? 1 : 0); + pcv[0]->synchronize(true); + + int lastSI = getDI().getInt("SortIndex"); + for (size_t k = 1; k < parts.size(); k++) { + pcv[k] = oe->addClass(getName() + makeDash(L"-") + itow(k + 1), getCourseId()); + if (pcv[k]) { + // Find suitable sort index + lastSI = pcv[k]->getSortIndex(lastSI + 1); + + memcpy(pcv[k]->oData, oData, sizeof(oData)); + + pcv[k]->getDI().setInt("SortIndex", lastSI); + pcv[k]->getDI().setInt("Heat", defineHeats ? k + 1 : 0); + pcv[k]->synchronize(); + } + + outClassId[k] = pcv[k]->getId(); } - outClassId[k] = pcv[k]->getId(); + setName(getName() + makeDash(L"-1")); + synchronize(); } - setName(getName() + makeDash(L"-1")); - synchronize(); - for (size_t k = 0; k < t.size(); k++) { pTeam it = t[k]; int clsIx = cc.getClassIndex(*it); @@ -2186,11 +2252,16 @@ void oClass::splitClass(ClassSplitMethod method, const vector &parts, vecto for (size_t k = 0; k < r.size(); k++) { pRunner it = r[k]; int clsIx = cc.getClassIndex(*it); - it->Class = pcv[clsIx]; - if (defineHeats) - it->getDI().setInt("Heat", clsIx+1); + if (qf) { + it->getDI().setInt("Heat", clsIx + 1); + } + else { + it->Class = pcv[clsIx]; + if (defineHeats) + it->getDI().setInt("Heat", clsIx + 1); + } it->updateChanged(); - it->synchronize(); + it->synchronize(); } } @@ -2541,16 +2612,37 @@ const vector< pair > &oEvent::fillClassTypes(vector< paircalculateNumRemainingMaps(forceRecalculate); -int oClass::getNumRemainingMaps(bool recalculate) const -{ - if (recalculate) - oe->calculateNumRemainingMaps(); + int numMaps = tMapsRemaining; - if (Course) - return Course->tMapsRemaining; + if (Course && Course->tMapsRemaining != numeric_limits::min()) { + if (numMaps == numeric_limits::min()) + numMaps = Course->tMapsRemaining; + else + numMaps = min(numMaps, Course->tMapsRemaining); + + return numMaps; + } else - return numeric_limits::min(); + return numMaps; +} + +void oClass::setNumberMaps(int nm) { + getDI().setInt("NumberMaps", nm); +} + +int oClass::getNumberMaps(bool rawAttribute) const { + int nm = getDCI().getInt("NumberMaps"); + + if (rawAttribute) + return nm; + + if (nm == 0 && Course) + nm = Course->getNumberMaps(); + + return nm; } void oEvent::getStartBlocks(vector &blocks, vector &starts) const @@ -2617,7 +2709,7 @@ void oEvent::generateClassTableData(Table &table, oClass *addClass) return; } - synchronizeList(oLClassId); + synchronizeList(oListId::oLClassId); oClassList::iterator it; for (it=Classes.begin(); it != Classes.end(); ++it){ @@ -2769,8 +2861,7 @@ void oClass::addClassDefaultFee(bool resetFee) { } } -void oClass::reinitialize() -{ +void oClass::reinitialize() { int ix = getDI().getInt("SortIndex"); if (ix == 0) { ix = getSortIndex(getId()*10); @@ -2785,6 +2876,9 @@ void oClass::reinitialize() wstring wInfo = getDI().getString("Qualification"); if (!wInfo.empty()) { + if (qualificatonFinal && !qualificatonFinal->matchSerialization(wInfo)) + clearQualificationFinal(); + if (!qualificatonFinal) qualificatonFinal = make_shared(MaxClassId, Id); @@ -2796,13 +2890,28 @@ void oClass::reinitialize() getVirtualClass(i); } else { - qualificatonFinal.reset(); + clearQualificationFinal(); } tNoTiming = -1; tIgnoreStartPunch = -1; } +void oClass::clearQualificationFinal() { + if (!qualificatonFinal) + return; + + int nc = qualificatonFinal->getNumClasses(); + for (pClass pc : virtualClasses) { + if (pc) + pc->parentClass = nullptr; + } + + virtualClasses.clear(); + qualificatonFinal.reset(); +} + + void oEvent::reinitializeClasses() { for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) @@ -2946,6 +3055,8 @@ void oClass::insertAccLegPlace(int courseId, int controlNo, int time, int place) } void oClass::getStartRange(int leg, int &firstStart, int &lastStart) const { + leg = mapLeg(leg); + if (tFirstStart.empty()) { size_t s = getLastStageIndex() + 1; assert(s>0); @@ -2953,11 +3064,11 @@ void oClass::getStartRange(int leg, int &firstStart, int &lastStart) const { lFirstStart.resize(s, 3600 * 24 * 365); lLastStart.resize(s, 0); for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { - if (it->isRemoved() || it->Class != this) + if (it->isRemoved() || it->getClassRef(true) != this) continue; if (it->needNoCard()) continue; - size_t tleg = it->tLeg; + size_t tleg = mapLeg(it->tLeg); if (tleg < s) { lFirstStart[tleg] = min(lFirstStart[tleg], it->tStartTime); lLastStart[tleg] = max(lLastStart[tleg], it->tStartTime); @@ -3031,7 +3142,7 @@ void oClass::calculateSplits() { vector acceptMissingPunch(nc+1, true); for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { - if (it->isRemoved() || it->Class != this) + if (it->isRemoved() || it->getClassRef(true) != this) continue; pCourse tpc = it->getCourse(false); if (tpc != pc || tpc == 0) @@ -3050,7 +3161,7 @@ void oClass::calculateSplits() { } } for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { - if (it->isRemoved() || it->Class != this) + if (it->isRemoved() || it->getClassRef(true) != this) continue; pCourse tpc = it->getCourse(false); @@ -3344,6 +3455,8 @@ bool oClass::wasSQLChanged(int leg, int control) const { } if (control != -1) { + if (control == -2) // Any control + return sqlChangedControlLeg.size() > 0; res = sqlChangedControlLeg.find(control); if (res != sqlChangedControlLeg.end()) { if (leg == -1 || res->second.count(-1) || res->second.count(leg)) @@ -3406,6 +3519,8 @@ bool oClass::checkForking(vector< vector > &legOrder, vector order; long long hash = 0; for (size_t j = 0; j< MultiCourse.size(); j++) { + if (getLegType(j) == LTExtra || getLegType(j) == LTIgnore) + continue; if (!MultiCourse[j].empty()) { int ix = k % MultiCourse[j].size(); int cid = MultiCourse[j][ix]->getId(); @@ -3423,7 +3538,7 @@ bool oClass::checkForking(vector< vector > &legOrder, else { int test = hashes[hash]; if (legOrder[test] != order) { - // Test for hash collition. Will not happen... + // Test for hash collision. Will not happen... bool exist = false; for (size_t i = 0; i < legOrder.size(); i++) { if (legOrder[i] == order) { @@ -3470,7 +3585,7 @@ bool oClass::checkForking(vector< vector > &legOrder, else { int test = hashes[hash]; if (legOrder[test] != order) { - // Test for hash collition. Will not happen... + // Test for hash collision. Will not happen... bool exist = false; for (size_t i = 0; i < legOrder.size(); i++) { if (legOrder[i] == order) { @@ -3700,9 +3815,10 @@ pair oClass::autoForking(const vector< vector > &inputCourses) { break; } permute(fperm); - + int lastSet = -1; for (int j = 0; j < legs; j++) { if (nf[j] > 0) { + lastSet = j; for (size_t k = 0; k < courseMatrix[j].size(); k++) { if (k < fperm.size()) { addStageCourse(j, courseMatrix[j][fperm[k]]); @@ -3712,6 +3828,16 @@ pair oClass::autoForking(const vector< vector > &inputCourses) { } } } + else if (lastSet >= 0 && getLegType(j) == LTExtra) { + MultiCourse[j] = MultiCourse[lastSet]; + // getCourses(lastSet, courses); + //clearStageCourses(j); + //for (size_t k = 0; k < courses.size(); k++) + // addStageCourse(j, courses[k]->getId()); + } + else { + lastSet = -1; + } } return make_pair(generatedForkKeys.size(), coursesUsed.size()); @@ -3941,7 +4067,7 @@ void oClass::drawSeeded(ClassSeedMethod seed, int leg, int firstStart, vector< pair > seedIx; if (seed == SeedResult) { oe->reEvaluateAll(set(), true); - oe->calculateResults(oEvent::ResultType::RTClassResult, false); + oe->calculateResults({}, oEvent::ResultType::ClassResult, false); } for (size_t k = 0; k < r.size(); k++) { if (r[k]->tLeg != leg && leg != -1) @@ -4265,7 +4391,7 @@ void oClass::configureInstance(int instance, bool allowCreation) const { copy.Id = Id + instance * MaxClassId; copy.setExtIdentifier(copy.Id); copy.Name += makeDash(L"-") + qualificatonFinal->getInstanceName(instance); - copy.setLocalObject(); + copy.sqlUpdated.clear(); copy.parentClass = pClass(this); copy.tSortIndex += instance; copy.getDI().setInt("SortIndex", copy.tSortIndex); @@ -4293,6 +4419,8 @@ void oClass::loadQualificationFinalScheme(const wstring &fileName) { setLegType(i, LegTypes::LTNormal); setLegRunner(i, 0); } + // Clear any old scheme + clearQualificationFinal(); qualificatonFinal = qf; getDI().setString("Qualification", enc); for (int i = 0; i < qualificatonFinal->getNumClasses(); i++) { @@ -4317,13 +4445,21 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) if (!qualificatonFinal) return; assert(!causingResult || causingResult->Class == this); + + //oe->gdibase.addStringUT(0, L"UF:" + getName() + L" for " + (causingResult ? causingResult->getName() : L"-")); + int instance = causingResult ? causingResult->classInstance() : 0; pClass currentInst = getVirtualClass(instance, false); - if (instance == virtualClasses.size()) + if (qualificatonFinal->isFinalClass(instance)) return; // Final class + if (instance > 0) { + int baseLevel = qualificatonFinal->getLevel(instance); + instance = qualificatonFinal->getMinInstance(baseLevel); + } int maxDepth = getNumStages(); bool needIter = true; int limit = virtualClasses.size() - 1; + bool wasReset = false; while (needIter && --maxDepth > 0) { needIter = false; @@ -4331,7 +4467,8 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) break; // Final class vector< vector > classSplit(virtualClasses.size()); - + vector nonQualified; + for (oRunner &r : oe->Runners) { if (r.isRemoved() || !r.Class) continue; @@ -4339,18 +4476,41 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) if (r.Class != this && (r.Class->getId() % MaxClassId) != getId()) continue; - int inst = r.Class == this ? r.classInstance() : (r.Class->getId() - getId()) & MaxClassId; + int inst = r.Class == this ? r.classInstance() : (r.Class->getId() - getId()) / MaxClassId; - if (inst == 0 && r.tLeg > 0) + if (inst == 0 && r.tLeg > 0) { + if (r.tLeg < maxDepth - 1 && r.Class == this) + nonQualified.push_back(&r); continue; // Only allow base class for leg 0. - + } if (inst < instance || inst >= limit) continue; classSplit[inst].push_back(&r); } + // Reset non-qualified + if (!wasReset) { + for (size_t i = 0; i < nonQualified.size(); i++) { + pRunner r = nonQualified[i]; + pRunner next = r->getMultiRunner(r->tLeg + 1); + if (next && next->getClassRef(true) != this) { + pClass nextCls = next->getClassRef(true); + if (!nextCls->lockedClassAssignment()) { + wasReset = true; + next->getDI().setInt("Heat", 0); + nonQualified.push_back(next); + } + } + } + if (wasReset) { // Only do this once. + maxDepth++; + needIter = true; + continue; // Redo + } + } GeneralResult gr; + qualificatonFinal->prepareCalculations(); for (int i = instance; i < limit; i++) { if (classSplit[i].empty()) @@ -4360,23 +4520,26 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) set allowed; qualificatonFinal->getBaseClassInstances(allowed); // Place all in this group - for (pRunner r : classSplit[i]) { + for (int rix = classSplit[0].size() - 1; rix >= 0; rix--) { + pRunner r = classSplit[0][rix]; auto di = r->getDI(); int oldHeat = di.getInt("Heat"); if (allowed.count(oldHeat) || classSplit.size() < 2) continue; - // Take the smallest gruop. User can set heat explicitly of other distribution is wanted. + // Take the smallest group. User can set heat explicitly of other distribution is wanted. int heat = 1; for (int i : allowed) { if (size_t(i) < classSplit.size() && - classSplit[heat].size() > classSplit[i].size()) + classSplit[heat].size() > classSplit[i].size()) heat = i; } if (heat != oldHeat) { bool lockedStartList = getVirtualClass(heat)->lockedClassAssignment() || - getVirtualClass(oldHeat)->lockedClassAssignment(); + getVirtualClass(oldHeat)->lockedClassAssignment(); if (!lockedStartList) { + classSplit[heat].push_back(r); + classSplit[0].erase(classSplit[0].begin() + rix); pClass oldClass = r->getClassRef(true); oldClass->markSQLChanged(-1, 0); di.setInt("Heat", heat); @@ -4392,7 +4555,7 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) int numEqual = 0; for (size_t k = 0; k < classSplit[i].size(); k++) { auto &res = classSplit[i][k]->getTempResult(); - int heat = 0; + //int heat = 0; if (res.getStatus() == StatusOK) { int place = res.getPlace(); if (lastPlace == place) @@ -4400,20 +4563,34 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) else numEqual = 0; - auto nextFinal = qualificatonFinal->getNextFinal(i, orderPlace, numEqual); - heat = nextFinal.first; + qualificatonFinal->setupNextFinal(classSplit[i][k], i, orderPlace, numEqual); + //auto nextFinal = qualificatonFinal->getNextFinal(i, orderPlace, numEqual); + //heat = nextFinal.first; lastPlace = place; } orderPlace++; + } + } + + qualificatonFinal->computeFinals(); + + for (int i = instance; i < limit; i++) { + if (classSplit[i].empty()) + continue; + + for (size_t k = 0; k < classSplit[i].size(); k++) { oRunner &thisRunner = *classSplit[i][k]; pRunner runnerToChange = thisRunner.getMultiRunner(thisRunner.getRaceNo() + 1); if (runnerToChange) { + auto res = qualificatonFinal->getNextFinal(thisRunner.getId()); + int heat = res.first; + auto di = runnerToChange->getDI(); int oldHeat = di.getInt("Heat"); if (heat != oldHeat) { - bool lockedStartList = getVirtualClass(heat)->lockedClassAssignment() || + bool lockedStartList = (heat != 0 && getVirtualClass(heat)->lockedClassAssignment()) || getVirtualClass(oldHeat)->lockedClassAssignment(); if (!lockedStartList) { @@ -4421,6 +4598,8 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) oldClass->markSQLChanged(-1, 0); di.setInt("Heat", heat); runnerToChange->classInstanceRev.first = -1; + //oe->gdibase.addStringUT(0, L"HU:" + thisRunner.getName() + L" " + itow(oldHeat) + L"->" + itow(heat)); + runnerToChange->apply(false, nullptr, false); runnerToChange->synchronize(); if (runnerToChange->getFinishTime() > 0) needIter = true; @@ -4473,3 +4652,17 @@ vector> oClass::getAllFees() const { return ff; } + +bool oEvent::hasAnyRestartTime() const { + for (auto &c : Classes) { + if (c.isRemoved()) + continue; + + for (auto &leg : c.legInfo) { + if (leg.legRopeTime > 0 && leg.legRestartTime > 0) + return true; + } + } + + return false; +} diff --git a/code/oClass.h b/code/oClass.h index 8d0bd97..2bab018 100644 --- a/code/oClass.h +++ b/code/oClass.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -136,8 +136,13 @@ struct ClassResultInfo { class QualificationFinal; -enum ClassType {oClassIndividual=1, oClassPatrol=2, - oClassRelay=3, oClassIndividRelay=4}; +enum ClassType { + oClassIndividual = 1, + oClassPatrol = 2, + oClassRelay = 3, + oClassIndividRelay = 4, + oClassKnockout = 5 +}; enum ClassMetaType {ctElite, ctNormal, ctYouth, ctTraining, ctExercise, ctOpen, ctUnknown}; @@ -247,6 +252,13 @@ protected: void calculateSplits(); void clearSplitAnalysis(); + /** Map to correct leg number for diff class/runner class (for example qual/final)*/ + int mapLeg(int inputLeg) const { + if (inputLeg > 0 && legInfo.size() == 1) + return 0; // The case with different class for team/runner. Leg is an index in another class. + return inputLeg; + } + /** Info about the result in the class for each leg. Use oEvent::analyseClassResultStatus to setup */ mutable vector tResultInfo; @@ -266,6 +278,10 @@ protected: shared_ptr qualificatonFinal; + int tMapsRemaining; + mutable int tMapsUsed; + mutable int tMapsUsedNoVacant; + void configureInstance(int instance, bool allowCreation) const; public: @@ -274,6 +290,21 @@ public: const QualificationFinal *getQualificationFinal() const { return qualificatonFinal.get(); } + void clearQualificationFinal(); + + bool isQualificationFinalClass() const { + return parentClass && parentClass->isQualificationFinalBaseClass(); + } + + bool isQualificationFinalBaseClass() const { + return qualificatonFinal != nullptr; + } + + bool isTeamClass() const { + int ns = getNumStages(); + return ns > 0 && getNumDistinctRunners() == 1; + } + /** Returns the number of possible final classes.*/ int getNumQualificationFinalClasses() const; void loadQualificationFinalScheme(const wstring &fileName); @@ -345,7 +376,7 @@ public: return false; } - oClass *getVirtualClass(int instance, bool allowCreation); + pClass getVirtualClass(int instance, bool allowCreation); const pClass getVirtualClass(int instance) const; ClassStatus getClassStatus() const; @@ -509,7 +540,10 @@ public: int getNumRunners(bool checkFirstLeg, bool noCountVacant, bool noCountNotCompeting) const; //Get remaining maps for class (or int::minvalue) - int getNumRemainingMaps(bool recalculate) const; + int getNumRemainingMaps(bool forceRecalculate) const; + + void setNumberMaps(int nm); + int getNumberMaps(bool rawAttribute = false) const; const wstring &getName() const {return Name;} void setName(const wstring &name); diff --git a/code/oClub.cpp b/code/oClub.cpp index d57af11..0dfebc3 100644 --- a/code/oClub.cpp +++ b/code/oClub.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,6 +36,7 @@ #include "Table.h" #include "localizer.h" #include "pdfwriter.h" +#include "HTMLWriter.h" #include "intkeymapimpl.hpp" @@ -272,7 +273,7 @@ const vector< pair > & oEvent::fillClubs(vector< pairaddTableRow(table); return; } - synchronizeList(oLClubId); + synchronizeList(oListId::oLClubId); oClubList::iterator it; for (it=Clubs.begin(); it != Clubs.end(); ++it){ @@ -416,7 +417,7 @@ void oEvent::mergeClub(int clubIdPri, int clubIdSec) void oEvent::getClubs(vector &c, bool sort) { if (sort) { - synchronizeList(oLClubId); + synchronizeList(oListId::oLClubId); Clubs.sort(); } c.clear(); @@ -521,14 +522,14 @@ void oClub::addRunnerInvoiceLine(const pRunner r, bool inTeam, ts = r->getPrintPlaceS(true)+ L" (" + r->getRunningTimeS() + L")"; } else - ts = r->getStatusS(); + ts = r->getStatusS(true); } else { if (r->getTotalStatus()==StatusOK) { ts = r->getPrintTotalPlaceS(true) + L" (" + r->getTotalRunningTimeS() + L")"; } else if (r->getTotalStatus()!=StatusNotCompetiting) - ts = r->getStatusS(); + ts = r->getStatusS(true); else { ts = r->getInputStatusS(); } @@ -587,7 +588,7 @@ void oClub::addTeamInvoiceLine(const pTeam t, const map &definedPa ts = t->getPrintPlaceS(true) + L" (" + t->getRunningTimeS() + L")"; } else - ts = t->getStatusS(); + ts = t->getStatusS(true); } @@ -818,7 +819,7 @@ void oEvent::printInvoices(gdioutput &gdi, InvoicePrintType type, oClubList::iterator it; oe->calculateTeamResults(false); oe->sortTeams(ClassStartTime, 0, true); - oe->calculateResults(RTClassResult); + oe->calculateResults(set(), ResultType::ClassResult); oe->sortRunners(ClassStartTime); int pay, paid; vector fees, vpaid; @@ -868,10 +869,10 @@ void oEvent::printInvoices(gdioutput &gdi, InvoicePrintType type, if (type == IPTAllPDF) { pdfwriter pdf; - pdf.generatePDF(gdi, path + filename, lang.tl("Faktura"), L"", gdi.getTL()); + pdf.generatePDF(gdi, path + filename, lang.tl("Faktura"), L"", gdi.getTL(), true); } else - gdi.writeHTML(path + filename, lang.tl(L"Faktura"), 0); + HTMLWriter::writeHTML(gdi, path + filename, lang.tl(L"Faktura"), 0, 1.0); clubId.insert(it->getId()); fees.push_back(pay); @@ -1091,7 +1092,7 @@ void oClub::clearClubs(oEvent &oe) { } void oClub::assignInvoiceNumber(oEvent &oe, bool reset) { - oe.synchronizeList(oLClubId); + oe.synchronizeList(oListId::oLClubId); oe.Clubs.sort(); int numberStored = oe.getPropertyInt("FirstInvoice", 100); int number = numberStored; @@ -1124,7 +1125,7 @@ void oClub::assignInvoiceNumber(oEvent &oe, bool reset) { } int oClub::getFirstInvoiceNumber(oEvent &oe) { - oe.synchronizeList(oLClubId); + oe.synchronizeList(oListId::oLClubId); int number = 0; for (oClubList::iterator it = oe.Clubs.begin(); it != oe.Clubs.end(); ++it) { if (it->isRemoved()) diff --git a/code/oClub.h b/code/oClub.h index 3b72324..9e8ffd3 100644 --- a/code/oClub.h +++ b/code/oClub.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/oControl.cpp b/code/oControl.cpp index b719889..04a619a 100644 --- a/code/oControl.cpp +++ b/code/oControl.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -355,7 +355,7 @@ const vector< pair > &oEvent::fillControls(vector< pair > &oEvent::fillControls(vector< pair > &oEvent::fillControlTypes(vector< pair > &out) { oControlList::iterator it; - synchronizeList(oLControlId); + synchronizeList(oListId::oLControlId); out.clear(); //gdi.clearList(name); out.clear(); @@ -830,7 +830,7 @@ void oEvent::generateControlTableData(Table &table, oControl *addControl) return; } - synchronizeList(oLControlId); + synchronizeList(oListId::oLControlId); oControlList::iterator it; for (it=Controls.begin(); it != Controls.end(); ++it){ diff --git a/code/oControl.h b/code/oControl.h index ae4c957..5a89743 100644 --- a/code/oControl.h +++ b/code/oControl.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/oCourse.cpp b/code/oCourse.cpp index 29fef57..2871a4f 100644 --- a/code/oCourse.cpp +++ b/code/oCourse.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -584,7 +584,7 @@ const vector< pair > &oEvent::fillCourses(vector< pair > &oEvent::fillCourses(vector< pairgetShorterVersion(); + pCourse sh = ac[k].first->getShorterVersion().second; if (sh != 0) { int ix = id2ix[sh->getId()]; if (!ac[ix].second.first) @@ -643,7 +643,7 @@ int oCourse::getNumberMaps() const int oCourse::getNumUsedMaps(bool noVacant) const { if (tMapsUsed == -1) - oe->calculateNumRemainingMaps(); + oe->calculateNumRemainingMaps(false); if (noVacant) return tMapsUsedNoVacant; @@ -651,7 +651,6 @@ int oCourse::getNumUsedMaps(bool noVacant) const { return tMapsUsed; } - void oCourse::setStart(const wstring &start, bool sync) { if (getDI().setString("StartName", start)) { @@ -673,25 +672,42 @@ wstring oCourse::getStart() const return getDCI().getString("StartName"); } -void oEvent::calculateNumRemainingMaps() -{ - synchronizeList(oLCourseId, true, false); - synchronizeList(oLTeamId, false, false); - synchronizeList(oLRunnerId, false, true); - - for (oCourseList::iterator cit=Courses.begin(); - cit!=Courses.end();++cit) { - int numMaps = cit->getNumberMaps(); - if (numMaps == 0) - cit->tMapsRemaining = numeric_limits::min(); - else - cit->tMapsRemaining = numMaps; - - cit->tMapsUsed = 0; - cit->tMapsUsedNoVacant = 0; +void oEvent::calculateNumRemainingMaps(bool forceRecalculate) { + if (!forceRecalculate) { + if (dataRevision == tCalcNumMapsDataRevision) + return; } + synchronizeList({ oListId::oLCourseId, oListId::oLClassId, oListId::oLTeamId, oListId::oLRunnerId }); + for (auto &cit : Courses) { + if (cit.isRemoved()) + continue; + + int numMaps = cit.getNumberMaps(); + if (numMaps == 0) + cit.tMapsRemaining = numeric_limits::min(); + else + cit.tMapsRemaining = numMaps; + + cit.tMapsUsed = 0; + cit.tMapsUsedNoVacant = 0; + } + + for (auto &cit : Classes) { + if (cit.isRemoved()) + continue; + + int numMaps = cit.getNumberMaps(true); + if (numMaps == 0) + cit.tMapsRemaining = numeric_limits::min(); + else + cit.tMapsRemaining = numMaps; + + cit.tMapsUsed = 0; + cit.tMapsUsedNoVacant = 0; + } + for (oRunnerList::const_iterator it=Runners.begin(); it != Runners.end(); ++it) { if (!it->isRemoved() && it->getStatus() != StatusDNS && it->getStatus() != StatusCANCEL) { pCourse pc = it->getCourse(false); @@ -703,19 +719,33 @@ void oEvent::calculateNumRemainingMaps() if (!it->isVacant()) pc->tMapsUsedNoVacant++; } + pClass cls = it->getClassRef(true); + if (cls) { + if (cls->tMapsRemaining != numeric_limits::min()) + cls->tMapsRemaining--; + + cls->tMapsUsed++; + if (!it->isVacant()) + cls->tMapsUsedNoVacant++; + } } } // Count maps used for vacant team positions for (oTeamList::const_iterator it=Teams.begin(); it != Teams.end(); ++it) { - if (!it->isRemoved()/* && it->getStatus() != StatusDNS*/) { + if (!it->isRemoved()) { for (size_t j = 0; j < it->Runners.size(); j++) { pRunner r = it->Runners[j]; if (r) continue; // Already included if (it->Class) { + it->Class->tMapsUsed++; + + if (it->Class->tMapsRemaining != numeric_limits::min()) + it->Class->tMapsRemaining--; + const vector &courses = it->Class->MultiCourse[j]; if (courses.size()>0) { int index = it->StartNo; @@ -733,6 +763,8 @@ void oEvent::calculateNumRemainingMaps() } } } + + tCalcNumMapsDataRevision = dataRevision; } int oCourse::getIdSum(int nC) { @@ -966,6 +998,24 @@ int oCourse::getCommonControl() const { return getDCI().getInt("CControl"); } + +int oCourse::getNumLoops() const { + int cc = getCommonControl(); + if (cc == 0) + return 0; + bool wasCC = true; + int loopCount = 0; + for (int i = 0; i < nControls; i++) { + if (Controls[i]->getId() == cc) + wasCC = true; + else if (wasCC) { + loopCount++; + wasCC = false; + } + } + return loopCount; +} + void oCourse::setCommonControl(int ctrlId) { if (ctrlId != 0) { int found = 0; @@ -979,7 +1029,7 @@ void oCourse::setCommonControl(int ctrlId) { getDI().setInt("CControl", ctrlId); } -pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse) const { +pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse, int &numShorten) const { /*adaptedToOriginalCardOrder.resize(nControls + 1); for (int k = 0; k < nControls + 1; k++) adaptedToOriginalCardOrder[k] = k;*/ @@ -988,13 +1038,13 @@ pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse) const return pCourse(this); vector ccIndex; - vector< vector > loopKeys; + vector> loopKeys; if (!constructLoopKeys(cc, loopKeys, ccIndex)) return pCourse(this); bool firstAsStart = ccIndex[0] == 0; - vector< vector > punchSequence; + vector> punchSequence; vector punches; card.getPunches(punches); @@ -1012,7 +1062,6 @@ pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse) const } } - map > > preferences; for (size_t k = 0; k < punchSequence.size(); k++) { for (size_t j = 0; j < loopKeys.size(); j++) { @@ -1060,43 +1109,6 @@ pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse) const break; } - - /*for (size_t k = 0; k < punchSequence.size(); k++) { - bool done = false; - for (size_t j = 0; j < loopKeys.size(); j++) { - if (assignedKeys[j] == -1 && matchLoopKey(punchSequence[k], loopKeys[j])) { - assignedKeys[j] = k; - done = true; - break; - } - } - if (!done) { - // Check if an already assigned key matches the current loop - int undone = -1; - size_t start = loopKeys.size(); - for (size_t j = 0; j < loopKeys.size(); j++) { - if (matchLoopKey(punchSequence[k], loopKeys[j])) { - // Push that key away and try it somewhere else - undone = assignedKeys[j]; - start = j + 1; - assignedKeys[j] = k; - done = true; - break; - } - } - - // Try an already assigned key later - for (size_t j = start; undone != -1 && j < loopKeys.size(); j++) { - if (matchLoopKey(punchSequence[undone], loopKeys[j])) { - swap(assignedKeys[j], undone); - } - } - } - }*/ - - //set loops; - //for (size_t k = 0; k < ccIndex.size(); k++) - // loops.insert(k); vector loopOrder; map keyToIndex; assert(ccIndex.size() == assignedKeys.size()); @@ -1137,13 +1149,17 @@ pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse) const if (0 < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[0]); } - - + bool allowShorten = getShorterVersion().first; + numShorten = 0; bool lastAsFinish = useLastAsFinish() || Controls[nControls-1]->getId() == cc; int endIx = lastAsFinish ? nControls - 1 : nControls; for (size_t k = 0; k< loopOrder.size(); k++) { + if (allowShorten && assignedKeys[loopOrder[k]] == -1) { + numShorten++; + continue; + } int start = ccIndex[loopOrder[k]]; int end = size_t(loopOrder[k] + 1) < ccIndex.size() ? ccIndex[loopOrder[k] + 1] : endIx; for (int i = start + 1; i < end; i++) { @@ -1155,29 +1171,43 @@ pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse) const tmpCourse.tMapToOriginalOrder.push_back(end); if (k + 1 < loopOrder.size()) { int currentCC = ccIndex[k+1]; - //tmpCourse.tMapToOriginalOrder.push_back(currentCC); tmpCourse.Controls[tmpCourse.nControls++] = Controls[currentCC]; if (size_t(end) < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[end]); } } + if (numShorten > 0) { + //Shortened course. Do not duplicate last. + if (lastAsFinish && !useLastAsFinish()) { + lastAsFinish = false; + } + else { + if (tmpCourse.nControls > 0) { + tmpCourse.tMapToOriginalOrder.pop_back(); + tmpCourse.nControls--; + if (!legLengths.empty()) + tmpCourse.legLengths.pop_back(); + } + } + } if (lastAsFinish) { - //tmpCourse.tMapToOriginalOrder.push_back(nControls - 1); tmpCourse.tMapToOriginalOrder.push_back(nControls); tmpCourse.Controls[tmpCourse.nControls++] = Controls[nControls - 1]; if (size_t(nControls-1) < legLengths.size()) tmpCourse.legLengths.push_back(legLengths[nControls-1]); } - //tmpCourse.tMapToOriginalOrder.push_back(nControls); - - assert(tmpCourse.nControls == nControls); - assert(tmpCourse.tMapToOriginalOrder.size() == nControls + 1); + if (!allowShorten) { + assert(tmpCourse.nControls == nControls); + assert(tmpCourse.tMapToOriginalOrder.size() == nControls + 1); + } if (!legLengths.empty()) { tmpCourse.legLengths.push_back(legLengths.back()); - assert(tmpCourse.legLengths.size() == legLengths.size()); + if (!allowShorten) { + assert(tmpCourse.legLengths.size() == legLengths.size()); + } } tmpCourse.Id = Id; return &tmpCourse; @@ -1346,9 +1376,12 @@ wstring oCourse::getRadioName(int courseControlId) const { } // Returns the next shorter course, if any, null otherwise -pCourse oCourse::getShorterVersion() const { +pair oCourse::getShorterVersion() const { int ix = getDCI().getInt("Shorten"); - return oe->getCourse(ix); + if (ix == -1) + return make_pair(true, nullptr); + auto c = oe->getCourse(ix); + return make_pair(c != 0, c); } // Returns the next longer course, if any, null otherwise. Note that this method is slow. @@ -1362,9 +1395,9 @@ pCourse oCourse::getLongerVersion() const { return 0; } -void oCourse::setShorterVersion(pCourse shorten) { - if (shorten != 0) - getDI().setInt("Shorten", shorten->getId()); +void oCourse::setShorterVersion(bool activeShortening, pCourse shorten) { + if (activeShortening) + getDI().setInt("Shorten", shorten != 0 ? shorten->getId() : -1); else getDI().setInt("Shorten", 0); } diff --git a/code/oCourse.h b/code/oCourse.h index 5dc3c54..0f907a9 100644 --- a/code/oCourse.h +++ b/code/oCourse.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -120,6 +120,8 @@ public: int getCommonControl() const; void setCommonControl(int ctrlId); + int getNumLoops() const; + bool operator<(const oCourse &b) const {return Name getShorterVersion() const; // Returns the next longer course, if any, null otherwise. Note that this method is slow. pCourse getLongerVersion() const; // Set a shorter version of the course. - void setShorterVersion(pCourse shorter); + void setShorterVersion(bool activeShortening, pCourse shorter); // Returns a map for an adapted course to the original control order const vector &getMapToOriginalOrder() const {return tMapToOriginalOrder;} diff --git a/code/oDataContainer.cpp b/code/oDataContainer.cpp index a72e5e7..aed7778 100644 --- a/code/oDataContainer.cpp +++ b/code/oDataContainer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -830,13 +830,25 @@ void oDataContainer::allDataStored(const oBase *ob) { } } +namespace { + char *ensureCapacity(int size, vector &bfData, int &alloc) { + assert(alloc > 100); + if (size >= alloc) { + alloc += size; + bfData.resize(alloc); + } + return &bfData[0]; + } +} string oDataContainer::generateSQLSet(const oBase *ob, bool forceSetAll) const { void *data, *oldData; vector< vector > *strptr; ob->getDataBuffers(data, oldData, strptr); string sql; - char bf[1024*8]; + int alloc = 256; + vector bfData(alloc); + char *bf = &bfData[0]; for (size_t kk = 0; kk < ordered.size(); kk++) { const oDataInfo &di=ordered[kk]; @@ -848,47 +860,49 @@ string oDataContainer::generateSQLSet(const oBase *ob, bool forceSetAll) const { if (di.Type==oDTInt) { LPBYTE vd=LPBYTE(data)+di.Index; if (di.SubType == oIS8U) { - sprintf_s(bf, ", `%s`=%u", di.Name, (*((int *)vd))&0xFF); + sprintf_s(bf, alloc, ", `%s`=%u", di.Name, (*((int *)vd))&0xFF); sql+=bf; } else if (di.SubType == oIS16U) { - sprintf_s(bf, ", `%s`=%u", di.Name, (*((int *)vd))&0xFFFF); + sprintf_s(bf, alloc, ", `%s`=%u", di.Name, (*((int *)vd))&0xFFFF); sql+=bf; } else if (di.SubType == oIS8) { char r = (*((int *)vd))&0xFF; - sprintf_s(bf, ", `%s`=%d", di.Name, (int)r); + sprintf_s(bf, alloc, ", `%s`=%d", di.Name, (int)r); sql+=bf; } else if (di.SubType == oIS16) { short r = (*((int *)vd))&0xFFFF; - sprintf_s(bf, ", `%s`=%d", di.Name, (int)r); + sprintf_s(bf, alloc, ", `%s`=%d", di.Name, (int)r); sql+=bf; } else if (di.SubType != oIS64) { - sprintf_s(bf, ", `%s`=%d", di.Name, *((int *)vd)); + sprintf_s(bf, alloc, ", `%s`=%d", di.Name, *((int *)vd)); sql+=bf; } else { char tmp[32]; _i64toa_s(*((__int64 *)vd), tmp, 32, 10); - sprintf_s(bf, ", `%s`=%s", di.Name, tmp); + sprintf_s(bf, alloc, ", `%s`=%s", di.Name, tmp); sql+=bf; } } else if (di.Type==oDTString) { LPBYTE vd=LPBYTE(data)+di.Index; - sprintf_s(bf, ", `%s`='%s'", di.Name, SQL_quote((wchar_t *)vd).c_str()); + sprintf_s(bf, alloc, ", `%s`='%s'", di.Name, SQL_quote((wchar_t *)vd).c_str()); sql+=bf; } else if (di.Type==oDTStringDynamic) { const wstring &str = (*strptr)[0][di.Index]; - sprintf_s(bf, ", `%s`='%s'", di.Name, SQL_quote(str.c_str()).c_str()); + bf = ensureCapacity(2 * str.length() + 30, bfData, alloc); + sprintf_s(bf, alloc, ", `%s`='%s'", di.Name, SQL_quote(str.c_str()).c_str()); sql+=bf; } else if (di.Type==oDTStringArray) { const wstring str = encodeArray((*strptr)[di.Index]); - sprintf_s(bf, ", `%s`='%s'", di.Name, SQL_quote(str.c_str()).c_str()); + bf = ensureCapacity(2 * str.length() + 30, bfData, alloc); + sprintf_s(bf, alloc, ", `%s`='%s'", di.Name, SQL_quote(str.c_str()).c_str()); sql+=bf; } } diff --git a/code/oDataContainer.h b/code/oDataContainer.h index 197a18a..8d22b36 100644 --- a/code/oDataContainer.h +++ b/code/oDataContainer.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/oEvent.cpp b/code/oEvent.cpp index 82d7b7f..361531f 100644 --- a/code/oEvent.cpp +++ b/code/oEvent.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -64,7 +64,7 @@ #include "Table.h" //Version of database -int oEvent::dbVersion = 80; +int oEvent::dbVersion = 82; class RelativeTimeFormatter : public oDataDefiner { string name; @@ -357,7 +357,8 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oRunnerData->addVariableInt("EntrySource", oDataContainer::oIS32, "Källa"); oRunnerData->addVariableInt("Heat", oDataContainer::oIS8U, "Heat"); oRunnerData->addVariableInt("Reference", oDataContainer::oIS32, "Referens"); - + oRunnerData->addVariableInt("NoRestart", oDataContainer::oIS8U, "Ej omstart"); + oControlData=new oDataContainer(oControl::dataSize); oControlData->addVariableInt("TimeAdjust", oDataContainer::oIS32, "Tidsjustering"); oControlData->addVariableInt("MinTime", oDataContainer::oIS32, "Minitid"); @@ -443,6 +444,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oClassData->addVariableInt("Heat", oDataContainer::oIS8U, "Heat"); oClassData->addVariableInt("Locked", oDataContainer::oIS8U, "Låst gaffling"); oClassData->addVariableString("Qualification", "Kvalschema"); + oClassData->addVariableInt("NumberMaps", oDataContainer::oIS16, "Kartor"); oTeamData = new oDataContainer(oTeam::dataSize); oTeamData->addVariableCurrency("Fee", "Anm. avgift"); @@ -462,6 +464,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oTeamData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring"); oTeamData->addVariableInt("EntrySource", oDataContainer::oIS32, "Källa"); oTeamData->addVariableInt("Heat", oDataContainer::oIS8U, "Heat"); + oTeamData->addVariableInt("NoRestart", oDataContainer::oIS8U, "Ej omstart"); generalResults.push_back(GeneralResultCtr("atcontrol", L"Result at a control", new ResultAtControl())); generalResults.push_back(GeneralResultCtr("totatcontrol", L"Total/team result at a control", new TotalResultAtControl())); @@ -1280,6 +1283,9 @@ bool oEvent::open(const xmlparser &xml) { } } + for (oRunner &r : Runners) + r.apply(false, 0, true); + toc("team"); xo=xml.getObject("PunchList"); @@ -1288,8 +1294,6 @@ bool oEvent::open(const xmlparser &xml) { xo.getObjects(xl); xmlList::const_iterator it; - if (xl.size() > 10) - setupCardHash(false); // This improves performance when there are many cards. oFreePunch::disableHashing = true; try { for(it=xl.begin(); it != xl.end(); ++it){ @@ -1306,7 +1310,6 @@ bool oEvent::open(const xmlparser &xml) { } oFreePunch::disableHashing = false; oFreePunch::rehashPunches(*this, 0, 0); - setupCardHash(true); // Clear } toc("punch"); @@ -1414,6 +1417,7 @@ pRunner oEvent::dbLookUpById(__int64 extId) const oEvent *toe = const_cast(this); static oRunner sRunner = oRunner(toe, 0); sRunner = oRunner(toe, 0); + sRunner.setTemporary(); RunnerWDBEntry *dbr = runnerDB->getRunnerById(int(extId)); if (dbr != 0) { sRunner.init(*dbr); @@ -1443,7 +1447,7 @@ pRunner oEvent::dbLookUpByCard(int cardNo) const dbr->getName(sRunner.sName); oRunner::getRealName(sRunner.sName, sRunner.tRealName); sRunner.init(*dbr); - sRunner.CardNo = cardNo; + sRunner.cardNumber = cardNo; return &sRunner; } else @@ -1459,6 +1463,7 @@ pRunner oEvent::dbLookUpByName(const wstring &name, int clubId, int classId, int static oRunner sRunner = oRunner(toe, 0); sRunner = oRunner(toe, 0); + sRunner.setTemporary(); if (birthYear == 0) { pClass pc = getClass(classId); @@ -1521,7 +1526,7 @@ void oEvent::updateRunnerDatabase() oRunnerList::iterator it; map clubIdMap; for (it=Runners.begin(); it != Runners.end(); ++it){ - if (it->Card && it->Card->cardNo == it->CardNo && + if (it->Card && it->Card->cardNo == it->cardNumber && it->getDI().getInt("CardFee")==0 && it->Card->getNumPunches()>5) updateRunnerDatabase(&*it, clubIdMap); } @@ -1544,7 +1549,7 @@ void oEvent::updateRunnerDatabase() void oEvent::updateRunnerDatabase(pRunner r, map &clubIdMap) { - if (!r->CardNo) + if (!r->cardNumber) return; runnerDB->updateAdd(*r, clubIdMap); } @@ -1635,7 +1640,7 @@ pRunner oEvent::addRunner(const wstring &name, int clubId, int classId, db_r->Club = getClub(clubId); db_r->Class = getClass(classId); if (cardNo>0) - db_r->CardNo = cardNo; + db_r->cardNumber = cardNo; if (birthYear>0) db_r->setBirthYear(birthYear); return addRunnerFromDB(db_r, classId, autoAdd); @@ -1646,7 +1651,7 @@ pRunner oEvent::addRunner(const wstring &name, int clubId, int classId, r.Club = getClub(clubId); r.Class = getClass(classId); if (cardNo>0) - r.CardNo = cardNo; + r.cardNumber = cardNo; if (birthYear>0) r.setBirthYear(birthYear); pRunner pr = addRunner(r, true); @@ -1685,7 +1690,7 @@ pRunner oEvent::addRunnerFromDB(const pRunner db_r, oRunner r(this); r.sName = db_r->sName; oRunner::getRealName(r.sName, r.tRealName); - r.CardNo = db_r->CardNo; + r.cardNumber = db_r->cardNumber; if (db_r->Club) { r.Club = getClub(db_r->getClubId()); @@ -1716,9 +1721,14 @@ pRunner oEvent::addRunnerFromDB(const pRunner db_r, pRunner oEvent::addRunner(const oRunner &r, bool updateStartNo) { bool needUpdate = Runners.empty(); - + Runners.push_back(r); pRunner pr=&Runners.back(); + + if (cardToRunnerHash && r.getCardNo() != 0) { + cardToRunnerHash->emplace(r.getCardNo(), pr); + } + if (pr->StartNo == 0 && updateStartNo) { pr->StartNo = ++nextFreeStartNo; // Need not be unique } @@ -2106,7 +2116,9 @@ bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) { Teams.sort(oTeam::compareResult); } - else if (so==ClassStartTime) { + else if (so==ClassStartTime || so == ClassStartTimeClub) { + if (leg == -1) + leg = 0; for (it=Teams.begin(); it != Teams.end(); ++it) { it->_cachedStatus = StatusUnknown; it->_sortStatus=0; @@ -2114,6 +2126,40 @@ bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) { if (it->_sortTime<=0) it->_sortStatus=1; } + if (so == ClassStartTime) + Teams.sort(oTeam::compareResult); + else + Teams.sort(oTeam::compareResultNoSno); + } + else if (so == ClassKnockoutTotalResult) { + for (it = Teams.begin(); it != Teams.end(); ++it) { + it->_cachedStatus = StatusUnknown; + it->_sortStatus = 0; + it->_sortTime = 0; + + // Count number of races with results + int numResult = 0; + int lastClassHeat = 0; + for (pRunner r : it->Runners) { + if (r && (r->prelStatusOK() || + (r->tStatus != StatusUnknown && r->tStatus != StatusDNS && r->tStatus != StatusCANCEL))) { + + if (r->Class && r->tLeg > 0 && r->Class->isQualificationFinalBaseClass() && r->getClassRef(true) == r->Class) + continue; // Skip if not qualified. + + numResult++; + lastClassHeat = r->getDCI().getInt("Heat"); + it->_cachedStatus = r->tStatus; + it->_sortTime = r->getRunningTime(); + } + } + if (lastClassHeat > 50 || lastClassHeat < 0) + lastClassHeat = 0; + + unsigned rawStatus = it->_cachedStatus; + it->_sortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0] - (numResult * 100 + lastClassHeat) * 1000; + + } Teams.sort(oTeam::compareResult); } return true; @@ -2189,7 +2235,6 @@ const wstring &oEvent::getAbsTime(DWORD time) const { } } - const wstring &oEvent::getTimeZoneString() const { if (!date2LocalTZ.count(Date)) date2LocalTZ[Date] = ::getTimeZoneString(Date); @@ -2327,12 +2372,15 @@ int oEvent::convertAbsoluteTime(const wstring &m) int len=m.length(); bool firstComma = false; - for (int k=0;k='0' && b<='9')) ) { - if (b==':' && firstComma == false) + bool anyColon = false; + for (int k = 0; k < len; k++) { + wchar_t b = m[k]; + if (!(b == ' ' || (b >= '0' && b <= '9'))) { + if (b == ':' && firstComma == false) { + anyColon = true; continue; - else if ((b==',' || b=='.') && firstComma == false) { + } + else if ((b == ',' || b == '.') && firstComma == false) { firstComma = true; continue; } @@ -2342,6 +2390,16 @@ int oEvent::convertAbsoluteTime(const wstring &m) int hour=_wtoi(m.c_str()); + if (!anyColon && hour>=0 && len>=5) { + int second = hour % 100; + hour /= 100; + int minute = hour % 100; + hour /= 100; + if (hour > 23 || minute >=60 || second >= 60) + return -1; + return hour * 3600 + minute * 60 + second; + } + if (hour<0 || hour>23) return -1; @@ -2445,6 +2503,7 @@ int oEvent::getRelativeTime(const wstring &m) const { void oEvent::removeRunner(const vector &ids) { + cardToRunnerHash.reset(); oRunnerList::iterator it; set toRemove; @@ -2926,7 +2985,6 @@ void oEvent::generateInForestList(gdioutput &gdi, GUICALLBACK cb, GUICALLBACK cb y=gdi.getCY(); vector rr; - setupCardHash(false); for(it=Runners.begin(); it != Runners.end(); ++it){ if (it->skip() || it->needNoCard()) @@ -2961,9 +3019,9 @@ void oEvent::generateInForestList(gdioutput &gdi, GUICALLBACK cb, GUICALLBACK cb } } - getRunnersByCardNo(it->getCardNo(), true, true, rr); + getRunnersByCardNo(it->getCardNo(), false, CardLookupProperty::SkipNoStart, rr); for (size_t k = 0; k < rr.size(); k++) { - if (rr[k]->getId() != it->getId()) { + if (!rr[k]->skip() && rr[k]->getId() != it->getId()) { if (otherRunners.empty()) { otherRunners = lang.tl("Bricka X används också av: #" + itos(it->getCardNo())); } @@ -3009,7 +3067,6 @@ void oEvent::generateInForestList(gdioutput &gdi, GUICALLBACK cb, GUICALLBACK cb gdi.addString("", 10, "inforestwarning"); } } - setupCardHash(true); gdi.updateScrollbars(); } @@ -3534,6 +3591,7 @@ void oEvent::clear() Id=0; dataRevision = 0; tClubDataRevision = -1; + tCalcNumMapsDataRevision = -1; ZeroTime=0; Name.clear(); @@ -3548,6 +3606,7 @@ void oEvent::clear() tLongTimesCached = -1; //Order of destruction is extreamly important... + cardToRunnerHash.reset(); runnerById.clear(); bibStartNoToRunnerTeam.clear(); Runners.clear(); @@ -3744,7 +3803,7 @@ void oEvent::reEvaluateAll(const set &cls, bool doSync) if (!cls.empty() && cls.count(it->getClassId(true)) == 0) continue; - if (!it->tInTeam) { + if (!it->tInTeam || it->Class != it->tInTeam->Class || (it->Class && it->Class->isQualificationFinalBaseClass())) { it->resetTmpStore(); it->apply(false, 0, true); } @@ -3788,7 +3847,7 @@ void oEvent::reEvaluateAll(const set &cls, bool doSync) if (!cls.empty() && cls.count(it->getClassId(true)) == 0) continue; - if (!it->tInTeam) + if (!it->tInTeam || it->Class != it->tInTeam->Class || (it->Class && (it->Class->isQualificationFinalBaseClass()))) it->apply(false, 0, true); it->setTmpStore(); it->storeTimes(); @@ -4146,9 +4205,10 @@ int oEvent::getFirstStart(int classId) const { int minTime=3600*24; while(it!=Runners.end()){ - if (!it->isRemoved() && classId==0 || it->getClassId(true)==classId) - if (it->tStartTime < minTime && it->tStatus!=StatusNotCompetiting && it->tStartTime>0) + if (!it->isRemoved() && (classId == 0 || it->getClassId(true) == classId)) { + if (it->tStartTime < minTime && it->tStatus != StatusNotCompetiting && it->tStartTime>0) minTime = it->tStartTime; + } ++it; } @@ -4723,7 +4783,7 @@ void oEvent::assignCardInteractive(gdioutput &gdi, GUICALLBACK cb) r+=it->getClass(false)+L", "; if (it->tInTeam) { - r+=itow(it->tInTeam->getStartNo()); + L" " + it->tInTeam->getName(); + r+=itow(it->tInTeam->getStartNo()) + L" " + it->tInTeam->getName() + L", "; } r += it->getName() + L":"; @@ -4851,10 +4911,8 @@ void oEvent::generateTestCard(SICard &sic) const oRunner *r = 0; int cardNo = 0; while(r==0 && it!=Runners.end()) { - cardNo = it->CardNo; - if (cardNo == 0 && it->tParentRunner) - cardNo = it->tParentRunner->CardNo; - + cardNo = it->getCardNo(); + if (it->Class && it->tLeg>0) { StartTypes st = it->Class->getStartType(it->tLeg); if (st == STHunting) { @@ -4884,9 +4942,7 @@ void oEvent::generateTestCard(SICard &sic) const } --it; while(r==0 && it!=Runners.begin()) { - cardNo = it->CardNo; - if (cardNo == 0 && it->tParentRunner) - cardNo = it->tParentRunner->CardNo; + cardNo = it->getCardNo(); if (it->Class && it->tLeg>0) { StartTypes st = it->Class->getStartType(it->tLeg); @@ -5066,7 +5122,6 @@ void oEvent::generateTestCompetition(int nClasses, int nRunners, oe->setZeroTime(L"05:00:00"); oe->getMeOSFeatures().useAll(*oe); } - bool useSOFTMethod = true; vector gname; //gname.reserve(RunnerDatabase.size()); vector fname; @@ -5162,7 +5217,7 @@ void oEvent::generateTestCompetition(int nClasses, int nRunners, if (k%5!=5) { vector spec; spec.push_back(ClassDrawSpecification(cls->getId(), 0, getRelativeTime(start), 10, 3)); - drawList(spec, useSOFTMethod, 1, drawAll); + drawList(spec, DrawMethod::MeOS, 1, oEvent::DrawType::DrawAll); } else cls->Name += L" Öppen"; @@ -5184,7 +5239,7 @@ void oEvent::generateTestCompetition(int nClasses, int nRunners, if ( cls->getStartType(0)==STDrawn ) { vector spec; spec.push_back(ClassDrawSpecification(cls->getId(), 0, getRelativeTime(start), 20, 3)); - drawList(spec, useSOFTMethod, 1, drawAll); + drawList(spec, DrawMethod::MeOS, 1, DrawType::DrawAll); } } } @@ -5256,7 +5311,7 @@ void oEvent::fillLegNumbers(const set &cls, bool includeSubLegs, vector< pair > &out) { oClassList::iterator it; - synchronizeList(oLClassId); + synchronizeList(oListId::oLClassId); out.clear(); set< pair > legs; @@ -5390,8 +5445,7 @@ void oEvent::generateTableData(const string &tname, Table &table, TableUpdateInf void oEvent::applyEventFees(bool updateClassFromEvent, bool updateFees, bool updateCardFees, const set &classFilter) { - synchronizeList(oLClassId, true, false); - synchronizeList(oLRunnerId, false, true); + synchronizeList({ oListId::oLClassId, oListId::oLRunnerId }); bool allClass = classFilter.empty(); if (updateClassFromEvent) { @@ -5435,7 +5489,7 @@ void oEvent::applyEventFees(bool updateClassFromEvent, #ifndef MEOSDB void hideTabs(); void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker, - bool skipEconomy, bool skipLists, bool skipRunners, bool skipControls); + bool skipEconomy, bool skipLists, bool skipRunners, bool skipCourses, bool skipControls); void oEvent::updateTabs(bool force, bool hide) const { @@ -5449,14 +5503,14 @@ void oEvent::updateTabs(bool force, bool hide) const bool hasRunner = !Runners.empty() || !Classes.empty(); bool hasLists = !empty(); - + bool skipCourses = getMeOSFeatures().withoutCourses(*this); if (hide || isReadOnly()) hideTabs(); else createTabs(force, empty(), !hasTeam, !getMeOSFeatures().hasFeature(MeOSFeatures::Speaker), !(getMeOSFeatures().hasFeature(MeOSFeatures::Economy) || getMeOSFeatures().hasFeature(MeOSFeatures::EditClub)), - !hasLists, !hasRunner, Controls.empty()); + !hasLists, !hasRunner, skipCourses, Controls.empty() && !skipCourses); } #else @@ -5465,15 +5519,16 @@ void oEvent::updateTabs(bool force) const } #endif -bool oEvent::useRunnerDb() const -{ +bool oEvent::useRunnerDb() const { return getMeOSFeatures().hasFeature(MeOSFeatures::RunnerDb); - //return getDCI().getInt("SkipRunnerDb")==0; } -/* -void oEvent::useRunnerDb(bool use) { - getDI().setInt("SkipRunnerDb", use ? 0 : 1); -}*/ + +int oEvent::getBaseCardFee() const { + int baseCardFee = oe->getDI().getInt("CardFee"); + if (baseCardFee == 0) + baseCardFee = -1; + return baseCardFee; +} bool oEvent::hasMultiRunner() const { for (oClassList::const_iterator it = Classes.begin(); it!=Classes.end(); ++it) { @@ -5486,18 +5541,18 @@ bool oEvent::hasMultiRunner() const { /** Return false if card is not used */ bool oEvent::checkCardUsed(gdioutput &gdi, oRunner &runnerToAssignCard, int cardNo) { - synchronizeList(oLRunnerId); - //pRunner pold=oe->getRunnerByCardNo(cardNo, 0, true, true); - wchar_t bf[1024]; pRunner pold = 0; - for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { - if (it->skip()) - continue; - if (!runnerToAssignCard.canShareCard(&*it, cardNo)) { - pold = &*it; - break; + if (cardNo != 0) { + vector allR; + getRunnersByCardNo(cardNo, true, CardLookupProperty::OnlyMainInstance, allR); + for (pRunner it : allR) { + if (!runnerToAssignCard.canShareCard(it, cardNo)) { + pold = &*it; + break; + } } } + wchar_t bf[1024]; if (pold) { swprintf_s(bf, (L"#" + lang.tl("Bricka %d används redan av %s och kan inte tilldelas.")).c_str(), @@ -5560,6 +5615,7 @@ void oEvent::sanityCheck(gdioutput &gdi, bool expectResult, int onlyThisClass) { if (!it->tInTeam) { ClassType type = it->Class->getClassType(); if (type == oClassIndividRelay) { + it->setClassId(0, true); it->setClassId(it->Class->getId(), true); it->synchronize(); } @@ -5772,7 +5828,7 @@ void oEvent::setCurrency(int factor, const wstring &symbol, const wstring &separ } wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes, - bool cloneCourses, bool cloneResult, bool addToDate) { + bool cloneCourses, bool cloneResult, bool addToDate) { if (cloneResult) { cloneTimes = true; @@ -5852,7 +5908,7 @@ wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes, r.sName = it->sName; oRunner::getRealName(r.sName, r.tRealName); r.StartNo = it->StartNo; - r.CardNo = it->CardNo; + r.cardNumber = it->cardNumber; r.Club = ce.getClub(it->getClubId()); r.Class = ce.getClass(it->getClassId(false)); @@ -5933,12 +5989,41 @@ wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes, if (getMeOSFeatures().hasFeature(f)) ce.getMeOSFeatures().useFeature(f, true, ce); } - - ce.save(); + + // Transfer lists and list configurations. + if (listContainer) { + loadGeneralResults(false); + swap(ce.generalResults, generalResults); + try { + ce.listContainer = new MetaListContainer(&ce, *listContainer); + ce.save(); + } + catch (...) { + swap(ce.generalResults, generalResults); + throw; + } + swap(ce.generalResults, generalResults); + } return ce.CurrentFile; } +void oEvent::transferListsAndSave(const oEvent &src) { + src.loadGeneralResults(false); + swap(src.generalResults, generalResults); + try { + src.getListContainer().synchronizeTo(getListContainer()); + save(); + } + catch (...) { + swap(src.generalResults, generalResults); + throw; + } + + swap(src.generalResults, generalResults); +} + + bool checkTargetClass(pRunner target, pRunner source, const oClassList &Classes, const vector &targetVacant, @@ -6062,7 +6147,7 @@ void oEvent::transferResult(oEvent &ce, } } - calculateResults(RTTotalResult); + calculateResults({}, ResultType::TotalResult); // Lookup by id for (size_t k = 0; k < targetRunners.size(); k++) { pRunner it = targetRunners[k]; @@ -6082,7 +6167,7 @@ void oEvent::transferResult(oEvent &ce, wstring ccnB = canonizeName(r->getClub().c_str()); if ((id1>0 && id1==id2) || - (r->CardNo>0 && r->CardNo == it->CardNo) || + (r->cardNumber>0 && r->cardNumber == it->cardNumber) || (it->sName == r->sName) || (cnA == cnB && ccnA == ccnB)) { processed.insert(it->Id, 1); used.insert(r->Id, 1); @@ -6098,13 +6183,12 @@ void oEvent::transferResult(oEvent &ce, if (processed.size() < int(targetRunners.size())) { // Lookup by card int v; - setupCardHash(false); for (size_t k = 0; k < targetRunners.size(); k++) { pRunner it = targetRunners[k]; if (processed.lookup(it->Id, v)) continue; - if (it->CardNo > 0) { - pRunner r = getRunnerByCardNo(it->CardNo, 0); + if (it->getCardNo() > 0) { + pRunner r = getRunnerByCardNo(it->getCardNo(), 0, CardLookupProperty::Any); if (!r || used.lookup(r->Id, v)) continue; @@ -6127,7 +6211,6 @@ void oEvent::transferResult(oEvent &ce, } } } - setupCardHash(true); // Clear cache } int v = -1; diff --git a/code/oEvent.h b/code/oEvent.h index 7f01c4b..92dc7ab 100644 --- a/code/oEvent.h +++ b/code/oEvent.h @@ -2,16 +2,11 @@ // ////////////////////////////////////////////////////////////////////// -#if !defined(AFX_OEVENT_H__CDA15578_CB62_4EAD_96B9_3037355F5D48__INCLUDED_) -#define AFX_OEVENT_H__CDA15578_CB62_4EAD_96B9_3037355F5D48__INCLUDED_ - -#if _MSC_VER > 1000 #pragma once -#endif // _MSC_VER > 1000 /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,18 +42,8 @@ #include #include -#ifdef OLD - #include - #include - -#define unordered_multimap stdext::hash_multimap -#define unordered_map stdext::hash_map -#define unordered_set stdext::hash_set - -#else - #include - #include -#endif +#include +#include #define cVacantId 888888888 #define cNoClubId 999999999 @@ -206,9 +191,9 @@ typedef bool (__cdecl* OPENDB_FCN)(void); typedef int (__cdecl* SYNCHRONIZE_FCN)(oBase *obj); typedef bool (__cdecl* SYNCHRONIZELIST_FCN)(oBase *obj, int lid); -enum oListId {oLRunnerId=1, oLClassId=2, oLCourseId=4, - oLControlId=8, oLClubId=16, oLCardId=32, - oLPunchId=64, oLTeamId=128, oLEventId=256}; +enum class oListId {oLRunnerId=1, oLClassId=2, oLCourseId=4, + oLControlId=8, oLClubId=16, oLCardId=32, + oLPunchId=64, oLTeamId=128, oLEventId=256}; class Table; @@ -469,13 +454,19 @@ protected: /** Format a string for a list. */ const wstring &formatListStringAux(const oPrintPost &pp, const oListParam &par, const pTeam t, const pRunner r, const pClub c, - const pClass pc, oCounter &counter) const; + const pClass pc, const oCounter &counter) const; /** Format a string that does not depend on team or runner*/ const wstring &formatSpecialStringAux(const oPrintPost &pp, const oListParam &par, const pTeam t, int legIndex, const pCourse pc, const pControl ctrl, - oCounter &counter) const; + const oCounter &counter) const; + + /** Format a string that depends on punches/card, not a course.*/ + const wstring &formatPunchStringAux(const oPrintPost &pp, const oListParam &par, + const pTeam t, const pRunner r, + const oPunch *punch, oCounter &counter) const; + void changedObject(); mutable vector generalResults; @@ -493,12 +484,37 @@ public: Raw, }; + /** What to draw */ + enum class DrawType { + DrawAll, RemainingBefore, RemainingAfter, + }; + + /** Drawing algorithm. */ + enum class DrawMethod { + NOMethod = -1, + + Random = 1, + SOFT = 6, + MeOS = 2, + + Clumped = 3, + Simultaneous = 4, + Seeded = 5, + + Pursuit = 11, + ReversePursuit = 12 + }; + private: NameMode currentNameMode; public: - NameMode getNameMode() const {return currentNameMode;}; - NameMode setNameMode(NameMode newNameMode); + + // Returns true if there is a class with restart time + bool hasAnyRestartTime() const; + + NameMode getNameMode() const {return currentNameMode;} + NameMode setNameMode(NameMode newNameMode) { currentNameMode = newNameMode; } /// Get new punches since firstTime void getLatestPunches(int firstTime, vector &punches) const; @@ -533,8 +549,7 @@ public: //Get list of runners in a class void getRunners(int classId, int courseId, vector &r, bool sortRunners = true); - void getRunnersByCard(int cardNo, vector &r); - + void getTeams(int classId, vector &t, bool sortTeams = true); bool hasRank() const; @@ -573,7 +588,7 @@ public: // Automatic draw of all classes void automaticDrawAll(gdioutput &gdi, const wstring &firstStart, const wstring &minIntervall, const wstring &vacances, - bool lateBefore, bool softMethod, int pairSize); + bool lateBefore, DrawMethod method, int pairSize); // Restore a backup by renamning the file to .meos void restoreBackup(); @@ -585,7 +600,6 @@ public: void updateTabs(bool force = false, bool hide = false) const; bool useRunnerDb() const; - void useRunnerDb(bool use); int getFirstClassId(bool teamClass) const; @@ -606,6 +620,8 @@ public: void applyEventFees(bool updateClassFromEvent, bool updateFees, bool updateCardFees, const set &classFilter); + /** Return a positive base card fee, or -1 for no card fee. (Which is to be set as card fee on runners)*/ + int getBaseCardFee() const; void listConnectedClients(gdioutput &gdi); void validateClients(); @@ -707,6 +723,10 @@ public: const wstring &formatSpecialString(const oPrintPost &pp, const oListParam &par, const pTeam t, int legIndex, const pCourse crs, const pControl ctrl, oCounter &counter) const; + + const wstring &formatPunchString(const oPrintPost &pp, const oListParam &par, + const pTeam t, const pRunner r, + const oPunch *punch, oCounter &counter) const; void calculatePrintPostKey(const list &ppli, gdioutput &gdi, const oListParam &par, const pTeam t, const pRunner r, const pClub c, @@ -719,8 +739,10 @@ public: /** Format a print post. Returns true of output is not empty*/ bool formatPrintPost(const list &ppli, PrintPostInfo &ppi, const pTeam t, const pRunner r, const pClub c, - const pClass pc, const pCourse crs, const pControl ctrl, int legIndex); - void listGeneratePunches(const list &ppli, gdioutput &gdi, const oListParam &par, + const pClass pc, const pCourse crs, + const pControl ctrl, const oPunch *punch, int legIndex); + + void listGeneratePunches(const oListInfo &listInfo, gdioutput &gdi, pTeam t, pRunner r, pClub club, pClass cls); void getListTypes(map &listMap, int filter); void getListType(EStdListType type, oListInfo &li); @@ -784,16 +806,24 @@ public: }; void getResultEvents(const set &classFilter, const set &controlFilter, vector &results) const; + + /** Compute results for split times while runners are on course.*/ + void computePreliminarySplitResults(const set &classes); + protected: // Returns hash key for punch based on control id, and leg. Class is marked as changed if oldHashKey != newHashKey. int getControlIdFromPunch(int time, int type, int card, bool markClassChanged, oFreePunch &punch); - static void drawSOFTMethod(vector &runners, bool handleBlanks=true); bool enumerateBackups(const wstring &file, const wstring &filetype, int type); - unordered_multimap cardHash; mutable multimap bibStartNoToRunnerTeam; + + mutable shared_ptr> cardToRunnerHash; + unordered_multimap &getCardToRunner() const; + int tClubDataRevision; + int tCalcNumMapsDataRevision = -1; + bool readOnly; mutable int tLongTimesCached; mutable map > cachedFirstStart; //First start per classid. @@ -816,9 +846,6 @@ public: void setupClubInfoData(); //Precalculate temporary data in club object - // Clears map if clear is true. A cleared map is not used. Map must be cleared after use, is not updated. - void setupCardHash(bool clear = false); - void remove(); bool canRemove() const; @@ -849,6 +876,8 @@ public: //Returns true if data is changed. bool autoSynchronizeLists(bool syncPunches); + + bool synchronizeList(initializer_list types); bool synchronizeList(oListId id, bool preSyncEvent = true, bool postSyncEvent = true); void generateInForestList(gdioutput &gdi, GUICALLBACK cb, @@ -870,7 +899,7 @@ public: pCard getCardByNumber(int cno) const; bool isCardRead(const SICard &card) const; void getCards(vector &cards); - + int getNumCards() const { return Cards.size(); } /** Try to find the class that best matches the card. Negative return = missing controls Positve return = extra controls @@ -980,8 +1009,9 @@ public: bool openRunnerDatabase(const wchar_t *file); bool saveRunnerDatabase(const wchar_t *file, bool onlyLocal); - enum ResultType {RTClassResult, RTTotalResult, RTCourseResult, RTClassCourseResult}; - void calculateResults(ResultType result, bool includePreliminary = false); + enum class ResultType {ClassResult, TotalResult, CourseResult, + ClassCourseResult, PreliminarySplitResults}; + void calculateResults(const set &classes, ResultType result, bool includePreliminary = false); void calculateRogainingResults(const set &classSelection); void calculateResults(list &rl); @@ -1001,13 +1031,9 @@ public: void loadDrawSettings(const set &classes, DrawInfo &drawInfo, vector &cInfo) const; - enum DrawType { - drawAll, remainingBefore, remainingAfter, - }; - - void drawRemaining(bool useSOFTMethod, bool placeAfter); + void drawRemaining(DrawMethod method, bool placeAfter); void drawList(const vector &spec, - bool useSOFTMethod, int pairSize, DrawType drawType); + DrawMethod method, int pairSize, DrawType drawType); void drawListClumped(int classID, int firstStart, int interval, int vacances); void drawPersuitList(int classId, int firstTime, int restartTime, int ropeTime, int interval, int pairSize, @@ -1040,6 +1066,16 @@ public: pRunner addRunnerVacant(int classId); pRunner getRunner(int Id, int stage) const; + + enum class CardLookupProperty { + Any, /** Does not include NotCompeting */ + ForReadout, /** Runners with no card, even if status DNS.*/ + IncludeNotCompeting, + CardInUse, /** Runners with no card, ignoring DNS runners*/ + SkipNoStart, + OnlyMainInstance + }; + /** Get a competitor by cardNo. @param cardNo card number to look for. @param time if non-zero, try to find a runner actually running on the specified time, if there are multiple runners using the same card. @@ -1047,17 +1083,14 @@ public: @param ignoreRunnersWithNoStart If true, never return a runner with status NoStart @return runner of null. */ - pRunner getRunnerByCardNo(int cardNo, int time, - bool onlyRunnerWithNoCard = false, - bool ignoreRunnersWithNoStart = false) const; + pRunner getRunnerByCardNo(int cardNo, int time, CardLookupProperty prop) const; /** Get all competitors for a cardNo. @param cardNo card number to look for. @param ignoreRunnersWithNoStart If true, skip runners with status DNS @param skipDuplicates if true, only return the main instance of each runner (if several races) @param out runners using the card */ - void getRunnersByCardNo(int cardNo, bool ignoreRunnersWithNoStart, - bool skipDuplicates, vector &out) const; + void getRunnersByCardNo(int cardNo, bool updateSort, CardLookupProperty prop, vector &out) const; /** Finds a runner by start number (linear search). If several runners has same bib/number try to get the right one: findWithoutCardNo false : find first that has not finished findWithoutCardNo true : find first with no card. @@ -1124,7 +1157,11 @@ public: wstring getAutoClassName() const; pClass addClass(const wstring &pname, int CourseId = 0, int classId = 0); pClass addClass(oClass &c); - pClass getClassCreate(int Id, const wstring &createName); + /** Get a class if it exists, or create it. + exactNames is a set of class names that must be matched exactly. + It is extended with the name of the class added. The purpose is to allow very + similar (but distinct) names in a single imported file. */ + pClass getClassCreate(int id, const wstring &createName, set &exactNames); pClass getClass(const wstring &name) const; void getClasses(vector &classes, bool sync) const; pClass getBestClassMatch(const wstring &name) const; @@ -1165,13 +1202,16 @@ public: pCourse getCourse(int Id) const; void getCourses(vector &courses) const; + + int getNumCourses() const { return Courses.size(); } + /** Get controls. If calculateCourseControls, duplicate numbers are calculated for each control and course. */ void getControls(vector &controls, bool calculateCourseControls) const; void fillCourses(gdioutput &gdi, const string &id, bool simple = false); const vector< pair > &fillCourses(vector< pair > &out, bool simple = false); - void calculateNumRemainingMaps(); + void calculateNumRemainingMaps(bool forceRecalculate); pControl getControl(int Id) const; pControl getControl(int Id, bool create); @@ -1218,7 +1258,7 @@ protected: bool addXMLRank(const xmlobject &xrank, const map<__int64, int> &externIdToRunnerId, map> &output); bool addXMLEvent(const xmlobject &xevent); - bool addXMLCourse(const xmlobject &xcourse, bool addClasses); + bool addXMLCourse(const xmlobject &xcourse, bool addClasses, set &matchedClasses); /** type: 0 control, 1 start, 2 finish*/ bool addXMLControl(const xmlobject &xcontrol, int type); @@ -1257,6 +1297,9 @@ public: vector ¬Transfered, vector &noAssignmentTarget); + + void transferListsAndSave(const oEvent &src); + enum MultiStageType { MultiStageNone = 0, MultiStageSeparateEntry = 1, @@ -1313,9 +1356,5 @@ public: friend class TestMeOS; - //const string &toNarrow(const wstring &in) const {return gdibase.toNarrow(in);} - //const wstring &toWide(const string &in) const {return gdibase.toWide(in);} const gdioutput &gdiBase() const {return gdibase;} }; - -#endif // !defined(AFX_OEVENT_H__CDA15578_CB62_4EAD_96B9_3037355F5D48__INCLUDED_) diff --git a/code/oEventDraw.cpp b/code/oEventDraw.cpp index 72cd14b..e8d2bf2 100644 --- a/code/oEventDraw.cpp +++ b/code/oEventDraw.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include "stdafx.h" #include +#include #include #include #include @@ -101,87 +102,323 @@ bool ClassBlockInfo::operator <(ClassBlockInfo &ci) return Depth > > &StartField, int nFields, - int FirstPos, int PosInterval, ClassInfo &cInfo) -{ - int Type = cInfo.unique; - int courseId = cInfo.courseId; +namespace { - int nEntries = cInfo.nRunners; - bool disallowNeighbors = !di.allowNeighbourSameCourse; - // Adjust first pos to make room for extra (before first start) - if (cInfo.nExtra>0) { - int newFirstPos = FirstPos - cInfo.nExtra * PosInterval; - while (newFirstPos<0) - newFirstPos += PosInterval; - int extra = (FirstPos - newFirstPos) / PosInterval; - nEntries += extra; - FirstPos = newFirstPos; - } - - //Check if free at all... - for(int k = 0; k < nEntries; k++){ - bool hasFree=false; - for(int f=0;f 1 && ix+1 < StartField[f].size()) - nextT = StartField[f][ix + 1].second; - if (PosInterval > 1 && ix>0) - prevT = StartField[f][ix - 1].second; - - if ((nextT > 0 && nextT == courseId) || (prevT > 0 && prevT == courseId)) - return false; - } - if (t == 0) - hasFree=true; - else if (t == Type) - return false;//Type of course occupied. Cannot put it here; + void getLargestClub(map > &clubRunner, vector &largest) + { + size_t maxClub = 0; + for (map >::iterator it = + clubRunner.begin(); it != clubRunner.end(); ++it) { + maxClub = max(maxClub, it->second.size()); } - if (!hasFree) return false;//No free start position. + for (map >::iterator it = + clubRunner.begin(); it != clubRunner.end(); ++it) { + if (it->second.size() == maxClub) { + swap(largest, it->second); + clubRunner.erase(it); + return; + } + } } - return true; -} - -bool insertStart(vector< vector< pair > > &StartField, int nFields, ClassInfo &cInfo) -{ - int Type = cInfo.unique; - int courseId = cInfo.courseId; - int nEntries = cInfo.nRunners; - int FirstPos = cInfo.firstStart; - int PosInterval = cInfo.interval; - - // Adjust first pos to make room for extra (before first start) - if (cInfo.nExtra>0) { - int newFirstPos = FirstPos - cInfo.nExtra * PosInterval; - while (newFirstPos<0) - newFirstPos += PosInterval; - int extra = (FirstPos - newFirstPos) / PosInterval; - nEntries += extra; - FirstPos = newFirstPos; + void getRange(int size, vector &p) { + p.resize(size); + for (size_t k = 0; k < p.size(); k++) + p[k] = k; } - for (int k=0; k &runners, bool handleBlanks) { + if (runners.empty()) + return; - for (int f=0;f > clubRunner; + + for (size_t k = 0; k < runners.size(); k++) { + int clubId = runners[k] ? runners[k]->getClubId() : -1; + clubRunner[clubId].push_back(runners[k]); + } + + vector< vector > runnerGroups(1); + + // Find largest club + getLargestClub(clubRunner, runnerGroups[0]); + + int largeSize = runnerGroups[0].size(); + int ngroups = (runners.size() + largeSize - 1) / largeSize; + runnerGroups.resize(ngroups); + + while (!clubRunner.empty()) { + // Find the smallest available group + unsigned small = runners.size() + 1; + int cgroup = -1; + for (size_t k = 1; k < runnerGroups.size(); k++) + if (runnerGroups[k].size() < small) { + cgroup = k; + small = runnerGroups[k].size(); + } + + // Add the largest remaining group to the smallest. + vector largest; + getLargestClub(clubRunner, largest); + runnerGroups[cgroup].insert(runnerGroups[cgroup].end(), largest.begin(), largest.end()); + } + + unsigned maxGroup = runnerGroups[0].size(); + + //Permute the first group + vector pg(maxGroup); + getRange(pg.size(), pg); + permute(pg); + vector pr(maxGroup); + for (unsigned k = 0; k < maxGroup; k++) + pr[k] = runnerGroups[0][pg[k]]; + runnerGroups[0] = pr; + + //Find the largest group + for (size_t k = 1; k < runnerGroups.size(); k++) + maxGroup = max(maxGroup, runnerGroups[k].size()); + + if (handleBlanks) { + //Give all groups same size (fill with 0) + for (size_t k = 1; k < runnerGroups.size(); k++) + runnerGroups[k].resize(maxGroup); + } + + // Apply algorithm recursivly to groups with several clubs + for (size_t k = 1; k < runnerGroups.size(); k++) + drawSOFTMethod(runnerGroups[k], true); + + // Permute the order of groups + vector p(runnerGroups.size()); + getRange(p.size(), p); + permute(p); + + // Write back result + int index = 0; + for (unsigned level = 0; level < maxGroup; level++) { + for (size_t k = 0; k < runnerGroups.size(); k++) { + int gi = p[k]; + if (level < runnerGroups[gi].size() && (runnerGroups[gi][level] != 0 || !handleBlanks)) + runners[index++] = runnerGroups[gi][level]; } } - if (!HasFree) - return false; //No free start position. Fail. + if (handleBlanks) + runners.resize(index); } - return true; + void drawMeOSMethod(vector &runners) { + if (runners.empty()) + return; + map> runnersPerClub; + for (pRunner r : runners) + runnersPerClub[r->getClubId()].push_back(r); + vector> sizeClub; + for (auto &rc : runnersPerClub) + sizeClub.emplace_back(rc.second.size(), rc.first); + + sort(sizeClub.rbegin(), sizeClub.rend()); + + int targetGroupSize = max(runners.size()/20, sizeClub.front().first); + + vector> groups(1); + for (auto &sc : sizeClub) { + int currentSize = groups.back().size(); + int newSize = currentSize + sc.first; + if (abs(currentSize - targetGroupSize) < abs(newSize - targetGroupSize)) { + groups.emplace_back(); + } + groups.back().insert(groups.back().end(), runnersPerClub[sc.second].begin(), + runnersPerClub[sc.second].end()); + } + + size_t nRunnerTot = runners.size(); + + if (groups.front().size() > (nRunnerTot + 2) / 2 && groups.size() > 1) { + // We cannot distribute without clashes -> move some to other groups to prevent tail of same club + int toMove = groups.front().size() - (nRunnerTot + 2) / 2; + for (int i = 0; i < toMove; i++) { + int dest = 1 + i % (groups.size() - 1); + groups[dest].push_back(groups.front().back()); + groups.front().pop_back(); + } + } + + // Permute groups + size_t maxGroupSize = 0; + vector pv; + + for (auto &group : groups) { + pv.clear(); + for (size_t i = 0; i < group.size(); i++) { + pv.push_back(i); + } + permute(pv); + vector tg; + tg.reserve(group.size()); + for (int i : pv) + tg.push_back(group[i]); + tg.swap(group); + maxGroupSize = max(maxGroupSize, group.size()); + + } + + runners.clear(); + size_t takeMaxGroupInterval; + if (groups.size() > 10) + takeMaxGroupInterval = groups.size() / 4; + else + takeMaxGroupInterval = max(2u, groups.size() - 2); + + deque recentGroups; + + int ix = 0; + + if (maxGroupSize * 2 > nRunnerTot) { + takeMaxGroupInterval = 2; + ix = 1; + } + + while (true) { + ix++; + pair currentMaxGroup(0, -1); + int otherNonEmpty = -1; + size_t nonEmptyGroups = 0; + for (size_t gx = 0; gx < groups.size(); gx++) { + if (groups[gx].empty()) + continue; + + nonEmptyGroups++; + + if (groups[gx].size() > currentMaxGroup.first) { + if (otherNonEmpty == -1 || groups[otherNonEmpty].size() < currentMaxGroup.first) + otherNonEmpty = currentMaxGroup.second; + + currentMaxGroup.first = groups[gx].size(); + currentMaxGroup.second = gx; + } + else { + if (otherNonEmpty == -1 || groups[otherNonEmpty].size() < groups[gx].size()) + otherNonEmpty = gx; + } + } + if (currentMaxGroup.first == 0) + break; // Done + + int groupToUse = currentMaxGroup.second; + if (ix != takeMaxGroupInterval) { + // Select some other group + for (size_t attempt = 0; attempt < groups.size() * 2; attempt++) { + int g = GetRandomNumber(groups.size()); + if (!groups[g].empty() && count(recentGroups.begin(), recentGroups.end(), g) == 0) { + groupToUse = g; + break; + } + } + } + else { + ix = 0; + } + + if (!recentGroups.empty()) { //Make sure to avoid duplicates of same group (if possible) + if (recentGroups.back() == groupToUse && otherNonEmpty != -1) + groupToUse = otherNonEmpty; + } + + // Try to spread groups by ensuring that the same group is not used near itself + recentGroups.push_back(groupToUse); + if (recentGroups.size() > takeMaxGroupInterval || recentGroups.size() >= nonEmptyGroups) + recentGroups.pop_front(); + + runners.push_back(groups[groupToUse].back()); + groups[groupToUse].pop_back(); + } + } + + bool isFree(const DrawInfo &di, vector< vector > > &StartField, int nFields, + int FirstPos, int PosInterval, ClassInfo &cInfo) + { + int Type = cInfo.unique; + int courseId = cInfo.courseId; + + int nEntries = cInfo.nRunners; + bool disallowNeighbors = !di.allowNeighbourSameCourse; + // Adjust first pos to make room for extra (before first start) + if (cInfo.nExtra > 0) { + int newFirstPos = FirstPos - cInfo.nExtra * PosInterval; + while (newFirstPos < 0) + newFirstPos += PosInterval; + int extra = (FirstPos - newFirstPos) / PosInterval; + nEntries += extra; + FirstPos = newFirstPos; + } + + //Check if free at all... + for (int k = 0; k < nEntries; k++) { + bool hasFree = false; + for (int f = 0; f < nFields; f++) { + size_t ix = FirstPos + k * PosInterval; + int t = StartField[f][ix].first; + + if (disallowNeighbors) { + int prevT = -1, nextT = -1; + if (PosInterval > 1 && ix + 1 < StartField[f].size()) + nextT = StartField[f][ix + 1].second; + if (PosInterval > 1 && ix > 0) + prevT = StartField[f][ix - 1].second; + + if ((nextT > 0 && nextT == courseId) || (prevT > 0 && prevT == courseId)) + return false; + } + if (t == 0) + hasFree = true; + else if (t == Type) + return false;//Type of course occupied. Cannot put it here; + } + + if (!hasFree) return false;//No free start position. + } + + return true; + } + + bool insertStart(vector< vector< pair > > &StartField, int nFields, ClassInfo &cInfo) + { + int Type = cInfo.unique; + int courseId = cInfo.courseId; + int nEntries = cInfo.nRunners; + int FirstPos = cInfo.firstStart; + int PosInterval = cInfo.interval; + + // Adjust first pos to make room for extra (before first start) + if (cInfo.nExtra > 0) { + int newFirstPos = FirstPos - cInfo.nExtra * PosInterval; + while (newFirstPos < 0) + newFirstPos += PosInterval; + int extra = (FirstPos - newFirstPos) / PosInterval; + nEntries += extra; + FirstPos = newFirstPos; + } + + for (int k = 0; k < nEntries; k++) { + bool HasFree = false; + + for (int f = 0; f < nFields && !HasFree; f++) { + if (StartField[f][FirstPos + k * PosInterval].first == 0) { + StartField[f][FirstPos + k * PosInterval].first = Type; + StartField[f][FirstPos + k * PosInterval].second = courseId; + HasFree = true; + } + } + + if (!HasFree) + return false; //No free start position. Fail. + } + + return true; + } } void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector &cInfo) @@ -661,23 +898,22 @@ void oEvent::optimizeStartOrder(vector< vector > > &StartField, D alternator += alteration; } } - } -void oEvent::drawRemaining(bool useSOFTMethod, bool placeAfter) +void oEvent::drawRemaining(DrawMethod method, bool placeAfter) { - DrawType drawType = placeAfter ? remainingAfter : remainingBefore; + DrawType drawType = placeAfter ? DrawType::RemainingAfter : DrawType::RemainingBefore; for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) { vector spec; spec.push_back(ClassDrawSpecification(it->getId(), 0, 0, 0, 0)); - drawList(spec, useSOFTMethod, 1, drawType); + drawList(spec, method, 1, drawType); } } void oEvent::drawList(const vector &spec, - bool useSOFTMethod, int pairSize, DrawType drawType) { + DrawMethod method, int pairSize, DrawType drawType) { autoSynchronizeLists(false); assert(pairSize > 0); @@ -716,7 +952,7 @@ void oEvent::drawList(const vector &spec, vector runners; runners.reserve(Runners.size()); - if (drawType == drawAll) { + if (drawType == DrawType::DrawAll) { if (!clsIdClearVac.empty()) { //Only remove vacances on leg 0. @@ -796,7 +1032,7 @@ void oEvent::drawList(const vector &spec, } for (size_t k = 0; k < spec.size(); k++) { - if (drawType == remainingBefore) + if (drawType == DrawType::RemainingBefore) spec[k].firstStart = first[k] - runners.size()*baseInterval; else spec[k].firstStart = last[k] + baseInterval; @@ -829,10 +1065,19 @@ void oEvent::drawList(const vector &spec, if (gdibase.isTest()) InitRanom(0,0); - if (useSOFTMethod) - drawSOFTMethod(runners); - else + switch (method) { + case DrawMethod::SOFT: + drawSOFTMethod(runners, true); + break; + case DrawMethod::MeOS: + drawMeOSMethod(runners); + break; + case DrawMethod::Random: permute(stimes); + break; + default: + throw 0; + } int minStartNo = Runners.size(); for(unsigned k=0;k &spec, nextFreeStartNo = max(nextFreeStartNo, minStartNo + stimes.size()); } -void getLargestClub(map > &clubRunner, vector &largest) -{ - size_t maxClub=0; - for (map >::iterator it = - clubRunner.begin(); it!=clubRunner.end(); ++it) { - maxClub = max(maxClub, it->second.size()); - } - - for (map >::iterator it = - clubRunner.begin(); it!=clubRunner.end(); ++it) { - if (it->second.size()==maxClub) { - swap(largest, it->second); - clubRunner.erase(it); - return; - } - } -} - -void getRange(int size, vector &p) -{ - p.resize(size); - for (size_t k=0;k &runners, bool handleBlanks) -{ - if (runners.empty()) - return; - - //Group runners per club - map > clubRunner; - - for (size_t k=0;kgetClubId() : -1; - clubRunner[clubId].push_back(runners[k]); - } - - vector< vector > runnerGroups(1); - - // Find largest club - getLargestClub(clubRunner, runnerGroups[0]); - - int largeSize = runnerGroups[0].size(); - int ngroups = (runners.size()+largeSize-1) / largeSize; - runnerGroups.resize(ngroups); - - while (!clubRunner.empty()) { - // Find the smallest available group - unsigned small = runners.size()+1; - int cgroup = -1; - for (size_t k=1;k largest; - getLargestClub(clubRunner, largest); - runnerGroups[cgroup].insert(runnerGroups[cgroup].end(), largest.begin(), largest.end()); - } - - unsigned maxGroup=runnerGroups[0].size(); - - //Permute the first group - vector pg(maxGroup); - getRange(pg.size(), pg); - permute(pg); - vector pr(maxGroup); - for (unsigned k=0;k p(runnerGroups.size()); - getRange(p.size(), p); - permute(p); - - // Write back result - int index = 0; - for (unsigned level = 0; level spec; spec.push_back(ClassDrawSpecification(it->getId(), 0, iFirstStart, 0, 0)); - oe->drawList(spec, false, 1, drawAll); + oe->drawList(spec, DrawMethod::Random, 1, DrawType::DrawAll); } return; } @@ -1310,7 +1447,7 @@ void oEvent::automaticDrawAll(gdioutput &gdi, const wstring &firstStart, di.firstStart + di.baseInterval * ci.firstStart, di.baseInterval * ci.interval, ci.nVacant)); - drawList(spec, softMethod, pairSize, oEvent::drawAll); + drawList(spec, method, pairSize, DrawType::DrawAll); gdi.scrollToBottom(); gdi.refreshFast(); drawn++; @@ -1328,7 +1465,7 @@ void oEvent::automaticDrawAll(gdioutput &gdi, const wstring &firstStart, vector spec; spec.push_back(ClassDrawSpecification(it->getId(), leg, 0, 0, 0)); - drawList(spec, softMethod, 1, lateBefore ? remainingBefore : remainingAfter); + drawList(spec, method, 1, lateBefore ? DrawType::RemainingBefore : DrawType::RemainingAfter); gdi.scrollToBottom(); gdi.refreshFast(); diff --git a/code/oEventDraw.h b/code/oEventDraw.h index dc71e00..54c4d00 100644 --- a/code/oEventDraw.h +++ b/code/oEventDraw.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,18 +36,6 @@ struct ClassDrawSpecification { interval(interval), vacances(vacances), ntimes(0) {} }; -enum DrawMethod { - NOMethod = -1, - - DMRandom = 1, - DMSOFT = 2, - DMClumped = 3, - DMSimultaneous = 4, - DMSeeded = 5, - - DMPursuit = 11, - DMReversePursuit = 12 -}; /** Struct with info to draw a class */ struct ClassInfo { diff --git a/code/oEventResult.cpp b/code/oEventResult.cpp index b1dc0e1..9578941 100644 --- a/code/oEventResult.cpp +++ b/code/oEventResult.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -105,10 +105,14 @@ void oEvent::calculateSplitResults(int controlIdFrom, int controlIdTo) } } -void oEvent::calculateResults(ResultType resultType, bool includePreliminary) { - const bool totalResults = resultType == RTTotalResult; - const bool courseResults = resultType == RTCourseResult; - const bool classCourseResults = resultType == RTClassCourseResult; +void oEvent::calculateResults(const set &classes, ResultType resultType, bool includePreliminary) { + if (resultType == ResultType::PreliminarySplitResults) { + computePreliminarySplitResults(classes); + return; + } + const bool totalResults = resultType == ResultType::TotalResult; + const bool courseResults = resultType == ResultType::CourseResult; + const bool classCourseResults = resultType == ResultType::ClassCourseResult; if (classCourseResults) sortRunners(ClassCourseResult); @@ -598,3 +602,184 @@ void oEvent::calculateTeamResultAtControl(const set &classId, int leg, int team->tmpResult.points = 0; // Not supported } } + +void oEvent::computePreliminarySplitResults(const set &classes) { + bool allClasses = classes.empty(); + map, vector> runnerByClassLeg; + for (auto &r : Runners) { + r.tOnCourseResults.clear(); + r.currentControlTime.first = 1; + r.currentControlTime.second = 100000; + + if (r.isRemoved() || r.getClassId(false) == 0) + continue; + int cls = r.getClassId(true); + if (!allClasses && classes.count(cls) == 0) + continue; + int leg = r.getLegNumber(); + if (r.getClassRef(false)->getQualificationFinal()) + leg = 0; + + r.setupRunnerStatistics(); + runnerByClassLeg[make_pair(cls, leg)].push_back(&r); + } + + map, int> courseCCid2CourseIx; + for (auto &c : Courses) { + if (c.isRemoved()) + continue; + for (int ix = 0; ix < c.getNumControls(); ix++) { + int ccid = c.getCourseControlId(ix); + courseCCid2CourseIx[make_pair(c.getId(), ccid)] = ix; + } + courseCCid2CourseIx[make_pair(c.getId(), oPunch::PunchFinish)] = c.getNumControls(); + } + + map, set> classLeg2ExistingCCId; + for (auto &p : punches) { + if (p.isRemoved()) + continue; + pRunner r = p.getTiedRunner(); + if (!r) + continue; + pClass cls = r->getClassRef(false); + if (r->getCourse(false) && cls) { + int ccId = p.getCourseControlId(); + if (ccId <= 0) + continue; + int crs = r->getCourse(false)->getId(); + int time = p.getTimeInt() - r->getStartTime(); //XXX Team time + r->tOnCourseResults.emplace_back(ccId, courseCCid2CourseIx[make_pair(crs, ccId)], time); + int clsId = r->getClassId(true); + int leg = r->getLegNumber(); + if (cls->getQualificationFinal()) + leg = 0; + + classLeg2ExistingCCId[make_pair(clsId, leg)].insert(ccId); + } + } + // Add missing punches from card + for (auto &r : Runners) { + if (r.isRemoved() || !r.Card || r.getClassId(false) == 0) + continue; + int clsId = r.getClassId(true); + int leg = r.getLegNumber(); + if (r.getClassRef(false)->getQualificationFinal()) + leg = 0; + + const set &expectedCCid = classLeg2ExistingCCId[make_pair(clsId, leg)]; + size_t nRT = 0; + for (auto &radioTimes : r.tOnCourseResults) { + if (expectedCCid.count(radioTimes.courseControlId)) + nRT++; + } + + if (nRT < expectedCCid.size()) { + pCourse crs = r.getCourse(true); + for (auto &p : r.Card->punches) { + if (p.tIndex >= 0 && p.tIndex < crs->getNumControls()) { + int ccId = crs->getCourseControlId(p.tIndex); + if (expectedCCid.count(ccId)) { + bool added = false; + for (auto &stored : r.tOnCourseResults) { + if (stored.courseControlId == ccId) { + added = true; + break; + } + } + if (!added) { + int time = p.getTimeInt() - r.getStartTime(); //XXX Team time + r.tOnCourseResults.emplace_back(ccId, p.tIndex, time); + } + } + } + } + } + } + + vector> timeRunnerIx; + for (auto rList : runnerByClassLeg) { + auto &rr = rList.second; + pClass cls = getClass(rList.first.first); + assert(cls); + bool totRes = cls->getNumStages() > 1; + + set &legCCId = classLeg2ExistingCCId[rList.first]; + legCCId.insert(oPunch::PunchFinish); + for (const int ccId : legCCId) { + // Leg with negative sign + int negLeg = 0; + timeRunnerIx.clear(); + int nRun = rr.size(); + if (ccId == oPunch::PunchFinish) { + negLeg = -1000; //Finish, smallest number + for (int j = 0; j < nRun; j++) { + pRunner r = rr[j]; + if (r->prelStatusOK()) { + int time; + if (!r->tInTeam || !totRes) + time = r->getRunningTime(); + else { + time = r->tInTeam->getLegRunningTime(r->tLeg, false); + } + int ix = -1; + int nr = r->tOnCourseResults.size(); + for (int i = 0; i < nr; i++) { + if (r->tOnCourseResults[i].courseControlId == ccId) { + ix = i; + break; + } + } + if (ix == -1) { + ix = r->tOnCourseResults.size(); + int nc = 0; + pCourse crs = r->getCourse(false); + if (crs) + nc = crs->getNumControls(); + r->tOnCourseResults.emplace_back(ccId, nc, time); + } + timeRunnerIx.emplace_back(time, j, ix); + } + } + } + else { + for (int j = 0; j < nRun; j++) { + pRunner r = rr[j]; + int nr = r->tOnCourseResults.size(); + for (int i = 0; i < nr; i++) { + if (r->tOnCourseResults[i].courseControlId == ccId) { + timeRunnerIx.emplace_back(r->tOnCourseResults[i].time, j, i); + negLeg = min(negLeg, -r->tOnCourseResults[i].controlIx); + break; + } + } + } + } + sort(timeRunnerIx.begin(), timeRunnerIx.end()); + + int place = 0; + int time = 0; + int leadTime = 0; + int numPlace = timeRunnerIx.size(); + for (int i = 0; i < numPlace; i++) { + int ct = get<0>(timeRunnerIx[i]); + if (time != ct) { + time = ct; + place = i + 1; + if (leadTime == 0) + leadTime = time; + } + pRunner r = rr[get<1>(timeRunnerIx[i])]; + int locIx = get<2>(timeRunnerIx[i]); + r->tOnCourseResults[locIx].place = place; + r->tOnCourseResults[locIx].after = time - leadTime; + + int &legWithTimeIndexNeg = r->currentControlTime.first; + if (negLeg < legWithTimeIndexNeg) { + legWithTimeIndexNeg = negLeg; + r->currentControlTime.second = ct; + } + } + } + } +} diff --git a/code/oEventSQL.cpp b/code/oEventSQL.cpp index 1e8902c..971b3e3 100644 --- a/code/oEventSQL.cpp +++ b/code/oEventSQL.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,51 +52,6 @@ bool oEvent::connectToServer() if (isThreadReconnecting()) return false; -#ifdef BUILD_DB_DLL - if (msOpenDatabase) - return true; -#endif - -#ifdef BUILD_DB_DLL - hMod=LoadLibrary("meosdb.dll"); - - msOpenDatabase = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msOpenDatabase"); - msConnectToServer = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msConnectToServer"); - msSynchronizeUpdate = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msSynchronizeUpdate"); - msSynchronizeRead = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msSynchronizeRead"); - msSynchronizeList = (SYNCHRONIZELIST_FCN)GetProcAddress(hMod, "msSynchronizeList"); - msRemove = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msRemove"); - msMonitor = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msMonitor"); - msDropDatabase = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msDropDatabase"); - msUploadRunnerDB = (SYNCHRONIZE_FCN)GetProcAddress(hMod, "msUploadRunnerDB"); - - msGetErrorState = (ERRORMESG_FCN)GetProcAddress(hMod, "msGetErrorState"); - msResetConnection = (OPENDB_FCN)GetProcAddress(hMod, "msResetConnection"); - msReConnect = (OPENDB_FCN)GetProcAddress(hMod, "msReConnect"); - - if (!msOpenDatabase || !msSynchronizeUpdate || !msSynchronizeRead - || !msConnectToServer || !msReConnect || !msGetErrorState - || !msMonitor || !msDropDatabase || !msUploadRunnerDB) { - MessageBox(NULL, "Cannot load MySQL library.", NULL, MB_OK); - // handle the error - FreeLibrary(hMod); - hMod=0; - return false;//SOME_ERROR_CODE; - } -#else/* - msOpenDatabase = (SYNCHRONIZE_FCN)::msOpenDatabase; - msConnectToServer = (SYNCHRONIZE_FCN)::msConnectToServer; - msSynchronizeUpdate = (SYNCHRONIZE_FCN)::msSynchronizeUpdate; - msSynchronizeRead = (SYNCHRONIZE_FCN)::msSynchronizeRead; - msSynchronizeList = (SYNCHRONIZELIST_FCN)::msSynchronizeList; - msRemove = (SYNCHRONIZE_FCN)::msRemove; - msMonitor = (SYNCHRONIZE_FCN)::msMonitor; - msDropDatabase = (SYNCHRONIZE_FCN)::msDropDatabase; - msUploadRunnerDB = (SYNCHRONIZE_FCN)::msUploadRunnerDB; - msGetErrorState = (ERRORMESG_FCN)::msGetErrorState; - msResetConnection = (OPENDB_FCN)::msResetConnection; - msReConnect = (OPENDB_FCN)::msReConnect;*/ -#endif return true; } @@ -232,6 +187,30 @@ void oEvent::storeChangeStatus(bool onlyChangable) } } +bool MEOSDB_API msSynchronizeList(oEvent *, oListId lid); + +bool oEvent::synchronizeList(initializer_list types) { + if (!HasDBConnection) + return true; + + msSynchronize(this); + resetSQLChanged(true, false); + + for (oListId t : types) { + if (!msSynchronizeList(this, t)) { + verifyConnection(); + return false; + } + + if (t == oListId::oLPunchId) + advanceInformationPunches.clear(); + } + + reinitializeClasses(); + reEvaluateChanged(); + resetChangeStatus(); + return true; +} bool oEvent::synchronizeList(oListId id, bool preSyncEvent, bool postSyncEvent) { if (!HasDBConnection) @@ -247,7 +226,7 @@ bool oEvent::synchronizeList(oListId id, bool preSyncEvent, bool postSyncEvent) return false; } - if (id == oLPunchId) + if (id == oListId::oLPunchId) advanceInformationPunches.clear(); if (postSyncEvent) { @@ -270,6 +249,8 @@ bool oEvent::needReEvaluate() { } void oEvent::resetSQLChanged(bool resetAllTeamsRunners, bool cleanClasses) { + if (empty()) + return; sqlChangedRunners = false; sqlChangedClasses = false; sqlChangedCourses = false; @@ -305,6 +286,11 @@ void oEvent::resetSQLChanged(bool resetAllTeamsRunners, bool cleanClasses) { bool BaseIsRemoved(const oBase &ob){return ob.isRemoved();} +namespace { + bool isSet(int mask, oListId id) { + return (mask & int(id)) != 0; + } +} //Returns true if data is changed. bool oEvent::autoSynchronizeLists(bool SyncPunches) { @@ -323,7 +309,7 @@ bool oEvent::autoSynchronizeLists(bool SyncPunches) resetSQLChanged(true, false); //Synchronize ourself - if (mask & oLEventId) { + if (isSet(mask, oListId::oLEventId)) { ot=sqlUpdated; msSynchronize(this); if (sqlUpdated!=ot) { @@ -333,73 +319,73 @@ bool oEvent::autoSynchronizeLists(bool SyncPunches) } //Controls - if (mask & oLControlId) { + if (isSet(mask, oListId::oLControlId)) { int oc = sqlCounterControls; ot = sqlUpdateControls; - synchronizeList(oLControlId, false, false); + synchronizeList(oListId::oLControlId, false, false); changed |= oc!=sqlCounterControls; changed |= ot!=sqlUpdateControls; } //Courses - if (mask & oLCourseId) { + if (isSet(mask, oListId::oLCourseId)) { int oc = sqlCounterCourses; ot = sqlUpdateCourses; - synchronizeList(oLCourseId, false, false); + synchronizeList(oListId::oLCourseId, false, false); changed |= oc!=sqlCounterCourses; changed |= ot!=sqlUpdateCourses; } //Classes - if (mask & oLClassId) { + if (isSet(mask, oListId::oLClassId)) { int oc = sqlCounterClasses; ot = sqlUpdateClasses; - synchronizeList(oLClassId, false, false); + synchronizeList(oListId::oLClassId, false, false); changed |= oc!=sqlCounterClasses; changed |= ot!=sqlUpdateClasses; } //Clubs - if (mask & oLClubId) { + if (isSet(mask, oListId::oLClubId)) { int oc = sqlCounterClubs; ot = sqlUpdateClubs; - synchronizeList(oLClubId, false, false); + synchronizeList(oListId::oLClubId, false, false); changed |= oc!=sqlCounterClubs; changed |= ot!=sqlUpdateClubs; } //Cards - if (mask & oLCardId) { + if (isSet(mask, oListId::oLCardId)) { int oc = sqlCounterCards; ot = sqlUpdateCards; - synchronizeList(oLCardId, false, false); + synchronizeList(oListId::oLCardId, false, false); changed |= oc!=sqlCounterCards; changed |= ot!=sqlUpdateCards; } //Runners - if (mask & oLRunnerId) { + if (isSet(mask, oListId::oLRunnerId)) { int oc = sqlCounterRunners; ot = sqlUpdateRunners; - synchronizeList(oLRunnerId, false, false); + synchronizeList(oListId::oLRunnerId, false, false); changed |= oc!=sqlCounterRunners; changed |= ot!=sqlUpdateRunners; } //Teams - if (mask & oLTeamId) { + if (isSet(mask, oListId::oLTeamId)) { int oc = sqlCounterTeams; ot = sqlUpdateTeams; - synchronizeList(oLTeamId, false, false); + synchronizeList(oListId::oLTeamId, false, false); changed |= oc!=sqlCounterTeams; changed |= ot!=sqlUpdateTeams; } - if (SyncPunches && (mask & oLPunchId)) { + if (SyncPunches && isSet(mask, oListId::oLPunchId)) { //Punches int oc = sqlCounterPunches; ot = sqlUpdatePunches; - synchronizeList(oLPunchId, false, false); + synchronizeList(oListId::oLPunchId, false, false); changed |= oc!=sqlCounterPunches; changed |= ot!=sqlUpdatePunches; } diff --git a/code/oEventSpeaker.cpp b/code/oEventSpeaker.cpp index 354f1e8..623e58f 100644 --- a/code/oEventSpeaker.cpp +++ b/code/oEventSpeaker.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -528,7 +528,7 @@ void oEvent::speakerList(gdioutput &gdi, int ClassId, int leg, int ControlId, if (refresh) gdi.takeShownStringsSnapshot(); - int storedY = gdi.GetOffsetY(); + int storedY = gdi.getOffsetY(); int storedHeight = gdi.getHeight(); gdi.restoreNoUpdate("SpeakerList"); @@ -537,7 +537,7 @@ void oEvent::speakerList(gdioutput &gdi, int ClassId, int leg, int ControlId, gdi.pushX(); gdi.pushY(); gdi.updatePos(0,0,0, storedHeight); gdi.popX(); gdi.popY(); - gdi.SetOffsetY(storedY); + gdi.setOffsetY(storedY); gdi.setData("ClassId", ClassId); gdi.setData("ControlId", ControlId); @@ -786,7 +786,7 @@ void oEvent::playPrewarningSounds(const wstring &basedir, set &controls) for (it=punches.rbegin(); it!=punches.rend() && !it->hasBeenPlayed; ++it) { if (controls.count(it->Type)==1 || controls.empty()) { - pRunner r = getRunnerByCardNo(it->CardNo, it->getAdjustedTime()); + pRunner r = getRunnerByCardNo(it->CardNo, it->getAdjustedTime(), oEvent::CardLookupProperty::ForReadout); if (r){ wchar_t wave[20]; diff --git a/code/oFreeImport.cpp b/code/oFreeImport.cpp index 39d5a46..8c91168 100644 --- a/code/oFreeImport.cpp +++ b/code/oFreeImport.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/oFreeImport.h b/code/oFreeImport.h index 35dacf1..6ff6633 100644 --- a/code/oFreeImport.h +++ b/code/oFreeImport.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/oFreePunch.cpp b/code/oFreePunch.cpp index 8b9a3f0..a7828f9 100644 --- a/code/oFreePunch.cpp +++ b/code/oFreePunch.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -158,6 +158,7 @@ Table *oEvent::getPunchesTB()//Table mode table->addColumn("Tid", 70, false); table->addColumn("Löpare", 170, false); table->addColumn("Lag", 170, false); + table->addColumn("Klass", 170, false); tables["punch"] = table; table->addOwnership(); } @@ -173,16 +174,14 @@ void oEvent::generatePunchTableData(Table &table, oFreePunch *addPunch) return; } - synchronizeList(oLPunchId); + synchronizeList({ oListId::oLPunchId, oListId::oLRunnerId }); oFreePunchList::iterator it; - oe->setupCardHash(false); table.reserve(punches.size()); for (it = punches.begin(); it != punches.end(); ++it){ if (!it->isRemoved()){ it->addTableRow(table); } } - oe->setupCardHash(true); } void oFreePunch::addTableRow(Table &table) const { @@ -196,7 +195,7 @@ void oFreePunch::addTableRow(Table &table) const { table.set(row++, it, TID_TIME, getTime(), true, cellEdit); pRunner r = 0; if (CardNo > 0) - r = oe->getRunnerByCardNo(CardNo, Time, false); + r = oe->getRunnerByCardNo(CardNo, Time, oEvent::CardLookupProperty::Any); table.set(row++, it, TID_RUNNER, r ? r->getName() : L"?", false, cellEdit); @@ -204,6 +203,8 @@ void oFreePunch::addTableRow(Table &table) const { table.set(row++, it, TID_TEAM, r->getTeam()->getName(), false, cellEdit); else table.set(row++, it, TID_TEAM, L"", false, cellEdit); + + table.set(row++, it, TID_CLASSNAME, r ? r->getClass(true) : L"", false, cellEdit); } bool oFreePunch::inputData(int id, const wstring &input, @@ -364,7 +365,7 @@ int oFreePunch::getControlIdFromHash(int hash, bool courseControlId) { int oEvent::getControlIdFromPunch(int time, int type, int card, bool markClassChanged, oFreePunch &punch) { - pRunner r = getRunnerByCardNo(card, time); + pRunner r = getRunnerByCardNo(card, time, oEvent::CardLookupProperty::Any); punch.tRunnerId = -1; punch.tMatchControlId = type; if (r!=0) { @@ -621,11 +622,12 @@ void oEvent::getPunchesForRunner(int runnerId, vector &runnerPunches // Get times for when other runners used this card vector< pair > times; + int refCno = r->getCardNo(); - for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) { + for (auto it = Runners.begin(); it != Runners.end(); ++it) { if (it->Id == runnerId) continue; - if (it->Card && it->CardNo == r->CardNo) { + if (it->Card && it->getCardNo() == refCno) { pair t = it->Card->getTimeRange(); if (it->getStartTime() > 0) t.first = min(it->getStartTime(), t.first); @@ -638,7 +640,7 @@ void oEvent::getPunchesForRunner(int runnerId, vector &runnerPunches } for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) { - if (it->CardNo == r->CardNo) { + if (it->CardNo == refCno) { bool other = false; int t = it->Time; for (size_t k = 0; k matchedClasses; if (xo) { @@ -376,7 +377,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, gdi.refreshFast(); int ent = 0, fail = 0; - + if (xo.getAttrib("iofVersion")) { IOF30Interface reader(this, false); reader.readStartList(gdi, xo, ent, fail); @@ -384,7 +385,6 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, else { xmlList xl; xo.getObjects(xl); - xmlList::const_iterator it; for(it=xl.begin(); it != xl.end(); ++it){ if (it->is("ClassStart")){ @@ -396,11 +396,11 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, wstring clsName; it->getObjectString("ClassShortName", clsName); if (!clsName.empty()) - cls = getClassCreate(0, clsName); + cls = getClassCreate(0, clsName, matchedClasses); + } + else { + cls = getClassCreate(clsId, lang.tl(L"Klass ") + itow(clsId), matchedClasses); } - else - cls = getClassCreate(clsId, lang.tl(L"Klass ") + itow(clsId)); - it->getObjects("PersonStart", entries); for (size_t k = 0; k0) gdi.addString("", 0, "Antal ignorerade: X#" + itos(fail)); gdi.dropLine(); @@ -548,7 +548,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, for(it=xl.begin(); it != xl.end(); ++it){ if (it->is("Course")){ - if (addXMLCourse(*it, updateClass)) + if (addXMLCourse(*it, updateClass, matchedClasses)) imp++; else fail++; @@ -1472,8 +1472,7 @@ bool oEvent::addXMLControl(const xmlobject &xcontrol, int type) return true; } -bool oEvent::addXMLCourse(const xmlobject &xcrs, bool addClasses) -{ +bool oEvent::addXMLCourse(const xmlobject &xcrs, bool addClasses, set &matchedClasses) { if (!xcrs) return false; @@ -1558,10 +1557,9 @@ bool oEvent::addXMLCourse(const xmlobject &xcrs, bool addClasses) courses.push_back(pc); } - if (addClasses) { for (size_t k = 0; kgetNumStages()==0) { @@ -1811,6 +1809,9 @@ bool oEvent::addXMLRank(const xmlobject &xrank, const map<__int64, int> &externI swap(cn, cns); r=getRunnerByName(name, cns); + + if (r == nullptr) + r = getRunnerByName(name); } } @@ -2599,9 +2600,9 @@ void oEvent::exportIOFSplits(IOFVersion version, const wchar_t *file, oClass::initClassId(*this); reEvaluateAll(set(), true); if (version != IOF20) - calculateResults(RTClassCourseResult); - calculateResults(RTTotalResult); - calculateResults(RTClassResult); + calculateResults(set(), ResultType::ClassCourseResult); + calculateResults(set(), ResultType::TotalResult); + calculateResults(set(), ResultType::ClassResult); calculateTeamResults(true); calculateTeamResults(false); set rgClasses; diff --git a/code/oListInfo.cpp b/code/oListInfo.cpp index f65d27f..6185158 100644 --- a/code/oListInfo.cpp +++ b/code/oListInfo.cpp @@ -1,6 +1,6 @@ /********************i**************************************************** MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,6 +53,7 @@ oListInfo::oListInfo() { calcResults=false; calcTotalResults = false; rogainingResults = false; + calculateLiveResults = false; calcCourseClassResults = false; listPostFilter.resize(_EFilterMax+1, 0); listPostSubFilter.resize(_ESubFilterMax+1, 0); @@ -73,11 +74,31 @@ oListInfo::oListInfo() { supportPageBreak = true; supportClassLimit = true; - needPunches = false; + needPunches = PunchMode::NoPunch; } -oListInfo::~oListInfo(void) -{ +oListInfo::~oListInfo(void) { +} + +wstring oListParam::getContentsDescriptor(const oEvent &oe) const { + wstring cls; + vector classes; + oe.getClasses(classes, false); + if (classes.size() == selection.size() || selection.empty()) + cls = oe.getName(); // All classes + else { + for (pClass c : classes) { + if (selection.count(c->getId())) { + if (!cls.empty()) + cls += L", "; + cls += c->getName(); + } + } + } + if (legNumber != -1) + return lang.tl(L"Sträcka X#" + getLegName()) + L": " + cls; + else + return cls; } oPrintPost::oPrintPost() @@ -120,7 +141,7 @@ bool oListInfo::needRegenerate(const oEvent &oe) const { continue; // Not our class int legToCheck = -1; - if (needPunches) { + if (needPunches == PunchMode::SpecificPunch) { int to = oControl::getIdIndexFromCourseControlId(lp.useControlIdResultTo).first; int from = oControl::getIdIndexFromCourseControlId(lp.useControlIdResultFrom).first; @@ -128,6 +149,10 @@ bool oListInfo::needRegenerate(const oEvent &oe) const { it->wasSQLChanged(legToCheck, from) ) return true; } + else if (needPunches == PunchMode::AnyPunch) { + if (it->wasSQLChanged(legToCheck, -2)) + return true; + } else { if (it->wasSQLChanged(legToCheck, -1)) return true; @@ -275,19 +300,43 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, for (size_t k = 0; k < pps.size(); k++) { wstring extra; switch (pps[k].type) { + case lPunchName: + case lControlName: + case lPunchNamedTime: { + wstring maxcn = lang.tl("Mål"); + vector ctrl; + oe->getControls(ctrl, false); + for (pControl c : ctrl) { + wstring cn = c->getName(); + if (cn.length() > maxcn.length()) + maxcn.swap(cn); + } + if (pps[k].type == lPunchNamedTime) + extra = maxcn + L": 50:50 (50:50)"; + else + maxcn.swap(extra); + } + break; + case lRunnerFinish: + case lRunnerCheck: + case lPunchAbsTime: + case lRunnerStart: + case lTeamStart: + extra = L"10:10:00"; + break; case lRunnerTotalTimeAfter: case lRunnerClassCourseTimeAfter: case lRunnerTimeAfterDiff: case lRunnerTempTimeAfter: case lRunnerTimeAfter: - case lRunnerMissedTime: + case lRunnerLostTime: case lTeamTimeAfter: case lTeamLegTimeAfter: case lTeamTotalTimeAfter: case lTeamTimeAdjustment: case lRunnerTimeAdjustment: case lRunnerGeneralTimeAfter: - + case lPunchTotalTimeAfter: extra = L"+10:00"; break; case lTeamRogainingPointOvertime: @@ -295,6 +344,7 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, case lResultModuleTime: case lResultModuleTimeTeam: case lTeamTime: + case lTeamGrossTime: case lTeamTotalTime: case lTeamTotalTimeStatus: case lTeamLegTimeStatus: @@ -303,10 +353,15 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, case lRunnerTotalTimeStatus: case lRunnerTotalTime: case lClassStartTime: - case lRunnerFinish: case lRunnerTime: + case lRunnerGrossTime: case lRunnerTimeStatus: case lRunnerTimePlaceFixed: + case lPunchLostTime: + case lPunchTotalTime: + case lPunchTimeSinceLast: + case lPunchSplitTime: + case lPunchNamedSplit: extra = L"50:50"; break; case lRunnerGeneralTimeStatus: @@ -353,13 +408,33 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, for (size_t k = 0; k < pps.size(); k++) { const oPrintPost &pp = pps[k]; - for (oClassList::const_iterator it = oe->Classes.begin(); it != oe->Classes.end(); ++it) { - if (it->isRemoved()) + int cardSkip = 0; + int skip = 0; + wstring last; + for (auto crd = oe->Cards.begin(); crd != oe->Cards.end(); ++crd) { + if (--skip > 0) continue; - if (!clsSel.empty() && clsSel.count(it->getId()) == 0) + skip = cardSkip++; + + for (auto &p : crd->punches) { + pRunner r = crd->tOwner; + p.previousPunchTime = 0; + const wstring &out = oe->formatPunchString(pp, par, r ? r->getTeam() : nullptr, r, &p, c); + if (last == out) + break; + else + last = out; + extras[k].add(out); + } + } + + for (auto &cls : oe->Classes) { + if (cls.isRemoved()) + continue; + if (!clsSel.empty() && clsSel.count(cls.getId()) == 0) continue; - const wstring &out = oe->formatListString(pp, par, 0, 0, 0, pClass(&*it), c); + const wstring &out = oe->formatListString(pp, par, 0, 0, 0, pClass(&cls), c); extras[k].add(out); } @@ -431,7 +506,6 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, totWord.append(samples[k]); } - totMeasure.add(totWord); } @@ -442,29 +516,6 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, return w + 5; else return w + 15; - /* - double w = width; - double s = 1.0; - - s = oe->gdibase.getRelativeFontScale(font, fontFace); - - if (w > 15) { - if (large) - w *= adjustmentFactor(w-15, 0.7); - else - w *= adjustmentFactor(w-15, 0.85); - - if (w > 40) - w *= adjustmentFactor(w-40, 0.85); - } - w = max(w, minSize); - - if (width>0 && !large) - return int(s*(w*6.0+20.0)); - else if (width>0 && large) - return int(s*(w*7.0+10.0)); - else - return 0;*/ } const wstring & oEvent::formatListString(EPostType type, const pRunner r) const @@ -515,6 +566,32 @@ const wstring &oEvent::formatListString(const oPrintPost &pp, const oListParam & return *tmp; } +const wstring &oEvent::formatPunchString(const oPrintPost &pp, const oListParam &par, + const pTeam t, const pRunner r, + const oPunch *punch, oCounter &counter) const { + const oPrintPost *cpp = &pp; + const wstring *tmp = 0; + wstring *out = 0; + while (cpp) { + if (tmp) { + if (!out) { + out = &StringCache::getInstance().wget(); + *out = L""; + } + out->append(*tmp); + } + tmp = &formatPunchStringAux(*cpp, par, t, r, punch, counter); + cpp = cpp->mergeWithTmp; + } + + if (out) { + out->append(*tmp); + return *out; + } + else + return *tmp; +} + const wstring &oEvent::formatSpecialString(const oPrintPost &pp, const oListParam &par, const pTeam t, int legIndex, const pCourse crs, const pControl ctrl, oCounter &counter) const { const oPrintPost *cpp = &pp; @@ -540,10 +617,156 @@ const wstring &oEvent::formatSpecialString(const oPrintPost &pp, const oListPara return *tmp; } +const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListParam &par, + const pTeam t, const pRunner r, + const oPunch *punch, oCounter &counterIn) const { + wchar_t bfw[128]; + const wstring *wsptr = 0; + bfw[0] = 0; + pClass pc = r ? r->getClassRef(true) : 0; + bool invalidClass = pc && pc->getClassStatus() != oClass::Normal; + oCounter counter(counterIn); + + static bool reentrantLock = false; + if (reentrantLock == true) { + reentrantLock = false; + throw meosException("Internal list error"); + } + bool doDefault = false; + + switch (pp.type) { + case lControlName: + case lPunchName: + if (punch) { + pCourse pc = r ? r->getCourse(false) : nullptr; + if (punch->isFinish(pc ? pc->getFinishPunchType() : oPunch::PunchFinish)) { + wsptr = &lang.tl("Mål"); + } + else { + pControl ctrl = getControl(punch->getControlId()); + if (!ctrl) + ctrl = getControl(punch->Type); + + if (ctrl && ctrl->hasName()) { + swprintf_s(bfw, L"%s", ctrl->getName().c_str()); + } + } + } + break; + case lPunchTimeSinceLast: + if (punch && punch->previousPunchTime && r && r->Card && !invalidClass) { + int time = punch->Time; + int pTime = punch->previousPunchTime; + if (pTime > 0 && time > pTime) { + int t = time - pTime; + wsptr = &formatTime(t); + } + } + break; + case lPunchTime: + case lPunchControlNumber: + case lPunchControlCode: + case lPunchLostTime: + case lPunchControlPlace: + case lPunchControlPlaceAcc: + + case lPunchSplitTime: + case lPunchTotalTime: + case lPunchAbsTime: + if (punch && r && r->Card && !invalidClass) { + if (punch->tIndex >= 0) { + // Punch in course + counter.level3 = punch->tIndex; + doDefault = true; + break; + } + switch (pp.type) { + case lPunchTime: { + if (punch->Time > 0) { + swprintf_s(bfw, L"\u2013 (%s)", formatTime(punch->Time - r->getStartTime()).c_str()); + } + else { + wsptr = &makeDash(L"- (-)"); + } + break; + } + case lPunchSplitTime: { + swprintf_s(bfw, L"\u2013"); + break; + } + case lPunchControlNumber: { + wcscpy_s(bfw, L"\u2013"); + break; + } + case lPunchControlCode: { + swprintf_s(bfw, L"%d", punch->getTypeCode()); + break; + } + case lPunchAbsTime: { + if (punch->Time > 0) + wsptr = &getAbsTime(punch->Time); + break; + } + case lPunchTotalTime: { + if (punch->Time > 0) + wsptr = &formatTime(punch->Time - r->getStartTime()); + break; + } + } + } + break; + + default: + doDefault = true; + } + + if (doDefault) { + reentrantLock = true; + try { + const wstring &res = formatListStringAux(pp, par, t, r, + r ? r->getClubRef() : 0, + r ? r->getClassRef(true) : 0, counter); + reentrantLock = false; + return res; + } + catch (...) { + reentrantLock = false; + throw; + } + } + + if (pp.type != lString && (wsptr == 0 || wsptr->empty()) && bfw[0] == 0) + return _EmptyWString; + else if (wsptr) { + if (pp.text.empty()) + return *wsptr; + else { + swprintf_s(bfw, pp.text.c_str(), wsptr->c_str()); + wstring &res = StringCache::getInstance().wget(); + res = bfw; + return res; + } + } + else { + if (pp.text.empty()) { + wstring &res = StringCache::getInstance().wget(); + res = bfw; + return res; + } + else { + wchar_t bf2[512]; + swprintf_s(bf2, pp.text.c_str(), bfw); + wstring &res = StringCache::getInstance().wget(); + res = bf2; + return res; + } + } +} + const wstring &oEvent::formatSpecialStringAux(const oPrintPost &pp, const oListParam &par, const pTeam t, int legIndex, const pCourse pc, const pControl ctrl, - oCounter &counter) const { + const oCounter &counter) const { wchar_t bfw[512]; const wstring *wsptr=0; @@ -622,7 +845,6 @@ const wstring &oEvent::formatSpecialStringAux(const oPrintPost &pp, const oListP wcsncpy_s(bfw, tmp.c_str(), 256); } break; - case lControlName: if (ctrl) @@ -768,7 +990,7 @@ const wstring &oEvent::formatSpecialStringAux(const oPrintPost &pp, const oListP const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListParam &par, const pTeam t, const pRunner r, const pClub c, - const pClass pc, oCounter &counter) const { + const pClass pc, const oCounter &counter) const { wchar_t wbf[512]; const wstring *wsptr=0; @@ -911,7 +1133,16 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wsptr = &makeDash(L"-"); } break; - + case lRunnerCheck: + if (r && !invalidClass && r->Card) { + oPunch *punch = r->Card->getPunchByType(oPunch::PunchCheck); + if (punch && punch->Time > 0) + wsptr = &getAbsTime(punch->Time); + else + wsptr = &makeDash(L"-"); + } + break; + case lRunnerName: wsptr = r ? &r->getName() : 0; break; @@ -975,8 +1206,18 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; } - } + break; + case lRunnerGrossTime: + if (r && !invalidClass) { + int tm = r->getRunningTime(); + if (tm > 0) + tm -= r->getTimeAdjustment(); + + wsptr = &formatTime(tm); + } + break; + case lRunnerTimeStatus: if (r) { if (invalidClass) @@ -994,7 +1235,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (ok) wsptr = &formatStatus(StatusOK); else - wsptr = &r->getStatusS(); + wsptr = &r->getStatusS(true); } } else { @@ -1041,7 +1282,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } } else - wsptr = &r->getStatusS(); + wsptr = &r->getStatusS(true); } else { const oAbstractRunner::TempResult &res = r->getTempResult(pp.resultModuleIndex); @@ -1135,7 +1376,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } } else - wsptr = &r->getTotalStatusS(); + wsptr = &r->getTotalStatusS(true); } else { const oAbstractRunner::TempResult &res = r->getTempResult(pp.resultModuleIndex); @@ -1359,7 +1600,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else wcscpy_s(wbf, par.getLegName().c_str()); break; - case lRunnerMissedTime: + case lRunnerLostTime: if (r && r->tStatus == StatusOK && pc && !pc->getNoTiming() && !invalidClass) { wcscpy_s(wbf, r->getMissedTimeS().c_str()); } @@ -1380,7 +1621,12 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; case lRunnerRank: if (r) { - int rank=r->getDI().getInt("Rank"); + int rank=r->getDCI().getInt("Rank"); + if (rank == 0) { + if (r->tParentRunner) + rank = r->tParentRunner->getDCI().getInt("Rank"); + } + if (rank>0) wcscpy_s(wbf, formatRank(rank).c_str()); } @@ -1443,6 +1689,12 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wcscpy_s(wbf, s.c_str()); } break; + case lRunnerId: + if (r) { + wstring s = r->getExtIdentifierString(); + wcscpy_s(wbf, s.c_str()); + } + break; case lRunnerPayMethod: if (r) { wsptr = &r->getDCI().formatString(r, "PayMode"); @@ -1514,6 +1766,12 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0); } break; + case lTeamGrossTime: + if (t && !invalidClass) { + int tm = t->getLegRunningTimeUnadjusted(legIndex, false); + wsptr = &formatTime(tm); + } + break; case lTeamTimeStatus: if (invalidClass) wsptr = &lang.tl("Struken"); @@ -1764,70 +2022,116 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; break; + case lControlName: + case lPunchName: case lPunchNamedTime: - if (r && r->Card && r->getCourse(true) && !invalidClass) { - const pCourse crs = r->getCourse(true); - const oControl *ctrl=crs->getControl(counter.level3); - if (!ctrl || ctrl->isRogaining(crs->hasRogaining())) - break; - if (r->getPunchTime(counter.level3, false)>0 && ctrl->hasName()) { - swprintf_s(wbf, L"%s: %s (%s)", ctrl->getName().c_str(), - r->getNamedSplitS(counter.level3).c_str(), - r->getPunchTimeS(counter.level3, false).c_str()); - } - } - break; + case lPunchNamedSplit: case lPunchTime: + case lPunchSplitTime: + case lPunchTotalTime: case lPunchControlNumber: case lPunchControlCode: case lPunchLostTime: case lPunchControlPlace: case lPunchControlPlaceAcc: - - if (r && r->Card && r->getCourse(true) && !invalidClass) { + case lPunchAbsTime: + case lPunchTotalTimeAfter: + if (r && r->getCourse(false) && !invalidClass) { const pCourse crs=r->getCourse(true); + const oControl *ctrl = nullptr; int nCtrl = crs->getNumControls(); if (counter.level3 != nCtrl) { // Always allow finish - const oControl *ctrl=crs->getControl(counter.level3); + ctrl=crs->getControl(counter.level3); if (!ctrl || ctrl->isRogaining(crs->hasRogaining())) break; } - if (pp.type == lPunchTime) { - if (r->getPunchTime(counter.level3, false)>0) { - swprintf_s(wbf, L"%s (%s)", - r->getSplitTimeS(counter.level3, false).c_str(), - r->getPunchTimeS(counter.level3, false).c_str()); - } - else { - wsptr = &makeDash(L"- (-)"); - } - } - else if (pp.type == lPunchControlNumber) { - wcscpy_s(wbf, crs->getControlOrdinal(counter.level3).c_str()); - } - else if (pp.type == lPunchControlCode) { - const oControl *ctrl=crs->getControl(counter.level3); - if (ctrl) { - if (ctrl->getStatus() == oControl::StatusMultiple) { - wstring str = ctrl->getStatusS(); - swprintf_s(wbf, L"%s.", str.substr(0, 1).c_str()); + switch (pp.type) { + case lPunchNamedSplit: + if (ctrl && ctrl->hasName() && r->getPunchTime(counter.level3, false) > 0) { + swprintf_s(wbf, L"%s", r->getNamedSplitS(counter.level3).c_str()); } - else - swprintf_s(wbf, L"%d", ctrl->getFirstNumber()); + break; + + case lPunchNamedTime: + if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false) > 0)) { + swprintf_s(wbf, L"%s: %s (%s)", ctrl->getName().c_str(), + r->getNamedSplitS(counter.level3).c_str(), + r->getPunchTimeS(counter.level3, false).c_str()); + } + break; + + case lControlName: + case lPunchName: + if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false) > 0)) { + swprintf_s(wbf, L"%s", ctrl->getName().c_str()); + } + else if (counter.level3 == nCtrl) { + wsptr = &lang.tl(L"Mål"); + } + break; + + case lPunchTime: { + swprintf_s(wbf, L"%s (%s)", + r->getSplitTimeS(counter.level3, false).c_str(), + r->getPunchTimeS(counter.level3, false).c_str()); + break; + } + case lPunchSplitTime: { + wcscpy_s(wbf, r->getSplitTimeS(counter.level3, false).c_str()); + break; + } + case lPunchTotalTime: { + if (r->getPunchTime(counter.level3, false) > 0) { + wcscpy_s(wbf, r->getPunchTimeS(counter.level3, false).c_str()); + } + break; + } + case lPunchTotalTimeAfter: { + if (r->getPunchTime(counter.level3, false) > 0) { + int rt = r->getLegTimeAfterAcc(counter.level3); + if (rt > 0) + wcscpy_s(wbf, (L"+" + formatTime(rt)).c_str()); + } + break; + } + case lPunchControlNumber: { + wcscpy_s(wbf, crs->getControlOrdinal(counter.level3).c_str()); + break; + } + case lPunchControlCode: { + const oControl *ctrl = crs->getControl(counter.level3); + if (ctrl) { + if (ctrl->getStatus() == oControl::StatusMultiple) { + wstring str = ctrl->getStatusS(); + swprintf_s(wbf, L"%s.", str.substr(0, 1).c_str()); + } + else + swprintf_s(wbf, L"%d", ctrl->getFirstNumber()); + } + break; + } + case lPunchControlPlace: { + int p = r->getLegPlace(counter.level3); + if (p > 0) + swprintf_s(wbf, L"%d", p); + break; + } + case lPunchControlPlaceAcc: { + int p = r->getLegPlaceAcc(counter.level3); + if (p > 0) + swprintf_s(wbf, L"%d", p); + break; + } + case lPunchLostTime: { + wcscpy_s(wbf, r->getMissedTimeS(counter.level3).c_str()); + break; + } + case lPunchAbsTime: { + int t = r->getPunchTime(counter.level3, false); + if (t > 0) + wsptr = &getAbsTime(r->tStartTime + t); + break; } - } - else if (pp.type == lPunchControlPlace) { - int p = r->getLegPlace(counter.level3); - if (p>0) - swprintf_s(wbf, L"%d", p); - } - else if (pp.type == lPunchControlPlaceAcc) { - int p = r->getLegPlaceAcc(counter.level3); - if (p>0) - swprintf_s(wbf, L"%d", p); - } - else if (pp.type == lPunchLostTime) { - wcscpy_s(wbf, r->getMissedTimeS(counter.level3).c_str()); } } break; @@ -1914,6 +2218,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } } break; + + case lLineBreak: + break; } if (pp.type!=lString && (wsptr==0 || wsptr->empty()) && wbf[0]==0) @@ -1944,18 +2251,28 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } } - - bool oEvent::formatPrintPost(const list &ppli, PrintPostInfo &ppi, const pTeam t, const pRunner r, const pClub c, - const pClass pc, const pCourse crs, const pControl ctrl, int legIndex) + const pClass pc, const pCourse crs, const pControl ctrl, + const oPunch *punch, int legIndex) { list::const_iterator ppit; int y=ppi.gdi.getCY(); int x=ppi.gdi.getCX(); bool updated=false; - for (ppit=ppli.begin();ppit!=ppli.end();) { - const oPrintPost &pp=*ppit; + int lineHeight = 0; + int pdx = 0, pdy = 0; + for (ppit = ppli.begin(); ppit != ppli.end();) { + const oPrintPost &pp = *ppit; + + if (pp.type == lLineBreak) { + x -= ppi.gdi.scaleLength(pp.dx) - pdx; + pdx = ppi.gdi.scaleLength(pp.dx); + y += lineHeight; + ++ppit; + continue; + } + int limit = 0; bool keepNext = false; @@ -1964,53 +2281,63 @@ bool oEvent::formatPrintPost(const list &ppli, PrintPostInfo &ppi, ++ppit; // Main increment below - if ( ++ppit != ppli.end() && ppit->dy==pp.dy) + if (++ppit != ppli.end() && ppit->dy == pp.dy) limit = ppit->dx - pp.dx; else keepNext = true; - limit=max(pp.fixedWidth, limit); + limit = max(pp.fixedWidth, limit); - assert(limit>=0); + assert(limit >= 0); pRunner rr = r; if (!rr && t) { if (pp.legIndex >= 0) { int lg = pc ? pc->getLinearIndex(pp.legIndex, pp.linearLegIndex) : pp.legIndex; - rr=t->getRunner(lg); + rr = t->getRunner(lg); } else if (legIndex >= 0) - rr=t->getRunner(legIndex); + rr = t->getRunner(legIndex); else { int lg = ppi.par.getLegNumber(pc); rr = t->getRunner(lg); } } - const wstring &text = (legIndex == -1) ? formatListString(pp, ppi.par, t, rr, c, pc, ppi.counter) : - formatSpecialString(pp, ppi.par, t, legIndex, crs, ctrl, ppi.counter); + const wstring &text = punch ? formatPunchString(pp, ppi.par, t, rr, punch, ppi.counter) : + ((legIndex == -1) ? formatListString(pp, ppi.par, t, rr, c, pc, ppi.counter) : + formatSpecialString(pp, ppi.par, t, legIndex, crs, ctrl, ppi.counter)); updated |= !text.empty(); + TextInfo *ti = 0; if (!text.empty()) { - if ( (pp.type == lRunnerName || pp.type == lRunnerCompleteName || - pp.type == lRunnerFamilyName || pp.type == lRunnerGivenName || - pp.type == lTeamRunner || (pp.type == lPatrolNameNames && !t)) && rr) { - ti = &ppi.gdi.addStringUT(y+ppi.gdi.scaleLength(pp.dy), x+ppi.gdi.scaleLength(pp.dx), pp.format, text, - ppi.gdi.scaleLength(limit), ppi.par.cb, pp.fontFace.c_str()); + pdy = ppi.gdi.scaleLength(pp.dy); + pdx = ppi.gdi.scaleLength(pp.dx); + if ((pp.type == lRunnerName || pp.type == lRunnerCompleteName || + pp.type == lRunnerFamilyName || pp.type == lRunnerGivenName || + pp.type == lTeamRunner || (pp.type == lPatrolNameNames && !t)) && rr) { + ti = &ppi.gdi.addStringUT(y + pdy, x + pdx, pp.format, text, + ppi.gdi.scaleLength(limit), ppi.par.cb, pp.fontFace.c_str()); ti->setExtra(rr->getId()); ti->id = "R"; } else if ((pp.type == lTeamName || pp.type == lPatrolNameNames) && t) { - ti = &ppi.gdi.addStringUT(y+ppi.gdi.scaleLength(pp.dy), x+ppi.gdi.scaleLength(pp.dx), pp.format, text, - ppi.gdi.scaleLength(limit), ppi.par.cb, pp.fontFace.c_str()); + ti = &ppi.gdi.addStringUT(y + pdy, x + pdx, pp.format, text, + ppi.gdi.scaleLength(limit), ppi.par.cb, pp.fontFace.c_str()); ti->setExtra(t->getId()); ti->id = "T"; } else { - ti = &ppi.gdi.addStringUT(y + ppi.gdi.scaleLength(pp.dy), x + ppi.gdi.scaleLength(pp.dx), - pp.format, text, ppi.gdi.scaleLength(limit), 0, pp.fontFace.c_str()); + ti = &ppi.gdi.addStringUT(y + pdy, x + pdx, + pp.format, text, ppi.gdi.scaleLength(limit), 0, pp.fontFace.c_str()); } if (ti && ppi.keepToghether) ti->lineBreakPrioity = -1; + if (ti) { + lineHeight = ti->getHeight(); + + } + + if (pp.color != colorDefault) ti->setColor(pp.color); } @@ -2044,80 +2371,136 @@ void oEvent::calculatePrintPostKey(const list &ppli, gdioutput &gdi, } } -void oEvent::listGeneratePunches(const list &ppli, gdioutput &gdi, const oListParam &par, - pTeam t, pRunner r, pClub club, pClass cls) -{ +void oEvent::listGeneratePunches(const oListInfo &listInfo, gdioutput &gdi, + pTeam t, pRunner r, pClub club, pClass cls) { + const list &ppli = listInfo.subListPost; + const oListParam &par = listInfo.lp; + auto type = listInfo.listSubType; if (!r || ppli.empty()) return; + bool filterNamed = listInfo.subFilter(ESubFilterNamedControl); + const bool filterFinish = listInfo.subFilter(ESubFilterNotFinish); - pCourse crs=r->getCourse(true); - - if (!crs) - return; + pCourse crs = r->getCourse(true); if (cls && cls->getNoTiming()) return; - bool filterNamed = false; int h = gdi.getLineHeight(); - int w=0; - - for (list::const_iterator it = ppli.begin(); it != ppli.end(); ++it) { - if (it->type == lPunchNamedTime) + int w = 0; + bool newLine = false; + int haccum = 0; + for (auto &pl : ppli) { + if (pl.type == lPunchNamedTime) filterNamed = true; - h = max(h, gdi.getLineHeight(it->getFont(), it->fontFace.c_str()) + gdi.scaleLength(it->dy)); - w = max(w, gdi.scaleLength(it->fixedWidth + it->dx)); + if (pl.type == lLineBreak) { + haccum += h; + h = 0; + newLine = true; + continue; + } + h = max(h, gdi.getLineHeight(pl.getFont(), pl.fontFace.c_str()) + gdi.scaleLength(pl.dy)); + w = max(w, gdi.scaleLength(pl.fixedWidth + pl.dx)); } + h += haccum; + int xlimit = gdi.getCX() + gdi.scaleLength(600); + par.lineBreakControlList = newLine; // Controls if controls names are printed even if the runner has not punched there yet. - int xlimit=gdi.getCX()+ gdi.scaleLength(600); - - if (w>0) { + if (w > 0) { gdi.pushX(); gdi.fillNone(); } bool neednewline = false; - bool updated=false; + bool updated = false; - int limit = crs->nControls + 1; + int limit = crs ? crs->nControls + 1 : 1; - if (r->Card && r->Card->getNumPunches()>limit) + if (r->Card && r->Card->getNumPunches() > limit) limit = r->Card->getNumPunches(); vector skip(limit, false); - if (filterNamed) { + if (filterNamed && crs) { for (int k = 0; k < crs->nControls; k++) { if (crs->getControl(k) && !crs->getControl(k)->hasName()) skip[k] = true; } - for (int k = crs->nControls; k < limit; k++) { + for (int k = crs->nControls + 1; k < limit; k++) { skip[k] = true; } + if (filterFinish) + skip[crs->nControls] = true; + } + bool filterRadioTimes = listInfo.sortOrder == SortOrder::ClassLiveResult || listInfo.filter(EFilterList::EFilterAnyResult); + if (filterRadioTimes && crs) { + for (int k = 0; k < crs->nControls; k++) { + if (crs->getControl(k) && !crs->getControl(k)->isValidRadio()) + skip[k] = true; + } + for (int k = crs->nControls + 1; k < limit; k++) { + skip[k] = true; + } + if (filterFinish) + skip[crs->nControls] = true; } - PrintPostInfo ppi(gdi, par); - - for (int k=0; k0 && updated) { - updated=false; - if ( gdi.getCX() + w > xlimit) { - neednewline = false; - gdi.popX(); - gdi.setCY(gdi.getCY() + h); + if (type == oListInfo::EBaseType::EBaseTypeCoursePunches) { + for (int k = 0; k < limit; k++) { + if (w > 0 && updated) { + updated = false; + if (gdi.getCX() + w > xlimit || newLine) { + neednewline = false; + gdi.popX(); + gdi.setCY(gdi.getCY() + h); + } + else + gdi.setCX(gdi.getCX() + w); } - else - gdi.setCX(gdi.getCX()+w); - } - if (!skip[k]) { - updated |= formatPrintPost(ppli, ppi, t, r, club, cls, 0, 0, -1); - neednewline |= updated; + if (!skip[k]) { + updated |= formatPrintPost(ppli, ppi, t, r, club, cls, + nullptr, nullptr, nullptr, -1); + neednewline |= updated; + } + ppi.counter.level3++; } - - ppi.counter.level3++; } + else if(type == oListInfo::EBaseType::EBaseTypeAllPunches && r->Card) { + int startType = -1; + int finishType = -1; + const pCourse pcrs = r->getCourse(false); - if (w>0) { + if (pcrs) { + startType = pcrs->getStartPunchType(); + finishType = pcrs->getFinishPunchType(); + } + int prevPunchTime = r->getStartTime(); + for (auto &punch : r->Card->punches) { + punch.previousPunchTime = prevPunchTime; + + if (punch.isCheck() || punch.isStart(startType)) + continue; + if (filterFinish && punch.isFinish(finishType)) + continue; + prevPunchTime = punch.Time; + if (w > 0 && updated) { + updated = false; + if (gdi.getCX() + w > xlimit || newLine) { + neednewline = false; + gdi.popX(); + gdi.setCY(gdi.getCY() + h); + } + else + gdi.setCX(gdi.getCX() + w); + } + + updated |= formatPrintPost(ppli, ppi, t, r, club, cls, + nullptr, nullptr, &punch, -1); + neednewline |= updated; + ppi.counter.level3++; + } + } + if (w > 0) { gdi.popX(); gdi.fillDown(); if (neednewline) @@ -2130,7 +2513,7 @@ void oEvent::generateList(gdioutput &gdi, bool reEvaluate, const oListInfo &li, reEvaluateAll(set(), false); oe->calcUseStartSeconds(); - oe->calculateNumRemainingMaps(); + oe->calculateNumRemainingMaps(false); oe->updateComputerTime(); vector< pair > > tagNameList; oe->getGeneralResults(false, tagNameList, false); @@ -2145,7 +2528,7 @@ void oEvent::generateList(gdioutput &gdi, bool reEvaluate, const oListInfo &li, listname = name; li.lp.updateDefaultName(name); } - bool addHead = !li.lp.pageBreak && !li.lp.useLargeSize; + bool addHead = li.lp.showHeader && !li.lp.useLargeSize; size_t nClassesSelected = li.lp.selection.size(); if (nClassesSelected!=0 && nClassesSelected < min(Classes.size(), Classes.size()/2+5) ) { // Non-trivial class selection: @@ -2170,10 +2553,15 @@ void oEvent::generateList(gdioutput &gdi, bool reEvaluate, const oListInfo &li, bool interHead = addHead && it->getParam().showInterTitle; if (li.lp.pageBreak || it->lp.pageBreak) { gdi.dropLine(1.0); - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); } - else if (interHead) + else if (interHead) { gdi.dropLine(1.5); + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewChapter, ""); + } + else { + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + } generateListInternal(gdi, *it, interHead); } @@ -2186,6 +2574,112 @@ void oEvent::generateList(gdioutput &gdi, bool reEvaluate, const oListInfo &li, gdi.updateScrollbars(); } +bool oListInfo::filterRunner(const oRunner &r) const { + if (r.isRemoved() || r.tStatus == StatusNotCompetiting) + return true; + + if (!lp.selection.empty() && lp.selection.count(r.getClassId(true)) == 0) + return true; + + if (!lp.matchLegNumber(r.getClassRef(false), r.legToRun())) + return true; + + if (filter(EFilterExcludeDNS)) { + if (r.tStatus == StatusDNS) + return true; + if (r.Class && r.Class->isQualificationFinalBaseClass()) { + if (r.getLegNumber() > 0 && r.getClassRef(true) == r.Class) + return true; //Not qualified -> out + } + } + if (filter(EFilterExcludeCANCEL) && r.tStatus == StatusCANCEL) + return true; + + if (filter(EFilterVacant)) { + if (r.isVacant()) + return true; + } + if (filter(EFilterOnlyVacant)) { + if (!r.isVacant()) + return true; + } + + if (filter(EFilterAnyResult)) { + if (r.tOnCourseResults.empty()) + return true; + } + + if (filter(EFilterAPIEntry)) { + if (!r.hasFlag(oRunner::FlagAddedViaAPI)) + return true; + } + + if (filter(EFilterRentCard) && r.getDCI().getInt("CardFee") == 0) + return true; + + if (filter(EFilterHasCard) && r.getCardNo() == 0) + return true; + + if (filter(EFilterHasNoCard) && r.getCardNo() > 0) + return true; + + return false; +} + +bool oListInfo::filterRunnerResult(GeneralResult *gResult, const oRunner &r) const { + if (gResult && r.getTempResult(0).getStatus() == StatusNotCompetiting) + return true; + + if (filter(EFilterHasResult)) { + if (gResult == 0) { + if (lp.useControlIdResultTo <= 0 && r.tStatus == StatusUnknown) + return true; + else if ((lp.useControlIdResultTo > 0 || lp.useControlIdResultFrom > 0) && r.tempStatus != StatusOK) + return true; + else if (calcTotalResults && r.getTotalStatus() == StatusUnknown) + return true; + } + else { + auto &res = r.getTempResult(0); + RunnerStatus st = res.getStatus(); + if (st == StatusUnknown) + return true; + } + } + else if (filter(EFilterHasPrelResult)) { + if (gResult == 0) { + if (lp.useControlIdResultTo <= 0 && r.tStatus == StatusUnknown && r.getRunningTime() <= 0) + return true; + else if ((lp.useControlIdResultTo > 0 || lp.useControlIdResultFrom > 0) && r.tempStatus != StatusOK) + return true; + else if (calcTotalResults && r.getTotalStatus() == StatusUnknown && r.getTotalRunningTime() <= 0) + return true; + } + else { + auto &res = r.getTempResult(0); + int rt = res.getRunningTime(); + RunnerStatus st = res.getStatus(); + if (st == StatusUnknown && rt <= 0) + return true; + } + } + return false; +} + +GeneralResult *oListInfo::applyResultModule(oEvent &oe, vector &rlist) const { + GeneralResult *gResult = 0; + if (!resultModule.empty()) { + wstring src; + oListInfo::ResultType resType = getResultType(); + gResult = &oe.getGeneralResult(resultModule, src); + gResult->calculateIndividualResults(rlist, resType, sortOrder == Custom, getParam().getInputNumber()); + + if (sortOrder == SortByFinishTime || sortOrder == SortByFinishTimeReverse || sortOrder == SortByStartTime) + gResult->sort(rlist, sortOrder); + } + return gResult; +} + void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool formatHead) { li.setupLinks(); pClass sampleClass = 0; @@ -2199,38 +2693,48 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form //oCounter counter; //Render header - if (formatHead) - formatPrintPost(li.Head, printPostInfo, 0,0,0,0,0,0, -1); - + if (formatHead && li.getParam().showHeader) { + for (auto &h : li.Head) { + if (h.type == lCmpName || h.type == lString) { + const_cast(h.text) = li.lp.getCustomTitle(h.text); + break; + } + } + formatPrintPost(li.Head, printPostInfo, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, -1); + } if (li.fixedType) { generateFixedList(gdi, li); return; } // Apply for all teams (calculate start times etc.) - for (oTeamList::iterator it=Teams.begin(); it != Teams.end(); ++it) { + for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) { if (it->isRemoved() || it->tStatus == StatusNotCompetiting) continue; - if (!li.lp.selection.empty() && li.lp.selection.count(it->getClassId(false))==0) + if (!li.lp.selection.empty() && li.lp.selection.count(it->getClassId(false)) == 0) continue; it->apply(false, 0, true); } wstring oldKey; - if ( li.listType==li.EBaseTypeRunner ) { + if (li.listType == li.EBaseTypeRunner) { + + if (li.calculateLiveResults || li.sortOrder == SortOrder::ClassLiveResult) + calculateResults(li.lp.selection, ResultType::PreliminarySplitResults); if (li.calcCourseClassResults) - calculateResults(RTClassCourseResult); + calculateResults(li.lp.selection, ResultType::ClassCourseResult); if (li.calcTotalResults) { if (li.calcResults) { - calculateResults(RTClassResult); + calculateResults(li.lp.selection, ResultType::ClassResult); calculateTeamResults(false); } calculateTeamResults(true); - calculateResults(RTTotalResult); + calculateResults(li.lp.selection, ResultType::TotalResult); if (li.sortOrder != ClassTotalResult) sortRunners(li.sortOrder); @@ -2241,14 +2745,14 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form if (li.sortOrder != ClassPoints) sortRunners(li.sortOrder); } - else if (li.lp.useControlIdResultTo>0 || li.lp.useControlIdResultFrom>0) + else if (li.lp.useControlIdResultTo > 0 || li.lp.useControlIdResultFrom > 0) calculateSplitResults(li.lp.useControlIdResultFrom, li.lp.useControlIdResultTo); else if (li.sortOrder == CourseResult) { - calculateResults(RTCourseResult); + calculateResults(li.lp.selection, ResultType::CourseResult); } else { calculateTeamResults(false); - calculateResults(RTClassResult); + calculateResults(li.lp.selection, ResultType::ClassResult); if (li.sortOrder != ClassResult) sortRunners(li.sortOrder); } @@ -2258,129 +2762,52 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form vector rlist; rlist.reserve(Runners.size()); - - for (oRunnerList::iterator it=Runners.begin(); it != Runners.end(); ++it) { - if (it->isRemoved() || it->tStatus == StatusNotCompetiting) - continue; - - if (!li.lp.selection.empty() && li.lp.selection.count(it->getClassId(true))==0) - continue; - - if (!li.lp.matchLegNumber(it->getClassRef(false), it->legToRun())) - continue; - - if (li.filter(EFilterExcludeDNS)) - if (it->tStatus == StatusDNS) - continue; - - - if (li.filter(EFilterExcludeCANCEL) && it->tStatus == StatusCANCEL) - continue; - - if (li.filter(EFilterVacant)) { - if (it->isVacant()) - continue; - } - if (li.filter(EFilterOnlyVacant)) { - if (!it->isVacant()) - continue; - } - - if (li.filter(EFilterRentCard) && it->getDI().getInt("CardFee")==0) - continue; - - if (li.filter(EFilterHasCard) && it->getCardNo()==0) - continue; - - if (li.filter(EFilterHasNoCard) && it->getCardNo()>0) - continue; - - rlist.push_back(&*it); + for (auto &r : Runners) { + if (!li.filterRunner(r)) + rlist.push_back(&r); } - GeneralResult *gResult = 0; - if (!li.resultModule.empty()) { - wstring src; - oListInfo::ResultType resType = li.getResultType(); - gResult = &getGeneralResult(li.resultModule, src); - gResult->calculateIndividualResults(rlist, resType, li.sortOrder == Custom, li.getParam().getInputNumber()); - - if (li.sortOrder == SortByFinishTime || li.sortOrder == SortByFinishTimeReverse || li.sortOrder == SortByStartTime) - gResult->sort(rlist, li.sortOrder); - } + GeneralResult *gResult = li.applyResultModule(*this, rlist); for (size_t k = 0; k < rlist.size(); k++) { pRunner it = rlist[k]; - - if (gResult && it->getTempResult(0).getStatus() == StatusNotCompetiting) - continue; - - if (li.filter(EFilterHasResult)) { - if (gResult == 0) { - if (li.lp.useControlIdResultTo<=0 && it->tStatus==StatusUnknown) - continue; - else if ( (li.lp.useControlIdResultTo>0 || li.lp.useControlIdResultFrom>0) && it->tempStatus!=StatusOK) - continue; - else if (li.calcTotalResults && it->getTotalStatus() == StatusUnknown) - continue; - } - else { - const oAbstractRunner::TempResult &res = it->getTempResult(0); - RunnerStatus st = res.getStatus(); - if (st==StatusUnknown) - continue; - } - } - else if (li.filter(EFilterHasPrelResult)) { - if (gResult == 0) { - if (li.lp.useControlIdResultTo<=0 && it->tStatus==StatusUnknown && it->getRunningTime()<=0) - continue; - else if ( (li.lp.useControlIdResultTo>0 || li.lp.useControlIdResultFrom>0) && it->tempStatus!=StatusOK) - continue; - else if (li.calcTotalResults && it->getTotalStatus() == StatusUnknown && it->getTotalRunningTime()<=0) - continue; - } - else { - const oAbstractRunner::TempResult &res = it->getTempResult(0); - int rt = res.getRunningTime(); - RunnerStatus st = res.getStatus(); - if (st==StatusUnknown && rt<=0) - continue; - } - } - + if (li.filterRunnerResult(gResult, *it)) + continue; + wstring newKey; printPostInfo.par.relayLegIndex = -1; calculatePrintPostKey(li.subHead, gdi, li.lp, it->tInTeam, &*it, it->Club, it->getClassRef(true), printPostInfo.counter, newKey); if (newKey != oldKey) { - if (li.lp.pageBreak) { - if (!oldKey.empty()) - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); - } + if (!oldKey.empty()) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + gdi.addStringUT(pagePageInfo, it->getClass(true)); oldKey.swap(newKey); - printPostInfo.counter.level2=0; - printPostInfo.counter.level3=0; + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; printPostInfo.reset(); printPostInfo.par.relayLegIndex = -1; - formatPrintPost(li.subHead, printPostInfo, it->tInTeam, &*it, it->Club, it->getClassRef(true), 0, 0, -1); + formatPrintPost(li.subHead, printPostInfo, it->tInTeam, &*it, it->Club, it->getClassRef(true), + nullptr, nullptr, nullptr, -1); } - if (li.lp.filterMaxPer==0 || printPostInfo.counter.level2tLeg; - formatPrintPost(li.listPost, printPostInfo, it->tInTeam, &*it, it->Club, it->getClassRef(true), 0, 0, -1); + formatPrintPost(li.listPost, printPostInfo, it->tInTeam, &*it, it->Club, it->getClassRef(true), + nullptr, nullptr, nullptr, -1); - if (li.listSubType==li.EBaseTypePunches) { - listGeneratePunches(li.subListPost, gdi, li.lp, it->tInTeam, &*it, it->Club, it->getClassRef(true)); + if (li.listSubType == li.EBaseTypeCoursePunches || + li.listSubType == li.EBaseTypeAllPunches) { + listGeneratePunches(li, gdi, it->tInTeam, &*it, it->Club, it->getClassRef(true)); } } ++printPostInfo.counter; } } - else if ( li.listType==li.EBaseTypeTeam ) { + else if (li.listType == li.EBaseTypeTeam) { if (li.calcResults) calculateTeamResults(false); if (li.calcTotalResults) @@ -2388,7 +2815,7 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form if (li.rogainingResults && li.resultModule.empty()) throw std::exception("Not implemented"); if (li.calcCourseClassResults) - calculateResults(RTClassCourseResult); + calculateResults(li.lp.selection, ResultType::ClassCourseResult); if (li.resultModule.empty()) { pair legInfo = li.lp.getLegInfo(sampleClass); @@ -2396,11 +2823,11 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form } vector tlist; tlist.reserve(Teams.size()); - for (oTeamList::iterator it=Teams.begin(); it != Teams.end(); ++it) { + for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) { if (it->isRemoved() || it->tStatus == StatusNotCompetiting) continue; - if (!li.lp.selection.empty() && li.lp.selection.count(it->getClassId(true))==0) + if (!li.lp.selection.empty() && li.lp.selection.count(it->getClassId(true)) == 0) continue; tlist.push_back(&*it); } @@ -2414,16 +2841,16 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form // Range of runners to include int parLegRangeMin = 0, parLegRangeMax = 1000; pClass parLegRangeClass = 0; - const bool needParRange = li.subFilter(ESubFilterSameParallel) - || li.subFilter(ESubFilterSameParallelNotFirst); + const bool needParRange = li.subFilter(ESubFilterSameParallel) + || li.subFilter(ESubFilterSameParallelNotFirst); for (size_t k = 0; k < tlist.size(); k++) { pTeam it = tlist[k]; int linearLegSpec = li.lp.getLegNumber(it->getClassRef(false)); if (gResult && it->getTempResult(0).getStatus() == StatusNotCompetiting) - continue; - + continue; + if (li.filter(EFilterExcludeDNS)) if (it->tStatus == StatusDNS) continue; @@ -2437,27 +2864,27 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form continue; } - if ( li.filter(EFilterHasResult) ) { + if (li.filter(EFilterHasResult)) { if (gResult) { if (it->getTempResult(0).getStatus() == StatusUnknown) continue; } else { - if (it->getLegStatus(linearLegSpec, false)==StatusUnknown) + if (it->getLegStatus(linearLegSpec, false) == StatusUnknown) continue; else if (li.calcTotalResults && it->getLegStatus(linearLegSpec, true) == StatusUnknown) continue; } } - else if ( li.filter(EFilterHasPrelResult) ) { + else if (li.filter(EFilterHasPrelResult)) { if (gResult) { if (it->getTempResult(0).getStatus() == StatusUnknown && it->getTempResult(0).getRunningTime() <= 0) continue; } else { - if (it->getLegStatus(linearLegSpec, false)==StatusUnknown && it->getLegRunningTime(linearLegSpec, false)<=0) + if (it->getLegStatus(linearLegSpec, false) == StatusUnknown && it->getLegRunningTime(linearLegSpec, false) <= 0) continue; - else if (li.calcTotalResults && it->getLegStatus(linearLegSpec, true) == StatusUnknown && it->getTotalRunningTime()<=0) + else if (li.calcTotalResults && it->getLegStatus(linearLegSpec, true) == StatusUnknown && it->getTotalRunningTime() <= 0) continue; } } @@ -2473,10 +2900,9 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form printPostInfo.par.relayLegIndex = linearLegSpec; calculatePrintPostKey(li.subHead, gdi, li.lp, &*it, 0, it->Club, it->Class, printPostInfo.counter, newKey); if (newKey != oldKey) { - if (li.lp.pageBreak) { - if (!oldKey.empty()) - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); - } + if (!oldKey.empty()) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + wstring legInfo; if (linearLegSpec >= 0 && it->getClassRef(false)) { // Specified leg @@ -2486,37 +2912,38 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form gdi.addStringUT(pagePageInfo, it->getClass(true) + legInfo); // Teamlist oldKey.swap(newKey); - printPostInfo.counter.level2=0; - printPostInfo.counter.level3=0; + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; printPostInfo.reset(); printPostInfo.par.relayLegIndex = linearLegSpec; - formatPrintPost(li.subHead, printPostInfo, &*it, 0, it->Club, it->Class, 0,0,-1); + formatPrintPost(li.subHead, printPostInfo, &*it, 0, it->Club, it->Class, + nullptr, nullptr, nullptr, -1); } ++printPostInfo.counter; - if (li.lp.filterMaxPer==0 || printPostInfo.counter.level2<=li.lp.filterMaxPer) { - printPostInfo.counter.level3=0; + if (li.lp.filterMaxPer == 0 || printPostInfo.counter.level2 <= li.lp.filterMaxPer) { + printPostInfo.counter.level3 = 0; printPostInfo.reset(); printPostInfo.par.relayLegIndex = linearLegSpec; - formatPrintPost(li.listPost, printPostInfo, &*it, 0, it->Club, it->Class, 0, 0, -1); + formatPrintPost(li.listPost, printPostInfo, &*it, 0, it->Club, it->Class, + nullptr, nullptr, nullptr, -1); if (li.subListPost.empty()) continue; - if (li.listSubType==li.EBaseTypeRunner) { + if (li.listSubType == li.EBaseTypeRunner) { int nr = int(it->Runners.size()); vector tr; tr.reserve(nr); vector usedIx(nr, -1); - for (int k=0;kRunners[k]) { - - if (li.filter(EFilterHasResult) || li.subFilter(ESubFilterHasResult) || - li.filter(EFilterHasPrelResult) || li.subFilter(ESubFilterHasPrelResult) || - li.filter(EFilterExcludeDNS) || li.filter(EFilterExcludeCANCEL) || li.subFilter(ESubFilterExcludeDNS) || - li.subFilter(ESubFilterVacant)) { - usedIx[k] = -2; // Skip totally - } + if (li.filter(EFilterHasResult) || li.subFilter(ESubFilterHasResult) || + li.filter(EFilterHasPrelResult) || li.subFilter(ESubFilterHasPrelResult) || + li.filter(EFilterExcludeDNS) || li.filter(EFilterExcludeCANCEL) || li.subFilter(ESubFilterExcludeDNS) || + li.subFilter(ESubFilterVacant)) { + usedIx[k] = -2; // Skip totally + } continue; } else @@ -2529,13 +2956,18 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form noResult = it->Runners[k]->tStatus == StatusUnknown; noPrelResult = it->Runners[k]->tStatus == StatusUnknown && it->Runners[k]->getRunningTime() <= 0; noStart = it->Runners[k]->tStatus == StatusDNS || it->Runners[k]->tStatus == StatusCANCEL; + if (it->Runners[k]->Class && it->Runners[k]->Class->isQualificationFinalBaseClass()) { + if (k > 0 && it->Runners[k]->getClassRef(true) == it->Runners[k]->Class) + noStart = true; + } + cancelled = it->Runners[k]->tStatus == StatusCANCEL; //XXX TODO Multiday } else { noResult = it->Runners[k]->tmpResult.status == StatusUnknown; - noPrelResult = it->Runners[k]->tmpResult.status == StatusUnknown && it->Runners[k]->tmpResult.runningTime <= 0; - noStart = it->Runners[k]->tmpResult.status == StatusDNS || it->Runners[k]->tmpResult.status == StatusCANCEL; + noPrelResult = it->Runners[k]->tmpResult.status == StatusUnknown && it->Runners[k]->tmpResult.runningTime <= 0; + noStart = it->Runners[k]->tmpResult.status == StatusDNS || it->Runners[k]->tmpResult.status == StatusCANCEL; cancelled = it->Runners[k]->tmpResult.status == StatusCANCEL; } @@ -2554,8 +2986,8 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form if (it->Runners[k]->isVacant() && li.subFilter(ESubFilterVacant)) continue; - if ( (it->Runners[k]->tLeg < parLegRangeMin || it->Runners[k]->tLeg > parLegRangeMax) - && needParRange) + if ((it->Runners[k]->tLeg < parLegRangeMin || it->Runners[k]->tLeg > parLegRangeMax) + && needParRange) continue; usedIx[k] = tr.size(); @@ -2566,11 +2998,12 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form gResult->sortTeamMembers(tr); for (size_t k = 0; k < tr.size(); k++) { - bool suitableBreak = k<2 || (k+2)>=tr.size(); + bool suitableBreak = k < 2 || (k + 2) >= tr.size(); printPostInfo.keepToghether = suitableBreak; printPostInfo.par.relayLegIndex = tr[k] ? tr[k]->tLeg : -1; formatPrintPost(li.subListPost, printPostInfo, &*it, tr[k], - it->Club, tr[k]->getClassRef(true), 0, 0, -1); + it->Club, tr[k]->getClassRef(true), + nullptr, nullptr, nullptr, -1); printPostInfo.counter.level3++; } } @@ -2578,53 +3011,74 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form for (size_t k = 0; k < usedIx.size(); k++) { if (usedIx[k] == -2) continue; // Skip - bool suitableBreak = k<2 || (k+2)>=usedIx.size(); + bool suitableBreak = k < 2 || (k + 2) >= usedIx.size(); printPostInfo.keepToghether = suitableBreak; printPostInfo.par.relayLegIndex = k; if (usedIx[k] == -1) { - pCourse crs = it->Class ? it->Class->getCourse(k, it->StartNo) : 0; + pCourse crs = it->Class ? it->Class->getCourse(k, it->StartNo) : 0; formatPrintPost(li.subListPost, printPostInfo, &*it, 0, - it->Club, it->Class, crs, 0, k); + it->Club, it->Class, crs, nullptr, nullptr, k); } else { formatPrintPost(li.subListPost, printPostInfo, &*it, tr[usedIx[k]], - it->Club, tr[usedIx[k]]->getClassRef(true), 0, 0, -1); + it->Club, tr[usedIx[k]]->getClassRef(true), + nullptr, nullptr, nullptr, -1); } printPostInfo.counter.level3++; } } } - else if (li.listSubType==li.EBaseTypePunches) { - pRunner r=it->Runners.empty() ? 0:it->Runners[0]; + else if (li.listSubType == li.EBaseTypeCoursePunches || + li.listSubType == li.EBaseTypeAllPunches) { + pRunner r = it->Runners.empty() ? 0 : it->Runners[0]; if (!r) continue; - listGeneratePunches(li.subListPost, gdi, li.lp, &*it, r, it->Club, it->Class); + listGeneratePunches(li, gdi, &*it, r, it->Club, it->Class); } } } } - else if ( li.listType==li.EBaseTypeClub ) { + else if (li.listType == li.EBaseTypeClub) { if (li.calcResults) { calculateTeamResults(true); calculateTeamResults(false); } if (li.calcCourseClassResults) - calculateResults(RTClassCourseResult); + calculateResults(li.lp.selection, ResultType::ClassCourseResult); pair info = li.lp.getLegInfo(sampleClass); sortTeams(li.sortOrder, info.first, info.second); - if ( li.calcResults ) { - if (li.lp.useControlIdResultTo>0 || li.lp.useControlIdResultFrom>0) + if (li.calcResults) { + if (li.lp.useControlIdResultTo > 0 || li.lp.useControlIdResultFrom > 0) calculateSplitResults(li.lp.useControlIdResultFrom, li.lp.useControlIdResultTo); else - calculateResults(RTClassResult); + calculateResults(li.lp.selection, ResultType::ClassResult); } else sortRunners(li.sortOrder); Clubs.sort(); oClubList::iterator it; - oRunnerList::iterator rit; + + map> clubToRunner; + + vector rlist; + rlist.reserve(Runners.size()); + for (auto &r : Runners) { + if (!li.filterRunner(r)) + rlist.push_back(&r); + } + + GeneralResult *gResult = li.applyResultModule(*this, rlist); + + for (pRunner r : rlist) { + int club = r->getClubId(); + if (club == 0) + continue; + + clubToRunner[club].push_back(r); + } + bool first = true; for (it = Clubs.begin(); it != Clubs.end(); ++it) { if (it->isRemoved()) @@ -2642,57 +3096,36 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form bool startClub = false; - for (rit = Runners.begin(); rit != Runners.end(); ++rit) { - if (rit->isRemoved() || rit->tStatus == StatusNotCompetiting) + for (auto rit : clubToRunner[it->getId()]) { + if (li.filterRunnerResult(gResult, *rit)) continue; - if (!li.lp.selection.empty() && li.lp.selection.count(rit->getClassId(true))==0) - continue; - - if (!li.lp.matchLegNumber(rit->getClassRef(false), rit->legToRun())) - continue; - - if (li.filter(EFilterExcludeDNS)) - if (rit->tStatus==StatusDNS) - continue; - - if (li.filter(EFilterExcludeCANCEL)) - if (rit->tStatus == StatusCANCEL) - continue; - - if (li.filter(EFilterHasResult)) { - if (li.lp.useControlIdResultTo<=0 && rit->tStatus==StatusUnknown) - continue; - else if ((li.lp.useControlIdResultTo>0 || li.lp.useControlIdResultFrom>0) && rit->tempStatus!=StatusOK) - continue; - else if (li.calcTotalResults && rit->getTotalStatus() == StatusUnknown) - continue; + if (!startClub) { + if (!first) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + else + first = false; + + gdi.addStringUT(pagePageInfo, it->getName()); + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; + printPostInfo.reset(); + printPostInfo.par.relayLegIndex = -1; + formatPrintPost(li.subHead, printPostInfo, nullptr, nullptr, &*it, + nullptr, nullptr, nullptr, nullptr, -1); + startClub = true; } + ++printPostInfo.counter; + if (li.lp.filterMaxPer == 0 || printPostInfo.counter.level2 <= li.lp.filterMaxPer) { + printPostInfo.counter.level3 = 0; + printPostInfo.reset(); + printPostInfo.par.relayLegIndex = rit->tLeg; + formatPrintPost(li.listPost, printPostInfo, nullptr, &*rit, &*it, rit->getClassRef(true), + nullptr, nullptr, nullptr, -1); - if (rit->Club == &*it) { - if (!startClub) { - if (li.lp.pageBreak) { - if (!first) - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); - else - first = false; - } - gdi.addStringUT(pagePageInfo, it->getName()); - printPostInfo.counter.level2=0; - printPostInfo.counter.level3=0; - printPostInfo.reset(); - printPostInfo.par.relayLegIndex = -1; - formatPrintPost(li.subHead, printPostInfo, 0, 0, &*it, 0, 0, 0, -1); - startClub = true; - } - ++printPostInfo.counter; - if (li.lp.filterMaxPer==0 || printPostInfo.counter.level2<=li.lp.filterMaxPer) { - printPostInfo.counter.level3=0; - printPostInfo.reset(); - printPostInfo.par.relayLegIndex = rit->tLeg; - formatPrintPost(li.listPost, printPostInfo, 0, &*rit, &*it, rit->getClassRef(true), 0, 0, -1); - if (li.subListPost.empty()) - continue; + if (li.listSubType == li.EBaseTypeCoursePunches || + li.listSubType == li.EBaseTypeAllPunches) { + listGeneratePunches(li, gdi, rit->tInTeam, &*rit, rit->Club, rit->getClassRef(true)); } } }//Runners @@ -2722,27 +3155,26 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form calculatePrintPostKey(li.subHead, gdi, li.lp, 0, 0, 0, 0, printPostInfo.counter, newKey); if (newKey != oldKey) { - if (li.lp.pageBreak) { - if (!oldKey.empty()) - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); - } - + if (!oldKey.empty()) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + oldKey.swap(newKey); - printPostInfo.counter.level2=0; - printPostInfo.counter.level3=0; + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; printPostInfo.reset(); - formatPrintPost(li.subHead, printPostInfo, 0, 0, 0, 0, &*it, 0, 0); + formatPrintPost(li.subHead, printPostInfo, nullptr, + nullptr, nullptr, nullptr, &*it, nullptr, nullptr, 0); } - if (li.lp.filterMaxPer==0 || printPostInfo.counter.level2tInTeam, &*it, it->Club, it->Class); } } ++printPostInfo.counter; - } } else if (li.listType == oListInfo::EBaseTypeControl) { @@ -2756,20 +3188,20 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form calculatePrintPostKey(li.subHead, gdi, li.lp, 0, 0, 0, 0, printPostInfo.counter, newKey); if (newKey != oldKey) { - if (li.lp.pageBreak) { - if (!oldKey.empty()) - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); - } - + if (!oldKey.empty()) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + oldKey.swap(newKey); - printPostInfo.counter.level2=0; - printPostInfo.counter.level3=0; + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; printPostInfo.reset(); - formatPrintPost(li.subHead, printPostInfo, 0, 0, 0, 0, 0, &*it, 0); + formatPrintPost(li.subHead, printPostInfo, nullptr, nullptr, nullptr, + nullptr, nullptr, &*it, nullptr, 0); } - if (li.lp.filterMaxPer==0 || printPostInfo.counter.level2 &par, int lineHeight, oListInfo &li) { + li.getParam().sourceParam = -1;// Reset source loadGeneralResults(false); lineHeight = 14; for (size_t k = 0; k < par.size(); k++) { @@ -3475,16 +3908,16 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, if (li.lp.showInterTimes) { li.addSubListPost(oPrintPost(lPunchNamedTime, L"", italicSmall, pos.get("name"), 0, make_pair(1, true))); li.subListPost.back().fixedWidth = 160; - li.listSubType = li.EBaseTypePunches; + li.listSubType = li.EBaseTypeCoursePunches; } else if (li.lp.showSplitTimes) { li.addSubListPost(oPrintPost(lPunchTime, L"", italicSmall, pos.get("name"), 0, make_pair(1, true))); li.subListPost.back().fixedWidth = 95; - li.listSubType = li.EBaseTypePunches; + li.listSubType = li.EBaseTypeCoursePunches; } } else { - li.needPunches = true; + li.needPunches = oListInfo::PunchMode::SpecificPunch; li.addListPost(oPrintPost(lRunnerTempTimeStatus, L"", normalText, pos.get("status"), 0)); li.addListPost(oPrintPost(lRunnerTempTimeAfter, L"", normalText, pos.get("after"), 0)); } @@ -3492,7 +3925,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addSubHead(oPrintPost(lString, lang.tl(L"Efter"), boldText, pos.get("after"), 10)); if (li.lp.splitAnalysis) { - li.addListPost(oPrintPost(lRunnerMissedTime, L"", normalText, pos.get("missed"), 0)); + li.addListPost(oPrintPost(lRunnerLostTime, L"", normalText, pos.get("missed"), 0)); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldText, pos.get("missed"), 10)); } @@ -3547,16 +3980,16 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, if (li.lp.showInterTimes) { li.addSubListPost(oPrintPost(lPunchNamedTime, L"", small, pos.get("name", s), 0, make_pair(1, true))); li.subListPost.back().fixedWidth = 160; - li.listSubType = li.EBaseTypePunches; + li.listSubType = li.EBaseTypeCoursePunches; } else if (li.lp.showSplitTimes) { li.addSubListPost(oPrintPost(lPunchTime, L"", small, pos.get("name", s), 0, make_pair(1, true))); li.subListPost.back().fixedWidth = 95; - li.listSubType = li.EBaseTypePunches; + li.listSubType = li.EBaseTypeCoursePunches; } } else { - li.needPunches = true; + li.needPunches = oListInfo::PunchMode::SpecificPunch; li.addListPost(oPrintPost(lRunnerTempTimeStatus, L"", normal, pos.get("status", s), 0)); li.addListPost(oPrintPost(lRunnerTempTimeAfter, L"", normal, pos.get("after", s), 0)); } @@ -3650,7 +4083,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addSubListPost(oPrintPost(lTeamLegTimeAfter, L"", normalText, 460, 0, make_pair(0, true))); if (li.lp.splitAnalysis) { - li.addSubListPost(oPrintPost(lRunnerMissedTime, L"", normalText, 510, 0)); + li.addSubListPost(oPrintPost(lRunnerLostTime, L"", normalText, 510, 0)); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldText, 510, 14)); } @@ -3697,7 +4130,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addListPost(oPrintPost(lTeamTimeAfter, L"", normalText, pos.get("after"), 2, ln)); if (li.lp.splitAnalysis) { - li.addListPost(oPrintPost(lRunnerMissedTime, L"", normalText, pos.get("missed"), 2, ln)); + li.addListPost(oPrintPost(lRunnerLostTime, L"", normalText, pos.get("missed"), 2, ln)); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldText, pos.get("missed"), 14)); } @@ -3738,7 +4171,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addListPost(oPrintPost(lTeamTimeAfter, L"", fontLarge, pos.get("after", scale), 5, ln)); if (li.lp.splitAnalysis) { - li.addListPost(oPrintPost(lRunnerMissedTime, L"", fontLarge, pos.get("missed", scale), 5, ln)); + li.addListPost(oPrintPost(lRunnerLostTime, L"", fontLarge, pos.get("missed", scale), 5, ln)); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldLarge, pos.get("missed", scale), 14)); } @@ -3908,7 +4341,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addListPost(oPrintPost(lTeamTimeAfter, L"", normalText, 560, 0, ln)); if (li.lp.splitAnalysis) { - li.addListPost(oPrintPost(lRunnerMissedTime, L"", normalText, 620, 0, ln)); + li.addListPost(oPrintPost(lRunnerLostTime, L"", normalText, 620, 0, ln)); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldText, 620, 14)); } @@ -4043,11 +4476,11 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, if (li.lp.showInterTimes) { li.addSubListPost(oPrintPost(lPunchNamedTime, L"", normalText, 10, 0, make_pair(1, true))); li.subListPost.back().fixedWidth=160; - li.listSubType=li.EBaseTypePunches; + li.listSubType=li.EBaseTypeCoursePunches; } if (li.lp.splitAnalysis) { - li.addListPost(oPrintPost(lRunnerMissedTime, L"", normalText, 520, vspace, make_pair(1, true))); + li.addListPost(oPrintPost(lRunnerLostTime, L"", normalText, 520, vspace, make_pair(1, true))); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldText, 520, 10)); } @@ -4083,11 +4516,11 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, if (li.lp.showInterTimes) { li.addSubListPost(oPrintPost(lPunchNamedTime, L"", normalText, 10, 0, make_pair(1, true))); li.subListPost.back().fixedWidth=200; - li.listSubType=li.EBaseTypePunches; + li.listSubType=li.EBaseTypeCoursePunches; } if (li.lp.splitAnalysis) { - li.addListPost(oPrintPost(lRunnerMissedTime, L"", fontLarge, pos.get("missed", scale), vspace, make_pair(0, true))); + li.addListPost(oPrintPost(lRunnerLostTime, L"", fontLarge, pos.get("missed", scale), vspace, make_pair(0, true))); li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldLarge, pos.get("missed", scale), 10)); } @@ -4115,7 +4548,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addSubHead(oPrintPost(lClassName, L"", boldLarge, 0, 10)); li.listType=li.EBaseTypeTeam; - li.listSubType=li.EBaseTypePunches; + li.listSubType=li.EBaseTypeCoursePunches; li.sortOrder=ClassResult; li.calcResults=true; break; @@ -4138,23 +4571,23 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, li.addListPost(oPrintPost(lRunnerTimeStatus, L"", fontLarge, pos.get("status", scale), vspace)); } else { - li.needPunches = true; + li.needPunches = oListInfo::PunchMode::SpecificPunch; li.addListPost(oPrintPost(lRunnerTempTimeStatus, L"", normalText, pos.get("status", scale), vspace)); } if (li.lp.splitAnalysis) { li.addSubHead(oPrintPost(lString, lang.tl(L"Bomtid"), boldLarge, pos.get("missed", scale), 10)); - li.addListPost(oPrintPost(lRunnerMissedTime, L"", fontLarge, pos.get("missed", scale), vspace)); + li.addListPost(oPrintPost(lRunnerLostTime, L"", fontLarge, pos.get("missed", scale), vspace)); } if (li.lp.showInterTimes) { li.addSubListPost(oPrintPost(lPunchNamedTime, L"", normalText, 0, 0, make_pair(1, true))); li.subListPost.back().fixedWidth = 160; - li.listSubType = li.EBaseTypePunches; + li.listSubType = li.EBaseTypeCoursePunches; } else if (li.lp.showSplitTimes) { li.addSubListPost(oPrintPost(lPunchTime, L"", normalText, 0, 0, make_pair(1, true))); li.subListPost.back().fixedWidth = 95; - li.listSubType = li.EBaseTypePunches; + li.listSubType = li.EBaseTypeCoursePunches; } li.setFilter(EFilterHasResult); @@ -4204,7 +4637,7 @@ void oEvent::generateListInfoAux(oListParam &par, int lineHeight, oListInfo &li, if (li.lp.splitAnalysis || li.lp.showInterTimes) { li.addSubListPost(oPrintPost(lRogainingPunch, L"", normalText, 10, 0, make_pair(1, true))); li.subListPost.back().fixedWidth=130; - li.listSubType=li.EBaseTypePunches; + li.listSubType=li.EBaseTypeCoursePunches; } li.addSubHead(oPrintPost(lClassName, L"", boldText, pos.get("place"), 10)); diff --git a/code/oListInfo.h b/code/oListInfo.h index 60a8445..4e24f44 100644 --- a/code/oListInfo.h +++ b/code/oListInfo.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -66,6 +66,7 @@ enum EPostType lPatrolClubNameNames, // Single runner's club or combination of patrol clubs lRunnerFinish, lRunnerTime, + lRunnerGrossTime, lRunnerTimeStatus, lRunnerTotalTime, lRunnerTimePerKM, @@ -82,9 +83,10 @@ enum EPostType lRunnerGeneralPlace, lRunnerGeneralTimeAfter, lRunnerTimeAfter, - lRunnerMissedTime, + lRunnerLostTime, lRunnerPlace, lRunnerStart, + lRunnerCheck, lRunnerStartCond, lRunnerStartZero, lRunnerClub, @@ -116,6 +118,7 @@ enum EPostType lRunnerPayMethod, lRunnerEntryDate, lRunnerEntryTime, + lRunnerId, lTeamName, lTeamStart, @@ -134,6 +137,7 @@ enum EPostType lTeamPointAdjustment, lTeamTime, + lTeamGrossTime, lTeamStatus, lTeamClub, lTeamRunner, @@ -150,6 +154,9 @@ enum EPostType lTeamPlaceDiff, lPunchNamedTime, + lPunchNamedSplit, + lPunchName, + lPunchTime, lPunchControlNumber, lPunchControlCode, @@ -157,6 +164,12 @@ enum EPostType lPunchControlPlace, lPunchControlPlaceAcc, + lPunchSplitTime, + lPunchTotalTime, + lPunchTotalTimeAfter, + lPunchAbsTime, + lPunchTimeSinceLast, + lResultModuleTime, lResultModuleNumber, lResultModuleTimeTeam, @@ -180,6 +193,8 @@ enum EPostType lTotalCounter, lSubCounter, lSubSubCounter, + + lLineBreak, lLastItem }; @@ -241,6 +256,8 @@ enum EFilterList EFilterExcludeCANCEL, EFilterVacant, EFilterOnlyVacant, + EFilterAnyResult, // With any (radio) punch on a leg + EFilterAPIEntry, // Entry via API _EFilterMax }; @@ -252,6 +269,8 @@ enum ESubFilterList ESubFilterVacant, ESubFilterSameParallel, ESubFilterSameParallelNotFirst, + ESubFilterNotFinish, + ESubFilterNamedControl, _ESubFilterMax }; @@ -303,6 +322,7 @@ struct oListParam { a.useControlIdResultTo == useControlIdResultTo && a.filterMaxPer == filterMaxPer && a.pageBreak == pageBreak && + a.showHeader == showHeader && a.showInterTimes == showInterTimes && a.showSplitTimes == showSplitTimes && a.inputNumber == inputNumber && @@ -311,8 +331,18 @@ struct oListParam { a.bgColor == bgColor && a.bgColor2 == bgColor2 && a.bgImage == bgImage && - a.legNumber == legNumber; + a.legNumber == legNumber && + a.nColumns == nColumns && + a.timePerPage == timePerPage && + a.screenMode == screenMode && + a.animate == animate && + a.htmlRows == htmlRows && + a.htmlScale == htmlScale && + a.htmlTypeTag == htmlTypeTag; } + + wstring getContentsDescriptor(const oEvent &oe) const; + EStdListType listCode; GUICALLBACK cb; set selection; @@ -323,6 +353,7 @@ struct oListParam { int useControlIdResultFrom; int filterMaxPer; bool pageBreak; + bool showHeader = true; bool showInterTimes; bool showSplitTimes; bool splitAnalysis; @@ -333,9 +364,10 @@ struct oListParam { int nextList; // 1-based index of next list (in the container, MetaListParam::listParam) for linked lists int previousList; // 1-based index of previous list (in the container, MetaListParam::listParam) for linked lists. Not serialized + mutable bool lineBreakControlList = false; mutable int relayLegIndex; // Current index of leg (or -1 for entire team) mutable wstring defaultName; // Initialized when generating list - // Generate a large-size list (supported as input when supportLarge is true) + // generate a large-size list (supported as input when supportLarge is true) bool useLargeSize; bool saved; @@ -351,6 +383,10 @@ struct oListParam { int margin; int screenMode;// 0 normal window, 1 = page by page, 2 = scroll + int htmlRows; + double htmlScale; + string htmlTypeTag; // free, table, or template tag. + void updateDefaultName(const wstring &pname) const {defaultName = pname;} void setCustomTitle(const wstring &t) {title = t;} void getCustomTitle(wchar_t *t) const; // 256 size buffer required. Get title if set @@ -384,6 +420,8 @@ struct oListParam { return legNumber >= 0 ? legNumber : 1000; } + int sourceParam = -1; + private: int legNumber; }; @@ -393,7 +431,8 @@ public: enum EBaseType {EBaseTypeRunner, EBaseTypeTeam, EBaseTypeClub, - EBaseTypePunches, + EBaseTypeCoursePunches, + EBaseTypeAllPunches, EBaseTypeNone, EBaseTypeRunnerGlobal, // Used only in metalist (meaning global, not classwise) EBaseTypeRunnerLeg, // Used only in metalist, meaning legwise @@ -411,10 +450,21 @@ public: Coursewise }; + enum class PunchMode { + NoPunch, + SpecificPunch, + AnyPunch + }; static bool addRunners(EBaseType t) {return t == EBaseTypeRunner || t == EBaseTypeClub;} static bool addTeams(EBaseType t) {return t == EBaseTypeTeam || t == EBaseTypeClub;} static bool addPatrols(EBaseType t) {return t == EBaseTypeTeam || t == EBaseTypeClub;} + // Return true if the runner should be skipped + bool filterRunner(const oRunner &r) const; + bool filterRunnerResult(GeneralResult *gResult, const oRunner &r) const; + + GeneralResult *applyResultModule(oEvent &oe, vector &rlist) const; + const wstring &getName() const {return Name;} protected: wstring Name; @@ -426,6 +476,7 @@ protected: bool calcCourseClassResults; bool calcTotalResults; bool rogainingResults; + bool calculateLiveResults; oListParam lp; @@ -436,7 +487,8 @@ protected: vector listPostSubFilter; list subListPost; bool fixedType; - bool needPunches; + + PunchMode needPunches; string resultModule; set additionalModules; @@ -469,10 +521,13 @@ public: ResultType resType; - bool needPunchCheck() const {return needPunches;} + PunchMode needPunchCheck() const {return needPunches;} void setCallback(GUICALLBACK cb); int getLegNumberCoded() const {return lp.getLegNumberCoded();} + bool supportUpdateClasses() const { + return supportClasses && next.empty(); + } EStdListType getListCode() const {return lp.listCode;} oPrintPost &addHead(const oPrintPost &pp) { diff --git a/code/oPunch.cpp b/code/oPunch.cpp index 3b8347d..abca535 100644 --- a/code/oPunch.cpp +++ b/code/oPunch.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -130,6 +130,11 @@ int oPunch::getAdjustedTime() const } void oPunch::setTime(const wstring &t) { + if (convertAbsoluteTimeHMS(t, -1) <= 0) { + setTimeInt(-1, false); + return; + } + int tt = oe->getRelativeTime(t)-tTimeAdjust; if (tt < 0) tt = 0; diff --git a/code/oPunch.h b/code/oPunch.h index 6d25a07..52f8128 100644 --- a/code/oPunch.h +++ b/code/oPunch.h @@ -2,17 +2,12 @@ // ////////////////////////////////////////////////////////////////////// -#if !defined(AFX_OPUNCH_H__67B23AF5_5783_4A6A_BB2E_E522B9283A42__INCLUDED_) -#define AFX_OPUNCH_H__67B23AF5_5783_4A6A_BB2E_E522B9283A42__INCLUDED_ - -#if _MSC_VER > 1000 #pragma once -#endif // _MSC_VER > 1000 #include "oBase.h" /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,7 +48,7 @@ protected: //Adjustment of this punch, loaded from control int tTimeAdjust; - volatile int tCardIndex; // Index into card + int tCardIndex = -1; // Index into card int tIndex; // Control match index in course int tMatchControlId; bool hasBeenPlayed; @@ -63,9 +58,11 @@ protected: int getDISize() const {return -1;} void changedObject(); + mutable int previousPunchTime; /// Note that this is not valid in general + public: - virtual int getControlId() {return tMatchControlId;} + virtual int getControlId() const {return tMatchControlId;} bool isUsedInCourse() const {return isUsed;} void remove(); @@ -106,8 +103,7 @@ public: friend class oRunner; friend class oTeam; friend class oEvent; + friend class oListInfo; }; typedef oPunch * pPunch; - -#endif // !defined(AFX_OPUNCH_H__67B23AF5_5783_4A6A_BB2E_E522B9283A42__INCLUDED_) diff --git a/code/oReport.cpp b/code/oReport.cpp index 6093e69..b63566e 100644 --- a/code/oReport.cpp +++ b/code/oReport.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -414,7 +414,7 @@ void oEvent::generatePreReport(gdioutput &gdi) { deque si_duplicate; if (Runners.size()>1){ - Runners.sort(oRunner::CompareSINumber); + Runners.sort(oRunner::CompareCardNumber); map > initDup; r_it=Runners.begin(); @@ -473,20 +473,22 @@ void oEvent::generatePreReport(gdioutput &gdi) { } if (!no_class.empty()) gdi.addStringUT(1, Ellipsis); - gdi.dropLine(); - swprintf_s(bf, lang.tl("Löpare utan bana: %d.").c_str(), no_course.size()); - gdi.addStringUT(1, bf); - i=0; + if (getMeOSFeatures().withCourses(this)) { + gdi.dropLine(); + swprintf_s(bf, lang.tl("Löpare utan bana: %d.").c_str(), no_course.size()); + gdi.addStringUT(1, bf); + i = 0; - while(!no_course.empty() && ++i<20){ - pRunner r=no_course.front(); - no_course.pop_front(); - wstring name = r->getClass(true) + L": " + r->getName(); - if (!r->getClub().empty()) - name += L" ("+ r->getClub()+ L")"; - gdi.addStringUT(0, name); + while (!no_course.empty() && ++i < 20) { + pRunner r = no_course.front(); + no_course.pop_front(); + wstring name = r->getClass(true) + L": " + r->getName(); + if (!r->getClub().empty()) + name += L" (" + r->getClub() + L")"; + gdi.addStringUT(0, name); + } + if (!no_course.empty()) gdi.addStringUT(1, Ellipsis); } - if (!no_course.empty()) gdi.addStringUT(1, Ellipsis); if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { gdi.dropLine(); diff --git a/code/oRunner.cpp b/code/oRunner.cpp index 0eb8f61..633edad 100644 --- a/code/oRunner.cpp +++ b/code/oRunner.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,6 +45,7 @@ #include "MeOSFeatures.h" #include "oListInfo.h" #include "qualification_final.h" +#include "metalist.h" oRunner::RaceIdFormatter oRunner::raceIdFormatter; @@ -107,27 +108,27 @@ void oAbstractRunner::setStartTimeS(const wstring &t) setStartTime(oe->getRelativeTime(t), true, false); } -oRunner::oRunner(oEvent *poe):oAbstractRunner(poe, false) +oRunner::oRunner(oEvent *poe) :oAbstractRunner(poe, false) { isTemporaryObject = false; - Id=oe->getFreeRunnerId(); - Course=0; - StartNo=0; - CardNo=0; + Id = oe->getFreeRunnerId(); + Course = 0; + StartNo = 0; + cardNumber = 0; - tInTeam=0; - tLeg=0; + tInTeam = 0; + tLeg = 0; tLegEquClass = 0; - tNeedNoCard=false; - tUseStartPunch=true; + tNeedNoCard = false; + tUseStartPunch = true; getDI().initData(); correctionNeeded = false; - tDuplicateLeg=0; - tParentRunner=0; + tDuplicateLeg = 0; + tParentRunner = 0; - Card=0; - cPriority=0; + Card = 0; + cPriority = 0; tCachedRunningTime = 0; tSplitRevision = -1; @@ -143,28 +144,28 @@ oRunner::oRunner(oEvent *poe):oAbstractRunner(poe, false) tNumShortening = 0; } -oRunner::oRunner(oEvent *poe, int id):oAbstractRunner(poe, true) +oRunner::oRunner(oEvent *poe, int id) :oAbstractRunner(poe, true) { isTemporaryObject = false; - Id=id; + Id = id; oe->qFreeRunnerId = max(id, oe->qFreeRunnerId); - Course=0; - StartNo=0; - CardNo=0; + Course = 0; + StartNo = 0; + cardNumber = 0; - tInTeam=0; - tLeg=0; + tInTeam = 0; + tLeg = 0; tLegEquClass = 0; - tNeedNoCard=false; - tUseStartPunch=true; + tNeedNoCard = false; + tUseStartPunch = true; getDI().initData(); correctionNeeded = false; - tDuplicateLeg=0; - tParentRunner=0; + tDuplicateLeg = 0; + tParentRunner = 0; - Card=0; - cPriority=0; + Card = 0; + cPriority = 0; tCachedRunningTime = 0; tSplitRevision = -1; @@ -212,7 +213,7 @@ bool oRunner::Write(xmlparser &xml) xml.write("Start", startTime); xml.write("Finish", FinishTime); xml.write("Status", status); - xml.write("CardNo", CardNo); + xml.write("CardNo", cardNumber); xml.write("StartNo", StartNo); xml.write("InputPoint", inputPoints); @@ -249,39 +250,39 @@ void oRunner::Set(const xmlobject &xo) xo.getObjects(xl); xmlList::const_iterator it; - for(it=xl.begin(); it != xl.end(); ++it){ - if (it->is("Id")){ + for (it = xl.begin(); it != xl.end(); ++it) { + if (it->is("Id")) { Id = it->getInt(); } - else if (it->is("Name")){ + else if (it->is("Name")) { sName = it->getw(); getRealName(sName, tRealName); } - else if (it->is("Start")){ + else if (it->is("Start")) { tStartTime = startTime = it->getInt(); } - else if (it->is("Finish")){ + else if (it->is("Finish")) { FinishTime = it->getInt(); } - else if (it->is("Status")){ + else if (it->is("Status")) { unsigned rawStat = it->getInt(); - tStatus = status = RunnerStatus(rawStat<100u ? rawStat : 0); + tStatus = status = RunnerStatus(rawStat < 100u ? rawStat : 0); } - else if (it->is("CardNo")){ - CardNo=it->getInt(); + else if (it->is("CardNo")) { + cardNumber = it->getInt(); } else if (it->is("StartNo") || it->is("OrderId")) - StartNo=it->getInt(); + StartNo = it->getInt(); else if (it->is("Club")) - Club=oe->getClub(it->getInt()); + Club = oe->getClub(it->getInt()); else if (it->is("Class")) - Class=oe->getClass(it->getInt()); + Class = oe->getClass(it->getInt()); else if (it->is("Course")) - Course=oe->getCourse(it->getInt()); - else if (it->is("Card")){ - Card=oe->allocateCard(this); + Course = oe->getCourse(it->getInt()); + else if (it->is("Card")) { + Card = oe->allocateCard(this); Card->Set(*it); - assert(Card->getId()!=0); + assert(Card->getId() != 0); } else if (it->is("oData")) getDI().set(*it); @@ -294,7 +295,7 @@ void oRunner::Set(const xmlobject &xo) } else if (it->is("InputStatus")) { unsigned rawStat = it->getInt(); - inputStatus = RunnerStatus(rawStat<100u ? rawStat : 0); + inputStatus = RunnerStatus(rawStat < 100u ? rawStat : 0); } else if (it->is("InputPoint")) { inputPoints = it->getInt(); @@ -419,9 +420,11 @@ void oAbstractRunner::setClassId(int id, bool isManualUpdate) { } // Update all classes (for multirunner) -void oRunner::setClassId(int id, bool isManualUpdate) -{ - if (Class && Class->getQualificationFinal() && isManualUpdate) { +void oRunner::setClassId(int id, bool isManualUpdate) { + pClass nPc = id>0 ? oe->getClass(id) : 0; + if (Class == nPc) + return; + if (Class && Class->getQualificationFinal() && isManualUpdate && nPc && nPc->parentClass == Class) { int heat = Class->getQualificationFinal()->getHeatFromClass(id, Class->getId()); if (heat >= 0) { int oldHeat = getDI().getInt("Heat"); @@ -433,6 +436,7 @@ void oRunner::setClassId(int id, bool isManualUpdate) oldHeatClass->clearCache(true); newHeatClass->clearCache(true); tSplitRevision = 0; + apply(false, 0, false); } } return; @@ -444,8 +448,7 @@ void oRunner::setClassId(int id, bool isManualUpdate) } else { pClass pc = Class; - pClass nPc = id>0 ? oe->getClass(id):0; - + if (pc && pc->isSingleRunnerMultiStage() && nPc!=pc && tInTeam) { if (!isTemporaryObject) { oe->autoRemoveTeam(this); @@ -519,7 +522,7 @@ void oRunner::setCourseId(int id) if (Course!=pc) { updateChanged(); if (Class) - Class->clearSplitAnalysis(); + getClassRef(true)->clearSplitAnalysis(); tSplitRevision = 0; } } @@ -622,7 +625,7 @@ const wstring &oAbstractRunner::getFinishTimeS() const } int oAbstractRunner::getRunningTime() const { - int rt = max(FinishTime-tStartTime, 0); + int rt = FinishTime-tStartTime; if (rt > 0) return getTimeAdjustment() + rt; else @@ -652,14 +655,19 @@ int oRunner::getTotalRunningTime() const { } -const wstring &oAbstractRunner::getStatusS() const +const wstring &oAbstractRunner::getStatusS(bool formatForPrint) const { + if (formatForPrint && tStatus == StatusUnknown) + return formatTime(-1); return oEvent::formatStatus(tStatus); } -const wstring &oAbstractRunner::getTotalStatusS() const +const wstring &oAbstractRunner::getTotalStatusS(bool formatForPrint) const { - return oEvent::formatStatus(getTotalStatus()); + auto ts = getTotalStatus(); + if (formatForPrint && ts == StatusUnknown) + return formatTime(-1); + return oEvent::formatStatus(ts); } /* @@ -724,14 +732,16 @@ void oRunner::addPunches(pCard card, vector &missingPunches) { if (card) { if (card->cardNo > 0) - CardNo=card->cardNo; + setCardNo(card->cardNo, false, true); //315422 assert(card->tOwner==0 || card->tOwner==this); } // Auto-select shortening pCourse mainCourse = getCourse(false); + int shortenLevel = 0; + if (mainCourse && Card) { - pCourse shortVersion = mainCourse->getShorterVersion(); + pCourse shortVersion = mainCourse->getShorterVersion().second; if (shortVersion) { //int s = mainCourse->getStartPunchType(); //int f = mainCourse->getFinishPunchType(); @@ -741,15 +751,14 @@ void oRunner::addPunches(pCard card, vector &missingPunches) { SICard sic(ConvertedTimeStatus::Unknown); Card->getSICard(sic); - int level = 0; while (mainCourse->distance(sic) < 0 && abs(numCtrl-numCtrlShort) < abs(numCtrl-numCtrlLong)) { - level++; + shortenLevel++; if (shortVersion->distance(sic) >= 0) { - setNumShortening(level); // We passed at some level + setNumShortening(shortenLevel); // We passed at some level break; } mainCourse = shortVersion; - shortVersion = mainCourse->getShorterVersion(); + shortVersion = mainCourse->getShorterVersion().second; numCtrlLong = numCtrlShort; if (!shortVersion) { break; @@ -758,6 +767,13 @@ void oRunner::addPunches(pCard card, vector &missingPunches) { } } } + if (mainCourse && mainCourse->getCommonControl() != 0 && mainCourse->getShorterVersion().first) { + oCourse tmpCourse(oe); + int numShorten; + mainCourse->getAdapetedCourse(*Card, tmpCourse, numShorten); + setNumShortening(shortenLevel + numShorten); + } + if (Card) Card->tOwner=this; @@ -875,7 +891,7 @@ pCourse oRunner::getCourse(bool useAdaptedCourse) const { int ns = getNumShortening(); pCourse shortCrs = tCrs; while (ns > 0 && shortCrs) { - shortCrs = shortCrs->getShorterVersion(); + shortCrs = shortCrs->getShorterVersion().second; if (shortCrs) tCrs = shortCrs; ns--; @@ -889,7 +905,8 @@ pCourse oRunner::getCourse(bool useAdaptedCourse) const { if (!tAdaptedCourse) tAdaptedCourse = new oCourse(oe, -1); - tCrs = tCrs->getAdapetedCourse(*Card, *tAdaptedCourse); + int numShorten; + tCrs = tCrs->getAdapetedCourse(*Card, *tAdaptedCourse, numShorten); tAdaptedCourseRevision = oe->dataRevision; return tCrs; } @@ -925,7 +942,7 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, int addpunch, bool sync) { if (unsigned(status) >= 100u) status = StatusUnknown; //Reset bad input - + pClass clz = getClassRef(true); MissingPunches.clear(); int oldFT = FinishTime; int oldStartTime; @@ -981,28 +998,26 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if ((inTeam || !tUseStartPunch) && doApply) apply(sync, 0, false); //Post apply. Update start times. - if (storeTimes() && Class && sync) { - set cls; - cls.insert(Class->getId()); - oe->reEvaluateAll(cls, sync); + if (storeTimes() && clz && sync) { + oe->reEvaluateAll({ clz->getId() }, sync); } normalizedSplitTimes.clear(); if (oldTimes.size() > 0 && Class) - Class->clearSplitAnalysis(); + clz->clearSplitAnalysis(); return false; } //Try to match class?! - if (!Class) + if (!clz) return false; - if (Class->ignoreStartPunch()) + if (clz->ignoreStartPunch()) tUseStartPunch = false; const pCourse course = getCourse(true); if (!course) { // Reset rogaining. Store start/finish - for (p_it=Card->punches.begin(); p_it!=Card->punches.end(); ++p_it) { + for (p_it = Card->punches.begin(); p_it != Card->punches.end(); ++p_it) { if (p_it->isStart() && tUseStartPunch) *refStartTime = p_it->Time; else if (p_it->isFinish()) @@ -1012,6 +1027,33 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, apply(sync, 0, false); //Post apply. Update start times. storeTimes(); + // No course mode + int maxTimeStatus = 0; + if (getFinishTime() <= 0) + *refStatus = StatusDNF; + else { + if (clz) { + int mt = clz->getMaximumRunnerTime(); + if (mt>0) { + if (getRunningTime() > mt) + maxTimeStatus = 1; + else + maxTimeStatus = 2; + } + else + maxTimeStatus = 2; + } + + if (*refStatus == StatusMAX && maxTimeStatus == 2) + *refStatus = StatusUnknown; + } + if (*refStatus == StatusUnknown || *refStatus == StatusCANCEL || *refStatus == StatusDNS || *refStatus == StatusMAX) { + if (maxTimeStatus == 1) + *refStatus = StatusMAX; + else + *refStatus = StatusOK; + } + return false; } @@ -1045,15 +1087,13 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if ((inTeam || !tUseStartPunch) && doApply) apply(sync, 0, false); //Post apply. Update start times. - if (storeTimes() && Class && sync) { - set cls; - cls.insert(Class->getId()); - oe->reEvaluateAll(cls, sync); + if (storeTimes() && clz && sync) { + oe->reEvaluateAll({ clz->getId() }, sync); } normalizedSplitTimes.clear(); - if (oldTimes.size() > 0 && Class) - Class->clearSplitAnalysis(); + if (oldTimes.size() > 0 && clz) + clz->clearSplitAnalysis(); tRogainingPoints = max(0, getPointAdjustment()); return false; } @@ -1291,8 +1331,8 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, } int maxTimeStatus = 0; - if (Class && FinishTime>0) { - int mt = Class->getMaximumRunnerTime(); + if (clz && FinishTime>0) { + int mt = clz->getMaximumRunnerTime(); if (mt>0) { if (getRunningTime() > mt) maxTimeStatus = 1; @@ -1387,36 +1427,34 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if (clear) { normalizedSplitTimes.clear(); - if (Class) - Class->clearSplitAnalysis(); + if (clz) + clz->clearSplitAnalysis(); } if (doApply) storeTimes(); - if (Class && sync) { + if (clz && sync) { bool update = false; if (tInTeam) { - int t1 = Class->getTotalLegLeaderTime(tLeg, false); + int t1 = clz->getTotalLegLeaderTime(tLeg, false); int t2 = tInTeam->getLegRunningTime(tLeg, false); if (t2<=t1 && t2>0) update = true; - int t3 = Class->getTotalLegLeaderTime(tLeg, true); + int t3 = clz->getTotalLegLeaderTime(tLeg, true); int t4 = tInTeam->getLegRunningTime(tLeg, true); if (t4<=t3 && t4>0) update = true; } if (!update) { - int t1 = Class->getBestLegTime(tLeg); + int t1 = clz->getBestLegTime(tLeg); int t2 = getRunningTime(); if (t2<=t1 && t2>0) update = true; } if (update) { - set cls; - cls.insert(Class->getId()); - oe->reEvaluateAll(cls, sync); + oe->reEvaluateAll({ clz->getId() }, sync); } } return true; @@ -1427,7 +1465,7 @@ void oRunner::clearOnChangedRunningTime() { tCachedRunningTime = FinishTime - tStartTime; normalizedSplitTimes.clear(); if (Class) - Class->clearSplitAnalysis(); + getClassRef(true)->clearSplitAnalysis(); } } @@ -1496,11 +1534,15 @@ bool oRunner::storeTimes() { bool updated = storeTimesAux(Class); if (tInTeam && tInTeam->Class && tInTeam->Class != Class) updated |= storeTimesAux(tInTeam->Class); - + else if (Class && Class->getQualificationFinal()) { + updated |= storeTimesAux(getClassRef(true)); + } return updated; } bool oRunner::storeTimesAux(pClass targetClass) { + if (!targetClass) + return false; if (tInTeam) { if (tInTeam->getNumShortening(tLeg) > 0) return false; @@ -1582,28 +1624,30 @@ bool oRunner::storeTimesAux(pClass targetClass) { } } else { - if (targetClass && unsigned(tDuplicateLeg)tLeaderTime.size()) { - if (tStatus==StatusOK) { - int &bt=targetClass->tLeaderTime[tDuplicateLeg].bestTimeOnLeg; - int rt=getRunningTime(); - if (rt > 0 && (bt==0 || rtmapLeg(tDuplicateLeg); + if (targetClass && dupLeg < targetClass->tLeaderTime.size()) { + if (tStatus == StatusOK) { + int &bt = targetClass->tLeaderTime[dupLeg].bestTimeOnLeg; + int rt = getRunningTime(); + if (rt > 0 && (bt == 0 || rt < bt)) { + bt = rt; updated = true; } } - int &bt=targetClass->tLeaderTime[tDuplicateLeg].totalLeaderTime; - int rt=getRaceRunningTime(tDuplicateLeg); - if (rt>0 && (bt==0 || rttLeaderTime[dupLeg].totalLeaderTime; + int rt = getRaceRunningTime(dupLeg); + if (rt > 0 && (bt == 0 || rt < bt)) { bt = rt; updated = true; - targetClass->tLeaderTime[tDuplicateLeg].totalLeaderTimeInput = rt; + targetClass->tLeaderTime[dupLeg].totalLeaderTimeInput = rt; } } } + size_t mappedLeg = targetClass->mapLeg(tLeg); // Best input time - if (targetClass && unsigned(tLeg)tLeaderTime.size()) { - int &it = targetClass->tLeaderTime[tLeg].inputTime; + if (mappedLegtLeaderTime.size()) { + int &it = targetClass->tLeaderTime[mappedLeg].inputTime; if (inputTime > 0 && inputStatus == StatusOK && (it == 0 || inputTime < it) ) { it = inputTime; updated = true; @@ -1695,13 +1739,13 @@ bool oRunner::operator<(const oRunner &c) const { const oClass * myClass = getClassRef(true); const oClass * cClass = c.getClassRef(true); if (!myClass || !cClass) - return size_t(myClass)getClassStatus() != oClass::Normal) return CompareString(LOCALE_USER_DEFAULT, 0, tRealName.c_str(), tRealName.length(), c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN; - if (oe->CurrentSortOrder==ClassStartTime) { + if (oe->CurrentSortOrder == ClassStartTime) { if (myClass->Id != cClass->Id) { if (myClass->tSortIndex != cClass->tSortIndex) return myClass->tSortIndex < cClass->tSortIndex; @@ -1725,20 +1769,20 @@ bool oRunner::operator<(const oRunner &c) const { } } } - else if (oe->CurrentSortOrder==ClassResult) { + else if (oe->CurrentSortOrder == ClassResult) { RunnerStatus stat = tStatus == StatusUnknown ? StatusOK : tStatus; RunnerStatus cstat = c.tStatus == StatusUnknown ? StatusOK : c.tStatus; - - if (myClass != cClass) + + if (myClass != cClass) return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); - else if (tLegEquClass != c.tLegEquClass) + else if (tLegEquClass != c.tLegEquClass) return tLegEquClass < c.tLegEquClass; else if (tDuplicateLeg != c.tDuplicateLeg) return tDuplicateLeg < c.tDuplicateLeg; else if (stat != cstat) return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat]; else { - if (stat==StatusOK) { + if (stat == StatusOK) { if (Class->getNoTiming()) { return CompareString(LOCALE_USER_DEFAULT, 0, tRealName.c_str(), tRealName.length(), @@ -1749,15 +1793,15 @@ bool oRunner::operator<(const oRunner &c) const { if (s != cs) return s < cs; - int t=getRunningTime(); - if (t<=0) - t = 3600*100; - int ct=c.getRunningTime(); - if (ct<=0) - ct = 3600*100; + int t = getRunningTime(); + if (t <= 0) + t = 3600 * 100; + int ct = c.getRunningTime(); + if (ct <= 0) + ct = 3600 * 100; - if (t!=ct) - return tgetNoTiming()) { return CompareString(LOCALE_USER_DEFAULT, 0, tRealName.c_str(), tRealName.length(), @@ -1788,19 +1832,19 @@ bool oRunner::operator<(const oRunner &c) const { if (s != cs) return s < cs; - int t=getRunningTime(); - int ct=c.getRunningTime(); - if (t!=ct) - return tCurrentSortOrder==SortByName) { + else if (oe->CurrentSortOrder == SortByName) { return CompareString(LOCALE_USER_DEFAULT, 0, - tRealName.c_str(), tRealName.length(), - c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN; + tRealName.c_str(), tRealName.length(), + c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN; } - else if (oe->CurrentSortOrder==SortByLastName) { + else if (oe->CurrentSortOrder == SortByLastName) { wstring a = getFamilyName(); wstring b = c.getFamilyName(); if (a.empty() && !b.empty()) @@ -1820,40 +1864,49 @@ bool oRunner::operator<(const oRunner &c) const { b.c_str(), b.length()) == CSTR_LESS_THAN; } } - else if (oe->CurrentSortOrder==SortByFinishTime) { + else if (oe->CurrentSortOrder == SortByFinishTime) { if (tStatus != c.tStatus) return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus]; else { int ft = getFinishTimeAdjusted(); int cft = c.getFinishTimeAdjusted(); - if (tStatus==StatusOK && ft != cft) + if (tStatus == StatusOK && ft != cft) return ft < cft; } } - else if (oe->CurrentSortOrder==SortByFinishTimeReverse) { + else if (oe->CurrentSortOrder == SortByFinishTimeReverse) { int ft = getFinishTimeAdjusted(); int cft = c.getFinishTimeAdjusted(); if (ft != cft) return ft > cft; } - else if (oe->CurrentSortOrder == ClassFinishTime){ + else if (oe->CurrentSortOrder == ClassFinishTime) { if (myClass != cClass) return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); if (tStatus != c.tStatus) return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus]; - else{ + else { int ft = getFinishTimeAdjusted(); int cft = c.getFinishTimeAdjusted(); - if (tStatus==StatusOK && ft != cft) - return ftCurrentSortOrder==SortByStartTime){ + else if (oe->CurrentSortOrder == SortByStartTime) { if (tStartTime < c.tStartTime) return true; else if (tStartTime > c.tStartTime) return false; } + else if (oe->CurrentSortOrder == SortByStartTimeClass) { + if (tStartTime < c.tStartTime) + return true; + else if (tStartTime > c.tStartTime) + return false; + + if (myClass != cClass) + return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); + } else if (oe->CurrentSortOrder == SortByEntryTime) { auto dci = getDCI(), cdci = c.getDCI(); int ed = dci.getInt("EntryDate"); @@ -1873,17 +1926,17 @@ bool oRunner::operator<(const oRunner &c) const { else if (tStatus != c.tStatus) return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus]; else { - if (tStatus==StatusOK) { + if (tStatus == StatusOK) { if (tRogainingPoints != c.tRogainingPoints) return tRogainingPoints > c.tRogainingPoints; - int t=getRunningTime(); - int ct=c.getRunningTime(); + int t = getRunningTime(); + int ct = c.getRunningTime(); if (t != ct) return t < ct; } } } - else if (oe->CurrentSortOrder==ClassTotalResult) { + else if (oe->CurrentSortOrder == ClassTotalResult) { if (myClass != cClass) return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); else if (tDuplicateLeg != c.tDuplicateLeg) @@ -1902,7 +1955,7 @@ bool oRunner::operator<(const oRunner &c) const { } int t = getTotalRunningTime(FinishTime, true); int ct = c.getTotalRunningTime(c.FinishTime, true); - if (t!=ct) + if (t != ct) return t < ct; } } @@ -1918,17 +1971,17 @@ bool oRunner::operator<(const oRunner &c) const { else if (tStatus != c.tStatus) return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus]; else { - if (tStatus==StatusOK) { - + if (tStatus == StatusOK) { + int s = getNumShortening(); int cs = c.getNumShortening(); if (s != cs) return s < cs; - int t=getRunningTime(); - int ct=c.getRunningTime(); + int t = getRunningTime(); + int ct = c.getRunningTime(); if (t != ct) { - return tCurrentSortOrder==ClassStartTimeClub) { + else if (oe->CurrentSortOrder == ClassStartTimeClub) { if (myClass != cClass) return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); else if (tStartTime != c.tStartTime) { @@ -1958,12 +2011,12 @@ bool oRunner::operator<(const oRunner &c) const { return getClub() < c.getClub(); } } - else if (oe->CurrentSortOrder==ClassTeamLeg) { + else if (oe->CurrentSortOrder == ClassTeamLeg) { if (myClass->Id != cClass->Id) return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); else if (tInTeam != c.tInTeam) { if (tInTeam == 0) - return true; + return true; else if (c.tInTeam == 0) return false; if (tInTeam->StartNo != c.tInTeam->StartNo) @@ -1987,10 +2040,16 @@ bool oRunner::operator<(const oRunner &c) const { return StartNo < c.StartNo; } } - + else if (oe->CurrentSortOrder == ClassLiveResult) { + if (myClass->Id != cClass->Id) + return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); + + if (currentControlTime != c.currentControlTime) + return currentControlTime < c.currentControlTime; + } return CompareString(LOCALE_USER_DEFAULT, 0, - tRealName.c_str(), tRealName.length(), - c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN; + tRealName.c_str(), tRealName.length(), + c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN; } @@ -2057,7 +2116,7 @@ void oRunner::propagateClub() { multiRunner[k]->updateChanged(); } } - if (tInTeam && Class && Class->getNumDistinctRunners() == 1 && tInTeam->getClubRef() != Club) { + if (tInTeam && tInTeam->getClubRef() != Club && ((Class && Class->getNumDistinctRunners() == 1) || tInTeam->getNumAssignedRunners() <= 1)) { tInTeam->Club = Club; tInTeam->updateChanged(); } @@ -2172,15 +2231,19 @@ wstring oRunner::getFamilyName() const void oRunner::setCardNo(int cno, bool matchCard, bool updateFromDatabase) { - if (cno!=CardNo){ - int oldNo = CardNo; - CardNo=cno; + if (cno != getCardNo()) { + int oldNo = getCardNo(); + cardNumber = cno; + + if (oe->cardToRunnerHash && cno != 0 && !isTemporaryObject) { + oe->cardToRunnerHash->emplace(cno, this); + } oFreePunch::rehashPunches(*oe, oldNo, 0); - oFreePunch::rehashPunches(*oe, CardNo, 0); + oFreePunch::rehashPunches(*oe, cardNumber, 0); if (matchCard && !Card) { - pCard c=oe->getCardByNumber(cno); + pCard c = oe->getCardByNumber(cno); if (c && !c->tOwner) { vector mp; @@ -2199,11 +2262,11 @@ bool oRunner::isHiredCard() const { if (tParentRunner && tParentRunner != this) return tParentRunner->isHiredCard(getCardNo()); - return isHiredCard(CardNo); + return isHiredCard(cardNumber); } bool oRunner::isHiredCard(int cno) const { - if (cno == CardNo) + if (cno == getCardNo()) return getDCI().getInt("CardFee") != 0; for (pRunner r : multiRunner) { @@ -2213,30 +2276,29 @@ bool oRunner::isHiredCard(int cno) const { return false; } - int oRunner::setCard(int cardId) { - pCard c=cardId ? oe->getCard(cardId) : 0; - int oldId=0; + pCard c = cardId ? oe->getCard(cardId) : 0; + int oldId = 0; - if (Card!=c) { + if (Card != c) { if (Card) { - oldId=Card->getId(); - Card->tOwner=0; + oldId = Card->getId(); + Card->tOwner = 0; } if (c) { if (c->tOwner) { - pRunner otherR=c->tOwner; - assert(otherR!=this); - otherR->Card=0; + pRunner otherR = c->tOwner; + assert(otherR != this); + otherR->Card = 0; otherR->updateChanged(); otherR->setStatus(StatusUnknown, true, false); otherR->synchronize(true); } - c->tOwner=this; - CardNo=c->cardNo; + c->tOwner = this; + setCardNo(c->cardNo, false, true); } - Card=c; + Card = c; vector mp; evaluateCard(true, mp); updateChanged(); @@ -2334,9 +2396,10 @@ bool oAbstractRunner::setStatus(RunnerStatus st, bool updateSource, bool tmpOnly bool ch = false; if (tStatus!=st) { ch = true; + bool someOK = (st == StatusOK) || (tStatus == StatusOK); tStatus=st; - if (Class) { + if (Class && someOK) { Class->clearCache(recalculate); } } @@ -2376,7 +2439,7 @@ oDataContainer &oRunner::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr void oEvent::getRunners(int classId, int courseId, vector &r, bool sort) { if (sort) { - synchronizeList(oLRunnerId); + synchronizeList(oListId::oLRunnerId); sortRunners(SortByName); } r.clear(); @@ -2397,18 +2460,6 @@ void oEvent::getRunners(int classId, int courseId, vector &r, bool sort } } -void oEvent::getRunnersByCard(int cardNo, vector &r) { - synchronizeList(oLRunnerId); - sortRunners(SortByName); - r.clear(); - - for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { - if (it->getCardNo() == cardNo) - r.push_back(&*it); - } -} - - pRunner oEvent::getRunner(int Id, int stage) const { pRunner value; @@ -2430,208 +2481,203 @@ pRunner oRunner::nextNeedReadout() const { // For a runner in a team, first the team for the card for (size_t k = 0; k < tInTeam->Runners.size(); k++) { pRunner tr = tInTeam->Runners[k]; - if (tr && tr->getCardNo() == CardNo && !tr->Card && !tr->statusOK()) + if (tr && tr->getCardNo() == getCardNo() && !tr->Card && !tr->statusOK()) return tr; } } - if (!Card || Card->cardNo!=CardNo || Card->isConstructedFromPunches()) //-1 means card constructed from punches + if (!Card || Card->cardNo!=getCardNo() || Card->isConstructedFromPunches()) //-1 means card constructed from punches return pRunner(this); for (size_t k=0;kCard || - multiRunner[k]->Card->cardNo!=CardNo)) + multiRunner[k]->Card->cardNo!=getCardNo())) return multiRunner[k]; } - return 0; + return nullptr; } -void oEvent::setupCardHash(bool clear) { - if (clear) { - cardHash.clear(); - } - else { - assert(cardHash.empty()); - for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { - if (it->isRemoved()) +unordered_multimap &oEvent::getCardToRunner() const { + if (!cardToRunnerHash || cardToRunnerHash->size() > Runners.size() * 2) { + cardToRunnerHash = make_shared>(); + for (auto &rc : Runners) { + pRunner r = const_cast(&rc); + int cno = r->getCardNo(); + if (cno == 0 || r->isRemoved()) continue; - if (it->CardNo != 0) - cardHash.insert(make_pair(it->CardNo, &*it)); + + cardToRunnerHash->emplace(cno, r); // The cache is "to large" -> filter is needed when looking into it. } } + return *cardToRunnerHash; } -typedef unordered_multimap::const_iterator hashConstIter; +pRunner oEvent::getRunnerByCardNo(int cardNo, int time, CardLookupProperty prop) const { + auto range = getCardToRunner().equal_range(cardNo); + bool skipDNS = (prop == CardLookupProperty::SkipNoStart || prop == CardLookupProperty::CardInUse); -pRunner oEvent::getRunnerByCardNo(int cardNo, int time, bool onlyWithNoCard, bool ignoreRunnersWithNoStart) const -{ - oRunnerList::const_iterator it; + if (range.first != range.second && std::distance(range.first, range.second) == 1) { + // Single hit + pRunner r = range.first->second; + if (r->isRemoved() || r->getCardNo() != cardNo) + return nullptr; + if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) + return nullptr; + if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting) + return nullptr; + if (prop == CardLookupProperty::ForReadout || prop == CardLookupProperty::CardInUse) + return r->nextNeedReadout(); + + return r; // Only one runner with this card + } vector cand; bool forceRet = false; - if (!onlyWithNoCard && !cardHash.empty()) { - forceRet = true; - pair range = cardHash.equal_range(cardNo); - if (range.first != range.second) { - hashConstIter t = range.first; - ++t; - if (t == range.second) { - pRunner r = range.first->second; - assert(r->getCardNo() == cardNo); - if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) - return 0; - if (r->getStatus() == StatusNotCompetiting) - return 0; - return r; // Only one runner with this card + + for (auto it = range.first; it != range.second; ++it) { + pRunner r = it->second; + if (r->isRemoved() || r->getCardNo() != cardNo) + continue; + + if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) + continue; + + if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting) + continue; + + if (prop == CardLookupProperty::OnlyMainInstance && r->skip()) + continue; + + cand.push_back(r); + } + + if (time <= 0) { //No time specified. Card readout search + pRunner secondTry = nullptr; + pRunner dnsR = nullptr; + for (pRunner r : cand) { + pRunner ret = r->nextNeedReadout(); + if (ret) { + if (ret->getStatus() == StatusDNS || ret->getStatus() == StatusCANCEL || ret->getStatus() == StatusDNF) + dnsR = ret; //Return a DNS runner if there is no better match. + else if (!r->skip()) + return ret; + else if (secondTry == 0 || secondTry->tLeg > ret->tLeg) + secondTry = ret; } } - - for (hashConstIter it = range.first; it != range.second; ++it) { - pRunner r = it->second; - assert(r->getCardNo() == cardNo); - if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) - continue; - if (r->getStatus() == StatusNotCompetiting) - continue; - if (!r->isRemoved()) - cand.push_back(r); - } + if (secondTry) + return secondTry; + if (dnsR) + return dnsR; } else { - if (time <= 0) { //No time specified. Card readout search + pRunner bestR = 0; + const int K = 3600 * 24; + int dist = 10 * K; + for (size_t k = 0; k < cand.size(); k++) { + pRunner r = cand[k]; + if (time <= 0) + return r; // No time specified. + //int start = r->getStartTime(); + //int finish = r->getFinishTime(); + int start = r->getStartTime(); + int finish = r->getFinishTime(); + if (r->getCard()) { + pair cc = r->getCard()->getTimeRange(); + if (cc.first > 0) + start = min(start, cc.first); + if (cc.second > 0) + finish = max(finish, cc.second); + } + start = max(0, start - 3 * 60); // Allow some extra time before start - pRunner secondTry = 0; - //First try runners with no card read or a different card read. - for (it=Runners.begin(); it != Runners.end(); ++it) { - if (it->isRemoved()) - continue; - if (ignoreRunnersWithNoStart && (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL)) - continue; - if (it->getStatus() == StatusNotCompetiting) - continue; - - pRunner ret; - if (it->CardNo == cardNo && (ret = it->nextNeedReadout()) != 0) { - if (!it->skip()) - return ret; - else if (secondTry == 0 || secondTry->tLeg > ret->tLeg) - secondTry = ret; + if (start > 0 && finish > 0 && time >= start && time <= finish) + return r; + int d = 3 * K; + if (start > 0 && finish > 0 && start < finish) { + if (time < start) + d += K + (start - time); + else if (time > finish) + d += K + (time - finish); + } + else { + if (start > 0) { + if (time < start) + d = K + start - time; + else + d = time - start; + } + if (finish > 0) { + if (time > finish) + d += K + time - finish; } } - if (secondTry) - return secondTry; - } - else { - for (it=Runners.begin(); it != Runners.end(); ++it) { - pRunner r = pRunner(&*it); - if (r->CardNo != cardNo || r->isRemoved()) - continue; - if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) - continue; - if (r->getStatus() == StatusNotCompetiting) - continue; - cand.push_back(r); + if (d < dist) { + bestR = r; + dist = d; } } - } - pRunner bestR = 0; - const int K = 3600*24; - int dist = 10*K; - for (size_t k = 0; k < cand.size(); k++) { - pRunner r = cand[k]; - if (time <= 0) - return r; // No time specified. - //int start = r->getStartTime(); - //int finish = r->getFinishTime(); - int start = r->getStartTime(); - int finish = r->getFinishTime(); - if (r->getCard()) { - pair cc = r->getCard()->getTimeRange(); - if (cc.first > 0) - start = min(start, cc.first); - if (cc.second > 0) - finish = max(finish, cc.second); - } - start = max(0, start - 3 * 60); // Allow some extra time before start - if (start > 0 && finish > 0 && time >= start && time <= finish) - return r; - int d = 3*K; - if (start > 0 && finish > 0 && start finish) - d += K + (time-finish); - } - else { - if (start > 0) { - if (time < start) - d = K + start-time; - else - d = time - start; - } - if (finish > 0) { - if (time > finish) - d += K + time - finish; - } - } - if (d < dist) { - bestR = r; - dist = d; + if (bestR != 0 || forceRet) + return bestR; + } + + if (prop != CardLookupProperty::ForReadout && !skipDNS) { + for (pRunner r : cand) { + pRunner rx = r->nextNeedReadout(); + return rx ? rx : r; } } - if (bestR != 0 || forceRet) - return bestR; - - if (!onlyWithNoCard) { - //Then try all runners. - for (it=Runners.begin(); it != Runners.end(); ++it){ - if (ignoreRunnersWithNoStart && (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL)) - continue; - if (it->getStatus() == StatusNotCompetiting) - continue; - if (!it->isRemoved() && it->CardNo==cardNo) { - pRunner r = it->nextNeedReadout(); - return r ? r : pRunner(&*it); - } - } - } - - return 0; + return nullptr; } -void oEvent::getRunnersByCardNo(int cardNo, bool ignoreRunnersWithNoStart, bool skipDuplicates, vector &out) const { +void oEvent::getRunnersByCardNo(int cardNo, bool sortUpdate, CardLookupProperty prop, vector &out) const { out.clear(); - if (!cardHash.empty()) { - pair range = cardHash.equal_range(cardNo); + bool skipDNS = (prop == CardLookupProperty::SkipNoStart || prop == CardLookupProperty::CardInUse); + + if (sortUpdate) + const_cast(this)->synchronizeList(oListId::oLRunnerId); - for (hashConstIter it = range.first; it != range.second; ++it) { + if (cardNo != 0) { + auto range = getCardToRunner().equal_range(cardNo); + for (auto it = range.first; it != range.second; ++it) { pRunner r = it->second; - assert(r->getCardNo() == cardNo); - if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) + if (r->isRemoved() || r->getCardNo() != cardNo) continue; - if (skipDuplicates && r->getRaceNo() != 0) + if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) continue; - if (r->getStatus() == StatusNotCompetiting) + if (prop == CardLookupProperty::OnlyMainInstance && r->getRaceNo() != 0) continue; - if (!r->isRemoved()) - out.push_back(r); + if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting) + continue; + if (prop == CardLookupProperty::ForReadout && r->getCard() && !r->getCard()->isConstructedFromPunches()) + continue; + + out.push_back(r); } } else { - for (oRunnerList::const_iterator it=Runners.begin(); it != Runners.end(); ++it) { - if (it->CardNo != cardNo) + for (auto it=Runners.begin(); it != Runners.end(); ++it) { + pRunner r = const_cast(&*it); + if (r->isRemoved() || r->getCardNo() != cardNo) continue; - if (ignoreRunnersWithNoStart && (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL)) + if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) continue; - if (it->getStatus() == StatusNotCompetiting) + if (prop == CardLookupProperty::OnlyMainInstance && r->getRaceNo() != 0) continue; - if (skipDuplicates && it->getRaceNo() != 0) + if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting) continue; - if (!it->isRemoved()) - out.push_back(pRunner(&*it)); + if (prop == CardLookupProperty::ForReadout && r->getCard() && !r->getCard()->isConstructedFromPunches()) + continue; + + out.push_back(r); } } + + if (sortUpdate) { + const_cast(this)->CurrentSortOrder = SortByName; + sort(out.begin(), out.end(), [](const pRunner &a, const pRunner &b) {return *a < *b; }); + } } int oRunner::getRaceIdentifier() const { @@ -2790,7 +2836,7 @@ const vector< pair > &oEvent::fillRunners(vector< pair > &oEvent::fillRunners(vector< pairskip() || (showAll && !it->isRemoved())) { if (compact) { - swprintf_s(bf, L"%s, %s (%s)", it->getNameAndRace(true).c_str(), - it->getClub().c_str(), - it->getClass(true).c_str()); + const wstring &club = it->getClub(); + if (!club.empty()) { + swprintf_s(bf, L"%s, %s (%s)", it->getNameAndRace(true).c_str(), + club.c_str(), + it->getClass(true).c_str()); + } + else { + swprintf_s(bf, L"%s (%s)", it->getNameAndRace(true).c_str(), + it->getClass(true).c_str()); + } } else { swprintf_s(bf, L"%s\t%s\t%s", it->getNameAndRace(true).c_str(), it->getClass(true).c_str(), it->getClub().c_str()); } - out.push_back(make_pair(bf, it->Id)); + out.emplace_back(bf, it->Id); } } } @@ -2844,7 +2897,7 @@ const vector< pair > &oEvent::fillRunners(vector< pairgetUIName(), it->Id)); else { swprintf_s(bf, L"%s (%s)", it->getUIName().c_str(), it->getClass(true).c_str()); - out.push_back(make_pair(bf, it->Id)); + out.emplace_back(bf, it->Id); } } } @@ -2910,8 +2963,7 @@ void oRunner::createMultiRunner(bool createMaster, bool sync) multiRunner[k]->multiRunnerId.clear(); multiRunner[k]->tDuplicateLeg = k+1; multiRunner[k]->tParentRunner = this; - multiRunner[k]->CardNo=0; - + if (multiRunner[k]->Id != multiRunnerId[k]) markForCorrection(); } @@ -3121,7 +3173,7 @@ void oEvent::generateRunnerTableData(Table &table, oRunner *addRunner) return; } - synchronizeList(oLRunnerId); + synchronizeList(oListId::oLRunnerId); oRunnerList::iterator it; table.reserve(Runners.size()); for (it=Runners.begin(); it != Runners.end(); ++it){ @@ -3184,7 +3236,7 @@ void oRunner::addTableRow(Table &table) const table.set(row++, it, TID_START, getStartTimeS(), true); table.set(row++, it, TID_FINISH, getFinishTimeS(), true); - table.set(row++, it, TID_STATUS, getStatusS(), true, cellSelection); + table.set(row++, it, TID_STATUS, getStatusS(false), true, cellSelection); table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(), false); table.set(row++, it, TID_PLACE, getPlaceS(), false); @@ -3287,7 +3339,7 @@ bool oRunner::inputData(int id, const wstring &input, if (s!=getStatus()) throw std::exception("Status matchar inte data i löparbrickan."); synchronize(true); - output = getStatusS(); + output = getStatusS(false); } break; @@ -3449,7 +3501,7 @@ int oRunner::getPunchTime(int controlNumber, bool normalized) const return getFinishTime() - tStartTime; int ccId = pc->getCourseControlId(controlNumber); - pFreePunch fp = oe->getPunch(Id, ccId, CardNo); + pFreePunch fp = oe->getPunch(Id, ccId, getCardNo()); if (fp) return fp->Time - tStartTime; return -1; @@ -3478,6 +3530,14 @@ bool oAbstractRunner::isVacant() const return vacClub > 0 && getClubId()==vacClub; } +bool oRunner::isAnnonumousTeamMember() const { + wstring anon = lang.tl("N.N."); + if (getNameRaw() == anon && getExtIdentifier() == 0) + return true; + + return false; +} + bool oRunner::needNoCard() const { const_cast(this)->apply(false, 0, false); return tNeedNoCard; @@ -3599,7 +3659,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_set0) { - if (matchNumber(r->StartNo, s_lc) || matchNumber(r->CardNo, s_lc)) { + if (matchNumber(r->StartNo, s_lc) || matchNumber(r->getCardNo(), s_lc)) { matchFilter.insert(id); if (res == 0) res = r; @@ -3634,7 +3694,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_set0) { - if (matchNumber(r->StartNo, s_lc) || matchNumber(r->CardNo, s_lc)) { + if (matchNumber(r->StartNo, s_lc) || matchNumber(r->getCardNo(), s_lc)) { matchFilter.insert(r->Id); if (res == 0) res = r; @@ -3654,7 +3714,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_set0) { - if (matchNumber(r->StartNo, s_lc) || matchNumber(r->CardNo, s_lc)) { + if (matchNumber(r->StartNo, s_lc) || matchNumber(r->getCardNo(), s_lc)) { matchFilter.insert(r->Id); if (res == 0) res = r; @@ -3752,6 +3812,9 @@ const wstring &oAbstractRunner::getBib() const } void oRunner::setBib(const wstring &bib, int bibNumerical, bool updateStartNo, bool tmpOnly) { + if (!tmpOnly && getBib() == bib) + return; + const bool freeBib = !Class || Class->getBibMode() == BibMode::BibFree; if (tParentRunner && !freeBib) @@ -3823,7 +3886,7 @@ void oEvent::analyseDNS(vector &unknown_dns, vector &known_dns known.clear(); for (size_t k=0;kCardNo; + int card = stUnknown[k]->getCardNo(); if (card == 0) unknown.push_back(stUnknown[k]); else { @@ -3849,7 +3912,7 @@ void oEvent::analyseDNS(vector &unknown_dns, vector &known_dns known_dns.clear(); for (size_t k=0;kCardNo; + int card = stDNS[k]->getCardNo(); if (card == 0) unknown_dns.push_back(stDNS[k]); else { @@ -3937,8 +4000,8 @@ void oRunner::printSplits(gdioutput &gdi) const { bool withResult = (oe->getDI().getInt("Analysis") & 4) == 0; const bool wideFormat = oe->getPropertyInt("WideSplitFormat", 0) == 1; const int numCol = 4; - - if (Class && Class->getNoTiming()) { + pClass cls = getClassRef(true); + if (cls && cls->getNoTiming()) { withResult = false; withAnalysis = false; } @@ -3964,7 +4027,13 @@ void oRunner::printSplits(gdioutput &gdi) const { gdi.addStringUT(normal, getClub()); gdi.dropLine(0.5); gdi.addStringUT(normal, lang.tl("Start: ") + getStartTimeS() + lang.tl(", Mål: ") + getFinishTimeS()); - wstring statInfo = lang.tl("Status: ") + getStatusS() + lang.tl(", Tid: ") + getRunningTimeS(); + if (cls && cls->isRogaining()) { + gdi.addStringUT(normal, lang.tl("Poäng: ") + + itow(getRogainingPoints(false)) + + +L" (" + lang.tl("Avdrag: ") + itow(getRogainingReduction()) + L")"); + } + + wstring statInfo = lang.tl("Status: ") + getStatusS(true) + lang.tl(", Tid: ") + getRunningTimeS(); if (withSpeed && pc && pc->getLength() > 0) { int kmt = (getRunningTime() * 1000) / pc->getLength(); statInfo += L" (" + formatTime(kmt) + lang.tl(" min/km") + L")"; @@ -4232,12 +4301,13 @@ void oRunner::printSplits(gdioutput &gdi) const { if (withResult && statusOK()) { gdi.dropLine(0.5); - oe->calculateResults(oEvent::RTClassResult); + oe->calculateResults({ getClassId(true) }, oEvent::ResultType::ClassResult); if (hasInputData()) - oe->calculateResults(oEvent::RTTotalResult); + oe->calculateResults({ getClassId(true) }, oEvent::ResultType::TotalResult); if (tInTeam) oe->calculateTeamResults(tLeg, true); - + if (cls && cls->isRogaining()) + oe->calculateRogainingResults({cls->getId()}); wstring place = oe->formatListString(lRunnerGeneralPlace, pRunner(this), L"%s"); wstring timestatus; if (tInTeam || hasInputData()) { @@ -4267,7 +4337,28 @@ void oRunner::printSplits(gdioutput &gdi) const { oe->getExtraLines("SPExtra", lines); for (size_t k = 0; k < lines.size(); k++) { - gdi.addStringUT(lines[k].second, lines[k].first); + /*wstring ws = lines[k].first, wsOut; + size_t parS = ws.find_first_of('['); + while (parS != wstring::npos) { + size_t parE = ws.find_first_of(']'); + if (parE != wstring::npos && parE > parS) { + wsOut += ws.substr(0, parS); + wstring cmd = ws.substr(parS + 1, parE-parS-1); + ws = ws.substr(parE + 1); + parS = ws.find_first_of('['); + + auto type = MetaList::getTypeFromSymbol(cmd); + if (type == EPostType::lNone) + wsOut += L"{Error: " + cmd + L"}"; + else { + wsOut += oe->formatListString(type, pRunner(this)); + } + } + else + break; // Error + } + wsOut += ws;*/ + gdi.addStringUT(lines[k].second, formatExtraLine(pRunner(this), lines[k].first)); } if (lines.size()>0) gdi.dropLine(0.5); @@ -4327,7 +4418,7 @@ void oRunner::printStartInfo(gdioutput &gdi) const { oe->getExtraLines("EntryExtra", lines); for (size_t k = 0; k < lines.size(); k++) { - gdi.addStringUT(lines[k].second, lines[k].first); + gdi.addStringUT(lines[k].second, formatExtraLine(pRunner(this), lines[k].first)); } if (lines.size()>0) gdi.dropLine(0.5); @@ -4574,9 +4665,12 @@ void oRunner::getSplitAnalysis(vector &deltaTimes) const { if (splitTimes.empty() || !Class) return; + pClass cls = getClassRef(true); - if (Class->tSplitRevision == tSplitRevision) + if (cls->tSplitRevision == tSplitRevision) { deltaTimes = tMissedTime; + return; + } pCourse pc = getCourse(true); if (!pc) @@ -4591,11 +4685,10 @@ void oRunner::getSplitAnalysis(vector &deltaTimes) const { } int id = pc->getId(); + if (cls->tSplitAnalysisData.count(id) == 0) + cls->calculateSplits(); - if (Class->tSplitAnalysisData.count(id) == 0) - Class->calculateSplits(); - - const vector &baseLine = Class->tSplitAnalysisData[id]; + const vector &baseLine = cls->tSplitAnalysisData[id]; const unsigned nc = pc->getNumControls(); if (baseLine.size() != nc+1) @@ -4662,25 +4755,28 @@ void oRunner::getSplitAnalysis(vector &deltaTimes) const { double delta = part - baseLine[reorder[k]] / bestTime; int deltaAbs = int(floor(delta * resSum + 0.5)); - if (deltaTimes[k]==0 && fabs(delta) > 1.0/100 && deltaAbs>=15) + if (deltaTimes[k]==0 && fabs(delta) > 1.0/100 && deltaAbs>=8) deltaTimes[k] = deltaAbs; } } } -void oRunner::getLegPlaces(vector &places) const -{ +void oRunner::getLegPlaces(vector &places) const { places.clear(); pCourse pc = getCourse(true); if (!pc || !Class || splitTimes.empty()) return; - if (Class->tSplitRevision == tSplitRevision) + pClass cls = getClassRef(true); + + if (cls->tSplitRevision == tSplitRevision) { places = tPlaceLeg; + return; + } int id = pc->getId(); - if (Class->tSplitAnalysisData.count(id) == 0) - Class->calculateSplits(); + if (cls->tSplitAnalysisData.count(id) == 0) + cls->calculateSplits(); const unsigned nc = pc->getNumControls(); @@ -4697,7 +4793,7 @@ void oRunner::getLegPlaces(vector &places) const int time = getSplitTime(k, false); if (time>0) - places[k] = Class->getLegPlace(from, to, time); + places[k] = cls->getLegPlace(from, to, time); else places[k] = 0; } @@ -4708,7 +4804,8 @@ void oRunner::getLegTimeAfter(vector ×) const times.clear(); if (splitTimes.empty() || !Class) return; - if (Class->tSplitRevision == tSplitRevision) { + pClass cls = getClassRef(true); + if (cls->tSplitRevision == tSplitRevision) { times = tAfterLeg; return; } @@ -4719,12 +4816,12 @@ void oRunner::getLegTimeAfter(vector ×) const int id = pc->getId(); - if (Class->tCourseLegLeaderTime.count(id) == 0) - Class->calculateSplits(); + if (cls->tCourseLegLeaderTime.count(id) == 0) + cls->calculateSplits(); const unsigned nc = pc->getNumControls(); - const vector leaders = Class->tCourseLegLeaderTime[id]; + const vector leaders = cls->tCourseLegLeaderTime[id]; if (leaders.size() != nc + 1) return; @@ -4746,7 +4843,7 @@ void oRunner::getLegTimeAfter(vector ×) const const vector &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { vector orderedTimes(times.size()); - for (size_t k = 0; k < times.size(); k++) { + for (size_t k = 0; k < min(reorder.size(), times.size()); k++) { orderedTimes[k] = times[reorder[k]]; } times.swap(orderedTimes); @@ -4758,21 +4855,23 @@ void oRunner::getLegTimeAfterAcc(vector ×) const times.clear(); if (splitTimes.empty() || !Class || tStartTime<=0) return; - if (Class->tSplitRevision == tSplitRevision) + pClass cls = getClassRef(true); + if (cls->tSplitRevision == tSplitRevision) { times = tAfterLegAcc; - + return; + } pCourse pc = getCourse(false); //XXX Does not work for loop courses if (!pc) return; int id = pc->getId(); - if (Class->tCourseAccLegLeaderTime.count(id) == 0) - Class->calculateSplits(); + if (cls->tCourseAccLegLeaderTime.count(id) == 0) + cls->calculateSplits(); const unsigned nc = pc->getNumControls(); - const vector leaders = Class->tCourseAccLegLeaderTime[id]; + const vector leaders = cls->tCourseAccLegLeaderTime[id]; const vector &sp = getSplitTimes(true); if (leaders.size() != nc + 1) return; @@ -4799,7 +4898,7 @@ void oRunner::getLegTimeAfterAcc(vector ×) const const vector &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { vector orderedTimes(times.size()); - for (size_t k = 0; k < times.size(); k++) { + for (size_t k = 0; k < min(reorder.size(), times.size()); k++) { orderedTimes[k] = times[reorder[k]]; } times.swap(orderedTimes); @@ -4814,7 +4913,8 @@ void oRunner::getLegPlacesAcc(vector &places) const return; if (splitTimes.empty() || tStartTime<=0) return; - if (Class->tSplitRevision == tSplitRevision) { + pClass cls = getClassRef(true); + if (cls->tSplitRevision == tSplitRevision) { places = tPlaceLegAcc; return; } @@ -4834,7 +4934,7 @@ void oRunner::getLegPlacesAcc(vector &places) const int time = s - tStartTime; if (time>0) - places[k] = Class->getAccLegPlace(id, k, time); + places[k] = cls->getAccLegPlace(id, k, time); else places[k] = 0; } @@ -4843,8 +4943,8 @@ void oRunner::getLegPlacesAcc(vector &places) const // Normalized order const vector &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { - vector orderedPlaces(places.size()); - for (size_t k = 0; k < places.size(); k++) { + vector orderedPlaces(reorder.size()); + for (size_t k = 0; k < reorder.size(); k++) { orderedPlaces[k] = places[reorder[k]]; } places.swap(orderedPlaces); @@ -4855,15 +4955,19 @@ void oRunner::setupRunnerStatistics() const { if (!Class) return; - if (Class->tSplitRevision == tSplitRevision) + pClass cls = getClassRef(true); + + if (cls->tSplitRevision == tSplitRevision) return; + if (Card) + tOnCourseResults.clear(); getSplitAnalysis(tMissedTime); getLegPlaces(tPlaceLeg); getLegTimeAfter(tAfterLeg); getLegPlacesAcc(tPlaceLegAcc); - getLegPlacesAcc(tAfterLegAcc); - tSplitRevision = Class->tSplitRevision; + getLegTimeAfterAcc(tAfterLegAcc); + tSplitRevision = cls->tSplitRevision; } int oRunner::getMissedTime(int ctrlNo) const { @@ -4911,6 +5015,13 @@ int oRunner::getLegTimeAfter(int ctrlNo) const { } int oRunner::getLegPlaceAcc(int ctrlNo) const { + for (auto &res : tOnCourseResults) { + if (res.controlIx == ctrlNo) + return res.place; + } + if (!Card) { + return 0; + } setupRunnerStatistics(); if (unsigned(ctrlNo) < tPlaceLegAcc.size()) return tPlaceLegAcc[ctrlNo]; @@ -4919,6 +5030,12 @@ int oRunner::getLegPlaceAcc(int ctrlNo) const { } int oRunner::getLegTimeAfterAcc(int ctrlNo) const { + for (auto &res : tOnCourseResults) { + if (res.controlIx == ctrlNo) + return res.after; + } + if (!Card) + return -1; setupRunnerStatistics(); if (unsigned(ctrlNo) < tAfterLegAcc.size()) return tAfterLegAcc[ctrlNo]; @@ -4948,9 +5065,7 @@ pRunner oRunner::getMatchedRunner(const SICard &sic) const { return pRunner(this); if (!Class) return pRunner(this); - if (Class->getLegType(tLeg) != LTExtra) - return pRunner(this); - + const vector &multiV = tParentRunner ? tParentRunner->multiRunner : multiRunner; vector multiOrdered; @@ -4964,11 +5079,13 @@ pRunner oRunner::getMatchedRunner(const SICard &sic) const { if (!multiOrdered[k] || multiOrdered[k]->Card || multiOrdered[k]->getStatus() != StatusUnknown) continue; - if (Class->getLegType(multiOrdered[k]->tLeg) != LTExtra) + LegTypes lt = Class->getLegType(multiOrdered[k]->tLeg); + StartTypes st = Class->getStartType(multiOrdered[k]->tLeg); + + if (lt == LTNormal || lt == LTParallel || st==STChange || st == STHunting) return pRunner(this); vector crs; - if (Class->hasCoursePool()) { Class->getCourses(multiOrdered[k]->tLeg, crs); } @@ -5228,7 +5345,7 @@ void oRunner::init(const RunnerWDBEntry &dbr) { setTemporary(); dbr.getName(sName); getRealName(sName, tRealName); - CardNo = dbr.dbe().cardNo; + cardNumber = dbr.dbe().cardNo; Club = oe->getRunnerDatabase().getClub(dbr.dbe().clubNo); getDI().setString("Nationality", dbr.getNationality()); getDI().setInt("BirthYear", dbr.getBirthYear()); @@ -5328,7 +5445,8 @@ const vector &oRunner::getSplitTimes(bool normalized) const { return splitTimes; else { pCourse pc = getCourse(true); - if (pc && pc->isAdapted() && splitTimes.size() == pc->nControls) { + if (pc && pc->isAdapted() && splitTimes.size() == pc->nControls + && getCourse(false)->nControls == pc->nControls) { if (!normalizedSplitTimes.empty()) return normalizedSplitTimes; const vector &mapToOriginal = pc->getMapToOriginalOrder(); @@ -5489,7 +5607,7 @@ const wstring &oAbstractRunner::TempResult::getStatusS(RunnerStatus inputStatus) if (inputStatus == StatusOK) return oEvent::formatStatus(getStatus()); else if (inputStatus == StatusUnknown) - return oEvent::formatStatus(StatusUnknown); + return formatTime(-1); else return oEvent::formatStatus(max(inputStatus, getStatus())); } @@ -5658,6 +5776,8 @@ bool oRunner::startTimeAvailable() const { int oRunner::getRanking() const { int rank = getDCI().getInt("Rank"); + if (rank == 0 && tParentRunner) + rank = tParentRunner->getRanking(); if (rank <= 0) return MaxRankingConstant; else @@ -5675,12 +5795,17 @@ void oAbstractRunner::hasManuallyUpdatedTimeStatus() { } bool oRunner::canShareCard(const pRunner other, int newCardNo) const { - if (!other || other->CardNo != newCardNo || newCardNo == 0) + if (!other || other->getCardNo() != newCardNo || newCardNo == 0) return true; if (getCard() && getCard()->getCardNo() == newCardNo) return true; + + if (other->getStatus() == StatusDNF || other->getStatus() == StatusCANCEL + || other->getStatus() == StatusNotCompetiting || other->getStatus() == StatusDNS) + return true; + if (other->skip() || other->getCard() || other == this || other->getMultiRunner(0) == getMultiRunner(0)) return true; @@ -5752,3 +5877,53 @@ const wstring &oAbstractRunner::getClass(bool virtualClass) const { else return _EmptyWString; } + +wstring oRunner::formatExtraLine(pRunner r, const wstring &input) { + wstring ws = input, wsOut; + size_t parS = ws.find_first_of('['); + while (parS != wstring::npos) { + size_t parE = ws.find_first_of(']'); + if (parE != wstring::npos && parE > parS) { + wsOut += ws.substr(0, parS); + wstring cmd = ws.substr(parS + 1, parE - parS - 1); + ws = ws.substr(parE + 1); + parS = ws.find_first_of('['); + + auto type = MetaList::getTypeFromSymbol(cmd); + if (r) { + if (type == EPostType::lNone) + wsOut += L"{Error: " + cmd + L"}"; + else { + wsOut += r->getEvent()->formatListString(type, r); + } + } + else if (type == EPostType::lNone) { + throw meosException(L"Unknown type: " + cmd); + } + } + else { + if (r == nullptr) + throw meosException(L"Syntax error: " + input); + break; // Error + } + } + wsOut += ws; + + return wsOut; +} + +bool oAbstractRunner::preventRestart() const { + if (tPreventRestartCache.second == oe->dataRevision) + return tPreventRestartCache.first; + + tPreventRestartCache.first = getDCI().getInt("NoRestart") != 0; + tPreventRestartCache.second = oe->dataRevision; + + return tPreventRestartCache.first; +} + +void oAbstractRunner::preventRestart(bool state) { + getDI().setInt("NoRestart", state); + tPreventRestartCache.first = state; + tPreventRestartCache.second = oe->dataRevision; +} \ No newline at end of file diff --git a/code/oRunner.h b/code/oRunner.h index a869b93..821bff1 100644 --- a/code/oRunner.h +++ b/code/oRunner.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -141,7 +141,14 @@ protected: bool tEntryTouched; void changedObject(); + + mutable pair tPreventRestartCache = { false, -1 }; public: + + /** Return true if the runner/team should not be part av restart in a relay etc.*/ + bool preventRestart() const; + void preventRestart(bool state); + /** Call this method after doing something to just this runner/team that changed the time/status etc, that effects the result. May make a global evaluation of the class. @@ -174,7 +181,8 @@ public: FlagFeeSpecified = 8, FlagUpdateClass = 16, FlagUpdateName = 32, - FlagAutoDNS = 64, + FlagAutoDNS = 64, // The competitor was set to DNS by the in-forest algorithm + FlagAddedViaAPI = 128, // Added by the REST api entry. }; bool hasFlag(TransferFlags flag) const; @@ -223,7 +231,7 @@ public: int getInputPlace() const {return inputPlace;} bool isVacant() const; - + bool wasSQLChanged() const {return sqlChanged;} /** Use -1 for all, PunchFinish or controlId */ @@ -336,10 +344,10 @@ public: /// Get total status for this running (including team/earlier races) virtual RunnerStatus getTotalStatus() const; - virtual const wstring &getStatusS() const; + const wstring &getStatusS(bool formatForPrint) const; wstring getIOFStatusS() const; - virtual const wstring &getTotalStatusS() const; + const wstring &getTotalStatusS(bool formatForPrint) const; wstring getIOFTotalStatusS() const; void setSpeakerPriority(int pri); @@ -396,7 +404,7 @@ class oRunner : public oAbstractRunner protected: pCourse Course; - int CardNo; + int cardNumber; pCard Card; vector multiRunner; @@ -479,6 +487,21 @@ protected: mutable vector tPlaceLegAcc; mutable vector tAfterLegAcc; + // Used to calculate temporary split time results + struct OnCourseResult { + OnCourseResult(int courseControlId, + int controlIx, + int time) : courseControlId(courseControlId), + controlIx(controlIx), time(time) {} + int courseControlId; + int controlIx; + int time; + int place; + int after; + }; + pair currentControlTime; + mutable vector tOnCourseResults; + // Rogainig results. Control and punch time vector< pair > tRogaining; int tRogainingPoints; @@ -514,6 +537,9 @@ protected: bool isHiredCard(int card) const; public: + // Returns true if there are radio control results, provided result calculation oEvent::ResultType::PreliminarySplitResults was invoked. + bool hasOnCourseResult() const { return tOnCourseResults.size() > 0 || getFinishTime() > 0; } + /** Get a runner reference (drawing) */ pRunner getReference() const; @@ -548,6 +574,8 @@ public: // Returns public unqiue identifier of runner's race (for binding card numbers etc.) int getRaceIdentifier() const; + bool isAnnonumousTeamMember() const; + // Get entry date of runner (or its team) wstring getEntryDate(bool useTeamEntryDate = true) const; @@ -732,7 +760,7 @@ public: int getCardId(){if (Card) return Card->Id; else return 0;} bool operator<(const oRunner &c) const; - bool static CompareSINumber(const oRunner &a, const oRunner &b){return a.CardNo &missingPunches, int addpunch=0, bool synchronize=false); void addPunches(pCard card, vector &missingPunches); @@ -746,7 +774,7 @@ public: bool isHiredCard() const; - int getCardNo() const {return tParentRunner && CardNo == 0 ? tParentRunner->CardNo : CardNo;} + int getCardNo() const { return tParentRunner && cardNumber == 0 ? tParentRunner->cardNumber : cardNumber; } void setCardNo(int card, bool matchCard, bool updateFromDatabase = false); /** Sets the card to a given card. An existing card is marked as unpaired. CardNo is updated. Returns id of old card (or 0). @@ -769,6 +797,9 @@ public: // Return true if the input name is considered equal to output name bool matchName(const wstring &pname) const; + /** Formats extra line for runner []-syntax, or if r is null, checks validity and throws on error.*/ + static wstring formatExtraLine(pRunner r, const wstring &input); + virtual ~oRunner(); friend class MeosSQL; diff --git a/code/oTeam.cpp b/code/oTeam.cpp index 17f73c5..48fd895 100644 --- a/code/oTeam.cpp +++ b/code/oTeam.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -724,34 +724,26 @@ bool oTeam::compareResult(const oTeam &a, const oTeam &b) b.sName.c_str(), b.sName.length()) == CSTR_LESS_THAN; } -bool oTeam::compareStartTime(const oTeam &a, const oTeam &b) +bool oTeam::compareResultNoSno(const oTeam &a, const oTeam &b) { if (a.Class != b.Class) { if (a.Class) { - if (b.Class) - return a.Class->tSortIndextSortIndex || (a.Class->tSortIndex == b.Class->tSortIndex && a.Class->Id < b.Class->Id); + if (b.Class) return a.Class->tSortIndex < b.Class->tSortIndex || (a.Class->tSortIndex == b.Class->tSortIndex && a.Class->Id < b.Class->Id); else return true; } + else return false; } - else if (a.tStartTime != b.tStartTime) - return a.tStartTime < b.tStartTime; - - const wstring &as = a.getBib(); - const wstring &bs = b.getBib(); - if (as != bs) { - return compareBib(as, bs); - } - - int aix = a.getDCI().getInt("SortIndex"); - int bix = b.getDCI().getInt("SortIndex"); - if (aix != bix) - return aix < bix; + else if (a._sortStatus != b._sortStatus) + return a._sortStatusgetRestartTime(i); int rope=pc->getRopeTime(i); - if ((restart>0 && rope>0 && (ft==0 || ft>rope)) || (ft == 0 && restart>0)) { - ft=restart; //Runner in restart + if (((restart > 0 && rope > 0 && (ft == 0 || ft > rope)) || (ft == 0 && restart > 0)) && + !preventRestart() && !Runners[i]->preventRestart()) { + ft = restart; //Runner in restart tNumRestarts++; } @@ -1127,8 +1120,9 @@ bool oTeam::apply(bool sync, pRunner source, bool setTmpOnly) { lastStartTime = restart; } - if (restart>0 && rope>0 && (lastStartTime>rope)) { - lastStartTime=restart; //Runner in restart + if (restart > 0 && rope > 0 && (lastStartTime > rope) && + !preventRestart() && !Runners[i]->preventRestart()) { + lastStartTime = restart; //Runner in restart tNumRestarts++; } if (!availableStartTimes.empty()) { @@ -1738,7 +1732,7 @@ RunnerStatus oTeam::getTotalStatus() const { void oEvent::getTeams(int classId, vector &t, bool sort) { if (sort) { - synchronizeList(oLTeamId); + synchronizeList(oListId::oLTeamId); //sortTeams(SortByName); } t.clear(); @@ -1845,7 +1839,7 @@ void oEvent::generateTeamTableData(Table &table, oTeam *addTeam) return; } - synchronizeList(oLTeamId); + synchronizeList(oListId::oLTeamId); oTeamList::iterator it; table.reserve(Teams.size()); for (it=Teams.begin(); it != Teams.end(); ++it){ @@ -1869,7 +1863,7 @@ void oTeam::addTableRow(Table &table) const { table.set(row++, it, TID_START, getStartTimeS(), true); table.set(row++, it, TID_FINISH, getFinishTimeS(), true); - table.set(row++, it, TID_STATUS, getStatusS(), true, cellSelection); + table.set(row++, it, TID_STATUS, getStatusS(false), true, cellSelection); table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(), false); table.set(row++, it, TID_PLACE, getPlaceS(), false); @@ -1995,7 +1989,7 @@ bool oTeam::inputData(int id, const wstring &input, RunnerStatus sOut = getStatus(); if (sOut != sIn) throw meosException("Status matchar inte deltagarnas status."); - output = getStatusS(); + output = getStatusS(false); } break; diff --git a/code/oTeam.h b/code/oTeam.h index 64f59f0..a96d0f6 100644 --- a/code/oTeam.h +++ b/code/oTeam.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -188,6 +188,11 @@ public: pRunner getRunner(unsigned leg) const; int getNumRunners() const {return Runners.size();} + int getNumAssignedRunners() const { + int cnt = 0; + for (auto &r : Runners) if (r) cnt++; + return cnt; + } void decodeRunners(const string &rns, vector &rid); void importRunners(const vector &rns); @@ -231,8 +236,8 @@ public: static bool compareSNO(const oTeam &a, const oTeam &b); static bool compareName(const oTeam &a, const oTeam &b) {return a.sName &clsWithRef); static void convertClassWithReferenceToPatrol(oEvent &oe, const set &clsWithRef); diff --git a/code/oTeamEvent.cpp b/code/oTeamEvent.cpp index a5ede33..5bbbf60 100644 --- a/code/oTeamEvent.cpp +++ b/code/oTeamEvent.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -57,7 +57,7 @@ void oEvent::fillTeams(gdioutput &gdi, const string &id, int classId) const vector< pair > &oEvent::fillTeams(vector< pair > &out, int ClassId) { - synchronizeList(oLTeamId); + synchronizeList(oListId::oLTeamId); oTeamList::iterator it; Teams.sort(oTeam::compareSNO); @@ -289,7 +289,7 @@ bool oTeam::matchTeam(int number, const wchar_t *s_lc) const return true; for(size_t k = 0; k < Runners.size(); k++) { - if (Runners[k] && matchNumber(Runners[k]->CardNo, s_lc)) + if (Runners[k] && matchNumber(Runners[k]->getCardNo(), s_lc)) return true; } } @@ -304,8 +304,6 @@ bool oTeam::matchTeam(int number, const wchar_t *s_lc) const return false; } - - void oEvent::fillPredefinedCmp(gdioutput &gdi, const string &name) const { bool hasPatrol = getMeOSFeatures().hasFeature(MeOSFeatures::Patrol); diff --git a/code/onlineinput.cpp b/code/onlineinput.cpp index b2ab327..89d8c03 100644 --- a/code/onlineinput.cpp +++ b/code/onlineinput.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -248,10 +248,6 @@ void OnlineInput::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { res.getObjects("entry", entries); processEntries(*oe, entries); - xmlList updates; - res.getObjects("update", updates); - processUpdates(*oe, updates); - xmlList cards; res.getObjects("card", cards); processCards(gdi, *oe, cards); @@ -260,6 +256,10 @@ void OnlineInput::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { res.getObjects("p", punches); processPunches(*oe, punches); + xmlList teamlineup; + res.getObjects("team", teamlineup); + processTeamLineups(*oe, teamlineup); + lastImportedId = res.getAttrib("lastid").getInt(); } else { @@ -307,7 +307,7 @@ void OnlineInput::processPunches(oEvent &oe, const xmlList &punches) { if (startno.length() > 0) r = oe.getRunnerByBibOrStartNo(startno, false); else - r = oe.getRunnerByCardNo(card, time); + r = oe.getRunnerByCardNo(card, time, oEvent::CardLookupProperty::Any); wstring rname; if (r) { @@ -340,7 +340,7 @@ void OnlineInput::processPunches(oEvent &oe, list< vector > &rocData) { if (specialPunches.count(code)) code = specialPunches[code]; - pRunner r = oe.getRunnerByCardNo(card, time); + pRunner r = oe.getRunnerByCardNo(card, time, oEvent::CardLookupProperty::Any); wstring rname; if (r) { @@ -385,9 +385,107 @@ void OnlineInput::processCards(gdioutput &gdi, oEvent &oe, const xmlList &cards) } } -void OnlineInput::processUpdates(oEvent &oe, const xmlList &updates) { +void OnlineInput::processTeamLineups(oEvent &oe, const xmlList &updates) { } void OnlineInput::processEntries(oEvent &oe, const xmlList &entries) { -} + + map raceId2R; + vector runners; + oe.getRunners(0, 0, runners); + for (auto &r : runners) { + int stored = r->getDCI().getInt("RaceId"); + if (stored > 0 && (stored & (1 << 30))) { + int v = (stored & ~(1 << 30)); + raceId2R[v] = r; + } + } + for (auto &entry : entries) { + pClass cls = nullptr; + + int classId = entry.getObjectInt("classid"); + if (classId > 0) + cls = oe.getClass(classId); + + if (!cls) { + wstring clsName; + entry.getObjectString("classname", clsName); + cls = oe.getClass(clsName); + } + if (!cls) + continue; + + int fee = entry.getObjectInt("fee"); + bool paid = entry.getObjectBool("paid"); + + xmlobject xname = entry.getObject("name"); + int birthyear = 0; + if (xname) { + birthyear = xname.getObjectInt("birthyear"); + } + + wstring name; + entry.getObjectString("name", name); + if (name.empty()) + continue; + + wstring club; + entry.getObjectString("club", club); + + int cardNo = 0; + bool hiredCard = false; + + xmlobject card = entry.getObject("card"); + if (card) { + cardNo = card.getInt(); + hiredCard = card.getObjectBool("hired"); + } + + pRunner r = nullptr; + int id = entry.getObjectInt("id"); + if (id > 0) { + auto res = raceId2R.find(id); + if (res != raceId2R.end()) { + r = res->second; + } + } + else { + if (cardNo != 0) { + r = oe.getRunnerByCardNo(cardNo, 0, oEvent::CardLookupProperty::Any); + if (r && !r->matchName(name)) + r = nullptr; + } + if (r == nullptr) { + r = oe.getRunnerByName(name, club); + if (r && r->getCardNo() != cardNo) + r = nullptr; + } + } + if (r == nullptr) + r = oe.addRunner(name, club, cls->getId(), cardNo, birthyear, true); + else { + r->setName(name, false); + r->setClub(club); + r->setBirthYear(birthyear); + r->setCardNo(cardNo, false, false); + } + + r->getDI().setInt("Fee", fee); + int toPay = fee; + int cf = 0; + if (hiredCard) { + cf = r->getEvent()->getBaseCardFee(); + if (cf > 0) + toPay += cf; + } + r->getDI().setInt("CardFee", cf); + r->getDI().setInt("Paid", paid ? toPay : 0); + + if (id > 0) { + r->getDI().setInt("RaceId", id | (1 << 30)); + raceId2R[id] = r; + } + r->synchronize(true); + } +} diff --git a/code/onlineinput.h b/code/onlineinput.h index 617d07b..b6ba6bf 100644 --- a/code/onlineinput.h +++ b/code/onlineinput.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -54,7 +54,7 @@ protected: void fillMappings(gdioutput &gdi) const; void processCards(gdioutput &gdi, oEvent &oe, const xmlList &cards); - void processUpdates(oEvent &oe, const xmlList &updates); + void processTeamLineups(oEvent &oe, const xmlList &updates); void processEntries(oEvent &oe, const xmlList &entries); void processPunches(oEvent &oe, const xmlList &punches); @@ -68,7 +68,7 @@ public: OnlineInput *clone() const {return new OnlineInput(*this);} void status(gdioutput &gdi); void process(gdioutput &gdi, oEvent *oe, AutoSyncType ast); - OnlineInput() : AutoMachine("Onlineinput"), cmpId(0), importCounter(1), + OnlineInput() : AutoMachine("Onlineinput", Machines::mOnlineInput), cmpId(0), importCounter(1), bytesImported(0), lastSync(0), lastImportedId(0), useROCProtocol(false), useUnitId(false) {} ~OnlineInput(); friend class TabAuto; diff --git a/code/onlineresults.cpp b/code/onlineresults.cpp index ef2be8c..5de8702 100644 --- a/code/onlineresults.cpp +++ b/code/onlineresults.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -68,7 +68,7 @@ static int OnlineCB(gdioutput *gdi, int type, void *data) { return 0; } -OnlineResults::OnlineResults() : AutoMachine("Onlineresultat"), infoServer(nullptr), dataType(1), +OnlineResults::OnlineResults() : AutoMachine("Onlineresultat", Machines::mOnlineResults), infoServer(nullptr), dataType(DataType::MOP20), zipFile(true), includeCourse(false), includeTotal(false), sendToURL(false), sendToFile(false), cmpId(0), exportCounter(1), bytesExported(0), lastSync(0) { @@ -130,10 +130,11 @@ void OnlineResults::settings(gdioutput &gdi, oEvent &oe, bool created) { // gdi.addInput("Interval", time, 10, 0, "Uppdateringsintervall (sekunder):"); gdi.addSelection("Format", 200, 200, OnlineCB, L"Exportformat:"); - gdi.addItem("Format", L"MeOS Online Protocol XML", 1); - gdi.addItem("Format", L"IOF XML 2.0.3", 2); - gdi.addItem("Format", L"IOF XML 3.0", 3); - gdi.selectItemByData("Format", dataType); + gdi.addItem("Format", L"MeOS Online Protocol XML 2.0", int(DataType::MOP20)); + gdi.addItem("Format", L"MeOS Online Protocol XML 1.0", int(DataType::MOP10)); + gdi.addItem("Format", L"IOF XML 3.0", int(DataType::IOF3)); + gdi.addItem("Format", L"IOF XML 2.0.3", int(DataType::IOF2)); + gdi.selectItemByData("Format", int(dataType)); gdi.addCheckbox("IncludeCourse", "Inkludera bana", 0, includeCourse); @@ -142,7 +143,7 @@ void OnlineResults::settings(gdioutput &gdi, oEvent &oe, bool created) { gdi.addCheckbox("IncludeTotal", "Inkludera resultat från tidigare etapper", 0, includeTotal); InfoCompetition &ic = getInfoServer(); gdi.check("IncludeTotal", ic.includeTotalResults()); - gdi.setInputStatus("IncludeTotal", dataType == 1); + gdi.setInputStatus("IncludeTotal", int(dataType) < 10); } int cx = gdi.getCX(); gdi.fillRight(); @@ -268,8 +269,8 @@ void OnlineResults::save(oEvent &oe, gdioutput &gdi) { ListBoxInfo lbi; gdi.getSelectedItem("Format", lbi); - dataType = lbi.data; - if (dataType == 1) { + dataType = DataType(lbi.data); + if (lbi.data < 10) { getInfoServer().includeTotalResults(includeTotal); getInfoServer().includeCourse(includeCourse); } @@ -370,18 +371,18 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { int xmlSize = 0; InfoCompetition &ic = getInfoServer(); xmlbuffer xmlbuff; - if (dataType == 1) { - if (ic.synchronize(*oe, false, classes, controls)) { + if (dataType == DataType::MOP10 || dataType == DataType::MOP20) { + if (ic.synchronize(*oe, false, classes, controls, dataType != DataType::MOP10)) { lastSync = tick; // If error, avoid to quick retry ic.getDiffXML(xmlbuff); } } else { t = getTempFile(); - if (dataType == 2) + if (dataType == DataType::IOF2) oe->exportIOFSplits(oEvent::IOF20, t.c_str(), false, false, classes, -1, false, true, true, false); - else if (dataType == 3) + else if (dataType == DataType::IOF3) oe->exportIOFSplits(oEvent::IOF30, t.c_str(), false, false, classes, -1, false, true, true, false); else diff --git a/code/onlineresults.h b/code/onlineresults.h index 08e9d16..176bc1b 100644 --- a/code/onlineresults.h +++ b/code/onlineresults.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,7 +36,15 @@ protected: int cmpId; set classes; set controls; - int dataType; + + enum class DataType { + MOP20 = 1, + MOP10 = 2, + IOF3 = 10, + IOF2 = 11 + }; + + DataType dataType; bool zipFile; bool includeTotal; bool includeCourse; diff --git a/code/ospeaker.h b/code/ospeaker.h index df58e25..d29babd 100644 --- a/code/ospeaker.h +++ b/code/ospeaker.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/parser.cpp b/code/parser.cpp index ce91c49..bf5457d 100644 --- a/code/parser.cpp +++ b/code/parser.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/parser.h b/code/parser.h index 894db51..9fe4c84 100644 --- a/code/parser.h +++ b/code/parser.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/pdfwriter.cpp b/code/pdfwriter.cpp index 7370713..752f7c8 100644 --- a/code/pdfwriter.cpp +++ b/code/pdfwriter.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -146,7 +146,8 @@ void pdfwriter::generatePDF(const gdioutput &gdi, const wstring &file, const wstring &pageTitleW, const wstring &authorW, - const list &tl) { + const list &tl, + bool respectPageBreak) { checkWriteAccess(file); string pageTitle = gdi.narrow(pageTitleW); // XXX WCS string author = gdi.narrow(authorW); @@ -220,7 +221,7 @@ void pdfwriter::generatePDF(const gdioutput &gdi, pageInfo.yMM2PrintK = 0; list rectangles; - pageInfo.renderPages(tl, rectangles, true, pages); + pageInfo.renderPages(tl, rectangles, true, respectPageBreak, pages); for (size_t j = 0; j< pages.size(); j++) { wstring pinfo = pageInfo.pageInfo(pages[j]); if (!pinfo.empty()) { diff --git a/code/pdfwriter.h b/code/pdfwriter.h index b5e2d4c..b6602c4 100644 --- a/code/pdfwriter.h +++ b/code/pdfwriter.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,5 +56,6 @@ class pdfwriter { const wstring &file, const wstring &pageTitle, const wstring &author, - const list &tl); + const list &tl, + bool respectPageBreak); }; diff --git a/code/prefseditor.cpp b/code/prefseditor.cpp index 1f54a3f..2efe346 100644 --- a/code/prefseditor.cpp +++ b/code/prefseditor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/prefseditor.h b/code/prefseditor.h index 1e6165b..58611d1 100644 --- a/code/prefseditor.h +++ b/code/prefseditor.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/printer.cpp b/code/printer.cpp index 18ac5e2..50ca413 100644 --- a/code/printer.cpp +++ b/code/printer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -198,8 +198,7 @@ void gdioutput::printSetup(PrinterObject &po) } } -void gdioutput::print(pEvent oe, Table *t, bool printMeOSHeader, bool noMargin) -{ +void gdioutput::print(pEvent oe, Table *t, bool printMeOSHeader, bool noMargin, bool respectPageBreak) { PageInfo pageInfo; pageInfo.printHeader = printMeOSHeader; pageInfo.noPrintMargin = noMargin; @@ -207,7 +206,7 @@ void gdioutput::print(pEvent oe, Table *t, bool printMeOSHeader, bool noMargin) setWaitCursor(true); PRINTDLG pd; - PrinterObject &po=*po_default; + PrinterObject &po = *po_default; po.printedPages.clear();//Don't remember pd.lStructSize = sizeof(PRINTDLG); @@ -215,26 +214,26 @@ void gdioutput::print(pEvent oe, Table *t, bool printMeOSHeader, bool noMargin) pd.hDevNames = po.hDevNames; pd.Flags = PD_RETURNDC; pd.hwndOwner = hWndAppMain; - pd.hDC = (HDC) NULL; + pd.hDC = (HDC)NULL; pd.nFromPage = 1; pd.nToPage = 1; pd.nMinPage = 1; pd.nMaxPage = 1; pd.nCopies = 1; - pd.hInstance = (HINSTANCE) NULL; + pd.hInstance = (HINSTANCE)NULL; pd.lCustData = 0L; - pd.lpfnPrintHook = (LPPRINTHOOKPROC) NULL; - pd.lpfnSetupHook = (LPSETUPHOOKPROC) NULL; - pd.lpPrintTemplateName = (LPCWSTR) NULL; - pd.lpSetupTemplateName = (LPCWSTR) NULL; - pd.hPrintTemplate = (HANDLE) NULL; - pd.hSetupTemplate = (HANDLE) NULL; + pd.lpfnPrintHook = (LPPRINTHOOKPROC)NULL; + pd.lpfnSetupHook = (LPSETUPHOOKPROC)NULL; + pd.lpPrintTemplateName = (LPCWSTR)NULL; + pd.lpSetupTemplateName = (LPCWSTR)NULL; + pd.hPrintTemplate = (HANDLE)NULL; + pd.hSetupTemplate = (HANDLE)NULL; - int iret=PrintDlg(&pd); + int iret = PrintDlg(&pd); - if (iret==false) { - int error=CommDlgExtendedError(); - if (error!=0) { + if (iret == false) { + int error = CommDlgExtendedError(); + if (error != 0) { char sb[128]; sprintf_s(sb, "Printing Error Code=%d", error); alert(sb); @@ -254,28 +253,37 @@ void gdioutput::print(pEvent oe, Table *t, bool printMeOSHeader, bool noMargin) if (t) t->print(*this, po.hDC, 20, 0); - DEVNAMES *dn=(LPDEVNAMES)GlobalLock(pd.hDevNames); + DEVNAMES *dn = (LPDEVNAMES)GlobalLock(pd.hDevNames); if (dn) { - DEVMODE *dm=(LPDEVMODE)GlobalLock(pd.hDevMode); + DEVMODE *dm = (LPDEVMODE)GlobalLock(pd.hDevMode); if (dm) { - po.DevMode=*dm; - po.DevMode.dmSize=sizeof(po.DevMode); + po.DevMode = *dm; + po.DevMode.dmSize = sizeof(po.DevMode); } - po.Driver=(wchar_t *)(dn)+dn->wDriverOffset;//XXX WCS - po.Device=(wchar_t *)(dn)+dn->wDeviceOffset; + po.Driver = (wchar_t *)(dn)+dn->wDriverOffset;//XXX WCS + po.Device = (wchar_t *)(dn)+dn->wDeviceOffset; //GlobalUnlock(pd.hDevMode); //GlobalUnlock(pd.hDevNames); } - doPrint(po, pageInfo, oe); + doPrint(po, pageInfo, oe, respectPageBreak); // Delete the printer DC. DeleteDC(pd.hDC); - po.hDC=0; + po.hDC = 0; } -void gdioutput::print(PrinterObject &po, pEvent oe, bool printMeOSHeader, bool noMargin) +void gdioutput::print(PrinterObject &po, pEvent oe, bool printMeOSHeader, bool noMargin, bool respectPageBreak) { + if (isTestMode) { + if (!cmdAnswers.empty()) { + string ans = cmdAnswers.front(); + cmdAnswers.pop_front(); + if (ans == "print") + return; + } + throw std::exception("Printing error"); + } PageInfo pageInfo; pageInfo.printHeader = printMeOSHeader; pageInfo.noPrintMargin = noMargin; @@ -338,7 +346,7 @@ void gdioutput::print(PrinterObject &po, pEvent oe, bool printMeOSHeader, bool n else if (po.hDC==0) { po.hDC = CreateDC(po.Driver.c_str(), po.Device.c_str(), NULL, &po.DevMode); } - doPrint(po, pageInfo, oe); + doPrint(po, pageInfo, oe, respectPageBreak); } void gdioutput::destroyPrinterDC(PrinterObject &po) @@ -384,7 +392,7 @@ bool gdioutput::startDoc(PrinterObject &po) return true; } -bool gdioutput::doPrint(PrinterObject &po, PageInfo &pageInfo, pEvent oe) +bool gdioutput::doPrint(PrinterObject &po, PageInfo &pageInfo, pEvent oe, bool respectPageBreak) { setWaitCursor(true); @@ -447,7 +455,7 @@ bool gdioutput::doPrint(PrinterObject &po, PageInfo &pageInfo, pEvent oe) pageInfo.pageY = float(PageYMax); vector pages; - pageInfo.renderPages(TL, Rectangles, false, pages); + pageInfo.renderPages(TL, Rectangles, false, respectPageBreak, pages); vector toPrint; for (size_t k = 0; k < pages.size(); k++) { @@ -621,7 +629,12 @@ struct PrintItemInfo { bool isNewPage() const { const TextInfo *ti = dynamic_cast(obj); - return ti && ti->format == pageNewPage; + return ti && (ti->format == pageNewPage || ti->format == pageNewChapter); + } + + bool isNewChapter() const { + const TextInfo *ti = dynamic_cast(obj); + return ti && ti->format == pageNewChapter; } bool isNoPrint() const { @@ -633,6 +646,7 @@ struct PrintItemInfo { void PageInfo::renderPages(const list &tl, const list &rects, bool invertHeightY, + bool respectPageBreak, vector &pages) { const PageInfo &pi = *this; TIList::const_iterator it; @@ -672,9 +686,11 @@ void PageInfo::renderPages(const list &tl, if (needSort) stable_sort(indexedTL.begin(), indexedTL.end()); + bool startChapter = true; bool addPage = true; bool wasOrphan = false; int offsetY = 0; + int desiredChapterStartY = 0; wstring infoText; int extraLimit = 0; for (size_t k = 0; k < indexedTL.size(); k++) { @@ -711,11 +727,20 @@ void PageInfo::renderPages(const list &tl, pages.push_back(RenderedPage()); pages.back().nPage = pages.size(); pages.back().info = infoText; - if (k == 0) + pages.back().startChapter = startChapter; + + if (k == 0) { offsetY = 0; + desiredChapterStartY = tlp->yp; + } + else if (startChapter) { + offsetY = desiredChapterStartY - tlp->yp + extraLimit; + } else offsetY = -tlp->yp + extraLimit; + extraLimit = 0; + startChapter = false; } if (gdioutput::skipTextRender(tlp->format)) @@ -742,6 +767,12 @@ void PageInfo::renderPages(const list &tl, } pages.back().calculateCS(text.ti); +#ifdef _DEBUG + static wchar_t breakbuff[8] = L"1429586"; + if (text.ti.text == breakbuff) { + text.ti.text.empty(); // Break when hitting symbol + } +#endif if (k + 1 < indexedTL.size() && tlp->yp != indexedTL[k+1].yp) { size_t j = k + 1; @@ -751,15 +782,18 @@ void PageInfo::renderPages(const list &tl, // Required new page if (indexedTL[j].isNewPage()) { k++; - addPage = true; - extraLimit = indexedTL[j].obj->getExtraInt(); - infoText.clear(); - continue; + if (respectPageBreak) { + addPage = true; + startChapter = indexedTL[j].isNewChapter(); + extraLimit = indexedTL[j].obj->getExtraInt(); + infoText.clear(); + continue; + } } map forwardyp; while ( j < indexedTL.size() && forwardyp.size() < 3) { - if (!indexedTL[j].isNewPage()) { + if (!indexedTL[j].isNewPage() && !indexedTL[j].isNoPrint()) { if (forwardyp.count(indexedTL[j].yp) == 0 || indexedTL[j].isNewPage()) forwardyp[indexedTL[j].yp] = j; } diff --git a/code/progress.cpp b/code/progress.cpp index 85b3837..be73ba9 100644 --- a/code/progress.cpp +++ b/code/progress.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/progress.h b/code/progress.h index ea38294..ac5d1dc 100644 --- a/code/progress.h +++ b/code/progress.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/qualification_final.cpp b/code/qualification_final.cpp index 4108938..e99d169 100644 --- a/code/qualification_final.cpp +++ b/code/qualification_final.cpp @@ -1,4 +1,26 @@ -#include "StdAfx.h" +/************************************************************************ + MeOS - Orienteering Software + Copyright (C) 2009-2019 Melin Software HB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Melin Software HB - software@melin.nu - www.melin.nu + Eksoppsvägen 16, SE-75646 UPPSALA, Sweden + +************************************************************************/ + +#include "StdAfx.h" #include "qualification_final.h" @@ -7,6 +29,8 @@ #include #include #include "xmlparser.h" +#include "oRunner.h" +#include "localizer.h" pair QualificationFinal::getNextFinal(int instance, int orderPlace, int numSharedPlaceNext) const { pair key(instance, orderPlace); @@ -70,6 +94,14 @@ void QualificationFinal::import(const wstring &file) { int iLevel = 0; for (size_t iLevel = 0; iLevel < levels.size(); iLevel++) { auto &level = levels[iLevel]; + wstring rankS; + level.getObjectString("distribution", rankS); + bool rankSort = false; + if (rankS == L"Ranking") + rankSort = true; + else if (!rankS.empty()) + throw meosException(L"Unknown distribution: " + rankS); + xmlList classes; level.getObjects("Class", classes); for (auto &cls : classes) { @@ -91,7 +123,7 @@ void QualificationFinal::import(const wstring &file) { idToIndex[classId] = classDefinition.size() + numBaseLevels; classDefinition.push_back(Class()); classDefinition.back().name = name; - + classDefinition.back().rankLevel = rankSort; for (auto &qf : rules) { int place = qf.getObjectInt("place"); if (place > 0) { @@ -145,15 +177,22 @@ void QualificationFinal::import(const wstring &file) { } void QualificationFinal::init(const wstring &def) { + serializedFrom = def; vector races, rtdef, rdef; split(def, L"|", races); classDefinition.resize(races.size()); bool valid = true; bool first = true; for (size_t k = 0; k < races.size(); k++) { + bool rankLevel = false; + if (races[k].size() > 0 && races[k][0] == 'R') { + rankLevel = true; + races[k] = races[k].substr(1); + } split(races[k], L"T", rtdef); classDefinition[k].qualificationMap.clear(); classDefinition[k].timeQualifications.clear(); + classDefinition[k].rankLevel = rankLevel; if (first && rtdef.empty()) continue; @@ -219,6 +258,9 @@ void QualificationFinal::encode(wstring &output) const { if (k > 0) output.append(L"|"); + if (classDefinition[k].rankLevel) + output.append(L"R"); + auto &qm = classDefinition[k].qualificationMap; for (size_t j = 0; j < qm.size(); j++) { @@ -249,7 +291,9 @@ void QualificationFinal::encode(wstring &output) const { output.append(itow(source[i])); } } - } + } + + serializedFrom = output; } int QualificationFinal::getNumStages(int stage) const { @@ -272,9 +316,12 @@ int QualificationFinal::getNumStages(int stage) const { void QualificationFinal::initgmap(bool check) { sourcePlaceToFinalOrder.clear(); + levels.resize(getNumLevels(), LevelInfo::Normal); for (int ix = 0; ix < (int)classDefinition.size(); ix++) { auto &c = classDefinition[ix]; + if (c.rankLevel) + levels[getLevel(ix+1)] = LevelInfo::RankSort; for (int k = 0; k < (int)c.qualificationMap.size(); k++) { const pair &sd = c.qualificationMap[k]; @@ -286,13 +333,233 @@ void QualificationFinal::initgmap(bool check) { } } +int QualificationFinal::getLevel(int instance) const { + if (instance == 0) + return 0; + instance--; + if (classDefinition[instance].level >= 0) + return classDefinition[instance].level; + int level = 0; + int src = instance; + while (!classDefinition[instance].qualificationMap.empty()) { + instance = classDefinition[instance].qualificationMap.front().first - 1; + if (instance < 0) + break; + level++; + if (size_t(level) > classDefinition.size()) + throw meosException("Internal error"); + } + classDefinition[src].level = level; + return level; +} + +bool QualificationFinal::isFinalClass(int instance) const { + return sourcePlaceToFinalOrder.count(make_pair(instance, 1)) == 0; +} + +int QualificationFinal::getNumLevels() const { + return getLevel(classDefinition.size() - 1) + 1; +} + +int QualificationFinal::getMinInstance(int level) const { + int minInst = classDefinition.size() - 1; + for (int i = classDefinition.size() - 1; i >= 0; i--) { + if (i == 0 && minInst == 1) + break; // No need to include base instance. + if (getLevel(i) == level) + minInst = i; + } + return minInst; +} + + int QualificationFinal::getHeatFromClass(int finalClassId, int baseClassId) const { if (baseClassId == baseId) { int fci = (finalClassId - baseId) / maxClassId; if (fci * maxClassId + baseClassId == finalClassId) return fci; + else + return -1; } return 0; } + +/** Clear previous staus data*/ +void QualificationFinal::prepareCalculations() { + storedInfoLookup.clear(); + storedInfo.clear(); +} + +/** Retuns the final class and the order within that class. */ +void QualificationFinal::setupNextFinal(pRunner r, int instance, int orderPlace, int numSharedPlaceNext) { + storedInfo.resize(getNumClasses()); + + pair res = getNextFinal(instance, orderPlace, numSharedPlaceNext); + int level = getLevel(instance); + storedInfo[level].emplace_back(r, res.first, res.second); +} + +/** Do internal calculations. */ +void QualificationFinal::computeFinals() { + if (storedInfo.empty()) + return; + int nl = getNumLevels(); + for (int level = 1; level < nl; level++) { + vector placeCount(classDefinition.size(), 0); + if (levels[level] == LevelInfo::RankSort) { + vector< pair > rankIx; + vector< pair > placeQueue; + + set numClassCount; + auto &si = storedInfo[level-1]; + if (si.empty()) + continue; + rankIx.reserve(si.size()); + placeQueue.reserve(si.size()); + + for (int i = 0; size_t(i) < si.size(); i++) { + if (si[i].instance > 0) { + int rank = si[i].r->getRanking(); + rankIx.emplace_back(rank, i); + placeQueue.emplace_back(si[i].order, si[i].instance); + numClassCount.insert(si[i].instance); + } + } + int numClass = numClassCount.size(); + vector rotmap; + if (numClass == 2) { + rotmap = { 1,0 }; + } + else if (numClass == 3) { + rotmap = { 2,0,1 }; + } + else if (numClass == 4) { + rotmap = { 3, 2, 0, 1 }; + } + else if (numClass == 5) { + rotmap = { 4, 3, 0, 2, 1 }; + } + else if (numClass == 6) { + rotmap = { 5, 4, 3, 0, 2, 1 }; + } + else { + rotmap.resize(numClass); + for (int k = 0; k < numClass; k++) + rotmap[k] = (k + 1) % numClass; + } + + sort(rankIx.begin(), rankIx.end()); + sort(placeQueue.begin(), placeQueue.end()); + vector placementOrder; + for (size_t ix = 0; ix < placeQueue.size(); ix++) { + placementOrder.push_back(ix); + } + for (size_t i = numClass; i + numClass <= placementOrder.size(); i += numClass * 2) { + reverse(placementOrder.begin() + i, placementOrder.begin() + i + numClass); + } + reverse(placementOrder.begin(), placementOrder.end()); + /* + vector work; + + int ix = placeQueue.size() - 1; + int iteration = 0; + bool versionA = false; + while (ix>=0) { + work.clear(); + for (int i = 0; i < numClass && ix >= 0; i++) { + work.push_back(ix--); + } + if (versionA) { + if (work.size() == numClass) + rotate(work.begin(), work.begin() + iteration, work.end()); + iteration = rotmap[iteration]; + } + else { + if (iteration % 2 == 1) + reverse(work.begin(), work.end()); + iteration++; + } + + for (size_t i = 1; i <= work.size(); i++) { + placementOrder[ix + i] = work[work.size() - i]; + } + }*/ + + for (auto &rankRes : rankIx) { + si[rankRes.second].instance = placeQueue[placementOrder.back()].second; + si[rankRes.second].order = placeQueue[placementOrder.back()].first; + placementOrder.pop_back(); + } + } + } + + for (auto & si : storedInfo) { + for (auto &res : si) { + storedInfoLookup[res.r->getId()] = &res; + } + } +} + +/** Retuns the final class and the order within that class. */ +pair QualificationFinal::getNextFinal(int runnerId) const { + auto res = storedInfoLookup.find(runnerId); + if (res == storedInfoLookup.end()) { + return make_pair(0, -1); + } + + return make_pair(res->second->instance, res->second->order); +} + +void QualificationFinal::printScheme(oClass * cls, gdioutput &gdi) const { + vector cname; +// vector > targetCls(classDefinition.size()); + + for (size_t i = 0; i < classDefinition.size(); i++) { + pClass inst = cls->getVirtualClass(i + 1); + cname.push_back(inst ? inst->getName() : lang.tl("Saknad klass")); + // for (auto d : classDefinition[i].qualificationMap) { + // targetCls[i].push_back(d.first); + // } + } + + int ylimit = gdi.getHeight(); + gdi.pushY(); + for (size_t i = 0; i < classDefinition.size(); i++) { + gdi.dropLine(); + + gdi.addStringUT(1, itow(i+1) + L" " + cname[i]); + bool rankingBased = false; + vector dst; + + for (int place = 1; place < 20; place++) { + auto res = sourcePlaceToFinalOrder.find(make_pair(i + 1, place)); + + if (res != sourcePlaceToFinalOrder.end()) { + if (classDefinition[res->second.first - 1].rankLevel) + rankingBased = true; + dst.push_back(itow(place) + L". ➞ " + cname[res->second.first - 1]); + } + else break; + } + + if (rankingBased) { + gdi.addString("", 0, L"X går vidare, klass enligt ranking#" + itow(dst.size())); + } + else { + for (auto &c : dst) { + gdi.addStringUT(0, c); + } + } + + if (gdi.getCY() > ylimit) { + gdi.newColumn(); + gdi.popY(); + gdi.pushX(); + + } + } + + gdi.refresh(); +} diff --git a/code/qualification_final.h b/code/qualification_final.h index c0ec041..63cdc7e 100644 --- a/code/qualification_final.h +++ b/code/qualification_final.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,10 +28,13 @@ Eksoppsvägen 16, SE-75646 UPPSALA, Sweden #include #include +class oRunner; +typedef oRunner * pRunner; +class oClass; class QualificationFinal { private: - + mutable wstring serializedFrom; int maxClassId; int baseId; @@ -39,8 +42,27 @@ private: wstring name; vector< pair > qualificationMap; vector< vector > timeQualifications; + bool rankLevel = false; + mutable int level = -1; }; + enum LevelInfo { + Normal, + RankSort, + }; + + vector levels; + struct ResultInfo { + ResultInfo(pRunner r, int inst, int order) : r(r), instance(inst), order(order) {} + pRunner r; + int instance; + int order; + }; + + + vector> storedInfo; + map storedInfoLookup; + vector classDefinition; map, pair> sourcePlaceToFinalOrder; @@ -53,6 +75,10 @@ public: QualificationFinal(int maxClassId, int baseId) : maxClassId(maxClassId), baseId(baseId) {} + bool matchSerialization(const wstring &ser) const { + return serializedFrom == ser; + } + int getNumClasses() const { return classDefinition.size(); } @@ -61,6 +87,9 @@ public: return classDefinition.at(inst-1).name; } + /** Return true if this is a final class*/ + bool isFinalClass(int instance) const; + // Count number of stages int getNumStages() const { return getNumStages(classDefinition.size()); @@ -73,12 +102,35 @@ public: void init(const wstring &def); void encode(wstring &output) const; + /** Calculate the level of a particular instance. */ + int getLevel(int instance) const; + + /** Calculate the number of levels. */ + int getNumLevels() const; + + /** Get the minimum number instance of a specified level. */ + int getMinInstance(int level) const; + /** Retuns the final class and the order within that class. */ pair getNextFinal(int instance, int orderPlace, int numSharedPlaceNext) const; + /** Clear previous staus data*/ + void prepareCalculations(); + + /** Return the final class and the order within that class. */ + void setupNextFinal(pRunner r, int instance, int orderPlace, int numSharedPlaceNext); + + /** Do internal calculations. */ + void computeFinals(); + + /** Returns the final class and the order within that class. */ + pair getNextFinal(int runnerId) const; + /** Returns true if all competitors are automatically qualified*/ bool noQualification(int instance) const; /** Fills in the set of no-qualification classes*/ void getBaseClassInstances(set &base) const; + + void printScheme(oClass * cls, gdioutput &gdi) const; }; diff --git a/code/random.cpp b/code/random.cpp index 974521d..1e4637a 100644 --- a/code/random.cpp +++ b/code/random.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/random.h b/code/random.h index 6f9f692..e968492 100644 --- a/code/random.h +++ b/code/random.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/recorder.cpp b/code/recorder.cpp index 6233303..b233034 100644 --- a/code/recorder.cpp +++ b/code/recorder.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/recorder.h b/code/recorder.h index d24d248..3c61590 100644 --- a/code/recorder.h +++ b/code/recorder.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/restserver.cpp b/code/restserver.cpp index b87b58d..bd74448 100644 --- a/code/restserver.cpp +++ b/code/restserver.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,11 +34,18 @@ Eksoppsvägen 16, SE-75646 UPPSALA, Sweden #include "oListInfo.h" #include "TabList.h" #include "generalresult.h" +#include "HTMLWriter.h" +#include "RunnerDB.h" +#include "image.h" using namespace restbed; vector< shared_ptr > RestServer::startedServers; +const wstring &wideParam(const string ¶m) { + return gdioutput::fromUTF8(param); +} + shared_ptr RestServer::construct() { shared_ptr obj(new RestServer()); startedServers.push_back(obj); @@ -101,9 +108,17 @@ void RestServer::handleRequest(const shared_ptr &session) { session->fetch(content_length, [request, answer](const shared_ptr< Session > session, const Bytes & body) { - session->close(restbed::OK, answer->answer, { { "Content-Length", itos(answer->answer.length()) }, - { "Connection", "close" }, - { "Access-Control-Allow-Origin", "*" } }); + if (answer->image.empty()) { + session->close(restbed::OK, answer->answer, { { "Content-Length", itos(answer->answer.length()) }, + { "Connection", "close" }, + { "Access-Control-Allow-Origin", "*" } }); + } + else { + session->close(restbed::OK, answer->image, { { "Content-Type", "image/png"}, + { "Content-Length", itos(answer->image.size()) }, + { "Connection", "close" }, + { "Access-Control-Allow-Origin", "*" } }); + } }); } @@ -169,17 +184,20 @@ void RestServer::compute(oEvent &ref) { waitForCompletion.notify_all(); } +extern wchar_t programPath[MAX_PATH]; void RestServer::computeInternal(oEvent &ref, shared_ptr &rq) { if (rq->parameters.empty()) { - rq->answer = "" + rq->answer = "" "" "MeOS Information Service" "" "" - "

MeOS

" + ref.gdiBase().toUTF8(lang.tl(getMeosFullVersion())) + "

" + "\"MeOS\"" + "

" + ref.gdiBase().toUTF8(lang.tl(getMeosFullVersion())) + "

" "

    \n"; + rq->answer += "

    " + ref.gdiBase().toUTF8(lang.tl("Listor")) + "

    "; vector lists; TabList::getPublicLists(ref, lists); map listMap; @@ -217,6 +235,15 @@ void RestServer::computeInternal(oEvent &ref, shared_ptrStartlista" rq->answer += "
\n"; + + string entryLinks = "

" + ref.gdiBase().toUTF8(lang.tl(L"Direktanmälan", true)) + "

"; + entryLinks += ""+ref.gdiBase().toUTF8(lang.tl("Anmäl")) + "
"; + + entryLinks += "
"; + + rq->answer += entryLinks; + + HINSTANCE hInst = GetModuleHandle(0); HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(132), RT_HTML); HGLOBAL res = LoadResource(hInst, hRes); @@ -241,10 +268,54 @@ void RestServer::computeInternal(oEvent &ref, shared_ptranswer += "\n\n"; } + else if (rq->parameters.count("entry") > 0) { + newEntry(ref, rq->parameters, rq->answer); + } else if (rq->parameters.count("get") > 0) { string what = rq->parameters.find("get")->second; getData(ref, what, rq->parameters, rq->answer); } + else if (rq->parameters.count("lookup") > 0) { + string what = rq->parameters.find("lookup")->second; + lookup(ref, what, rq->parameters, rq->answer); + } + else if (rq->parameters.count("page") > 0) { + string what = rq->parameters.find("page")->second; + auto &writer = HTMLWriter::getWriter(HTMLWriter::TemplateType::Page, what); + writer.getPage(ref, rq->answer); + } + else if (rq->parameters.count("enter") > 0) { + auto &writer = HTMLWriter::getWriter(HTMLWriter::TemplateType::Page, "entryform"); + writer.getPage(ref, rq->answer); + } + else if (rq->parameters.count("image") > 0) { + ifstream fin; + string image = rq->parameters.find("image")->second; + if (imageCache.count(image)) { + rq->image = imageCache[image]; + } + if (image == "meos") { + imageCache[image] = rq->image = Image::loadResourceToMemory(MAKEINTRESOURCE(513), _T("PNG")); + } + else { + wchar_t fn[260]; + if (image.find_first_of("\\/.?*") == string::npos) { + wstring par = wideParam(image) + L".png"; + getUserFile(fn, par.c_str()); + fin.open(fn, ios::binary); + if (fin.good()) { + fin.seekg(0, ios::end); + int p2 = (int)fin.tellg(); + fin.seekg(0, ios::beg); + rq->image.resize(p2); + fin.read((char *)&rq->image[0], rq->image.size()); + fin.close(); + + imageCache[image] = rq->image; + } + } + } + } else if (rq->parameters.count("html") > 0) { string stype = rq->parameters.count("type") ? rq->parameters.find("type")->second : _EmptyString; int type = atoi(stype.c_str()); @@ -260,7 +331,8 @@ void RestServer::computeInternal(oEvent &ref, shared_ptrsecond.second, false); wstring exportFile = getTempFile(); - gdiPrint.writeHTML(exportFile, ref.getName(), 30); + HTMLWriter::write(gdiPrint, exportFile, ref.getName(), 30, res->second.first, ref); + ifstream fin(exportFile.c_str()); string rbf; while (std::getline(fin, rbf)) { @@ -774,6 +846,52 @@ void RestServer::getData(oEvent &oe, const string &what, const multimap classes; + xmlparser mem; + mem.openMemoryOutput(false); + mem.startTag("EntryClasses", "xmlns", "http://www.melin.nu/mop"); + if (epClass != EntryPermissionClass::None) { + oe.getClasses(classes, true); + for (auto cls : classes) { + if (epClass == EntryPermissionClass::Any || cls->getAllowQuickEntry()) { + mem.startTag("Class", "id", itow(cls->getId())); + mem.write("Name", cls->getName()); + for (auto &f : cls->getAllFees()) + mem.write("Fee", f.first); + pCourse crs = cls->getCourse(true); + if (crs) { + int len = crs->getLength(); + if (len > 0) + mem.write("Length", len); + } + int lowAge = cls->getDCI().getInt("LowAge"); + if (lowAge > 0) + mem.write("MinAge", lowAge); + int highAge = cls->getDCI().getInt("HighAge"); + if (highAge > 0) + mem.write("MaxAge", highAge); + auto sex = cls->getDCI().getString("Sex"); + if (!sex.empty()) + mem.write("Sex", sex); + + auto start = cls->getStart(); + if (!start.empty()) + mem.write("Start", start); + + if (cls->getNumberMaps() > 0) { + int numMaps = cls->getNumRemainingMaps(false); + mem.write("AvailableStarts", numMaps); + } + mem.endTag(); + } + } + } + mem.endTag(); + mem.getMemoryOutput(answer); + } + if (out.size() > 0) { xmlparser mem; mem.openMemoryOutput(false); @@ -833,3 +951,342 @@ void RestServer::getStatistics(Statistics &s) { s.averageResponseTime /= s.numRequests; } } + +void RestServer::lookup(oEvent &oe, const string &what, const multimap ¶m, string &answer) { + bool okRequest = false; + if (what == "competitor") { + vector runners; + if (param.count("card") > 0) { + int card = atoi(param.find("card")->second.c_str()); + int time = 0; + if (param.count("running") && param.find("card")->second == "true") { + time = oe.getRelativeTime(getLocalTime()); + if (time < 0) + time = 0; + } + pRunner r = oe.getRunnerByCardNo(card, time, oEvent::CardLookupProperty::CardInUse); + if (!r) + r = oe.getRunnerByCardNo(card, time, oEvent::CardLookupProperty::Any); + + if (r) + runners.push_back(r); + } + if (param.count("name") > 0) { + wstring club; + if (param.count("club")) + club = wideParam(param.find("club")->second); + + pRunner r = oe.getRunnerByName(wideParam(param.find("name")->second), club); + if (r) + runners.push_back(r); + } + if (param.count("id") > 0) { + pRunner r = oe.getRunner(atoi(param.find("id")->second.c_str()), 0); + if (r) + runners.push_back(r); + } + if (param.count("bib") > 0) { + pRunner r = oe.getRunnerByBibOrStartNo(wideParam(param.find("bib")->second), false); + if (r) + runners.push_back(r); + } + + xmlparser xml; + xml.openMemoryOutput(false); + xml.startTag("Competitors", "xmlns", "http://www.melin.nu/mop"); + + for (auto r : runners) { + xml.startTag("Competitor", "id", itos(r->getId())); + xml.write("Name", r->getName()); + xml.write("ExternalId", r->getExtIdentifierString()); + xml.write("Club", { make_pair("id", itow(r->getClubId())) }, r->getClub()); + xml.write("Class", { make_pair("id", itow(r->getClassId(true))) }, r->getClass(true)); + xml.write("Card", r->getCardNo()); + xml.write("Status", {make_pair("code", itow(r->getStatus()))}, r->getStatusS(true)); + xml.write("Start", r->getStartTimeS()); + if (r->getFinishTime() > 0) { + xml.write("Finish", r->getFinishTimeS()); + xml.write("RunningTime", r->getRunningTimeS()); + xml.write("Place", r->getPlaceS()); + xml.write("TimeAfter", formatTime(r->getTimeAfter())); + } + if (r->getTeam()) { + xml.write("Team", { make_pair("id", itow(r->getTeam()->getId())) }, r->getTeam()->getName()); + xml.write("Leg", r->getLegNumber()); + } + if ((r->getFinishTime() > 0 || r->getCard() != nullptr) && r->getCourse(false)) { + auto &sd = r->getSplitTimes(false); + vector after; + r->getLegTimeAfter(after); + vector afterAcc; + r->getLegTimeAfterAcc(afterAcc); + vector delta; + r->getSplitAnalysis(delta); + + auto crs = r->getCourse(true); + int ix = 0; + xml.startTag("Splits"); + vector> analysis = { make_pair("lost", L""), + make_pair("behind", L""), + make_pair("mistake", L""), + make_pair("leg", L""), + make_pair("total", L""), }; + for (auto &s : sd) { + auto ctrl = crs->getControl(ix); + if (ctrl) { + xml.startTag("Control", "number", itow(ix+1)); + xml.write("Name", ctrl->getName()); + if (s.hasTime()) { + xml.write("Time", formatTime(s.time - r->getStartTime())); + + if (size_t(ix) < delta.size() && size_t(ix) < after.size() && size_t(ix) < afterAcc.size()) { + if (after[ix] > 0) + analysis[0].second = formatTime(after[ix]); + else + analysis[0].second = L""; + + if (afterAcc[ix] > 0) + analysis[1].second = formatTime(afterAcc[ix]); + else + analysis[1].second = L""; + + if (delta[ix] > 0) + analysis[2].second = formatTime(delta[ix]); + else + analysis[2].second = L""; + + int place = r->getLegPlace(ix); + analysis[3].second = place > 0 ? itow(place) : L""; + + int placeAcc = r->getLegPlaceAcc(ix); + analysis[4].second = placeAcc > 0 ? itow(placeAcc) : L""; + + xml.write("Analysis", analysis, L""); + } + } + else if (!s.isMissing()) + xml.write("Time", "Unknown"); + xml.endTag(); + ix++; + } + } + xml.endTag(); + } + xml.endTag(); + } + + xml.endTag(); + xml.getMemoryOutput(answer); + } + else if (what == "dbcompetitor") { + vector wdb; + if (param.count("card") > 0) { + int card = atoi(param.find("card")->second.c_str()); + auto res = oe.getRunnerDatabase().getRunnerByCard(card); + if (res) + wdb.push_back(res); + } + if (param.count("name") > 0) { + int clubId = 0; + if (param.count("club")) { + wstring club = wideParam(param.find("club")->second); + pClub clb = oe.getRunnerDatabase().getClub(club); + if (clb) + clubId = clb->getId(); + } + wstring name = wideParam(param.find("name")->second); + auto res = oe.getRunnerDatabase().getRunnerSuggestions(name, clubId, 20); + for (auto &r : res) + wdb.push_back(r.first); + } + if (param.count("id") > 0) { + long long id = oBase::converExtIdentifierString(wideParam(param.find("id")->second)); + auto res = oe.getRunnerDatabase().getRunnerById(id); + if (res) + wdb.push_back(res); + } + + xmlparser xml; + xml.openMemoryOutput(false); + xml.startTag("DatabaseCompetitors", "xmlns", "http://www.melin.nu/mop"); + for (auto &r : wdb) { + wstring name = r->getGivenName() + L" " + r->getFamilyName(); + wchar_t bf[16]; + oBase::converExtIdentifierString(r->getExtId(), bf); + + xml.startTag("Competitor", "id", bf); + xml.write("Name", name); + if (r->dbe().clubNo > 0) { + wstring club; + if (oe.getRunnerDatabase().getClub(r->dbe().clubNo, club)) { + xml.write("Club", { make_pair("id", itow(r->dbe().clubNo)) }, club); + } + } + xml.write("Card", r->dbe().cardNo); + xml.write("Nationality", r->getNationality()); + wstring sex = r->getSex(); + if (!sex.empty()) + xml.write("Sex", sex); + if (r->dbe().birthYear > 0) + xml.write("BirthYear", itow(r->dbe().birthYear)); + + xml.endTag(); + } + xml.endTag(); + xml.getMemoryOutput(answer); + } + else if (what == "dbclub") { + vector clubs; + + if (param.count("name") > 0) { + wstring club = wideParam(param.find("name")->second); + clubs = oe.getRunnerDatabase().getClubSuggestions(club, 20); + } + if (param.count("id") > 0) { + long long id = oBase::converExtIdentifierString(wideParam(param.find("id")->second)); + auto res = oe.getRunnerDatabase().getClub(int(id)); + if (res) + clubs.push_back(res); + } + + xmlparser xml; + xml.openMemoryOutput(false); + xml.startTag("DatabaseClubs", "xmlns", "http://www.melin.nu/mop"); + for (auto &c : clubs) { + xml.startTag("Club", "id", c->getExtIdentifierString()); + xml.write("Name", c->getName()); + xml.endTag(); + } + xml.endTag(); + xml.getMemoryOutput(answer); + } +} + +void RestServer::setEntryPermission(EntryPermissionClass epClass, EntryPermissionType epType) { + this->epClass = epClass; + this->epType = epType; +} + +void RestServer::newEntry(oEvent &oe, const multimap ¶m, string &answer) { + xmlparser xml; + xml.openMemoryOutput(false); + xml.startTag("Answer", "xmlns", "http://www.melin.nu/mop"); + + bool permissionDenied = false; + wstring error; + + if (epClass == EntryPermissionClass::None || epType == EntryPermissionType::None) + permissionDenied = true; + + if (!permissionDenied) { + wstring name, club; + long long extId = 0; + int clubId = 0; + RunnerWDBEntry *dbr = nullptr; + if (param.count("id")) { + extId = oBase::converExtIdentifierString(wideParam(param.find("id")->second)); + dbr = oe.getRunnerDatabase().getRunnerById(extId); + if (dbr) { + clubId = dbr->dbe().clubNo; + } + } + else if(param.count("name")) + name = wideParam(param.find("name")->second); + + if (param.count("club")) + club = wideParam(param.find("club")->second); + + pClub existingClub = oe.getClub(club); + + if (epType != EntryPermissionType::Any) { + if (extId == 0) { + dbr = oe.getRunnerDatabase().getRunnerByName(name, clubId, 0); + if (dbr) + extId = dbr->getExtId(); + } + } + + if (existingClub) + clubId = existingClub->getId(); + + int classId = 0; + if (param.count("class")) + classId = atoi(param.find("class")->second.c_str()); + + auto cls = oe.getClass(classId); + if (cls == nullptr) { + error = L"Okänd klass"; + } + else if (epClass != EntryPermissionClass::Any && !cls->getAllowQuickEntry()) { + permissionDenied = true; + } + + if (epType != EntryPermissionType::Any && extId == 0) { + error = L"Anmälan måste hanteras manuellt"; + } + + if (epType == EntryPermissionType::InDbExistingClub && clubId == 0) { + error = L"Anmälan måste hanteras manuellt"; + } + + int cardNo = 0; + if (param.count("card")) + cardNo = atoi(param.find("card")->second.c_str()); + + if (cardNo <= 0) { + error = L"Ogiltigt bricknummer X#" + itow(cardNo); + } + else { + vector runners; + oe.getRunnersByCardNo(cardNo, true, oEvent::CardLookupProperty::CardInUse, runners); + for (auto r : runners) { + if (!r->getCard()) { + error = L"Bricknummret är upptaget (X)#" + r->getCompleteIdentification(); + } + } + } + + if (!permissionDenied && error.empty()) { + pRunner r = oe.addRunner(name, club, classId, cardNo, 0, true); + if (r && dbr) { + r->init(*dbr); + } + + if (r) { + r->setFlag(oRunner::FlagAddedViaAPI, true); + r->addClassDefaultFee(true); + r->synchronize(); + r->markClassChanged(-1); + xml.write("Status", "OK"); + xml.write("Fee", r->getDCI().getInt("Fee")); + xml.write("Info", r->getClass(true) + L", " + r->getCompleteIdentification(false)); + } + } + } + if (permissionDenied) { + xml.write("Status", "Failed"); + xml.write("Info", lang.tl("Permission denied")); + } + else if (!error.empty()) { + xml.write("Status", "Failed"); + xml.write("Info", lang.tl(error)); + } + + xml.endTag(); + xml.getMemoryOutput(answer); +} + +vector> RestServer::getPermissionsPersons() { + vector> res; + res.emplace_back(lang.tl("Anyone"), size_t(EntryPermissionType::Any)); + res.emplace_back(lang.tl("Från löpardatabasen"), size_t(EntryPermissionType::InDbAny)); + res.emplace_back(lang.tl("Från löpardatabasen i befintliga klubbar"), size_t(EntryPermissionType::InDbExistingClub)); + return res; +} + +vector> RestServer::getPermissionsClass() { + vector> res; + res.emplace_back(lang.tl("Alla"), size_t(EntryPermissionClass::Any)); + res.emplace_back(lang.tl("Med direktanmälan"), size_t(EntryPermissionClass::DirectEntry)); + return res; +} diff --git a/code/restserver.h b/code/restserver.h index 4323031..bc557e3 100644 --- a/code/restserver.h +++ b/code/restserver.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ Eksoppsvägen 16, SE-75646 UPPSALA, Sweden #include #include #include +#include namespace restbed { class Service; @@ -37,11 +38,30 @@ namespace restbed { }; class RestServer { +public: + + enum class EntryPermissionClass { + None, + DirectEntry, + Any + }; + + enum class EntryPermissionType { + None, + InDbExistingClub, + InDbAny, + Any + }; + + static vector> getPermissionsPersons(); + static vector> getPermissionsClass(); + private: struct EventRequest { EventRequest() : state(false) {} multimap parameters; string answer; + vector image; atomic_bool state; //false - asked, true - answerd bool isCompleted() { @@ -49,6 +69,9 @@ private: } }; + EntryPermissionClass epClass = EntryPermissionClass::None; + EntryPermissionType epType = EntryPermissionType::None; + mutex lock; atomic_bool hasAnyRequest; shared_ptr service; @@ -57,6 +80,10 @@ private: deque> requests; void getData(oEvent &ref, const string &what, const multimap ¶m, string &answer); + void lookup(oEvent &ref, const string &what, const multimap ¶m, string &answer); + + void newEntry(oEvent &ref, const multimap ¶m, string &answer); + void compute(oEvent &ref); void startThread(int port); @@ -70,13 +97,14 @@ private: vector responseTimes; + map> imageCache; + RestServer(); static vector< shared_ptr > startedServers; RestServer(const RestServer &); RestServer & operator=(const RestServer &) const; - - + void computeInternal(oEvent &ref, shared_ptr &rq); map > > listCache; @@ -89,9 +117,14 @@ public: static shared_ptr construct(); static void remove(shared_ptr server); - static void computeRequested(oEvent &ref); + void setEntryPermission(EntryPermissionClass epClass, EntryPermissionType epType); + + tuple getEntryPermission() const { + return make_tuple(epClass, epType); + } + struct Statistics { int numRequests; int averageResponseTime; diff --git a/code/russian.lng b/code/russian.lng index ce5de0f..1caccd0 100644 --- a/code/russian.lng +++ b/code/russian.lng @@ -979,7 +979,7 @@ Radera tävlingen = Удалить соревнования Radera vakanser = Удалить резерв Radiotider, kontroll = РадиоКП Ranking = Ранг -Ranking (IOF, xml) = Ранг (IOF, xml) +Ranking (IOF, xml, csv) = Ранг (IOF, xml, csv) Rapport inför = Отчет за Rapporter = Отчёты Rapportläge = Режим отчётов diff --git a/code/socket.cpp b/code/socket.cpp index b6297fa..7cd2a58 100644 --- a/code/socket.cpp +++ b/code/socket.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/socket.h b/code/socket.h index d642e0b..0408115 100644 --- a/code/socket.h +++ b/code/socket.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/speakermonitor.cpp b/code/speakermonitor.cpp index db9ae53..e09e16b 100644 --- a/code/speakermonitor.cpp +++ b/code/speakermonitor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/speakermonitor.h b/code/speakermonitor.h index 5750836..023848b 100644 --- a/code/speakermonitor.h +++ b/code/speakermonitor.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/subcommand.h b/code/subcommand.h index 35fe16f..c662d59 100644 --- a/code/subcommand.h +++ b/code/subcommand.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2018 Melin Software HB +Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/swedish.lng b/code/swedish.lng index f27a6b2..ac5edea 100644 --- a/code/swedish.lng +++ b/code/swedish.lng @@ -748,7 +748,7 @@ Radera tävlingen = Radera tävlingen Radera vakanser = Radera vakanser Radiotider, kontroll = Radiotider, kontroll Ranking = Ranking -Ranking (IOF, xml) = Ranking (IOF, xml) +Ranking (IOF, xml, csv) = Ranking (IOF, xml, csv) Rapport inför = Rapport inför Rapporter = Rapporter Rapportläge = Rapportläge @@ -1947,7 +1947,7 @@ Hela banan = Hela banan Ogiltigt maximalt intervall = Ogiltigt maximalt intervall Startintervallet får inte vara kortare än basintervallet = Startintervallet får inte vara kortare än basintervallet Ett startintervall måste vara en multipel av basintervallet = Ett startintervall måste vara en multipel av basintervallet -Ogiltigt minimalt intervall = Ogiltigt minimalt intervall +Ogiltigt minimalt intervall = Ogiltigt minsta intervall Ogiltigt basintervall = Ogiltigt basintervall Country = Land CourseShortening = Banavkortningar @@ -2011,7 +2011,7 @@ open_error = Kan inte öppna X.\n\nY. open_error_locked = Tävlingen är redan öppnad i MeOS.\n\nDu måste använda en databas för att öppna flera instanser av tävlingen. Ogiltigt bricknummer = Ogiltigt bricknummer ask:updatelegs = Sträcklängderna kan behöva uppdateras efter att banan har ändrats.\n\nVill du göra det nu? -warn:updatelegs = Stäcklängderna kan behöva uppdateras efter att banan har ändrats. +warn:updatelegs = Sträcklängderna kan behöva uppdateras efter att banan har ändrats. Ingen deltagare vald = Ingen deltagare vald Från klubben = Från klubben Från laget = Från laget @@ -2293,7 +2293,7 @@ help:rest = MeOS REST API låter dig komma åt tävlingsdata via en webbanslutni Server startad på X = Server startad på X Inconsistent qualification rule, X = Inkonsekvent kvalifikationsregel, X help:LockStartList = MeOS uppdaterar inte löparna i en låst klass även om kvalificeringsresultaten ändras. -Kval-Final-Schema = Kval-Final-Schema +Kval/final-schema = Kval/final-schema Lås startlista = Lås startlista FilterNoCancel = Ej återbud CourseStartTime = Bana, starttid @@ -2323,3 +2323,89 @@ Finish order = Målgångsordning First to finish = Först i mål Individual result by finish time = Individuella resultat efter måltid Endast tidtagning = Endast tidtagning +AllPunches = Alla stämplingar +CoursePunches = Stämplingar (inom banan) +FilterNamedControl = Namngivna kontroller +FilterNotFinish = Ej målstämpling +LineBreak = Radbrytning +PunchAbsTime = Stämpling, klocktid +PunchTimeSinceLast = Tid mellan stämplingar +PunchTotalTime = Tid till stämpling +PunchName = Stämpling, kontrollnamn +PunchNamedSplit = Tid sedan föregående namngivna kontroll +PunchSplitTime = Tid sedan föregående kontroll +ClassLiveResult = Liveresultat (radiotider), klassvis +Felaktigt datum 'X' (Använd YYYY-MM-DD) = Felaktigt datum 'X' (Använd YYYY-MM-DD) +FilterAnyResult = Radiotid/resultat +Liveresultat, radiotider = Liveresultat, radiotider +PunchTotalTimeAfter = Tid efter vid kontroll +RunnerCheck = Tidpunkt för check +RunnerId = Deltagares externa Id +StartTimeClass = Starttid, klass +ask:outofmaps = Kartorna är slut. Vill du ändå anmäla i denna klass? +Varning: Kartorna är slut = Varning: Kartorna är slut +X går vidare, klass enligt ranking = X går vidare, klass enligt ranking +Vill du ta bort schemat? = Vill du ta bort schemat? +ask:removescheme = Det finns resultat som går förlorade om du tar bort schemat. Vill du fortsätta? +ClassKnockoutTotalResult = Klass, Knockout-totalresultat +Support intermediate legs = Stöd specificerad stafettsträcka +help:custom_text_lines = Du kan infoga löparspecifik data genom att skriva [Symbolnamn]. Tillgängliga symboler finns i listan till höger. Exempel: Bra kämpat [RunnerName]! +Importerar ranking = Importerar ranking +Klart. X värden tilldelade = Klart. X värden tilldelade +Felaktigt rankingformat i X. Förväntat: Y = Felaktigt rankingformat i X.\nFörväntat: Y +Importerar RAID patrull csv-fil = Importerar RAID patrull csv-fil +Varning: Följande deltagare har ett osäkert resultat = Varning: Följande deltagare har ett osäkert resultat +Direkt tidtagning = Direkt tidtagning +Klassval för 'X' = Klassval för 'X' +Endast tidtagning (utan banor) = Endast tidtagning (utan banor) +Knockout total = Knockout sammanställning +Varvräkning = Varvräkning +Varvräkning med mellantid = Varvräkning med mellantid +Without courses = Utan banor +Timekeeping = Tidtagning +Endast grundläggande (enklast möjligt) = Endast grundläggande (enklast möjligt) +Endast tidtagning (utan banor), stafett = Endast tidtagning (utan banor), stafett +Individuellt = Individuellt +Lag och stafett = Lag och stafett +Övrigt = Övrigt +htmlhelp = HTML kan exporteras som en strukturerad tabell, eller som ett fritt formaterat dokument (som mer liknar listorna i MeOS). Man kan också använda exportmallar för speciell formatering: kolumner, automatisk visning av sidor, rullning, etc. Du kan egna skapa mallar genom att lägga till '.template'-filter i MeOS datakatalog. Om du använder en mall finns ett antal parametrar att styra nedan. Exakt hur de tolkas beror på mallen.\n\nOm du väljer att sparas listan och inställningarna permanent i tävlingen. Du kan då komma åt listan genom att använda MeOS som webbserver (Automaten informationsserver) eller exportera listan till fil automatiskt med jämna mellanrum. +HTML Export = HTML Export +HTML Export för 'X' = HTML Export för 'X' +Lagra inställningar = Lagra inställningar +Kolumner = Kolumner +Rader = Rader +HTML formaterad genom listinställningar = HTML formaterad genom listinställningar +Begränsa antal rader per sida = Begränsa antal rader per sida +Färre slingor = Färre slingor +RunnerGrossTime = Deltagares tid före justering +TeamGrossTime = Lags tid före justering +Visa detaljerad rapport för viss deltagare = Visa detaljerad rapport för viss deltagare +Förhindra att laget deltar i någon omstart = Förhindra att laget deltar i någon omstart +Förhindra omstart = Förhindra omstart +Ej omstart = Ej omstart +Visa rubrik mellan listorna = Visa rubrik mellan listorna +Slå ihop med befintlig lista = Slå ihop med befintlig lista +Från löpardatabasen = Från löpardatabasen +Från löpardatabasen i befintliga klubbar = Från löpardatabasen i befintliga klubbar +Med direktanmälan = Med direktanmälan +Tillåt anmälan = Tillåt anmälan +Anyone = Alla +Bricknummer = Bricknummer +Anmäl andra = Anmäl andra +Anmälan mottagen = Anmälan mottagen +Automatisk omladdning = Automatisk omladdning +Till vilka klasser = Till vilka klasser +Vem får anmäla sig = Vem får anmäla sig +Anmälan måste hanteras manuellt = Anmälan måste hanteras manuellt +EFilterAPIEntry = Anmälda via API +Visa rubrik = Visa rubrik +Rad X är ogiltig = Rad X är ogiltig +Klassen X är listad flera gånger = Klassen X är listad flera gånger +Ogiltig starttid X = Ogiltig starttid X +Ogiltigt startintervall X = Ogiltigt startintervall X +Hittar inte klass X = Hittar inte klass X +MeOS utvecklinsstöd = MeOS utvecklinsstöd +info:pageswithcolumns = Visa listan en sida i taget med angivet antal kolumner. Ladda om data automatiskt efter varje varv. +Pages with columns = Sidor med kolumner +Pages with columns, no header = Sidor med kolumner utan rubrik +Externa adresser = Externa adresser diff --git a/code/testmeos.cpp b/code/testmeos.cpp index 9616940..321d5a1 100644 --- a/code/testmeos.cpp +++ b/code/testmeos.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -134,9 +134,11 @@ void TestMeOS::runProtected(bool protect) const { oe_main->useDefaultProperties(true); oe_main->setProperty("FirstTime", 0); oe_main->setProperty("TestPath", tp); - oe_main->getPropertyInt("UseEventor", 1); + oe_main->getPropertyInt("UseEventor", 1); + oe_main->setProperty("Interactive", 0); TabSI *tsi = dynamic_cast(gdi_main->getTabs().get(TabType::TSITab)); tsi->setMode(TabSI::ModeReadOut); + tsi->clearQueue(); //string pmOrig = oe_main->getPropertyString("PayModes", ""); //oe_main->setProperty("PayModes", ""); @@ -207,6 +209,10 @@ void TestMeOS::runProtected(bool protect) const { if (protect) { cleanup(); } + else { + gdi_main->addStringUT(1, 1, 1, "Test PASSED").setColor(colorDarkGreen); + gdi_main->refresh(); + } } void TestMeOS::runAll() const { diff --git a/code/testmeos.h b/code/testmeos.h index 1e5ccc7..9b9b0f0 100644 --- a/code/testmeos.h +++ b/code/testmeos.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/toolbar.cpp b/code/toolbar.cpp index 06ba95c..3674737 100644 --- a/code/toolbar.cpp +++ b/code/toolbar.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/toolbar.h b/code/toolbar.h index ccc27a8..1076b83 100644 --- a/code/toolbar.h +++ b/code/toolbar.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/xmlparser.cpp b/code/xmlparser.cpp index 7523e1c..c85cc83 100644 --- a/code/xmlparser.cpp +++ b/code/xmlparser.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/code/xmlparser.h b/code/xmlparser.h index b2c3598..144b54a 100644 --- a/code/xmlparser.h +++ b/code/xmlparser.h @@ -10,7 +10,7 @@ #endif // _MSC_VER > 1000 /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2018 Melin Software HB + Copyright (C) 2009-2019 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by