/********************i**************************************************** MeOS - Orienteering Software Copyright (C) 2009-2023 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Melin Software HB - software@melin.nu - www.melin.nu Eksoppsvägen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ #include "stdafx.h" #include "oEvent.h" #include "gdioutput.h" #include "meos_util.h" #include #include "Localizer.h" #include #include "gdifonts.h" #include "meosexception.h" #include "liveresult.h" LiveResult::LiveResult(oEvent *oe) : oe(oe), active(false), lastTime(0), rToWatch(0) { baseFont = oe->getPropertyString("LiveResultFont", L"Consolas"); showResultList = -1; timerScale = 1.0; } wstring LiveResult::getFont(const gdioutput &gdi, double relScale) const { int h,w; gdi.getTargetDimension(w, h); if (!gdi.isFullScreen()) w -= gdi.scaleLength(160); double fact = min(h/180.0, w/300.0); double size = relScale * fact; wchar_t ss[32]; swprintf_s(ss, L"%f", size); wstring font = baseFont + L";" + ss; return font; } void LiveResult::showDefaultView(gdioutput &gdi) { int h,w; gdi.getTargetDimension(w, h); if (!gdi.isFullScreen()) w -= gdi.scaleLength(160); RECT rc; rc.top = h-24; rc.left = 20; rc.right = w - 30; rc.bottom = h - 22; wstring font = getFont(gdi, 1.0); gdi.addRectangle(rc, colorLightYellow, true); gdi.addString("timing", 50, w / 2, textCenter|boldHuge, L"MeOS Timing", 0, 0, font.c_str()); TextInfo &ti = gdi.addString("measure", 0, 0, boldHuge, L"55:55:55", 0, 0, font.c_str()); int tw = ti.textRect.right - ti.textRect.left; timerScale = double(w) * 0.8 / double(tw); gdi.removeString("measure"); } void LiveResult::showTimer(gdioutput &gdi, const oListInfo &liIn) { li = liIn; active = true; int h,w; gdi.getTargetDimension(w, h); gdi.clearPage(false); showDefaultView(gdi); gdi.registerEvent("DataUpdate", 0).setHandler(this); gdi.setData("DataSync", 1); gdi.setData("PunchSync", 1); gdi.setRestorePoint("LiveResult"); readoutResult(); resYPos = h/3; calculateResults(); showResultList = 0; gdi.addTimeoutMilli(1000, "res", 0).setHandler(this); gdi.refreshFast(); } void LiveResult::readoutResult() { lastTime = 0; vector pp; oe->synchronizeList({ oListId::oLRunnerId, oListId::oLPunchId }); oe->getLatestPunches(lastTime, pp); processedPunches.clear(); map, vector > > storedPunches; int fromPunch = li.getParam().useControlIdResultFrom; int toPunch = li.getParam().useControlIdResultTo; if (fromPunch == 0) fromPunch = oPunch::PunchStart; if (toPunch == 0) toPunch = oPunch::PunchFinish; for (size_t k = 0; k < pp.size(); k++) { lastTime = max(pp[k]->getModificationTime(), lastTime); pRunner r = pp[k]->getTiedRunner(); if (r) { pair key = make_pair(r->getId(), pp[k]->getControlId()); processedPunches[key] = max(processedPunches[key], pp[k]->getAdjustedTime()); if (!li.getParam().selection.empty() && !li.getParam().selection.count(r->getClassId(true))) continue; // Filter class if (pp[k]->getTypeCode() == fromPunch) { storedPunches[r->getId()].first.push_back(k); } else if (pp[k]->getTypeCode() == toPunch) { storedPunches[r->getId()].second.push_back(k); } } } startFinishTime.clear(); results.clear(); for (map, vector > >::iterator it = storedPunches.begin(); it != storedPunches.end(); ++it) { vector& froms = it->second.first; vector& tos = it->second.second; pRunner r = oe->getRunner(it->first, 0); for (size_t j = 0; j < tos.size(); j++) { int fin = pp[tos[j]]->getAdjustedTime(); int time = 100000000; int sta = 0; for (size_t k = 0; k < froms.size(); k++) { int t = fin - pp[froms[k]]->getAdjustedTime(); if (t > 0 && t < time) { time = t; sta = pp[froms[k]]->getAdjustedTime(); } } if (time < 100000000 && r->getStatus() <= StatusOK) { // results.push_back(Result()); // results.back().r = r; // results.back().time = time; startFinishTime[r->getId()].first = sta; startFinishTime[r->getId()].second = fin; } } } } void LiveResult::handle(gdioutput &gdi, BaseInfo &bu, GuiEventType type) { if (type == GUI_EVENT) { vector pp; oe->getLatestPunches(lastTime, pp); int fromPunch = li.getParam().useControlIdResultFrom; int toPunch = li.getParam().useControlIdResultTo; if (fromPunch == 0) fromPunch = oPunch::PunchStart; if (toPunch == 0) toPunch = oPunch::PunchFinish; bool hasCheckedOld = false; vector< pair > enter, exit, backExit; for (size_t k = 0; k < pp.size(); k++) { lastTime = max(pp[k]->getModificationTime(), lastTime); pRunner r = pp[k]->getTiedRunner(); if (!r) continue; if (!li.getParam().selection.empty() && !li.getParam().selection.count(r->getClassId(true))) continue; // Filter class pair key = make_pair(r->getId(), pp[k]->getControlId()); bool accept = !processedPunches.count(key) || abs(processedPunches[key] - pp[k]->getAdjustedTime()) > 5; if (accept && !hasCheckedOld && pp[k]->getTypeCode() == fromPunch) { hasCheckedOld = true; while (rToWatch.size() > 1) { watchedR.push_back(rToWatch.front()); rToWatch.erase(rToWatch.begin()); // TODO: Better algorithm for forgetting? } } processedPunches[key] = pp[k]->getAdjustedTime(); if (accept) { if (pp[k]->getTypeCode() == fromPunch) { enter.push_back(make_pair(pp[k]->getAdjustedTime(), pp[k])); } else if (pp[k]->getTypeCode() == toPunch) { if (count(rToWatch.begin(), rToWatch.end(), r->getId()) > 0) { exit.push_back(make_pair(pp[k]->getAdjustedTime(), pp[k])); } else if (count(watchedR.begin(), watchedR.end(), r->getId()) > 0) { backExit.push_back(make_pair(pp[k]->getAdjustedTime(), pp[k])); } } } } sort(enter.begin(), enter.end()); sort(exit.begin(), exit.end()); int h,w; gdi.getTargetDimension(w, h); bool doRefresh = false; for (size_t k = 0; k < enter.size(); k++) { showResultList = -1; const oFreePunch *fp = enter[k].second; pRunner newRToWatch = fp->getTiedRunner(); if (count(rToWatch.begin(), rToWatch.end(), newRToWatch->getId())) continue; rToWatch.push_back(newRToWatch->getId()); gdi.restore("LiveResult", false); startFinishTime[newRToWatch->getId()].first = fp->getAdjustedTime(); isDuel = false; if (rToWatch.size() == 1) { wstring font = getFont(gdi, timerScale); BaseInfo *bi = gdi.setText("timing", newRToWatch->getName(), false); dynamic_cast(*bi).changeFont(getFont(gdi, 0.7)); gdi.addTimer(h/2, w/2, boldHuge|textCenter|timeWithTenth, 0, 0, 0, NOTIMEOUT, font.c_str()); screenSize = 1; } else if (rToWatch.size() == 2) { wstring font = getFont(gdi, timerScale * 0.6); pRunner r0 = oe->getRunner(rToWatch[0], 0); pRunner r1 = oe->getRunner(rToWatch[1], 0); wstring n = (r0 ? r0->getName(): L"-") + L" / " + (r1 ? r1->getName() : L"-"); bool duel = r0 && r1 && fromPunch == oPunch::PunchStart && r0->getTeam() != 0 && r0->getTeam() == r1->getTeam(); isDuel = duel; if (n.length() < 30) { BaseInfo *bi = gdi.setText("timing", n, false); TextInfo &ti = dynamic_cast(*bi); ti.changeFont(getFont(gdi, 0.5)); } else { BaseInfo *bi = gdi.setText("timing", L"", false); TextInfo &ti = dynamic_cast(*bi); wstring sfont = getFont(gdi, 0.5); TextInfo &ti2 = gdi.addString("n1", ti.yp, gdi.scaleLength(20), boldHuge, L"#" + (r0 ? r0->getName() : wstring(L"")), 0, 0, sfont.c_str()); gdi.addString("n2", ti.yp + ti2.getHeight() + 4, gdi.getWidth(), boldHuge | textRight, L"#" + (r1 ? r1->getName() : wstring(L"")), 0, 0, sfont.c_str()); } int id1 = rToWatch[0]; int id2 = rToWatch[1]; int t1 = startFinishTime[id1].first; int diff = abs(fp->getAdjustedTime() - t1); runner2ScreenPos[id1] = 1; runner2ScreenPos[id2] = 2; screenSize = 2; int startTimeR2 = 0; if (duel) { // Ensure same start time int t2 = startFinishTime[id2].first; int st = min(t1,t2); startFinishTime[id1].first = st; startFinishTime[id2].first = st; for (size_t i = 0; i < rToWatch.size(); i++) { pRunner r = oe->getRunner(rToWatch[i], 0); if (r) { r->synchronize(); r->setStartTime(st, true, oBase::ChangeType::Update, true); r->synchronize(false); } } startTimeR2 = diff; } gdi.addTimer(h/2, w/2-w/4, boldHuge|textCenter|timeWithTenth, diff, 0, 0, NOTIMEOUT, font.c_str()).id = "timer1"; gdi.addTimer(h/2, w/2+w/4, boldHuge|textCenter|timeWithTenth, startTimeR2, 0, 0, NOTIMEOUT, font.c_str()).id = "timer2"; } doRefresh = true; } for (size_t k = 0; k < exit.size(); k++) { const oFreePunch *fp = exit[k].second; pRunner rToFinish = fp->getTiedRunner(); if (count(rToWatch.begin(), rToWatch.end(), rToFinish->getId()) > 0) { showResultList = -1; pair &se = startFinishTime[rToFinish->getId()]; se.second = fp->getAdjustedTime(); int rt = se.second - se.first; size_t ix = find(rToWatch.begin(), rToWatch.end(), rToFinish->getId()) - rToWatch.begin(); if (screenSize == 1) { gdi.restore("LiveResult", false); wstring font = getFont(gdi, timerScale); gdi.addString("", h/2, w/2, boldHuge|textCenter, formatTime(rt), 0, 0, font.c_str()).setColor(colorGreen); gdi.addTimeout(5, 0).setHandler(this); } else if (screenSize == 2) { string id = "timer" + itos(runner2ScreenPos[rToFinish->getId()]); BaseInfo *bi = gdi.setText(id, formatTime(rt), false); wstring font = getFont(gdi, timerScale * 0.6); if (bi) { TextInfo &ti = dynamic_cast(*bi); ti.format = boldHuge|textCenter; ti.hasTimer = false; if (rToWatch.size() == 2 || !isDuel) ti.setColor(colorGreen); } if (rToWatch.size() == 1) { gdi.addTimeout(5, 0).setHandler(this); } } rToWatch.erase(rToWatch.begin() + ix); doRefresh = true; } } for (size_t k = 0; k < backExit.size(); k++) { const oFreePunch *fp = backExit[k].second; pRunner rToFinish = fp->getTiedRunner(); if (count(watchedR.begin(), watchedR.end(), rToFinish->getId()) > 0) { pair &se = startFinishTime[rToFinish->getId()]; se.second = fp->getAdjustedTime(); size_t ix = find(watchedR.begin(), watchedR.end(), rToFinish->getId()) - watchedR.begin(); watchedR.erase(watchedR.begin() + ix); } } if (doRefresh) gdi.refreshFast(); else { auto resCopy = results; calculateResults(); bool reshow = false; if (resCopy.size() != results.size()) reshow = true; else { for (size_t i = 0; i < results.size(); i++) { if (resCopy[i].name.empty()) break; if (resCopy[i].runnerId != results[i].runnerId) { reshow = true; break; } if (resCopy[i].time != results[i].time) { reshow = true; break; } pRunner r = oe->getRunner(results[i].runnerId, 0); if (!r || resCopy[i].name != r->getName()) { reshow = true; break; } } } if (reshow) { showResults(gdi); } } } else if (type == GUI_TIMEOUT) { showResults(gdi); } else if (type == GUI_TIMER) { if (size_t(showResultList) >= results.size()) return; Result &res = results[showResultList]; wstring font = getFont(gdi, 0.7); int y = resYPos; pRunner r = oe->getRunner(res.runnerId, 0); if (!r) { showResultList++; gdi.addTimeoutMilli(10, "res" + itos(showResultList), 0).setHandler(this); } else if (res.place > 0) { res.name = r->getName(); int h,w; gdi.getTargetDimension(w, h); gdi.takeShownStringsSnapshot(); TextInfo &ti = gdi.addStringUT(y, 30, fontLarge, itow(res.place) + L".", 0, 0, font.c_str()); int ht = ti.textRect.bottom - ti.textRect.top; gdi.addStringUT(y, 30 + ht * 2 , fontLarge, r->getName(), 0, 0, font.c_str()); //int w = gdi.getWidth(); gdi.addStringUT(y, w - 4 * ht, fontLarge, formatTime(res.time), 0, 0, font.c_str()); gdi.refreshSmartFromSnapshot(false); resYPos += int (ht * 1.1); showResultList++; int limit = h - ht * 2; //OutputDebugString(("w:" + itos(resYPos) + " " + itos(limit) + "\n").c_str()); if ( resYPos < limit ) gdi.addTimeoutMilli(300, "res" + itos(showResultList), 0).setHandler(this); } } } void LiveResult::showResults(gdioutput &gdi) { gdi.restore("LiveResult", false); int h, w; gdi.getTargetDimension(w, h); gdi.fillDown(); BaseInfo* bi = gdi.setTextTranslate("timing", L"MeOS Timing", false); TextInfo& ti = dynamic_cast(*bi); ti.changeFont(getFont(gdi, 0.7)); gdi.refreshFast(); resYPos = ti.textRect.bottom + gdi.scaleLength(20); calculateResults(); showResultList = 0; gdi.addTimeoutMilli(300, "res", 0).setHandler(this); } void LiveResult::calculateResults() { rToWatch.clear(); results.clear(); results.reserve(startFinishTime.size()); const int highTime = 10000000; for (map >::iterator it = startFinishTime.begin(); it != startFinishTime.end(); ++it) { pRunner r = oe->getRunner(it->first, 0); if (!r) continue; results.push_back(Result()); results.back().runnerId = it->first; results.back().time = it->second.second - it->second.first; if (results.back().time <= 0 || r->getStatus() > StatusOK) results.back().time = highTime; } sort(results.begin(), results.end()); int place = 1; for (size_t k = 0; k< results.size(); k++) { if (results[k].time < highTime) { if (k>0 && results[k-1].time < results[k].time) place = k + 1; results[k].place = place; } else { results[k].place = 0; } } }