From 55d526c3e4e7ed00135b3155424a28291237ffc2 Mon Sep 17 00:00:00 2001 From: Erik Melin <31467290+erikmelin@users.noreply.github.com> Date: Sat, 11 May 2019 21:11:36 +0200 Subject: [PATCH] MeOS version 3.6.1069 --- code/RestService.cpp | 29 +++- code/RestService.h | 2 +- code/TabAuto.cpp | 2 +- code/TabClass.cpp | 214 ++++++++++++++++++++++------ code/TabClass.h | 1 + code/TabCompetition.cpp | 69 +++++++-- code/TabCompetition.h | 4 + code/TabCourse.cpp | 4 +- code/TabList.cpp | 103 ++++++++++---- code/TabRunner.cpp | 31 ++-- code/TabSI.cpp | 193 +++++++++++++++++++++++-- code/TabSI.h | 14 +- code/TabSpeaker.cpp | 121 +++++++++------- code/TabTeam.cpp | 25 +++- code/Table.cpp | 44 +++--- code/Table.h | 5 +- code/csvparser.cpp | 2 +- code/danish.lng | 94 ++++++++++++ code/english.lng | 23 ++- code/gdioutput.cpp | 96 ++++++++++++- code/gdioutput.h | 5 + code/gdistructures.h | 6 +- code/html1.htm | 190 +++++++++++++++++++++++++ code/iof30interface.cpp | 268 +++++++++++++++++++++++++++++++---- code/iof30interface.h | 9 ++ code/meos.cpp | 7 + code/meosversion.cpp | 8 +- code/metalist.cpp | 4 + code/methodeditor.cpp | 8 +- code/oBase.cpp | 13 +- code/oBase.h | 25 +++- code/oCard.cpp | 8 +- code/oClass.cpp | 105 +++++++------- code/oClass.h | 24 ++-- code/oClub.cpp | 7 +- code/oCourse.cpp | 1 + code/oCourse.h | 4 +- code/oEvent.cpp | 260 +++++++++++++++++++++++++-------- code/oEvent.h | 33 +++-- code/oEventDraw.cpp | 19 ++- code/oEventResult.cpp | 113 ++++++++------- code/oEventSpeaker.cpp | 6 +- code/oFreePunch.cpp | 112 ++++++++++++++- code/oImportExport.cpp | 37 ++++- code/oListInfo.cpp | 78 +++++++--- code/oListInfo.h | 6 +- code/oPunch.h | 3 +- code/oRunner.cpp | 134 ++++++++++++++---- code/oRunner.h | 39 ++++- code/oTeam.cpp | 2 +- code/oTeam.h | 6 +- code/oTeamEvent.cpp | 16 +-- code/onlineinput.cpp | 4 + code/onlineresults.cpp | 8 +- code/qualification_final.cpp | 2 +- code/restserver.cpp | 138 ++++++++++++++++-- code/restserver.h | 6 + code/socket.cpp | 7 +- code/swedish.lng | 29 +++- 59 files changed, 2282 insertions(+), 544 deletions(-) diff --git a/code/RestService.cpp b/code/RestService.cpp index de99274..c776932 100644 --- a/code/RestService.cpp +++ b/code/RestService.cpp @@ -46,6 +46,8 @@ void RestService::save(oEvent &oe, gdioutput &gdi) { int xport = gdi.getTextNo("Port"); if (xport > 0 && xport < 65536) { + oe.setProperty("ServicePort", xport); + port = xport; server->startService(port); } @@ -53,6 +55,12 @@ void RestService::save(oEvent &oe, gdioutput &gdi) { throw meosException("Invalid port number"); } + if (gdi.isChecked("MapRoot")) { + rootMap = gdi.recodeToNarrow(gdi.getText("RootMap")); + server->setRootMap(rootMap); + oe.setProperty("ServiceRootMap", gdi.getText("RootMap")); + } + if (gdi.isChecked("AllowEntry")) { RestServer::EntryPermissionType pt = (RestServer::EntryPermissionType)gdi.getSelectedItem("PermissionPerson").first; RestServer::EntryPermissionClass pc = (RestServer::EntryPermissionClass)gdi.getSelectedItem("PermissionClass").first; @@ -66,10 +74,11 @@ void RestService::save(oEvent &oe, gdioutput &gdi) { void ListIpAddresses(vector& ip); -void RestService::settings(gdioutput &gdi, oEvent &oe, bool created) { - if (port == -1) - port = oe.getPropertyInt("ServicePort", 2009); - +void RestService::settings(gdioutput &gdi, oEvent &oe, bool created) { + if (port == -1) { + port = oe.getPropertyInt("ServicePort", 2009); + rootMap = oe.getPropertyString("ServiceRootMap", ""); + } settingsTitle(gdi, "MeOS Informationsserver REST-API"); //gdi.fillRight(); @@ -87,10 +96,15 @@ void RestService::settings(gdioutput &gdi, oEvent &oe, bool created) { bool disablePermisson = true; gdi.popX(); - startCancelInterval(gdi, "Save", created, IntervalNone, L""); + gdi.addCheckbox("MapRoot", "Mappa rootadresssen (http:///localhost:port/) till funktion:", nullptr, !rootMap.empty()).setHandler(this); + gdi.addInput("RootMap", gdi.recodeToWide(rootMap)); + gdi.setInputStatus("RootMap", !rootMap.empty()); - if (!server) + startCancelInterval(gdi, "Save", created, IntervalNone, L""); + + if (!server) { gdi.addInput("Port", itow(port), 10, 0, L"Port:", L"#http://localhost:[PORT]/meos"); + } else { gdi.addString("", 0, "Server startad på X#" + itos(port)); auto per = server->getEntryPermission(); @@ -180,6 +194,9 @@ void RestService::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { gdi.setInputStatus("PermissionPerson", gdi.isChecked(bi.id)); gdi.setInputStatus("PermissionClass", gdi.isChecked(bi.id)); } + else if (bi.id == "MapRoot") { + gdi.setInputStatus("RootMap", gdi.isChecked(bi.id)); + } } else if (type == GUI_LINK) { wstring url = ((TextInfo &)info).text; diff --git a/code/RestService.h b/code/RestService.h index 923a379..af987f2 100644 --- a/code/RestService.h +++ b/code/RestService.h @@ -31,7 +31,7 @@ class RestService : { int port; shared_ptr server; - + string rootMap; public: void save(oEvent &oe, gdioutput &gdi) override; diff --git a/code/TabAuto.cpp b/code/TabAuto.cpp index 7021944..508f56f 100644 --- a/code/TabAuto.cpp +++ b/code/TabAuto.cpp @@ -1040,7 +1040,7 @@ void PunchMachine::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) auto pc = r->getCourse(false); if (radio < 10 || pc->hasControlCode(radio)) { pp.clear(); - oe->getPunchesForRunner(r->getId(), pp); + oe->getPunchesForRunner(r->getId(), false, pp); bool hit = false; for (auto p : pp) { if (p->getTypeCode() == radio) diff --git a/code/TabClass.cpp b/code/TabClass.cpp index c3cb177..598949a 100644 --- a/code/TabClass.cpp +++ b/code/TabClass.cpp @@ -467,9 +467,11 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) oe->setupRelay(*pc, newType, nstages, st); if (gdi.hasField("MAdd")) { - gdi.enableInput("MAdd"); - gdi.enableInput("MCourses"); - gdi.enableInput("MRemove"); + for (const char *s : {"MCourses", "StageCourses", "MAdd", "MRemove", "MUp", "MDown"}) { + if (gdi.hasField(s)) { + gdi.enableInput(s); + } + } } pc->forceShowMultiDialog(true); selectClass(gdi, pc->getId()); @@ -506,11 +508,21 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) if (gdi.getSelectedItem("MCourses", lbi)) { int courseid=lbi.data; - pc->addStageCourse(currentStage, courseid); + int ix = -1; + ListBoxInfo selS; + if (gdi.getSelectedItem("StageCourses", selS)) { + ix = selS.index+1; + } + + pc->addStageCourse(currentStage, courseid, ix); pc->fillStageCourses(gdi, currentStage, "StageCourses"); + if (ix != -1) + gdi.selectItemByIndex("StageCourses", ix); pc->synchronize(); oe->checkOrderIdMultipleCourses(cid); + + setLockForkingState(gdi, *pc); } } EditChanged=true; @@ -530,18 +542,67 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) ListBoxInfo lbi; if (gdi.getSelectedItem("StageCourses", lbi)) { int courseid=lbi.data; - pc->removeStageCourse(currentStage, courseid, lbi.index); + int ix = lbi.index; + pc->removeStageCourse(currentStage, courseid, ix); pc->synchronize(); pc->fillStageCourses(gdi, currentStage, "StageCourses"); + if (ix > 0) + gdi.selectItemByIndex("StageCourses", ix-1); + + + setLockForkingState(gdi, *pc); } } + + } + else if (bi.id == "MUp" || bi.id == "MDown") { + DWORD cid = ClassId; + if (!checkClassSelected(gdi)) + return false; + pClass pc = oe->getClass(cid); + + if (!pc) + return false; + + if (currentStage >= 0) { + int ix = -1; + ListBoxInfo selS; + if (gdi.getSelectedItem("StageCourses", selS)) { + ix = selS.index; + } + + if (ix != -1) { + int off = bi.id == "MUp" ? -1 : 1; + pc->moveStageCourse(currentStage, ix, off); + pc->synchronize(); + pc->fillStageCourses(gdi, currentStage, "StageCourses"); + gdi.selectItemByIndex("StageCourses", ix + off); + } + } + setLockForkingState(gdi, *pc); + EditChanged = true; } EditChanged=true; } + else if (type == GUI_LISTBOXSELECT) { + const ListBoxInfo &bi = *(ListBoxInfo *)data; + if (bi.id == "MCourses") { + gdi.sendCtrlMessage("MAdd"); + } + else if (bi.id == "StageCourses") { + //gdi.sendCtrlMessage("MRemove"); + } + } else if (type==GUI_LISTBOX) { ListBoxInfo bi=*(ListBoxInfo *)data; - if (bi.id.substr(0, 7)=="LegType") { + if (bi.id == "StageCourses") { + pClass pc = oe->getClass(ClassId); + if (!pc) + return false; + setLockForkingState(gdi, *pc); + } + else if (bi.id.substr(0, 7)=="LegType") { LegTypes lt = LegTypes(bi.data); int i=atoi(bi.id.substr(7).c_str()); @@ -956,7 +1017,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) vector cls; oe->getClasses(cls, true); for (size_t k = 0; k < cls.size(); k++) { - if (cls[k]->getStart() == start) + if (cls[k]->getStart() == start && !cls[k]->hasFreeStart()) lst.insert(cls[k]->getId()); } gdi.setSelection("Classes", lst); @@ -1805,6 +1866,18 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.dropLine(); int tot, fin, dns; oe->getNumClassRunners(pc->getId(), 0, tot, fin, dns); + if (pc->isQualificationFinalBaseClass()) { + set base; + pc->getQualificationFinal()->getBaseClassInstances(base); + for (int i : base) { + if (pc->getVirtualClass(i)) { + int tot2 = 0; + oe->getNumClassRunners(pc->getVirtualClass(i)->getId(), 0, tot2, fin, dns); + tot += tot2; + } + } + } + gdi.addString("", fontMediumPlus, "Antal deltagare: X#" + itos(tot)); gdi.dropLine(1.2); gdi.pushX(); @@ -1826,9 +1899,6 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) for (int i : base) { if (pc->getVirtualClass(i)) { gdi.addStringUT(0, pc->getVirtualClass(i)->getName()); - int tot2 = 0; - oe->getNumClassRunners(pc->getVirtualClass(i)->getId(), 0, tot2, fin, dns); - tot += tot2; } } numSplitDef = base.size(); @@ -1844,7 +1914,6 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.selectFirstItem("SplitInput"); gdi.dropLine(3); gdi.popX(); - } updateSplitDistribution(gdi, numSplitDef, tot); @@ -2260,58 +2329,77 @@ void TabClass::showClassSettings(gdioutput &gdi) int y = 0; int xp = gdi.getCX(); - const int width = 80; + const int width = gdi.scaleLength(80); + int classW = gdi.scaleLength(300); + vector str(cInfo.size()); + for (size_t k = 0; k < cInfo.size(); k++) { + auto &ci = cInfo[k]; + wchar_t bf1[128]; + wchar_t bf2[128]; + int cstart = ci.firstStart + (ci.nRunners - 1) * ci.interval; + wstring first = oe->getAbsTime(ci.firstStart*drawInfo.baseInterval + drawInfo.firstStart); + wstring last = oe->getAbsTime((cstart)*drawInfo.baseInterval + drawInfo.firstStart); + pClass pc = oe->getClass(ci.classId); + + swprintf_s(bf1, L"%s, %d", pc ? pc->getName().c_str() : L"-", ci.nRunners); + swprintf_s(bf2, L"%d-[%d]-%d (%s-%s)", ci.firstStart, ci.interval, cstart, first.c_str(), last.c_str()); + str[k] = L"X platser. Startar Y#" + wstring(bf1) + L"#" + bf2; + + TextInfo ti; + ti.xp = 0; + ti.yp = 0; + ti.format = 0; + ti.text = str[k]; + gdi.calcStringSize(ti); + classW = max(classW, ti.realWidth + gdi.scaleLength(10)); + } if (!cInfo.empty()) { gdi.dropLine(); y = gdi.getCY(); gdi.addString("", y, xp, 1, "Sammanställning, klasser:"); - gdi.addString("", y, xp+300, 0, "Första start:"); - gdi.addString("", y, xp+300+width, 0, "Intervall:"); - gdi.addString("", y, xp+300+width*2, 0, "Vakanser:"); - gdi.addString("", y, xp+300+width*3, 0, "Reserverade:"); + gdi.addString("", y, xp + classW, 0, "Första start:"); + gdi.addString("", y, xp + classW + width, 0, "Intervall:"); + gdi.addString("", y, xp + classW + width * 2, 0, "Vakanser:"); + gdi.addString("", y, xp + classW + width * 3, 0, "Reserverade:"); } gdi.pushX(); - for (size_t k=0;kgetAbsTime(ci.firstStart*drawInfo.baseInterval+drawInfo.firstStart); - wstring last=oe->getAbsTime((cstart)*drawInfo.baseInterval+drawInfo.firstStart); - pClass pc=oe->getClass(ci.classId); - swprintf_s(bf1, L"%s, %d", pc ? pc->getName().c_str() : L"-", ci.nRunners); - swprintf_s(bf2, L"%d-[%d]-%d (%s-%s)", ci.firstStart, ci.interval, cstart, first.c_str(), last.c_str()); + int cstart = ci.firstStart + (ci.nRunners - 1) * ci.interval; + wstring first = oe->getAbsTime(ci.firstStart*drawInfo.baseInterval + drawInfo.firstStart); + wstring last = oe->getAbsTime((cstart)*drawInfo.baseInterval + drawInfo.firstStart); + pClass pc = oe->getClass(ci.classId); gdi.fillRight(); - + int id = ci.classId; GDICOLOR clr = ci.hasFixedTime || ci.nExtraSpecified || ci.nVacantSpecified ? colorDarkGreen : colorBlack; - gdi.addString("C" + itos(id), 0, L"X platser. Startar Y#" + wstring(bf1) + L"#" + bf2).setColor(clr).setExtra(clr); - + gdi.addString("C" + itos(id), 0, str[k]).setColor(clr).setExtra(clr); + y = gdi.getCY(); InputInfo *ii; GDICOLOR fixedColor = colorLightGreen; - ii = &gdi.addInput(xp+300, y, "S"+itos(id), first, 7, DrawClassesCB); + ii = &gdi.addInput(xp + classW, y, "S" + itos(id), first, 7, DrawClassesCB); if (ci.hasFixedTime) { ii->setBgColor(fixedColor).setExtra(fixedColor); } - ii = &gdi.addInput(xp+300+width, y, "I"+itos(id), formatTime(ci.interval*drawInfo.baseInterval), 7, DrawClassesCB); + ii = &gdi.addInput(xp + classW + width, y, "I" + itos(id), formatTime(ci.interval*drawInfo.baseInterval), 7, DrawClassesCB); if (ci.hasFixedTime) { ii->setBgColor(fixedColor).setExtra(fixedColor); } - ii = &gdi.addInput(xp+300+width*2, y, "V"+itos(id), itow(ci.nVacant), 7, DrawClassesCB); + ii = &gdi.addInput(xp + classW + width * 2, y, "V" + itos(id), itow(ci.nVacant), 7, DrawClassesCB); if (ci.nVacantSpecified) { ii->setBgColor(fixedColor).setExtra(fixedColor); } - ii = &gdi.addInput(xp+300+width*3, y, "R"+itos(id), itow(ci.nExtra), 7, DrawClassesCB); + ii = &gdi.addInput(xp + classW + width * 3, y, "R" + itos(id), itow(ci.nExtra), 7, DrawClassesCB); if (ci.nExtraSpecified) { ii->setBgColor(fixedColor).setExtra(fixedColor); } - if (k%5 == 4) + if (k % 5 == 4) gdi.dropLine(1); gdi.dropLine(1.6); @@ -2414,7 +2502,8 @@ void TabClass::selectClass(gdioutput &gdi, int cid) gdi.check("LockStartList", false); gdi.setInputStatus("LockStartList", false); } - gdi.check("NoTiming", false); + if (gdi.hasField("NoTiming")) + gdi.check("NoTiming", false); ClassId=cid; EditChanged=false; @@ -2455,7 +2544,8 @@ void TabClass::selectClass(gdioutput &gdi, int cid) } gdi.check("AllowQuickEntry", pc->getAllowQuickEntry()); - gdi.check("NoTiming", pc->getNoTiming()); + if (gdi.hasField("NoTiming")) + gdi.check("NoTiming", pc->getNoTiming()); if (gdi.hasField("FreeStart")) gdi.check("FreeStart", pc->hasFreeStart()); @@ -2890,7 +2980,8 @@ void TabClass::save(gdioutput &gdi, bool skipReload) pc->lockedForking(gdi.isChecked("LockForking")); pc->setAllowQuickEntry(gdi.isChecked("AllowQuickEntry")); - pc->setNoTiming(gdi.isChecked("NoTiming")); + if (gdi.hasField("NoTiming")) + pc->setNoTiming(gdi.isChecked("NoTiming")); if (gdi.hasField("FreeStart")) pc->setFreeStart(gdi.isChecked("FreeStart")); @@ -2901,7 +2992,8 @@ void TabClass::save(gdioutput &gdi, bool skipReload) if (gdi.hasField("DirectResult")) { bool withDirect = gdi.isChecked("DirectResult"); - if (withDirect && !pc->hasDirectResult() && !hasWarnedDirect) { + if (withDirect && !pc->hasDirectResult() && !hasWarnedDirect && + !oe->getMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { if (gdi.ask(L"warning:direct_result")) hasWarnedDirect = true; else @@ -3135,20 +3227,24 @@ bool TabClass::loadPage(gdioutput &gdi) gdi.popX(); gdi.dropLine(3.5); gdi.addCheckbox("AllowQuickEntry", "Tillåt direktanmälan", 0); - gdi.addCheckbox("NoTiming", "Utan tidtagning", 0); + if (showAdvanced || !oe->getMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { + gdi.addCheckbox("NoTiming", "Utan tidtagning", 0); + } if (showAdvanced) { gdi.dropLine(2); gdi.popX(); gdi.addCheckbox("FreeStart", "Fri starttid", 0, false, "Klassen lottas inte, startstämpling"); gdi.addCheckbox("IgnoreStart", "Ignorera startstämpling", 0, false, "Uppdatera inte starttiden vid startstämpling"); - gdi.dropLine(2); gdi.popX(); - + } + + if (showAdvanced || oe->getMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { gdi.addCheckbox("DirectResult", "Resultat vid målstämpling", 0, false, "help:DirectResult"); } + gdi.dropLine(2); gdi.popX(); @@ -3794,7 +3890,7 @@ void TabClass::simultaneous(int classId, const wstring &time) { pCourse crs = pc->getCourse(); pc->setNumStages(1); if (crs) - pc->addStageCourse(0, crs->getId()); + pc->addStageCourse(0, crs->getId(), -1); } pc->setStartType(0, STTime, false); @@ -3845,18 +3941,23 @@ void TabClass::selectCourses(gdioutput &gdi, int legNo) { } gdi.fillRight(); int x1=gdi.getCX(); - gdi.addListBox("StageCourses", 180, 200, MultiCB, getCourseLabel(pc->hasCoursePool())).ignore(true); + gdi.addListBox("StageCourses", 240, 200, MultiCB, getCourseLabel(pc->hasCoursePool())).ignore(true); pc->fillStageCourses(gdi, currentStage, "StageCourses"); int x2=gdi.getCX(); gdi.fillDown(); - gdi.addListBox("MCourses", 180, 200, MultiCB, L"Banor:").ignore(true); + gdi.addListBox("MCourses", 240, 200, MultiCB, L"Banor:").ignore(true); oe->fillCourses(gdi, "MCourses", true); gdi.setCX(x1); gdi.fillRight(); gdi.addButton("MRemove", "Ta bort markerad >>", MultiCB); - gdi.setCX(x2); + gdi.addButton(gdi.getCX(), gdi.getCY(), gdi.scaleLength(30), "MUp", L"#▲", MultiCB, L"Flytta upp", false, false); + gdi.addButton(gdi.getCX(), gdi.getCY(), gdi.scaleLength(30), "MDown", L"#▼", MultiCB, L"Flytta ner", false, false); + gdi.disableInput("MUp"); + gdi.disableInput("MDown"); + + gdi.setCX(max(x2, gdi.getCY())); gdi.fillDown(); gdi.addButton("MAdd", "<< Lägg till", MultiCB); @@ -4331,6 +4432,10 @@ void TabClass::writeDrawInfo(gdioutput &gdi, const DrawInfo &drawInfoIn) { gdi.setText("FirstStart", oe->getAbsTime(drawInfoIn.firstStart)); } +void TabClass::setLockForkingState(gdioutput &gdi, const oClass &c) { + setLockForkingState(gdi, c.hasCoursePool(), c.lockedForking()); +} + void TabClass::setLockForkingState(gdioutput &gdi, bool poolState, bool lockState) { if (gdi.hasField("DefineForking")) gdi.setInputStatus("DefineForking", !lockState && !poolState); @@ -4348,6 +4453,27 @@ void TabClass::setLockForkingState(gdioutput &gdi, bool poolState, bool lockStat gdi.setInputStatus(s, !lockState || poolState); } } + + bool moveUp = false; + bool moveDown = false; + + if (gdi.hasField("MCourses")) { + ListBoxInfo lbi; + if (gdi.getSelectedItem("StageCourses", lbi)) { + if (lbi.index > 0) + moveUp = true; + int numItem = gdi.getNumItems("StageCourses"); + if (lbi.index < numItem - 1) + moveDown = true; + } + } + + if (gdi.hasField("MUp")) { + gdi.setInputStatus("MUp", (!lockState || poolState) && moveUp); + } + if (gdi.hasField("MDown")) { + gdi.setInputStatus("MDown", (!lockState || poolState) && moveDown); + } } bool TabClass::warnDrawStartTime(gdioutput &gdi, const wstring &firstStart) { diff --git a/code/TabClass.h b/code/TabClass.h index e43c67d..02b9dd3 100644 --- a/code/TabClass.h +++ b/code/TabClass.h @@ -150,6 +150,7 @@ class TabClass : static vector< pair > getPairOptions(); void setLockForkingState(gdioutput &gdi, bool poolState, bool lockState); + void setLockForkingState(gdioutput &gdi, const oClass &c); void loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstring& firstStart, int maxNumControl, const wstring& minInterval, const wstring& vacances, const set &clsId); diff --git a/code/TabCompetition.cpp b/code/TabCompetition.cpp index f8a636f..d452cd2 100644 --- a/code/TabCompetition.cpp +++ b/code/TabCompetition.cpp @@ -383,6 +383,9 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) wstring url = ti.text; ShellExecute(NULL, L"open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); } + else if (ti.id == "fnpath") { + ShellExecute(NULL, L"open", ti.text.c_str(), NULL, NULL, SW_SHOWNORMAL); + } } else if (type==GUI_BUTTON) { ButtonInfo bi=*(ButtonInfo *)data; @@ -558,17 +561,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.refresh(); } else if (bi.id=="Test") { - vector< pair > cpp; - cpp.push_back(make_pair(L"Source", L"*.cpp")); - int ix = 0; - wstring fn = gdi.browseForSave(cpp, L".cpp", ix); - if (!fn.empty()) - gdi.getRecorder().saveRecordings(gdi.narrow(fn)); - else { - TestMeOS tm(oe, "base"); - tm.runAll(); - tm.publish(gdi); - } + checkRentCards(gdi); } else if (bi.id=="Report") { gdi.clearPage(true); @@ -2076,7 +2069,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) // Construct runner from database oRunner sRunner(oe, 0); - sRunner.init(*dbr); + sRunner.init(*dbr, false); pRunner added = oe->addRunnerFromDB(&sRunner, classId, true); if (added) added->synchronize(); @@ -2375,8 +2368,25 @@ void TabCompetition::copyrightLine(gdioutput &gdi) const void TabCompetition::loadAboutPage(gdioutput &gdi) const { gdi.clearPage(false); - gdi.addString("", 2, makeDash(L"Om MeOS - ett Mycket Enkelt OrienteringsSystem")).setColor(colorDarkBlue); - gdi.dropLine(2); + gdi.addString("", textImage, "513"); + + gdi.addString("", fontMediumPlus, makeDash(L"Om MeOS - ett Mycket Enkelt OrienteringsSystem")).setColor(colorDarkBlue); + gdi.dropLine(1); + + wchar_t FileNamePath[260]; + getUserFile(FileNamePath, L""); + gdi.pushX(); + gdi.fillRight(); + gdi.addString("", 0, "MeOS lokala datakatalog är: "); + gdi.fillDown(); + gdi.addString("fnpath", 0, FileNamePath, CompetitionCB); + gdi.popX(); + gdi.dropLine(0.5); + RECT rc = { gdi.getCX(), gdi.getCY(), gdi.getPageX(), gdi.getCY() + 2 }; + gdi.addRectangle(rc, colorBlack); + gdi.dropLine(1.5); + gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); + gdi.addStringUT(1, makeDash(L"Copyright © 2007-2019 Melin Software HB")); gdi.dropLine(); gdi.addStringUT(10, "The database connection used is MySQL++\nCopyright " @@ -4115,3 +4125,34 @@ void TabCompetition::checkReadyForResultExport(gdioutput &gdi, const set &c } } +void TabCompetition::checkRentCards(gdioutput &gdi) { + gdi.clearPage(false); + + wstring fn = gdi.browseForOpen({ make_pair(L"csv", L"*.csv") }, L"csv"); + if (!fn.empty()) { + csvparser csv; + list> data; + csv.parse(fn, data); + set rentCards; + for (auto &c : data) { + if (c.size() > 0) { + int cn = _wtoi(c[0].c_str()); + rentCards.insert(cn); + } + } + + vector runners; + oe->getRunners(0, 0, runners); + int bcf = oe->getBaseCardFee(); + for (pRunner r : runners) { + if (rentCards.count(r->getCardNo()) && r->getDCI().getInt("CardFee") == 0) { + gdi.addStringUT(0, r->getCompleteIdentification()); + r->getDI().setInt("CardFee", bcf); + } + } + } + + gdi.dropLine(); + gdi.addButton("Cancel", "OK", CompetitionCB); + gdi.refresh(); +} diff --git a/code/TabCompetition.h b/code/TabCompetition.h index c0adec9..57d5784 100644 --- a/code/TabCompetition.h +++ b/code/TabCompetition.h @@ -139,6 +139,10 @@ class TabCompetition : void createCompetition(gdioutput &gdi); void listBackups(gdioutput &gdi); + + + void checkRentCards(gdioutput &gdi); + protected: void clearCompetitionData(); diff --git a/code/TabCourse.cpp b/code/TabCourse.cpp index 141e487..8cb4d12 100644 --- a/code/TabCourse.cpp +++ b/code/TabCourse.cpp @@ -914,7 +914,7 @@ void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename, } else { for (size_t i = 0; igetNumStages(); i++) - cls[k]->addStageCourse(i, res->second->getId()); + cls[k]->addStageCourse(i, res->second->getId(), -1); } } else { @@ -939,7 +939,7 @@ void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename, } else { for (size_t i = 0; igetNumStages(); i++) - bestClass->addStageCourse(i, crs[k]->getId()); + bestClass->addStageCourse(i, crs[k]->getId(), -1); } } } diff --git a/code/TabList.cpp b/code/TabList.cpp index ec18501..dc96e05 100644 --- a/code/TabList.cpp +++ b/code/TabList.cpp @@ -56,6 +56,13 @@ const static int CUSTOM_OFFSET = 10; const static int NUMTEXTSAMPLE = 13; +const static int ForcePageBreak = 1024; +const static int IgnoreLimitPer = 512; +const static int AddTeamClasses = 4; +const static int AddPatrolClasses = 3; +const static int AddRogainingClasses = 2; +const static int AddAllClasses = 1; + TabList::TabList(oEvent *poe):TabBase(poe) { listEditor = 0; @@ -209,9 +216,12 @@ void TabList::generateList(gdioutput &gdi, bool forceUpdate) gdi.getData("GeneralList", storedWidth); gdi.restoreNoUpdate("GeneralList"); } - else + else { gdi.clearPage(false); - + if (currentList.getParam().filterMaxPer > 0 && !ownWindow && !gdi.isFullScreen()) { + gdi.addInfoBox("infofilter", L"Visar de X bästa#" + itow(currentList.getParam().filterMaxPer), 6000, 0); + } + } gdi.setRestorePoint("GeneralList"); currentList.setCallback(ownWindow ? 0 : openRunnerTeamCB); @@ -519,10 +529,12 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id == "RemoveSaved") { ListBoxInfo lbi; if (gdi.getSelectedItem("SavedInstance", lbi)) { - oe->synchronize(false); - oe->getListContainer().removeParam(lbi.data); - oe->synchronize(true); - loadPage(gdi); + if (gdi.ask(L"Vill du ta bort 'X'?#" + lbi.text)) { + oe->synchronize(false); + oe->getListContainer().removeParam(lbi.data); + oe->synchronize(true); + loadPage(gdi); + } } return 0; } @@ -888,6 +900,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) par.back().listCode = EIndCourseList; par.back().showInterTitle = false; + par.back().setLegNumberCoded(-1); cnf.getIndividual(par.back().selection); } @@ -938,14 +951,23 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) bi.id.substr(0, 7) == "StartL:" || bi.id.substr(0, 7) == "GenLst:") { bool isReport = bi.id.substr(0, 7) == "GenLst:"; - bool allClasses = bi.getExtraInt() == 1; - bool rogaining = bi.getExtraInt() == 2; - bool patrol = bi.getExtraInt() == 3; + int baseType = bi.getExtraInt() & 0xFF; + int flags = bi.getExtraInt() & 0xFF00; + bool isStartList = bi.id.substr(0, 7) == "StartL:"; + bool allClasses = baseType == AddAllClasses; + bool rogaining = baseType == AddRogainingClasses; + bool patrol = baseType == AddPatrolClasses; + bool team = baseType == AddTeamClasses; oe->sanityCheck(gdi, bi.id.substr(0, 7) == "Result:"); oListParam par; par.listCode = oe->getListContainer().getType(bi.id.substr(7)); - readSettings(gdi, par, true); - + readSettings(gdi, par, !isStartList); + if ((flags & IgnoreLimitPer) == IgnoreLimitPer || isReport) + par.filterMaxPer = 0; + + if ((flags & ForcePageBreak) == ForcePageBreak) + par.pageBreak = true; + par.setLegNumberCoded(-1); if (patrol) { ClassConfigInfo cnf; @@ -957,6 +979,11 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oe->getClassConfigurationInfo(cnf); cnf.getRogaining(par.selection); } + else if (team) { + ClassConfigInfo cnf; + oe->getClassConfigurationInfo(cnf); + cnf.getRelay(par.selection); + } else if (!isReport && !allClasses) { ClassConfigInfo cnf; oe->getClassConfigurationInfo(cnf); @@ -1872,20 +1899,20 @@ namespace { gdi.addString("", 0, info); gdi.dropLine(0.3); gdi.fillRight(); - gdi.addInput("Margin", itow(tmpSettingsParam.margin) + L" %", 5, 0, L"Marginal:"); - gdi.addInput("Scale", itow(int(tmpSettingsParam.htmlScale*100)) + L" %", 5, 0, L"Skalfaktor:"); + gdi.addInput("Margin", itow(tmpSettingsParam.margin) + L" %", 5, 0, L"Marginal:").setHandler(&htmlClass); + gdi.addInput("Scale", itow(int(tmpSettingsParam.htmlScale*100)) + L" %", 5, 0, L"Skalfaktor:").setHandler(&htmlClass); if (tmpSettingsParam.nColumns <= 0) tmpSettingsParam.nColumns = 1; - gdi.addInput("Columns", itow(tmpSettingsParam.nColumns), 5, 0, L"Kolumner:"); - gdi.addInput("Time", itow(tmpSettingsParam.timePerPage) + L"ms", 5, 0, L"Visningstid:"); + gdi.addInput("Columns", itow(tmpSettingsParam.nColumns), 5, 0, L"Kolumner:").setHandler(&htmlClass); + gdi.addInput("Time", itow(tmpSettingsParam.timePerPage) + L"ms", 5, 0, L"Visningstid:").setHandler(&htmlClass); gdi.popX(); gdi.dropLine(3.4); gdi.addCheckbox("UseRows", "Begränsa antal rader per sida", 0, tmpSettingsParam.htmlRows>0).setHandler(&htmlClass); gdi.dropLine(-0.4); - gdi.addInput("Rows", itow(tmpSettingsParam.htmlRows), 5); + gdi.addInput("Rows", itow(tmpSettingsParam.htmlRows), 5).setHandler(&htmlClass); gdi.setInputStatus("Rows", tmpSettingsParam.htmlRows > 0); gdi.popX(); @@ -1893,13 +1920,13 @@ namespace { } else { gdi.fillRight(); - gdi.addInput("Scale", itow(int(tmpSettingsParam.htmlScale * 100)) + L" %", 5, 0, L"Skalfaktor:"); + gdi.addInput("Scale", itow(int(tmpSettingsParam.htmlScale * 100)) + L" %", 5, 0, L"Skalfaktor:").setHandler(&htmlClass); gdi.popX(); gdi.dropLine(3.4); gdi.addCheckbox("Reload", "Automatisk omladdning", 0, tmpSettingsParam.timePerPage>999).setHandler(&htmlClass); gdi.dropLine(-0.4); - gdi.addInput("ReloadTime", itow(tmpSettingsParam.timePerPage/1000) + L" s", 5); + gdi.addInput("ReloadTime", itow(tmpSettingsParam.timePerPage/1000) + L" s", 5).setHandler(&htmlClass); gdi.setInputStatus("Reload", tmpSettingsParam.timePerPage>999); gdi.popX(); @@ -1907,6 +1934,8 @@ namespace { } gdi.fillRight(); gdi.addButton("ApplyList", "Lagra inställningar").setHandler(&htmlClass); + if (tmpSettingsParam.sourceParam != -1) + gdi.disableInput("ApplyList"); gdi.addButton("Automatic", "Automatisera", 0, "Skriv ut eller exportera listan automatiskt.").setHandler(&htmlClass); gdi.addButton("HTML", "Exportera").setHandler(&htmlClass); } @@ -2021,6 +2050,7 @@ void TabList::handleHTMLSettings(gdioutput &gdi, BaseInfo &info, GuiEventType ty else { dest_gdi.sendCtrlMessage("Remember"); } + gdi.disableInput("ApplyList"); } else { if (lastHtmlTarget.empty()) { @@ -2055,18 +2085,24 @@ void TabList::handleHTMLSettings(gdioutput &gdi, BaseInfo &info, GuiEventType ty } else if (bi.id == "UseRows") { gdi.setInputStatus("Rows", gdi.isChecked(bi.id)); + gdi.enableInput("ApplyList"); } else if (bi.id == "Reload") { gdi.setInputStatus("ReloadTime", gdi.isChecked(bi.id)); + gdi.enableInput("ApplyList"); } } else if (type == GUI_LISTBOX) { ListBoxInfo lbi = dynamic_cast(info); if (lbi.id == "Format") { htmlDetails(gdi, tmpSettingsParam, html2IdToInfo[lbi.data], lbi.data > 5); + gdi.enableInput("ApplyList"); gdi.refresh(); } } + else if (type == GUI_INPUTCHANGE) { + gdi.enableInput("ApplyList"); + } } void TabList::loadClassSettings(gdioutput &gdi, string targetTag) { @@ -2345,7 +2381,7 @@ bool TabList::loadPage(gdioutput &gdi) } if (cnf.hasPatrol()) { checkWidth(gdi); - gdi.addButton("StartL:patrolstart", "Patrull", ListsCB).setExtra(3); + gdi.addButton("StartL:patrolstart", "Patrull", ListsCB).setExtra(AddPatrolClasses); } for (size_t k = 0; k < cnf.raceNStart.size(); k++) { if (cnf.raceNStart[k].size() > 0) { @@ -2386,7 +2422,7 @@ bool TabList::loadPage(gdioutput &gdi) } if (cnf.hasPatrol()) { - gdi.addButton("Result:patrolresult", "Patrull", ListsCB).setExtra(3); + gdi.addButton("Result:patrolresult", "Patrull", ListsCB).setExtra(AddPatrolClasses); checkWidth(gdi); } @@ -2395,7 +2431,7 @@ bool TabList::loadPage(gdioutput &gdi) gdi.addButton("Result:liveresultradio", "Liveresultat", ListsCB); checkWidth(gdi); - gdi.addButton("Result:latestresult", "Latest Results", ListsCB).setExtra(1); + gdi.addButton("Result:latestresult", "Latest Results", ListsCB).setExtra(AddAllClasses); checkWidth(gdi); if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { @@ -2457,8 +2493,7 @@ bool TabList::loadPage(gdioutput &gdi) if (cnf.hasRogaining()) { checkWidth(gdi); - //gdi.addButton("RogainingResultList", "Rogaining", ListsCB); - gdi.addButton("Result:rogainingind", "Rogaining", ListsCB).setExtra(2); + gdi.addButton("Result:rogainingind", "Rogaining", ListsCB).setExtra(AddRogainingClasses); } checkWidth(gdi); @@ -2519,24 +2554,33 @@ bool TabList::loadPage(gdioutput &gdi) gdi.fillRight(); gdi.pushX(); - gdi.addButton("InForestList", "Kvar-i-skogen", ListsCB, "tooltip:inforest"); + gdi.addButton("InForestList", "Kvar-i-skogen", ListsCB, "tooltip:inforest").setExtra(IgnoreLimitPer); if (cnf.hasIndividual()) { gdi.addButton("PriceList", "Prisutdelningslista", ListsCB); } gdi.addButton("PreReport", "Kör kontroll inför tävlingen...", ListsCB); checkWidth(gdi); + if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy) && cnf.hasIndividual()) { + gdi.addButton("GenLst:unexpectedfee", "Unexpected Fee", ListsCB); + checkWidth(gdi); + } + if (cnf.hasMultiCourse) { - gdi.addButton("CourseReport", "Bantilldelning", ListsCB); + gdi.addButton("CourseReport", "Bantilldelning", ListsCB).setExtra(IgnoreLimitPer); checkWidth(gdi); if (cnf.hasTeamClass()) { gdi.addButton("GenLst:courseteamtable", "Gafflingar i tabellformat", ListsCB, - "Från den här listan kan man skapa etiketter att klistra på kartor"); + "Från den här listan kan man skapa etiketter att klistra på kartor"); checkWidth(gdi); - } + } } + if (cnf.hasTeamClass()) { + gdi.addButton("GenLst:teamchanges", "Lagändringblankett", ListsCB).setExtra(AddTeamClasses | ForcePageBreak); + checkWidth(gdi); + } bool hasVac = false; bool hasAPIEntry = false; { @@ -2579,7 +2623,7 @@ bool TabList::loadPage(gdioutput &gdi) } if (cnf.hasRentedCard) - gdi.addButton("HiredCards", "Hyrbricksrapport", ListsCB); + gdi.addButton("HiredCards", "Hyrbricksrapport", ListsCB).setExtra(IgnoreLimitPer); gdi.popX(); @@ -3055,9 +3099,6 @@ void TabList::getPublicLists(oEvent &oe, vector &lists) { getResultPatrol(oe, lists.back(), cnf); } - if (cnf.hasRogaining()) { - //gdi.addButton("Result:rogainingind", "Rogaining", ListsCB).setExtra(2); - } } MetaListContainer &lc = oe.getListContainer(); diff --git a/code/TabRunner.cpp b/code/TabRunner.cpp index 9aeab3b..ae74a0e 100644 --- a/code/TabRunner.cpp +++ b/code/TabRunner.cpp @@ -1089,6 +1089,10 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) pRunner r = oe->getRunner(runnerId, 0); if (r) { warnDuplicateCard(gdi, cardNo, r); + + if (ii.changedInput() && oe->hasHiredCardData()) { + gdi.check("RentCard", oe->isHiredCard(cardNo)); + } } } } @@ -1368,7 +1372,6 @@ int TabRunner::vacancyCB(gdioutput &gdi, int type, void *data) if (type == GUI_BUTTON) { ButtonInfo bi=*(ButtonInfo *)data; TabSI &tsi = dynamic_cast(*gdi.getTabs().get(TSITab)); - if (bi.id == "VacancyAdd") { showVacancyList(gdi, "add"); @@ -1509,9 +1512,12 @@ int TabRunner::vacancyCB(gdioutput &gdi, int type, void *data) return 0; } -void TabRunner::setCardNo(gdioutput &gdi, int cardNo) -{ - pRunner db_r=oe->dbLookUpByCard(cardNo); +void TabRunner::setCardNo(gdioutput &gdi, int cardNo) { + pRunner db_r=oe->dbLookUpByCard(cardNo); + + if (cardNo > 0 && oe->hasHiredCardData()) { + gdi.check("RentCard", oe->isHiredCard(cardNo)); + } if (db_r) { gdi.setText("Name", db_r->getName()); @@ -1810,7 +1816,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { } else { vector punches; - oe.getPunchesForRunner(r->getId(), punches); + oe.getPunchesForRunner(r->getId(), true, punches); int lastT = r->getStartTime(); for (size_t k = 0; k < punches.size(); k++) { @@ -1953,7 +1959,7 @@ void TabRunner::showVacancyList(gdioutput &gdi, const string &method, int classI lastFee = tsi.storedInfo.storedFee; gdi.addCombo("Fee", 60, 150, 0, L"Avgift:"); - oe->fillFees(gdi, "Fee", true); + oe->fillFees(gdi, "Fee", false, true); gdi.autoGrow("Fee"); if (!lastFee.empty() && lastFee != L"@") { @@ -2097,11 +2103,12 @@ void TabRunner::listRunners(gdioutput &gdi, const vector &r, bool filte for (size_t k=0; kisVacant()) continue; + out.clear(); sprintf_s(bf, "%d.", k+1); gdi.addStringUT(yp, xp, 0, bf); - gdi.addStringUT(yp, xp+40, 0, r[k]->getNameAndRace(true), 190); - gdi.addStringUT(yp, xp+200, 0, r[k]->getClass(true), 140); - gdi.addStringUT(yp, xp+350, 0, r[k]->getClub(), 190); + gdi.addStringUT(yp, xp+gdi.scaleLength(40), 0, r[k]->getNameAndRace(true), gdi.scaleLength(190)); + gdi.addStringUT(yp, xp+gdi.scaleLength(200), 0, r[k]->getClass(true), gdi.scaleLength(140)); + gdi.addStringUT(yp, xp + gdi.scaleLength(350), 0, r[k]->getClub(), +gdi.scaleLength(190)); int c = r[k]->getCardNo(); if (c>0) { { @@ -2113,10 +2120,10 @@ void TabRunner::listRunners(gdioutput &gdi, const vector &r, bool filte } } if (out.size() <= 1) { - gdi.addStringUT(yp, xp+550, 0, "(" + itos(c) + ")", 190); + gdi.addStringUT(yp, xp+gdi.scaleLength(550), 0, "(" + itos(c) + ")", 190); } else { - TextInfo &ti = gdi.addStringUT(yp, xp+550, 0, L"(" + itow(c) + lang.tl(", reused card") + L")", 100); + TextInfo &ti = gdi.addStringUT(yp, xp+gdi.scaleLength(550), 0, L"(" + itow(c) + lang.tl(", reused card") + L")", gdi.scaleLength(100)); wstring tt; for (size_t j = 0; j < out.size(); j++) { if (out[j] == r[k]->getMultiRunner(0)) @@ -2793,6 +2800,8 @@ void TabRunner::fillRunnerList(gdioutput &gdi) { bool TabRunner::canSetStart(pRunner r) const { pClass pc = r->getTeam() ? r->getTeam()->getClassRef(false) : r->getClassRef(true); + if (r->getTeam() && pc->isQualificationFinalBaseClass()) + pc = r->getClassRef(true); if (pc && pc->getNumStages() > 0) { StartTypes st = pc->getStartType(r->getLegNumber()); diff --git a/code/TabSI.cpp b/code/TabSI.cpp index bdab253..487e5d3 100644 --- a/code/TabSI.cpp +++ b/code/TabSI.cpp @@ -933,12 +933,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.restore("EntryLine"); wchar_t bf[256]; + wstring cno = r->getCardNo() > 0 ? L"(" + itow(r->getCardNo()) + L"), " : L""; + if (r->getClubId() != 0) { - swprintf_s(bf, L"(%d), %s, %s", r->getCardNo(), r->getClub().c_str(), + swprintf_s(bf, L"%s%s, %s", cno.c_str(), r->getClub().c_str(), r->getClass(true).c_str()); } else { - swprintf_s(bf, L"(%d), %s", r->getCardNo(), r->getClass(true).c_str()); + swprintf_s(bf, L"%s%s", cno.c_str(), r->getClass(true).c_str()); } wstring info(bf); @@ -1035,6 +1037,84 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.setInputStatus("StatusOK", !dnf); gdi.check("StatusOK", !dnf); } + else if (bi.id == "RHCClear") { + if (gdi.ask(L"Vill du tömma listan med hyrbrickor?")) { + oe->clearHiredCards(); + loadPage(gdi); + } + } + else if (bi.id == "RHCImport") { + wstring fn = gdi.browseForOpen({ make_pair(L"Semikolonseparerad (csv)", L"*.csv") }, L"csv"); + if (!fn.empty()) { + csvparser csv; + list> data; + csv.parse(fn, data); + set rentCards; + for (auto &c : data) { + for (wstring wc : c) { + int cn = _wtoi(wc.c_str()); + if (cn > 0) { + oe->setHiredCard(cn, true); + gdi.addStringUT(0, itos(cn)).setHandler(getResetHiredCardHandler()); + } + } + } + gdi.scrollToBottom(); + gdi.refresh(); + vector runners; + oe->getRunners(0, 0, runners); + if (!runners.empty() && gdi.ask(L"Vill du sätta hyrbricka på befintliga löpare med dessa brickor?")) { + int bcf = oe->getBaseCardFee(); + for (pRunner r : runners) { + if (rentCards.count(r->getCardNo()) && r->getDCI().getInt("CardFee") == 0) { + gdi.addStringUT(0, r->getCompleteIdentification()); + r->getDI().setInt("CardFee", bcf); + } + } + } + loadPage(gdi); + } + } + else if (bi.id == "RHCExport") { + int ix = 0; + wstring fn = gdi.browseForSave({ make_pair(L"Semikolonseparerad (csv)", L"*.csv") }, L"csv", ix); + if (!fn.empty()) { + oe->synchronizeList(oListId::oLPunchId); + + auto hc = oe->getHiredCards(); + csvparser csv; + csv.openOutput(fn); + for (int c : hc) + csv.outputRow(itos(c)); + csv.closeOutput(); + } + } + else if (bi.id == "RHCPrint") { + gdioutput gdiPrint("print", gdi.getScale()); + gdiPrint.clearPage(false); + + gdiPrint.addString("", boldLarge, "Hyrbricksrapport"); + + oe->synchronizeList(oListId::oLPunchId); + auto hc = oe->getHiredCards(); + int dc = gdiPrint.scaleLength(70); + int col = 0; + gdiPrint.dropLine(2); + int cx = gdiPrint.getCX(); + int cy = gdiPrint.getCY(); + + for (int h : hc) { + if (col >= 8) { + col = 0; + cy += gdiPrint.getLineHeight() * 2; + } + gdiPrint.addStringUT(cy, cx + col * dc, 0, itow(h)); + col++; + } + + gdiPrint.refresh(); + gdiPrint.print(oe); + } else if (bi.id == "CCSClear") { if (gdi.ask(L"Vill du göra om avbockningen från början igen?")) { checkedCardFlags.clear(); @@ -1138,6 +1218,9 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) else if (mode == ModeCheckCards) { showCheckCardStatus(gdi, "init"); } + else if (mode == ModeRegisterCards) { + showRegisterHiredCards(gdi); + } gdi.refresh(); } else if (bi.id=="Fee") { @@ -1385,12 +1468,17 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (runnerMatchedId != -1 && gdi.isChecked("AutoTie") && cardNo>0) gdi.addTimeoutMilli(50, "TieCard", SportIdentCB).setExtra(runnerMatchedId); } - else if (cardNo>0 && gdi.getText("Name").empty()) { - SICard sic(ConvertedTimeStatus::Hour24); - sic.clear(0); - sic.CardNumber = cardNo; + else if (cardNo>0) { + if (ii.changedInput() && oe->hasHiredCardData()) + gdi.check("RentCard", oe->isHiredCard(cardNo)); - entryCard(gdi, sic); + if (gdi.getText("Name").empty()) { + SICard sic(ConvertedTimeStatus::Hour24); + sic.clear(0); + sic.CardNumber = cardNo; + + entryCard(gdi, sic); + } } } else if (ii.id[0]=='*') { @@ -1644,6 +1732,7 @@ bool TabSI::loadPage(gdioutput &gdi) { gdi.addItem("ReadType", lang.tl("Avläsning/radiotider"), ModeReadOut); gdi.addItem("ReadType", lang.tl("Tilldela hyrbrickor"), ModeAssignCards); gdi.addItem("ReadType", lang.tl("Avstämning hyrbrickor"), ModeCheckCards); + gdi.addItem("ReadType", lang.tl("Registrera hyrbrickor"), ModeRegisterCards); gdi.addItem("ReadType", lang.tl("Anmälningsläge"), ModeEntry); gdi.addItem("ReadType", lang.tl("Print card data"), ModeCardData); @@ -1709,7 +1798,9 @@ bool TabSI::loadPage(gdioutput &gdi) { else if (mode == ModeCheckCards) { showCheckCardStatus(gdi, "init"); } - + else if (mode == ModeRegisterCards) { + showRegisterHiredCards(gdi); + } // Unconditional clear activeSIC.clear(0); @@ -1783,7 +1874,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) DWORD loaded; bool pageLoaded=gdi.getData("SIPageLoaded", loaded); - if (pageLoaded && manualInput) + if (pageLoaded && manualInput && mode == ModeReadOut) gdi.restore("ManualInput"); if (!pageLoaded && !insertCardNumberField.empty()) { @@ -1816,6 +1907,16 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) checkCard(gdi, sic, true); return; } + else if (mode == ModeRegisterCards) { + if (!pageLoaded) { + CardQueue.push_back(sic); + gdi.addInfoBox("SIREAD", L"Inläst bricka ställd i kö"); + } + else { + registerHiredCard(gdi, sic); + } + return; + } else if (mode == ModeCardData) { if (sic.convertedTime == ConvertedTimeStatus::Hour12) { int locTime = getLocalAbsTime(); @@ -2549,6 +2650,9 @@ void TabSI::entryCard(gdioutput &gdi, const SICard &sic) { gdi.setText("CardNo", sic.CardNumber); + if (oe->hasHiredCardData()) + gdi.check("RentCard", oe->isHiredCard(sic.CardNumber)); + wstring name; wstring club; int age = 0; @@ -2582,6 +2686,9 @@ void TabSI::entryCard(gdioutput &gdi, const SICard &sic) if (cls && age > 0) { directEntryGUI.updateFees(gdi, cls, age); } + else { + updateEntryInfo(gdi); + } } void TabSI::assignCard(gdioutput &gdi, const SICard &sic) @@ -2690,7 +2797,7 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy)) { gdi.addCombo("Fee", 60, 150, SportIdentCB, L"Anm. avgift:"); - oe->fillFees(gdi, "Fee", false); + oe->fillFees(gdi, "Fee", true, false); if (!storedInfo.storedFee.empty() && storedInfo.storedFee != L"@") gdi.setText("Fee", storedInfo.storedFee); @@ -3503,6 +3610,35 @@ wstring TabSI::getCardInfo(bool param, vector &count) const { return msg; } +void TabSI::showRegisterHiredCards(gdioutput &gdi) { + gdi.disableInput("Interactive"); + gdi.disableInput("Database"); + gdi.disableInput("PrintSplits"); + gdi.disableInput("UseManualInput"); + + gdi.fillDown(); + gdi.addString("", 10, "help:registerhiredcards"); + + gdi.dropLine(); + gdi.fillRight(); + gdi.pushX(); + gdi.addButton("RHCClear", "Nollställ", SportIdentCB); + gdi.addButton("RHCImport", "Importera...", SportIdentCB); + gdi.addButton("RHCExport", "Exportera...", SportIdentCB); + gdi.addButton("RHCPrint", "Skriv ut...", SportIdentCB); + gdi.popX(); + gdi.dropLine(3); + gdi.fillDown(); + + oe->synchronizeList(oListId::oLPunchId); + auto &hiredCards = oe->getHiredCards(); + for (int i : hiredCards) { + gdi.addStringUT(0, itos(i)).setExtra(i).setHandler(getResetHiredCardHandler()); + } + + gdi.refresh(); +} + void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { vector r; const int cx = gdi.getCX(); @@ -3514,6 +3650,7 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { gdi.disableInput("Database"); gdi.disableInput("PrintSplits"); gdi.disableInput("UseManualInput"); + gdi.fillDown(); gdi.addString("", 10, "help:checkcards"); @@ -3640,6 +3777,42 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { gdi.dropLine(); } + +class ResetHiredCard : public GuiHandler { + oEvent *oe; + +public: + void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { + if (type == GuiEventType::GUI_LINK) { + TextInfo &ti = dynamic_cast(info); + int c = _wtoi(ti.text.c_str()); + if (gdi.ask(L"Vill du ta bort brickan från hyrbrickslistan?")) { + oe->setHiredCard(c, false); + ti.text = L"-"; + ti.setHandler(nullptr); + gdi.refreshFast(); + } + } + } + + ResetHiredCard(oEvent *oe) : oe(oe) {} +}; + +GuiHandler *TabSI::getResetHiredCardHandler() { + if (!resetHiredCardHandler) + resetHiredCardHandler = make_shared(oe); + + return resetHiredCardHandler.get(); +} + +void TabSI::registerHiredCard(gdioutput &gdi, const SICard &sic) { + if (!oe->isHiredCard(sic.CardNumber)) + oe->setHiredCard(sic.CardNumber, true); + gdi.addStringUT(0, itos(sic.CardNumber)).setHandler(getResetHiredCardHandler()); + gdi.scrollToBottom(); + gdi.refresh(); +} + void TabSI::checkCard(gdioutput &gdi, const SICard &card, bool updateAll) { bool wasChecked = (checkedCardFlags[card.CardNumber] & CNFChecked) != 0 && updateAll; diff --git a/code/TabSI.h b/code/TabSI.h index f67cf38..0f58a4d 100644 --- a/code/TabSI.h +++ b/code/TabSI.h @@ -35,9 +35,10 @@ public: enum SIMode { ModeReadOut, ModeAssignCards, - ModeCheckCards, + ModeCheckCards, ModeEntry, - ModeCardData + ModeCardData, + ModeRegisterCards, }; void setMode(SIMode m) { mode = m; } @@ -72,7 +73,10 @@ private: vector filterDate; set warnedClassOutOfMaps; - + + shared_ptr resetHiredCardHandler; + GuiHandler *getResetHiredCardHandler(); + int runnerMatchedId; bool printErrorShown; void printProtected(gdioutput &gdi, gdioutput &gdiprint); @@ -94,7 +98,8 @@ private: int inputId; void showCheckCardStatus(gdioutput &gdi, const string &cmd); - + void showRegisterHiredCards(gdioutput &gdi); + wstring getCardInfo(bool param, vector &count) const; // Formatting for card tick off bool checkHeader; @@ -119,6 +124,7 @@ private: map checkedCardFlags; void checkCard(gdioutput &gdi, const SICard &sic, bool updateAll); + void registerHiredCard(gdioutput &gdi, const SICard &sic); void showReadPunches(gdioutput &gdi, vector &punches, set &dates); void showReadCards(gdioutput &gdi, vector &cards); diff --git a/code/TabSpeaker.cpp b/code/TabSpeaker.cpp index 26f5a78..b41a9c1 100644 --- a/code/TabSpeaker.cpp +++ b/code/TabSpeaker.cpp @@ -979,15 +979,15 @@ bool TabSpeaker::loadPage(gdioutput &gdi) { int h,w; gdi.getTargetDimension(w, h); - int bw=gdi.scaleLength(100); - int nbtn=max((w-80)/bw, 1); - bw=(w-80)/nbtn; + int bw = gdi.scaleLength(100); + int numBtn = max((w - gdi.scaleLength(80)) / bw, 1); + bw = (w - 80) / numBtn; int basex = SPEAKER_BASE_X; int basey=gdi.getCY(); int cx=basex; int cy=basey; - int cb=1; + vector clsToWatch; for (int cid : classesToWatch) { pClass pc = oe->getClass(cid); @@ -998,18 +998,29 @@ bool TabSpeaker::loadPage(gdioutput &gdi) { sort(clsToWatch.begin(), clsToWatch.end(), [](const pClass &a, const pClass &b) {return *a < *b; }); + int bwCls = bw; + TextInfo ti; + for (auto pc : clsToWatch) { + ti.xp = 0; + ti.yp = 0; + ti.format = 0; + ti.text = pc->getName(); + gdi.calcStringSize(ti); + bwCls = max(bwCls, ti.realWidth+ gdi.scaleLength(10)); + } + int limitX = w - bw / 3; + for (auto pc : clsToWatch) { char classid[32]; sprintf_s(classid, "cid%d", pc->getId()); - gdi.addButton(cx, cy, bw, classid, L"#" + pc->getName(), tabSpeakerCB, L"", false, false); - cx+=bw; - cb++; - if (cb>nbtn) { - cb=1; - cx=basex; - cy+=gdi.getButtonHeight()+4; + if (cx > basex && (cx + bwCls) >= limitX) { + cx = basex; + cy += gdi.getButtonHeight() + 4; } + + gdi.addButton(cx, cy, bwCls-2, classid, L"#" + pc->getName(), tabSpeakerCB, L"", false, false); + cx += bwCls; } bool pm = false; @@ -1021,20 +1032,21 @@ bool TabSpeaker::loadPage(gdioutput &gdi) { cx=gdi.getCX(); } else { + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "Events", "Händelser", tabSpeakerCB, "Löpande information om viktiga händelser i tävlingen", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; pm = true; } - gdi.addButton(cx + db, cy, bw - 2, "Report", "Rapportläge", tabSpeakerCB, "Visa detaljerad rapport för viss deltagare", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; cy += gdi.getButtonHeight() + 4; } - else db += bw; + gdi.addButton(cx + db, cy, bw - 2, "Report", "Rapportläge", tabSpeakerCB, "Visa detaljerad rapport för viss deltagare", false, false); + db += bw; if (pm) { gdi.addButton(cx + db, cy, bw / 5, "ZoomIn", "+", tabSpeakerCB, "Zooma in (Ctrl + '+')", false, false); @@ -1043,62 +1055,69 @@ bool TabSpeaker::loadPage(gdioutput &gdi) { db += bw / 5 + 2; } + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "Settings", "Inställningar...", tabSpeakerCB, "Välj vilka klasser och kontroller som bevakas", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; + + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "Manual", "Tidsinmatning", tabSpeakerCB, "Mata in radiotider manuellt", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "PunchTable", "Stämplingar", tabSpeakerCB, "Visa en tabell över alla stämplingar", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "LiveResult", "Direkt tidtagning", tabSpeakerCB, "Visa rullande tider mellan kontroller i helskärmsläge", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; if (!ownWindow) { + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "Priority", "Prioritering", tabSpeakerCB, "Välj löpare att prioritera bevakning för", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; + cy += gdi.getButtonHeight() + 4; + } gdi.addButton(cx+db, cy, bw-2, "Window", "Nytt fönster", tabSpeakerCB, "", false, false); - if (++cb>nbtn) { - cb = 1, cx = basex, db = 0; - cy += gdi.getButtonHeight()+4; - } else db += bw; + db += bw; if (getExtraWindows().size() == 1) { wstring sf = getSpeakerSettingsFile(); if (fileExist(sf.c_str())) { - gdi.addButton(cx + db, cy, bw - 2, "LoadWindows", "Återskapa", tabSpeakerCB, "Återskapa tidigare sparade fönster- och speakerinställningar", false, false); - if (++cb > nbtn) { - cb = 1, cx = basex, db = 0; + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; cy += gdi.getButtonHeight() + 4; } - else db += bw; + gdi.addButton(cx + db, cy, bw - 2, "LoadWindows", "Återskapa", tabSpeakerCB, "Återskapa tidigare sparade fönster- och speakerinställningar", false, false); + db += bw; } } else { - gdi.addButton(cx + db, cy, bw - 2, "SaveWindows", "Spara", tabSpeakerCB, "Spara fönster- och speakerinställningar på datorn", false, false); - if (++cb > nbtn) { - cb = 1, cx = basex, db = 0; + if ((cx + db) > basex && (cx + db + bw) >= limitX) { + cx = basex; db = 0; cy += gdi.getButtonHeight() + 4; } - else db += bw; + gdi.addButton(cx + db, cy, bw - 2, "SaveWindows", "Spara", tabSpeakerCB, "Spara fönster- och speakerinställningar på datorn", false, false); + db += bw; } } diff --git a/code/TabTeam.cpp b/code/TabTeam.cpp index 76cc3d0..804918c 100644 --- a/code/TabTeam.cpp +++ b/code/TabTeam.cpp @@ -442,7 +442,6 @@ bool TabTeam::save(gdioutput &gdi, bool dontReloadTeams) { r->setName(name, true); } r->setCardNo(cardNo, true); - if (gdi.isChecked("RENT" + itos(i))) r->getDI().setInt("CardFee", oe->getBaseCardFee()); else @@ -460,11 +459,16 @@ bool TabTeam::save(gdioutput &gdi, bool dontReloadTeams) { cardNo, 0, false); } } - else - r=oe->addRunner(name, t->getClubId(), t->getClassId(false), cardNo, 0, false); + else + r = oe->addRunner(name, t->getClubId(), t->getClassId(false), cardNo, 0, false); r->setName(name, true); r->setCardNo(cardNo, true); + if (gdi.isChecked("RENT" + itos(i))) + r->getDI().setInt("CardFee", oe->getBaseCardFee()); + else + r->getDI().setInt("CardFee", 0); + r->synchronize(); t->setRunner(i, r, true); } @@ -1065,8 +1069,11 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) } if (ii.id == "DirName" || ii.id == "DirCard") { - gdi.setInputStatus("DirOK", !gdi.getText("DirName").empty() && - gdi.getTextNo("DirCard") > 0); + int cno = gdi.getTextNo("DirCard"); + if (cno > 0 && oe->hasHiredCardData()) { + gdi.check("DirRent", oe->isHiredCard(cno)); + } + gdi.setInputStatus("DirOK", !gdi.getText("DirName").empty() && cno > 0); } @@ -1135,12 +1142,16 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) } pClass pc=oe->getClass(classId); if (pc) { - for(unsigned i=0;igetNumStages();i++){ + for (unsigned i = 0; i < pc->getNumStages(); i++) { if (ii.id == "SI" + itos(i)) { int cardNo = _wtoi(ii.text.c_str()); pTeam t = oe->getTeam(teamId); if (t) { - warnDuplicateCard(gdi, ii.id, cardNo, t->getRunner(i)); + pRunner r = t->getRunner(i); + if (ii.changedInput() && oe->hasHiredCardData()) { + gdi.check("RENT" + itos(i), oe->isHiredCard(cardNo)); + } + warnDuplicateCard(gdi, ii.id, cardNo, r); } break; } diff --git a/code/Table.cpp b/code/Table.cpp index e4ebed5..59293a5 100644 --- a/code/Table.cpp +++ b/code/Table.cpp @@ -209,11 +209,11 @@ void Table::addRow(int rowId, oBase *object) Data[0].cells[i].canEdit=false; Data[0].cells[i].type=cellEdit; - Data[0].cells[i].owner=0; + Data[0].cells[i].ownerRef.reset(); Data[1].cells[i].canEdit=false; Data[1].cells[i].type=cellEdit; - Data[1].cells[i].owner=0; + Data[1].cells[i].ownerRef.reset(); } } else { @@ -236,7 +236,7 @@ void Table::set(int column, oBase &owner, int id, const wstring &data, bool canE TableRow &row=Data[dataPointer]; TableCell &cell=row.cells[column]; cell.contents=data; - cell.owner=&owner; + cell.ownerRef = owner.getReference(); cell.id=id; cell.canEdit=canEdit; cell.type=type; @@ -725,7 +725,8 @@ void Table::selection(gdioutput &gdi, const wstring &text, int data) { TableCell &cell = Data[selectionRow].cells[selectionCol]; int id = Data[selectionRow].id; - cell.owner->inputData(cell.id, text, data, cell.contents, false); + if (cell.hasOwner()) + cell.getOwner()->inputData(cell.id, text, data, cell.contents, false); reloadRow(id); RECT rc; getRowRect(selectionRow, rc); @@ -913,7 +914,7 @@ bool Table::editCell(gdioutput &gdi, int row, int col) { if (cell.type == cellAction) { ReleaseCapture(); - gdi.makeEvent("CellAction", internalName, cell.id, cell.owner ? cell.owner->getId() : 0, false); + gdi.makeEvent("CellAction", internalName, cell.id, cell.hasOwner() ? cell.getOwner()->getId() : 0, false); return true; } @@ -934,7 +935,8 @@ bool Table::editCell(gdioutput &gdi, int row, int col) { vector< pair > out; size_t selected = 0; - cell.owner->fillInput(cell.id, out, selected); + if (cell.hasOwner()) + cell.getOwner()->fillInput(cell.id, out, selected); int width = 40; for (size_t k = 0; kinputData(cell.id, bf, 0, output, false); + if (cell.hasOwner()) + cell.getOwner()->inputData(cell.id, bf, 0, output, false); cell.contents = output; if (hEdit != 0) DestroyWindow(hEdit); @@ -2108,7 +2111,8 @@ void Table::importClipboard(gdioutput &gdi) if (cell.type==cellSelection || cell.type==cellCombo) { vector< pair > out; size_t selected = 0; - cell.owner->fillInput(cell.id, out, selected); + if (cell.hasOwner()) + cell.getOwner()->fillInput(cell.id, out, selected); index = -1; for (size_t i = 0; iinputData(cell.id, table[k][j], index, output, false); + if (cell.hasOwner()) + cell.getOwner()->inputData(cell.id, table[k][j], index, output, false); cell.contents = output; } else if (cell.type == cellCombo) { - cell.owner->inputData(cell.id, table[k][j], index, output, false); + if (cell.hasOwner()) + cell.getOwner()->inputData(cell.id, table[k][j], index, output, false); cell.contents = output; } } @@ -2158,13 +2164,13 @@ int Table::deleteRows(int row1, int row2) if ( k >= sortIndex.size()) throw std::exception("Index out of range"); const TableRow &tr = Data[sortIndex[k].index]; - oBase *ob = tr.cells[0].owner; - if (!ob) - throw std::exception("Null pointer exception"); - if (ob->canRemove()) - ob->remove(); - else - failed++; + oBase *ob = tr.cells[0].getOwner(); + if (ob) { + if (ob->canRemove()) + ob->remove(); + else + failed++; + } } clearCellSelection(0); @@ -2323,8 +2329,8 @@ int Table::getNumDataRows() const { void TableRow::setObject(oBase &obj) { ob = &obj; for (size_t k = 0; k < cells.size(); k++) { - if (cells[k].owner != 0) - cells[k].owner = &obj; + if (cells[k].hasOwner()) + cells[k].ownerRef = obj.getReference(); } } diff --git a/code/Table.h b/code/Table.h index 6c5d98f..9cc5c8c 100644 --- a/code/Table.h +++ b/code/Table.h @@ -52,7 +52,10 @@ class TableCell RECT absPos; DWORD id; - oBase *owner; + + bool hasOwner() const { return ownerRef && ownerRef->get() != nullptr; }; + oBase *getOwner() const { return ownerRef ? ownerRef->get() : nullptr; } + shared_ptr ownerRef; bool canEdit; CellType type; diff --git a/code/csvparser.cpp b/code/csvparser.cpp index 5424d3a..619ede0 100644 --- a/code/csvparser.cpp +++ b/code/csvparser.cpp @@ -642,7 +642,7 @@ bool csvparser::importOCAD_CSV(oEvent &event, const wstring &file, bool addClass } else { for (size_t i = 0; igetNumStages(); i++) - cls->addStageCourse(i, pc->getId()); + cls->addStageCourse(i, pc->getId(), -1); } cls->synchronize(); diff --git a/code/danish.lng b/code/danish.lng index 5b33625..4ead5b9 100644 --- a/code/danish.lng +++ b/code/danish.lng @@ -2315,3 +2315,97 @@ xml-data = XML data Year of birth = Førdselsår Zooma in (Ctrl + '+') = Zoom ind (Ctrl + '+') Zooma ut (Ctrl + '-') = Zoom ud (Ctrl + '-') +AllPunches = Alle stemplinger +Anmäl andra = Tilmeld andre +Anmälan mottagen = Tilmelding modtaget +Anmälan måste hanteras manuellt = Tilmelding skal håndteres manuelt. +Anyone = Enhver +Automatisk omladdning = Automatisk opdatering +Begränsa antal rader per sida = Begræns antal rækker per side +Bricknummer = Si brik nummer +ClassKnockoutTotalResult = Klasse, knock-out, samlet resultat +ClassLiveResult = Live resultater (radio tider), klassevis +Classes together = Klasser samlet +CoursePunches = Stemplinger (for bane) +Database is used and cannot be deleted = Databasen er i brug og kan ikke slettes +Direkt tidtagning = Direkte tidtagning +EFilterAPIEntry = Indtastning via API +Ej omstart = Ej omstart +Endast grundläggande (enklast möjligt) = Kund grundlæggende(så simpelt som muligt) +Endast tidtagning = Kun tidtagning +Endast tidtagning (utan banor) = Kun tidtagning (uden baner) +Endast tidtagning (utan banor), stafett = Kun tidtagning (uden baner), stafet +Externa adresser = Eksterne adresser +Felaktigt datum 'X' (Använd YYYY-MM-DD) = Forkert dato format 'X' (Brug YYYY-MM-DD) +Felaktigt rankingformat i X. Förväntat: Y = Forkert format på ranking i X \nForventet format: Y +FilterAnyResult = Med radiotider/resultat +FilterNamedControl = Med angivne poster +FilterNotFinish = Udeluk ikke gennemført +Finish order = Målrækkefølge +First to finish = Først gennemført +Från löpardatabasen = Fra løberdatabase +Från löpardatabasen i befintliga klubbar = Fra løber database fra klubber der er kendt +Färre slingor = Færre sløjfer +Förhindra att laget deltar i någon omstart = Udeluk at holdet deltager i omstart +Förhindra omstart = Udeluk omstart +HTML Export = HTML Export +HTML Export för 'X' = HTML export af 'X' +HTML formaterad genom listinställningar = HTML formateret ud fra liste indstillinger +Hittar inte klass X = Kan ikke finde klasse X +Importerar RAID patrull csv-fil = Importerer RAID patrulje .csv data +Importerar ranking = Importer ranking +Individual result by finish time = Individuelle resultater ud fra måltid +Individuellt = Individuelt +Klart. X värden tilldelade = Klar, X værdier er tildelt. +Klassen X är listad flera gånger = Klassen X er angivet flere gange +Klassval för 'X' = Valg af klasse for 'X' +Knockout total = Knock-out samlet +Kolumner = Kolonner +Lag och stafett = Hold og stafet +Lagra inställningar = Gem indstillinger +LineBreak = Linieskift +Liveresultat, radiotider = Live resultater med radiotider +MeOS utvecklinsstöd = Støtte til MeOS udvikling +Med direktanmälan = Med tilmelding på stævneplads +Ogiltig starttid X = Ugyldig starttid X +Ogiltigt startintervall X = Ugyldigt start interval X +Pages with columns = Sider med kolonner +Pages with columns, no header = Sider med kolonner, uden overskrift +PunchAbsTime = Stempling rigtig tid +PunchName = Stempling, post benævnelse +PunchNamedSplit = Tid siden seneste nævnte post +PunchSplitTime = Tid siden seneste post (mellemtid) +PunchTimeSinceLast = Tid mellem poster +PunchTotalTime = Samlet tid ved post +PunchTotalTimeAfter = Samlet efter ved post +Rad X är ogiltig = Række X er ugyldig +Rader = Rækker +RunnerCheck = Tidspunkt for check stempling +RunnerGrossTime = Løbers tid før korrektion +RunnerId = Løbers eksterne ID +Slå ihop med befintlig lista = Slå sammen med eksisterende liste +StartTimeClass = Start tid, klasse +Support intermediate legs = Understøt angivne stafet stræk +TeamGrossTime = Holdets tid før korrektion +Till vilka klasser = Til hvilke klasser +Tillåt anmälan = Tillad tilmelding +Timekeeping = Tidtagning +Varning: Följande deltagare har ett osäkert resultat = Advarsel: tildeling er uklar for følgende løbere. +Varning: Kartorna är slut = Advarsel: Der er ikke flere kort +Varning: deltagare med blankt namn påträffad. MeOS kräver att alla deltagare har ett namn, och tilldelar namnet 'N.N.' = Advarsel: Der er fundet en løber uden tildelt navn. MeOS kræver at alle løbere har et navn. MeOS har tildelt navnet 'N.N.' +Varning: lag utan namn påträffat. MeOS kräver att alla lag har ett namn, och tilldelar namnet 'N.N.' = Advarsel: Der er fundet et hold uden tildelt navn. MeOS kræver at hold har et navn og har tildelt navnet 'N.N.' +Varvräkning = Tæl omgange +Varvräkning med mellantid = Tæl omgange med mellemtider +Vem får anmäla sig = Hvem kan tilmelde sig +Vill du ta bort schemat? = Vil du fjerne skemaet? +Visa detaljerad rapport för viss deltagare = Vis detaljeret rapport for en specifik deltager +Visa rubrik = Vis overskrift +Visa rubrik mellan listorna = Vis overskrift imellem listerne +Without courses = Uden baner +X går vidare, klass enligt ranking = X går videre, klasse ifølge ranking +ask:outofmaps = Der er ikke flere kort. Vil du alligevel tilføje denne løber? +ask:removescheme = Resultater slettes hvis du sletter dette skema. Vil du fortsætte? +help:custom_text_lines = Du kan indsætte egne data ved at skrive [Symbol Name]. De mulige tegn kan ses i listen til højre.\n\nExample: Well done [Løbernavn]! +htmlhelp = HTML kan eksporteres som en struktureret tabel eller som et frit formateret dokument (mere i stil med MeOS lister). Du kan også bruge eksporter skabeloner med egen formatering: kolonner, JavaScript base page flips, automatisk rulning, o.s.v. Det er muligt at tilføje egne skabeloner ved at tilføje '.template' filer i MeOS mappen. Hvis du bruger skabeloner er der et antal parametre der skal angives, se nedenfor. Den præcise fortolkning af parametrene afhænger af skabelonen.\n\nHvis du vælger bliver listen og dens opsætning gemt permanent i løbet. Du kan så tilgå listen ved at bruge MeOS som Web server (Tjenesten 'Information Server') eller ved at eksportere listen ved jævne mellemrum. +info:pageswithcolumns = Vis listen en side af gangen med det angivne antal kolonner. Genindlæs listen automatisk efter hvert gennemløb. +Övrigt = Øvrigt diff --git a/code/english.lng b/code/english.lng index c8ea89f..7f6b52b 100644 --- a/code/english.lng +++ b/code/english.lng @@ -2392,7 +2392,7 @@ Bricknummer = Card number Anmäl andra = New entry Anmälan mottagen = Accepted entry Automatisk omladdning = Automatic update -Till vilka klasser = To which classes +Till vilka klasser = To what classes Vem får anmäla sig = Who may enter Anmälan måste hanteras manuellt = Your entry requires manual processing. EFilterAPIEntry = Entries via API @@ -2407,3 +2407,24 @@ info:pageswithcolumns = Show the list one page at the time, with the specified n Pages with columns = Pages with columns Pages with columns, no header = Pages with columns, no header Externa adresser = External links +info:advanceinfo = Starting the service for instant result transfer failed. Results will be recieved with a few seconds delay. This is expected behaviour if more than one MeOS process is started on this computer. +Klassen är full = The class is full +Flytta upp = Move up +Flytta ner = Move down +EFilterWrongFee = Unexpected fee +RunnerExpectedFee = Competitors expected fee +Unexpected Fee = Unexpected entry fees +Anmälningsdatum = Entry date +Förväntad = Expected +Registrera hyrbrickor = Register rental cards +Vill du sätta hyrbricka på befintliga löpare med dessa brickor? = Do you want to apply rental card data on existing runners? +Vill du ta bort brickan från hyrbrickslistan? = Do you want to remove the card from the rental card list? +Vill du tömma listan med hyrbrickor? = Do you want to clear the rental card list? +prefsLastExportTarget = Last export target +prefsServiceRootMap = Standard function for web server root +prefsshowheader = Show page headers +help:registerhiredcards = Preregister punching cards as rental cards to get automatic hired card status when the card is assigned. +Lagändringblankett = Team Change Form +Mappa rootadresssen (http:///localhost:port/) till funktion = Map root address (http:///localhost:port/) to function +ClassAvailableMaps = Available maps for class +ClassTotalMaps = Total number of maps for class diff --git a/code/gdioutput.cpp b/code/gdioutput.cpp index c1d3ef6..60c49a0 100644 --- a/code/gdioutput.cpp +++ b/code/gdioutput.cpp @@ -1016,14 +1016,11 @@ ButtonInfo &gdioutput::addButton(int x, int y, const string &id, const wstring & HANDLE bm = 0; int width = 0; if (text[0] == '@') { - HINSTANCE hInst = GetModuleHandle(0);//(HINSTANCE)GetWindowLong(hWndTarget, GWL_HINSTANCE); - int ir = _wtoi(text.c_str() + 1); - // bm = LoadImage(hInst, MAKEINTRESOURCE(ir), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); - bm = LoadBitmap(hInst, MAKEINTRESOURCE(ir));// , IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); + HINSTANCE hInst = GetModuleHandle(0); int ir = _wtoi(text.c_str() + 1); + bm = LoadBitmap(hInst, MAKEINTRESOURCE(ir)); SIZE size; size.cx = 24; - //GetBitmapDimensionEx(bm, &size); width = size.cx+4; } else { @@ -1031,6 +1028,13 @@ ButtonInfo &gdioutput::addButton(int x, int y, const string &id, const wstring & HDC hDC = GetDC(hWndTarget); SelectObject(hDC, getGUIFont()); wstring ttext = lang.tl(text); + int tts = ttext.size(); + if (tts > 2 && ttext[0] == '<' && ttext[1] == '<') { + ttext = L"◀" + ttext.substr(2); + } + else if (tts > 2 && ttext[tts-1] == '>' && ttext[tts-2] == '>') { + ttext = ttext.substr(0, tts-2) + L"▶"; + } if (lang.capitalizeWords()) capitalizeWords(ttext); GetTextExtentPoint32(hDC, ttext.c_str(), ttext.length(), &size); @@ -1100,6 +1104,13 @@ ButtonInfo &gdioutput::addButton(int x, int y, int w, const string &id, ButtonInfo bi; wstring ttext = lang.tl(text); + int tts = ttext.size(); + if (tts > 2 && ttext[0] == '<' && ttext[1] == '<') { + ttext = L"◀" + ttext.substr(2); + } + else if (tts > 2 && ttext[tts - 1] == '>' && ttext[tts - 2] == '>') { + ttext = ttext.substr(0, tts - 2) + L"▶"; + } if (lang.capitalizeWords()) capitalizeWords(ttext); int height = getButtonHeight(); @@ -1779,6 +1790,26 @@ bool gdioutput::getSelectedItem(ListBoxInfo &lbi) { return true; } +int gdioutput::getNumItems(const char *id) { + for (auto &lbi : LBI) { + if (lbi.id == id) { + if (lbi.IsCombo) { + return SendMessage(lbi.hWnd, CB_GETCOUNT, 0, 0); + } + else { + return SendMessage(lbi.hWnd, LB_GETCOUNT, 0, 0); + } + } + } + +#ifdef _DEBUG + string err = string("Internal Error, identifier not found: X#") + id; + throw std::exception(err.c_str()); +#endif + + return 0; +} + int gdioutput::getItemDataByName(const char *id, const char *name) const{ wstring wname = recodeToWide(name); list::const_iterator it; @@ -1872,6 +1903,61 @@ bool gdioutput::selectItemByData(const char *id, int data) return false; } +bool gdioutput::selectItemByIndex(const char *id, int index) { + for (auto it = LBI.begin(); it != LBI.end(); ++it) { + if (it->id == id) { + if (it->IsCombo) { + if (index == -1) { + SendMessage(it->hWnd, CB_SETCURSEL, -1, 0); + it->data = 0; + it->text = L""; + it->original = L""; + it->originalIdx = -1; + return true; + } + else { + SendMessage(it->hWnd, CB_SETCURSEL, index, 0); + int data = SendMessage(it->hWnd, CB_GETITEMDATA, index, 0); + it->data = data; + it->originalIdx = data; + TCHAR bf[1024]; + if (SendMessage(it->hWnd, CB_GETLBTEXT, index, LPARAM(bf)) != CB_ERR) { + it->text = bf; + it->original = bf; + } + return true; + } + return false; + } + else { + if (index == -1) { + SendMessage(it->hWnd, LB_SETCURSEL, -1, 0); + it->data = 0; + it->text = L""; + it->original = L""; + it->originalIdx = -1; + return true; + } + else { + SendMessage(it->hWnd, LB_SETCURSEL, index, 0); + int data = SendMessage(it->hWnd, LB_GETITEMDATA, index, 0); + + it->data = data; + it->originalIdx = data; + TCHAR bf[1024]; + if (SendMessage(it->hWnd, LB_GETTEXT, index, LPARAM(bf)) != LB_ERR) { + it->text = bf; + it->original = bf; + } + return true; + } + return false; + } + } + } + return false; +} + bool gdioutput::autoGrow(const char *id) { list::iterator it; int size = 0; diff --git a/code/gdioutput.h b/code/gdioutput.h index 4a3edc1..9700a64 100644 --- a/code/gdioutput.h +++ b/code/gdioutput.h @@ -549,6 +549,8 @@ public: int getItemDataByName(const char *id, const char *name) const; bool selectItemByData(const char *id, int data); + bool selectItemByIndex(const char *id, int index); + void removeSelected(const char *id); bool selectItemByData(const string &id, int data) { @@ -579,6 +581,9 @@ public: /** Return a selected item*/ bool getSelectedItem(const string &id, ListBoxInfo &lbi); + /** Get number of items in a list box.'*/ + int getNumItems(const char *id); + /** Return the selected data in first, second indicates if data was available*/ pair getSelectedItem(const string &id); pair getSelectedItem(const char *id); diff --git a/code/gdistructures.h b/code/gdistructures.h index 9064fa2..d8a10e2 100644 --- a/code/gdistructures.h +++ b/code/gdistructures.h @@ -251,7 +251,9 @@ public: InputInfo &setFont(gdioutput &gdi, gdiFonts font); GDICOLOR getBgColor() const {return bgColor;} GDICOLOR getFgColor() const {return fgColor;} - + /** Return the previously stored text */ + const wstring &getPreviousText() const { return focusText; } + bool changedInput() const { return text != focusText; } InputInfo &setPassword(bool pwd); HWND getControlWindow() const {return hWnd;} @@ -276,7 +278,7 @@ private: bool isEditControl; bool writeLock; wstring original; - wstring focusText; // Test when got focus + wstring focusText; // Text when got focus bool ignoreCheck; // True if changed-state should be ignored friend class gdioutput; }; diff --git a/code/html1.htm b/code/html1.htm index d12cc6b..a2d5f9f 100644 --- a/code/html1.htm +++ b/code/html1.htm @@ -272,6 +272,196 @@ When and only when the type is CourseIndividual, the attribute course< */person> + +

Status

+Syntax: +
/meos?get=status
+ +Returns: +

MeOS Status Data. Includes MeOS version and competition name id, which also is the name of the database used, if any.

+ +Example: +
+*MOPComplete>
+ *status version="3.6.1029" eventNameId="meos_20190223_212818_2FD" onDatabase="1"/>
+*/MOPComplete>
+
+ +

Entry Classes

+Syntax: +
/meos?get=entryclass
+ +Returns: +

Classes where entry is allowed via the API.

+ +
+*EntryClasses>
+  *Class id="5">
+    *Name>U1*Name>
+    *Fee>70 kr*/Fee>
+    *MaxAge>16*/MaxAge>
+    *Start>Start 1*/Start>
+    *AvailableStarts>30*/AvailableStarts>
+  */Class>
+*/EntryClasses>
+
+ +

Lookup competitor

+Syntax: +
/meos?lookup=competitor&id=*id>
+
/meos?lookup=competitor&card=*card>
+
/meos?lookup=competitor&bib=*bib>
+
/meos?lookup=competitor&name=*name>&club=*club>
+ +Returns: +

Competitor including individual result.

+ +Arguments: +
    +
  • id Competitor id. MeOS internal id.
  • +
  • card Card number.
  • +
  • bib Bib or start number.
  • +
  • name Name of competitor.
  • +
  • club Name of club.
  • +
+ +
+*Competitors>
+  *Competitor id="85">
+    *Name>Nils Bor*/Name>
+    *ExternalId>1234*/ExternalId>
+    *Club id="84">OK Linné*/Club>
+    *Class id="204">Men*/Class>
+    *Card>16733*/Card>
+    *Status code="1">OK*/Status>
+    *Start>17:38:00*/Start>
+    *Finish>18:22:21*/Finish>
+    *RunningTime>44:21*/RunningTime>
+    *Place>7*/Place>
+    *TimeAfter>13:04*/TimeAfter>
+    *Team id="26">OK Linné 2*/Team>
+    *Leg>1*/Leg>
+    *Splits>
+      *Control number="1">
+        *Name>[31]*/Name>
+        *Time>6:25*/Time>
+        *Analysis lost="1:11" behind="1:11" mistake="" leg="5" total="3"/>
+      */Control>
+      *Control number="2">
+        *Name>Radio*/Name>
+        *Time>12:50*/Time>
+        *Analysis lost="1:10" behind="2:21" mistake="1:20" leg="3" total="3"/>
+      */Control>
+      ...
+    */Splits>
+  */Competitor>
+*/Competitors>
+
+ + +

Lookup Database Competitor

+Syntax: +
/meos?lookup=dbcompetitor&id=*id>
+
/meos?lookup=dbcompetitor&card=*card>
+
/meos?lookup=dbcompetitor&name=*name>&club=*club>
+ +Returns: +

Competitor from runner database. Note that a partial name may be submitted, and that several matching result may be returned, sorted by relevance. +This query is suitable for auto complete functionality.

+ +Arguments: +
    +
  • id External id from runner database.
  • +
  • card Card number.
  • +
  • name Name of competitor. Possibly a partial name.
  • +
  • club Name of club.
  • +
+ + +
+*DatabaseCompetitors>
+  *Competitor id="15393">
+    *Name>Anskar Dahl*/Name>
+    *Club id="575">IFK Mora OK*/Club>
+    *Card>79709*/Card>
+    *Nationality>SWE*/Nationality>
+    *Sex>M*/Sex>
+    *BirthYear>1957*/BirthYear>
+  */Competitor>
+*/DatabaseCompetitors>
+
+ + +

Lookup Database Club

+Syntax: +
/meos?lookup=dbclub&id=*id>
+
/meos?lookup=dbclub&name=*name>
+ +Returns: +

Club from club database. Note that a partial name may be submitted, and that several matching result may be returned, sorted by relevance. +This query is suitable for auto complete functionality.

+ +Arguments: +
    +
  • id External id from club database.
  • +
  • name Name of club. Possibly a partial name.
  • +
+ +
+*DatabaseClubs>
+  *Club id="134">
+  *Name>OK Enen*/Name>
+  */Club>
+*/DatabaseClubs>
+
+ +

API New Entry

+Syntax: +
/meos?entry&id=*id>&class=*classid>&card=*card>
+
/meos?entry&name=*name>&club=*club>&class=*classid>&card=*card>
+ +Arguments: +
    +
  • id External id of runner from runner database.
  • +
  • name Name of runner.
  • +
  • club Name of runner's club.
  • +
  • class Id of class.
  • +
  • card Card number.
  • +
+ +Returns: +Status. + +
+*Answer>
+  *Status>OK*/Status>
+  *Fee>130*/Fee>
+  *Info>Open Long, Rudolf Minowski (OK Tisaren)*/Info>
+*/Answer>
+
+ +
+*Answer>
+  *Status>Failed*/Status>
+  *Info>Out of maps.*/Info>
+*/Answer>
+
+ +

Page template

+Syntax: +
/meos?page=*page>
+ +Returns: +Installed template file with the specified tag. + + +

Image

+Syntax: +
/meos?image=*image>
+ +Returns: +Image, *image>.png, if installed in MeOS datafolder. MeOS logo if *image> is meos. +

IOF XML Results

Syntax: diff --git a/code/iof30interface.cpp b/code/iof30interface.cpp index 19127f6..d5fef11 100644 --- a/code/iof30interface.cpp +++ b/code/iof30interface.cpp @@ -269,7 +269,7 @@ void IOF30Interface::classCourseAssignment(gdioutput &gdi, xmlList &xAssignment, if (pc->hasMultiCourse()) { for (size_t k = 0; k < pc->getNumStages(); k++) { for (size_t j = 0; j < pCrs.size(); j++) - pc->addStageCourse(k, pCrs[j]); + pc->addStageCourse(k, pCrs[j], -1); } } else @@ -277,7 +277,7 @@ void IOF30Interface::classCourseAssignment(gdioutput &gdi, xmlList &xAssignment, } else if (leg == 0 && pCrs.size() == 1) { if (pc->hasMultiCourse()) - pc->addStageCourse(0, pCrs[0]); + pc->addStageCourse(0, pCrs[0], -1); else pc->setCourse(pCrs[0]); } @@ -286,7 +286,7 @@ void IOF30Interface::classCourseAssignment(gdioutput &gdi, xmlList &xAssignment, pc->setNumStages(leg+1); for (size_t j = 0; j < pCrs.size(); j++) - pc->addStageCourse(leg, pCrs[j]); + pc->addStageCourse(leg, pCrs[j], -1); } } } @@ -620,7 +620,7 @@ void IOF30Interface::classAssignmentObsolete(gdioutput &gdi, xmlList &xAssignmen c[k]->setNumStages(1); c[k]->clearStageCourses(0); for (size_t j = 0; j < crsFam.size(); j++) - c[k]->addStageCourse(0, crsFam[j]->getId()); + c[k]->addStageCourse(0, crsFam[j]->getId(), -1); } } else { @@ -628,7 +628,7 @@ void IOF30Interface::classAssignmentObsolete(gdioutput &gdi, xmlList &xAssignmen for (int i = 0; i < nl; i++) { c[k]->clearStageCourses(i); for (int j = 0; j < nFam; j++) - c[k]->addStageCourse(i, crsFam[(j + i)%nFam]->getId()); + c[k]->addStageCourse(i, crsFam[(j + i)%nFam]->getId(), -1); } } assigned = true; @@ -648,7 +648,7 @@ void IOF30Interface::classAssignmentObsolete(gdioutput &gdi, xmlList &xAssignmen const vector &crsFam = coursesFamilies.find(*fit)->second; int nFam = crsFam.size(); for (int j = 0; j < nFam; j++) - c[k]->addStageCourse(i, crsFam[j]->getId()); + c[k]->addStageCourse(i, crsFam[j]->getId(), -1); } assigned = true; @@ -667,7 +667,7 @@ void IOF30Interface::classAssignmentObsolete(gdioutput &gdi, xmlList &xAssignmen for (int i = 0; i < nl; i++) { c[k]->clearStageCourses(i); for (int j = 0; j < nCrs; j++) - c[k]->addStageCourse(i, crs[(j + i)%nCrs]->getId()); + c[k]->addStageCourse(i, crs[(j + i)%nCrs]->getId(), -1); } } } @@ -739,6 +739,8 @@ void IOF30Interface::readEntryList(gdioutput &gdi, xmlobject &xo, bool removeNon int &entRead, int &entFail, int &entRemoved) { string ver; entRemoved = 0; + bool wasEmpty = oe.getNumRunners() == 0; + xo.getObjectString("iofVersion", ver); if (!ver.empty() && ver > "3.0") gdi.addString("", 0, "Varning, okänd XML-version X#" + ver); @@ -962,8 +964,95 @@ void IOF30Interface::readEntryList(gdioutput &gdi, xmlobject &xo, bool removeNon if (!rids.empty()) oe.removeRunner(rids); } + + if (wasEmpty) { + vector allCls; + oe.getClasses(allCls, false); + set fees; + set redFees; + set factor; + for (pClass cls : allCls) { + int cf = cls->getDCI().getInt("ClassFee"); + int cfRed = cls->getDCI().getInt("ClassFeeRed"); + if (cf > 0) + fees.insert(cf); + + if (cfRed != 0 && cfRed != cf) + redFees.insert(cfRed); + + int cfLate = cls->getDCI().getInt("HighClassFee"); + + if (cfLate > cf && cf > 0) { + factor.insert(double(cfLate) / double(cf)); + } + } + + int youthFee = numeric_limits::max(); + if (!fees.empty()) + youthFee = min(youthFee, *fees.begin()); + if (!redFees.empty()) + youthFee = min(youthFee, *redFees.begin()); + + int eliteFee = numeric_limits::max(); + if (!fees.empty()) + eliteFee = *fees.rbegin(); + + int normalFee = eliteFee; + for (int f : fees) { + if (f != youthFee && f != eliteFee) { + normalFee = f; + } + } + + if (youthFee != numeric_limits::max()) { + oe.getDI().setInt("YouthFee", youthFee); + } + + if (eliteFee != numeric_limits::max()) { + oe.getDI().setInt("EliteFee", eliteFee); + } + + if (normalFee != numeric_limits::max()) { + oe.getDI().setInt("EntryFee", normalFee); + } + + if (factor.size() > 0) { + double f = *factor.rbegin(); + wstring fs = std::to_wstring(int((f - 1.0) * 100.0)) + L" %"; + oe.getDI().setString("LateEntryFactor", fs); + } + } } +void IOF30Interface::readServiceRequestList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail) { + string ver; + xo.getObjectString("iofVersion", ver); + if (!ver.empty() && ver > "3.0") + gdi.addString("", 0, "Varning, okänd XML-version X#" + ver); + + xmlList req; + xo.getObjects("PersonServiceRequest", req); + entrySourceId = 0; + + for (auto &rx : req) { + xmlobject xPers = rx.getObject("Person"); + pRunner r = 0; + if (xPers) + r = readPerson(gdi, xPers); + + if (r) { + auto xreq = rx.getObject("ServiceRequest"); + if (xreq) { + auto xServ = xreq.getObject("Service"); + string type; + if (xServ && xServ.getObjectString("type", type)=="StartGroup") { + int id = xServ.getObjectInt("Id"); + r->getDI().setInt("Heat", id); + } + } + } + } +} void IOF30Interface::readStartList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail) { string ver; @@ -1149,10 +1238,13 @@ void IOF30Interface::readEvent(gdioutput &gdi, const xmlobject &xo, if (date) { wstring dateStr; date.getObjectString("Date", dateStr); - oe.setDate(dateStr); wstring timeStr; date.getObjectString("Time", timeStr); if (!timeStr.empty()) { + wstring tDate, tTime; + getLocalDateTime(dateStr, timeStr, tDate, tTime); + dateStr.swap(tDate); + timeStr.swap(tTime); int t = convertAbsoluteTimeISO(timeStr); if (t >= 0 && oe.getNumRunners() == 0) { int zt = t - 3600; @@ -1161,6 +1253,9 @@ void IOF30Interface::readEvent(gdioutput &gdi, const xmlobject &xo, oe.setZeroTime(formatTimeHMS(zt)); } } + + oe.setDate(dateStr); + //oe.setZeroTime(...); } @@ -1346,12 +1441,12 @@ pTeam IOF30Interface::readTeamEntry(gdioutput &gdi, xmlobject &xTeam, if (newTeam) { wstring entryTime; xTeam.getObjectString("EntryTime", entryTime); - di.setDate("EntryDate", entryTime); - size_t tpos = entryTime.find_first_of(L"T"); - if (tpos != -1) { - wstring timeString = entryTime.substr(tpos+1); - int t = convertAbsoluteTimeISO(timeString); + wstring date, time; + getLocalDateTime(entryTime, date, time); + di.setDate("EntryDate", date); + if (time.length() > 0) { + int t = convertAbsoluteTimeISO(time); if (t >= 0) di.setInt("EntryTime", t); } @@ -1450,7 +1545,8 @@ pTeam IOF30Interface::getCreateTeam(gdioutput &gdi, const xmlobject &xTeam, bool if (!t) return 0; - t->setEntrySource(entrySourceId); + if (entrySourceId > 0) + t->setEntrySource(entrySourceId); t->flagEntryTouched(true); if (t->getName().empty() || !t->hasFlag(oAbstractRunner::FlagUpdateName)) t->setName(name, false); @@ -1608,16 +1704,16 @@ pRunner IOF30Interface::readPersonEntry(gdioutput &gdi, xmlobject &xo, pTeam tea wstring entryTime; xo.getObjectString("EntryTime", entryTime); - di.setDate("EntryDate", entryTime); - size_t tpos = entryTime.find_first_of(L"T"); - if (tpos != -1) { - wstring timeString = entryTime.substr(tpos+1); - int t = convertAbsoluteTimeISO(timeString); + + wstring date, time; + getLocalDateTime(entryTime, date, time); + di.setDate("EntryDate", date); + if (time.length() > 0) { + int t = convertAbsoluteTimeISO(time); if (t >= 0) di.setInt("EntryTime", t); } - - + double fee = 0, paid = 0, taxable = 0, percentage = 0; wstring currency; xmlList xAssigned; @@ -1836,7 +1932,8 @@ pRunner IOF30Interface::readPerson(gdioutput &gdi, const xmlobject &person) { } } - r->setEntrySource(entrySourceId); + if (entrySourceId > 0) + r->setEntrySource(entrySourceId); r->flagEntryTouched(true); if (!r->hasFlag(oAbstractRunner::FlagUpdateName)) { @@ -2115,16 +2212,20 @@ void IOF30Interface::getAgeLevels(const vector &fees, const vector redIx = ix[k]; if (fees[normalIx] < fees[ix[k]]) normalIx = ix[k]; + } + for (size_t k = 0; k < ix.size(); k++) { const wstring &to = fees[ix[k]].toBirthDate; const wstring &from = fees[ix[k]].fromBirthDate; - if (!from.empty() && (youthLimit.empty() || youthLimit > from)) + if (!from.empty() && (youthLimit.empty() || youthLimit > from) && fees[ix[k]].fee == fees[redIx].fee) youthLimit = from; - if (!to.empty() && (seniorLimit.empty() || seniorLimit > to)) + if (!to.empty() && (seniorLimit.empty() || seniorLimit > to) && fees[ix[k]].fee == fees[redIx].fee) seniorLimit = to; } + + } } @@ -2395,6 +2496,123 @@ int IOF30Interface::parseISO8601Time(const xmlobject &xo) { return oe.getRelativeTime(date, time, zone); } +void IOF30Interface::getLocalDateTime(const string &date, const string &time, + string &dateOut, string &timeOut) { + int zIx = -1; + for (size_t k = 0; k < time.length(); k++) { + if (time[k] == '+' || time[k] == '-' || time[k] == 'Z') { + if (zIx == -1) + zIx = k; + else; + // Bad format + } + } + + if (zIx == -1) { + dateOut = date; + timeOut = time; + return; + } + + string timePart = time.substr(0, zIx); + string zone = time.substr(zIx); + wstring wTime(timePart.begin(), timePart.end()); + + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + + int atime = convertAbsoluteTimeISO(wTime); + int idate = convertDateYMS(date, st, true); + if (idate != -1) { + if (zone == "Z" || zone == "z") { + st.wHour = atime / 3600; + st.wMinute = (atime / 60) % 60; + st.wSecond = atime % 60; + + SYSTEMTIME localTime; + memset(&localTime, 0, sizeof(SYSTEMTIME)); + SystemTimeToTzSpecificLocalTime(0, &st, &localTime); + + char bf[64]; + sprintf(bf, "%02d:%02d:%02d", localTime.wHour, localTime.wMinute, localTime.wSecond); + timeOut = bf; + sprintf(bf, "%d-%02d-%02d", localTime.wYear, localTime.wMonth, localTime.wDay); + dateOut = bf; + } + else { + dateOut = date; + timeOut = time; + } + } +} + +void IOF30Interface::getLocalDateTime(const wstring &datetime, wstring &dateOut, wstring &timeOut) { + size_t t = datetime.find_first_of('T'); + if (t != dateOut.npos) { + wstring date = datetime.substr(0, t); + wstring time = datetime.substr(t + 1); + getLocalDateTime(date, time, dateOut, timeOut); + } + else { + dateOut = datetime; + timeOut = L""; + } +} + +void IOF30Interface::getLocalDateTime(const wstring &date, const wstring &time, + wstring &dateOut, wstring &timeOut) { + int zIx = -1; + for (size_t k = 0; k < time.length(); k++) { + if (time[k] == '+' || time[k] == '-' || time[k] == 'Z') { + if (zIx == -1) + zIx = k; + else; + // Bad format + } + } + + if (zIx == -1) { + dateOut = date; + timeOut = time; + return; + } + + wstring timePart = time.substr(0, zIx); + wstring zone = time.substr(zIx); + wstring wTime(timePart.begin(), timePart.end()); + + SYSTEMTIME st; + memset(&st, 0, sizeof(SYSTEMTIME)); + + int atime = convertAbsoluteTimeISO(wTime); + int idate = convertDateYMS(date, st, true); + if (idate != -1) { + if (zone == L"Z" || zone == L"z") { + st.wHour = atime / 3600; + st.wMinute = (atime / 60) % 60; + st.wSecond = atime % 60; + + SYSTEMTIME localTime; + memset(&localTime, 0, sizeof(SYSTEMTIME)); + SystemTimeToTzSpecificLocalTime(0, &st, &localTime); + + atime = localTime.wHour * 3600 + localTime.wMinute * 60 + localTime.wSecond; + wchar_t bf[64]; + wsprintf(bf, L"%02d:%02d:%02d", localTime.wHour, localTime.wMinute, localTime.wSecond); + timeOut = bf; + wsprintf(bf, L"%d-%02d-%02d", localTime.wYear, localTime.wMonth, localTime.wDay); + dateOut = bf; + //dateOut = itow(localTime.wYear) + L"-" + itow(localTime.wMonth) + L"-" + itow(localTime.wDay); + //timeOut = itow(localTime.wHour) + L":" + itow(localTime.wMinute) + L":" + itow(localTime.wSecond); + } + else { + dateOut = date; + timeOut = time; + } + } +} + + void IOF30Interface::getProps(vector &props) const { props.push_back(L"xmlns"); props.push_back(L"http://www.orienteering.org/datastandard/3.0"); @@ -3601,7 +3819,7 @@ void IOF30Interface::bindClassCourse(oClass &pc, const vector< vector > for (size_t k = 0; k < crs.size(); k++) { pc.clearStageCourses(k); for (size_t j = 0; j < crs[k].size(); j++) { - pc.addStageCourse(k, crs[k][j]->getId()); + pc.addStageCourse(k, crs[k][j]->getId(), -1); } } } diff --git a/code/iof30interface.h b/code/iof30interface.h index ef66d59..288a5d4 100644 --- a/code/iof30interface.h +++ b/code/iof30interface.h @@ -274,6 +274,13 @@ public: IOF30Interface(oEvent *oe, bool forceSplitFee); virtual ~IOF30Interface() {} + static void getLocalDateTime(const wstring &datetime, wstring &dateOut, wstring &timeOut); + + static void getLocalDateTime(const wstring &date, const wstring &time, + wstring &dateOut, wstring &timeOut); + static void getLocalDateTime(const string &date, const string &time, + string &dateOut, string &timeOut); + void getIdTypes(vector &types); void setPreferredIdType(const string &type); @@ -287,6 +294,8 @@ public: void readStartList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail); + void readServiceRequestList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail); + void readClassList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail); void readCompetitorList(gdioutput &gdi, const xmlobject &xo, int &personCount); diff --git a/code/meos.cpp b/code/meos.cpp index a51e4fb..63bb34d 100644 --- a/code/meos.cpp +++ b/code/meos.cpp @@ -1214,6 +1214,13 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (autoTask) autoTask->synchronize(gdi_extra); break; + + case WM_USER + 5: + if (gdi_main) + gdi_main->addInfoBox("ainfo", L"info:advanceinfo", 10000); + + break; + case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); diff --git a/code/meosversion.cpp b/code/meosversion.cpp index 255dd37..90c2659 100644 --- a/code/meosversion.cpp +++ b/code/meosversion.cpp @@ -30,7 +30,7 @@ //V35: abcdef //V36: abcdef int getMeosBuild() { - string revision("$Rev: 859 $"); + string revision("$Rev: 895 $"); return 174 + atoi(revision.substr(5, string::npos).c_str()); } @@ -42,12 +42,12 @@ int getMeosBuild() { //V33: abcdefghij //V34: abcdfge wstring getMeosDate() { - wstring date(L"$Date: 2019-03-19 21:52:12 +0100 (ti, 19 mar 2019) $"); + wstring date(L"$Date: 2019-05-11 07:26:35 +0200 (lö, 11 maj 2019) $"); return date.substr(7,10); } wstring getBuildType() { - return L"RC1"; // No parantheses (...) + return L""; // No parantheses (...) } wstring getMajorVersion() { @@ -128,5 +128,7 @@ void getSupporters(vector &supp, vector &developSupp) supp.emplace_back(L"Ligue PACA"); supp.emplace_back(L"SC vebr-sport"); supp.emplace_back(L"IP Skogen Göteborg"); + supp.emplace_back(L"Smedjebackens Orientering"); + supp.emplace_back(L"Gudhems IF"); reverse(supp.begin(), supp.end()); } diff --git a/code/metalist.cpp b/code/metalist.cpp index 3dae1e5..8f8fef0 100644 --- a/code/metalist.cpp +++ b/code/metalist.cpp @@ -1654,6 +1654,8 @@ void MetaList::initSymbols() { typeToSymbol[lClassStartTimeRange] = L"StartTimeForClassRange"; typeToSymbol[lClassLength] = L"ClassLength"; typeToSymbol[lClassResultFraction] = L"ClassResultFraction"; + typeToSymbol[lClassAvailableMaps] = L"ClassAvailableMaps"; + typeToSymbol[lClassTotalMaps] = L"ClassTotalMaps"; typeToSymbol[lCourseLength] = L"CourseLength"; typeToSymbol[lCourseName] = L"CourseName"; typeToSymbol[lCourseClimb] = L"CourseClimb"; @@ -1712,6 +1714,7 @@ void MetaList::initSymbols() { typeToSymbol[lRunnerNationality] = L"RunnerNationality"; typeToSymbol[lRunnerPhone] = L"RunnerPhone"; typeToSymbol[lRunnerFee] = L"RunnerFee"; + typeToSymbol[lRunnerExpectedFee] = L"RunnerExpectedFee"; typeToSymbol[lRunnerPaid] = L"RunnerPaid"; typeToSymbol[lRunnerPayMethod] = L"RunnerPayMethod"; typeToSymbol[lRunnerEntryDate] = L"RunnerEntryDate"; @@ -1881,6 +1884,7 @@ void MetaList::initSymbols() { filterToSymbol[EFilterHasNoCard] = "FilterNoCard"; filterToSymbol[EFilterAnyResult] = "FilterAnyResult"; filterToSymbol[EFilterAPIEntry] = "EFilterAPIEntry"; + filterToSymbol[EFilterWrongFee] = "EFilterWrongFee"; for (map::iterator it = filterToSymbol.begin(); it != filterToSymbol.end(); ++it) { diff --git a/code/methodeditor.cpp b/code/methodeditor.cpp index 068862b..144ca4c 100644 --- a/code/methodeditor.cpp +++ b/code/methodeditor.cpp @@ -536,7 +536,7 @@ int MethodEditor::methodCb(gdioutput &gdi, int type, BaseInfo &data) { wstring str; try { st = currentResult->deduceStatus(*rr[k]); - str = oe->formatStatus(st); + str = oe->formatStatus(st, false); } catch (meosException &ex) { err = ex.wwhat(); @@ -638,7 +638,7 @@ int MethodEditor::methodCb(gdioutput &gdi, int type, BaseInfo &data) { wstring str; try { st = currentResult->deduceStatus(*tr[k]); - str = oe->formatStatus(st); + str = oe->formatStatus(st, false); } catch (meosException &ex) { err = ex.wwhat(); @@ -964,7 +964,7 @@ void MethodEditor::debug(gdioutput &gdi_in, int id, bool isTeam) { st = currentResult->deduceStatus(r); currentResult->debugDumpVariables(gdi, true); - gdi.addStringUT(1, L"ComputedStatus: " + oe->formatStatus(st)).setColor(colorGreen); + gdi.addStringUT(1, L"ComputedStatus: " + oe->formatStatus(st, false)).setColor(colorGreen); } catch (meosException &ex) { currentResult->debugDumpVariables(gdi, true); @@ -1018,7 +1018,7 @@ void MethodEditor::debug(gdioutput &gdi_in, int id, bool isTeam) { st = currentResult->deduceStatus(t); currentResult->debugDumpVariables(gdi, true); - gdi.addStringUT(1, L"ComputedStatus: " + oe->formatStatus(st)).setColor(colorGreen); + gdi.addStringUT(1, L"ComputedStatus: " + oe->formatStatus(st, false)).setColor(colorGreen); } catch (meosException &ex) { currentResult->debugDumpVariables(gdi, true); diff --git a/code/oBase.cpp b/code/oBase.cpp index 2468994..de8384f 100644 --- a/code/oBase.cpp +++ b/code/oBase.cpp @@ -44,8 +44,7 @@ static char THIS_FILE[]=__FILE__; char RunnerStatusOrderMap[100]; -oBase::oBase(oEvent *poe) -{ +oBase::oBase(oEvent *poe) { Removed = false; oe = poe; Id = 0; @@ -57,8 +56,14 @@ oBase::oBase(oEvent *poe) localObject = false; } -oBase::~oBase() -{ +oBase::~oBase(){ + if (myReference) + myReference->ref = nullptr; +} + +void oBase::remove() { + if (myReference) + myReference->ref = nullptr; } bool oBase::synchronize(bool writeOnly) diff --git a/code/oBase.h b/code/oBase.h index 1aa604c..dda3853 100644 --- a/code/oBase.h +++ b/code/oBase.h @@ -34,6 +34,7 @@ #include "TimeStamp.h" #include "stdafx.h" #include +#include class oEvent; class gdioutput; @@ -72,8 +73,18 @@ enum SortOrder {ClassStartTime, Custom, SortEnumLastItem}; -class oBase -{ +class oBase { +public: + class oBaseReference { + private: + oBase * ref = nullptr; + public: + oBase * get() { + return ref; + } + + friend class oBase; + }; private: void storeChangeStatus() {reChanged = changed;} void resetChangeStatus() {changed &= reChanged;} @@ -83,6 +94,7 @@ private: const static unsigned long long BaseGenStringFlag = 1ull << 63; const static unsigned long long Base36StringFlag = 1ull << 62; const static unsigned long long ExtStringMask = ~(BaseGenStringFlag|Base36StringFlag); + shared_ptr myReference; protected: int Id; @@ -118,6 +130,15 @@ protected: public: + // Get a safe reference to this object + const shared_ptr &getReference() { + if (!myReference) { + myReference = make_shared(); + myReference->ref = this; + } + return myReference; + } + // Returns true if the object is local, not stored in DB/On disc bool isLocalObject() { return localObject; } diff --git a/code/oCard.cpp b/code/oCard.cpp index 0a61277..0c1c7b1 100644 --- a/code/oCard.cpp +++ b/code/oCard.cpp @@ -706,16 +706,10 @@ void oCard::getPunches(vector &punchesOut) const { } } -bool oCard::comparePunchTime(oPunch *p1, oPunch *p2) { - return p1->Time < p2->Time; -} - void oCard::setupFromRadioPunches(oRunner &r) { oe->synchronizeList(oListId::oLPunchId); vector p; - oe->getPunchesForRunner(r.getId(), p); - - sort(p.begin(), p.end(), comparePunchTime); + oe->getPunchesForRunner(r.getId(), true, p); for (size_t k = 0; k < p.size(); k++) addPunch(p[k]->Type, p[k]->Time, 0); diff --git a/code/oClass.cpp b/code/oClass.cpp index d2efaa3..3685af7 100644 --- a/code/oClass.cpp +++ b/code/oClass.cpp @@ -644,21 +644,44 @@ bool oClass::fillStageCourses(gdioutput &gdi, int stage, return true; } -bool oClass::addStageCourse(int iStage, int courseId) +bool oClass::addStageCourse(int iStage, int courseId, int index) { - return addStageCourse(iStage, oe->getCourse(courseId)); + return addStageCourse(iStage, oe->getCourse(courseId), index); } -bool oClass::addStageCourse(int iStage, pCourse pc) +bool oClass::addStageCourse(int iStage, pCourse pc, int index) { if (unsigned(iStage)>=MultiCourse.size()) return false; - vector &Stage=MultiCourse[iStage]; + vector &stage=MultiCourse[iStage]; if (pc) { tCoursesChanged = true; - Stage.push_back(pc); + if (index == -1 || size_t(index) >= stage.size()) + stage.push_back(pc); + else { + stage.insert(stage.begin() + index, pc); + } + updateChanged(); + return true; + } + return false; +} + +bool oClass::moveStageCourse(int stage, int index, int offset) { + if (unsigned(stage) >= MultiCourse.size()) + return false; + + vector &stages = MultiCourse[stage]; + + if (offset == -1 && size_t(index) < stages.size() && index > 0) { + swap(stages[index - 1], stages[index]); + updateChanged(); + return true; + } + else if (offset == 1 && size_t(index + 1) < stages.size() && index >= 0) { + swap(stages[index + 1], stages[index]); updateChanged(); return true; } @@ -2622,11 +2645,8 @@ int oClass::getNumRemainingMaps(bool forceRecalculate) const { numMaps = Course->tMapsRemaining; else numMaps = min(numMaps, Course->tMapsRemaining); - - return numMaps; - } - else - return numMaps; + } + return numMaps; } void oClass::setNumberMaps(int nm) { @@ -2648,39 +2668,19 @@ int oClass::getNumberMaps(bool rawAttribute) const { void oEvent::getStartBlocks(vector &blocks, vector &starts) const { oClassList::const_iterator it; - map bs; + set> bs; for (it = Classes.begin(); it != Classes.end(); ++it) { if (it->isRemoved()) continue; - map::iterator v = bs.find(it->getBlock()); - - if (v!=bs.end() && v->first!=0 && v->second!=it->getStart()) { - wstring msg = L"Ett startblock spänner över flera starter: X/Y#" + it->getStart() + L"#" + v->second; - throw meosException(msg.c_str()); - } - bs[it->getBlock()] = it->getStart(); + + bs.emplace(it->getStart(), it->getBlock()); } - blocks.clear(); starts.clear(); - if (bs.size() > 1) { - for (map::iterator v = bs.begin(); v != bs.end(); ++v) { - blocks.push_back(v->first); - starts.push_back(v->second); - } - } - else if (bs.size() == 1) { - set s; - for (it = Classes.begin(); it != Classes.end(); ++it) { - if (it->isRemoved()) - continue; - s.insert(it->getStart()); - } - for (set::iterator v = s.begin(); v != s.end(); ++v) { - blocks.push_back(bs.begin()->first); - starts.push_back(*v); - } + for (auto &v : bs) { + blocks.push_back(v.second); + starts.push_back(v.first); } } @@ -2861,20 +2861,24 @@ void oClass::addClassDefaultFee(bool resetFee) { } } -void oClass::reinitialize() { - int ix = getDI().getInt("SortIndex"); +void oClass::reinitialize(bool force) const { + if (!force && isInitialized) + return; + isInitialized = true; // Prevent recursion + + int ix = getDCI().getInt("SortIndex"); if (ix == 0) { ix = getSortIndex(getId()*10); - getDI().setInt("SortIndex", ix); + const_cast(this)->getDI().setInt("SortIndex", ix); } tSortIndex = ix; - tMaxTime = getDI().getInt("MaxTime"); + tMaxTime = getDCI().getInt("MaxTime"); if (tMaxTime == 0 && oe) { tMaxTime = oe->getMaximalTime(); } - wstring wInfo = getDI().getString("Qualification"); + wstring wInfo = getDCI().getString("Qualification"); if (!wInfo.empty()) { if (qualificatonFinal && !qualificatonFinal->matchSerialization(wInfo)) clearQualificationFinal(); @@ -2897,7 +2901,7 @@ void oClass::reinitialize() { tIgnoreStartPunch = -1; } -void oClass::clearQualificationFinal() { +void oClass::clearQualificationFinal() const { if (!qualificatonFinal) return; @@ -2911,15 +2915,12 @@ void oClass::clearQualificationFinal() { qualificatonFinal.reset(); } - -void oEvent::reinitializeClasses() -{ - for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) - it->reinitialize(); +void oEvent::reinitializeClasses() const { + for (auto &c : Classes) + c.reinitialize(true); } -int oClass::getSortIndex(int candidate) -{ +int oClass::getSortIndex(int candidate) const { int major = numeric_limits::max(); int minor = 0; @@ -3340,6 +3341,7 @@ bool oClass::canRemove() const } int oClass::getMaximumRunnerTime() const { + reinitialize(false); return tMaxTime; } @@ -3821,10 +3823,10 @@ pair oClass::autoForking(const vector< vector > &inputCourses) { lastSet = j; for (size_t k = 0; k < courseMatrix[j].size(); k++) { if (k < fperm.size()) { - addStageCourse(j, courseMatrix[j][fperm[k]]); + addStageCourse(j, courseMatrix[j][fperm[k]], -1); } else { - addStageCourse(j, courseMatrix[j][k]); + addStageCourse(j, courseMatrix[j][k], -1); } } } @@ -4402,6 +4404,7 @@ void oClass::configureInstance(int instance, bool allowCreation) const { } int oClass::getNumQualificationFinalClasses() const { + reinitialize(false); if (qualificatonFinal) return qualificatonFinal->getNumClasses()+1; return 0; diff --git a/code/oClass.h b/code/oClass.h index 2bab018..7321032 100644 --- a/code/oClass.h +++ b/code/oClass.h @@ -200,8 +200,10 @@ protected: mutable int tIgnoreStartPunch; // Sort classes for this index - int tSortIndex; - int tMaxTime; + mutable int tSortIndex; + mutable int tMaxTime; + + mutable bool isInitialized = false; // True when courses was changed on this client. Used to update course pool bindings bool tCoursesChanged; @@ -244,7 +246,7 @@ protected: void exportIOFStart(xmlparser &xml); /** Setup transient data */ - void reinitialize(); + void reinitialize(bool force) const; /** Recalculate derived data */ void apply(); @@ -264,7 +266,7 @@ protected: mutable vector tResultInfo; /** Get/calculate sort index from candidate */ - int getSortIndex(int candidate); + int getSortIndex(int candidate) const; /** Get internal data buffers for DI */ oDataContainer &getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const; @@ -276,7 +278,7 @@ protected: mutable vector virtualClasses; pClass parentClass; - shared_ptr qualificatonFinal; + mutable shared_ptr qualificatonFinal; int tMapsRemaining; mutable int tMapsUsed; @@ -288,9 +290,12 @@ public: /** The master class in a qualification/final scheme. */ const pClass getParentClass() const { return parentClass; } - const QualificationFinal *getQualificationFinal() const { return qualificatonFinal.get(); } + const QualificationFinal *getQualificationFinal() const { + reinitialize(false); + return qualificatonFinal.get(); + } - void clearQualificationFinal(); + void clearQualificationFinal() const; bool isQualificationFinalClass() const { return parentClass && parentClass->isQualificationFinalBaseClass(); @@ -565,9 +570,10 @@ public: int getCourseId() const {if (Course) return Course->getId(); else return 0;} void setCourse(pCourse c); - bool addStageCourse(int stage, int courseId); - bool addStageCourse(int stage, pCourse pc); + bool addStageCourse(int stage, int courseId, int index); + bool addStageCourse(int stage, pCourse pc, int index); void clearStageCourses(int stage); + bool moveStageCourse(int stage, int index, int offset); bool removeStageCourse(int stage, int courseId, int position); diff --git a/code/oClub.cpp b/code/oClub.cpp index 0dfebc3..e404e27 100644 --- a/code/oClub.cpp +++ b/code/oClub.cpp @@ -317,12 +317,13 @@ Table *oEvent::getClubsTB()//Table mode void oEvent::generateClubTableData(Table &table, oClub *addClub) { - oe->setupClubInfoData(); if (addClub) { addClub->addTableRow(table); return; } synchronizeList(oListId::oLClubId); + oe->setupClubInfoData(); + oClubList::iterator it; for (it=Clubs.begin(); it != Clubs.end(); ++it){ @@ -919,7 +920,9 @@ void oEvent::printInvoices(gdioutput &gdi, InvoicePrintType type, if (!it->isRemoved() && clubId.count(it->getId()) > 0) { gdi.addStringUT(yp, 50, fontMedium, itos(it->getDCI().getInt("InvoiceNo"))); - gdi.addStringUT(yp, 240, textRight|fontMedium, itos(it->getId())); + if (it->getExtIdentifier() != 0) + gdi.addStringUT(yp, 240, textRight|fontMedium, it->getExtIdentifierString()); + gdi.addStringUT(yp, 250, fontMedium, it->getName()); gdi.addStringUT(yp, 550, fontMedium|textRight, oe->formatCurrency(fees[k])); gdi.addStringUT(yp, 620, fontMedium|textRight, oe->formatCurrency(vpaid[k])); diff --git a/code/oCourse.cpp b/code/oCourse.cpp index 2871a4f..b5fb4c1 100644 --- a/code/oCourse.cpp +++ b/code/oCourse.cpp @@ -904,6 +904,7 @@ void oCourse::clearCache() const { cachedHasRogaining = 0; cachedControlOrdinal.clear(); cacheDataRevision = oe->dataRevision; + oe->tCalcNumMapsDataRevision = -1; tMapsUsed = -1; tMapsUsedNoVacant = -1; } diff --git a/code/oCourse.h b/code/oCourse.h index 0f907a9..58f3177 100644 --- a/code/oCourse.h +++ b/code/oCourse.h @@ -68,8 +68,8 @@ protected: vector legLengths; int tMapsRemaining; - mutable int tMapsUsed; - mutable int tMapsUsedNoVacant; + mutable int tMapsUsed = -1; + mutable int tMapsUsedNoVacant = -1; // Get an identity sum based on controls int getIdSum(int nControls); diff --git a/code/oEvent.cpp b/code/oEvent.cpp index 361531f..89286cc 100644 --- a/code/oEvent.cpp +++ b/code/oEvent.cpp @@ -650,16 +650,24 @@ pControl oEvent::getControl(int Id) const { return const_cast(this)->getControl(Id, false); } +pControl oEvent::getControlByType(int type) const { + for (auto &c : Controls) { + if (!c.isRemoved() && c.getFirstNumber() == type) + return pControl(&c); + } + return nullptr; +} + pControl oEvent::getControl(int Id, bool create) { oControlList::const_iterator it; for (it=Controls.begin(); it != Controls.end(); ++it) { - if (it->Id==Id) + if (it->Id==Id && !it->isRemoved()) return pControl(&*it); } if (!create || Id<=0) - return 0; + return nullptr; //Not found. Auto add... return addControl(Id, Id, L""); @@ -1420,14 +1428,7 @@ pRunner oEvent::dbLookUpById(__int64 extId) const sRunner.setTemporary(); RunnerWDBEntry *dbr = runnerDB->getRunnerById(int(extId)); if (dbr != 0) { - sRunner.init(*dbr); - /*dbr->getName(sRunner.Name); - sRunner.CardNo = dbr->cardNo; - sRunner.Club = runnerDB->getClub(dbr->clubNo); - sRunner.getDI().setString("Nationality", dbr->getNationality()); - sRunner.getDI().setInt("BirthYear", dbr->getBirthYear()); - sRunner.getDI().setString("Sex", dbr->getSex()); - sRunner.setExtIdentifier(dbr->getExtId());*/ + sRunner.init(*dbr, false); return &sRunner; } else @@ -1446,7 +1447,7 @@ pRunner oEvent::dbLookUpByCard(int cardNo) const if (dbr != 0) { dbr->getName(sRunner.sName); oRunner::getRealName(sRunner.sName, sRunner.tRealName); - sRunner.init(*dbr); + sRunner.init(*dbr, false); sRunner.cardNumber = cardNo; return &sRunner; } @@ -1482,14 +1483,7 @@ pRunner oEvent::dbLookUpByName(const wstring &name, int clubId, int classId, int RunnerWDBEntry *dbr = runnerDB->getRunnerByName(name, clubId, birthYear); if (dbr) { - sRunner.init(*dbr); - /* - dbr->getName(sRunner.Name); - sRunner.CardNo = dbr->cardNo; - sRunner.Club = runnerDB->getClub(dbr->clubNo); - sRunner.getDI().setString("Nationality", dbr->getNationality()); - sRunner.getDI().setInt("BirthYear", dbr->getBirthYear()); - sRunner.getDI().setString("Sex", dbr->getSex());*/ + sRunner.init(*dbr, false); sRunner.setExtIdentifier(int(dbr->getExtId())); return &sRunner; } @@ -2050,6 +2044,15 @@ bool oEvent::sortRunners(SortOrder so) { return true; } +bool oEvent::sortRunners(SortOrder so, vector &runners) const { + reinitializeClasses(); + auto oldSortOrder = CurrentSortOrder; + CurrentSortOrder = so; + sort(runners.begin(), runners.end(), [](const oRunner * &a, const oRunner * &b)->bool {return *a < *b; }); + CurrentSortOrder = oldSortOrder; + return true; +} + bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) { reinitializeClasses(); oTeamList::iterator it; @@ -2165,6 +2168,120 @@ bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) { return true; } +bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg, vector &teams) const { + reinitializeClasses(); + map classId2Linear; + if (so == ClassResult || so == ClassTotalResult || so == ClassTeamLegResult) { + bool totalResult = so == ClassTotalResult; + bool legResult = so == ClassTeamLegResult; + bool hasRunner = (leg == -1); + for (auto it : teams) { + int lg = leg; + int clsId = it->Class->getId(); + + if (leg >= 0 && !linearLeg && it->Class) { + map::iterator res = classId2Linear.find(it->Class->getId()); + if (res == classId2Linear.end()) { + unsigned linLegBase = it->Class->getLegNumberLinear(leg, 0); + while (linLegBase + 1 < it->Class->getNumStages()) { + if (it->Class->isParallel(linLegBase + 1) || it->Class->isOptional(linLegBase + 1)) + linLegBase++; + else + break; + } + lg = linLegBase; + //lg = linLegBase + it->Class->getNumParallel(linLegBase) - 1; + classId2Linear[clsId] = lg; + } + else { + lg = res->second; + } + } + + const int lastIndex = it->Class ? it->Class->getLastStageIndex() : 0; + lg = min(lg, lastIndex); + + + if (lg >= leg) + hasRunner = true; + if (legResult) { + pRunner r = it->getRunner(lg); + if (r) { + it->_sortTime = r->getRunningTime(); + it->_cachedStatus = r->getStatus(); + } + else { + it->_sortTime = 0; + it->_cachedStatus = StatusUnknown; + } + } + else { + it->_sortTime = it->getLegRunningTime(lg, totalResult) + it->getNumShortening(lg) * 3600 * 24 * 10; + it->_cachedStatus = it->getLegStatus(lg, totalResult); + + // Ensure number of restarts has effect on final result + if (lg == lastIndex) + it->_sortTime += it->tNumRestarts * 24 * 3600; + } + unsigned rawStatus = it->_cachedStatus; + it->_sortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0]; + + } + + if (!hasRunner) + return false; + + sort(teams.begin(), teams.end(), [](const oTeam * &a, const oTeam * &b)->bool {return oTeam::compareResult(*a, *b); }); + } + else if (so == ClassStartTime || so == ClassStartTimeClub) { + if (leg == -1) + leg = 0; + for (auto it : teams) { + it->_cachedStatus = StatusUnknown; + it->_sortStatus = 0; + it->_sortTime = it->getLegStartTime(leg); + if (it->_sortTime <= 0) + it->_sortStatus = 1; + } + if (so == ClassStartTime) + sort(teams.begin(), teams.end(), [](const oTeam * &a, const oTeam * &b)->bool {return oTeam::compareResult(*a, *b); }); + else + sort(teams.begin(), teams.end(), [](const oTeam * &a, const oTeam * &b)->bool {return oTeam::compareResultNoSno(*a, *b); }); + } + else if (so == ClassKnockoutTotalResult) { + for (auto it : teams) { + it->_cachedStatus = StatusUnknown; + it->_sortStatus = 0; + it->_sortTime = 0; + + // Count number of races with results + int numResult = 0; + int lastClassHeat = 0; + for (pRunner r : it->Runners) { + if (r && (r->prelStatusOK() || + (r->tStatus != StatusUnknown && r->tStatus != StatusDNS && r->tStatus != StatusCANCEL))) { + + if (r->Class && r->tLeg > 0 && r->Class->isQualificationFinalBaseClass() && r->getClassRef(true) == r->Class) + continue; // Skip if not qualified. + + numResult++; + lastClassHeat = r->getDCI().getInt("Heat"); + it->_cachedStatus = r->tStatus; + it->_sortTime = r->getRunningTime(); + } + } + if (lastClassHeat > 50 || lastClassHeat < 0) + lastClassHeat = 0; + + unsigned rawStatus = it->_cachedStatus; + it->_sortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0] - (numResult * 100 + lastClassHeat) * 1000; + + } + sort(teams.begin(), teams.end(), [](const oTeam * &a, const oTeam * &b)->bool {return oTeam::compareResult(*a, *b); }); + } + return true; +} + wstring oEvent::getZeroTime() const { return getAbsTime(0); @@ -2896,6 +3013,8 @@ void oEvent::generateInForestList(gdioutput &gdi, GUICALLBACK cb, GUICALLBACK cb typedef multimap::const_iterator TPunchIter; for (oFreePunchList::iterator it = punches.begin(); it != punches.end(); ++it) { + if (it->isRemoved() || it->isHiredCard()) + continue; punchHash.insert(make_pair(it->getCardNo(), &*it)); } @@ -3087,7 +3206,7 @@ void oEvent::generateMinuteStartlist(gdioutput &gdi) { for (size_t k=0;k0) - gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, ""); + gdi.addStringUT(gdi.getCY()-1, 0, pageNewChapter, ""); gdi.addStringUT(boldLarge|Capitalize, lang.tl(L"Minutstartlista", true) + makeDash(L" - ") + getName()); if (!starts[k].empty()) { @@ -3103,9 +3222,9 @@ void oEvent::generateMinuteStartlist(gdioutput &gdi) { sb.reserve(Runners.size()); int LastStartTime=-1; for (oRunnerList::iterator it=Runners.begin(); it != Runners.end(); ++it) { - if (it->Class && it->Class->getBlock()!=blocks[k]) + if (it->Class && it->Class->getBlock() != blocks[k]) continue; - if (it->Class && it->Class->getStart() != starts[k] ) + if (it->Class && it->Class->getStart() != starts[k]) continue; if (!it->Class && blocks[k]!=0) continue; @@ -3626,6 +3745,7 @@ void oEvent::clear() punchIndex.clear(); punches.clear(); cachedFirstStart.clear(); + hiredCardHash.clear(); updateFreeId(); @@ -3778,7 +3898,7 @@ void oEvent::reEvaluateAll(const set &cls, bool doSync) if (cls.empty() || cls.count(it->Id)) { it->clearSplitAnalysis(); it->resetLeaderTime(); - it->reinitialize(); + it->reinitialize(true); } } @@ -3877,7 +3997,7 @@ void oEvent::reEvaluateChanged() if (it->wasSQLChanged(-1, oPunch::PunchFinish)) { it->clearSplitAnalysis(); it->resetLeaderTime(); - it->reinitialize(); + it->reinitialize(true); resetClasses[it->getId()] = it->hasClassGlobalDependence(); } } @@ -4281,11 +4401,13 @@ void oEvent::addBib(int ClassId, int leg, const wstring &firstNumber) { if (cls == 0) throw meosException("Class not found"); - if (cls->getParentClass()) + if (cls->getParentClass()) { cls->getParentClass()->setBibMode(BibFree); - + cls->getParentClass()->synchronize(true); + } if (!firstNumber.empty()) { cls->setBibMode(BibFree); + cls->synchronize(true); wchar_t pattern[32]; int num = oClass::extractBibPattern(firstNumber, pattern); @@ -4536,6 +4658,8 @@ void oEvent::addAutoBib() { // Switch to free mode if bib set for subclass cls->getParentClass()->setBibMode(BibFree); cls->setBibMode(BibFree); + cls->getParentClass()->synchronize(true); + cls->synchronize(true); } for (size_t k = 0; k < rl.size(); k++) { if (pattern[0]) { @@ -4553,27 +4677,40 @@ void oEvent::addAutoBib() { } } -void oEvent::checkOrderIdMultipleCourses(int ClassId) -{ +void oEvent::checkOrderIdMultipleCourses(int ClassId) { sortRunners(ClassStartTime); int order=1; oRunnerList::iterator it; //Find first free order - for(it=Runners.begin(); it != Runners.end(); ++it){ - if (ClassId==0 || it->getClassId(false)==ClassId){ + for (it = Runners.begin(); it != Runners.end(); ++it) { + if (it->isRemoved()) + continue; + if (ClassId == 0 || it->getClassId(false) == ClassId) { it->synchronize();//Ensure we are up-to-date - order=max(order, it->StartNo); + order = max(order, it->StartNo); } } //Assign orders - for(it=Runners.begin(); it != Runners.end(); ++it){ - if (ClassId==0 || it->getClassId(false)==ClassId) - if (it->StartNo==0){ - it->StartNo=++order; - it->updateChanged(); //Mark as changed. - it->synchronize(); //Sync! + for (it = Runners.begin(); it != Runners.end(); ++it) { + if (it->isRemoved()) + continue; + if (it->getClassRef(true) && it->getClassRef(true)->lockedForking()) + continue; + if (ClassId == 0 || it->getClassId(false) == ClassId) + if (it->StartNo == 0) { + if (it->getTeam()) { + if (it->getTeam()->getStartNo() == 0) { + it->updateStartNo(++order); + } + else { + it->setStartNo(it->getTeam()->getStartNo(), false); + } + } + else { + it->updateStartNo(++order); + } } } } @@ -4817,7 +4954,7 @@ void oEvent::calcUseStartSeconds() } } -const wstring &oEvent::formatStatus(RunnerStatus status) +const wstring &oEvent::formatStatus(RunnerStatus status, bool forPrint) { const static wstring stats[9]={L"?", L"Godkänd", L"Ej start", L"Felst.", L"Utg.", L"Disk.", L"Maxtid", L"Deltar ej", L"Återbud[status]"}; @@ -4838,6 +4975,12 @@ const wstring &oEvent::formatStatus(RunnerStatus status) return lang.tl(stats[6]); case StatusNotCompetiting: return lang.tl(stats[7]); + case StatusUnknown: { + if (forPrint) + return formatTime(-1); + else + return stats[0]; + } default: return stats[0]; } @@ -5081,8 +5224,8 @@ pClass oEvent::generateTestClass(int nlegs, int nrunners, setupRelay(*cls, PPatrol, 2, start); int nCtrl=rand()%15+10; pCourse pc=generateTestCourse(nCtrl); - cls->addStageCourse(0, pc->getId()); - cls->addStageCourse(1, pc->getId()); + cls->addStageCourse(0, pc->getId(), -1); + cls->addStageCourse(1, pc->getId(), -1); } else if (nlegs>1 && nrunners==2) { setupRelay(*cls, PTwinRelay, nlegs, start); @@ -5093,7 +5236,7 @@ pClass oEvent::generateTestClass(int nlegs, int nrunners, for (int k=0;kaddStageCourse(k, cid[(k+j)%nlegs]); + cls->addStageCourse(k, cid[(k+j)%nlegs], -1); } else if (nlegs>1 && nrunners==nlegs) { setupRelay(*cls, PRelay, nlegs, start); @@ -5104,12 +5247,12 @@ pClass oEvent::generateTestClass(int nlegs, int nrunners, for (int k=0;kaddStageCourse(k, cid[(k+j)%nlegs]); + cls->addStageCourse(k, cid[(k+j)%nlegs], -1); } else if (nlegs>1 && nrunners==1) { setupRelay(*cls, PHunting, 2, start); - cls->addStageCourse(0, generateTestCourse(rand()%8+10)->getId()); - cls->addStageCourse(1, generateTestCourse(rand()%8+10)->getId()); + cls->addStageCourse(0, generateTestCourse(rand()%8+10)->getId(), -1); + cls->addStageCourse(1, generateTestCourse(rand()%8+10)->getId(), -1); } return cls; } @@ -5256,7 +5399,7 @@ void oEvent::getFreeImporter(oFreeImport &fi) } -void oEvent::fillFees(gdioutput &gdi, const string &name, bool withAuto) const { +void oEvent::fillFees(gdioutput &gdi, const string &name, bool onlyDirect, bool withAuto) const { gdi.clearList(name); set fees; @@ -5265,6 +5408,8 @@ void oEvent::fillFees(gdioutput &gdi, const string &name, bool withAuto) const { for (oClassList::const_iterator it = Classes.begin(); it != Classes.end(); ++it) { if (it->isRemoved()) continue; + if (onlyDirect && !it->getAllowQuickEntry()) + continue; f = it->getDCI().getInt("ClassFee"); if (f > 0) @@ -5284,19 +5429,22 @@ void oEvent::fillFees(gdioutput &gdi, const string &name, bool withAuto) const { fees.insert(f); } } + + if (fees.empty()) { + if (!onlyDirect) { + f = getDCI().getInt("EliteFee"); + if (f > 0) + fees.insert(f); + } - f = getDCI().getInt("EliteFee"); - if (f > 0) - fees.insert(f); - - f = getDCI().getInt("EntryFee"); - if (f > 0) - fees.insert(f); - - f = getDCI().getInt("YouthFee"); - if (f > 0) - fees.insert(f); + f = getDCI().getInt("EntryFee"); + if (f > 0) + fees.insert(f); + f = getDCI().getInt("YouthFee"); + if (f > 0) + fees.insert(f); + } vector< pair > ff; if (withAuto) ff.push_back(make_pair(lang.tl(L"Från klassen"), -1)); diff --git a/code/oEvent.h b/code/oEvent.h index 92dc7ab..d888791 100644 --- a/code/oEvent.h +++ b/code/oEvent.h @@ -351,7 +351,7 @@ protected: void updateFreeId(); void updateFreeId(oBase *ob); - SortOrder CurrentSortOrder; + mutable SortOrder CurrentSortOrder; list cinfo; list backupInfo; @@ -433,7 +433,7 @@ protected: void exportTeamSplits(xmlparser &xml, const set &classes, bool oldStylePatrol); /** Set up transient data in classes */ - void reinitializeClasses(); + void reinitializeClasses() const; /** Analyze the result status of each class*/ void analyzeClassResultStatus() const; @@ -656,7 +656,7 @@ public: pTeam findTeam(const wstring &s, int lastId, unordered_set &filter) const; pRunner findRunner(const wstring &s, int lastId, const unordered_set &inputFilter, unordered_set &filter) const; - static const wstring &formatStatus(RunnerStatus status); + static const wstring &formatStatus(RunnerStatus status, bool forPrint); inline bool useStartSeconds() const {return tUseStartSeconds;} void calcUseStartSeconds(); @@ -808,7 +808,14 @@ public: void getResultEvents(const set &classFilter, const set &controlFilter, vector &results) const; /** Compute results for split times while runners are on course.*/ - void computePreliminarySplitResults(const set &classes); + void computePreliminarySplitResults(const set &classes) const; + + /** Synchronizes to server and checks if there are hired card data*/ + bool hasHiredCardData(); + bool isHiredCard(int cardNo) const; + void setHiredCard(int cardNo, bool flag); + vector getHiredCards() const; + void clearHiredCards(); protected: // Returns hash key for punch based on control id, and leg. Class is marked as changed if oldHashKey != newHashKey. @@ -821,6 +828,9 @@ protected: mutable shared_ptr> cardToRunnerHash; unordered_multimap &getCardToRunner() const; + mutable set hiredCardHash; + mutable int tHiredCardHashDataRevision = -1; + int tClubDataRevision; int tCalcNumMapsDataRevision = -1; @@ -872,7 +882,7 @@ public: void removeFreePunch(int id); pFreePunch getPunch(int id) const; pFreePunch getPunch(int runnerId, int courseControlId, int card) const; - void getPunchesForRunner(int runnerId, vector &punches) const; + void getPunchesForRunner(int runnerId, bool sort, vector &punches) const; //Returns true if data is changed. bool autoSynchronizeLists(bool syncPunches); @@ -1011,8 +1021,8 @@ public: enum class ResultType {ClassResult, TotalResult, CourseResult, ClassCourseResult, PreliminarySplitResults}; - void calculateResults(const set &classes, ResultType result, bool includePreliminary = false); - void calculateRogainingResults(const set &classSelection); + void calculateResults(const set &classes, ResultType result, bool includePreliminary = false) const; + void calculateRogainingResults(const set &classSelection) const; void calculateResults(list &rl); void calculateTeamResults(bool totalMultiday); @@ -1021,9 +1031,15 @@ public: void calculateTeamResultAtControl(const set &classId, int leg, int controlId, bool totalResults); bool sortRunners(SortOrder so); + + bool sortRunners(SortOrder so, vector &runners) const; + /** If linear leg is true, leg is interpreted as actual leg numer, otherwise w.r.t to parallel legs. */ bool sortTeams(SortOrder so, int leg, bool linearLeg); + bool sortTeams(SortOrder so, int leg, bool linearLeg, vector &teams) const; + + pCard allocateCard(pRunner owner); /** Optimize the start order based on drawInfo. Result in cInfo */ @@ -1153,7 +1169,7 @@ public: void updateClubsFromDB(); void updateRunnersFromDB(); - void fillFees(gdioutput &gdi, const string &name, bool withAuto) const; + void fillFees(gdioutput &gdi, const string &name, bool onlyDirect, bool withAuto) const; wstring getAutoClassName() const; pClass addClass(const wstring &pname, int CourseId = 0, int classId = 0); pClass addClass(oClass &c); @@ -1214,6 +1230,7 @@ public: void calculateNumRemainingMaps(bool forceRecalculate); pControl getControl(int Id) const; + pControl getControlByType(int type) const; pControl getControl(int Id, bool create); enum ControlType {CTAll, CTRealControl, CTCourseControl}; diff --git a/code/oEventDraw.cpp b/code/oEventDraw.cpp index e8d2bf2..1808040 100644 --- a/code/oEventDraw.cpp +++ b/code/oEventDraw.cpp @@ -938,12 +938,13 @@ void oEvent::drawList(const vector &spec, throw std::exception("Det går endast att sätta in vakanser på sträcka 1."); if (size_t(spec[k].leg) < pc->legInfo.size()) { - pc->legInfo[spec[k].leg].startMethod = STDrawn; //Automatically change start method + pc->setStartType(spec[k].leg, STDrawn, true); //Automatically change start method } else if (spec[k].leg == -1) { for (size_t j = 0; j < pc->legInfo.size(); j++) - pc->legInfo[j].startMethod = STDrawn; //Automatically change start method + pc->setStartType(j, STDrawn, true); //Automatically change start method } + pc->synchronize(true); clsId2Ix[spec[k].classID] = k; if (!multiDay && spec[k].leg == 0 && pc->getParentClass() == 0) clsIdClearVac.insert(spec[k].classID); @@ -1080,20 +1081,26 @@ void oEvent::drawList(const vector &spec, } int minStartNo = Runners.size(); + vector> newStartNo; for(unsigned k=0;ksetStartTime(stimes[k], true, false, false); + runners[k]->synchronize(); minStartNo = min(minStartNo, runners[k]->getStartNo()); + newStartNo.emplace_back(stimes[k], k); } - CurrentSortOrder = SortByStartTime; - sort(runners.begin(), runners.end()); + sort(newStartNo.begin(), newStartNo.end()); + //CurrentSortOrder = SortByStartTime; + //sort(runners.begin(), runners.end()); if (minStartNo == 0) minStartNo = nextFreeStartNo + 1; for(size_t k=0; ksetStartNo(k+minStartNo, false); - runners[k]->synchronize(); + pClass pCls = runners[k]->getClassRef(true); + if (pCls && pCls->lockedForking() || runners[k]->getLegNumber() > 0) + continue; + runners[k]->updateStartNo(newStartNo[k].second + minStartNo); } nextFreeStartNo = max(nextFreeStartNo, minStartNo + stimes.size()); diff --git a/code/oEventResult.cpp b/code/oEventResult.cpp index 9578941..a71c684 100644 --- a/code/oEventResult.cpp +++ b/code/oEventResult.cpp @@ -41,8 +41,7 @@ #include "listeditor.h" -void oEvent::calculateSplitResults(int controlIdFrom, int controlIdTo) -{ +void oEvent::calculateSplitResults(int controlIdFrom, int controlIdTo) { oRunnerList::iterator it; for (it=Runners.begin(); it!=Runners.end(); ++it) { @@ -98,14 +97,14 @@ void oEvent::calculateSplitResults(int controlIdFrom, int controlIdTo) cTime=it->tempRT; - it->tPlace=vPlace; + it->tPlace.update(*this, vPlace); // XXX User other result container } else - it->tPlace=99000+it->tStatus; + it->tPlace.update(*this, 99000+it->tStatus); } } -void oEvent::calculateResults(const set &classes, ResultType resultType, bool includePreliminary) { +void oEvent::calculateResults(const set &classes, ResultType resultType, bool includePreliminary) const { if (resultType == ResultType::PreliminarySplitResults) { computePreliminarySplitResults(classes); return; @@ -113,15 +112,27 @@ void oEvent::calculateResults(const set &classes, ResultType resultType, bo const bool totalResults = resultType == ResultType::TotalResult; const bool courseResults = resultType == ResultType::CourseResult; const bool classCourseResults = resultType == ResultType::ClassCourseResult; + + bool all = classes.empty(); + vector runners; + runners.reserve(Runners.size()); + for (auto &r : Runners) { + if (r.isRemoved()) + continue; + + if (!all && !classes.count(r.getClassId(true))) + continue; + runners.push_back(&r); + } if (classCourseResults) - sortRunners(ClassCourseResult); + sortRunners(ClassCourseResult, runners); else if (courseResults) - sortRunners(CourseResult); + sortRunners(CourseResult, runners); else if (!totalResults) - sortRunners(ClassResult); + sortRunners(ClassResult, runners); else - sortRunners(ClassTotalResult); + sortRunners(ClassTotalResult, runners); oRunnerList::iterator it; @@ -133,9 +144,8 @@ void oEvent::calculateResults(const set &classes, ResultType resultType, bo int cLegEquClass = 0; bool invalidClass = false; bool useResults = false; - for (it=Runners.begin(); it != Runners.end(); ++it) { - if (it->isRemoved()) - continue; + for (auto it : runners) { + // Start new "class" if (classCourseResults) { const pCourse crs = it->getCourse(false); @@ -175,7 +185,7 @@ void oEvent::calculateResults(const set &classes, ResultType resultType, bo // Calculate results if (invalidClass) { it->tTotalPlace = 0; - it->tPlace = 0; + it->tPlace.update(*this, 0); } else if (!totalResults) { int tPlace = 0; @@ -197,7 +207,7 @@ void oEvent::calculateResults(const set &classes, ResultType resultType, bo tPlace = 99000 + it->tStatus; if (!classCourseResults) - it->tPlace = tPlace; + it->tPlace.update(*this, tPlace); else it->tCoursePlace = tPlace; } @@ -224,10 +234,22 @@ void oEvent::calculateResults(const set &classes, ResultType resultType, bo } } -void oEvent::calculateRogainingResults(const set &classSelection) { +void oEvent::calculateRogainingResults(const set &classSelection) const { const bool all = classSelection.empty(); - sortRunners(ClassPoints); - oRunnerList::iterator it; + vector runners; + runners.reserve(Runners.size()); + for (auto &r : Runners) { + if (r.isRemoved()) + continue; + + if (!all && !classSelection.count(r.getClassId(true))) + continue; + + if (r.Class && r.Class->isRogaining()) { + runners.push_back(&r); + } + } + sortRunners(ClassPoints, runners); int cClassId=-1; int cPlace = 0; @@ -238,13 +260,7 @@ void oEvent::calculateRogainingResults(const set &classSelection) { bool isRogaining = false; bool invalidClass = false; - for (it=Runners.begin(); it != Runners.end(); ++it) { - if (it->isRemoved()) - continue; - - if (!all && !classSelection.count(it->getClassId(false))) - continue; - + for (auto it : runners) { if (it->getClassId(true)!=cClassId || it->tDuplicateLeg!=cDuplicateLeg) { cClassId = it->getClassId(true); useResults = it->Class ? !it->Class->getNoTiming() : false; @@ -252,16 +268,12 @@ void oEvent::calculateRogainingResults(const set &classSelection) { vPlace = 0; cTime = numeric_limits::min(); cDuplicateLeg = it->tDuplicateLeg; - isRogaining = it->Class ? it->Class->isRogaining() : false; invalidClass = it->Class ? it->Class->getClassStatus() != oClass::Normal : false; } - if (!isRogaining) - continue; - if (invalidClass) { it->tTotalPlace = 0; - it->tPlace = 0; + it->tPlace.update(*this, 0); } else if (it->tStatus==StatusOK) { cPlace++; @@ -274,12 +286,12 @@ void oEvent::calculateRogainingResults(const set &classSelection) { cTime = cmpRes; if (useResults) - it->tPlace = vPlace; + it->tPlace.update(*this, vPlace); else - it->tPlace = 0; + it->tPlace.update(*this, 0); } else - it->tPlace = 99000 + it->tStatus; + it->tPlace.update(*this, 99000 + it->tStatus); } } @@ -603,9 +615,9 @@ void oEvent::calculateTeamResultAtControl(const set &classId, int leg, int } } -void oEvent::computePreliminarySplitResults(const set &classes) { +void oEvent::computePreliminarySplitResults(const set &classes) const { bool allClasses = classes.empty(); - map, vector> runnerByClassLeg; + map, vector> runnerByClassLeg; for (auto &r : Runners) { r.tOnCourseResults.clear(); r.currentControlTime.first = 1; @@ -637,11 +649,14 @@ void oEvent::computePreliminarySplitResults(const set &classes) { map, set> classLeg2ExistingCCId; for (auto &p : punches) { - if (p.isRemoved()) + if (p.isRemoved() || p.isHiredCard()) continue; pRunner r = p.getTiedRunner(); if (!r) continue; + if (!p.isCheck() && r->getCourse(false) == nullptr) + r->tOnCourseResults.hasAnyRes = true; // Register all punches for runners without course + pClass cls = r->getClassRef(false); if (r->getCourse(false) && cls) { int ccId = p.getCourseControlId(); @@ -669,7 +684,7 @@ void oEvent::computePreliminarySplitResults(const set &classes) { const set &expectedCCid = classLeg2ExistingCCId[make_pair(clsId, leg)]; size_t nRT = 0; - for (auto &radioTimes : r.tOnCourseResults) { + for (auto &radioTimes : r.tOnCourseResults.res) { if (expectedCCid.count(radioTimes.courseControlId)) nRT++; } @@ -681,7 +696,7 @@ void oEvent::computePreliminarySplitResults(const set &classes) { int ccId = crs->getCourseControlId(p.tIndex); if (expectedCCid.count(ccId)) { bool added = false; - for (auto &stored : r.tOnCourseResults) { + for (auto &stored : r.tOnCourseResults.res) { if (stored.courseControlId == ccId) { added = true; break; @@ -714,7 +729,7 @@ void oEvent::computePreliminarySplitResults(const set &classes) { if (ccId == oPunch::PunchFinish) { negLeg = -1000; //Finish, smallest number for (int j = 0; j < nRun; j++) { - pRunner r = rr[j]; + auto r = rr[j]; if (r->prelStatusOK()) { int time; if (!r->tInTeam || !totRes) @@ -723,15 +738,15 @@ void oEvent::computePreliminarySplitResults(const set &classes) { time = r->tInTeam->getLegRunningTime(r->tLeg, false); } int ix = -1; - int nr = r->tOnCourseResults.size(); + int nr = r->tOnCourseResults.res.size(); for (int i = 0; i < nr; i++) { - if (r->tOnCourseResults[i].courseControlId == ccId) { + if (r->tOnCourseResults.res[i].courseControlId == ccId) { ix = i; break; } } if (ix == -1) { - ix = r->tOnCourseResults.size(); + ix = r->tOnCourseResults.res.size(); int nc = 0; pCourse crs = r->getCourse(false); if (crs) @@ -744,12 +759,12 @@ void oEvent::computePreliminarySplitResults(const set &classes) { } else { for (int j = 0; j < nRun; j++) { - pRunner r = rr[j]; - int nr = r->tOnCourseResults.size(); + auto r = rr[j]; + int nr = r->tOnCourseResults.res.size(); for (int i = 0; i < nr; i++) { - if (r->tOnCourseResults[i].courseControlId == ccId) { - timeRunnerIx.emplace_back(r->tOnCourseResults[i].time, j, i); - negLeg = min(negLeg, -r->tOnCourseResults[i].controlIx); + if (r->tOnCourseResults.res[i].courseControlId == ccId) { + timeRunnerIx.emplace_back(r->tOnCourseResults.res[i].time, j, i); + negLeg = min(negLeg, -r->tOnCourseResults.res[i].controlIx); break; } } @@ -769,10 +784,10 @@ void oEvent::computePreliminarySplitResults(const set &classes) { if (leadTime == 0) leadTime = time; } - pRunner r = rr[get<1>(timeRunnerIx[i])]; + auto r = rr[get<1>(timeRunnerIx[i])]; int locIx = get<2>(timeRunnerIx[i]); - r->tOnCourseResults[locIx].place = place; - r->tOnCourseResults[locIx].after = time - leadTime; + r->tOnCourseResults.res[locIx].place = place; + r->tOnCourseResults.res[locIx].after = time - leadTime; int &legWithTimeIndexNeg = r->currentControlTime.first; if (negLeg < legWithTimeIndexNeg) { diff --git a/code/oEventSpeaker.cpp b/code/oEventSpeaker.cpp index 623e58f..25ac166 100644 --- a/code/oEventSpeaker.cpp +++ b/code/oEventSpeaker.cpp @@ -344,7 +344,7 @@ void renderRowSpeakerList(const oSpeakerObject &r, const oSpeakerObject *next_r, if (r.finishStatus<=1 || r.finishStatus==r.status) row.push_back(SpeakerString(normalText, names)); else - row.push_back(SpeakerString(normalText, names + L" ("+ oEvent::formatStatus(r.finishStatus) +L")")); + row.push_back(SpeakerString(normalText, names + L" ("+ oEvent::formatStatus(r.finishStatus, true) +L")")); row.push_back(SpeakerString(normalText, r.club)); @@ -410,7 +410,7 @@ void renderRowSpeakerList(const oSpeakerObject &r, const oSpeakerObject *next_r, else{ //gdi.addStringUT(y, x+dx[4], textRight, oEvent::formatStatus(r.status)).setColor(colorDarkRed); row.push_back(SpeakerString()); - row.push_back(SpeakerString(textRight, oEvent::formatStatus(r.status))); + row.push_back(SpeakerString(textRight, oEvent::formatStatus(r.status, true))); row.back().color = colorDarkRed; row.push_back(SpeakerString()); } @@ -1742,7 +1742,7 @@ void oEvent::getResultEvents(const set &classFilter, const set &punchF for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) { const oFreePunch &fp = *it; - if (fp.isRemoved() || fp.tRunnerId == 0 || fp.Type == oPunch::PunchCheck || fp.Type == oPunch::PunchStart) + if (fp.isRemoved() || fp.tRunnerId == 0 || fp.Type == oPunch::PunchCheck || fp.Type == oPunch::PunchStart || fp.Type == oPunch::HiredCard) continue; pRunner r = getRunner(fp.tRunnerId, 0); diff --git a/code/oFreePunch.cpp b/code/oFreePunch.cpp index a7828f9..a4440a9 100644 --- a/code/oFreePunch.cpp +++ b/code/oFreePunch.cpp @@ -178,7 +178,7 @@ void oEvent::generatePunchTableData(Table &table, oFreePunch *addPunch) oFreePunchList::iterator it; table.reserve(punches.size()); for (it = punches.begin(); it != punches.end(); ++it){ - if (!it->isRemoved()){ + if (!it->isRemoved() && !it->isHiredCard()){ it->addTableRow(table); } } @@ -292,7 +292,7 @@ void oFreePunch::rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch) { // Rehash all punches. Ignore cardNo and newPunch (will be included automatically) fp.reserve(oe.punches.size()); for (oFreePunchList::iterator pit = oe.punches.begin(); pit != oe.punches.end(); ++pit) { - if (pit->isRemoved()) + if (pit->isRemoved() || pit->isHiredCard()) continue; fp.push_back(&(*pit)); } @@ -335,7 +335,7 @@ void oFreePunch::rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch) { it->second.erase(res.first, res.second); } - if (newPunch) + if (newPunch && !newPunch->isHiredCard()) fp.push_back(newPunch); sort(fp.begin(), fp.end(), FreePunchComp()); @@ -614,12 +614,12 @@ pFreePunch oEvent::getPunch(int runnerId, int courseControlId, int card) const return 0; } -void oEvent::getPunchesForRunner(int runnerId, vector &runnerPunches) const { +void oEvent::getPunchesForRunner(int runnerId, bool doSort, vector &runnerPunches) const { runnerPunches.clear(); pRunner r = getRunner(runnerId, 0); if (r == 0) return; - + /* // Get times for when other runners used this card vector< pair > times; int refCno = r->getCardNo(); @@ -641,6 +641,9 @@ void oEvent::getPunchesForRunner(int runnerId, vector &runnerPunches for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) { if (it->CardNo == refCno) { + if (it->isRemoved() || it->isHiredCard()) + continue; + bool other = false; int t = it->Time; for (size_t k = 0; k &runnerPunches runnerPunches.push_back(pFreePunch(&*it)); } } + */ - // XXX Advance punches... + //Lazy setup + oFreePunch::rehashPunches(*oe, 0, 0); + + int card = r->getCardNo(); + if (card == 0) + return; + + for (auto &it1 :punchIndex) { + const oEvent::PunchIndexType &cIndex = it1.second; + pair res = cIndex.equal_range(card); + oEvent::PunchConstIterator pIter = res.first; + while (pIter != res.second) { + pFreePunch punch = pIter->second; + if (!punch->isRemoved()) { + assert(punch && punch->CardNo == card); + if (punch->tRunnerId == runnerId || runnerId == 0) + runnerPunches.push_back(punch); + ++pIter; + } + } + } + + if (doSort) { + sort(runnerPunches.begin(), runnerPunches.end(), [](const oPunch *p1, const oPunch *p2)->bool {return p1->Time < p2->Time; }); + } } @@ -732,3 +760,75 @@ void oFreePunch::changedObject() { if (r && tMatchControlId>0) r->markClassChanged(tMatchControlId); } + +bool oEvent::hasHiredCardData() { + synchronizeList(oListId::oLPunchId); + isHiredCard(0); + return !hiredCardHash.empty(); +} + +bool oEvent::isHiredCard(int cardNo) const { + if (tHiredCardHashDataRevision != dataRevision) { + hiredCardHash.clear(); + for (auto &p : punches) { + if (!p.isRemoved() && p.isHiredCard()) + hiredCardHash.insert(p.getCardNo()); + } + tHiredCardHashDataRevision = dataRevision; + } + return hiredCardHash.count(cardNo) > 0; +} + +void oEvent::setHiredCard(int cardNo, bool flag) { + if (cardNo <= 0) + return; + + if (isHiredCard(cardNo) != flag) { + if (flag) { + addFreePunch(0, oPunch::HiredCard, cardNo, false); + hiredCardHash.insert(cardNo); + tHiredCardHashDataRevision = dataRevision; + } + else { + hiredCardHash.erase(cardNo); + for (auto it = punches.begin(); it != punches.end();) { + if (!it->isRemoved() && it->isHiredCard() && it->CardNo == cardNo) { + if (HasDBConnection) + msRemove(&*it); + + auto toErase = it; + ++it; + punches.erase(toErase); + } + else { + ++it; + } + } + tHiredCardHashDataRevision = dataRevision; + } + } +} + +vector oEvent::getHiredCards() const { + isHiredCard(0); // Update hash + vector r(hiredCardHash.begin(), hiredCardHash.end()); + return r; +} + +void oEvent::clearHiredCards() { + vector toRemove; + for (auto it = punches.begin(); it != punches.end();) { + if (!it->isRemoved() && it->isHiredCard()) { + if (HasDBConnection) + msRemove(&*it); + + auto toErase = it; + ++it; + punches.erase(toErase); + } + else { + ++it; + } + } + hiredCardHash.clear(); +} diff --git a/code/oImportExport.cpp b/code/oImportExport.cpp index a661e5c..45aecde 100644 --- a/code/oImportExport.cpp +++ b/code/oImportExport.cpp @@ -118,7 +118,7 @@ bool oEvent::exportOECSV(const wchar_t *file, int languageTypeIndex, bool includ return false; calculateResults({}, ResultType::ClassResult); - + sortRunners(SortOrder::ClassResult); oRunnerList::iterator it; string maleString; string femaleString; @@ -269,7 +269,7 @@ bool oEvent::exportOECSV(const wchar_t *file, int languageTypeIndex, bool includ // Extra punches vector punches; - oe->getPunchesForRunner(it->getId(), punches); + oe->getPunchesForRunner(it->getId(), true, punches); for (vector::iterator punchIt = punches.begin(); punchIt != punches.end(); ++punchIt) { pPunch punch = *punchIt; if (!punch->isUsed && !(punch->isFinish() && !pc->useLastAsFinish()) && !(punch->isStart() && !pc->useFirstAsStart()) && !punch->isCheck()) @@ -321,6 +321,10 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, IOF30Interface reader(this, false); reader.setPreferredIdType(preferredIdType); reader.readEntryList(gdi, xo, removeNonexisting, filter, ent, fail, removed); + + for (auto &c : Clubs) { + c.updateFromDB(); + } } else { xmlList xl; @@ -604,6 +608,24 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, } } + + xo = xml.getObject("ServiceRequestList"); + + if (xo) { + gdi.addString("", 0, "Importerar tävlingsdata (IOF, xml)"); + gdi.refreshFast(); + + if (xo.getAttrib("iofVersion")) { + IOF30Interface reader(this, false); + int imp = 0, fail = 0; + + reader.readServiceRequestList(gdi, xo, imp, fail); + gdi.dropLine(); + gdi.refreshFast(); + } + + } + vector toRemove; for (size_t k = 0; k < runnersInTeam.size(); k++) { int id = runnersInTeam[k].first; @@ -1194,7 +1216,7 @@ void oEvent::importXML_IOF_Data(const wstring &clubfile, xmlparser xml_club; xml_club.setProgress(gdibase.getHWNDTarget()); - if (clear) + if (clear && !competitorfile.empty()) runnerDB->clearClubs(); gdibase.addString("",0,"Läser klubbar..."); @@ -1567,17 +1589,17 @@ bool oEvent::addXMLCourse(const xmlobject &xcrs, bool addClasses, set & } else { for (size_t i = 0; igetNumStages(); i++) - pCls->addStageCourse(i, courses[0]->getId()); + pCls->addStageCourse(i, courses[0]->getId(), -1); } } else { if (courses.size() == pCls->getNumStages()) { for (size_t i = 0; iaddStageCourse(i, courses[i]->getId()); + pCls->addStageCourse(i, courses[i]->getId(), -1); } else { for (size_t i = 0; iaddStageCourse(0, courses[i]->getId()); + pCls->addStageCourse(0, courses[i]->getId(), -1); } } } @@ -2605,6 +2627,9 @@ void oEvent::exportIOFSplits(IOFVersion version, const wchar_t *file, calculateResults(set(), ResultType::ClassResult); calculateTeamResults(true); calculateTeamResults(false); + sortRunners(SortOrder::ClassResult); + sortTeams(SortOrder::ClassResult, -1, false); + set rgClasses; for (int clz : classes) { pClass pc = getClass(clz); diff --git a/code/oListInfo.cpp b/code/oListInfo.cpp index 6185158..6ca1b07 100644 --- a/code/oListInfo.cpp +++ b/code/oListInfo.cpp @@ -645,7 +645,7 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar else { pControl ctrl = getControl(punch->getControlId()); if (!ctrl) - ctrl = getControl(punch->Type); + ctrl = getControlByType(punch->Type); if (ctrl && ctrl->hasName()) { swprintf_s(bfw, L"%s", ctrl->getName().c_str()); @@ -654,7 +654,7 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar } break; case lPunchTimeSinceLast: - if (punch && punch->previousPunchTime && r && r->Card && !invalidClass) { + if (punch && punch->previousPunchTime && r && !invalidClass) { int time = punch->Time; int pTime = punch->previousPunchTime; if (pTime > 0 && time > pTime) { @@ -673,7 +673,7 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar case lPunchSplitTime: case lPunchTotalTime: case lPunchAbsTime: - if (punch && r && r->Card && !invalidClass) { + if (punch && r && !invalidClass) { if (punch->tIndex >= 0) { // Punch in course counter.level3 = punch->tIndex; @@ -1074,6 +1074,23 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); } break; + + case lClassAvailableMaps: + if (pc) { + int n = pc->getNumRemainingMaps(false); + if (n != numeric_limits::min()) + wsptr = &itow(n); + } + break; + + case lClassTotalMaps: + if (pc) { + int n = pc->getNumberMaps(); + if (n > 0) + wsptr = &itow(n); + } + break; + case lCourseClimb: if (r) { pCourse crs = r->getCourse(false); @@ -1119,7 +1136,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } } if (r->getStatus() == StatusCANCEL) { - wsptr = &oEvent::formatStatus(StatusCANCEL); + wsptr = &oEvent::formatStatus(StatusCANCEL, true); } else if (r->startTimeAvailable()) { if (pp.type != lRunnerStartZero) @@ -1233,7 +1250,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } else { if (ok) - wsptr = &formatStatus(StatusOK); + wsptr = &formatStatus(StatusOK, true); else wsptr = &r->getStatusS(true); } @@ -1269,7 +1286,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara swap(vts, timeStatus); } else { - wstring vts = formatStatus(ts) + L" (" + timeStatus + L")"; + wstring vts = formatStatus(ts, true) + L" (" + timeStatus + L")"; swap(vts, timeStatus); } } @@ -1400,7 +1417,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (r->tempStatus==StatusOK && pc && !pc->getNoTiming()) wcscpy_s(wbf, formatTime(r->tempRT).c_str()); else - wcscpy_s(wbf, formatStatus(r->tempStatus).c_str() ); + wcscpy_s(wbf, formatStatus(r->tempStatus, true).c_str() ); } break; case lRunnerPlace: @@ -1683,6 +1700,12 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wcscpy_s(wbf, s.c_str()); } break; + case lRunnerExpectedFee: + if (r) { + wstring s = formatCurrency(r->getDefaultFee()); + wcscpy_s(wbf, s.c_str()); + } + break; case lRunnerPaid: if (r) { wstring s = formatCurrency(r->getDCI().getInt("Paid")); @@ -2465,7 +2488,7 @@ void oEvent::listGeneratePunches(const oListInfo &listInfo, gdioutput &gdi, ppi.counter.level3++; } } - else if(type == oListInfo::EBaseType::EBaseTypeAllPunches && r->Card) { + else if(type == oListInfo::EBaseType::EBaseTypeAllPunches) { int startType = -1; int finishType = -1; const pCourse pcrs = r->getCourse(false); @@ -2475,9 +2498,22 @@ void oEvent::listGeneratePunches(const oListInfo &listInfo, gdioutput &gdi, finishType = pcrs->getFinishPunchType(); } int prevPunchTime = r->getStartTime(); - for (auto &punch : r->Card->punches) { + vector punches; + if (r->Card) { + for (auto &punch : r->Card->punches) + punches.push_back(&punch); + } + else { + vector fPunches; + oe->getPunchesForRunner(r->getId(), true, fPunches); + for (auto punch : fPunches) + punches.push_back(punch); + } + + for (auto &pPunch : punches) { + const oPunch &punch = *pPunch; punch.previousPunchTime = prevPunchTime; - + if (punch.isCheck() || punch.isStart(startType)) continue; if (filterFinish && punch.isFinish(finishType)) @@ -2605,7 +2641,7 @@ bool oListInfo::filterRunner(const oRunner &r) const { } if (filter(EFilterAnyResult)) { - if (r.tOnCourseResults.empty()) + if (!r.hasOnCourseResult()) return true; } @@ -2614,6 +2650,11 @@ bool oListInfo::filterRunner(const oRunner &r) const { return true; } + if (filter(EFilterWrongFee)) { + if (r.getEntryFee() == r.getDefaultFee()) + return true; + } + if (filter(EFilterRentCard) && r.getDCI().getInt("CardFee") == 0) return true; @@ -2734,27 +2775,24 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form } calculateTeamResults(true); + sortRunners(li.sortOrder); calculateResults(li.lp.selection, ResultType::TotalResult); - - if (li.sortOrder != ClassTotalResult) - sortRunners(li.sortOrder); } else if (li.calcResults) { if (li.rogainingResults) { + sortRunners(li.sortOrder); calculateRogainingResults(li.lp.selection); - if (li.sortOrder != ClassPoints) - sortRunners(li.sortOrder); } else if (li.lp.useControlIdResultTo > 0 || li.lp.useControlIdResultFrom > 0) calculateSplitResults(li.lp.useControlIdResultFrom, li.lp.useControlIdResultTo); else if (li.sortOrder == CourseResult) { + sortRunners(li.sortOrder); calculateResults(li.lp.selection, ResultType::CourseResult); } else { calculateTeamResults(false); + sortRunners(li.sortOrder); calculateResults(li.lp.selection, ResultType::ClassResult); - if (li.sortOrder != ClassResult) - sortRunners(li.sortOrder); } } else @@ -3051,8 +3089,10 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form if (li.calcResults) { if (li.lp.useControlIdResultTo > 0 || li.lp.useControlIdResultFrom > 0) calculateSplitResults(li.lp.useControlIdResultFrom, li.lp.useControlIdResultTo); - else + else { + sortRunners(li.sortOrder); calculateResults(li.lp.selection, ResultType::ClassResult); + } } else sortRunners(li.sortOrder); diff --git a/code/oListInfo.h b/code/oListInfo.h index 4e24f44..52c26a6 100644 --- a/code/oListInfo.h +++ b/code/oListInfo.h @@ -51,6 +51,8 @@ enum EPostType lClassStartTimeRange, lClassLength, lClassResultFraction, + lClassAvailableMaps, + lClassTotalMaps, lCourseLength, lCourseName, lCourseClimb, @@ -114,6 +116,7 @@ enum EPostType lRunnerNationality, lRunnerPhone, lRunnerFee, + lRunnerExpectedFee, lRunnerPaid, lRunnerPayMethod, lRunnerEntryDate, @@ -255,9 +258,10 @@ enum EFilterList EFilterExcludeDNS, EFilterExcludeCANCEL, EFilterVacant, - EFilterOnlyVacant, + EFilterOnlyVacant, EFilterAnyResult, // With any (radio) punch on a leg EFilterAPIEntry, // Entry via API + EFilterWrongFee, _EFilterMax }; diff --git a/code/oPunch.h b/code/oPunch.h index 52f8128..b80a291 100644 --- a/code/oPunch.h +++ b/code/oPunch.h @@ -70,6 +70,7 @@ public: wstring getInfo() const; + bool isHiredCard() const { return Type == HiredCard; } bool isStart() const {return Type==PunchStart;} bool isStart(int startType) const {return Type==PunchStart || Type == startType;} bool isFinish() const {return Type==PunchFinish;} @@ -93,7 +94,7 @@ public: wstring getRunningTime(int startTime) const; - enum SpecialPunch {PunchStart=1, PunchFinish=2, PunchCheck=3}; + enum SpecialPunch {PunchStart=1, PunchFinish=2, PunchCheck=3, HiredCard=11111}; void decodeString(const string &s); string codeString() const; oPunch(oEvent *poe); diff --git a/code/oRunner.cpp b/code/oRunner.cpp index 633edad..d0daf16 100644 --- a/code/oRunner.cpp +++ b/code/oRunner.cpp @@ -49,6 +49,16 @@ oRunner::RaceIdFormatter oRunner::raceIdFormatter; +bool oAbstractRunner::DynamicValue::isOld(const oEvent &oe) const { + return oe.dataRevision != dataRevision; +} + +void oAbstractRunner::DynamicValue::update(const oEvent &oe, int v) { + value = v; + dataRevision = oe.dataRevision; +} + + const wstring &oRunner::RaceIdFormatter::formatData(const oBase *ob) const { return itow(dynamic_cast(*ob).getRaceIdentifier()); } @@ -317,6 +327,20 @@ int oRunner::getBirthAge() const { return 0; } +int oAbstractRunner::getDefaultFee() const { + int age = getBirthAge(); + wstring date = getEntryDate(); + if (Class) { + int fee = Class->getEntryFee(date, age); + return fee; + } + return 0; +} + +int oAbstractRunner::getEntryFee() const { + return getDCI().getInt("Fee"); +} + void oAbstractRunner::addClassDefaultFee(bool resetFees) { if (Class) { oDataInterface di = getDI(); @@ -343,8 +367,7 @@ void oAbstractRunner::addClassDefaultFee(bool resetFees) { } if ((currentFee == 0 && !hasFlag(FlagFeeSpecified)) || resetFees) { - int age = getBirthAge(); - int fee = Class->getEntryFee(date, age); + int fee = getDefaultFee(); di.setInt("Fee", fee); } } @@ -657,17 +680,13 @@ int oRunner::getTotalRunningTime() const { const wstring &oAbstractRunner::getStatusS(bool formatForPrint) const { - if (formatForPrint && tStatus == StatusUnknown) - return formatTime(-1); - return oEvent::formatStatus(tStatus); + return oEvent::formatStatus(tStatus, formatForPrint); } const wstring &oAbstractRunner::getTotalStatusS(bool formatForPrint) const { - auto ts = getTotalStatus(); - if (formatForPrint && ts == StatusUnknown) - return formatTime(-1); - return oEvent::formatStatus(ts); + auto ts = getTotalStatus(); + return oEvent::formatStatus(ts, formatForPrint); } /* @@ -2138,6 +2157,14 @@ void oAbstractRunner::setStartNo(int no, bool tmpOnly) { void oRunner::setStartNo(int no, bool tmpOnly) { + if (tInTeam) { + if (tInTeam->getStartNo() == 0) + tInTeam->setStartNo(no, tmpOnly); + else { + // Do not allow different from team + no = tInTeam->getStartNo(); + } + } if (tParentRunner) tParentRunner->setStartNo(no, tmpOnly); else { @@ -2149,17 +2176,51 @@ void oRunner::setStartNo(int no, bool tmpOnly) } } -int oRunner::getPlace() const -{ - return tPlace; +void oRunner::updateStartNo(int no) { + if (tInTeam) { + tInTeam->synchronize(false); + for (pRunner r : tInTeam->Runners) { + if (r) { + r->synchronize(false); + } + } + + tInTeam->setStartNo(no, false); + for (pRunner r : tInTeam->Runners) { + if (r) { + r->setStartNo(no, false); + } + } + + tInTeam->synchronize(true); + for (pRunner r : tInTeam->Runners) { + r->synchronize(true); + } + } + else { + setStartNo(no, false); + synchronize(true); + } } -int oRunner::getCoursePlace() const -{ + +int oRunner::getPlace() const { + if (tPlace.isOld(*oe)) { + if (Class) { + oEvent::ResultType rt = oEvent::ResultType::ClassResult; + if (Class->isRogaining()) + oe->calculateRogainingResults({ getClassId(true) }); + else + oe->calculateResults({ getClassId(true) }, rt, false); + } + } + return tPlace.value; +} + +int oRunner::getCoursePlace() const { return tCoursePlace; } - int oRunner::getTotalPlace() const { if (tInTeam) @@ -3873,7 +3934,8 @@ void oEvent::analyseDNS(vector &unknown_dns, vector &known_dns typedef multimap::const_iterator TPunchIter; for (oFreePunchList::iterator it = punches.begin(); it != punches.end(); ++it) { - punchHash.insert(make_pair(it->getCardNo(), &*it)); + if (!it->isRemoved() && !it->isHiredCard()) + punchHash.insert(make_pair(it->getCardNo(), &*it)); } set knownCards; @@ -5015,7 +5077,7 @@ int oRunner::getLegTimeAfter(int ctrlNo) const { } int oRunner::getLegPlaceAcc(int ctrlNo) const { - for (auto &res : tOnCourseResults) { + for (auto &res : tOnCourseResults.res) { if (res.controlIx == ctrlNo) return res.place; } @@ -5030,7 +5092,7 @@ int oRunner::getLegPlaceAcc(int ctrlNo) const { } int oRunner::getLegTimeAfterAcc(int ctrlNo) const { - for (auto &res : tOnCourseResults) { + for (auto &res : tOnCourseResults.res) { if (res.controlIx == ctrlNo) return res.after; } @@ -5265,7 +5327,7 @@ void oAbstractRunner::setInputStatus(RunnerStatus s) { } wstring oAbstractRunner::getInputStatusS() const { - return oe->formatStatus(inputStatus); + return oe->formatStatus(inputStatus, true); } void oAbstractRunner::setInputPoints(int p) @@ -5341,16 +5403,26 @@ void oEvent::getDBRunnersInEvent(intkeymap &runners) const { } } -void oRunner::init(const RunnerWDBEntry &dbr) { - setTemporary(); - dbr.getName(sName); - getRealName(sName, tRealName); - cardNumber = dbr.dbe().cardNo; - Club = oe->getRunnerDatabase().getClub(dbr.dbe().clubNo); - getDI().setString("Nationality", dbr.getNationality()); - getDI().setInt("BirthYear", dbr.getBirthYear()); - getDI().setString("Sex", dbr.getSex()); - setExtIdentifier(dbr.getExtId()); +void oRunner::init(const RunnerWDBEntry &dbr, bool updateOnlyExt) { + if (updateOnlyExt) { + dbr.getName(sName); + getRealName(sName, tRealName); + getDI().setString("Nationality", dbr.getNationality()); + getDI().setInt("BirthYear", dbr.getBirthYear()); + getDI().setString("Sex", dbr.getSex()); + setExtIdentifier(dbr.getExtId()); + } + else { + setTemporary(); + dbr.getName(sName); + getRealName(sName, tRealName); + cardNumber = dbr.dbe().cardNo; + Club = oe->getRunnerDatabase().getClub(dbr.dbe().clubNo); + getDI().setString("Nationality", dbr.getNationality()); + getDI().setInt("BirthYear", dbr.getBirthYear()); + getDI().setString("Sex", dbr.getSex()); + setExtIdentifier(dbr.getExtId()); + } } void oEvent::selectRunners(const wstring &classType, int lowAge, @@ -5605,11 +5677,11 @@ void oAbstractRunner::setTempResultZero(const TempResult &tr) { const wstring &oAbstractRunner::TempResult::getStatusS(RunnerStatus inputStatus) const { if (inputStatus == StatusOK) - return oEvent::formatStatus(getStatus()); + return oEvent::formatStatus(getStatus(), true); else if (inputStatus == StatusUnknown) return formatTime(-1); else - return oEvent::formatStatus(max(inputStatus, getStatus())); + return oEvent::formatStatus(max(inputStatus, getStatus()), true); } const wstring &oAbstractRunner::TempResult::getPrintPlaceS(bool withDot) const { diff --git a/code/oRunner.h b/code/oRunner.h index 821bff1..4d06901 100644 --- a/code/oRunner.h +++ b/code/oRunner.h @@ -198,6 +198,12 @@ public: // a non-zero fee is changed only if resetFee is true void addClassDefaultFee(bool resetFees); + /** Returns fee from the class. */ + int getDefaultFee() const; + + /** Returns the currently assigned fee. */ + int getEntryFee() const; + /** Returns true if the entry fee is a late fee. */ bool hasLateEntryFee() const; @@ -362,6 +368,13 @@ public: oAbstractRunner(oEvent *poe, bool loading); virtual ~oAbstractRunner() {}; + struct DynamicValue { + int dataRevision; + int value; + bool isOld(const oEvent &oe) const; + void update(const oEvent &oe, int v); + }; + friend class oListInfo; friend class GeneralResult; }; @@ -413,7 +426,7 @@ protected: wstring tRealName; //Can be changed by apply - mutable int tPlace; + mutable DynamicValue tPlace; mutable int tCoursePlace; mutable int tTotalPlace; mutable int tLeg; @@ -499,8 +512,22 @@ protected: int place; int after; }; - pair currentControlTime; - mutable vector tOnCourseResults; + mutable pair currentControlTime; + + struct OnCourseResultCollection { + bool hasAnyRes = false; + vector res; + void clear() { hasAnyRes = false; res.clear(); } + void emplace_back(int courseControlId, + int controlIx, + int time) { + res.emplace_back(courseControlId, controlIx, time); + hasAnyRes = true; + } + bool empty() const { return hasAnyRes == false; } + }; + + mutable OnCourseResultCollection tOnCourseResults; // Rogainig results. Control and punch time vector< pair > tRogaining; @@ -538,7 +565,7 @@ protected: public: // Returns true if there are radio control results, provided result calculation oEvent::ResultType::PreliminarySplitResults was invoked. - bool hasOnCourseResult() const { return tOnCourseResults.size() > 0 || getFinishTime() > 0; } + bool hasOnCourseResult() const { return !tOnCourseResults.empty() || getFinishTime() > 0 || getStatus() != RunnerStatus::StatusUnknown; } /** Get a runner reference (drawing) */ pRunner getReference() const; @@ -656,7 +683,7 @@ public: void setTemporary() {isTemporaryObject=true;} /** Init from dbrunner */ - void init(const RunnerWDBEntry &entry); + void init(const RunnerWDBEntry &entry, bool updateOnlyExt); /** Use db to pdate runner */ bool updateFromDB(const wstring &name, int clubId, int classId, @@ -686,6 +713,8 @@ public: // which should be more stable. void setBib(const wstring &bib, int bibNumerical, bool updateStartNo, bool setTmpOnly); void setStartNo(int no, bool setTmpOnly); + // Update and synch start number for runner and team. + void updateStartNo(int no); pRunner nextNeedReadout() const; diff --git a/code/oTeam.cpp b/code/oTeam.cpp index 48fd895..124f812 100644 --- a/code/oTeam.cpp +++ b/code/oTeam.cpp @@ -653,7 +653,7 @@ RunnerStatus oTeam::getLegStatus(int leg, bool multidayTotal) const const wstring &oTeam::getLegStatusS(int leg, bool multidayTotal) const { - return oe->formatStatus(getLegStatus(leg, multidayTotal)); + return oe->formatStatus(getLegStatus(leg, multidayTotal), true); } int oTeam::getLegPlace(int leg, bool multidayTotal) const { diff --git a/code/oTeam.h b/code/oTeam.h index a96d0f6..8713982 100644 --- a/code/oTeam.h +++ b/code/oTeam.h @@ -76,9 +76,9 @@ protected: TeamPlace _places[maxRunnersTeam]; - int _sortTime; - int _sortStatus; - RunnerStatus _cachedStatus; + mutable int _sortTime; + mutable int _sortStatus; + mutable RunnerStatus _cachedStatus; mutable vector< vector< vector > > resultCalculationCache; diff --git a/code/oTeamEvent.cpp b/code/oTeamEvent.cpp index 5bbbf60..4f0e008 100644 --- a/code/oTeamEvent.cpp +++ b/code/oTeamEvent.cpp @@ -422,7 +422,7 @@ void oEvent::setupRelay(oClass &cls, PredefinedTypes type, int nleg, const wstri cls.setCoursePool(type == PPool); if (crs) { - cls.addStageCourse(0, crsId); + cls.addStageCourse(0, crsId, -1); } break; @@ -437,7 +437,7 @@ void oEvent::setupRelay(oClass &cls, PredefinedTypes type, int nleg, const wstri cls.setCoursePool(true); if (crs) { - cls.addStageCourse(0, crsId); + cls.addStageCourse(0, crsId, -1); } break; @@ -456,8 +456,8 @@ void oEvent::setupRelay(oClass &cls, PredefinedTypes type, int nleg, const wstri cls.setRopeTime(1, L"-"); if (crs) { - cls.addStageCourse(0, crsId); - cls.addStageCourse(1, crsId); + cls.addStageCourse(0, crsId, -1); + cls.addStageCourse(1, crsId, -1); } cls.setCoursePool(false); break; @@ -477,8 +477,8 @@ void oEvent::setupRelay(oClass &cls, PredefinedTypes type, int nleg, const wstri cls.setRopeTime(1, L"-"); if (crs) { - cls.addStageCourse(0, crsId); - cls.addStageCourse(1, crsId); + cls.addStageCourse(0, crsId, -1); + cls.addStageCourse(1, crsId, -1); } cls.setCoursePool(false); break; @@ -498,8 +498,8 @@ void oEvent::setupRelay(oClass &cls, PredefinedTypes type, int nleg, const wstri cls.setRopeTime(1, L"-"); if (crs) { - cls.addStageCourse(0, crsId); - cls.addStageCourse(1, crsId); + cls.addStageCourse(0, crsId, -1); + cls.addStageCourse(1, crsId, -1); } cls.setCoursePool(false); diff --git a/code/onlineinput.cpp b/code/onlineinput.cpp index 89d8c03..c629a06 100644 --- a/code/onlineinput.cpp +++ b/code/onlineinput.cpp @@ -471,6 +471,9 @@ void OnlineInput::processEntries(oEvent &oe, const xmlList &entries) { r->setCardNo(cardNo, false, false); } + if (fee == 0) + fee = r->getDefaultFee(); + r->getDI().setInt("Fee", fee); int toPay = fee; int cf = 0; @@ -479,6 +482,7 @@ void OnlineInput::processEntries(oEvent &oe, const xmlList &entries) { if (cf > 0) toPay += cf; } + r->setFlag(oRunner::FlagAddedViaAPI, true); r->getDI().setInt("CardFee", cf); r->getDI().setInt("Paid", paid ? toPay : 0); diff --git a/code/onlineresults.cpp b/code/onlineresults.cpp index 5de8702..3c85141 100644 --- a/code/onlineresults.cpp +++ b/code/onlineresults.cpp @@ -342,8 +342,10 @@ void OnlineResults::status(gdioutput &gdi) } if (sendToFile || sendToURL) { - gdi.addString("", 0, "Exporterar om: "); - gdi.addTimer(gdi.getCY(), gdi.getCX(), timerIgnoreSign, (GetTickCount()-timeout)/1000); + if (interval > 0) { + gdi.addString("", 0, "Exporterar om: "); + gdi.addTimer(gdi.getCY(), gdi.getCX(), timerIgnoreSign, (GetTickCount() - timeout) / 1000); + } gdi.addString("", 0, "Antal skickade uppdateringar X (Y kb)#" + itos(exportCounter-1) + "#" + itos(bytesExported/1024)); } @@ -515,7 +517,7 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { if (ast == SyncNone) throw; else - gdi.addInfoBox("", L"Online Results Error X#" + ex.wwhat(), 5000); + gdi.addInfoBox("", L"Online Results Error X#" + lang.tl(ex.wwhat()), 5000); } catch(std::exception &ex) { if (ast == SyncNone) diff --git a/code/qualification_final.cpp b/code/qualification_final.cpp index e99d169..a2bc5cb 100644 --- a/code/qualification_final.cpp +++ b/code/qualification_final.cpp @@ -354,7 +354,7 @@ int QualificationFinal::getLevel(int instance) const { } bool QualificationFinal::isFinalClass(int instance) const { - return sourcePlaceToFinalOrder.count(make_pair(instance, 1)) == 0; + return instance > 0 && sourcePlaceToFinalOrder.count(make_pair(instance, 1)) == 0; } int QualificationFinal::getNumLevels() const { diff --git a/code/restserver.cpp b/code/restserver.cpp index bd74448..03c50d0 100644 --- a/code/restserver.cpp +++ b/code/restserver.cpp @@ -88,12 +88,18 @@ static void method_handler(const shared_ptr< restbed::Session > session) { void RestServer::handleRequest(const shared_ptr &session) { const auto request = session->get_request(); - size_t content_length = request->get_header("Content-Length", 0); - + string path = request->get_path(); + + size_t content_length = request->get_header("Content-Length", 0); chrono::time_point start, end; start = chrono::system_clock::now(); auto param = request->get_query_parameters(); + + if (path == "/" && !root.empty()) { + param = rootMap; + } + auto answer = RestServer::addRequest(param); { unique_lock mlock(lock); @@ -131,9 +137,30 @@ void RestServer::startThread(int port) { resource->set_method_handler("GET", method_handler); restService->publish(resource); + + auto resourceRoot = make_shared(this); + resourceRoot->set_path("/"); + resourceRoot->set_method_handler("GET", method_handler); + restService->publish(resourceRoot); + restService->start(settings); } +void RestServer::setRootMap(const string &rmap) { + root = rmap; + vector sp; + rootMap.clear(); + split(rmap, "&", sp); + for (string arg : sp) { + vector sp2; + split(arg, "=", sp2); + if (sp2.size() == 1) + rootMap.emplace(sp2[0], ""); + else if (sp2.size() == 2) + rootMap.emplace(sp2[0], sp2[1]); + } +} + void RestServer::startService(int port) { if (service) throw meosException("Server started"); @@ -243,6 +270,40 @@ void RestServer::computeInternal(oEvent &ref, shared_ptranswer += entryLinks; + vector runners; + ref.getRunners(-1, -1, runners, false); + string sRunnerId, sRunnerBib, sRunnerCard, sRunnerName, sRunnerClub, sRunnerClubId; + for (pRunner r : runners) { + if (sRunnerId.empty()) + sRunnerId = itos(r->getId()); + + if (sRunnerClub.empty()) { + sRunnerName = gdioutput::toUTF8(r->getName()); + sRunnerClub = gdioutput::toUTF8(r->getClub()); + if (r->getClubId()) + sRunnerClubId = gdioutput::toUTF8(r->getClubRef()->getExtIdentifierString()); + } + + if (sRunnerCard.empty() && r->getCardNo() > 0) + sRunnerCard = itos(r->getCardNo()); + + if (sRunnerBib.empty() && !r->getBib().empty()) + sRunnerBib = gdioutput::recodeToNarrow(r->getBib()); + } + if (sRunnerId.empty()) + sRunnerId = "1"; + if (sRunnerCard.empty()) + sRunnerCard = "12345"; + if (sRunnerBib.empty()) + sRunnerBib = "100"; + if (sRunnerClub.empty()) + sRunnerClub = "Lost and Found"; + if (sRunnerName.empty()) + sRunnerName = "Charles Kinsley"; + + + string sRunnerDbName, sRunnerDbId, sRunnerDbClub; + //oe.getRunnerDatabase(). HINSTANCE hInst = GetModuleHandle(0); HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(132), RT_HTML); @@ -257,8 +318,44 @@ void RestServer::computeInternal(oEvent &ref, shared_ptr 0) { + key += *pKey; + ++pKey; + resSize--; + } + if (key == "runnerid") { + htmlS += sRunnerId; + } + else if (key == "card") { + htmlS += sRunnerCard; + } + else if (key == "bib") { + htmlS += sRunnerBib; + } + else if (key == "name") { + htmlS += sRunnerName; + } + else if (key == "club") { + htmlS += sRunnerClub; + } + else if (key == "dbname") { + htmlS += sRunnerName.substr(0, sRunnerName.size() / 2); + } + else if (key == "dbclub") { + htmlS += sRunnerClub; + } + else if (key == "clubid") { + htmlS += sRunnerClubId; + } + html = pKey; + } else htmlS += *html; + ++html; resSize--; } @@ -959,17 +1056,13 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimap 0) { int card = atoi(param.find("card")->second.c_str()); int time = 0; - if (param.count("running") && param.find("card")->second == "true") { + if (param.count("running") && param.find("running")->second == "true") { time = oe.getRelativeTime(getLocalTime()); if (time < 0) time = 0; } - pRunner r = oe.getRunnerByCardNo(card, time, oEvent::CardLookupProperty::CardInUse); - if (!r) - r = oe.getRunnerByCardNo(card, time, oEvent::CardLookupProperty::Any); - - if (r) - runners.push_back(r); + + oe.getRunnersByCardNo(card, false, oEvent::CardLookupProperty::Any, runners); } if (param.count("name") > 0) { wstring club; @@ -986,7 +1079,7 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimap 0) { - pRunner r = oe.getRunnerByBibOrStartNo(wideParam(param.find("bib")->second), false); + pRunner r = oe.getRunnerByBibOrStartNo(wideParam(param.find("bib")->second), true); if (r) runners.push_back(r); } @@ -1012,7 +1105,9 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetTeam()) { xml.write("Team", { make_pair("id", itow(r->getTeam()->getId())) }, r->getTeam()->getName()); - xml.write("Leg", r->getLegNumber()); + pClass cls = r->getClassRef(false); + if (cls) + xml.write("Leg", cls->getLegNumber(r->getLegNumber())); } if ((r->getFinishTime() > 0 || r->getCard() != nullptr) && r->getCourse(false)) { auto &sd = r->getSplitTimes(false); @@ -1193,14 +1288,23 @@ void RestServer::newEntry(oEvent &oe, const multimap ¶m, str else if(param.count("name")) name = wideParam(param.find("name")->second); - if (param.count("club")) + if (param.count("club")) club = wideParam(param.find("club")->second); pClub existingClub = oe.getClub(club); if (epType != EntryPermissionType::Any) { if (extId == 0) { - dbr = oe.getRunnerDatabase().getRunnerByName(name, clubId, 0); + int clubExtId = 0; + if (existingClub) + clubExtId = (int)existingClub->getExtIdentifier(); + else if (!club.empty()) { + pClub dbClub = oe.getRunnerDatabase().getClub(club); + if (dbClub) { + clubExtId = dbClub->getId(); + } + } + dbr = oe.getRunnerDatabase().getRunnerByName(name, clubExtId, 0); if (dbr) extId = dbr->getExtId(); } @@ -1220,6 +1324,12 @@ void RestServer::newEntry(oEvent &oe, const multimap ¶m, str else if (epClass != EntryPermissionClass::Any && !cls->getAllowQuickEntry()) { permissionDenied = true; } + else { + int nm = cls->getNumRemainingMaps(false); + if (nm != numeric_limits::min() && nm<=0) { + error = L"Klassen är full"; + } + } if (epType != EntryPermissionType::Any && extId == 0) { error = L"Anmälan måste hanteras manuellt"; @@ -1249,7 +1359,7 @@ void RestServer::newEntry(oEvent &oe, const multimap ¶m, str if (!permissionDenied && error.empty()) { pRunner r = oe.addRunner(name, club, classId, cardNo, 0, true); if (r && dbr) { - r->init(*dbr); + r->init(*dbr, true); } if (r) { diff --git a/code/restserver.h b/code/restserver.h index bc557e3..f65efcd 100644 --- a/code/restserver.h +++ b/code/restserver.h @@ -108,6 +108,9 @@ private: void computeInternal(oEvent &ref, shared_ptr &rq); map > > listCache; + + string root; + multimap rootMap; public: ~RestServer(); @@ -121,6 +124,9 @@ public: void setEntryPermission(EntryPermissionClass epClass, EntryPermissionType epType); + void setRootMap(const string &rootMap); + const string &getRootMap() const { return root; } + tuple getEntryPermission() const { return make_tuple(epClass, epType); } diff --git a/code/socket.cpp b/code/socket.cpp index 7cd2a58..35ad9f1 100644 --- a/code/socket.cpp +++ b/code/socket.cpp @@ -126,6 +126,8 @@ void DirectSocket::listenDirectSocket() { closesocket(clientSocket); } +extern HWND hWndMain; + void startListeningDirectSocket(void *p) { wstring error; try { @@ -142,8 +144,9 @@ void startListeningDirectSocket(void *p) { error = L"Unknown error"; } if (!error.empty()) { - error = L"Setting up advance information service for punches failed. Punches will be recieved with some seconds delay. Is the network port blocked by an other MeOS session?\n\n" + error; - MessageBox(NULL, error.c_str(), L"MeOS", MB_OK|MB_ICONSTOP); + PostMessage(hWndMain, WM_USER + 5, 0, 0); + //error = L"Setting up advance information service for punches failed. Punches will be recieved with some seconds delay. Is the network port blocked by an other MeOS session?\n\n" + error; + //MessageBox(NULL, error.c_str(), L"MeOS", MB_OK|MB_ICONSTOP); } } diff --git a/code/swedish.lng b/code/swedish.lng index ac5edea..2a64b5f 100644 --- a/code/swedish.lng +++ b/code/swedish.lng @@ -1310,7 +1310,7 @@ help:30750 = Här kan du skapa olika sorters listor och rapporter. Du kan visa d help:31661 = En omstart definieras av en repdragningstid och en omstartstid. Vid repdragningstiden stängs växlingen, och inga startande släpps ut i skogen. Vid omstartstiden går omstarten. Det går att ställa in olika omstartstid på olika sträckor, men med hjälp av den här funktionen sätter du snabbt omstartstid för hela klasser. help:33940 = Importera anmälningsdata i fritextformat. Ange Namn, Klubb, Klass, och SI (och eventuell starttid) gärna separerade med komma, en person (ett lag) per rad. Det går också att anmäla flera personer i samma klubb/klass genom att (delvis) utelämna fälten klubb/klass. Det är också möjligt att importera anmälningar formaterade på andra sätt.\n\nKlasser skapas automatiskt, men om du importerar lag för stafett eller patrull bör du lägga upp klasserna själv innan du importerar anmälningarna. Annars finns risk att sträcktilldelningen blir fel. help:41072 = Markera en stämpling i stämplingslistan för att ta bort den eller ändra tiden. Från banmallen kan saknade stämplingar läggas till. Saknas måltid får löparen status utgått. Saknas stämpling får löparen status felstämplat. Det går inte att sätta en status på löparen som inte överensstämmer med stämplingsdata. Finns målstämpling måste tiden för denna ändras för att ändra måltiden; samma princip gäller för startstämpling. -help:41641 = Fyll i första starttid och startintervall. Lottning innebär slumpmässig lottning, SOFT-lottning en lottning enligt SOFT:s klubbfördelningsregler. Klungstart innebär att hela klassen startar i småklungor under det intervall du anger ("utdragen" masstart). \n\nAnge intervall 0 för gemensam start.\n\nNummerlappar: Ange första nummer eller lämna blankt för inga nummerlappar. I fältet sträcka (Str.) anger du vilken sträcka som ska lottas (om klassen har flera sträckor). +help:41641 = Välj metod för lottning. är helt slumpmässig. och ser till löpare från samma klubb inte startar på närliggande tider.\n\n innebär att hela klassen startar i småklungor under det intervall du anger ("utdragen" masstart). \n\nNummerlappar: Ange första nummer som ska användas i klassen. I fältet sträcka (Str.) anger du vilken sträcka som ska lottas (om klassen har flera sträckor). help:425188 = Du kan automatiskt hantera genom att läsa in SI-stationer (töm/check/start/kontroller) i SI-Config, spara inläsningen som en semikolonseparerad textfil och importera denna i MeOS. De löpare som förekommer i denna import får en registrering. Därefter kan du sätta på löpare utan registrering. Läser du senare in fler löpare kan du återställa de löpare som tidigare fått Ej Start men nu fått en registrering. help:471101 = Aktivera SI-enheten genom att välja rätt COM-port, eller genom att söka efter installerade SI-enheter. Info ger dig information om den valda enheten/porten. För att läsa in brickor ska enheten vara programmerad utan autosänd (men för radiokontroller används autosänd). Utökat protokoll rekommenderas, då det ger en stabilare uppkoppling. Enheten programmeras med SportIdents programvara SI-Config.\n\nInteraktiv inläsning används om du direkt vill ta hand om eventuella problem som felaktigt bricknummer; avmarkera om arrangemanget använder 'röd utgång'.\n\nLöpardatabasen används om du automatiskt vill lägga till inkommande löpare med hjälp av löpardatabasen. Löparens stämplingar används för att välja rätt klass. help:50431 = Du är nu ansluten mot en server. För att öppna en tävling från servern, markera den i listan och välj öppna. För att lägga upp en tävling på servern, öppna först tävlingen lokalt, och använd därefter knappen ladda upp tävling. När du öppnat en tävling på servern ser du vilka andra MeOS-klienter som är anslutna mot den.\n\nOm det står (på server) efter tävlingen är den öppnad på en server och kan delas av andra MeOS-klienter. Står det (lokalt) kan man bara komma åt tävlingen från den aktuella datorn. @@ -2409,3 +2409,30 @@ info:pageswithcolumns = Visa listan en sida i taget med angivet antal kolumner. Pages with columns = Sidor med kolumner Pages with columns, no header = Sidor med kolumner utan rubrik Externa adresser = Externa adresser +info:advanceinfo = Det gick inte att starta tjänsten för förhandsinformation om resultat; resultat kommer med några sekunders fördröjning. Detta är väntat om fler än en MeOS-process körs på samma dator. +Klassen är full = Klassen är full +Flytta upp = Flytta upp +Flytta ner = Flytta ner +EFilterWrongFee = Oväntat avgift +RunnerExpectedFee = Deltagares förväntade avgift +Unexpected Fee = Oväntade anmälningsavgifter +Anmälningsdatum = Anmälningsdatum +Förväntad = Förväntad +Registrera hyrbrickor = Registrera hyrbrickor +Vill du sätta hyrbricka på befintliga löpare med dessa brickor? = Vill du sätta hyrbricka på befintliga löpare med dessa brickor? +Vill du ta bort brickan från hyrbrickslistan? = Vill du ta bort brickan från hyrbrickslistan? +Vill du tömma listan med hyrbrickor? = Vill du tömma listan med hyrbrickor? +help:registerhiredcards = Förregistrera hyrbrickor för att få automatisk hyrbricka när den används. +prefsLastExportTarget = Senaste exportmål +prefsServiceRootMap = Standardfunktion för webbserverns root +prefsshowheader = Visa sidrubriker i listor +Lagändringblankett = Lagändringblankett +Mappa rootadresssen (http:///localhost:port/) till funktion = Mappa rootadresssen (http:///localhost:port/) till funktion +ClassAvailableMaps = Klassens lediga kartor +ClassTotalMaps = Klassens antal kartor +Patrol overtime = Patrulls tid övertid +Patrol score reduction = Patrulls poängreduktion +Patrol score, rogaining = Patrullpoäng, rogaining +Rogaining points before automatic reduction = Rogainingpoäng före automatisk reduktion +Runner's course id = Deltagares banans id +Status code for cancelled entry = Statuskod för återbud