435 lines
15 KiB
C++
435 lines
15 KiB
C++
/********************i****************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2022 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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 <cassert>
|
|
#include "Localizer.h"
|
|
#include <algorithm>
|
|
#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");
|
|
|
|
lastTime = 0;
|
|
vector<const oFreePunch *> pp;
|
|
oe->synchronizeList({ oListId::oLRunnerId, oListId::oLPunchId });
|
|
|
|
oe->getLatestPunches(lastTime, pp);
|
|
processedPunches.clear();
|
|
|
|
map<int, pair<vector<int>, vector<int> > > 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<int, int> 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<int, pair<vector<int>, vector<int> > >::iterator it = storedPunches.begin();
|
|
it != storedPunches.end(); ++it) {
|
|
vector<int> &froms = it->second.first;
|
|
vector<int> &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;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
resYPos = h/3;
|
|
|
|
calculateResults();
|
|
showResultList = 0;
|
|
gdi.addTimeoutMilli(1000, "res", 0).setHandler(this);
|
|
gdi.refreshFast();
|
|
}
|
|
|
|
|
|
void LiveResult::handle(gdioutput &gdi, BaseInfo &bu, GuiEventType type) {
|
|
if (type == GUI_EVENT) {
|
|
vector<const oFreePunch *> 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<int, const oFreePunch*> > 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<int, int> 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<TextInfo &>(*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<TextInfo &>(*bi);
|
|
ti.changeFont(getFont(gdi, 0.5));
|
|
}
|
|
else {
|
|
BaseInfo *bi = gdi.setText("timing", L"", false);
|
|
TextInfo &ti = dynamic_cast<TextInfo &>(*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<int,int> &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<TextInfo &>(*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<int,int> &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 if (type == GUI_TIMEOUT) {
|
|
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<TextInfo &>(*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);
|
|
}
|
|
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) {
|
|
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::calculateResults() {
|
|
rToWatch.clear();
|
|
results.clear();
|
|
results.reserve(startFinishTime.size());
|
|
const int highTime = 10000000;
|
|
for (map<int, pair<int, int> >::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;
|
|
}
|
|
}
|
|
}
|