diff --git a/code/TabClass.cpp b/code/TabClass.cpp index 12951e2..1bc6a93 100644 --- a/code/TabClass.cpp +++ b/code/TabClass.cpp @@ -1585,10 +1585,14 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) wstring bib; bool doBibs = false; - + bool bibToVacant = true; if (gdi.hasWidget("Bib")) { bib = gdi.getText("Bib"); doBibs = gdi.isChecked("HandleBibs"); + if (gdi.hasWidget("VacantBib")) { + bibToVacant = gdi.isChecked("VacantBib"); + oe->getDI().setInt("NoVacantBib", bibToVacant ? 0 : 1); + } } wstring time = gdi.getText("FirstStart"); @@ -1680,7 +1684,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) throw std::exception("Not implemented"); if (doBibs) - oe->addBib(cid, leg, bib); + oe->addBib(cid, leg, bib, bibToVacant); // Clear input gdi.restore("", false); @@ -1703,6 +1707,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) } else if (bi.id=="HandleBibs") { gdi.setInputStatus("Bib", gdi.isChecked("HandleBibs")); + gdi.setInputStatus("VacantBib", gdi.isChecked("HandleBibs"), true); } else if (bi.id == "DoDeleteStart") { pClass pc=oe->getClass(ClassId); @@ -1878,10 +1883,19 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.fillRight(); gdi.popX(); gdi.addString("", 0, "Antal reserverade nummerlappsnummer mellan klasser:"); - gdi.dropLine(-0.1); + gdi.dropLine(-0.2); gdi.addInput("BibGap", itow(oe->getBibClassGap()), 5); - gdi.dropLine(3); + + gdi.dropLine(2.4); gdi.popX(); + + if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Vacancy)) { + bool bibToVacant = oe->getDCI().getInt("NoVacantBib") == 0; + gdi.addCheckbox("VacantBib", "Tilldela nummerlapp till vakanter", nullptr, bibToVacant); + gdi.dropLine(2.5); + gdi.popX(); + } + gdi.fillRight(); gdi.addButton("DoBibs", "Tilldela", ClassesCB).setDefault(); @@ -1914,11 +1928,17 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) pc->setBibMode(BibMode(teamBib.first)); } + bool bibToVacant = true; + if (gdi.hasWidget("VacantBib")) { + bibToVacant = gdi.isChecked("VacantBib"); + oe->getDI().setInt("NoVacantBib", bibToVacant ? 0 : 1); + } + pc->getDI().setString("Bib", getBibCode(bt, gdi.getText("Bib"))); pc->synchronize(); int leg = pc->getParentClass() ? -1 : 0; if (bt == AutoBibManual) { - oe->addBib(cid, leg, gdi.getText("Bib")); + oe->addBib(cid, leg, gdi.getText("Bib"), bibToVacant); } else { oe->setBibClassGap(gdi.getTextNo("BibGap")); @@ -3691,6 +3711,16 @@ void TabClass::saveClassSettingsTable(gdioutput &gdi) { } } + if (gdi.hasWidget("VacantBib")) { + bool vacantBib = gdi.isChecked("VacantBib"); + bool vacantBibStored = oe->getDCI().getInt("NoVacantBib") == 0; + + if (vacantBib != vacantBibStored) { + oe->getDI().setInt("NoVacantBib", vacantBib ? 0 : 1); + modifiedBib = true; + } + } + if (!modifiedFee.empty() && oe->getNumRunners() > 0) { bool updateFee = gdi.ask(L"ask:changedclassfee"); @@ -3724,10 +3754,18 @@ void TabClass::prepareForDrawing(gdioutput &gdi) { if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Bib)) { gdi.fillRight(); gdi.addString("", 0, "Antal reserverade nummerlappsnummer mellan klasser:"); - gdi.dropLine(-0.1); + gdi.dropLine(-0.2); gdi.addInput("BibGap", itow(oe->getBibClassGap()), 5); + + if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Vacancy)) { + bool bibToVacant = oe->getDCI().getInt("NoVacantBib") == 0; + gdi.dropLine(0.2); + gdi.setCX(gdi.getCX() + gdi.scaleLength(15)); + gdi.addCheckbox("VacantBib", "Tilldela nummerlapp till vakanter", nullptr, bibToVacant); + } + gdi.popX(); - gdi.dropLine(1.5); + gdi.dropLine(2.4); gdi.fillDown(); } @@ -3868,7 +3906,15 @@ void TabClass::drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClas gdi.addCheckbox("HandleBibs", "Tilldela nummerlappar:", ClassesCB, lastHandleBibs).setSynchData(&lastHandleBibs); gdi.dropLine(-0.2); gdi.addInput("Bib", L"", 10, 0, L"", L"Mata in första nummerlappsnummer, eller blankt för att ta bort nummerlappar"); - gdi.setInputStatus("Bib", lastHandleBibs); + + if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Vacancy)) { + bool bibToVacant = oe->getDCI().getInt("NoVacantBib") == 0; + gdi.dropLine(0.2); + gdi.addCheckbox("VacantBib", "Tilldela nummerlapp till vakanter", nullptr, bibToVacant); + gdi.setInputStatus("VacantBib", lastHandleBibs); + } + + gdi.setInputStatus("Bib", lastHandleBibs); gdi.fillDown(); gdi.dropLine(2.5); gdi.popX(); @@ -3968,6 +4014,7 @@ void TabClass::setMultiDayClass(gdioutput &gdi, bool hasMulti, oEvent::DrawMetho if (hasMulti) { gdi.check("HandleBibs", false); gdi.setInputStatus("Bib", false); + gdi.setInputStatus("VacantBib", false, true); } } diff --git a/code/TabList.cpp b/code/TabList.cpp index 6d179e5..9beddbc 100644 --- a/code/TabList.cpp +++ b/code/TabList.cpp @@ -636,7 +636,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) ListBoxInfo lbi; bool advancedResults = false; if (gdi.getSelectedItem("ListType", lbi)) { - currentListType=EStdListType(lbi.data); + currentListType = EStdListType(lbi.data); } else if (gdi.getSelectedItem("ResultType", lbi)) { currentListType = getTypeFromResultIndex(lbi.data); @@ -1230,6 +1230,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (lbi.id == "ListSelection") { gdi.getSelection(lbi.id, lastClassSelection); if (gdi.hasWidget("ResultType")) { + lastResultClassSelection = lastClassSelection; ListBoxInfo entry; gdi.getSelectedItem("ResultType", entry); gdi.setInputStatus("Generate", !lastClassSelection.empty() && int(entry.data) >= 0); @@ -3010,8 +3011,7 @@ void TabList::setResultOptionsFromType(gdioutput &gdi, int data) { gdi.setInputStatus("ShowInterResults", builtIn); gdi.setInputStatus("ShowSplits", builtIn); - - + set clsUnused; vector< pair > out; @@ -3022,22 +3022,14 @@ void TabList::setResultOptionsFromType(gdioutput &gdi, int data) { if (!out.empty() && lastLeg >= 0) gdi.selectItemByData("LegNumber", lastLeg); - //oe->fillLegNumbers(gdi, "LegNumber", li.isTeamList(), true); gdi.setInputStatus("InputNumber", false); } - else { - + else { gdi.setInputStatus("UseLargeSize", li.supportLarge); gdi.setInputStatus("InputNumber", li.supportParameter); - //gdi.setInputStatus("SplitAnalysis", li.supportSplitAnalysis); - //gdi.setInputStatus("ShowInterResults", li.supportInterResults); - //gdi.setInputStatus("PageBreak", li.supportPageBreak); - //gdi.setInputStatus("ClassLimit", li.supportClassLimit); - if (li.supportLegs) { gdi.enableInput("LegNumber"); - //oe->fillLegNumbers(gdi, "LegNumber", li.isTeamList(), true); set clsUnused; vector< pair > out; oe->fillLegNumbers(clsUnused, li.isTeamList(), true, out); @@ -3095,12 +3087,19 @@ void TabList::setResultOptionsFromType(gdioutput &gdi, int data) { gdi.setSelection("ListSelection", lastResultClassSelection); } - gdi.setInputStatus("Generate", data >= 0 && !lastResultClassSelection.empty()); + gdi.setInputStatus("Generate", data >= 0 && hasSelectedClass(gdi)); +} + +bool TabList::hasSelectedClass(gdioutput& gdi) { + set sel; + gdi.getSelection("ListSelection", sel); + return !sel.empty(); } void TabList::clearCompetitionData() { SelectedList = ""; lastResultClassSelection.clear(); + lastClassSelection.clear(); ownWindow = false; hideButtons = false; diff --git a/code/TabList.h b/code/TabList.h index 425653a..dfe4a4b 100644 --- a/code/TabList.h +++ b/code/TabList.h @@ -49,6 +49,8 @@ protected: static void createListButtons(gdioutput &gdi); + static bool hasSelectedClass(gdioutput& gdi); + void generateList(gdioutput &gdi, bool forceUpdate = false); void selectGeneralList(gdioutput &gdi, EStdListType type); diff --git a/code/TabRunner.cpp b/code/TabRunner.cpp index f93f927..9181a21 100644 --- a/code/TabRunner.cpp +++ b/code/TabRunner.cpp @@ -207,8 +207,8 @@ void TabRunner::selectRunner(gdioutput &gdi, pRunner r) { vector delta; vector place; vector after; - vector placeAcc; - vector afterAcc; + vector placeAcc; + vector afterAcc; r->getSplitAnalysis(delta); r->getLegTimeAfter(after); @@ -221,13 +221,13 @@ void TabRunner::selectRunner(gdioutput &gdi, pRunner r) { for (size_t k = 0; k < delta.size(); k++) { out += itow(place[k]); if (k < placeAcc.size()) - out += L" (" + itow(placeAcc[k]) + L")"; + out += L" (" + itow(placeAcc[k].get(false)) + L")"; if (after[k] > 0) out += L" +" + formatTimeMS(after[k], false); - if (k < afterAcc.size() && afterAcc[k]>0) - out += L" (+" + formatTimeMS(afterAcc[k], false) + L")"; + if (k < afterAcc.size() && afterAcc[k].get(false)>0) + out += L" (+" + formatTimeMS(afterAcc[k].get(false), false) + L")"; if (delta[k] > 0) out += L" B: " + formatTimeMS(delta[k], false); @@ -463,7 +463,7 @@ int TabRunner::searchCB(gdioutput &gdi, int type, void *data) { bool formMode = currentMode == 0; - vector< pair > runners; + vector> runners; oe->fillRunners(runners, !formMode, formMode ? 0 : oEvent::RunnerFilterShowAll, filter); if (filter.size() == runners.size()){ @@ -813,6 +813,15 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) else if (bi.id == "ListenReadout") { listenToPunches = gdi.isChecked(bi.id); } + else if (bi.id == "HideControls") { + hideReportControls = true; + listenToPunches = true; + PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TRunnerTab, 0); + } + /*else if (bi.id == "ShowReportHeader") { + showReportHeader = gdi.isChecked(bi.id); + PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TRunnerTab, 0); + }*/ else if (bi.id=="Unpair") { ListBoxInfo lbi; int cid = bi.getExtraInt(); @@ -1225,7 +1234,14 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) return 0; } runnerId = bi.data; - //loadPage(gdi); + PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TRunnerTab, 0); + } + else if (bi.id == "NumCols") { + numReportColumn = bi.data; + PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TRunnerTab, 0); + } + else if (bi.id == "NumRows") { + numReportRow = bi.data; PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TRunnerTab, 0); } else if (bi.id=="RClass") { @@ -1322,24 +1338,20 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) cellAction(gdi, ei.getData(), b); } - else if ((ei.id == "DataUpdate") && listenToPunches && currentMode == 5) { - if (ei.getData() > 0) { - runnerId = ei.getData(); + else if (currentMode == 5 && ei.id == "PunchCard") { + if (listenToPunches && ei.getData() > 0) { + addToReport(ei.getData(), true); + + loadPage(gdi); } - loadPage(gdi); } - else if ((ei.id == "ReadCard") && - (listenToPunches || oe->isReadOnly()) && currentMode == 5) { - if (ei.getData() > 0) { - vector rs; - oe->getRunnersByCardNo(ei.getData(), true, oEvent::CardLookupProperty::Any, rs); - if (!rs.empty()) { - runnersToReport.resize(rs.size()); - for (size_t k = 0; kgetId(), false); - } - runnerId = 0; - } + else if (currentMode == 5 && ei.id == "DataUpdate" && currentMode == 5) { + showRunnerReport(gdi); + } + else if (currentMode == 5 && ei.id == "ReadCard" && + (listenToPunches || oe->isReadOnly())) { + if (ei.getData() > 0) + addToReport(ei.getData(), oe->isReadOnly()); loadPage(gdi); } } @@ -1610,127 +1622,392 @@ void TabRunner::setCardNo(gdioutput &gdi, int cardNo) { } } +string TabRunner::computeKeyForReport() { + string key = itos(runnersToReport.size()); + for (auto &id : runnersToReport) { + key += "|" + itos(id.first); + pRunner r = oe->getRunner(id.first, 0); + if (r) { + key += gdioutput::toUTF8(r->getCompleteIdentification() + r->getClass(true)); + key += itos(r->getStatus()) + itos(r->getFinishTime()) + itos(r->getStartTime()); + vector pl; + oe->getPunchesForRunner(id.first, true, pl); + for (auto p : pl) + key += itos(p->getTimeInt()); + + if (r->getCard()) { + auto ch = r->getCard()->getCardHash(); + key += itos(ch.first) + itos(ch.second); + } + } + } + return key; +} + void TabRunner::showRunnerReport(gdioutput &gdi) { + string oldHash, newHash = computeKeyForReport(); + + if (gdi.getData("ReportHash", oldHash) && oldHash == newHash) + return; + gdi.clearPage(true); + gdi.setData("ReportHash", newHash); currentMode = 5; if (!ownWindow && !oe->isReadOnly()) addToolbar(gdi); - else if (oe->isReadOnly()) + else if (oe->isReadOnly() && !listenToPunches) gdi.addString("", fontLarge, makeDash(L"MeOS - Resultatkiosk")).setColor(colorDarkBlue); - gdi.dropLine(); + if (!hideReportControls) { + bool any = false; + gdi.pushX(); + gdi.fillRight(); - gdi.pushX(); - gdi.fillRight(); - - gdi.addSelection("ReportRunner", 300, 300, RunnerCB); - oe->fillRunners(gdi, "ReportRunner", true, oEvent::RunnerFilterShowAll | oEvent::RunnerCompactMode); - gdi.selectItemByData("ReportRunner", runnerId); - - if (!oe->isReadOnly()) { - if (!ownWindow) { - gdi.addButton("Kiosk", "Resultatkiosk", RunnerCB); - gdi.addButton("Window", "Eget fönster", RunnerCB, "Öppna i ett nytt fönster."); + if (!oe->isReadOnly() || !listenToPunches) { + gdi.dropLine(); + gdi.addSelection("ReportRunner", 300, 300, RunnerCB); + oe->fillRunners(gdi, "ReportRunner", true, oEvent::RunnerFilterShowAll | oEvent::RunnerCompactMode); + gdi.selectItemByData("ReportRunner", runnerId); + gdi.autoGrow("ReportRunner"); + any = true; } - gdi.dropLine(0.2); - gdi.addCheckbox("ListenReadout", "Visa senast inlästa deltagare", RunnerCB, listenToPunches); - } - gdi.dropLine(3); + if (!oe->isReadOnly() && !hideReportControls) { + if (!ownWindow) { + gdi.addButton("Kiosk", "Resultatkiosk", RunnerCB); + gdi.addButton("Window", "Eget fönster", RunnerCB, "Öppna i ett nytt fönster."); + } + else { + gdi.addButton("HideControls", "Dölj inställningar", RunnerCB); + } + + any = true; + gdi.dropLine(0.2); + gdi.addCheckbox("ListenReadout", "Visa senast inlästa deltagare", RunnerCB, listenToPunches); + gdi.dropLine(2); + gdi.popX(); + vector> options; + for (int i = 1; i <= 10; i++) + options.emplace_back(itow(i), i); + + gdi.addString("", 0, "Layout"); + + gdi.addSelection("NumRows", 100, 200, RunnerCB); + gdi.addString("", 0, "rader"); + gdi.addItem("NumRows", options); + gdi.selectItemByData("NumRows", numReportRow); + + gdi.addSelection("NumCols", 100, 200, RunnerCB); + gdi.addString("", 0, "kolumner"); + gdi.addItem("NumCols", options); + gdi.selectItemByData("NumCols", numReportColumn); + + //gdi.addCheckbox("ShowReportHeader", "Visa sidhuvud", RunnerCB); + } + + if (any) + gdi.dropLine(3); + } gdi.popX(); gdi.registerEvent("DataUpdate", RunnerCB); gdi.registerEvent("ReadCard", RunnerCB); - - if (runnerId > 0) { - runnersToReport.resize(1); - runnersToReport[0] = make_pair(runnerId, false); - } - generateRunnerReport(*oe, gdi, runnersToReport); + gdi.registerEvent("PunchCard", RunnerCB); + gdi.setData("DataSync", 1); + gdi.setData("PunchSync", 1); - if (runnersToReport.size() == 1) + if (runnerId > 0) { + bool found = false; + addToReport(runnerId); + } + while (runnersToReport.size() > numReportRow * numReportColumn) + runnersToReport.pop_back(); + + generateRunnerReport(*oe, gdi, numReportColumn, numReportRow, true, runnersToReport); + + if (runnersToReport.size() > 0) runnerId = runnersToReport[0].first; } - void TabRunner::generateRunnerReport(oEvent &oe, gdioutput &gdi, vector> &runnersToReport) { - oe.synchronizeList({ oListId::oLRunnerId, oListId::oLTeamId, oListId::oLPunchId }); - gdi.fillDown(); +void TabRunner::addToReport(int id) { + bool found = false; + for (auto& rr : runnersToReport) { + if (rr.first == id) + found = true; + } + if (!found) { + runnersToReport.emplace_front(id, false); + } +} - cTeam t = 0; +void TabRunner::addToReport(int cardNo, bool punchForShowReport) { + vector rs; + oe->getRunnersByCardNo(cardNo, true, oEvent::CardLookupProperty::Any, rs); + + if (!punchForShowReport) { + // Take away runners with no card + vector rsFilter; + for (pRunner r : rs) { + if (r->getCard()) + rsFilter.push_back(r); + } + rs.swap(rsFilter); + } + + if (!rs.empty()) { + if (rs.size() == 1) { + addToReport(rs[0]->getId()); + } + else { + map> runnersPerTeam; + for (pRunner r : rs) { + if (r->getTeam()) + runnersPerTeam[r->getTeam()->getId()].push_back(r); + else + runnersPerTeam[0].push_back(r); + } + + for (auto& rpt : runnersPerTeam) { + if (rpt.second.size() == 1) { + addToReport(rpt.second[0]->getId()); + } + else if (rpt.first == 0) { + sort(rpt.second.begin(), rpt.second.end(), + [](const pRunner& a, const pRunner& b) {return unsigned(a->getStartTime() - 1) < unsigned(b->getStartTime() - 1); }); + // Take the last competitor using the card + addToReport(rpt.second.back()->getId()); + } + else { + sort(rpt.second.begin(), rpt.second.end(), + [](const pRunner& a, const pRunner& b) {return a->getLegNumber() < b->getLegNumber(); }); + bool hasResultAny = false; + bool done = false; + for (pRunner r : rpt.second) { + if (r->hasFinished()) + hasResultAny = true; + else if (hasResultAny) { + // Prefer to report the next runner to run + addToReport(r->getId()); + done = true; + break; + } + } + if (!done) { + if (hasResultAny) { + // Take the last one (final result) + addToReport(rpt.second.back()->getId()); + } + else { + // None finished. Take first + addToReport(rpt.second.front()->getId()); + } + } + } + } + } + /*runnersToReport.resize(rs.size()); + for (size_t k = 0; kgetId(), false);*/ + } + runnerId = 0; +} + + +void TabRunner::generateRunnerReport(oEvent &oe, gdioutput &gdi, + int numX, int numY, + bool onlySelectedRunner, + const deque> &runnersToReport) { + oe.synchronizeList({ oListId::oLRunnerId, oListId::oLTeamId, oListId::oLPunchId }); + gdi.fillDown(); + int xx, yy; + int margin = gdi.scaleLength(16); + gdi.getTargetDimension(xx, yy); + + int maxWidth = max(gdi.scaleLength(130 * 3) + 2*margin, xx / numX - margin * (numX + 1)); + if (numX == 1 && numY == 1) + maxWidth = min(maxWidth, gdi.scaleLength(130)*6+margin); + + bool frame = true; + + vector tList; set clsSet; + for (size_t k = 0; k < runnersToReport.size(); k++) { pRunner r = oe.getRunner(runnersToReport[k].first, 0); if (!r) continue; clsSet.insert(r->getClassId(true)); - if (r && r->getTeam()) { + pTeam t = r->getTeam(); + if (t) { pClass cls = r->getClassRef(true); if (cls && cls->getClassType() != oClassRelay) continue; - if (t == 0) - t = r->getTeam(); + bool added = count(tList.begin(), tList.end(), t) > 0; + if (added) + continue; + tList.push_back(t); } } 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(oe, gdi, runnersToReport[k].first, runnersToReport[k].second); - } - else { - oe.calculateTeamResults(clsSet, oEvent::ResultType::ClassResult); + RECT rcFrame; + + auto drawBox = [&gdi](RECT &rcFrame) { + RECT rc = rcFrame; + int mg = gdi.scaleLength(5); + rc.left -= mg; + rc.top -= mg; + rc.right += mg; + rc.bottom += mg; + gdi.addRectangle(rc, GDICOLOR::colorLightCyan, true, true); + }; - 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 && r->hasOnCourseResult()) - selHasRes = true; - } + int baseX = gdi.getCX(); + int baseY = gdi.getCY(); + vector rcList; - wstring tInfo = t->getName(); - if (t->statusOK(true, true)) { - tInfo += L", " + t->getRunningTimeS(true, SubSecond::Auto) + lang.tl(", Placering: ") + t->getPlaceS(); - if (t->getTimeAfter(-1, true) > 0) - tInfo += L", +" + formatTime(t->getTimeAfter(-1, true)); - } - else if (t->getStatusComputed(true) != StatusUnknown) { - tInfo += L" " + t->getStatusS(true, true); - } + auto updatePositionDrawBox = [&](bool force) { + if (!force) + rcList.push_back(rcFrame); - gdi.addStringUT(fontMediumPlus, t->getClass(true)); - gdi.addStringUT(boldLarge, tInfo); - gdi.dropLine(); - - bool visitedSelected = false; - for (int leg = 0; leg < t->getNumRunners(); leg++) { - if (selHasRes && visitedSelected) - break; - - pRunner r = t->getRunner(leg); - pRunner nextR = t->getRunner(leg + 1); - bool nextSelected = nextR && selectedRunners.count(nextR->getId()); - - if (r) { - bool selected = selectedRunners.count(r->getId()) > 0; - - if (selHasRes) { - runnerReport(oe, gdi, r->getId(), !selected); - } - else { - runnerReport(oe, gdi, r->getId(), !nextSelected); - } - - visitedSelected |= selected; + if ((force && !rcList.empty()) || rcList.size() == numX) { + int maxYP = 0; + for (RECT& rc : rcList) + maxYP = max(maxYP, rc.bottom); + for (RECT& rc : rcList) { + rc.bottom = maxYP; + drawBox(rc); } + rcList.clear(); + baseY = maxYP + margin; + } + + gdi.setCY(baseY); + gdi.setCX(baseX + (maxWidth + margin) * rcList.size()); + }; + + for (size_t k = 0; k < runnersToReport.size(); k++) { + pRunner r = oe.getRunner(runnersToReport[k].first, 0); + if (!r) + continue; + + if (count(tList.begin(), tList.end(), r->getTeam()) == 0) { + runnerReport(oe, gdi, runnersToReport[k].first, runnersToReport[k].second, maxWidth, rcFrame); + updatePositionDrawBox(false); } } + if (tList.size() > 0) { + oe.calculateTeamResults(clsSet, oEvent::ResultType::ClassResult); + for (const oTeam *t : tList) { + teamReport(oe, gdi, t, onlySelectedRunner, runnersToReport, maxWidth, rcFrame); + updatePositionDrawBox(false); + } + } + + updatePositionDrawBox(true); } -void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { +void TabRunner::teamReport(oEvent& oe, gdioutput& gdi, + const oTeam* t, + bool onlySelectedRunner, + const deque>& runnersToReport, + int maxWidth, + RECT& rc) { + rc.top = gdi.getCY(); + rc.left = gdi.getCX(); + rc.right = rc.left + maxWidth; + + set selectedRunners; + bool selHasPartialRes = false; + for (size_t k = 0; k < runnersToReport.size(); k++) { + pRunner r = oe.getRunner(runnersToReport[k].first, 0); + if (r && r->getTeam() == t) { + selectedRunners.insert(runnersToReport[k].first); + if (r && r->hasOnCourseResult()) + selHasPartialRes = true; //Partial (radio) or complete result + } + } + + wstring tInfo = t->getName(); + if (t->statusOK(true, true)) { + tInfo += L", " + t->getRunningTimeS(true, SubSecond::Auto) + lang.tl(", Placering: ") + t->getPlaceS(); + if (t->getTimeAfter(-1, true) > 0) + tInfo += L", +" + formatTime(t->getTimeAfter(-1, true)); + } + else if (t->getStatusComputed(true) != StatusUnknown) { + tInfo += L" " + t->getStatusS(true, true); + } + + gdi.addStringUT(fontMediumPlus, t->getClass(true)); + gdi.addStringUT(boldLarge, tInfo); + gdi.dropLine(); + + pClass cls = t->getClassRef(false); + + bool visitedSelected = false; + for (int leg = 0; leg < t->getNumRunners(); leg++) { + if ((selHasPartialRes || onlySelectedRunner) && visitedSelected) + break; + + pRunner r = t->getRunner(leg); + + pRunner nextR = t->getRunner(leg + 1); + bool nextSelected = false; + if (cls) { + // Check if leg has the selected runner (parallel legs) + int legNr, legOrd; + cls->splitLegNumberParallel(leg, legNr, legOrd); + int nextLeg = leg; + while (++nextLeg < t->getNumRunners()) { + int legNrN, legOrdN; + cls->splitLegNumberParallel(leg, legNrN, legOrdN); + if (legNrN == legNr + 1) { + nextR = t->getRunner(nextLeg); + nextSelected = nextR && selectedRunners.count(nextR->getId()); + if (nextSelected) + break; + } + } + } + else { + nextSelected = nextR && selectedRunners.count(nextR->getId()); + } + + if (r) { + bool selected = selectedRunners.count(r->getId()) > 0; + + if (onlySelectedRunner && !selected) { + if (!nextSelected) + continue; // Always skip if next is not selected + if (nextR->hasResult()) + continue; // Only include previous if next does not have results + } + RECT dmy; + if (selHasPartialRes) { + // The selected runner has some result. Focus on that + runnerReport(oe, gdi, r->getId(), !selected, maxWidth, dmy); + } + else { + // The selected runner has not started. Focus on previous result + runnerReport(oe, gdi, r->getId(), !nextSelected, maxWidth, dmy); + } + + visitedSelected |= selected; + } + } + rc.bottom = gdi.getCY(); +} + +void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, + int id, bool compact, + int maxWidth, RECT &rc) { + + rc.top = gdi.getCY(); + rc.left = gdi.getCX(); + rc.right = rc.left + maxWidth; + pRunner r = oe.getRunner(id, 0); if (!r || ! r->getClassRef(false)) return; @@ -1739,7 +2016,8 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { gdi.fillDown(); if (r->getTeam() == 0) { gdi.addStringUT(fontMediumPlus, r->getClass(true)); - gdi.addStringUT(boldLarge, r->getCompleteIdentification()); + gdi.addStringUT(gdi.getCY(), gdi.getCX(), boldLarge, + r->getCompleteIdentification(), maxWidth - 4); } else { wstring s; @@ -1784,20 +2062,31 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { gdi.popX(); gdi.fillRight(); + int ww = gdi.scaleLength(120); + auto optionalBreakLine = [&] { + if (gdi.getCX() + ww > rc.left + maxWidth) { + gdi.popX(); + gdi.dropLine(); + } + }; - if (r->getStartTime() > 0) + if (r->getStartTime() > 0) { + optionalBreakLine(); gdi.addString("", fontMedium, L"Starttid: X #" + r->getStartTimeCompact()); - - if (r->getFinishTime() > 0) + } + if (r->getFinishTime() > 0) { + optionalBreakLine(); gdi.addString("", fontMedium, L"Måltid: X #" + r->getFinishTimeS(false, SubSecond::Auto)); - + } const wstring &after = oe.formatListString(lRunnerTimeAfter, r); if (!after.empty()) { + optionalBreakLine(); gdi.addString("", fontMedium, L"Tid efter: X #" + after); } const wstring &lost = oe.formatListString(lRunnerLostTime, r); if (!lost.empty()) { + optionalBreakLine(); gdi.addString("", fontMedium, L"Bomtid: X #" + lost).setColor(colorDarkRed); } @@ -1813,17 +2102,28 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { int xp = gdi.getCX(); int yp = gdi.getCY(); int xw = gdi.scaleLength(130); - int cx = xp; + int mg4 = gdi.scaleLength(4); + int cx = xp + mg4; int limit = (9*xw)/10; int lh = gdi.getLineHeight(); + int maxTimesPerLine = min(10, max(3, maxWidth / xw)); + + auto drawBox = [&gdi, xw, mg4, lh](int yp, int cx, GDICOLOR color) { + RECT rc; + rc.top = yp - mg4 / 2; + rc.bottom = yp + lh * 5 - mg4; + rc.left = cx - mg4; + rc.right = cx + xw - mg4 * 2; + gdi.addRectangle(rc, color); + }; if (crs && r->getStatus() != StatusUnknown) { int nc = crs->getNumControls(); vector delta; vector place; vector after; - vector placeAcc; - vector afterAcc; + vector placeAcc; + vector afterAcc; r->getSplitAnalysis(delta); r->getLegTimeAfter(after); @@ -1844,7 +2144,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { else gdi.addStringUT(yp, cx, boldText, name, limit); - wstring split = r->getSplitTimeS(k, false); + wstring split = r->getSplitTimeS(k, false, SubSecond::Off); int bestTime = 0; if ( k < int(after.size()) && after[k] >= 0) @@ -1865,14 +2165,14 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { gdi.addStringUT(yp + lh, cx, fontMedium, split, limit); if (k>0 && k < int(placeAcc.size())) { - split = r->getPunchTimeS(k, false, false, SubSecond::Auto); - wstring pl = placeAcc[k] > 0 ? itow(placeAcc[k]) : L"-"; + split = r->getPunchTimeS(k, false, false, false, SubSecond::Auto); + wstring pl = placeAcc[k].get(false) > 0 ? itow(placeAcc[k].get(false)) : L"-"; if (k < int(afterAcc.size()) ) { - if (afterAcc[k] > 0) - split += L" (" + pl + L", +" + formatTimeMS(afterAcc[k], false) + L")"; - else if (placeAcc[k] == 1) + if (afterAcc[k].get(false) > 0) + split += L" (" + pl + L", +" + formatTimeMS(afterAcc[k].get(false), false) + L")"; + else if (placeAcc[k].get(false) == 1) split += lang.tl(" (ledare)"); - else if (placeAcc[k] > 0) + else if (placeAcc[k].get(false) > 0) split += L" " + pl; } gdi.addStringUT(yp + 2*lh, cx, fontMedium, split, limit).setColor(colorDarkBlue); @@ -1885,18 +2185,10 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { colorMediumDarkRed : colorMediumRed; } - RECT rc; - rc.top = yp - 2; - rc.bottom = yp + lh*5 - 4; - rc.left = cx - 4; - rc.right = cx + xw - 8; - - gdi.addRectangle(rc, color); - + drawBox(yp, cx, color); cx += xw; - - if (k % 6 == 5) { - cx = xp; + if ( (k+1) % maxTimesPerLine == 0) { + cx = xp + mg4; yp += lh * 5; } } @@ -1950,26 +2242,19 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { GDICOLOR color = colorDefault; - RECT rc; - rc.top = yp - 2; - rc.bottom = yp + lh*5 - 4; - rc.left = cx - 4; - rc.right = cx + xw - 8; - - gdi.addRectangle(rc, color); + drawBox(yp, cx, color); cx += xw; - if (k % 6 == 5) { - cx = xp; + if ((k + 1) % maxTimesPerLine == 0) { + cx = xp + mg4; yp += lh * 5; } } } - gdi.dropLine(3); gdi.popX(); + rc.bottom = gdi.getCY(); } - void TabRunner::showVacancyList(gdioutput &gdi, const string &method, int classId) { gdi.clearPage(true); diff --git a/code/TabRunner.h b/code/TabRunner.h index 30e58e3..6d745aa 100644 --- a/code/TabRunner.h +++ b/code/TabRunner.h @@ -23,6 +23,7 @@ #include "tabbase.h" #include "Printer.h" #include "autocompletehandler.h" +#include class Table; struct AutoCompleteRecord; @@ -43,6 +44,15 @@ private: void selectRunner(gdioutput &gdi, pRunner r); + int numReportRow = 1; + int numReportColumn = 1; + bool hideReportControls = false; + bool showReportHeader = true; + void addToReport(int cardNo, bool punchForShowReport); + void addToReport(int id); + + string computeKeyForReport(); + wstring lastSearchExpr; unordered_set lastFilter; DWORD timeToFill; @@ -64,7 +74,7 @@ private: int runnerId; bool ownWindow; bool listenToPunches; - vector< pair > runnersToReport; + deque> runnersToReport; vector unknown_dns; vector known_dns; @@ -76,7 +86,19 @@ private: PrinterObject splitPrinter; void showRunnerReport(gdioutput &gdi); - static void runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compactReport); + + static void runnerReport(oEvent &oe, gdioutput &gdi, + int id, bool compactReport, + int maxWidth, + RECT& rc); + + static void teamReport(oEvent& oe, gdioutput& gdi, + const oTeam *team, + bool onlySelectedRunner, + const deque> &runners, + int maxWidth, + RECT &rc); + void showVacancyList(gdioutput &gdi, const string &method="", int classId=0); void showCardsList(gdioutput &gdi); @@ -126,7 +148,11 @@ public: bool loadPage(gdioutput &gdi); bool loadPage(gdioutput &gdi, int runnerId); - static void generateRunnerReport(oEvent &oe, gdioutput &gdi, vector> &runnersToReport); + static void generateRunnerReport(oEvent &oe, + gdioutput &gdi, + int numX, int numY, + bool onlySelectedRunner, + const deque> &runnersToReport); TabRunner(oEvent *oe); ~TabRunner(void); diff --git a/code/TabSI.cpp b/code/TabSI.cpp index 5289b77..6a13c67 100644 --- a/code/TabSI.cpp +++ b/code/TabSI.cpp @@ -84,7 +84,6 @@ TabSI::TabSI(oEvent* poe) :TabBase(poe), activeSIC(ConvertedTimeStatus::Unknown) minRunnerId = 0; inputId = 0; printErrorShown = false; - NC = 8; splitPrinter.onlyChanged = false; } @@ -145,8 +144,7 @@ string TabSI::typeFromSndType(SND s) { return ""; } -int TabSI::siCB(gdioutput& gdi, int type, void* data) -{ +int TabSI::siCB(gdioutput& gdi, int type, void* data) { if (type == GUI_BUTTON) { ButtonInfo bi = *(ButtonInfo*)data; @@ -351,6 +349,12 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) ListBoxInfo lbi; if (gdi.getSelectedItem("ComPort", lbi)) { + if (lbi.data == 9999) { + showTestingPanel = !showTestingPanel; + loadPage(gdi); + return 0; + } + swprintf_s(bf, 64, L"COM%d", lbi.getDataInt()); wstring port = bf; @@ -705,11 +709,20 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) sic.CardNumber = gdi.getTextNo("SI"); int f = convertAbsoluteTimeHMS(gdi.getText("Finish"), oe->getZeroTimeNum()); int s = convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum()); + int c = convertAbsoluteTimeHMS(gdi.getText("Check"), oe->getZeroTimeNum()); + + testStartTime.clear(); + testFinishTime.clear(); + testCheckTime.clear(); + testCardNumber = 0; + if (f < s) { f += 24 * timeConstHour; } sic.FinishPunch.Time = f % (24 * timeConstHour); sic.StartPunch.Time = s % (24 * timeConstHour); + sic.CheckPunch.Time = c % (24 * timeConstHour); + if (!gdi.isChecked("HasFinish")) { sic.FinishPunch.Code = -1; sic.FinishPunch.Time = 0; @@ -720,9 +733,20 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) sic.StartPunch.Time = 0; } + if (!gdi.isChecked("HasCheck")) { + sic.CheckPunch.Code = -1; + sic.CheckPunch.Time = 0; + } + + if (NC > testControls.size()) + testControls.resize(NC); + + for (int i = 0; i < NC; i++) + testControls[i] = gdi.getTextNo("C" + itos(i + 1)); + double t = 0.1; for (sic.nPunch = 0; sic.nPunchgetZeroTimeNum()); - if (f > 0) { - sic.FinishPunch.Time = f; - sic.FinishPunch.Code = 1; + int t = convertAbsoluteTimeHMS(gdi.getText("PunchTime"), oe->getZeroTimeNum()); + testPunchTime.clear(); + testCardNumber = 0; + + if (t > 0) { + if (testType == oPunch::PunchStart) { + sic.StartPunch.Time = t; + sic.StartPunch.Code = 1; + } + else if (testType == oPunch::PunchFinish) { + sic.FinishPunch.Time = t; + sic.FinishPunch.Code = 1; + } + else if (testType == oPunch::PunchCheck) { + sic.CheckPunch.Time = t; + sic.CheckPunch.Code = 1; + } + else { + sic.Punch[sic.nPunch].Code = gdi.getTextNo("ControlNumber"); + sic.Punch[sic.nPunch].Time = t; + sic.nPunch = 1; + } sic.punchOnly = true; + sic.convertedTime = ConvertedTimeStatus::Hour24; gSI->addCard(sic); return 0; } - - int s = convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum()); - if (s > 0) { - sic.StartPunch.Time = s; - sic.StartPunch.Code = 1; - sic.punchOnly = true; - gSI->addCard(sic); - return 0; - } - - 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") { int origin = bi.getExtraInt(); @@ -1411,14 +1438,20 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) updateEntryInfo(gdi); } else if (bi.id == "ComPort") { - wchar_t bf[64]; + bool active = true; + if (bi.data == 9999) + active = showTestingPanel; + else { + wchar_t bf[64]; + if (bi.text.substr(0, 3) != L"TCP") + swprintf_s(bf, 64, L"COM%d", bi.getDataInt()); + else + wcscpy_s(bf, L"TCP"); - if (bi.text.substr(0, 3) != L"TCP") - swprintf_s(bf, 64, L"COM%d", bi.getDataInt()); - else - wcscpy_s(bf, L"TCP"); + active = gSI->isPortOpen(bf); + } - if (gSI->isPortOpen(bf)) + if (active) gdi.setText("StartSI", lang.tl("Koppla ifrån")); else gdi.setText("StartSI", lang.tl("Aktivera")); @@ -1463,9 +1496,15 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) updateEntryInfo(gdi); } else if (bi.id == "NC") { + readTestData(gdi); NC = bi.data; PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TSITab, 0); } + else if (bi.id == "TestType") { + readTestData(gdi); + testType = bi.data; + PostMessage(gdi.getHWNDTarget(), WM_USER + 2, TSITab, 0); + } } else if (type == GUI_LINK) { TextInfo ti = *(TextInfo*)data; @@ -1483,6 +1522,13 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) runnerMatchedId = r->getId(); } } + else if (ti.id == "edit") { + int rId = ti.getExtraInt(); + if (oe->getRunner(rId, 0)) { + TabRunner& tr = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); + tr.loadPage(gdi, rId); + } + } } else if (type == GUI_COMBO) { ListBoxInfo bi = *(ListBoxInfo*)data; @@ -1672,16 +1718,25 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data) } else if (ii.id == "SI") { 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); - int f = r->getStartTime() + (2800 + rand() % 1200) * timeConstSecond; - gdi.setText("Finish", oe->getAbsTime(f)); + if (testType == 0 && r) { + if (r->getStartTime() > 0) { + gdi.setText("Start", r->getStartTimeS()); + gdi.check("HasStart", false); + int f = r->getStartTime() + (2800 + rand() % 1200) * timeConstSecond; + gdi.setText("Finish", oe->getAbsTime(f)); + int c = r->getStartTime() - (120 + rand() % 30) * timeConstSecond; + gdi.setText("Check", oe->getAbsTime(c)); + } + pCourse pc = r->getCourse(false); if (pc) { + if (testControls.size() < pc->getNumControls()) + testControls.resize(pc->getNumControls()); for (int n = 0; n < pc->getNumControls(); n++) { - if (pc->getControl(n) && n < NC) { - gdi.setText("C" + itos(n + 1), pc->getControl(n)->getFirstNumber()); + if (pc->getControl(n)) { + testControls[n] = pc->getControl(n)->getFirstNumber(); + if (n < NC) + gdi.setText("C" + itos(n + 1), testControls[n]); } } } @@ -1833,7 +1888,11 @@ void TabSI::refillComPorts(gdioutput& gdi) gdi.addItem("ComPort", L"TCP [OK]", active); } else - gdi.addItem("ComPort", L"TCP"); + gdi.addItem("ComPort", L"TCP", 0); + + gdi.addItem("ComPort", lang.tl("Testning"), 9999); + if (showTestingPanel) + active = 9999; if (addTestPort) gdi.addItem("ComPort", L"TEST"); @@ -1909,7 +1968,7 @@ void TabSI::showReadCards(gdioutput& gdi, vector& cards) if (r != 0) gdi.addStringUT(yp, xp + off[1], 0, r->getName(), off[2] - off[1] + margin); - gdi.addStringUT(yp, xp + off[2], 0, oe->getAbsTime(cards[k].FinishPunch.Time)); + gdi.addStringUT(yp, xp + off[2], 0, formatTimeHMS(cards[k].FinishPunch.Time)); yp += gdi.getLineHeight(); } } @@ -1941,59 +2000,122 @@ bool TabSI::loadPage(gdioutput& gdi) { firstLoadedAfterNew = false; } -#ifdef _DEBUG - gdi.fillRight(); - gdi.pushX(); - gdi.addInput("SI", L"", 10, SportIdentCB, L"SI"); - int s = timeConstHour + (rand() % 60) * timeConstMinute; - int f = s + timeConstHour / 2 + ((rand() % 3600) / 3) * timeConstSecond; - gdi.setCX(gdi.getCX() + gdi.getLineHeight()); + if (showTestingPanel) { + RECT rc; + rc.left = gdi.getCX(); + rc.top = gdi.getCY(); - gdi.dropLine(1.4); - gdi.addCheckbox("HasStart", ""); - gdi.dropLine(-1.4); - gdi.setCX(gdi.getCX() - gdi.getLineHeight()); - gdi.addInput("Start", oe->getAbsTime(s), 6, 0, L"Start"); + gdi.setCX(gdi.getCX() + gdi.scaleLength(4)); + gdi.setCY(gdi.getCY() + gdi.scaleLength(4)); - gdi.dropLine(1.4); - gdi.addCheckbox("HasFinish", ""); - gdi.dropLine(-1.4); - gdi.setCX(gdi.getCX() - gdi.getLineHeight()); + gdi.addString("", fontMediumPlus, "Inmatning Testning"); - gdi.addInput("Finish", oe->getAbsTime(f), 6, 0, L"Mål"); - gdi.addSelection("NC", 45, 200, SportIdentCB, L"NC"); - const int src[11] = { 33, 34, 45, 50, 36, 38, 59, 61, 62, 67, 100 }; + gdi.fillRight(); + gdi.pushX(); - for (int i = 0; i < 32; i++) - gdi.addItem("NC", itow(i), i); + gdi.addSelection("TestType", 100, 100, SportIdentCB, L"Typ:"); + gdi.addItem("TestType", lang.tl("Avläsning"), 0); + gdi.addItem("TestType", lang.tl("Mål"), 2); + gdi.addItem("TestType", lang.tl("Start"), 1); + gdi.addItem("TestType", lang.tl("Check"), 3); + gdi.addItem("TestType", lang.tl("Radio"), 10); - gdi.selectItemByData("NC", NC); + gdi.selectItemByData("TestType", testType); + + gdi.addInput("SI", testCardNumber > 0 ? itow(testCardNumber) : L"", 10, SportIdentCB, L"Bricknummer:"); + + if (testType == 0) { + int s = timeConstHour + (rand() % 60) * timeConstMinute; + int f = s + timeConstHour / 2 + ((rand() % 3600) / 3) * timeConstSecond; + wstring ss = oe->getAbsTime(s); + wstring fs = oe->getAbsTime(f); + wstring cs = oe->getAbsTime(s - (60 + rand() % 60) * timeConstSecond); + + if (!testStartTime.empty()) + ss = testStartTime; + if (!testFinishTime.empty()) + fs = testFinishTime; + if (!testCheckTime.empty()) + cs = testCheckTime; + + gdi.setCX(gdi.getCX() + gdi.getLineHeight()); + + gdi.dropLine(1.6); + gdi.addCheckbox("HasCheck", "", nullptr, useTestCheck); + gdi.dropLine(-1.6); + gdi.setCX(gdi.getCX() - gdi.getLineHeight()); + gdi.addInput("Check", cs, 6, 0, L"Check:"); + + gdi.dropLine(1.6); + gdi.addCheckbox("HasStart", "", nullptr, useTestStart); + gdi.dropLine(-1.6); + gdi.setCX(gdi.getCX() - gdi.getLineHeight()); + gdi.addInput("Start", ss, 6, 0, L"Start:"); + + gdi.dropLine(1.6); + gdi.addCheckbox("HasFinish", "", nullptr, useTestFinish); + gdi.dropLine(-1.6); + gdi.setCX(gdi.getCX() - gdi.getLineHeight()); + + gdi.addInput("Finish", fs, 6, 0, L"Mål:"); + gdi.addSelection("NC", 45, 200, SportIdentCB, L"Stämplingar:"); + const int src[11] = { 33, 34, 45, 50, 36, 38, 59, 61, 62, 67, 100 }; + + for (int i = 0; i < 32; i++) + gdi.addItem("NC", itow(i), i); + + gdi.selectItemByData("NC", NC); + + for (int i = 0; i < NC; i++) { + int level = min(i, NC - i) / 5; + int c; + if (i < NC / 2) { + int ix = i % 6; + c = src[ix] + level * 10; + if (c == 100) + c = 183; + } + else { + int ix = 10 - (NC - i - 1) % 5; + c = src[ix] + level * 10; + } + + if (i < testControls.size()) + c = testControls[i]; + + gdi.addInput("C" + itos(i + 1), itow(c), 3, 0, L"#C" + itow(i + 1)); + } + + gdi.dropLine(); + gdi.fillDown(); + gdi.addButton("Save", "Spara", SportIdentCB); + gdi.popX(); - for (int i = 0; i < NC; i++) { - int level = min(i, NC - i) / 5; - int c; - if (i < NC / 2) { - int ix = i % 6; - c = src[ix] + level * 10; - if (c == 100) - c = 183; } else { - int ix = 10 - (NC - i - 1) % 5; - c = src[ix] + level * 10; - } + int p = timeConstHour + (rand() % 60) * timeConstMinute; + wstring ps = oe->getAbsTime(p); + + if (!testPunchTime.empty()) + ps = testPunchTime; + + if (testType == 10) + gdi.addInput("ControlNumber", itow(testRadioNumber), 6, 0, L"Kontroll:"); - gdi.addInput("C" + itos(i + 1), itow(c), 3, 0, L"#C" + itow(i + 1)); + gdi.addInput("PunchTime", ps, 6, 0, L"Tid:"); + + gdi.dropLine(); + gdi.fillDown(); + gdi.addButton("SaveP", "Spara", SportIdentCB); + gdi.popX(); + } + rc.right = gdi.getWidth(); + rc.bottom = gdi.getCY(); + gdi.addRectangle(rc, GDICOLOR::colorLightMagenta); + gdi.dropLine(); } - gdi.dropLine(); - gdi.addButton("Save", "Bricka", SportIdentCB); - gdi.fillDown(); - - gdi.addButton("SaveP", "Stämpling", SportIdentCB); - gdi.popX(); -#endif gdi.addString("", boldLarge, "SportIdent"); gdi.dropLine(); @@ -2944,6 +3066,7 @@ bool TabSI::processCard(gdioutput& gdi, pRunner runner, const SICard& csic, bool runner->setStatus(StatusOK, true, oBase::ChangeType::Update, false); SICard sic(csic); StoredReadout rout; + rout.runnerId = runner->getId(); if (!csic.isManualInput()) { pCard card = gEvent->allocateCard(runner); @@ -3045,7 +3168,7 @@ bool TabSI::processCard(gdioutput& gdi, pRunner runner, const SICard& csic, bool rout.statusline += lang.tl(L", Prel. bomtid: ") + runner->getMissedTimeS(); rout.rentCard = runner->isHiredCard() || oe->isHiredCard(sic.CardNumber); - + if (!silent) { rout.render(gdi, rout.computeRC(gdi)); gdi.scrollToBottom(); @@ -3125,6 +3248,12 @@ RECT TabSI::StoredReadout::computeRC(gdioutput& gdi) const { void TabSI::StoredReadout::render(gdioutput& gdi, const RECT& rc) const { gdi.fillDown(); gdi.addRectangle(rc, color, true); + + //gdi.addString("edit", rc.right - gdi.scaleLength(30), rc.top+gdi.scaleLength(4), textImage, "S" + itos(gdi.scaleLength(24))); + if (runnerId > 0) { + gdi.addImage("edit", rc.top + gdi.scaleLength(4), rc.right - gdi.scaleLength(30), 0, + itow(IDI_MEOSEDIT), gdi.scaleLength(24), gdi.scaleLength(24), SportIdentCB).setExtra(runnerId); + } int lh = gdi.getLineHeight(); int marg = gdi.scaleLength(20); int tmarg = gdi.scaleLength(6); @@ -3215,6 +3344,8 @@ wstring TabSI::getTimeAfterString(const oRunner* runner) { void TabSI::processPunchOnly(gdioutput& gdi, const SICard& csic) { + gdi.makeEvent("PunchCard", "sireadout", csic.CardNumber, 0, true); + SICard sic = csic; DWORD loaded; gEvent->convertTimes(nullptr, sic); @@ -3707,19 +3838,10 @@ void TabSI::tieCard(gdioutput& gdi) { } void TabSI::showAssignCard(gdioutput& gdi, bool showHelp) { - //gdi.fillDown(); - //gdi.setRestorePoint("AssignCardBase"); if (interactiveReadout) { if (showHelp) { checkBoxToolBar(gdi, { CheckBox::Interactive, CheckBox::AutoTie, CheckBox::AutoTieRent }); gdi.addString("", 10, L"Avmarkera 'X' för att hantera alla bricktildelningar samtidigt.#" + lang.tl("Interaktiv inläsning")); - /*gdi.dropLine(0.5); - gdi.addCheckbox("AutoTie", "Knyt automatiskt efter inläsning", SportIdentCB, oe->getPropertyInt("AutoTie", 1) != 0); - gdi.addCheckbox("AutoTieRent", "Automatisk hyrbrickshantering genom registrerade hyrbrickor", SportIdentCB, oe->getPropertyInt("AutoTieRent", 1) != 0); - if (!oe->hasHiredCardData()) { - gdi.disableInput("AutoTieRent"); - gdi.check("AutoTieRent", false); - }*/ gdi.dropLine(0.5); gdi.setRestorePoint("ManualTie"); } @@ -3730,8 +3852,7 @@ void TabSI::showAssignCard(gdioutput& gdi, bool showHelp) { gdi.addString("", 10, L"Markera 'X' för att hantera deltagarna en och en.#" + lang.tl("Interaktiv inläsning")); } - gEvent->assignCardInteractive(gdi, SportIdentCB); - gdi.refresh(); + gEvent->assignCardInteractive(gdi, SportIdentCB, sortAssignCards); return; } @@ -4291,6 +4412,8 @@ void TabSI::clearCompetitionData() { logger.reset(); numSavedCardsOnCmpOpen = savedCards.size(); + + sortAssignCards = SortOrder::Custom; } SICard& TabSI::getCard(int id) const { @@ -5088,7 +5211,7 @@ void TabSI::showReadoutStatus(gdioutput& gdi, const oRunner* r, if (rentalCard) { gdi.addRectangle(rc, colorYellow); - gdi.addStringUT(rc.top + (rc.bottom - rc.top) / 3, mrg, boldHuge | textCenter, + gdi.addString("", rc.top + (rc.bottom - rc.top) / 3, mrg, boldHuge | textCenter, "Vänligen återlämna hyrbrickan.", w - 3 * mrg); } @@ -5168,3 +5291,26 @@ void TabSI::fillMappings(gdioutput& gdi) const { gdi.addItem("Mappings", itow(mp.first) + L" \u21A6 " + oPunch::getType(mp.second), mp.first); } } + +void TabSI::readTestData(gdioutput& gdi) { + testCardNumber = gdi.getTextNo("SI"); + if (gdi.hasWidget("PunchTime")) { + testPunchTime = gdi.getText("PunchTime"); + } + else { + useTestCheck = gdi.isChecked("HasCheck"); + useTestStart = gdi.isChecked("HasStart"); + useTestFinish = gdi.isChecked("HasFinish"); + + testStartTime = gdi.getText("Start"); + testFinishTime = gdi.getText("Finish"); + testCheckTime = gdi.getText("Check"); + + if (NC > testControls.size()) + testControls.resize(NC); + + for (int i = 0; i < NC; i++) + testControls[i] = gdi.getTextNo("C" + itos(i+1)); + } +} + diff --git a/code/TabSI.h b/code/TabSI.h index f265cb2..0b63e11 100644 --- a/code/TabSI.h +++ b/code/TabSI.h @@ -94,6 +94,8 @@ private: shared_ptr resetHiredCardHandler; GuiHandler *getResetHiredCardHandler(); + SortOrder sortAssignCards = SortOrder::Custom; + int runnerMatchedId; bool printErrorShown; void printProtected(gdioutput &gdi, gdioutput &gdiprint); @@ -185,17 +187,32 @@ private: static int analyzePunch(SIPunch &p, int &start, int &accTime, int &days); - void createCompetitionFromCards(gdioutput &gdi); - int NC; + int NC = 8; + int testType = 0; + bool showTestingPanel = false; + wstring testStartTime; + bool useTestStart = true; + wstring testFinishTime; + bool useTestFinish = true; + wstring testCheckTime; + bool useTestCheck = false; + int testRadioNumber = 50; + wstring testPunchTime; + vector testControls; + int testCardNumber = 0; + + void readTestData(gdioutput& gdi); + class EditCardData : public GuiHandler { TabSI *tabSI; - EditCardData(const EditCardData&); - EditCardData &operator=(const EditCardData&); public: EditCardData() : tabSI(0) {} + EditCardData(const EditCardData&) = delete; + EditCardData& operator=(const EditCardData&) = delete; + void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type); friend class TabSI; }; @@ -252,6 +269,7 @@ private: vector MP; GDICOLOR color; bool rentCard = false; + int runnerId = 0; RECT computeRC(gdioutput &gdi) const; void render(gdioutput &gdi, const RECT &rc) const; diff --git a/code/TabSpeaker.cpp b/code/TabSpeaker.cpp index b760127..fe6dea6 100644 --- a/code/TabSpeaker.cpp +++ b/code/TabSpeaker.cpp @@ -241,11 +241,11 @@ int TabSpeaker::processButton(gdioutput &gdi, const ButtonInfo &bu) gdi.dropLine(3); gdi.popX(); gdi.registerEvent("DataUpdate", tabSpeakerCB); - vector> runnersToReport; + deque> runnersToReport; if (runnerId > 0) { runnersToReport.emplace_back(runnerId, false); } - TabRunner::generateRunnerReport(*oe, gdi, runnersToReport); + TabRunner::generateRunnerReport(*oe, gdi, 1, 1, false, runnersToReport); gdi.refresh(); } else if (bu.id == "Priority") { diff --git a/code/Table.cpp b/code/Table.cpp index 90628f7..7849a31 100644 --- a/code/Table.cpp +++ b/code/Table.cpp @@ -274,7 +274,7 @@ void Table::filter(int col, const wstring &filt, bool forceFilter) wchar_t filt_lc[1024]; wcscpy_s(filt_lc, filt.c_str()); - CharLowerBuff(filt_lc, filt.length()); + prepareMatchString(filt_lc, filt.length()); sortIndex.resize(2); for (size_t k=2;k0) { + if (tooltip.length() > 0) { addToolTip(id, tooltip, bi.hWnd); addToolTip(desc.id, tooltip, 0, &desc.textRect); } bi.isCheckbox = true; - bi.xp=x; - bi.yp=y; - bi.width = desc.textRect.right - (x-ox); - bi.text=ttext; - bi.id=id; - bi.callBack=cb; - bi.AbsPos=AbsPos; + bi.xp = x; + bi.yp = cbY; + bi.width = desc.textRect.right - (x - ox); + bi.text = ttext; + bi.id = id; + bi.callBack = cb; + bi.AbsPos = AbsPos; bi.originalState = Checked; bi.isEdit(true); BI.push_back(bi); @@ -2240,7 +2242,7 @@ void gdioutput::processButtonMessage(ButtonInfo &bi, WPARAM wParam) if (bi.isCheckbox) bi.checked = SendMessage(bi.hWnd, BM_GETCHECK, 0, 0)==BST_CHECKED; bi.synchData(); - if (bi.callBack || bi.handler) { + if (bi.callBack || bi.hasEventHandler()) { setWaitCursor(true); if (!bi.handleEvent(*this, GUI_BUTTON) && bi.callBack) bi.callBack(this, GUI_BUTTON, &bi); //it may be destroyed here... @@ -3027,10 +3029,8 @@ void gdioutput::doEnter() { HWND hWnd=GetFocus(); for (list::iterator it=BI.begin(); it!=BI.end(); ++it) - if (it->isDefaultButton() && (it->callBack || it->handler)) { - if (it->handler) - it->handleEvent(*this, GUI_BUTTON); - else + if (it->isDefaultButton()) { + if (!it->handleEvent(*this, GUI_BUTTON) && it->callBack) it->callBack(this, GUI_BUTTON, &*it); return; } @@ -3038,13 +3038,11 @@ void gdioutput::doEnter() { list::iterator it; for(it=II.begin(); it != II.end(); ++it) - if (it->hWnd==hWnd && (it->callBack || it->handler)){ + if (it->hWnd==hWnd && (it->hasEventHandler() || it->callBack)){ TCHAR bf[1024]; GetWindowText(hWnd, bf, 1024); - it->text=bf; - if (it->handler) - it->handleEvent(*this, GUI_INPUT); - else + it->text = bf; + if (!it->handleEvent(*this, GUI_INPUT)) it->callBack(this, GUI_INPUT, &*it); return; } @@ -3139,10 +3137,8 @@ void gdioutput::doEscape() tit->table->escape(*this); for (list::iterator it=BI.begin(); it!=BI.end(); ++it) { - if (it->isCancelButton() && (it->callBack || it->handler) ) { - if (it->handler) - it->handleEvent(*this, GUI_BUTTON); - else + if (it->isCancelButton() && (it->callBack || it->hasEventHandler()) ) { + if (!it->handleEvent(*this, GUI_BUTTON)) it->callBack(this, GUI_BUTTON, &*it); return; } @@ -3517,15 +3513,15 @@ BaseInfo *gdioutput::setText(const char *id, const wstring &text, bool Update, i bool gdioutput::insertText(const string &id, const wstring &text) { - for (list::iterator it=II.begin(); - it != II.end(); ++it) { - if (it->id==id) { + for (list::iterator it = II.begin(); + it != II.end(); ++it) { + if (it->id == id) { SetWindowText(it->hWnd, text.c_str()); it->text = text; - if (it->handler) + if (it->hasEventHandler()) it->handleEvent(*this, GUI_INPUT); - else if (it->callBack) + else if (it->callBack) it->callBack(this, GUI_INPUT, &*it); return true; @@ -4385,9 +4381,6 @@ void gdioutput::RenderString(TextInfo &ti, HDC hDC) { h = ti.textRect.bottom - ti.textRect.top; image.drawImage(imgId, Image::ImageMethod::Default, hDC, rc.left, rc.top, w, h); - - //width = image.getWidth(imgId); - //height = image.getHeight(imgId); } } if (!fixedRect) { @@ -5503,7 +5496,7 @@ int gdioutput::sendCtrlMessage(const string &id) { for (list::iterator it=BI.begin(); it != BI.end(); ++it) { if (id==it->id) { - if (it->handler) + if (it->hasEventHandler()) return it->handleEvent(*this, GUI_BUTTON); else if (it->callBack) return it->callBack(this, GUI_BUTTON, &*it); //it may be destroyed here... @@ -7201,11 +7194,11 @@ string gdioutput::dbPress(const string &id, int extra) { if (it->isCheckbox) { check(id, !isChecked(id)); } - else if(!it->callBack && !it->handler) + else if(!it->callBack && !it->hasEventHandler()) throw meosException("Button " + id + " is not active."); wstring val = it->text; - if (it->handler) + if (it->hasEventHandler()) it->handleEvent(*this, GUI_BUTTON); else if (it->callBack) it->callBack(this, GUI_BUTTON, &*it); //it may be destroyed here... @@ -7229,11 +7222,11 @@ string gdioutput::dbPress(const string &id, const char *extra) { if (it->isCheckbox) { check(id, !isChecked(id)); } - else if(!it->callBack && !it->handler) + else if(!it->callBack && !it->hasEventHandler()) throw meosException("Button " + id + " is not active."); wstring val = it->text; - if (it->handler) + if (it->hasEventHandler()) it->handleEvent(*this, GUI_BUTTON); else if (it->callBack) it->callBack(this, GUI_BUTTON, &*it); //it may be destroyed here... @@ -7272,12 +7265,12 @@ string gdioutput::dbSelect(const string &id, int data) { void gdioutput::internalSelect(ListBoxInfo &bi) { bi.syncData(); - if (bi.callBack || bi.handler) { + if (bi.callBack || bi.handler || bi.managedHandler) { setWaitCursor(true); hasCleared = false; try { bi.writeLock = true; - if (bi.handler) + if (bi.hasEventHandler()) bi.handleEvent(*this, GUI_LISTBOX); else bi.callBack(this, GUI_LISTBOX, &bi); //it may be destroyed here... Then hasCleared is set. @@ -7304,7 +7297,7 @@ void gdioutput::dbInput(const string &id, const string &text) { SetWindowText(it->hWnd, widen(text).c_str()); it->text = widen(text); it->data = -1; - if (it->handler) + if (it->hasEventHandler()) it->handleEvent(*this, GUI_COMBO); else if (it->callBack) it->callBack(this, GUI_COMBO, &*it); //it may be destroyed here... @@ -7319,7 +7312,7 @@ void gdioutput::dbInput(const string &id, const string &text) { it->text = widen(text); SetWindowText(it->hWnd, widen(text).c_str()); - if (it->handler) + if (it->hasEventHandler()) it->handleEvent(*this, GUI_INPUT); else if (it->callBack) it->callBack(this, GUI_INPUT, &*it); @@ -7357,7 +7350,7 @@ void gdioutput::dbDblClick(const string &id, int data) { if (!IsWindowEnabled(it->hWnd)) throw meosException("Selection " + id + " is not active."); selectItemByData(id, data); - if (it->handler) + if (it->hasEventHandler()) it->handleEvent(*this, GUI_LISTBOXSELECT); else if (it->callBack) it->callBack(this, GUI_LISTBOXSELECT, &*it); //it may be destroyed here... diff --git a/code/infoserver.h b/code/infoserver.h index 6e6d2e0..e5e7967 100644 --- a/code/infoserver.h +++ b/code/infoserver.h @@ -205,7 +205,7 @@ class InfoCompetitor : public InfoBaseCompetitor { class InfoTeam : public InfoBaseCompetitor { protected: // The outer level holds legs, the inner level holds (parallel/patrol) runners on each leg. - vector< vector > competitors; + vector> competitors; public: bool synchronize(oTeam &t); void serialize(xmlbuffer &xml, bool diffOnly) const; diff --git a/code/iof30interface.cpp b/code/iof30interface.cpp index 44a05b6..2555917 100644 --- a/code/iof30interface.cpp +++ b/code/iof30interface.cpp @@ -2417,7 +2417,10 @@ pRunner IOF30Interface::readPersonResult(gdioutput &gdi, pClass pc, xmlobject &x wstring s; for (auto &split : splits) { int code = split.getObjectInt("ControlCode"); - int time = split.getObjectInt("Time"); + wstring out; + split.getObjectString("Time", out); + double t = _wtof(out.c_str()); + int time = int(t * timeConstSecond); split.getObjectString("status", s); if (s != L"missing") card->addPunch(code, st + time, 0, 0); diff --git a/code/meos.cpp b/code/meos.cpp index afe1768..d92a9c9 100644 --- a/code/meos.cpp +++ b/code/meos.cpp @@ -463,6 +463,8 @@ int APIENTRY WinMain(HINSTANCE hInstance, gdi_main->init(hWndWorkspace, hWndMain, hMainTab); gdi_main->getTabs().get(TCmpTab)->loadPage(*gdi_main); + image.loadImage(IDI_MEOSEDIT, Image::ImageMethod::Default); + autoTask = new AutoTask(hWndMain, *gEvent, *gdi_main); autoTask->setTimers(); diff --git a/code/meos.rc b/code/meos.rc index c2f3092..72e5085 100644 --- a/code/meos.rc +++ b/code/meos.rc @@ -53,6 +53,7 @@ IDB_ECO BITMAP "bmp00001.bmp" IDI_SPLASHIMAGE PNG "meos.png" IDI_MEOSIMAGE PNG "title.png" IDI_MEOSINFO PNG "info24.png" +IDI_MEOSEDIT PNG "edit.png" ///////////////////////////////////////////////////////////////////////////// // English (United States) resources diff --git a/code/meos_util.cpp b/code/meos_util.cpp index 4510140..eec57dc 100644 --- a/code/meos_util.cpp +++ b/code/meos_util.cpp @@ -919,16 +919,10 @@ string itos(uint64_t i) return bf; } - -bool filterMatchString(const string &c, const char *filt_lc) -{ - if (filt_lc[0] == 0) - return true; - char key[2048]; - strcpy_s(key, c.c_str()); - CharLowerBuffA(key, c.length()); - - return strstr(key, filt_lc)!=0; +void prepareMatchString(wchar_t* data_c, int size) { + CharLowerBuff(data_c, size); + for (int j = 0; j < size; j++) + data_c[j] = toLowerStripped(data_c[j]); } bool filterMatchString(const wstring &c, const wchar_t *filt_lc, int &score) { @@ -937,7 +931,11 @@ bool filterMatchString(const wstring &c, const wchar_t *filt_lc, int &score) { return true; wchar_t key[2048]; wcscpy_s(key, c.c_str()); - CharLowerBuff(key, c.length()); + int cl = c.length(); + CharLowerBuff(key, cl); + for (int j = 0; j < cl; j++) + key[j] = toLowerStripped(key[j]); + bool match = wcsstr(key, filt_lc) != 0; if (match) { while (filt_lc[score] && key[score] && filt_lc[score] == key[score]) @@ -1259,14 +1257,14 @@ int toLowerStripped(wchar_t c) { return c; static wchar_t *map = 0; - if (map == 0) { + if (map == nullptr) { map = new wchar_t[65536]; for (int i = 0; i < 65536; i++) map[i] = i; - setChar(map, L'Å', L'å'); - setChar(map, L'Ä', L'ä'); - setChar(map, L'Ö', L'ö'); + setChar(map, L'Å', L'a'); + setChar(map, L'Ä', L'a'); + setChar(map, L'Ö', L'o'); setChar(map, L'É', L'e'); setChar(map, L'é', L'e'); @@ -1289,6 +1287,8 @@ int toLowerStripped(wchar_t c) { setChar(map, L'ñ', L'n'); setChar(map, L'Ñ', L'n'); + setChar(map, L'ä', L'a'); + setChar(map, L'å', L'a'); setChar(map, L'á', L'a'); setChar(map, L'Á', L'a'); setChar(map, L'à', L'a'); @@ -1315,19 +1315,29 @@ int toLowerStripped(wchar_t c) { setChar(map, L'Õ', L'o'); setChar(map, L'ô', L'o'); setChar(map, L'Ô', L'o'); + setChar(map, L'ö', L'o'); setChar(map, L'ý', L'y'); setChar(map, L'Ý', L'Y'); setChar(map, L'ÿ', L'y'); + + setChar(map, L'Æ', L'a'); + setChar(map, L'æ', L'a'); - setChar(map, L'Æ', L'ä'); - setChar(map, L'æ', L'ä'); - - setChar(map, L'Ø', L'ö'); - setChar(map, L'ø', L'ö'); + setChar(map, L'Ø', L'o'); + setChar(map, L'ø', L'o'); setChar(map, L'Ç', L'c'); setChar(map, L'ç', L'c'); + + wstring srcEx = L"Ă㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻĽľĿŀŁł" + L"ŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž"; + wstring dstEx = L"aaaaccccccccddddeeeeeeeeeegggggggghhhhiiiiiiiiiijjjjkkklllllllll" + L"nnnnnnnnnooooooaarrrrrrssssssssttttttuuuuuuuuuuuuwwyyyzzzzzz"; + + assert(srcEx.size() == dstEx.size()); + for (int j = 0; j < srcEx.size(); j++) + setChar(map, srcEx[j], dstEx[j]); } int a = map[c]; return a; diff --git a/code/meos_util.h b/code/meos_util.h index 3195ca5..c9a71d2 100644 --- a/code/meos_util.h +++ b/code/meos_util.h @@ -152,10 +152,13 @@ wstring itow(int64_t i); wstring itow(uint64_t i); -///Lower case match (filt_lc must be lc) -bool filterMatchString(const string &c, const char *filt_lc); +///Lower case match (filt_lc must be lc and stripped of accents) bool filterMatchString(const wstring &c, const wchar_t *filt_lc, int &score); +/** To lower case and strip accants */ +void prepareMatchString(wchar_t* data_c, int size); + + bool matchNumber(int number, const wchar_t *key); int getMeosBuild(); diff --git a/code/meosversion.cpp b/code/meosversion.cpp index f51824f..6cf00cb 100644 --- a/code/meosversion.cpp +++ b/code/meosversion.cpp @@ -25,12 +25,12 @@ //ABCDEFGHIJKLMNOP int getMeosBuild() { - string revision("$Rev: 1263 $"); + string revision("$Rev: 1266 $"); return 174 + atoi(revision.substr(5, string::npos).c_str()); } wstring getMeosDate() { - wstring date(L"$Date: 2023-05-12 16:11:34 +0200 (fre, 12 maj 2023) $"); + wstring date(L"$Date: 2023-06-01 22:17:24 +0200 (tor, 01 jun 2023) $"); return date.substr(7,10); } @@ -39,7 +39,7 @@ wstring getBuildType() { } wstring getMajorVersion() { - return L"3.9"; + return L"4.0"; } wstring getMeosFullVersion() { diff --git a/code/metalist.cpp b/code/metalist.cpp index dad0412..b37ddee 100644 --- a/code/metalist.cpp +++ b/code/metalist.cpp @@ -2198,6 +2198,7 @@ void MetaList::initSymbols() { typeToSymbol[lCourseUsage] = L"CourseUsage"; typeToSymbol[lCourseUsageNoVacant] = L"CourseUsageNoVacant"; typeToSymbol[lCourseClasses] = L"CourseClasses"; + typeToSymbol[lCourseNumControls] = L"CourseNumControls"; typeToSymbol[lCourseShortening] = L"CourseShortening"; typeToSymbol[lRunnerName] = L"RunnerName"; typeToSymbol[lRunnerGivenName] = L"RunnerGivenName"; @@ -2301,18 +2302,26 @@ void MetaList::initSymbols() { typeToSymbol[lPunchNamedTime] = L"PunchNamedTime"; typeToSymbol[lPunchName] = L"PunchName"; typeToSymbol[lPunchNamedSplit] = L"PunchNamedSplit"; + typeToSymbol[lPunchTeamTotalNamedTime] = L"PunchTeamTotalNamedTime"; typeToSymbol[lPunchTime] = L"PunchTime"; + typeToSymbol[lPunchTeamTime] = L"PunchTeamTime"; + typeToSymbol[lPunchControlNumber] = L"PunchControlNumber"; typeToSymbol[lPunchControlCode] = L"PunchControlCode"; typeToSymbol[lPunchLostTime] = L"PunchLostTime"; typeToSymbol[lPunchControlPlace] = L"PunchControlPlace"; typeToSymbol[lPunchControlPlaceAcc] = L"PunchControlPlaceAcc"; + typeToSymbol[lPunchControlPlaceTeamAcc] = L"PunchControlPlaceTeamAcc"; typeToSymbol[lPunchSplitTime] = L"PunchSplitTime"; typeToSymbol[lPunchTotalTime] = L"PunchTotalTime"; + typeToSymbol[lPunchTeamTotalTime] = L"PunchTeamTotalTime"; + typeToSymbol[lPunchAbsTime] = L"PunchAbsTime"; typeToSymbol[lPunchTotalTimeAfter] = L"PunchTotalTimeAfter"; + typeToSymbol[lPunchTeamTotalTimeAfter] = L"PunchTeamTotalTimeAfter"; + typeToSymbol[lPunchTimeSinceLast] = L"PunchTimeSinceLast"; typeToSymbol[lRogainingPunch] = L"RogainingPunch"; @@ -3461,7 +3470,7 @@ void MetaList::getAutoComplete(const wstring& w, vector& rec s_lc[j].resize(ws[j].size() + 1); ws[j] = trim(ws[j]); wcscpy_s(s_lc[j].data(), s_lc[j].size(), ws[j].c_str()); - CharLowerBuff(s_lc[j].data(), ws[j].length()); + prepareMatchString(s_lc[j].data(), ws[j].length()); } wstring tl; diff --git a/code/metalist.h b/code/metalist.h index 415f371..929291a 100644 --- a/code/metalist.h +++ b/code/metalist.h @@ -434,6 +434,10 @@ public: static void fillSymbols(vector < pair> &symb); + static const map& getOrderToSymbol() { + return orderToSymbol; + } + friend class MetaListPost; }; @@ -526,6 +530,6 @@ public: void synchronizeTo(MetaListContainer &dst) const; bool interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par, oListInfo &li) const; - + void enumerateLists(vector< pair > > &out) const; }; diff --git a/code/oCard.cpp b/code/oCard.cpp index 4f74900..f142ae9 100644 --- a/code/oCard.cpp +++ b/code/oCard.cpp @@ -815,7 +815,7 @@ wstring oCard::getCardVoltage() const { } wstring oCard::getCardVoltage(int miliVolt) { - if (miliVolt == 0) + if (miliVolt <= 10) return L""; int vi = miliVolt / 1000; int vd = (miliVolt % 1000) / 10; @@ -830,9 +830,9 @@ oCard::BatteryStatus oCard::isCriticalCardVoltage() const { } oCard::BatteryStatus oCard::isCriticalCardVoltage(int miliVolt) { - if (miliVolt > 0 && miliVolt < 2445) + if (miliVolt > 10 && miliVolt < 2445) return BatteryStatus::Bad; - else if (miliVolt > 0 && miliVolt <= 2710) + else if (miliVolt > 10 && miliVolt <= 2710) return BatteryStatus::Warning; return BatteryStatus::OK; diff --git a/code/oClass.cpp b/code/oClass.cpp index 20fece6..f57f901 100644 --- a/code/oClass.cpp +++ b/code/oClass.cpp @@ -404,7 +404,7 @@ int oClass::getNumRunners(bool checkFirstLeg, bool noCountVacant, bool noCountNo } } string key = getCountTypeKey(checkFirstLeg ? 0 : -1, - noCountNotCompeting ? CountKeyType::All : CountKeyType::IncludeNotCompeting, + noCountNotCompeting ? CountKeyType::AllCompeting : CountKeyType::IncludeNotCompeting, !noCountVacant); auto res = tTypeKeyToRunnerCount.second.find(key); @@ -419,7 +419,7 @@ int oClass::getNumRunners(bool checkFirstLeg, bool noCountVacant, bool noCountNo continue; if (noCountVacant && r.isVacant()) continue; - if (noCountNotCompeting && r.getStatus() == StatusNotCompetiting) + if (noCountNotCompeting && (r.getStatus() == StatusNotCompetiting || r.getStatus() == StatusCANCEL)) continue; int id = r.getClassId(true); @@ -3371,6 +3371,16 @@ int oClass::getLegPlace(int ifrom, int ito, int time) const return 0; } +int oClass::getAccLegControlLeader(int teamLeg, int courseControlId) const { + assert(false); + return 0; +} + +int oClass::getAccLegControlPlace(int teamLeg, int courseControlId, int time) const { + assert(false); + return 0; +} + void oClass::insertAccLegPlace(int courseId, int controlNo, int time, int place) { /* char bf[256]; @@ -3441,7 +3451,6 @@ int oClass::getAccLegPlace(int courseId, int controlNo, int time) const return 0; } - void oClass::calculateSplits() { clearSplitAnalysis(); set cSet; @@ -3460,33 +3469,50 @@ void oClass::calculateSplits() { LegResult legBestTime; vector rCls; oe->getRunners(Id, -1, rCls, false); - /* - if (isQualificationFinalBaseClass() || isQualificationFinalBaseClass()) { - - for (auto &r : oe->Runners) { - if (!r.isRemoved() && r.getClassRef(true) == this) - rCls.push_back(&r); + + for (pRunner it : rCls) { + pCourse tpc = it->getCourse(false); + if (tpc == nullptr) + continue; + cSet.insert(tpc); + } + + map> rClsCrs; + if (cSet.size() > 1) { + for (pRunner it : rCls) { + pCourse tpc = it->getCourse(false); + if (tpc) + rClsCrs[tpc->getId()].push_back(it); } } - else { - for (auto &r : oe->Runners) { - if (!r.isRemoved() && r.Class == this) - rCls.push_back(&r); - } - }*/ - for (set::iterator cit = cSet.begin(); cit!= cSet.end(); ++cit) { - pCourse pc = *cit; + bool multiLeg = getNumStages() > 1; // Perhaps ignore parallell legs... + + if (multiLeg) { + teamLegCourseControlToLeaderPlace.resize(getNumStages()); + for (auto& lp : teamLegCourseControlToLeaderPlace) + lp.clear(); + } + else { + teamLegCourseControlToLeaderPlace.clear(); + } + + for (pCourse pc : cSet) { // Store all split times in a matrix const unsigned nc = pc->getNumControls(); if (nc == 0) return; - vector< vector > splits(nc+1); - vector< vector > splitsAcc(nc+1); - vector acceptMissingPunch(nc+1, true); + vector> splits(nc+1); + vector> splitsAcc(nc+1); + vector acceptMissingPunch(nc+1, true); + vector* rList; + if (rClsCrs.empty()) + rList = &rCls; + else + rList = &rClsCrs[pc->getId()]; - for (pRunner it : rCls) { + for (pRunner it : *rList) { pCourse tpc = it->getCourse(false); if (tpc != pc || tpc == 0) continue; @@ -3504,7 +3530,7 @@ void oClass::calculateSplits() { } } - for (pRunner it : rCls) { + for (pRunner it : *rList) { pCourse tpc = it->getCourse(false); if (tpc != pc) @@ -3513,17 +3539,46 @@ void oClass::calculateSplits() { const vector &sp = it->getSplitTimes(true); const int s = min(nc, sp.size()); + int off = 0; + unordered_map* teamAccTimes = nullptr; + if (multiLeg && it->tInTeam) + off = it->tInTeam->getTotalRunningTimeAtLegStart(it->tLeg, false); + + if (off > 0 && it->tLeg < teamLegCourseControlToLeaderPlace.size()) + teamAccTimes = &teamLegCourseControlToLeaderPlace[it->tLeg]; + vector &tLegTimes = it->tLegTimes; tLegTimes.resize(nc + 1); bool ok = true; + // Acc team finish time + if (teamAccTimes && it->FinishTime > 0 && (it->tStatus == StatusOK || it->tStatus == StatusUnknown)) { + int ccId = oPunch::PunchFinish; + int t = it->getRunningTime(false); + auto& res = (*teamAccTimes)[ccId]; + if (res.leader <= 0 || res.leader > t + off) + res.leader = t + off; + // Count times + ++res.timeToPlace[t + off]; + } + for (int k = 0; k < s; k++) { if (sp[k].getTime(true) > 0) { if (ok) { // Store accumulated times int t = sp[k].getTime(true) - it->tStartTime; - if (it->tStartTime>0 && t>0) + if (it->tStartTime > 0 && t > 0) { splitsAcc[k].push_back(t); + if (teamAccTimes) { + int ccId = pc->getCourseControlId(k); + auto& res = (*teamAccTimes)[ccId]; + if (res.leader <= 0 || res.leader > t + off) + res.leader = t + off; + + // Count times + ++res.timeToPlace[t + off]; + } + } } if (k == 0) { // start -> first @@ -3636,8 +3691,7 @@ void oClass::calculateSplits() { } } - for (set::iterator cit = cSet.begin(); cit != cSet.end(); ++cit) { - pCourse pc = *cit; + for (pCourse pc : cSet) { const unsigned nc = pc->getNumControls(); vector normRes(nc+1); vector bestRes(nc+1); @@ -3655,6 +3709,18 @@ void oClass::calculateSplits() { swap(tSplitAnalysisData[pc->getId()], normRes); swap(tCourseLegLeaderTime[pc->getId()], bestRes); } + + // Convert number of competitors with time to place + for (auto& courseControlLeaderPlace : teamLegCourseControlToLeaderPlace) { + for (auto& leaderPlace : courseControlLeaderPlace) { + int place = 1; + for (auto& numTimes : leaderPlace.second.timeToPlace) { + int num = numTimes.second; + numTimes.second = place; + place += num; + } + } + } } bool oClass::isRogaining() const { diff --git a/code/oClass.h b/code/oClass.h index a2fe093..516e8ee 100644 --- a/code/oClass.h +++ b/code/oClass.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "inthashmap.h" class oClass; typedef oClass* pClass; @@ -260,9 +261,20 @@ protected: inthashmap *tLegTimeToPlace; inthashmap *tLegAccTimeToPlace; + struct PlaceTime { + int leader = -1; + map timeToPlace; + }; + + vector> teamLegCourseControlToLeaderPlace; + void insertLegPlace(int from, int to, int time, int place); void insertAccLegPlace(int courseId, int controlNo, int time, int place); + /** Get relay/team accumulated leader time/place at control. */ + int getAccLegControlLeader(int teamLeg, int courseControlId) const; + int getAccLegControlPlace(int teamLeg, int courseControlId, int time) const; + // For sub split times int tLegLeaderTime; mutable int tNoTiming; @@ -357,7 +369,7 @@ protected: mutable pair> tTypeKeyToRunnerCount; enum CountKeyType { - All, + AllCompeting, Finished, ExpectedStarting, DNS, @@ -620,7 +632,7 @@ public: bool hasTrueMultiCourse() const; unsigned getNumStages() const {return MultiCourse.size();} - /** Get the set of true legs, identifying parallell legs etc. Returns indecs into + /** Get the set of true legs, identifying parallell legs etc. Returns indices into legInfo of the last leg of the true leg (first), and true leg (second).*/ struct TrueLegInfo { protected: diff --git a/code/oCourse.cpp b/code/oCourse.cpp index 9a6d6d5..2c70c69 100644 --- a/code/oCourse.cpp +++ b/code/oCourse.cpp @@ -653,7 +653,8 @@ const vector< pair > &oEvent::getCourses(vector filt_lc(filter.length() + 1); wcscpy_s(filt_lc.data(), filt_lc.size(), filter.c_str()); - CharLowerBuff(filt_lc.data(), filter.length()); + prepareMatchString(filt_lc.data(), filter.length()); + int score; wstring b; for (size_t k = 0; k < ac.size(); k++) { diff --git a/code/oEvent.cpp b/code/oEvent.cpp index 39e3fe9..4d8e7ab 100644 --- a/code/oEvent.cpp +++ b/code/oEvent.cpp @@ -70,7 +70,7 @@ extern Image image; //Version of database -int oEvent::dbVersion = 90; +int oEvent::dbVersion = 91; bool oEvent::useSubSecond() const { if (useSubsecondsVersion == dataRevision) @@ -540,6 +540,8 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oEventData->addVariableString("MergeTag", 12, "Tag"); oEventData->addVariableString("MergeInfo", "MergeInfo"); oEventData->addVariableString("SplitPrint", 40, "Sträcktidslista"); // Id from MetaListContainer::getUniqueId + oEventData->addVariableInt("NoVacantBib", oDataContainer::oIS8U, "Inga vakanta nummerlappar"); + oEventData->initData(this, dataSize); oClubData=new oDataContainer(oClub::dataSize); @@ -4781,7 +4783,7 @@ bool oEvent::hasTeam() const return Teams.size() > 0; } -void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { +void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber, bool assignToVacant) { if ( !classHasTeams(ClassId) ) { sortRunners(ClassStartTimeClub); oRunnerList::iterator it; @@ -4804,6 +4806,8 @@ void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { if (it->isRemoved()) continue; if ( (ClassId==0 || it->getClassId(true)==ClassId) && (it->legToRun()==leg || leg == -1)) { + if (!assignToVacant && it->isVacant()) + continue; wchar_t bib[32]; swprintf_s(bib, pattern, num); pClass pc = it->getClassRef(true); @@ -4832,6 +4836,8 @@ void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { for (auto it = Teams.begin(); it != Teams.end(); ++it) { if (it->isRemoved()) continue; + if (!assignToVacant && it->isVacant()) + continue; if (ClassId == 0 || it->getClassId(false) == ClassId) { if (it->getClassRef(false) && it->getClassRef(false)->getBibMode() != BibFree) { for (size_t i = 0; i < it->Runners.size(); i++) { @@ -4847,7 +4853,7 @@ void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { } } - sortTeams(ClassStartTime, 0, true); // Sort on first leg starttime and sortindex + sortTeams(ClassStartTimeClub, 0, true); // Sort on first leg starttime and sortindex if (!firstNumber.empty()) { wchar_t pattern[32]; @@ -4856,6 +4862,8 @@ void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { for (auto it=Teams.begin(); it != Teams.end(); ++it) { if (it->isRemoved()) continue; + if (!assignToVacant && it->isVacant()) + continue; if (ClassId == 0 || it->getClassId(false) == ClassId) { wchar_t bib[32]; @@ -4887,6 +4895,8 @@ void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { } void oEvent::addAutoBib() { + bool noBibToVacant = oe->getDCI().getInt("NoVacantBib") != 0; + sortRunners(ClassStartTimeClub); oRunnerList::iterator it; int clsId = -1; @@ -4907,6 +4917,7 @@ void oEvent::addAutoBib() { pClass cls = tit->getClassRef(false); if (cls == 0) continue; + teamStartNo[tit->getId()] = tit->getStartNo(); wstring bibInfo = cls->getDCI().getString("Bib"); @@ -4935,7 +4946,7 @@ void oEvent::addAutoBib() { } } - sortTeams(ClassStartTime, 0, true); // Sort on first leg starttime and sortindex + sortTeams(ClassStartTimeClub, 0, true); // Sort on first leg starttime and sortindex map > cls2TeamList; for (oTeamList::iterator tit = Teams.begin(); tit != Teams.end(); ++tit) { @@ -4946,7 +4957,7 @@ void oEvent::addAutoBib() { } map > cls2RunnerList; - for (it=Runners.begin(); it != Runners.end(); ++it) { + for (it = Runners.begin(); it != Runners.end(); ++it) { if (it->isRemoved() || !it->getClassId(false)) continue; int clsId = it->getClassId(true); @@ -5015,17 +5026,23 @@ void oEvent::addAutoBib() { else { bool lockedForking = cls->lockedForking(); for (size_t k = 0; k < tl.size(); k++) { - wchar_t buff[32]; - swprintf_s(buff, pattern, number); - if (lockedForking) { - tl[k]->setBib(buff, number, false); - tl[k]->setStartNo(teamStartNo[tl[k]->getId()], ChangeType::Update); + if (noBibToVacant && tl[k]->isVacant()) { + tl[k]->getDI().setString("Bib", L""); //Remove only bib } else { - tl[k]->setBib(buff, number, true); + wchar_t buff[32]; + swprintf_s(buff, pattern, number); + + if (lockedForking) { + tl[k]->setBib(buff, number, false); + tl[k]->setStartNo(teamStartNo[tl[k]->getId()], ChangeType::Update); + } + else { + tl[k]->setBib(buff, number, true); + } + number += interval; } - number += interval; tl[k]->applyBibs(); tl[k]->evaluate(ChangeType::Update); } @@ -5046,7 +5063,7 @@ void oEvent::addAutoBib() { cls->synchronize(true); } for (size_t k = 0; k < rl.size(); k++) { - if (pattern[0]) { + if (pattern[0] && (!noBibToVacant || !rl[k]->isVacant())) { wchar_t buff[32]; swprintf_s(buff, pattern, number); rl[k]->setBib(buff, number, !locked); @@ -5267,19 +5284,73 @@ bool compareClubClassTeamName(const oRunner &a, const oRunner &b) return a.getClub()(info); + orderRunners = SortOrder(lb.data); + oe->assignCardInteractive(gdi, cb, orderRunners); + } + ~SortUpdate() { + } + }; + + if (gdi.hasData("AssignCardMark")) { + gdi.restore("AssignCardRP", false); + } + else { + auto h = make_shared(this, cb, orderRunners); + gdi.dropLine(0.5); + gdi.addSelection("Sorting", 200, 300, nullptr, L"Sortering:").setHandler(h); + + vector > orders; + for (auto ord : MetaList::getOrderToSymbol()) { + if (ord.first != SortOrder::Custom && ord.first != SortOrder::ClassDefaultResult) + orders.push_back(make_pair(lang.tl(ord.second), ord.first)); + } + sort(orders.begin(), orders.end()); + orders.insert(orders.begin(), make_pair(lang.tl("Standard"), SortOrder::Custom)); + + gdi.addItem("Sorting", orders); + gdi.selectItemByData("Sorting", orderRunners); + + gdi.dropLine(); + gdi.setData("AssignCardMark", 1); + gdi.setRestorePoint("AssignCardRP"); + } + + if (orderRunners == SortOrder::Custom) { + Runners.sort(compareClubClassTeamName); + } + else { + CurrentSortOrder = orderRunners; + Runners.sort(); + } oRunnerList::iterator it; - pClub lastClub=0; + pClub lastClub = nullptr; + pClass lastClass = nullptr; - int k=0; - for (it=Runners.begin(); it != Runners.end(); ++it) { + const int px4 = gdi.scaleLength(4); + const int px450 = gdi.scaleLength(450); + + int k = 0; + bool groupByClub = orderRunners == SortOrder::Custom || orderRunners == ClubClassStartTime; + bool groupByClass = orderByClass(orderRunners); + + for (it = Runners.begin(); it != Runners.end(); ++it) { if (it->skip() || it->getCardNo() || it->isVacant() || it->needNoCard()) continue; @@ -5287,37 +5358,54 @@ void oEvent::assignCardInteractive(gdioutput &gdi, GUICALLBACK cb) if (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL || it->getStatus() == StatusNotCompetiting) continue; - if (it->Club!=lastClub) { - lastClub=it->Club; + if (groupByClub && it->Club != lastClub) { + lastClub = it->Club; gdi.dropLine(0.5); - gdi.addString("", 1, it->getClub()); + gdi.addStringUT(1, it->getClub()); + } + else if (groupByClass && it->Class != lastClass) { + lastClass = it->getClassRef(true); + gdi.dropLine(0.5); + gdi.addStringUT(1, it->getClass(true)); } wstring r; - if (it->Class) - r+=it->getClass(false)+L", "; + if (!groupByClass && it->Class) + r += it->getClass(false) + L", "; + + if (!groupByClub && it->Club) + r += it->getClub() + L", "; if (it->tInTeam) { - r+=itow(it->tInTeam->getStartNo()) + L" " + it->tInTeam->getName() + L", "; - } + if (!it->tInTeam->getBib().empty()) + r += it->tInTeam->getBib() + L" "; + r += it->tInTeam->getName() + L", "; + } + else { + if (!it->getBib().empty()) + r += it->getBib() + L" "; + } r += it->getName() + L":"; + gdi.fillRight(); gdi.pushX(); gdi.addStringUT(0, r); char id[24]; sprintf_s(id, "*%d", k++); - gdi.addInput(max(gdi.getCX(), 450), gdi.getCY()-4, - id, L"", 10, cb).setExtra(it->getId()); + gdi.addInput(max(gdi.getCX(), px450), gdi.getCY() - px4, + id, L"", 10, cb).setExtra(it->getId()); gdi.popX(); gdi.dropLine(1.6); gdi.fillDown(); } - if (k==0) + if (k == 0) gdi.addString("", 0, "Ingen löpare saknar bricka"); + + gdi.refresh(); } void oEvent::calcUseStartSeconds() diff --git a/code/oEvent.h b/code/oEvent.h index 51fc459..a227240 100644 --- a/code/oEvent.h +++ b/code/oEvent.h @@ -726,7 +726,7 @@ public: inline bool useStartSeconds() const {return tUseStartSeconds;} void calcUseStartSeconds(); - void assignCardInteractive(gdioutput &gdi, GUICALLBACK cb); + void assignCardInteractive(gdioutput &gdi, GUICALLBACK cb, SortOrder& orderRunners); int getPropertyInt(const char *name, int def); const string &getPropertyString(const char *name, const string &def); @@ -820,7 +820,7 @@ public: void checkOrderIdMultipleCourses(int ClassId); - void addBib(int ClassId, int leg, const wstring &firstNumber); + void addBib(int ClassId, int leg, const wstring &firstNumber, bool assignVacant); void addAutoBib(); //Speaker functions. diff --git a/code/oEventDraw.cpp b/code/oEventDraw.cpp index 227bf5f..6257e91 100644 --- a/code/oEventDraw.cpp +++ b/code/oEventDraw.cpp @@ -510,9 +510,12 @@ private: else { vector cr; oe->getRunners(c_it->getId(), 0, cr, false); - for (pRunner r : cr) + for (pRunner r : cr) { + if (r->getStatus() == StatusNotCompetiting || r->getStatus() == StatusCANCEL) + continue; if (r->getStartGroup(true) == ci.startGroupId) nr++; + } } if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) { @@ -982,7 +985,7 @@ void oEvent::loadDrawSettings(const set &classes, DrawInfo &drawInfo, vecto cInfo[i].nRunners = pc->getNumRunners(true, true, true) + cInfo[i].nVacant; - if (cInfo[i].nRunners>0) { + if (cInfo[i].nRunners > 0) { runnerPerGroup[cInfo[i].unique] += cInfo[i].nRunners; runnerPerCourse[cInfo[i].courseId] += cInfo[i].nRunners; } @@ -996,6 +999,7 @@ void oEvent::loadDrawSettings(const set &classes, DrawInfo &drawInfo, vecto cInfo[k].nRunnersCourse = runnerPerCourse[cInfo[k].courseId]; } } + void oEvent::drawRemaining(DrawMethod method, bool placeAfter) { DrawType drawType = placeAfter ? DrawType::RemainingAfter : DrawType::RemainingBefore; @@ -1770,7 +1774,7 @@ void oEvent::drawList(const vector &spec, for (it=Runners.begin(); it != Runners.end(); ++it) { int cid = it->getClassId(true); if (!it->isRemoved() && clsId2Ix.count(cid)) { - if (it->getStatus() == StatusNotCompetiting) + if (it->getStatus() == StatusNotCompetiting || it->getStatus() == StatusCANCEL) continue; int ix = clsId2Ix[cid]; if (spec[ix].startGroup != 0 && it->getStartGroup(true) != spec[ix].startGroup) @@ -1792,7 +1796,7 @@ void oEvent::drawList(const vector &spec, for (it=Runners.begin(); it != Runners.end(); ++it) { if (!it->isRemoved() && clsId2Ix.count(it->getClassId(true))) { - if (it->getStatus() == StatusNotCompetiting) + if (it->getStatus() == StatusNotCompetiting || it->getStatus() == StatusCANCEL) continue; int st = it->getStartTime(); diff --git a/code/oEventResult.cpp b/code/oEventResult.cpp index ef7d895..ac5333c 100644 --- a/code/oEventResult.cpp +++ b/code/oEventResult.cpp @@ -1051,8 +1051,9 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { 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 time = p.getTimeInt() - r->getStartTime(); + int teamTotalTime = time + (r->tInTeam ? r->tInTeam->getTotalRunningTimeAtLegStart(r->tLeg, false) : 0); + r->tOnCourseResults.emplace_back(ccId, courseCCid2CourseIx[make_pair(crs, ccId)], time, teamTotalTime); int clsId = r->getClassId(true); int leg = r->getLegNumber(); if (cls->getQualificationFinal()) @@ -1072,6 +1073,8 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { const set &expectedCCid = classLeg2ExistingCCId[make_pair(clsId, leg)]; size_t nRT = 0; + int teamTotalOff = r.tInTeam ? r.tInTeam->getTotalRunningTimeAtLegStart(r.tLeg, false) : 0; + for (auto &radioTimes : r.tOnCourseResults.res) { if (expectedCCid.count(radioTimes.courseControlId)) nRT++; @@ -1091,8 +1094,9 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { } } if (!added) { - int time = p.getTimeInt() - r.getStartTime(); //XXX Team time - r.tOnCourseResults.emplace_back(ccId, p.tIndex, time); + int time = p.getTimeInt() - r.getStartTime(); + int teamTotalTime = time + teamTotalOff; + r.tOnCourseResults.emplace_back(ccId, p.tIndex, time, teamTotalTime); } } } @@ -1101,11 +1105,13 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { } vector> timeRunnerIx; + vector> totalTimeRunnerIx; + for (auto rList : runnerByClassLeg) { auto &rr = rList.second; pClass cls = getClass(rList.first.first); assert(cls); - bool totRes = cls->getNumStages() > 1; + //bool totRes = cls->getNumStages() > 1; set &legCCId = classLeg2ExistingCCId[rList.first]; legCCId.insert(oPunch::PunchFinish); @@ -1113,18 +1119,17 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { // Leg with negative sign int negLeg = 0; timeRunnerIx.clear(); + totalTimeRunnerIx.clear(); + int nRun = rr.size(); if (ccId == oPunch::PunchFinish) { negLeg = -1000; //Finish, smallest number for (int j = 0; j < nRun; j++) { auto r = rr[j]; if (r->prelStatusOK(true, false, false)) { - int time; - if (!r->tInTeam || !totRes) - time = r->getRunningTime(true); - else { - time = r->tInTeam->getLegRunningTime(r->tLeg, true, false); - } + int time = r->getRunningTime(true); + int teamTotalTime = r->tInTeam ? r->tInTeam->getLegRunningTime(r->tLeg, true, false) : time; + int ix = -1; int nr = r->tOnCourseResults.res.size(); for (int i = 0; i < nr; i++) { @@ -1139,9 +1144,10 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { pCourse crs = r->getCourse(false); if (crs) nc = crs->getNumControls(); - r->tOnCourseResults.emplace_back(ccId, nc, time); + r->tOnCourseResults.emplace_back(ccId, nc, time, teamTotalTime); } timeRunnerIx.emplace_back(time, j, ix); + totalTimeRunnerIx.emplace_back(teamTotalTime, j, ix); } } } @@ -1152,37 +1158,51 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { for (int i = 0; i < nr; i++) { if (r->tOnCourseResults.res[i].courseControlId == ccId) { timeRunnerIx.emplace_back(r->tOnCourseResults.res[i].time, j, i); + totalTimeRunnerIx.emplace_back(r->tOnCourseResults.res[i].teamTotalTime, j, i); + negLeg = min(negLeg, -r->tOnCourseResults.res[i].controlIx); break; } } } } - sort(timeRunnerIx.begin(), timeRunnerIx.end()); + + auto computeResult = [&rr, &negLeg](vector>& timeRunnerIx, bool total) { + 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; - } - auto r = rr[get<1>(timeRunnerIx[i])]; - int locIx = get<2>(timeRunnerIx[i]); - r->tOnCourseResults.res[locIx].place = place; - r->tOnCourseResults.res[locIx].after = time - leadTime; + 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; + } + auto r = rr[get<1>(timeRunnerIx[i])]; + int locIx = get<2>(timeRunnerIx[i]); + if (total) { + r->tOnCourseResults.res[locIx].teamTotalPlace = place; + r->tOnCourseResults.res[locIx].teamTotalAfter = time - leadTime; + } + else { + r->tOnCourseResults.res[locIx].place = place; + r->tOnCourseResults.res[locIx].after = time - leadTime; - int &legWithTimeIndexNeg = r->currentControlTime.first; - if (negLeg < legWithTimeIndexNeg) { - legWithTimeIndexNeg = negLeg; - r->currentControlTime.second = ct; + int& legWithTimeIndexNeg = r->currentControlTime.first; + if (negLeg < legWithTimeIndexNeg) { + legWithTimeIndexNeg = negLeg; + r->currentControlTime.second = ct; + } + } } - } + }; + + computeResult(timeRunnerIx, false); + computeResult(totalTimeRunnerIx, true); } } } diff --git a/code/oListInfo.cpp b/code/oListInfo.cpp index ddf2b5f..42da3fc 100644 --- a/code/oListInfo.cpp +++ b/code/oListInfo.cpp @@ -331,7 +331,8 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, break; case lPunchName: case lControlName: - case lPunchNamedTime: { + case lPunchNamedTime: + case lPunchTeamTotalNamedTime: { wstring maxcn = lang.tl("Mål"); vector ctrl; oe->getControls(ctrl, false); @@ -342,6 +343,8 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, } if (pps[k].type == lPunchNamedTime) extra = maxcn + L": 50:50 (50:50)"; + if (pps[k].type == lPunchTeamTotalNamedTime) + extra = maxcn + L": 2:50:50 (50:50)"; else maxcn.swap(extra); } @@ -366,6 +369,7 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, case lRunnerTimeAdjustment: case lRunnerGeneralTimeAfter: case lPunchTotalTimeAfter: + case lPunchTeamTotalTimeAfter: extra = L"+10:00"; break; case lTeamRogainingPointOvertime: @@ -390,12 +394,15 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, case lRunnerStageStatus: case lRunnerTimePlaceFixed: case lPunchLostTime: - case lPunchTotalTime: case lPunchTimeSinceLast: case lPunchSplitTime: case lPunchNamedSplit: extra = L"50:50"; break; + case lPunchTotalTime: + case lPunchTeamTotalTime: + extra = L"1:50:50"; + break; case lRunnerGeneralTimeStatus: case lClassStartTimeRange: extra = L"50:50 (50:50)"; @@ -415,6 +422,7 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, case lTeamTotalPlace: case lPunchControlPlace: case lPunchControlPlaceAcc: + case lPunchControlPlaceTeamAcc: case lRunnerStagePlace: extra = L"99."; break; @@ -517,7 +525,7 @@ int oListInfo::getMaxCharWidth(const oEvent *oe, pp.linearLegIndex = true; int numIter = 1; - if (pp.type == lPunchNamedTime || pp.type == lPunchTime) { + if (pp.type == lPunchNamedTime || pp.type == lPunchTime || pp.type == lPunchTeamTime) { row[k] = max(row[k], 10); pRunner r = pRunner(&*it); numIter = (r && r->getCard()) ? r->getCard()->getNumPunches() + 1 : 1; @@ -693,14 +701,16 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar } break; case lPunchTime: + case lPunchTeamTime: case lPunchControlNumber: case lPunchControlCode: case lPunchLostTime: case lPunchControlPlace: case lPunchControlPlaceAcc: - + case lPunchControlPlaceTeamAcc: case lPunchSplitTime: case lPunchTotalTime: + case lPunchTeamTotalTime: case lPunchAbsTime: if (punch && r && !invalidClass) { if (punch->tIndex >= 0) { @@ -710,9 +720,14 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar break; } switch (pp.type) { - case lPunchTime: { + case lPunchTime: + case lPunchTeamTime: { if (punch->hasTime()) { - swprintf_s(bfw, L"\u2013 (%s)", formatTime(punch->getTimeInt() - r->getStartTime()).c_str()); + int off = 0; + if (pp.type == lPunchTeamTime && r->getTeam()) + off = r->getTeam()->getTotalRunningTimeAtLegStart(r->getLegNumber(), false); + + swprintf_s(bfw, L"\u2013 (%s)", formatTime(off + punch->getTimeInt() - r->getStartTime(), SubSecond::Off).c_str()); } else { wsptr = &makeDash(L"- (-)"); @@ -733,12 +748,24 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar } case lPunchAbsTime: { if (punch->hasTime()) - wsptr = &getAbsTime(punch->getTimeInt()); + wsptr = &getAbsTime(punch->getTimeInt(), SubSecond::Off); break; } case lPunchTotalTime: { if (punch->hasTime()) - wsptr = &formatTime(punch->getTimeInt() - r->getStartTime()); + wsptr = &formatTime(punch->getTimeInt() - r->getStartTime(), SubSecond::Off); + break; + } + case lPunchTeamTotalTime: { + if (punch->hasTime()) { + pTeam t = r->getTeam(); + if (!t || r->getLegNumber() == 0) + wsptr = &formatTime(punch->getTimeInt() - r->getStartTime(), SubSecond::Off); + else { + int input = t->getTotalRunningTimeAtLegStart(r->getLegNumber(), false); + wsptr = &formatTime(input + punch->getTimeInt() - r->getStartTime(), SubSecond::Off); + } + } break; } } @@ -865,6 +892,11 @@ const wstring &oEvent::formatSpecialStringAux(const oPrintPost &pp, const oListP } break; + case lCourseNumControls: + if (pc) + wsptr = &itow(pc->getNumControls()); + break; + case lCourseClasses: if (pc) { vector cls; @@ -1175,11 +1207,21 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; case lCourseClimb: + case lCourseUsageNoVacant: + case lCourseUsage: if (r) { pCourse crs = r->getCourse(false); return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); } break; + + case lCourseNumControls: + if (r) { + pCourse crs = r->getCourse(true); + return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); + } + break; + case lCourseShortening: if (r) { int sh = r->getNumShortening(); @@ -1783,7 +1825,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lRunnerTimePlaceFixed: if (r && !invalidClass) { int t = r->getTimeWhenPlaceFixed(); - if (t == 0 || (t>0 && t < getComputerTime())) { + if (t == 0 || (t > 0 && t < getComputerTime())) { wcscpy_s(wbf, lang.tl("klar").c_str()); } else if (t == -1) @@ -2259,17 +2301,22 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lControlName: case lPunchName: case lPunchNamedTime: + case lPunchTeamTotalNamedTime: case lPunchNamedSplit: case lPunchTime: + case lPunchTeamTime: case lPunchSplitTime: case lPunchTotalTime: + case lPunchTeamTotalTime: case lPunchControlNumber: case lPunchControlCode: case lPunchLostTime: case lPunchControlPlace: case lPunchControlPlaceAcc: + case lPunchControlPlaceTeamAcc: case lPunchAbsTime: case lPunchTotalTimeAfter: + case lPunchTeamTotalTimeAfter: if (r && r->getCourse(false) && !invalidClass) { const pCourse crs=r->getCourse(true); const oControl *ctrl = nullptr; @@ -2281,22 +2328,23 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } switch (pp.type) { case lPunchNamedSplit: - if (ctrl && ctrl->hasName() && r->getPunchTime(counter.level3, false, true) > 0) { - swprintf_s(wbf, L"%s", r->getNamedSplitS(counter.level3).c_str()); + if (ctrl && ctrl->hasName() && r->getPunchTime(counter.level3, false, true, false) > 0) { + swprintf_s(wbf, L"%s", r->getNamedSplitS(counter.level3, SubSecond::Off).c_str()); } break; case lPunchNamedTime: - if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false, true) > 0)) { + case lPunchTeamTotalNamedTime: + if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false, true, false) > 0)) { swprintf_s(wbf, L"%s: %s (%s)", ctrl->getName().c_str(), - r->getNamedSplitS(counter.level3).c_str(), - r->getPunchTimeS(counter.level3, false, true, SubSecond::Off).c_str()); + r->getNamedSplitS(counter.level3, SubSecond::Off).c_str(), + r->getPunchTimeS(counter.level3, false, true, pp.type == lPunchTeamTotalNamedTime, SubSecond::Off).c_str()); } break; case lControlName: case lPunchName: - if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false, true) > 0)) { + if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false, true, false) > 0)) { swprintf_s(wbf, L"%s", ctrl->getName().c_str()); } else if (counter.level3 == nCtrl) { @@ -2304,27 +2352,30 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } break; - case lPunchTime: { + case lPunchTime: + case lPunchTeamTime: { swprintf_s(wbf, L"%s (%s)", - r->getSplitTimeS(counter.level3, false).c_str(), - r->getPunchTimeS(counter.level3, false, true, SubSecond::Off).c_str()); + r->getSplitTimeS(counter.level3, false, SubSecond::Off).c_str(), + r->getPunchTimeS(counter.level3, false, true, pp.type == lPunchTeamTime, SubSecond::Off).c_str()); break; } case lPunchSplitTime: { - wcscpy_s(wbf, r->getSplitTimeS(counter.level3, false).c_str()); + wcscpy_s(wbf, r->getSplitTimeS(counter.level3, false, SubSecond::Off).c_str()); break; } - case lPunchTotalTime: { - if (r->getPunchTime(counter.level3, false, true) > 0) { - wcscpy_s(wbf, r->getPunchTimeS(counter.level3, false, true, SubSecond::Off).c_str()); - } + case lPunchTotalTime: + case lPunchTeamTotalTime: { + int pt = r->getPunchTime(counter.level3, false, true, pp.type == lPunchTeamTotalTime); + if (pt > 0) + wsptr = &formatTime(pt, SubSecond::Off); break; } - case lPunchTotalTimeAfter: { - if (r->getPunchTime(counter.level3, false, true) > 0) { - int rt = r->getLegTimeAfterAcc(counter.level3); + case lPunchTotalTimeAfter: + case lPunchTeamTotalTimeAfter: { + if (r->getPunchTime(counter.level3, false, true, false) > 0) { + int rt = r->getLegTimeAfterAcc(counter.level3, pp.type == lPunchTeamTotalTimeAfter); if (rt > 0) - wcscpy_s(wbf, (L"+" + formatTime(rt)).c_str()); + wcscpy_s(wbf, (L"+" + formatTime(rt, SubSecond::Off)).c_str()); } break; } @@ -2350,8 +2401,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara swprintf_s(wbf, L"%d", p); break; } - case lPunchControlPlaceAcc: { - int p = r->getLegPlaceAcc(counter.level3); + case lPunchControlPlaceAcc: + case lPunchControlPlaceTeamAcc: { + int p = r->getLegPlaceAcc(counter.level3, pp.type == lPunchControlPlaceTeamAcc); if (p > 0) swprintf_s(wbf, L"%d", p); break; @@ -2361,7 +2413,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; } case lPunchAbsTime: { - int t = r->getPunchTime(counter.level3, false, true); + int t = r->getPunchTime(counter.level3, false, true, false); if (t > 0) wsptr = &getAbsTime(r->tStartTime + t); break; @@ -3359,8 +3411,8 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form } } else if (li.listSubType == li.EBaseTypeCoursePunches || - li.listSubType == li.EBaseTypeAllPunches) { - pRunner r = it->Runners.empty() ? 0 : it->Runners[0]; + li.listSubType == li.EBaseTypeAllPunches) { + pRunner r = it->getRunner(linearLegSpec); if (!r) return true; diff --git a/code/oListInfo.h b/code/oListInfo.h index 92c8377..11c8640 100644 --- a/code/oListInfo.h +++ b/code/oListInfo.h @@ -61,6 +61,7 @@ enum EPostType { lCourseUsage, lCourseUsageNoVacant, lCourseClasses, + lCourseNumControls, lRunnerName, lRunnerGivenName, lRunnerFamilyName, @@ -172,19 +173,28 @@ enum EPostType { lTeamPlaceDiff, lPunchNamedTime, + lPunchTeamTotalNamedTime, lPunchNamedSplit, + lPunchName, lPunchTime, + lPunchTeamTime, + lPunchControlNumber, lPunchControlCode, lPunchLostTime, lPunchControlPlace, lPunchControlPlaceAcc, + lPunchControlPlaceTeamAcc, lPunchSplitTime, lPunchTotalTime, lPunchTotalTimeAfter, + + lPunchTeamTotalTime, + lPunchTeamTotalTimeAfter, + lPunchAbsTime, lPunchTimeSinceLast, diff --git a/code/oRunner.cpp b/code/oRunner.cpp index 4702e1d..3e84e00 100644 --- a/code/oRunner.cpp +++ b/code/oRunner.cpp @@ -2027,19 +2027,9 @@ bool oRunner::operator<(const oRunner &c) const { pClub cl = getClubRef(); pClub ocl = c.getClubRef(); if (cl != ocl) { - if (cl == nullptr && ocl) - return true; - else if (ocl == nullptr) - return false; - - const wstring a = cl->getName(); - const wstring b = ocl->getName(); - int res = CompareString(LOCALE_USER_DEFAULT, 0, - a.c_str(), a.length(), - b.c_str(), b.length()); - - if (res != CSTR_EQUAL) - return res == CSTR_LESS_THAN; + int cres = compareClubs(cl, ocl); + if (cres != 2) + return cres != 0; } } @@ -2401,7 +2391,9 @@ bool oRunner::operator<(const oRunner &c) const { else return tStartTime < c.tStartTime; } else if (Club != c.Club) { - return getClub() < c.getClub(); + int cres = compareClubs(Club, c.Club); + if (cres != 2) + return cres != 0; } } else if (oe->CurrentSortOrder == ClassTeamLeg) { @@ -4173,11 +4165,11 @@ int oRunner::getSplitTime(int controlNumber, bool normalized) const { if (!Card) { if (controlNumber == 0) - return getPunchTime(0, false, true); + return getPunchTime(0, false, true, false); else { - int ct = getPunchTime(controlNumber, false, true); + int ct = getPunchTime(controlNumber, false, true, false); if (ct > 0) { - int dt = getPunchTime(controlNumber - 1, false, true); + int dt = getPunchTime(controlNumber - 1, false, true, false); if (dt > 0 && ct > dt) return ct - dt; } @@ -4219,7 +4211,7 @@ int oRunner::getNamedSplit(int controlNumber) const { return -1; int k=controlNumber-1; - int ct = getPunchTime(controlNumber, false, true); + int ct = getPunchTime(controlNumber, false, true, false); if (ct <= 0) return -1; @@ -4228,7 +4220,7 @@ int oRunner::getNamedSplit(int controlNumber) const { pControl c = crs->Controls[k]; if (c && c->hasName()) { - int dt = getPunchTime(k, false, true); + int dt = getPunchTime(k, false, true, false); if (dt > 0 && ct > dt) return max(ct - dt, -1); else return -1; @@ -4240,52 +4232,53 @@ int oRunner::getNamedSplit(int controlNumber) const { return ct; } -wstring oRunner::getSplitTimeS(int controlNumber, bool normalized) const +const wstring &oRunner::getSplitTimeS(int controlNumber, bool normalized, SubSecond mode) const { - return formatTime(getSplitTime(controlNumber, normalized)); + return formatTime(getSplitTime(controlNumber, normalized), mode); } -wstring oRunner::getNamedSplitS(int controlNumber) const +const wstring &oRunner::getNamedSplitS(int controlNumber, SubSecond mode) const { - return formatTime(getNamedSplit(controlNumber)); + return formatTime(getNamedSplit(controlNumber), mode); } -int oRunner::getPunchTime(int controlNumber, bool normalized, bool adjusted) const +int oRunner::getPunchTime(int controlIndex, bool normalized, bool adjusted, bool teamTotal) const { + int off = teamTotal && tInTeam ? tInTeam->getTotalRunningTimeAtLegStart(getLegNumber(), false) : 0; + if (!Card) { pCourse pc = getCourse(false); - if (!pc || controlNumber > pc->getNumControls()) + if (!pc || controlIndex > pc->getNumControls()) return -1; - if (controlNumber == pc->getNumControls()) - return getFinishTime() - tStartTime; + if (controlIndex == pc->getNumControls()) + return getFinishTime() - tStartTime + off; - int ccId = pc->getCourseControlId(controlNumber); + int ccId = pc->getCourseControlId(controlIndex); pFreePunch fp = oe->getPunch(Id, ccId, getCardNo()); if (fp) - return fp->getTimeInt() - tStartTime; + return fp->getTimeInt() - tStartTime + off; return -1; } const vector &st = getSplitTimes(normalized); - if (unsigned(controlNumber) < st.size()) { - if (st[controlNumber].hasTime()) - return st[controlNumber].getTime(adjusted) - tStartTime; + if (unsigned(controlIndex) < st.size()) { + if (st[controlIndex].hasTime()) + return st[controlIndex].getTime(adjusted) - tStartTime + off; else return -1; } - else if (unsigned(controlNumber) == st.size()) - return FinishTime - tStartTime; + else if (unsigned(controlIndex) == st.size()) + return FinishTime - tStartTime + off; return -1; } -wstring oRunner::getPunchTimeS(int controlNumber, bool normalized, bool adjusted, SubSecond mode) const -{ - return formatTime(getPunchTime(controlNumber, normalized, adjusted), mode); +const wstring &oRunner::getPunchTimeS(int controlIndex, bool normalized, bool adjusted, + bool teamTotal, SubSecond mode) const { + return formatTime(getPunchTime(controlIndex, normalized, adjusted, teamTotal), mode); } -bool oAbstractRunner::isVacant() const -{ +bool oAbstractRunner::isVacant() const { int vacClub = oe->getVacantClubIfExist(false); return vacClub > 0 && getClubId()==vacClub; } @@ -4409,7 +4402,8 @@ void oRunner::fillSpeakerObject(int leg, int courseControlId, int previousContro } } -pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_set &inputFilter, +pRunner oEvent::findRunner(const wstring &s, int lastId, + const unordered_set &inputFilter, unordered_set &matchFilter) const { matchFilter.clear(); @@ -4417,8 +4411,8 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_setnControls; n++) { spMax = max(spMax, getSplitTime(n, false)); - totMax = max(totMax, getPunchTime(n, false, false)); + totMax = max(totMax, getPunchTime(n, false, false, false)); } } bool moreThanHour = max(totMax, getRunningTime(true)) >= timeConstHour; @@ -5082,7 +5076,7 @@ void oRunner::printSplits(gdioutput& gdi, const oListInfo* li) const { adjust = getTimeAdjust(controlLegIndex); sp = getSplitTime(controlLegIndex, false); if (sp > 0) { - punchTime = getPunchTimeS(controlLegIndex, false, false, SubSecond::Off); + punchTime = getPunchTimeS(controlLegIndex, false, false, false, SubSecond::Off); gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(sp, SubSecond::Off)); } } @@ -5807,7 +5801,7 @@ void oRunner::getLegTimeAfter(vector ×) const } } -void oRunner::getLegTimeAfterAcc(vector ×) const +void oRunner::getLegTimeAfterAcc(vector ×) const { times.clear(); if (splitTimes.empty() || !Class || tStartTime<=0) @@ -5835,6 +5829,9 @@ void oRunner::getLegTimeAfterAcc(vector ×) const //xxx reorder output times.resize(nc+1); + bool isRelayTeam = tInTeam != nullptr; + int off = tInTeam ? tInTeam->getTotalRunningTimeAtLegStart(tLeg, false) : 0; + for (unsigned k = 0; k<=nc; k++) { int s = 0; if (k < sp.size()) @@ -5843,18 +5840,27 @@ void oRunner::getLegTimeAfterAcc(vector ×) const s = FinishTime; if (s>0) { - times[k] = s - tStartTime - leaders[k]; - if (times[k]<0) - times[k] = -1; + times[k].data = s - tStartTime - leaders[k]; + if (times[k].data < 0) + times[k].data = -1; } else - times[k] = -1; + times[k].data = -1; + + if (!isRelayTeam || times[k].data < 0) + times[k].teamTotalData = times[k].data; + else { + if (k < nc) + times[k].teamTotalData = s - tStartTime + off - cls->getAccLegControlLeader(tLeg, pc->getCourseControlId(k)); + else + times[k].teamTotalData = s - tStartTime + off - cls->getAccLegControlLeader(tLeg, oPunch::PunchFinish); + } } // Normalized order const vector &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { - vector orderedTimes(times.size()); + vector orderedTimes(times.size()); for (size_t k = 0; k < min(reorder.size(), times.size()); k++) { orderedTimes[k] = times[reorder[k]]; } @@ -5862,7 +5868,7 @@ void oRunner::getLegTimeAfterAcc(vector ×) const } } -void oRunner::getLegPlacesAcc(vector &places) const +void oRunner::getLegPlacesAcc(vector &places) const { places.clear(); pCourse pc = getCourse(false); @@ -5880,6 +5886,10 @@ void oRunner::getLegPlacesAcc(vector &places) const const unsigned nc = pc->getNumControls(); const vector &sp = getSplitTimes(true); places.resize(nc+1); + + bool isRelayTeam = tInTeam != nullptr; + int off = tInTeam ? tInTeam->getTotalRunningTimeAtLegStart(tLeg, false) : 0; + for (unsigned k = 0; k<=nc; k++) { int s = 0; if (k < sp.size()) @@ -5890,17 +5900,24 @@ void oRunner::getLegPlacesAcc(vector &places) const if (s>0) { int time = s - tStartTime; - if (time>0) - places[k] = cls->getAccLegPlace(id, k, time); - else - places[k] = 0; + if (time > 0) { + places[k].data = cls->getAccLegPlace(id, k, time); + if (k < nc) + places[k].teamTotalData = cls->getAccLegControlPlace(tLeg, pc->getCourseControlId(k), time + off); + else + places[k].teamTotalData = cls->getAccLegControlPlace(tLeg, oPunch::PunchFinish, time + off); + } + else { + places[k].data = 0; + places[k].teamTotalData = 0; + } } } // Normalized order const vector &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { - vector orderedPlaces(reorder.size()); + vector orderedPlaces(reorder.size()); for (size_t k = 0; k < reorder.size(); k++) { orderedPlaces[k] = places[reorder[k]]; } @@ -5974,31 +5991,31 @@ int oRunner::getLegTimeAfter(int ctrlNo) const { return -1; } -int oRunner::getLegPlaceAcc(int ctrlNo) const { +int oRunner::getLegPlaceAcc(int ctrlNo, bool teamTotal) const { for (auto &res : tOnCourseResults.res) { if (res.controlIx == ctrlNo) - return res.place; + return teamTotal ? res.place : res.teamTotalPlace; } if (!Card) { return 0; } setupRunnerStatistics(); if (unsigned(ctrlNo) < tPlaceLegAcc.size()) - return tPlaceLegAcc[ctrlNo]; + return tPlaceLegAcc[ctrlNo].get(teamTotal); else return 0; } -int oRunner::getLegTimeAfterAcc(int ctrlNo) const { +int oRunner::getLegTimeAfterAcc(int ctrlNo, bool teamTotal) const { for (auto &res : tOnCourseResults.res) { if (res.controlIx == ctrlNo) - return res.after; + return teamTotal ? res.teamTotalAfter : res.after; } if (!Card) return -1; setupRunnerStatistics(); if (unsigned(ctrlNo) < tAfterLegAcc.size()) - return tAfterLegAcc[ctrlNo]; + return tAfterLegAcc[ctrlNo].get(teamTotal); else return -1; } diff --git a/code/oRunner.h b/code/oRunner.h index ec81a6f..593bdf2 100644 --- a/code/oRunner.h +++ b/code/oRunner.h @@ -87,6 +87,25 @@ enum SortOrder { SortEnumLastItem }; +static bool orderByClass(SortOrder so) { + switch (so) { + case ClassStartTime: + case ClassTeamLeg: + case ClassResult: + case ClassDefaultResult: + case ClassCourseResult: + case ClassTotalResult: + case ClassTeamLegResult: + case ClassFinishTime: + case ClassStartTimeClub: + case ClassPoints: + case ClassLiveResult: + case ClassKnockoutTotalResult: + return true; + } + return false; +} + class oRunner; typedef oRunner* pRunner; typedef const oRunner* cRunner; @@ -178,6 +197,9 @@ public: protected: TempResult tmpResult; + /** Return 1 if a tMissedTime; mutable vector tPlaceLeg; mutable vector tAfterLeg; - mutable vector tPlaceLegAcc; - mutable vector tAfterLegAcc; + 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 time, + int teamTotalTime) : courseControlId(courseControlId), + controlIx(controlIx), time(time), + teamTotalTime(teamTotalTime) {} int courseControlId; int controlIx; int time; - int place; - int after; + int teamTotalTime; + + int place = -1; + int after = -1; + + int teamTotalPlace = -1; + int teamTotalAfter = -1; }; mutable pair currentControlTime; @@ -660,8 +707,9 @@ protected: void clear() { hasAnyRes = false; res.clear(); } void emplace_back(int courseControlId, int controlIx, - int time) { - res.emplace_back(courseControlId, controlIx, time); + int time, + int teamTotalTime) { + res.emplace_back(courseControlId, controlIx, time, teamTotalTime); hasAnyRes = true; } bool empty() const { return hasAnyRes == false; } @@ -867,8 +915,8 @@ public: int getMissedTime(int ctrlNo) const; int getLegPlace(int ctrlNo) const; int getLegTimeAfter(int ctrlNo) const; - int getLegPlaceAcc(int ctrlNo) const; - int getLegTimeAfterAcc(int ctrlNo) const; + int getLegPlaceAcc(int ctrlNo, bool teamTotal) const; + int getLegTimeAfterAcc(int ctrlNo, bool teamTotal) const; /** Calculate the time when the runners place is fixed, i.e, when no other runner can threaten the place. @@ -957,22 +1005,25 @@ public: void getLegPlaces(vector &places) const; void getLegTimeAfter(vector &deltaTimes) const; - void getLegPlacesAcc(vector &places) const; - void getLegTimeAfterAcc(vector &deltaTimes) const; + void getLegPlacesAcc(vector &places) const; + void getLegTimeAfterAcc(vector &deltaTimes) const; // Normalized = true means permuted to the unlooped version of the course int getSplitTime(int controlNumber, bool normalized) const; int getTimeAdjust(int controlNumber) const; int getNamedSplit(int controlNumber) const; - wstring getNamedSplitS(int controlNumber) const; + const wstring &getNamedSplitS(int controlNumber, SubSecond mode) const; + + /** Get running time from start to a control (specified by its index on the course) + normalized: true means permuted to the unlooped version of the course + teamTotalTime: true means time is measured from the team's starting time + */ + int getPunchTime(int controlIndex, bool normalized, bool adjusted, bool teamTotalTime) const; + const wstring &getPunchTimeS(int controlIndex, bool normalized, bool adjusted, bool teamTotalTime, SubSecond mode) const; // Normalized = true means permuted to the unlooped version of the course - int getPunchTime(int controlNumber, bool normalized, bool adjusted) const; - wstring getPunchTimeS(int controlNumber, bool normalized, bool adjusted, SubSecond mode) const; - - // Normalized = true means permuted to the unlooped version of the course - wstring getSplitTimeS(int controlNumber, bool normalized) const; + const wstring &getSplitTimeS(int controlNumber, bool normalized, SubSecond mode) const; void addTableRow(Table &table) const; pair inputData(int id, const wstring &input, diff --git a/code/oTeam.cpp b/code/oTeam.cpp index 0e82fa0..7087489 100644 --- a/code/oTeam.cpp +++ b/code/oTeam.cpp @@ -436,6 +436,17 @@ int oTeam::getLegFinishTime(int leg) const else return 0; } +int oTeam::getTotalRunningTimeAtLegStart(int leg, bool multidayTotal) const { + int off = multidayTotal ? max(0, getInputTime()) : 0; + if (!Class || leg == 0) + return off; + int pleg = Class->getPreceedingLeg(leg); + if (pleg < 0) + return off; + + return getLegRunningTime(pleg, false, multidayTotal); +} + int oTeam::getRunningTime(bool computedTime) const { return getLegRunningTime(-1, computedTime, false); } @@ -812,9 +823,7 @@ wstring oTeam::getLegPrintPlaceS(int leg, bool multidayTotal, bool withDot) cons return _EmptyWString; } -bool oTeam::compareResultClub(const oTeam& a, const oTeam& b) { - pClub ca = a.getClubRef(); - pClub cb = b.getClubRef(); +int oAbstractRunner::compareClubs(const oClub* ca, const oClub* cb) { if (ca != cb) { if (ca == nullptr && cb) return true; @@ -830,6 +839,17 @@ bool oTeam::compareResultClub(const oTeam& a, const oTeam& b) { if (res != CSTR_EQUAL) return res == CSTR_LESS_THAN; } + return 2; +} + +bool oTeam::compareResultClub(const oTeam& a, const oTeam& b) { + pClub ca = a.getClubRef(); + pClub cb = b.getClubRef(); + if (ca != cb) { + int cres = compareClubs(ca, cb); + if (cres != 2) + return cres != 0; + } return compareResult(a, b); } @@ -855,8 +875,13 @@ bool oTeam::compareResult(const oTeam &a, const oTeam &b) int aix = a.getDCI().getInt("SortIndex"); int bix = b.getDCI().getInt("SortIndex"); - if (aix != bix) + if (aix != bix) { + if (aix == 0) + aix = numeric_limits::max(); + if (bix == 0) + bix = numeric_limits::max(); return aix < bix; + } return CompareString(LOCALE_USER_DEFAULT, 0, a.sName.c_str(), a.sName.length(), @@ -877,6 +902,24 @@ bool oTeam::compareResultNoSno(const oTeam &a, const oTeam &b) else if (a.tmpSortTime != b.tmpSortTime) return a.tmpSortTime::max(); + if (bix == 0) + bix = numeric_limits::max(); + return aix < bix; + } + + pClub ca = a.getClubRef(); + pClub cb = b.getClubRef(); + if (ca != cb) { + int cres = compareClubs(ca, cb); + if (cres != 2) + return cres != 0; + } + return CompareString(LOCALE_USER_DEFAULT, 0, a.sName.c_str(), a.sName.length(), b.sName.c_str(), b.sName.length()) == CSTR_LESS_THAN; @@ -1751,7 +1794,7 @@ pRunner oTeam::getRunner(unsigned leg) const { if (leg==-1) leg=Runners.size()-1; - return leg &filter) int len = trm.length(); wchar_t s_lc[1024]; wcscpy_s(s_lc, trm.c_str()); - CharLowerBuff(s_lc, len); + prepareMatchString(s_lc, len); int sn = _wtoi(s.c_str()); oTeamList::const_iterator it; diff --git a/code/onlineinput.cpp b/code/onlineinput.cpp index 8832dba..24a32a8 100644 --- a/code/onlineinput.cpp +++ b/code/onlineinput.cpp @@ -478,7 +478,7 @@ void OnlineInput::processEntries(oEvent &oe, const xmlList &entries) { bool paid = entry.getObjectBool("paid"); xmlobject xname = entry.getObject("name"); - wstring birthyear = 0; + wstring birthyear; if (xname) { xname.getObjectString("birthyear", birthyear); } diff --git a/code/resource.h b/code/resource.h index 2bb1870..6562caf 100644 --- a/code/resource.h +++ b/code/resource.h @@ -17,6 +17,7 @@ #define IDI_SPLASHIMAGE 512 #define IDI_MEOSIMAGE 513 #define IDI_MEOSINFO 514 +#define IDI_MEOSEDIT 515 // Next default values for new objects // diff --git a/code/restserver.cpp b/code/restserver.cpp index cc486eb..bb88545 100644 --- a/code/restserver.cpp +++ b/code/restserver.cpp @@ -1150,7 +1150,7 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetSplitTimes(false); vector after; r->getLegTimeAfter(after); - vector afterAcc; + vector afterAcc; r->getLegTimeAfterAcc(afterAcc); vector delta; r->getSplitAnalysis(delta); @@ -1177,8 +1177,8 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimap 0) - analysis[1].second = formatTime(afterAcc[ix]); + if (afterAcc[ix].get(0) > 0) + analysis[1].second = formatTime(afterAcc[ix].get(false)); else analysis[1].second = L""; @@ -1190,7 +1190,7 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetLegPlace(ix); analysis[3].second = place > 0 ? itow(place) : L""; - int placeAcc = r->getLegPlaceAcc(ix); + int placeAcc = r->getLegPlaceAcc(ix, false); analysis[4].second = placeAcc > 0 ? itow(placeAcc) : L""; xml.write("Analysis", analysis, L""); diff --git a/code/swedish.lng b/code/swedish.lng index 6177cc5..fd070cd 100644 --- a/code/swedish.lng +++ b/code/swedish.lng @@ -2683,3 +2683,8 @@ Bevara höjd/bredd-relationen = Bevara höjd/bredd-relationen RunnerLegTeamLeaderName = Först i mål på sträckan info:offsetclassid = Om du importerar anmälningar och klasser från olika källor till samma tävling kan det hända att klassernas Id-nummer krockar. För att hålla isär klasserna kan du då ange en förskjutning av Id-nummer när du arbetar med datafiler från en viss källa; denna kommer att adderas till klassernas Id-nummer.\n\nDu måste ange samma förskjutning varje gång du importerar från en viss källa. Ett lämpligt värde kan vara 1000 (som fungerar om all Id-nummer är mindre än 1000). Förskjutning av klassers Id = Förskjutning av klassers Id +Tilldela nummerlapp till vakanter = Tilldela nummerlapp till vakanter +Avläsning = Avläsning +Inmatning Testning = Inmatning Testning +Testning = Testning +CourseNumControls = Antal kontroller