Development version

This commit is contained in:
Erik Melin 2023-09-07 22:30:09 +02:00
parent 16272ffa20
commit a1a05bd56d
41 changed files with 1512 additions and 572 deletions

View File

@ -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,6 +3906,14 @@ 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");
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);
@ -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);
}
}

View File

@ -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);
@ -3011,7 +3012,6 @@ void TabList::setResultOptionsFromType(gdioutput &gdi, int data) {
gdi.setInputStatus("ShowInterResults", builtIn);
gdi.setInputStatus("ShowSplits", builtIn);
set<int> clsUnused;
vector< pair<wstring, size_t> > 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 {
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<int> clsUnused;
vector< pair<wstring, size_t> > 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<int> sel;
gdi.getSelection("ListSelection", sel);
return !sel.empty();
}
void TabList::clearCompetitionData() {
SelectedList = "";
lastResultClassSelection.clear();
lastClassSelection.clear();
ownWindow = false;
hideButtons = false;

View File

@ -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);

View File

@ -207,8 +207,8 @@ void TabRunner::selectRunner(gdioutput &gdi, pRunner r) {
vector<int> delta;
vector<int> place;
vector<int> after;
vector<int> placeAcc;
vector<int> afterAcc;
vector<oRunner::ResultData> placeAcc;
vector<oRunner::ResultData> 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);
@ -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);
}
else if ((ei.id == "ReadCard") &&
(listenToPunches || oe->isReadOnly()) && currentMode == 5) {
if (ei.getData() > 0) {
vector<pRunner> rs;
oe->getRunnersByCardNo(ei.getData(), true, oEvent::CardLookupProperty::Any, rs);
if (!rs.empty()) {
runnersToReport.resize(rs.size());
for (size_t k = 0; k<rs.size(); k++)
runnersToReport[k] = make_pair(rs[k]->getId(), 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,85 +1622,312 @@ 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<pFreePunch> 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();
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;
}
if (!oe->isReadOnly()) {
if (!oe->isReadOnly() && !hideReportControls) {
if (!ownWindow) {
gdi.addButton("Kiosk", "Resultatkiosk", RunnerCB);
gdi.addButton("Window", "Eget fönster", RunnerCB, "Öppna i ett nytt fönster.");
}
gdi.dropLine(0.2);
gdi.addCheckbox("ListenReadout", "Visa senast inlästa deltagare", RunnerCB, listenToPunches);
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<pair<wstring, size_t>> 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);
gdi.registerEvent("PunchCard", RunnerCB);
gdi.setData("DataSync", 1);
gdi.setData("PunchSync", 1);
if (runnerId > 0) {
runnersToReport.resize(1);
runnersToReport[0] = make_pair(runnerId, false);
bool found = false;
addToReport(runnerId);
}
generateRunnerReport(*oe, gdi, runnersToReport);
while (runnersToReport.size() > numReportRow * numReportColumn)
runnersToReport.pop_back();
if (runnersToReport.size() == 1)
generateRunnerReport(*oe, gdi, numReportColumn, numReportRow, true, runnersToReport);
if (runnersToReport.size() > 0)
runnerId = runnersToReport[0].first;
}
void TabRunner::generateRunnerReport(oEvent &oe, gdioutput &gdi, vector<pair<int, bool>> &runnersToReport) {
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);
}
}
void TabRunner::addToReport(int cardNo, bool punchForShowReport) {
vector<pRunner> rs;
oe->getRunnersByCardNo(cardNo, true, oEvent::CardLookupProperty::Any, rs);
if (!punchForShowReport) {
// Take away runners with no card
vector<pRunner> 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<int, vector<pRunner>> 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; k<rs.size(); k++)
runnersToReport[k] = make_pair(rs[k]->getId(), false);*/
}
runnerId = 0;
}
void TabRunner::generateRunnerReport(oEvent &oe, gdioutput &gdi,
int numX, int numY,
bool onlySelectedRunner,
const deque<pair<int, bool>> &runnersToReport) {
oe.synchronizeList({ oListId::oLRunnerId, oListId::oLTeamId, oListId::oLPunchId });
gdi.fillDown();
int xx, yy;
int margin = gdi.scaleLength(16);
gdi.getTargetDimension(xx, yy);
cTeam t = 0;
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<const oTeam *> tList;
set<int> 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);
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);
};
int baseX = gdi.getCX();
int baseY = gdi.getCY();
vector<RECT> rcList;
auto updatePositionDrawBox = [&](bool force) {
if (!force)
rcList.push_back(rcFrame);
if ((force && !rcList.empty()) || rcList.size() == numX) {
int maxYP = 0;
for (RECT& rc : rcList)
maxYP = max<int>(maxYP, rc.bottom);
for (RECT& rc : rcList) {
rc.bottom = maxYP;
drawBox(rc);
}
else {
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::teamReport(oEvent& oe, gdioutput& gdi,
const oTeam* t,
bool onlySelectedRunner,
const deque<pair<int, bool>>& runnersToReport,
int maxWidth,
RECT& rc) {
rc.top = gdi.getCY();
rc.left = gdi.getCX();
rc.right = rc.left + maxWidth;
set<int> selectedRunners;
bool selHasRes = false;
bool selHasPartialRes = 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->getTeam() == t) {
selectedRunners.insert(runnersToReport[k].first);
if (r && r->hasOnCourseResult())
selHasRes = true;
selHasPartialRes = true; //Partial (radio) or complete result
}
}
wstring tInfo = t->getName();
@ -1705,32 +1944,70 @@ void TabRunner::showRunnerReport(gdioutput &gdi) {
gdi.addStringUT(boldLarge, tInfo);
gdi.dropLine();
pClass cls = t->getClassRef(false);
bool visitedSelected = false;
for (int leg = 0; leg < t->getNumRunners(); leg++) {
if (selHasRes && visitedSelected)
if ((selHasPartialRes || onlySelectedRunner) && visitedSelected)
break;
pRunner r = t->getRunner(leg);
pRunner nextR = t->getRunner(leg + 1);
bool nextSelected = nextR && selectedRunners.count(nextR->getId());
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 (selHasRes) {
runnerReport(oe, gdi, r->getId(), !selected);
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 {
runnerReport(oe, gdi, r->getId(), !nextSelected);
// 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) {
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<int> delta;
vector<int> place;
vector<int> after;
vector<int> placeAcc;
vector<int> afterAcc;
vector<oRunner::ResultData> placeAcc;
vector<oRunner::ResultData> 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);

View File

@ -23,6 +23,7 @@
#include "tabbase.h"
#include "Printer.h"
#include "autocompletehandler.h"
#include <deque>
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<int> lastFilter;
DWORD timeToFill;
@ -64,7 +74,7 @@ private:
int runnerId;
bool ownWindow;
bool listenToPunches;
vector< pair<int, bool> > runnersToReport;
deque<pair<int, bool>> runnersToReport;
vector<pRunner> unknown_dns;
vector<pRunner> 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<pair<int, bool>> &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<pair<int, bool>> &runnersToReport);
static void generateRunnerReport(oEvent &oe,
gdioutput &gdi,
int numX, int numY,
bool onlySelectedRunner,
const deque<pair<int, bool>> &runnersToReport);
TabRunner(oEvent *oe);
~TabRunner(void);

View File

@ -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.nPunch<unsigned(NC); sic.nPunch++) {
int c = gdi.getTextNo("C" + itos(sic.nPunch + 1));
int c = testControls[sic.nPunch];
sic.Punch[sic.nPunch].Time = ((int(f * t + s * (1.0 - t))/ timeUnitsPerSecond) % (24 * timeConstSecPerHour)) * timeUnitsPerSecond;
t += ((1.0 - t) * (sic.nPunch + 1) / 10.0) * ((rand() % 100) + 400.0) / 500.0;
if ((sic.nPunch % 11) == 1 || 5 == (sic.nPunch % 8))
@ -754,31 +778,34 @@ int TabSI::siCB(gdioutput& gdi, int type, void* data)
sic.FinishPunch.Code = -1;
sic.CheckPunch.Code = -1;
sic.StartPunch.Code = -1;
sic.CardNumber = gdi.getTextNo("SI");
int f = convertAbsoluteTimeHMS(gdi.getText("Finish"), oe->getZeroTimeNum());
if (f > 0) {
sic.FinishPunch.Time = f;
sic.FinishPunch.Code = 1;
sic.punchOnly = true;
gSI->addCard(sic);
return 0;
}
int t = convertAbsoluteTimeHMS(gdi.getText("PunchTime"), oe->getZeroTimeNum());
testPunchTime.clear();
testCardNumber = 0;
int s = convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum());
if (s > 0) {
sic.StartPunch.Time = s;
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;
}
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") {
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 (gSI->isPortOpen(bf))
active = 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<TabRunner&>(*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) {
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<SICard>& 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,28 +2000,66 @@ bool TabSI::loadPage(gdioutput& gdi) {
firstLoadedAfterNew = false;
}
#ifdef _DEBUG
if (showTestingPanel) {
RECT rc;
rc.left = gdi.getCX();
rc.top = gdi.getCY();
gdi.setCX(gdi.getCX() + gdi.scaleLength(4));
gdi.setCY(gdi.getCY() + gdi.scaleLength(4));
gdi.addString("", fontMediumPlus, "Inmatning Testning");
gdi.fillRight();
gdi.pushX();
gdi.addInput("SI", L"", 10, SportIdentCB, L"SI");
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("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.4);
gdi.addCheckbox("HasStart", "");
gdi.dropLine(-1.4);
gdi.dropLine(1.6);
gdi.addCheckbox("HasCheck", "", nullptr, useTestCheck);
gdi.dropLine(-1.6);
gdi.setCX(gdi.getCX() - gdi.getLineHeight());
gdi.addInput("Start", oe->getAbsTime(s), 6, 0, L"Start");
gdi.addInput("Check", cs, 6, 0, L"Check:");
gdi.dropLine(1.4);
gdi.addCheckbox("HasFinish", "");
gdi.dropLine(-1.4);
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", oe->getAbsTime(f), 6, 0, L"Mål");
gdi.addSelection("NC", 45, 200, SportIdentCB, L"NC");
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++)
@ -1984,16 +2081,41 @@ bool TabSI::loadPage(gdioutput& gdi) {
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.addButton("Save", "Bricka", SportIdentCB);
gdi.fillDown();
gdi.addButton("SaveP", "Stämpling", SportIdentCB);
gdi.addButton("Save", "Spara", SportIdentCB);
gdi.popX();
#endif
}
else {
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("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.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);
@ -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));
}
}

View File

@ -94,6 +94,8 @@ private:
shared_ptr<GuiHandler> 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<int> 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<int> MP;
GDICOLOR color;
bool rentCard = false;
int runnerId = 0;
RECT computeRC(gdioutput &gdi) const;
void render(gdioutput &gdi, const RECT &rc) const;

View File

@ -241,11 +241,11 @@ int TabSpeaker::processButton(gdioutput &gdi, const ButtonInfo &bu)
gdi.dropLine(3);
gdi.popX();
gdi.registerEvent("DataUpdate", tabSpeakerCB);
vector<pair<int, bool>> runnersToReport;
deque<pair<int, bool>> 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") {

View File

@ -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;k<baseIndex.size();k++) {

BIN
code/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

View File

@ -256,7 +256,7 @@ Checkenhet = Check unit
Choose result module = Choose result module
ClassAvailableMaps = Available maps for class
ClassCourseResult = Class, course, result
ClassDefaultResult = Class, Default result
ClassDefaultResult = Class, default result
ClassFinishTime = Class, finish time
ClassKnockoutTotalResult = Class, knock-out total result
ClassLength = Course length for class
@ -2674,3 +2674,8 @@ Bevara höjd/bredd-relationen = Preserve aspect ratio
RunnerLegTeamLeaderName = First competitor in team to finish leg
info:offsetclassid = If you import entries and classes from different sources to the same competition, it might happen that there are clashes in the Id numbers of the classes. To separate the classes, you may enter an offset for the Class Id when working with files from a particular source; this will be added to the Id numbers.\n\nYou must specify the same offset each time you import from that source.\n\n A suitble number could be1000 (which will work as long as every Id is less that 1000).
Förskjutning av klassers Id = Offset Class ID
Tilldela nummerlapp till vakanter = Assign bibs to vacancies
Avläsning = Readout
Inmatning Testning = Input Testing
Testning = Testing
CourseNumControls = Number of controls

View File

@ -214,7 +214,9 @@ Bevakar händelser i X = Moniteur d'évènement X
Bevakningsprioritering = Sélectionner le coureur à surveiller
Bevara höjd/bredd-relationen = Conserver les proportions
Bibs = Dossards
Bild = Image
Bild under text = Mettre l'image sous le texte
Bilder = Images
Block = Bloc
Blockbredd = Largeur de bloc
Bläddra = Parcourir
@ -265,7 +267,7 @@ ClassKnockoutTotalResult = Class, knock-out total result
ClassLength = Longueur du circuit pour la catégorie
ClassLiveResult = Live results (temps radios), par catégorie
ClassName = Catégorie
ClassNumEntries = Nombre d'inscrits
ClassNumEntries = Nombre d'inscrits dans la catégorie
ClassPoints = Catégorie, points
ClassResult = Catégorie, résultat
ClassResultFraction = Pourcentage de la catégorie ayant terminé
@ -422,6 +424,7 @@ Elektronisk godkänd = Accepté électroniquement
Elit = Élite
Elitavgift = Frais élites
Elitklasser = Catégorie élite
Empty Results Split Print = Temps intermédiaires sans résultats
En bana med slingor tillåter deltagaren att ta slingorna i valfri ordning = Un circuit avec des boucles autorise le coureur à effectuer les boucles dans n'importe quel ordre.
En gafflad sträcka = Circuit avec variations
En klass kan inte slås ihop med sig själv = Vous ne pouvez pas fusionner une catégorie avec elle-même
@ -621,6 +624,7 @@ Förhindra att laget deltar i någon omstart = Permet à l'équipe de ne pas pre
Förhindra omstart = Refuser le départ des attardés
Förhöjd avgift = Frais supplémentaires
Förskjutning = Déplacement
Förskjutning av klassers Id = Décalage de l'identifiant de catégorie
Först = En premier
Först-i-mål, gemensam = Du premier au dernier, commun
Först-i-mål, klassvis = Du premier au dernier, par catégorie
@ -685,6 +689,7 @@ Hittar inte hjälpfilen, X = Documentation introuvable, X
Hittar inte klass X = Impossible de trouver la catégorie X
Hjälp = Aide
Hoppar över stafettklass: X = Sauter la catégorie de relais: X
Horisontell = Horizontal
Horizontell = Horizontalement
Huvudlista = Liste principale
Hyravgift = Tarif de location
@ -763,6 +768,8 @@ Inconsistent qualification rule, X = Règle de qualification incohérente, X
Index = Index
Index in X[index] = Index en X[index]
Individual Example = Exemple de course individuelle
Individual Results Split Print = Avec résultats individuels
Individual Rogaining Split Print = Avec résultats de la course au score
Individual result by finish time = Résultat individuel sur l'heure d'arrivée
Individual results in a club = Résultats individuel au sein du club
Individuell = par catégorie
@ -1009,6 +1016,7 @@ Liveresultat, radiotider = Résultats en direct avec radios
Ljud = Son
Ljudfiler, baskatalog = Répertoire de base des fichiers sonores
Ljudval = Sélection des sons
Lokal kopia från X = Copie locale de X
Lokalt = Localement
Long = Longue
Longitud = Longitude
@ -1402,6 +1410,8 @@ Relation till föregående = Lien avec le précédent
Relativ skalfaktor för typsnittets storlek i procent = Facteur d'échelle pour la police (pourcentage)
Relay Example = Exemple de relais
Relay Example (Lokal kopia från: localhost) = Exemple de relais (Copie locale depuis : localhost)
Relay Results Split Print = Avec résultats du relais
Relay/Team Results Split Print = Avec résultats du relais/de l'équipe
Relays = Relais
Rep = Arrêt attardés
Reparera vald tävling = Réparer la compétition sélectionnée
@ -1526,6 +1536,7 @@ RunnerId = Licence/ID
RunnerLeg = Concurrent (variation spécifique)
RunnerLegNumber = Numéro de relais groupé du coureur
RunnerLegNumberAlpha = Numéro de relais exact du coureur
RunnerLegTeamLeaderName = Premier membre de l'équipe à terminer son circuit
RunnerName = Nom du coureur
RunnerNationality = Nationalité du coureur
RunnerPaid = Payé
@ -1888,6 +1899,7 @@ Tabelläge = Mode table
Tar bort X = Retrait de X
Team = Équipe
Team Rogaining = Course au score en équipe
Team Rogaining Split Print = Avec résultats de la course au score en équipe
TeamBib = Dossard de l'équipe
TeamClub = Club de l'équipe
TeamCourseName = Nom du circuit pour l'équipe/la branche
@ -2217,6 +2229,7 @@ Välj alla klasser = Sélectionner toutes les catégories
Välj allt = Sélectionner tout
Välj automatiskt = Sélection automatique
Välj bild = Choisir une image
Välj bland befintliga bilder = Sélectionner une image existante
Välj deltagare för förhandsgranskning = Sélectionner un coureur pour prévisualisation
Välj den etapp som föregår denna tävling = Sélectionner l'étape précédente
Välj den etapp som kommer efter denna tävling = Sélectionner l'étape suivante
@ -2428,11 +2441,12 @@ info:mapcontrol = MeOS ne sait pas déterminer la fonction d'un boîtier s'il n'
info:multieventnetwork = Pour prendre en charge plus d'une étape vous devez travailler localement. Faite une copie de sauvegarde de la compétition, ouvrez-la en local et transférez les résultats à l'étape suivante. Enfin, remontez l'étape suivante sur le serveur.
info:multiple_start = Un coureur peut faire plusieurs fois le même circuit. Une nouvelle inscription est créée à chaque lecture de la puce
info:nosplitprint = Chargement de la liste personnalisée impossible\n\nCelle par défaut la remplace
info:offsetclassid = Si vous importez des inscrits et des catégories depuis des sources différentes dans la même compétition, il peut y avoir des conflits entre les numéros d'identifiant des catégories. Pour différencier les catégories, vous pouvez entrer un décalage de la valeur de l'identifiant des catégorires pour chacune des sources. Celui-ci sera ajouté à l'identifiant existant.\n\nVous pouvez par exemple entrer 1000 (si aucune catégorie n'a déjà un identifiant supérieur à 1000).
info:pageswithcolumns = Montrer la liste page par page, avec un nombre spécifié de colonnes. Les infos sont mises à jour à chaque bouclage.
info:readout_action = X: Puce no. Y lue.\nDes actions manuelles sont requises.
info:readout_queue = X: Puce no. Y lue.\nLa puce a été mise en file d'attente.
info:readoutbase = Activez le boîtier SI en sélectionnant son port COM ou en recherchant les boîtiers SI installés. Appuyez sur Information pour obtenir le statut du port sélectionné.\n\nLecture Interactive vous permet de gérer directement les problèmes tels qu'un numéro de puce erroné. N'utilisez pas cette possibilité quand les coureurs ayant des problèmes sont pris en charge séparément.\n\nLa base de données des coureurs est utilisée si vous voulez ajouter automatiquement de nouveaux coureurs. Les poinçons sont utilisés pour trouver (détecter) la bonne catégorie.
info:readoutmore = Verrouillez la fonction pour éviter toute modification accidentelle.\n\nSélection des sons vous permet de lancer différents sons lors de la lecture des puces pour être plus facilement informé des problèmes.\n\n"Fenêtre de prévisualisation coureur" affiche une nouvelle fenêtre destinée au coureur qui lit sa puce, regroupant les informations principales de sa course.\n\nPlusieurs courses pour un coureur est utile si un coureur est autorisé a faire plusieurs fois son circuit. Une nouvelle inscriprion est créée à chaque lecture de la puce.
info:readoutmore = Verrouillez la fonction pour éviter toute modification accidentelle.\n\nSélection des sons vous permet de lancer différents sons lors de la lecture des puces pour être plus facilement informé des problèmes.\n\n"Fenêtre de prévisualisation coureur" affiche une nouvelle fenêtre destinée au coureur qui lit sa puce, regroupant les informations principales de sa course.\n\nPlusieurs courses pour un coureur est utile si un coureur est autorisé a faire plusieurs fois son circuit. Une nouvelle inscription est créée à chaque lecture de la puce.
info:readoutwindow = Cette fenêtre affiche les informations principales de chaque lecture de puce
info:runnerdbonline = Comme vous êtes connecté à un serveur, il n'est pas possible d'éditer les bases de données club et coureurs manuellement. Effectuez les changements avant d'uploader la compétition sur un serveur. Il est également possible de remplacer la base de données existante sur le serveur en important une nouvelle base (à partir de IOF XML).
info:teamcourseassignment = Le fichier des circuits IOF XML 3.0 contient les variations des équipes. Pour importer ces données, vous devez préparer la compétition en fonction des données du fichier :\n\n1.Assurez-vous que les catégories de relais ont le bon nombre de branches.\n2. Affectez les dossards à chaque catégorie. Passez par la Configuration Rapide dans l'onglet Catégories et renseignez le premier dossard de chaque catégorie (avec la répartition automatique des dossards). Vous pouvez aussi importer les équipes et leur attribuer des dossards comme d'habitude.\n\n\n3. Importez les circuits. Vous pouvez importer plusieurs fois ce fichier si nécessaire pour mettre à jour les variations.

View File

@ -888,6 +888,7 @@ TextInfo& gdioutput::addImage(const string& id, int yp, int xp, int format,
imageReferences.push_back(&TI);
TI.id = id;
TI.format = format | textImage;
TI.xp = xp;
TI.yp = yp;
@ -1256,7 +1257,7 @@ ButtonInfo &gdioutput::addButton(int x, int y, int w, const string &id,
updatePos(x, y, w+scaleLength(GDI_BUTTON_SPACING), height+5);
bi.xp=x;
bi.yp=y;
bi.yp = y - 1;
bi.width = w;
bi.text=ttext;
bi.id=id;
@ -1345,9 +1346,10 @@ ButtonInfo &gdioutput::addCheckbox(int x, int y, const string &id, const wstring
GetTextExtentPoint32(hDC, ttext.c_str(), ttext.length(), &size);
ReleaseDC(hWndTarget, hDC);
int cbY = y + (size.cy - h) / 2;
bi.hWnd = CreateWindowEx(0, L"BUTTON", L"", WS_TABSTOP | WS_VISIBLE |
WS_CHILD | WS_CLIPSIBLINGS | BS_AUTOCHECKBOX | BS_NOTIFY,
x-ox, y-oy + (size.cy-h)/2, h, h, hWndTarget, NULL,
x - ox, cbY - oy, h, h, hWndTarget, NULL,
(HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL);
TextInfo& desc = addStringUT(y, x + (3 * h) / 2, 0, ttext, 0, checkBoxCallback);
@ -1372,7 +1374,7 @@ ButtonInfo &gdioutput::addCheckbox(int x, int y, const string &id, const wstring
}
bi.isCheckbox = true;
bi.xp = x;
bi.yp=y;
bi.yp = cbY;
bi.width = desc.textRect.right - (x - ox);
bi.text = ttext;
bi.id = id;
@ -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<ButtonInfo>::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<InputInfo>::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
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<ButtonInfo>::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;
}
@ -3523,7 +3519,7 @@ bool gdioutput::insertText(const string &id, const wstring &text)
SetWindowText(it->hWnd, text.c_str());
it->text = text;
if (it->handler)
if (it->hasEventHandler())
it->handleEvent(*this, GUI_INPUT);
else if (it->callBack)
it->callBack(this, GUI_INPUT, &*it);
@ -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<ButtonInfo>::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...

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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'ä');
setChar(map, L'æ', L'ä');
setChar(map, L'Æ', L'a');
setChar(map, L'æ', L'a');
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;

View File

@ -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();

View File

@ -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() {

View File

@ -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<AutoCompleteRecord>& 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;

View File

@ -434,6 +434,10 @@ public:
static void fillSymbols(vector < pair<wstring, size_t>> &symb);
static const map<SortOrder, string>& getOrderToSymbol() {
return orderToSymbol;
}
friend class MetaListPost;
};

View File

@ -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;

View File

@ -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<pCourse> cSet;
@ -3460,23 +3469,35 @@ void oClass::calculateSplits() {
LegResult legBestTime;
vector<pRunner> 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<int, vector<pRunner>> rClsCrs;
if (cSet.size() > 1) {
for (pRunner it : rCls) {
pCourse tpc = it->getCourse(false);
if (tpc)
rClsCrs[tpc->getId()].push_back(it);
}
}
bool multiLeg = getNumStages() > 1; // Perhaps ignore parallell legs...
if (multiLeg) {
teamLegCourseControlToLeaderPlace.resize(getNumStages());
for (auto& lp : teamLegCourseControlToLeaderPlace)
lp.clear();
}
else {
for (auto &r : oe->Runners) {
if (!r.isRemoved() && r.Class == this)
rCls.push_back(&r);
teamLegCourseControlToLeaderPlace.clear();
}
}*/
for (set<pCourse>::iterator cit = cSet.begin(); cit!= cSet.end(); ++cit) {
pCourse pc = *cit;
for (pCourse pc : cSet) {
// Store all split times in a matrix
const unsigned nc = pc->getNumControls();
if (nc == 0)
@ -3484,9 +3505,14 @@ void oClass::calculateSplits() {
vector<vector<int>> splits(nc+1);
vector<vector<int>> splitsAcc(nc+1);
vector<bool> acceptMissingPunch(nc+1, true);
vector<int8_t> acceptMissingPunch(nc+1, true);
vector<pRunner>* 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<SplitData> &sp = it->getSplitTimes(true);
const int s = min<int>(nc, sp.size());
int off = 0;
unordered_map<int, PlaceTime>* 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<int> &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<pCourse>::iterator cit = cSet.begin(); cit != cSet.end(); ++cit) {
pCourse pc = *cit;
for (pCourse pc : cSet) {
const unsigned nc = pc->getNumControls();
vector<int> normRes(nc+1);
vector<int> 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 {

View File

@ -25,6 +25,7 @@
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include "inthashmap.h"
class oClass;
typedef oClass* pClass;
@ -260,9 +261,20 @@ protected:
inthashmap *tLegTimeToPlace;
inthashmap *tLegAccTimeToPlace;
struct PlaceTime {
int leader = -1;
map<int, int> timeToPlace;
};
vector<unordered_map<int, PlaceTime>> 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<int, map<string, int>> 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:

View File

@ -653,7 +653,8 @@ const vector< pair<wstring, size_t> > &oEvent::getCourses(vector<pair<wstring, s
vector<wchar_t> 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++) {

View File

@ -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<int, vector<pTeam> > cls2TeamList;
for (oTeamList::iterator tit = Teams.begin(); tit != Teams.end(); ++tit) {
@ -5015,6 +5026,11 @@ void oEvent::addAutoBib() {
else {
bool lockedForking = cls->lockedForking();
for (size_t k = 0; k < tl.size(); k++) {
if (noBibToVacant && tl[k]->isVacant()) {
tl[k]->getDI().setString("Bib", L""); //Remove only bib
}
else {
wchar_t buff[32];
swprintf_s(buff, pattern, number);
@ -5026,6 +5042,7 @@ void oEvent::addAutoBib() {
tl[k]->setBib(buff, number, true);
}
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,18 +5284,72 @@ bool compareClubClassTeamName(const oRunner &a, const oRunner &b)
return a.getClub()<b.getClub();
}
void oEvent::assignCardInteractive(gdioutput &gdi, GUICALLBACK cb)
void oEvent::assignCardInteractive(gdioutput& gdi, GUICALLBACK cb, SortOrder& orderRunners)
{
gdi.fillDown();
gdi.dropLine(1);
gdi.addString("", 2, "Tilldelning av hyrbrickor");
class SortUpdate : public GuiHandler {
SortOrder& orderRunners;
oEvent* oe;
GUICALLBACK cb;
public:
SortUpdate(oEvent *oe, GUICALLBACK cb, SortOrder& orderRunners) :
orderRunners(orderRunners), cb(cb), oe(oe) { }
void handle(gdioutput& gdi, BaseInfo& info, GuiEventType type) final {
ListBoxInfo& lb = dynamic_cast<ListBoxInfo&>(info);
orderRunners = SortOrder(lb.data);
oe->assignCardInteractive(gdi, cb, orderRunners);
}
~SortUpdate() {
}
};
if (gdi.hasData("AssignCardMark")) {
gdi.restore("AssignCardRP", false);
}
else {
auto h = make_shared<SortUpdate>(this, cb, orderRunners);
gdi.dropLine(0.5);
gdi.addSelection("Sorting", 200, 300, nullptr, L"Sortering:").setHandler(h);
vector<pair<wstring, size_t> > 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;
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())
@ -5287,28 +5358,43 @@ void oEvent::assignCardInteractive(gdioutput &gdi, GUICALLBACK cb)
if (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL || it->getStatus() == StatusNotCompetiting)
continue;
if (it->Club!=lastClub) {
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)
if (!groupByClass && it->Class)
r += it->getClass(false) + L", ";
if (it->tInTeam) {
r+=itow(it->tInTeam->getStartNo()) + L" " + it->tInTeam->getName() + L", ";
}
if (!groupByClub && it->Club)
r += it->getClub() + L", ";
if (it->tInTeam) {
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,
gdi.addInput(max(gdi.getCX(), px450), gdi.getCY() - px4,
id, L"", 10, cb).setExtra(it->getId());
gdi.popX();
@ -5318,6 +5404,8 @@ void oEvent::assignCardInteractive(gdioutput &gdi, GUICALLBACK cb)
if (k == 0)
gdi.addString("", 0, "Ingen löpare saknar bricka");
gdi.refresh();
}
void oEvent::calcUseStartSeconds()

View File

@ -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.

View File

@ -510,10 +510,13 @@ private:
else {
vector<pRunner> 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) {
// Auto initialize
@ -996,6 +999,7 @@ void oEvent::loadDrawSettings(const set<int> &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<ClassDrawSpecification> &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<ClassDrawSpecification> &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();

View File

@ -1051,8 +1051,9 @@ void oEvent::computePreliminarySplitResults(const set<int> &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<int> &classes) const {
const set<int> &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<int> &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<int> &classes) const {
}
vector<tuple<int, int, int>> timeRunnerIx;
vector<tuple<int, int, int>> 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<int> &legCCId = classLeg2ExistingCCId[rList.first];
legCCId.insert(oPunch::PunchFinish);
@ -1113,18 +1119,17 @@ void oEvent::computePreliminarySplitResults(const set<int> &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<int> &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,12 +1158,16 @@ void oEvent::computePreliminarySplitResults(const set<int> &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;
}
}
}
}
auto computeResult = [&rr, &negLeg](vector<tuple<int, int, int>>& timeRunnerIx, bool total) {
sort(timeRunnerIx.begin(), timeRunnerIx.end());
int place = 0;
@ -1174,6 +1184,11 @@ void oEvent::computePreliminarySplitResults(const set<int> &classes) const {
}
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;
@ -1184,5 +1199,10 @@ void oEvent::computePreliminarySplitResults(const set<int> &classes) const {
}
}
}
};
computeResult(timeRunnerIx, false);
computeResult(totalTimeRunnerIx, true);
}
}
}

View File

@ -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<pControl> 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<pClass> 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();
@ -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;
@ -3360,7 +3412,7 @@ 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];
pRunner r = it->getRunner(linearLegSpec);
if (!r)
return true;

View File

@ -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,

View File

@ -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<SplitData> &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<int> &inputFilter,
pRunner oEvent::findRunner(const wstring &s, int lastId,
const unordered_set<int> &inputFilter,
unordered_set<int> &matchFilter) const
{
matchFilter.clear();
@ -4418,7 +4412,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_set<int
int sn = _wtoi(trm.c_str());
wchar_t s_lc[1024];
wcscpy_s(s_lc, s.c_str());
CharLowerBuff(s_lc, len);
prepareMatchString(s_lc, len);
int score;
pRunner res = 0;
@ -4929,7 +4923,7 @@ void oRunner::printSplits(gdioutput& gdi, const oListInfo* li) const {
if (pc) {
for (int n = 0; n < pc->nControls; 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<int> &times) const
}
}
void oRunner::getLegTimeAfterAcc(vector<int> &times) const
void oRunner::getLegTimeAfterAcc(vector<ResultData> &times) const
{
times.clear();
if (splitTimes.empty() || !Class || tStartTime<=0)
@ -5835,6 +5829,9 @@ void oRunner::getLegTimeAfterAcc(vector<int> &times) 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<int> &times) 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<int> &reorder = getCourse(true)->getMapToOriginalOrder();
if (!reorder.empty()) {
vector<int> orderedTimes(times.size());
vector<ResultData> 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<int> &times) const
}
}
void oRunner::getLegPlacesAcc(vector<int> &places) const
void oRunner::getLegPlacesAcc(vector<ResultData> &places) const
{
places.clear();
pCourse pc = getCourse(false);
@ -5880,6 +5886,10 @@ void oRunner::getLegPlacesAcc(vector<int> &places) const
const unsigned nc = pc->getNumControls();
const vector<SplitData> &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<int> &places) const
if (s>0) {
int time = s - tStartTime;
if (time>0)
places[k] = cls->getAccLegPlace(id, k, time);
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] = 0;
places[k].teamTotalData = cls->getAccLegControlPlace(tLeg, oPunch::PunchFinish, time + off);
}
else {
places[k].data = 0;
places[k].teamTotalData = 0;
}
}
}
// Normalized order
const vector<int> &reorder = getCourse(true)->getMapToOriginalOrder();
if (!reorder.empty()) {
vector<int> orderedPlaces(reorder.size());
vector<ResultData> 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;
}

View File

@ -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<b, 0 if b<a, otherwise 2.*/
static int compareClubs(const oClub* a, const oClub* b);
mutable int tTimeAdjustment;
mutable int tPointAdjustment;
mutable int tAdjustDataRevision;
@ -551,6 +573,23 @@ public:
};
class oRunner final: public oAbstractRunner {
public:
struct ResultData {
private:
int data = 0;
int teamTotalData = 0;
public:
ResultData(int data, int teamTotalData) : data(data), teamTotalData(teamTotalData) { }
ResultData() = default;
int get(bool total) const {
return total ? teamTotalData : data;
}
friend class oRunner;
};
protected:
pCourse Course;
@ -633,24 +672,32 @@ protected:
void clearOnChangedRunningTime();
// Cached runner statistics
mutable vector<int> tMissedTime;
mutable vector<int> tPlaceLeg;
mutable vector<int> tAfterLeg;
mutable vector<int> tPlaceLegAcc;
mutable vector<int> tAfterLegAcc;
mutable vector<ResultData> tPlaceLegAcc;
mutable vector<ResultData> 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<int, int> 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<int> &places) const;
void getLegTimeAfter(vector<int> &deltaTimes) const;
void getLegPlacesAcc(vector<int> &places) const;
void getLegTimeAfterAcc(vector<int> &deltaTimes) const;
void getLegPlacesAcc(vector<ResultData> &places) const;
void getLegTimeAfterAcc(vector<ResultData> &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<int, bool> inputData(int id, const wstring &input,

View File

@ -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<int>::max();
if (bix == 0)
bix = numeric_limits<int>::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<b.tmpSortTime;
int aix = a.getDCI().getInt("SortIndex");
int bix = b.getDCI().getInt("SortIndex");
if (aix != bix) {
if (aix == 0)
aix = numeric_limits<int>::max();
if (bix == 0)
bix = numeric_limits<int>::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<Runners.size() ? Runners[leg] : 0;
return leg<Runners.size() ? Runners[leg] : nullptr;
}
int oTeam::getRogainingPoints(bool computed, bool multidayTotal) const {

View File

@ -269,6 +269,9 @@ public:
int getLegRunningTime(int leg, bool computed, bool multidayTotal) const;
// Get the team's total running time when starting specified leg
int getTotalRunningTimeAtLegStart(int leg, bool multidayTotal) const;
RunnerStatus getLegStatus(int leg, bool computed, bool multidayTotal) const;
const wstring &getLegStatusS(int leg, bool computed, bool multidayTotal) const;

View File

@ -240,7 +240,7 @@ pTeam oEvent::findTeam(const wstring &s, int lastId, unordered_set<int> &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;

View File

@ -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);
}

View File

@ -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
//

View File

@ -1150,7 +1150,7 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimap<string, s
auto &sd = r->getSplitTimes(false);
vector<int> after;
r->getLegTimeAfter(after);
vector<int> afterAcc;
vector<oRunner::ResultData> afterAcc;
r->getLegTimeAfterAcc(afterAcc);
vector<int> delta;
r->getSplitAnalysis(delta);
@ -1177,8 +1177,8 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimap<string, s
else
analysis[0].second = L"";
if (afterAcc[ix] > 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 multimap<string, s
int place = r->getLegPlace(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"");

View File

@ -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