From 032a39ab1eb391c1ece7a17df2a1e6e11b8cef71 Mon Sep 17 00:00:00 2001 From: Erik Melin <31467290+erikmelin@users.noreply.github.com> Date: Thu, 9 Feb 2023 08:15:40 +0100 Subject: [PATCH] MeOS version 3.9.1399 Beta --- code/HTMLWriter.cpp | 273 ++++-- code/HTMLWriter.h | 60 +- code/MeOSFeatures.cpp | 2 +- code/MeOSFeatures.h | 2 +- code/MeosSQL.cpp | 319 ++++++- code/MeosSQL.h | 9 +- code/Printer.h | 2 +- code/RestService.cpp | 2 +- code/RestService.h | 2 +- code/RunnerDB.cpp | 172 +++- code/RunnerDB.h | 28 +- code/SportIdent.cpp | 378 +++++--- code/SportIdent.h | 43 +- code/StdAfx.h | 16 +- code/TabAuto.cpp | 34 +- code/TabAuto.h | 2 +- code/TabBase.cpp | 2 +- code/TabBase.h | 2 +- code/TabClass.cpp | 134 ++- code/TabClass.h | 9 +- code/TabClub.cpp | 9 +- code/TabClub.h | 2 +- code/TabCompetition.cpp | 275 ++++-- code/TabCompetition.h | 7 +- code/TabControl.cpp | 187 +++- code/TabControl.h | 2 +- code/TabCourse.cpp | 114 ++- code/TabCourse.h | 4 +- code/TabList.cpp | 181 ++-- code/TabList.h | 4 +- code/TabMulti.cpp | 2 +- code/TabMulti.h | 2 +- code/TabRunner.cpp | 198 +++- code/TabRunner.h | 5 +- code/TabSI.cpp | 1696 ++++++++++++++++++++-------------- code/TabSI.h | 66 +- code/TabSpeaker.cpp | 12 +- code/TabSpeaker.h | 2 +- code/TabTeam.cpp | 87 +- code/TabTeam.h | 6 +- code/Table.cpp | 5 +- code/Table.h | 7 +- code/TimeStamp.cpp | 49 +- code/TimeStamp.h | 4 +- code/animationdata.cpp | 4 +- code/animationdata.h | 2 +- code/autocomplete.cpp | 9 +- code/autocomplete.h | 11 +- code/autocompletehandler.h | 2 +- code/autotask.cpp | 2 +- code/autotask.h | 2 +- code/binencoder.cpp | 204 ++++ code/binencoder.h | 39 + code/classconfiginfo.cpp | 6 +- code/classconfiginfo.h | 2 +- code/csvparser.cpp | 20 +- code/csvparser.h | 2 +- code/download.cpp | 2 +- code/download.h | 2 +- code/english.lng | 82 +- code/gdiconstants.h | 2 +- code/gdifonts.h | 3 +- code/gdiimpl.h | 2 +- code/gdioutput.cpp | 839 ++++++++++++----- code/gdioutput.h | 675 +++++++------- code/gdistructures.h | 17 +- code/generalresult.cpp | 71 +- code/generalresult.h | 2 +- code/guihandler.h | 2 +- code/image.cpp | 193 +++- code/image.h | 51 +- code/importformats.cpp | 2 +- code/info24.png | Bin 0 -> 874 bytes code/infoserver.cpp | 16 +- code/infoserver.h | 6 +- code/inthashmap.h | 2 +- code/intkeymap.hpp | 2 +- code/intkeymapimpl.hpp | 2 +- code/iof30interface.cpp | 237 +++-- code/iof30interface.h | 23 +- code/license.txt | 2 +- code/listeditor.cpp | 697 +++++++++++--- code/listeditor.h | 24 +- code/liveresult.cpp | 104 ++- code/liveresult.h | 7 +- code/localizer.cpp | 2 +- code/localizer.h | 2 +- code/machinecontainer.cpp | 2 +- code/meos.cpp | 11 +- code/meos.rc | 1 + code/meos_util.cpp | 366 ++++++-- code/meos_util.h | 27 +- code/meosexception.h | 5 +- code/meosvc15.vcxproj | 3 + code/meosversion.cpp | 60 +- code/metalist.cpp | 369 ++++++-- code/metalist.h | 81 +- code/methodeditor.cpp | 18 +- code/methodeditor.h | 2 +- code/mysqldaemon.cpp | 6 +- code/mysqlwrapper.cpp | 25 +- code/mysqlwrapper.h | 5 +- code/newcompetition.cpp | 178 ++-- code/oBase.cpp | 5 +- code/oBase.h | 5 +- code/oCard.cpp | 132 +-- code/oCard.h | 8 +- code/oClass.cpp | 330 +++++-- code/oClass.h | 21 +- code/oClub.cpp | 27 +- code/oClub.h | 2 +- code/oControl.cpp | 461 ++++++--- code/oControl.h | 46 +- code/oCourse.cpp | 138 ++- code/oCourse.h | 7 +- code/oDataContainer.cpp | 199 +++- code/oDataContainer.h | 108 ++- code/oEvent.cpp | 691 +++++++++----- code/oEvent.h | 130 ++- code/oEventDraw.cpp | 26 +- code/oEventDraw.h | 2 +- code/oEventResult.cpp | 24 +- code/oEventSQL.cpp | 52 +- code/oEventSpeaker.cpp | 28 +- code/oFreeImport.cpp | 8 +- code/oFreeImport.h | 2 +- code/oFreePunch.cpp | 126 ++- code/oFreePunch.h | 11 +- code/oImportExport.cpp | 102 +- code/oListInfo.cpp | 1173 +++++++++++++++-------- code/oListInfo.h | 93 +- code/oPunch.cpp | 145 ++- code/oPunch.h | 71 +- code/oReport.cpp | 2 +- code/oRunner.cpp | 744 +++++++++------ code/oRunner.h | 128 ++- code/oTeam.cpp | 112 ++- code/oTeam.h | 17 +- code/oTeamEvent.cpp | 39 +- code/oevent_transfer.cpp | 13 +- code/onlineinput.cpp | 50 +- code/onlineinput.h | 3 +- code/onlineresults.cpp | 45 +- code/onlineresults.h | 4 +- code/ospeaker.h | 2 +- code/parser.cpp | 2 +- code/parser.h | 2 +- code/pdfwriter.cpp | 21 +- code/pdfwriter.h | 2 +- code/prefseditor.cpp | 2 +- code/prefseditor.h | 2 +- code/printer.cpp | 2 +- code/progress.cpp | 2 +- code/progress.h | 2 +- code/qualification_final.cpp | 2 +- code/qualification_final.h | 2 +- code/random.cpp | 2 +- code/random.h | 2 +- code/recorder.cpp | 2 +- code/recorder.h | 2 +- code/resource.h | 2 + code/restserver.cpp | 61 +- code/restserver.h | 2 +- code/socket.cpp | 2 +- code/socket.h | 2 +- code/speakermonitor.cpp | 8 +- code/speakermonitor.h | 2 +- code/subcommand.h | 2 +- code/swedish.lng | 70 +- code/testmeos.cpp | 9 +- code/testmeos.h | 2 +- code/tests.cpp | 2 +- code/timeconstants.hpp | 11 + code/toolbar.cpp | 2 +- code/toolbar.h | 2 +- code/xmlparser.cpp | 89 +- code/xmlparser.h | 56 +- 177 files changed, 10182 insertions(+), 4475 deletions(-) create mode 100644 code/binencoder.cpp create mode 100644 code/binencoder.h create mode 100644 code/info24.png create mode 100644 code/timeconstants.hpp diff --git a/code/HTMLWriter.cpp b/code/HTMLWriter.cpp index 0ff824f..91b1cbf 100644 --- a/code/HTMLWriter.cpp +++ b/code/HTMLWriter.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -34,6 +34,9 @@ #include "Printer.h" #include "oListInfo.h" #include "meosexception.h" +#include "image.h" + +extern Image image; #include #include @@ -139,10 +142,11 @@ public: template void HTMLWriter::formatTL(ostream &fout, - const map, pair> &styles, - const T &tl, - double &yscale, double &xscale, - int &offsetY, int &offsetX) { + ImageWriter& imageWriter, + const map, pair> &styles, + const T &tl, + double &yscale, double &xscale, + int &offsetY, int &offsetX) { auto itt = tl.begin(); @@ -158,6 +162,14 @@ void HTMLWriter::formatTL(ostream &fout, string yp = itos(int(yscale*TI::y(ctr_it)) + offsetY); string xp = itos(int(xscale*TI::x(ctr_it)) + offsetX); + if ((it.format & 0xFF) == textImage) { + int imgW = int((it.textRect.right - it.textRect.left) * xscale); + int imgH = int((it.textRect.bottom - it.textRect.top) * xscale); + imageWriter.write(fout, xp, yp, it.text, imgW, imgH); + ++itt; + continue; + } + string estyle; if (it.format != 1 && it.format != boldSmall) { if (it.format & textRight) @@ -274,17 +286,31 @@ void HTMLWriter::writeHTML(gdioutput &gdi, const wstring &file, if (fout.bad()) throw std::exception("Bad output stream"); - writeHTML(gdi, fout, title, refreshTimeOut, scale); + wchar_t drive[20]; + wchar_t dir[MAX_PATH]; + wchar_t name[MAX_PATH]; + wchar_t ext[MAX_PATH]; + _wsplitpath_s(file.c_str(), drive, dir, name, ext); + wstring path = wstring(drive) + dir; + + writeHTML(gdi, fout, title, true, path, refreshTimeOut, scale); } -void HTMLWriter::writeHTML(gdioutput &gdi, ostream &fout, const wstring &title, int refreshTimeOut, double scale) { +void HTMLWriter::writeHTML(gdioutput& gdi, ostream& fout, + const wstring& title, + bool includeImages, + const wstring& imageDirectoryDestination, + int refreshTimeOut, double scale) { + + ImageWriter imgWriter(imageDirectoryDestination, includeImages); + if (scale <= 0) scale = 1.0; fout << "" << endl; fout << "\n\n"; fout << "\n"; - + if (refreshTimeOut > 0) fout << "\n"; @@ -299,16 +325,16 @@ void HTMLWriter::writeHTML(gdioutput &gdi, ostream &fout, const wstring &title, double yscale = 1.3 * scale; double xscale = 1.2 * scale; int offsetY = 0, offsetX = 0; - HTMLWriter::formatTL, InterpTextInfo>(fout, styles, gdi.getTL(), yscale, xscale, offsetY, offsetX); - - fout << "

"; + HTMLWriter::formatTL, InterpTextInfo>(fout, imgWriter, styles, gdi.getTL(), yscale, xscale, offsetY, offsetX); + + fout << "

"; char bf1[256]; char bf2[256]; GetTimeFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf2, 256); GetDateFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf1, 256); //fout << "Skapad av MeOS: " << bf1 << " "<< bf2 << "\n"; - fout << gdioutput::toUTF8(lang.tl("Skapad av ")) + "MeOS: " << bf1 << " "<< bf2 << "\n"; + fout << gdioutput::toUTF8(lang.tl("Skapad av ")) + "MeOS: " << bf1 << " " << bf2 << "\n"; fout << "

\n"; fout << "\n"; @@ -341,18 +367,29 @@ void HTMLWriter::writeTableHTML(gdioutput &gdi, if (fout.bad()) return throw std::exception("Bad output stream"); - writeTableHTML(gdi, fout, title, false, refreshTimeOut, scale); + wchar_t drive[20]; + wchar_t dir[MAX_PATH]; + wchar_t name[MAX_PATH]; + wchar_t ext[MAX_PATH]; + _wsplitpath_s(file.c_str(), drive, dir, name, ext); + wstring path = wstring(drive) + dir; + + writeTableHTML(gdi, fout, title, true, path, false, refreshTimeOut, scale); } -void HTMLWriter::writeTableHTML(gdioutput &gdi, - ostream &fout, - const wstring &title, +void HTMLWriter::writeTableHTML(gdioutput& gdi, + ostream& fout, + const wstring& title, + bool includeImages, + const wstring& imageDirectoryDestination, bool simpleFormat, int refreshTimeOut, double scale) { if (scale <= 0) scale = 1.0; + ImageWriter imgWriter(imageDirectoryDestination, includeImages); + fout << "" << endl; fout << "\n\n"; fout << "\n"; @@ -366,14 +403,14 @@ void HTMLWriter::writeTableHTML(gdioutput &gdi, fout << "\n"; fout << "\n"; - auto &TL = gdi.getTL(); + auto& TL = gdi.getTL(); auto it = TL.begin(); int MaxX = gdi.getPageX() - 100; - map tableCoordinates; + map tableCoordinates; //Get x-coordinates - while (it!=TL.end()) { - tableCoordinates[it->xp]=0; + while (it != TL.end()) { + tableCoordinates[it->xp] = 0; ++it; } @@ -384,47 +421,47 @@ void HTMLWriter::writeTableHTML(gdioutput &gdi, ++mit; } tableCoordinates[MaxX] = kr; - + vector sizeSet(kr + 1, false); fout << "\n"; - int linecounter=0; - it=TL.begin(); + int linecounter = 0; + it = TL.begin(); - vector< pair > > rows; + vector< pair > > rows; rows.reserve(TL.size() / 3); vector ypRow; int minHeight = 100000; - while (it!=TL.end()) { - int y=it->yp; - vector row; + while (it != TL.end()) { + int y = it->yp; + vector row; int subnormal = 0; int normal = 0; int header = 0; int mainheader = 0; - while (it!=TL.end() && it->yp==y) { + while (it != TL.end() && it->yp == y) { if (!gdioutput::skipTextRender(it->format)) { row.push_back(&*it); switch (it->getGdiFont()) { - case fontLarge: - case boldLarge: - case boldHuge: - mainheader++; - break; - case boldText: - case italicMediumPlus: - case fontMediumPlus: - header++; - break; - case fontSmall: - case italicSmall: - subnormal++; - break; - default: - normal++; + case fontLarge: + case boldLarge: + case boldHuge: + mainheader++; + break; + case boldText: + case italicMediumPlus: + case fontMediumPlus: + header++; + break; + case fontSmall: + case italicSmall: + subnormal++; + break; + default: + normal++; } } ++it; @@ -444,27 +481,27 @@ void HTMLWriter::writeTableHTML(gdioutput &gdi, int last = ypRow.size(); ypRow.push_back(y); if (last > 0) { - minHeight = min(minHeight, ypRow[last] - ypRow[last-1]); + minHeight = min(minHeight, ypRow[last] - ypRow[last - 1]); } } int numMin = 0; for (size_t gCount = 1; gCount < rows.size(); gCount++) { - int h = ypRow[gCount] - ypRow[gCount-1]; + int h = ypRow[gCount] - ypRow[gCount - 1]; if (h == minHeight) numMin++; } if (numMin == 0) numMin = 1; - int hdrLimit = (rows.size() / numMin) <= 4 ? int(minHeight * 1.2) : int(minHeight * 1.5); + int hdrLimit = (rows.size() / numMin) <= 4 ? int(minHeight * 1.2) : int(minHeight * 1.5); for (size_t gCount = 1; gCount + 1 < rows.size(); gCount++) { int type = rows[gCount].first; - int lastType = gCount > 0 ? rows[gCount-1].first : 0; + int lastType = gCount > 0 ? rows[gCount - 1].first : 0; int nextType = gCount + 1 < rows.size() ? rows[gCount + 1].first : 0; if (type == 0 && (lastType == 1 || lastType == 2) && (nextType == 1 || nextType == 2)) continue; // No reclassify - int h = ypRow[gCount] - ypRow[gCount-1]; + int h = ypRow[gCount] - ypRow[gCount - 1]; if (h > hdrLimit && rows[gCount].first == 0) rows[gCount].first = 2; } @@ -472,12 +509,12 @@ void HTMLWriter::writeTableHTML(gdioutput &gdi, ypRow.clear(); string lineclass; for (size_t gCount = 0; gCount < rows.size(); gCount++) { - vector &row = rows[gCount].second; + vector& row = rows[gCount].second; int type = rows[gCount].first; - int lastType = gCount > 0 ? rows[gCount-1].first : 0; + int lastType = gCount > 0 ? rows[gCount - 1].first : 0; int nextType = gCount + 1 < rows.size() ? rows[gCount + 1].first : 0; - vector::iterator rit; + vector::iterator rit; fout << "" << endl; if (simpleFormat) { @@ -498,48 +535,55 @@ void HTMLWriter::writeTableHTML(gdioutput &gdi, lineclass = ""; } else - lineclass = (linecounter&1) ? " class=\"e1\"" : " class=\"e0\""; + lineclass = (linecounter & 1) ? " class=\"e1\"" : " class=\"e0\""; linecounter++; } - for (size_t k=0;kxp]; + for (size_t k = 0; k < row.size(); k++) { + int thisCol = tableCoordinates[row[k]->xp]; -if (k == 0 && thisCol != 0) -fout << " "; + if (k == 0 && thisCol != 0) + fout << " "; -int nextCol; -if (row.size() == k + 1) -nextCol = tableCoordinates.rbegin()->second; -else -nextCol = tableCoordinates[row[k + 1]->xp]; + int nextCol; + if (row.size() == k + 1) + nextCol = tableCoordinates.rbegin()->second; + else + nextCol = tableCoordinates[row[k + 1]->xp]; -int colspan = nextCol - thisCol; + int colspan = nextCol - thisCol; -assert(colspan > 0); + assert(colspan > 0); -string style; + string style; -if (row[k]->format&textRight) -style = " style=\"text-align:right\""; + if (row[k]->format & textRight) + style = " style=\"text-align:right\""; -if (colspan == 1 && !sizeSet[thisCol]) { - fout << " xp - row[k]->xp) : (MaxX - row[k]->xp)) << "\">"; - sizeSet[thisCol] = true; -} -else if (colspan > 1) -fout << " "; -else -fout << " "; + if (colspan == 1 && !sizeSet[thisCol]) { + fout << " xp - row[k]->xp) : (MaxX - row[k]->xp)) << "\">"; + sizeSet[thisCol] = true; + } + else if (colspan > 1) + fout << " "; + else + fout << " "; -gdiFonts font = row[k]->getGdiFont(); -string starttag, endtag; -getStyle(styles, font, gdioutput::narrow(row[k]->font), "", starttag, endtag); - -fout << starttag << gdioutput::toUTF8(html_table_code(row[k]->text)) << endtag << "" << endl; + if ((row[k]->format & 0xFF) == textImage) { + int imgW = int((row[k]->textRect.right - row[k]->textRect.left) * scale); + int imgH = int((row[k]->textRect.bottom - row[k]->textRect.top) * scale); + imgWriter.write(fout, "", "", row[k]->text, imgW, imgH); + fout << "" << endl; + } + else { + gdiFonts font = row[k]->getGdiFont(); + string starttag, endtag; + getStyle(styles, font, gdioutput::narrow(row[k]->font), "", starttag, endtag); + fout << starttag << gdioutput::toUTF8(html_table_code(row[k]->text)) << endtag << "" << endl; + } } fout << "\n"; @@ -555,7 +599,7 @@ fout << starttag << gdioutput::toUTF8(html_table_code(row[k]->text)) << endtag < GetTimeFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf2, 256); GetDateFormatA(LOCALE_USER_DEFAULT, 0, NULL, NULL, bf1, 256); wstring meos = getMeosCompectVersion(); - fout << gdioutput::toUTF8(lang.tl("Skapad av ")) + "MeOS " + fout << gdioutput::toUTF8(lang.tl("Skapad av ")) + "MeOS " << gdioutput::toUTF8(meos) << ": " << bf1 << " " << bf2 << "\n"; fout << "


\n"; } @@ -781,8 +825,11 @@ void HTMLWriter::generate(gdioutput &gdi, double scale) const { int w, h; gdi.getTargetDimension(w, h); + + + ImageWriter imgWriter(L"", false); - string meos = "MeOS: " + gdioutput::toUTF8(getMeosCompectVersion()) + ""; + string meos = "MeOS: " + gdioutput::toUTF8(getMeosCompectVersion()) + ""; int margin = (w * marginPercent) / 100; int height = nRows * gdi.getLineHeight(); @@ -886,7 +933,7 @@ void HTMLWriter::generate(gdioutput &gdi, double xscale = 1.2 * scale; int offsetY = 0, offsetX = 0; - formatTL, InterpPrintTextInfo>(sout, styles, p.text, yscale, xscale, offsetY, offsetX); + formatTL, InterpPrintTextInfo>(sout, imgWriter, styles, p.text, yscale, xscale, offsetY, offsetX); output = innerpage; replaceAll(output, "@P", itos(ipCounter++)); @@ -921,17 +968,27 @@ void HTMLWriter::write(gdioutput &gdi, const wstring &file, const wstring &title checkWriteAccess(file); ofstream fout(file.c_str()); - write(gdi, fout, title, contentsDescription, respectPageBreak, typeTag, refresh, + wchar_t drive[20]; + wchar_t dir[MAX_PATH]; + wchar_t name[MAX_PATH]; + wchar_t ext[MAX_PATH]; + _wsplitpath_s(file.c_str(), drive, dir, name, ext); + wstring path = wstring(drive) + dir; + + write(gdi, fout, title, true, path, contentsDescription, respectPageBreak, typeTag, refresh, rows, cols, time_ms, margin, scale); } -void HTMLWriter::write(gdioutput &gdi, ostream &fout, const wstring &title, const wstring &contentsDescription, +void HTMLWriter::write(gdioutput &gdi, ostream &fout, const wstring &title, + bool includeImages, + const wstring& imageDirectoryDestination, + const wstring &contentsDescription, bool respectPageBreak, const string &typeTag, int refresh, int rows, int cols, int time_ms, int margin, double scale) { if (typeTag == "table") - writeTableHTML(gdi, fout, title, false, refresh, scale); + writeTableHTML(gdi, fout, title, includeImages, imageDirectoryDestination, false, refresh, scale); else if (typeTag == "free") { - writeHTML(gdi, fout, title, refresh, scale); + writeHTML(gdi, fout, title, includeImages, imageDirectoryDestination, refresh, scale); } else { /* auto res = tCache.find(typeTag); @@ -972,6 +1029,44 @@ void HTMLWriter::write(gdioutput &gdi, const wstring &file, const wstring &title param.timePerPage, param.margin, param.htmlScale); } +void HTMLWriter::write(gdioutput& gdi, ostream& fout, const wstring& title, int refresh, oListParam& param, const oEvent& oe) { + write(gdi, fout, title, true, L"", param.getContentsDescriptor(oe), param.pageBreak, param.htmlTypeTag, + refresh != 0 ? refresh : param.timePerPage / 1000, param.htmlRows, param.nColumns, + param.timePerPage, param.margin, param.htmlScale); +} + void HTMLWriter::getPage(const oEvent &oe, string &out) const { out = page; -} \ No newline at end of file +} + +void HTMLWriter::ImageWriter::write(ostream& fout, const string& xp, const string& yp, const wstring& img, int width, int height) { + if (!writeImages) { + if (xp.empty()) + fout << " "; + } + else { + if (img.empty() || img[0] != 'L') + throw meosException("Unsupported image"); + uint64_t imgId = _wcstoui64(img.c_str() + 1, nullptr, 10); + + if (!savedFiles.count(imgId)) { + if (!destination.empty()) { + auto& data = image.getRawData(imgId); + wstring d = destination + img + L".png"; + ofstream out(d, ofstream::out | ofstream::binary); + out.write((const char *)data.data(), data.size()); + savedFiles[imgId] = "L" + itos(imgId) + ".png"; + } + else { + savedFiles[imgId] = "/meos?image=ID" + itos(imgId) + ".png"; + } + } + string style; + if (xp.size() > 0) + style = " style=\"position:absolute;left:" + xp + "px;top:" + yp + "px\""; + + fout << ""; + + } +} diff --git a/code/HTMLWriter.h b/code/HTMLWriter.h index c8c1c9b..dd8e401 100644 --- a/code/HTMLWriter.h +++ b/code/HTMLWriter.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -39,6 +39,29 @@ class HTMLWriter { static string localize(const string &in); + + class ImageWriter { + wstring destination; + const bool writeImages; + const wstring imageDirectoryDestination; + map savedFiles; + public: + ImageWriter(const wstring& dst, bool writeImages) : destination(dst), writeImages(writeImages) {} + + + void write(ostream &fout, const string &xp, const string &yp, const wstring &img, int width, int height); + }; + + template + static void formatTL(ostream& fout, + ImageWriter& imageWriter, + const map< pair, pair >& styles, + const T& tl, + double& yscale, + double& xscale, + int& offsetY, + int& offsetX); + public: static void reset() { @@ -76,10 +99,15 @@ public: void getPage(const oEvent &oe, string &out) const; - static void writeHTML(gdioutput &gdi, ostream &dout, const wstring &title, int refreshTimeOut, double scale); + static void writeHTML(gdioutput &gdi, ostream &dout, const wstring &title, + bool includeImages, + const wstring& imageDirectoryDestination, + int refreshTimeOut, double scale); static void writeTableHTML(gdioutput &gdi, ostream &fout, const wstring &title, + bool includeImages, + const wstring &imageDirectoryDestination, bool simpleFormat, int refreshTimeOut, double scale); @@ -92,23 +120,17 @@ public: static void writeHTML(gdioutput &gdi, const wstring &file, const wstring &title, int refreshTimeOut, double scale); - static void write(gdioutput &gdi, const wstring &file, const wstring &title, const wstring &contentsDescription, - bool respectPageBreak, const string &typeTag, int refresh, - int rows, int cols, int time_ms, int margin, double scale); + static void write(gdioutput& gdi, const wstring& file, const wstring& title, int refresh, oListParam& param, const oEvent& oe); + static void write(gdioutput& gdi, ostream& fout, const wstring& title, int refresh, oListParam& param, const oEvent& oe); - static void write(gdioutput &gdi, ostream &fout, const wstring &title, const wstring &contentsDescription, - bool respectPageBreak, const string &typeTag, int refresh, - int rows, int cols, int time_ms, int margin, double scale); - - static void write(gdioutput &gdi, const wstring &file, const wstring &title, int refresh, oListParam ¶m, const oEvent &oe); - - template - static void formatTL(ostream &fout, - const map< pair, pair > &styles, - const T &tl, - double &yscale, - double &xscale, - int &offsetY, - int &offsetX); + static void write(gdioutput& gdi, const wstring& file, const wstring& title, const wstring& contentsDescription, + bool respectPageBreak, const string& typeTag, int refresh, + int rows, int cols, int time_ms, int margin, double scale); + static void write(gdioutput& gdi, ostream& fout, const wstring& title, + bool includeImages, + const wstring& imageDirectoryDestination, + const wstring& contentsDescription, + bool respectPageBreak, const string& typeTag, int refresh, + int rows, int cols, int time_ms, int margin, double scale); }; \ No newline at end of file diff --git a/code/MeOSFeatures.cpp b/code/MeOSFeatures.cpp index 0195120..119142d 100644 --- a/code/MeOSFeatures.cpp +++ b/code/MeOSFeatures.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/MeOSFeatures.h b/code/MeOSFeatures.h index baeb596..29d8303 100644 --- a/code/MeOSFeatures.h +++ b/code/MeOSFeatures.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/MeosSQL.cpp b/code/MeosSQL.cpp index de7939d..02e4d31 100644 --- a/code/MeosSQL.cpp +++ b/code/MeosSQL.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -99,6 +99,11 @@ string C_INT64(string name) return " "+name+" BIGINT NOT NULL DEFAULT 0, "; } +string C_UINT64(string name) +{ + return " " + name + " BIGINT UNSIGNED NOT NULL DEFAULT 0, "; +} + string C_STRING(string name, int len=64) { char bf[16]; @@ -451,8 +456,21 @@ void MeosSQL::upgradeDB(const string &db, oDataContainer const * dc) { sql = sql.substr(0, sql.length() - 2); query.execute(sql); } + if (!eCol.count("BDate")) { + string sql = "ALTER TABLE " + db + " "; + sql += "ADD COLUMN " + C_INT("BDate"); + sql = sql.substr(0, sql.length() - 2); + query.execute(sql); + } + } + else if (db == "oPunch") { + if (!eCol.count("Unit")) { + string sql = "ALTER TABLE " + db + " "; + sql += "ADD COLUMN " + C_INT("Unit"); + sql = sql.substr(0, sql.length() - 2); + query.execute(sql); + } } - if (dc) { // Ugrade table string sqlAdd = dc->generateSQLDefinition(eCol); @@ -478,7 +496,8 @@ bool MeosSQL::openDB(oEvent *oe) return 0; } monitorId = 0; - string dbname(oe->currentNameId.begin(), oe->currentNameId.end());//WCS + string dbname(oe->currentNameId.begin(), oe->currentNameId.end()); + bool tookLock = false; try { auto query = con->query(); @@ -489,11 +508,29 @@ bool MeosSQL::openDB(oEvent *oe) auto row = res.at(0); int version = row["Version"]; - if (version < oEvent::dbVersion) { + if (version <= 88) { + query.exec("LOCK TABLE MeOSMain.oEvent WRITE"); + tookLock = true; + + query.reset(); + query << "SELECT Version FROM oEvent WHERE NameId=" << quote << dbname; + auto resV = query.store(); + if (res && res.num_rows() >= 1) { + version = res.at(0)["Version"]; + } + } + query.reset(); query << "UPDATE oEvent SET Version=" << oEvent::dbVersion << " WHERE Id=" << int(row["Id"]); query.execute(); + if (version <= 88) { + upgradeTimeFormat(dbname); + } + + if (tookLock) { + con->query().exec("UNLOCK TABLES"); + } } else if (version > oEvent::dbVersion) { alert("A newer version av MeOS is required."); @@ -524,6 +561,10 @@ bool MeosSQL::openDB(oEvent *oe) } } catch (const Exception& er) { + if (tookLock && con) { + con->query().exec("UNLOCK TABLES"); + } + setDefaultDB(); alert(string(er.what()) + " MySQL Error. Select DB."); return 0; @@ -588,6 +629,7 @@ bool MeosSQL::openDB(oEvent *oe) << C_INT("CardNo") << C_UINT("ReadId") << C_UINT("Voltage") + << C_INT("BDate") << C_STRING("Punches", 16*190) << C_END(); query.execute(); @@ -657,9 +699,12 @@ bool MeosSQL::openDB(oEvent *oe) query << C_START("oPunch") << C_INT("CardNo") << C_INT("Time") - << C_INT("Type") << C_END(); + << C_INT("Type") + << C_INT("Unit") << C_END(); query.execute(); + upgradeDB("oPunch", nullptr); + query.reset(); query << C_START("oMonitor") << C_STRING("Client") @@ -691,6 +736,13 @@ bool MeosSQL::openDB(oEvent *oe) // Create runner/club DB createRunnerDB(oe, query); + + query.reset(); + query << C_START_noid("oImage") + << C_UINT64("Id") + << C_TEXT("Filename") + << " Image LONGBLOB)" << engine(); + query.execute(); } catch (const Exception& er){ alert(string(er.what()) + " MySQL Error."); @@ -979,9 +1031,9 @@ OpFailStatus MeosSQL::uploadRunnerDB(oEvent *oe) auto query = con->query(); query << "INSERT INTO dbRunner SET " << "Name=" << quote << rdb[k].name << - ", ExtId=" << rdb[k].extId << ", Club=" << rdb[k].clubNo << + ", ExtId=" << rdb[k].getExtId() << ", Club=" << rdb[k].clubNo << ", CardNo=" << rdb[k].cardNo << ", Sex=" << quote << rdb[k].getSex() << - ", Nation=" << quote << rdb[k].getNationality() << ", BirthYear=" << rdb[k].birthYear; + ", Nation=" << quote << rdb[k].getNationality() << ", BirthYear=" << rdb[k].getBirthDateInt(); try { query.execute(); @@ -1031,7 +1083,7 @@ bool MeosSQL::storeData(oDataInterface odi, const RowWrapper &row, unsigned long } } else { - __int64 val = row[(const char*)it_int->name].ulonglong(); + __int64 val = row[(const char*)it_int->name].longlong(); __int64 oldVal = *(it_int->data64); if (val != oldVal) { memcpy(it_int->data64, &val, 8); @@ -1476,7 +1528,7 @@ OpFailStatus MeosSQL::SyncRead(oEvent *oe) { RunnerDBEntry &dbn = db->dbe(); if (sex.length()==1) dbn.sex = sex[0]; - dbn.birthYear = short(atoi(birth.c_str())); + dbn.setBirthDate(atoi(birth.c_str())); if (nat.length()==3) { dbn.national[0] = nat[0]; @@ -1546,6 +1598,7 @@ void MeosSQL::storeCard(const RowWrapper &row, oCard &c) c.cardNo = row["CardNo"]; c.readId = row["ReadId"]; c.miliVolt = row["Voltage"]; + c.batteryDate = row["BDate"]; c.importPunches(string(row["Punches"])); c.sqlUpdated = row["Modified"]; @@ -1565,10 +1618,10 @@ void MeosSQL::storePunch(const RowWrapper &row, oFreePunch &p, bool rehash) } else { p.CardNo = row["CardNo"]; - p.Time = row["Time"]; - p.Type = row["Type"]; + p.punchTime = row["Time"]; + p.type = row["Type"]; } - + p.punchUnit = row["Unit"]; p.sqlUpdated = row["Modified"]; p.counter = row["Counter"]; p.Removed = row["Removed"]; @@ -2178,6 +2231,7 @@ OpFailStatus MeosSQL::syncUpdate(oCard *c, bool forceWriteAll) auto queryset = con->query(); queryset << " CardNo=" << c->cardNo << ", ReadId=" << c->readId << ", Voltage=" << max(0, c->miliVolt) + << ", BDate=" << c->batteryDate << ", Punches=" << quote << c->getPunchString(); return syncUpdate(queryset, "oCard", c); @@ -2287,6 +2341,77 @@ OpFailStatus MeosSQL::syncRead(bool forceRead, oTeam *t) return syncRead(forceRead, t, true); } +void MeosSQL::upgradeTimeFormat(const string & dbname) { + bool ok = false; + try { + con->select_db(dbname); + ok = true; + } + catch (const Exception &) { + } + + if (ok) { + auto query = con->query(); + query.exec("LOCK TABLES oEvent WRITE, oClass WRITE, oControl WRITE, " + "oCourse WRITE, oPunch WRITE, oRunner WRITE, oTeam WRITE"); + } + else + return; + + auto upgradeCol = [this](const string &db, const string &col) { + auto query = con->query(); + string v = "UPDATE " + db + " SET " + col + "=" + col + "*10 WHERE " + col + "<>-1"; + try { + query.execute(v); + } + catch (const Exception &) { + return false; + } + return true; + }; + + auto alter = [this](const string &db, const string &col) { + auto query = con->query(); + string v = "ALTER TABLE " + db + " MODIFY COLUMN " + col + " INT NOT NULL DEFAULT 0"; + try { + query.execute(v); + } + catch (const Exception &) { + return false; + } + return true; + + }; + upgradeCol("oEvent", "ZeroTime"); + upgradeCol("oEvent", "MaxTime"); + upgradeCol("oEvent", "DiffTime"); + + upgradeCol("oClass", "FirstStart"); + upgradeCol("oClass", "StartInterval"); + upgradeCol("oClass", "MaxTime"); + + upgradeCol("oControl", "TimeAdjust"); + upgradeCol("oControl", "MinTime"); + + upgradeCol("oCourse", "RTimeLimit"); + + upgradeCol("oPunch", "Time"); + + upgradeCol("oRunner", "StartTime"); + upgradeCol("oRunner", "FinishTime"); + upgradeCol("oRunner", "InputTime"); + alter("oRunner", "TimeAdjust"); + upgradeCol("oRunner", "TimeAdjust"); + upgradeCol("oRunner", "EntryTime"); + + upgradeCol("oTeam", "StartTime"); + upgradeCol("oTeam", "FinishTime"); + upgradeCol("oTeam", "InputTime"); + alter("oTeam", "TimeAdjust"); + upgradeCol("oTeam", "TimeAdjust"); + upgradeCol("oTeam", "EntryTime"); +} + OpFailStatus MeosSQL::syncRead(bool forceRead, oTeam *t, bool readRecursive) { errorMessage.clear(); @@ -2536,7 +2661,7 @@ OpFailStatus MeosSQL::syncReadControls(oEvent *oe, const set &controls) { int counter = row["Counter"]; string modified = row["Modified"]; - pControl pc = oe->getControl(id, false); + pControl pc = oe->getControl(id, false, false); if (!pc) { oControl oc(oe, id); success = min(success, syncRead(true, &oc)); @@ -2550,7 +2675,7 @@ OpFailStatus MeosSQL::syncReadControls(oEvent *oe, const set &controls) { // processedCourses should now be empty, unless there are local controls not yet added. for(set::iterator it = processedControls.begin(); it != processedControls.end(); ++it) { - pControl pc = oe->getControl(*it, false); + pControl pc = oe->getControl(*it, false, false); if (pc) { success = min(success, syncUpdate(pc, true)); } @@ -2647,7 +2772,7 @@ OpFailStatus MeosSQL::syncUpdate(oControl *c, bool forceWriteAll) { auto queryset = con->query(); queryset << " Name=" << quote << toString(c->Name) << ", " << " Numbers=" << quote << toString(c->codeNumbers()) << "," - << " Status=" << c->Status + << " Status=" << int(c->Status) << c->getDI().generateSQLSet(forceWriteAll); return syncUpdate(queryset, "oControl", c); @@ -2826,8 +2951,9 @@ OpFailStatus MeosSQL::syncUpdate(oFreePunch *c, bool forceWriteAll) } auto queryset = con->query(); queryset << " CardNo=" << c->CardNo << ", " - << " Type=" << c->Type << "," - << " Time=" << c->Time; + << " Type=" << c->type << "," + << " Time=" << c->punchTime << "," + << " Unit=" << c->punchUnit; return syncUpdate(queryset, "oPunch", c); } @@ -2974,38 +3100,55 @@ OpFailStatus MeosSQL::syncUpdate(QueryWrapper &updateqry, if (ob->isRemoved()) return opStatusOK; bool setId = false; - + bool update = false; if (ob->Id > 0) { - query << "SELECT Id FROM " << oTable << " WHERE Id=" << ob->Id; + query << "SELECT Removed FROM " << oTable << " WHERE Id=" << ob->Id; auto res=query.store(); if (res.empty()) setId = true; else if (ob->isImplicitlyCreated()) { return opStatusWarning;//XXX Should we read this object? } + else { + int removed = res.at(0).at(0); + if (removed) { + update = true; + } + } } else { assert(!ob->isImplicitlyCreated()); } query.reset(); - query << "INSERT INTO " << oTable << " SET " << updateqry.str(); + if (update) { + query << "UPDATE " << oTable << " SET Removed = 0, " << updateqry.str(); + } + else { + query << "INSERT INTO " << oTable << " SET " << updateqry.str(); - if (setId) - query << ", Id=" << ob->Id; + if (setId) + query << ", Id=" << ob->Id; + } if (writeTime) { query << ", Modified='" << ob->getTimeStampN() << "'"; } + if (update) { + query << " WHERE Id=" << ob->Id; + } + ResNSel res=query.execute(); if (res) { - if (ob->Id > 0 && ob->Id!=(int)res.insert_id) { - ob->correctionNeeded = true; - } + if (!update) { + if (ob->Id > 0 && ob->Id != (int)res.insert_id) { + ob->correctionNeeded = true; + } - if (ob->Id != res.insert_id) - ob->changeId((int)res.insert_id); + if (ob->Id != res.insert_id) + ob->changeId((int)res.insert_id); + } updateCounter(oTable, ob->Id, 0); ob->oe->updateFreeId(ob); @@ -3059,8 +3202,12 @@ OpFailStatus MeosSQL::syncUpdate(QueryWrapper &updateqry, bool MeosSQL::checkOldVersion(oEvent *oe, RowWrapper &row) { int dbv=int(row["BuildVersion"]); - if ( dbvupdateChanged(); + if (dbv < buildVersion) { + string bv = "UPDATE oEvent SET BuildVersion=if (BuildVersion<" + + itos(buildVersion) + "," + itos(buildVersion) + ",BuildVersion) WHERE Id = " + itos(oe->Id); + + con->query().exec(bv); + } else if (dbv>buildVersion) return true; @@ -3552,7 +3699,7 @@ bool MeosSQL::syncListControl(oEvent *oe) { if (int(row["Removed"])) { st = opStatusOK; - oControl *c = oe->getControl(Id, false); + oControl *c = oe->getControl(Id, false, false); if (c && !c->Removed) { c->Removed = true; @@ -3562,7 +3709,7 @@ bool MeosSQL::syncListControl(oEvent *oe) { } } else { - oControl *c = oe->getControl(Id, false); + oControl *c = oe->getControl(Id, false, false); if (c) { if (isOld(counter, modified, c)) st = syncRead(false, c); @@ -3848,11 +3995,12 @@ bool MeosSQL::dropDatabase(oEvent *oe) return 0; } + string error; try { con->drop_db(CmpDataBase); } - catch (const Exception& ) { - //Don't care if we fail. + catch (const Exception& ex) { + error = ex.what(); } try { @@ -3860,7 +4008,10 @@ bool MeosSQL::dropDatabase(oEvent *oe) query << "DELETE FROM oEvent WHERE NameId=" << quote << CmpDataBase; query.execute(); } - catch (const Exception& ) { + catch (const Exception& ex) { + if (!error.empty()) + error += ", "; + error += ex.what(); //Don't care if we fail. } @@ -3874,6 +4025,9 @@ bool MeosSQL::dropDatabase(oEvent *oe) catch (const Exception&) { } + if (!error.empty()) + throw meosException(error); + return true; } @@ -3930,7 +4084,9 @@ int getTypeId(const oBase &ob) } static int skipped = 0, notskipped = 0, readent = 0; -void MeosSQL::synchronized(const oBase &entity) { +void MeosSQL::synchronized(oBase &entity) { + entity.Modified.setStamp(entity.sqlUpdated); + int id = getTypeId(entity); readTimes[make_pair(id, entity.getId())] = GetTickCount(); readent++; @@ -4460,3 +4616,96 @@ OpFailStatus MeosSQL::synchronizeUpdate(oBase *obj) { return OpFailStatus::opStatusFail; } +OpFailStatus MeosSQL::enumerateImages(vector>& images) { + try { + auto query = con->query(); + images.clear(); + auto res = query.store("SELECT Id, Filename FROM oImage"); + if (res) { + for (int i = 0; i < res.num_rows(); i++) { + auto row = res.at(i); + wstring fileName = fromUTF((string)row["Filename"]); + uint64_t id = row["Id"].ulonglong(); + images.emplace_back(fileName, id); + } + } + } + catch (const Exception& ) { + return OpFailStatus::opStatusFail; + } + + return OpFailStatus::opStatusOK; +} + +OpFailStatus MeosSQL::getImage(uint64_t id, wstring& fileName, vector& data) { + try { + auto query = con->query(); + auto res = query.store("SELECT * FROM oImage WHERE id=" + itos(id)); + if (res && res.num_rows() > 0) { + auto row = res.at(0); + fileName = fromUTF((string)row["Filename"]); + row["Image"].storeBlob(data); + return OpFailStatus::opStatusOK; + } + } + catch (const Exception&) { + } + + return OpFailStatus::opStatusFail; +} + +string hexEncode(const vector& data) { + string out; + out.reserve(4 + data.size() * 2); + out.append("X'"); + + char table[17] = "0123456789ABCDEF"; + char block[33]; + block[32] = 0; + + for (int j = 0; j < data.size();) { + if (j + 16 < data.size()) { + for (int i = 0; i < 32; ) { + uint8_t b = data[j]; + int bLow = b & 0xF; + int bHigh = (b >> 4) & 0xF; + block[i++] = table[bHigh]; + block[i++] = table[bLow]; + j++; + } + } + else { + int i = 0; + while (j < data.size()) { + uint8_t b = data[j]; + int bLow = b & 0xF; + int bHigh = (b >> 4) & 0xF; + block[i++] = table[bHigh]; + block[i++] = table[bLow]; + j++; + } + block[i] = 0; + } + + out.append(block); + } + out.append("'"); + return out; +} + +OpFailStatus MeosSQL::storeImage(uint64_t id, const wstring& fileName, const vector& data) { + try { + auto query = con->query(); + auto res = query.store("SELECT Id FROM oImage WHERE Id=" + itos(id)); + if (res.empty()) { + query << ("INSERT INTO oImage SET Id=" + itos(id) + ", Filename=") + << quote << toString(fileName) << ", Image=" << hexEncode(data); + query.execute(); + } + } + catch (Exception &) { + return OpFailStatus::opStatusFail; + } + + return OpFailStatus::opStatusOK; +} diff --git a/code/MeosSQL.h b/code/MeosSQL.h index 55b6290..fccd011 100644 --- a/code/MeosSQL.h +++ b/code/MeosSQL.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -131,7 +131,7 @@ protected: bool checkOldVersion(oEvent *oe, RowWrapper &row); map, DWORD> readTimes; - void synchronized(const oBase &entity); + void synchronized(oBase &entity); bool skipSynchronize(const oBase &entity) const; ResNSel updateCounter(const char *oTable, int id, QueryWrapper *updateqry); @@ -186,9 +186,14 @@ protected: OpFailStatus syncUpdate(oTeam *t, bool forceWriteAll); OpFailStatus syncRead(bool forceRead, oTeam *t); + void upgradeTimeFormat(const string &dbname); public: + OpFailStatus enumerateImages(vector>& images); + OpFailStatus getImage(uint64_t id, wstring &fileName, vector &data); + OpFailStatus storeImage(uint64_t id, const wstring& fileName, const vector& data); + bool synchronizeList(oEvent *oe, oListId lid); OpFailStatus synchronizeUpdate(oBase *obj); diff --git a/code/Printer.h b/code/Printer.h index db7b7fb..53bb1d5 100644 --- a/code/Printer.h +++ b/code/Printer.h @@ -4,7 +4,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/RestService.cpp b/code/RestService.cpp index 73c3358..6c20fd1 100644 --- a/code/RestService.cpp +++ b/code/RestService.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/RestService.h b/code/RestService.h index eebbe3b..2310cf4 100644 --- a/code/RestService.h +++ b/code/RestService.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 diff --git a/code/RunnerDB.cpp b/code/RunnerDB.cpp index 9de2948..85186b7 100644 --- a/code/RunnerDB.cpp +++ b/code/RunnerDB.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -237,12 +237,12 @@ wstring RunnerWDBEntry::getFamilyName() const __int64 RunnerWDBEntry::getExtId() const { - return dbe().extId; + return dbe().getExtId(); } void RunnerWDBEntry::setExtId(__int64 id) { - dbe().extId = id; + dbe().setExtId(id); } void RunnerDBEntryV2::init(const RunnerDBEntryV1 &dbe) @@ -303,7 +303,7 @@ RunnerWDBEntry *RunnerDB::addRunner(const wchar_t *name, en.cardNo = card; en.clubNo = club; e.setName(name); - en.extId = extId; + en.setExtId(extId); if (!check(en) ) { rdb.pop_back(); @@ -336,7 +336,7 @@ RunnerWDBEntry *RunnerDB::addRunner(const char *nameUTF, en.cardNo = card; en.clubNo = club; e.setNameUTF(nameUTF); - en.extId = extId; + en.setExtId(extId); if (!check(en) ) { rdb.pop_back(); @@ -550,16 +550,16 @@ RunnerWDBEntry *RunnerDB::getRunnerByName(const wstring &name, int clubId, int bestYear = 0; for (size_t k = 0;k0) return (RunnerWDBEntry *)&rwdb[bestMatch]; } - return 0; + return nullptr; } void RunnerDB::setupIdHash() const @@ -569,7 +569,7 @@ void RunnerDB::setupIdHash() const for (size_t k=0; k &clubIdMap) if (dbe == nullptr) { dbe = addRunner(r.getName().c_str(), 0, localClubId, r.getCardNo()); if (dbe) - dbe->dbe().birthYear = r.getDCI().getInt("BirthYear"); + dbe->dbe().setBirthYear(r.getDCI().getInt("BirthYear")); } else { if (dbe->getExtId() == 0) { // Only update entries not in national db. dbe->setName(r.getName().c_str()); dbe->dbe().clubNo = localClubId; - dbe->dbe().birthYear = r.getDCI().getInt("BirthYear"); + dbe->dbe().setBirthYear(r.getDCI().getInt("BirthYear")); } } } @@ -1251,13 +1251,13 @@ const shared_ptr
&RunnerDB::getRunnerTB() { auto table = make_shared
(oe, 20, L"Löpardatabasen", "runnerdb"); table->addColumn("Index", 70, true, true); - table->addColumn("Id", 70, true, true); + table->addColumn("Externt Id", 70, true, true); table->addColumn("Namn", 200, false); table->addColumn("Klubb", 200, false); table->addColumn("SI", 70, true, true); table->addColumn("Nationalitet", 70, false, true); table->addColumn("Kön", 50, false, true); - table->addColumn("FödelseÃ¥r", 70, true, true); + table->addColumn("RunnerBirthDate", 70, true, true); table->addColumn("Anmäl", 120, false, true); table->setTableProp(Table::CAN_INSERT|Table::CAN_DELETE|Table::CAN_PASTE); @@ -1330,8 +1330,8 @@ void RunnerDB::refreshRunnerTableData(Table &table) { bool found = false; pRunner r = nullptr; - if (rdb[k].extId != 0) - found = runnerInEvent.lookup(rdb[k].extId, runnerId); + if (rdb[k].getExtId() != 0) + found = runnerInEvent.lookup(rdb[k].getExtId(), runnerId); else if (rdb[k].cardNo != 0) { found = runnerInEvent.lookup(rdb[k].cardNo + cardIdConstant, runnerId); if (found) { @@ -1404,8 +1404,8 @@ void oDBRunnerEntry::addTableRow(Table &table) const { table.set(row++, it, TID_INDEX, itow(index+1), false, cellEdit); wchar_t bf[16]; - oBase::converExtIdentifierString(rn.extId, bf); - table.set(row++, it, TID_ID, bf, false, cellEdit); + oBase::converExtIdentifierString(rn.getExtId(), bf); + table.set(row++, it, TID_ID, bf, canEdit, cellEdit); r.initName(); table.set(row++, it, TID_NAME, r.name, canEdit, cellEdit); @@ -1421,14 +1421,14 @@ void oDBRunnerEntry::addTableRow(Table &table) const { table.set(row++, it, TID_NATIONAL, nat, canEdit, cellEdit); wchar_t sex[2] = {wchar_t(rn.sex), 0}; table.set(row++, it, TID_SEX, sex, canEdit, cellEdit); - table.set(row++, it, TID_YEAR, itow(rn.birthYear), canEdit, cellEdit); + table.set(row++, it, TID_YEAR, rn.getBirthDate(), canEdit, cellEdit); int runnerId; bool found = false; pRunner cr = nullptr; - if (rn.extId != 0) - found = db->runnerInEvent.lookup(rn.extId, runnerId); + if (rn.getExtId() != 0) + found = db->runnerInEvent.lookup(rn.getExtId(), runnerId); else if (rn.cardNo != 0) { found = db->runnerInEvent.lookup(rn.cardNo + cardIdConstant, runnerId); if (found) { @@ -1467,8 +1467,29 @@ pair oDBRunnerEntry::inputData(int id, const wstring &input, throw meosException("Not initialized"); RunnerWDBEntry &r = db->rwdb[index]; RunnerDBEntry &rd = db->rdb[index]; + static bool hasWarnedId = false; switch(id) { + case TID_ID: { + wchar_t bf[16]; + auto key = oBase::converExtIdentifierString(input); + oBase::converExtIdentifierString(key, bf); + + if (compareStringIgnoreCase(bf, input)) { + throw meosException(L"Cannot represent ID X#" + input); + } + + if (key != r.getExtId() && !hasWarnedId) { + if (oe->gdiBase().askOkCancel(L"warn:changeid") == gdioutput::AskAnswer::AnswerCancel) + throw meosCancel(); + hasWarnedId = true; + } + + r.setExtId(key); + db->idhash.clear(); + output = bf; + } + break; case TID_NAME: r.setName(input.c_str()); r.getName(output); @@ -1503,8 +1524,8 @@ pair oDBRunnerEntry::inputData(int id, const wstring &input, output = r.getSex(); break; case TID_YEAR: - rd.birthYear = short(_wtoi(input.c_str())); - output = itow(r.getBirthYear()); + rd.setBirthDate(input); + output = rd.getBirthDate(); break; case TID_CLUB: @@ -1529,7 +1550,7 @@ void oDBRunnerEntry::fillInput(int id, vector< pair > &out, siz void oDBRunnerEntry::remove() { RunnerWDBEntry &r = db->rwdb[index]; r.remove(); - db->idhash.remove(r.dbe().extId); + db->idhash.remove(r.dbe().getExtId()); wstring cname(canonizeName(r.name)); multimap::const_iterator it = db->nhash.find(cname); @@ -1823,6 +1844,20 @@ vector> RunnerDB::getRunnerSuggestions(const wstring else setupAutoCompleteHash(AutoHashMode::Runners); + // Check if database key + int64_t id = 0; + for (int j = 0; j < key.length(); j++) { + if (key[j] >= '0' && key[j] <= '9') { + id = oBase::converExtIdentifierString(key); + wchar_t bf[16]; + oBase::converExtIdentifierString(id, bf); + if (compareStringIgnoreCase(key, bf)) + id = 0; + + break; + } + } + vector< pair > outOrder; set> ix; wchar_t bf[256]; @@ -1866,8 +1901,15 @@ vector> RunnerDB::getRunnerSuggestions(const wstring if (res != runnerHash.end()) res->second.match(*this, ix, nameParts); } - } + + if (id > 0) { + auto r = getRunnerById(id); + if (r) { + ix.emplace(1000, r->getIndex()); + } + } + if (ix.empty()) return ret; @@ -1896,3 +1938,85 @@ vector> RunnerDB::getRunnerSuggestions(const wstring } return ret; } + +const wstring& RunnerDBEntry::getBirthDate() const { + int year = getBirthYear(); + if (year <= 0 || year>9999) + return _EmptyWString; + + int month = getBirthMonth(); + if (month > 0 && month <= 12) { + int day = getBirthDay(); + if (day > 0 && day <= 31) { + wchar_t bf[16]; + swprintf_s(bf, L"%d-%02d-%02d", year, month, day); + wstring& res = StringCache::getInstance().wget(); + res = bf; + return res; + } + } + return itow(year); +} + +void RunnerDBEntry::setBirthDate(const wstring& in) { + SYSTEMTIME st; + if (convertDateYMS(in, st, true) > 0) { + setBirthYear(st.wYear); + setBirthMonth(st.wMonth); + setBirthDay(st.wDay); + } + else { + int year = _wtoi(in.c_str()); + if (year > 1900 && year < 9999) + setBirthYear(year); + else + setBirthYear(0); + + setBirthMonth(0); + setBirthDay(0); + } +} + +void RunnerDBEntry::setBirthDate(int dateOrYear) { + if (dateOrYear > 0 && dateOrYear < 100) + dateOrYear = extendYear(dateOrYear); + + if ((dateOrYear > 1900 && dateOrYear < 9999) || dateOrYear == 0) { + setBirthYear(dateOrYear); + setBirthMonth(0); + setBirthDay(0); + return; + } + int d = dateOrYear % 100; + dateOrYear /= 100; + + int m = dateOrYear % 100; + dateOrYear /= 100; + + int y = extendYear(dateOrYear); + if (d > 0 && d <= 31 && m > 0 && m <= 12 && y > 1900 && y < 9999) { + setBirthYear(y); + setBirthMonth(m); + setBirthDay(d); + } + else if (y > 1900 && y < 9999) { + setBirthYear(y); + } + else { + setBirthYear(0); + setBirthMonth(0); + setBirthDay(0); + } +} + +int RunnerDBEntry::getBirthDateInt() const { + int y = getBirthYear(); + int m = getBirthMonth(); + int d = getBirthDay(); + + if (y > 0 && y < 9999 && m > 0 && d > 0) + return y * 10000 + m * 100 + d; + else + return y; +} + diff --git a/code/RunnerDB.h b/code/RunnerDB.h index c75b5a9..e7750e8 100644 --- a/code/RunnerDB.h +++ b/code/RunnerDB.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -97,11 +97,13 @@ struct RunnerDBEntry { int clubNo; char national[3]; char sex; +private: short int birthYear; short int reserved; /** End of V1*/ __int64 extId; +public: bool isRemoved() const { return (reserved & 1) == 1; } void remove() { reserved |= 1; } @@ -111,6 +113,22 @@ struct RunnerDBEntry { bool operator==(const RunnerDBEntry &d) const { return memcmp(this, &d, sizeof(RunnerDBEntry)) == 0; } + + int getBirthYear() const { return birthYear; } + int getBirthMonth() const { return (reserved >> 2) & 0xF; }// 4 bits + int getBirthDay() const { return (reserved >> 6) & 0x1F; } // 5 bits + + void setBirthYear(int year) { birthYear = year; } + void setBirthMonth(int month) { reserved = (reserved & (~(0xF << 2))) | ((0xF & month) << 2); }// 4 bits + void setBirthDay(int day) { reserved = (reserved & (~(0x1F << 6))) | ((0x1F & day) << 6); } // 5 bits + + const wstring& getBirthDate() const; + void setBirthDate(const wstring& in); + void setBirthDate(int dateOrYear); + int getBirthDateInt() const; + + void setExtId(__int64 id) { extId = id; } + __int64 getExtId() const { return extId; } }; class RunnerDB; @@ -125,6 +143,8 @@ public: void init(RunnerDB *p, size_t ix); RunnerWDBEntry(); + size_t getIndex() const { return ix; } + // Link to narrow DB Entry const RunnerDBEntry &dbe() const; RunnerDBEntry &dbe(); @@ -145,14 +165,14 @@ public: wstring getFamilyName() const; wstring getNationality() const; - int getBirthYear() const {return dbe().birthYear;} + int getBirthYear() const {return dbe().getBirthYear(); } wstring getSex() const; __int64 getExtId() const; void setExtId(__int64 id); - bool isRemoved() const {return (dbe().reserved & 1) == 1;} - void remove() {dbe().reserved |= 1;} + bool isRemoved() const { return dbe().isRemoved(); } + void remove() { dbe().remove(); } }; typedef vector RunnerDBVector; diff --git a/code/SportIdent.cpp b/code/SportIdent.cpp index 0a05bec..3f20b51 100644 --- a/code/SportIdent.cpp +++ b/code/SportIdent.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -46,11 +46,11 @@ //#define DEBUG_SI SI_StationData::SI_StationData() { - stationNumber=0; - stationMode=0; - extended=false; - handShake=false; - autoSend=false; + stationNumber = 0; + stationMode = 0; + extended = false; + handShake = false; + autoSend = false; radioChannel = 0; } @@ -64,19 +64,28 @@ SI_StationInfo::SI_StationInfo() localZeroTime=0; } -SportIdent::SportIdent(HWND hWnd, DWORD Id, bool readVoltage) : readVoltage(readVoltage) -{ - ClassId=Id; - hWndNotify=hWnd; +SportIdent::SportIdent(HWND hWnd, DWORD Id, bool readVoltage) : readVoltage(readVoltage) { + ClassId = Id; + hWndNotify = hWnd; - //hComm=0; - //ThreadHandle=0; - n_SI_Info=0; + n_SI_Info = 0; InitializeCriticalSection(&SyncObj); - tcpPortOpen=0; - serverSocket=0; + tcpPortOpen = 0; + serverSocket = 0; + punchMap.resize(31, 0); + punchMap[oPunch::SpecialPunch::PunchStart] = oPunch::SpecialPunch::PunchStart; + punchMap[oPunch::SpecialPunch::PunchCheck] = oPunch::SpecialPunch::PunchCheck; + punchMap[oPunch::SpecialPunch::PunchFinish] = oPunch::SpecialPunch::PunchFinish; +} + +void SportIdent::resetPunchMap() { + punchMap.resize(31, 0); + fill(punchMap.begin(), punchMap.end(), 0); + punchMap[oPunch::SpecialPunch::PunchStart] = oPunch::SpecialPunch::PunchStart; + punchMap[oPunch::SpecialPunch::PunchCheck] = oPunch::SpecialPunch::PunchCheck; + punchMap[oPunch::SpecialPunch::PunchFinish] = oPunch::SpecialPunch::PunchFinish; } SportIdent::~SportIdent() @@ -299,7 +308,7 @@ string decode(BYTE *bf, int read) bool SportIdent::openComListen(const wchar_t *com, DWORD BaudRate) { closeCom(com); - SI_StationInfo *si = findStation(com); + SI_StationInfo *si = findStationInt(com); if (!si) { SI_Info[n_SI_Info].ComPort=com; @@ -309,6 +318,9 @@ bool SportIdent::openComListen(const wchar_t *com, DWORD BaudRate) { } si->data.clear(); + if (si->ComPort == L"TEST") + return true; // Passive test + wstring comfile=wstring(L"//./")+com; si->hComm = CreateFile( comfile.c_str(), GENERIC_READ | GENERIC_WRITE, @@ -355,7 +367,7 @@ bool SportIdent::tcpAddPort(int port, DWORD zeroTime) { closeCom(L"TCP"); - SI_StationInfo *si = findStation(L"TCP"); + SI_StationInfo *si = findStationInt(L"TCP"); if (!si) { SI_Info[n_SI_Info].ComPort=L"TCP"; @@ -374,7 +386,7 @@ bool SportIdent::openCom(const wchar_t *com) { closeCom(com); - SI_StationInfo *si = findStation(com); + SI_StationInfo *si = findStationInt(com); if (!si) { SI_Info[n_SI_Info].ComPort=com; @@ -386,7 +398,7 @@ bool SportIdent::openCom(const wchar_t *com) si->data.clear(); if (si->ComPort == L"TEST") { - return true; + return false; } wstring comfile=wstring(L"//./")+com; @@ -511,9 +523,7 @@ bool SportIdent::openCom(const wchar_t *com) return true; } - -SI_StationInfo *SportIdent::findStation(const wstring &com) -{ +SI_StationInfo* SportIdent::findStationInt(const wstring& com) { if (com == L"TEST" && n_SI_Info < 30) { if (n_SI_Info == 0 || SI_Info[n_SI_Info - 1].ComPort != com) { SI_Info[n_SI_Info].ComPort = com; @@ -521,6 +531,26 @@ SI_StationInfo *SportIdent::findStation(const wstring &com) } } + for (int i = 0; i < n_SI_Info; i++) + if (com == SI_Info[i].ComPort) + return &SI_Info[i]; + + return 0; +} + +void SportIdent::addTestStation(const wstring& com) { + SI_Info[n_SI_Info].ComPort = com; + n_SI_Info++; +} + +const SI_StationInfo *SportIdent::findStation(const wstring &com) const +{ + if (com == L"TEST" && n_SI_Info < 30) { + if (n_SI_Info == 0 || SI_Info[n_SI_Info - 1].ComPort != com) { + const_cast(this)->addTestStation(com); + } + } + for(int i=0;iComPort==L"TCP") { if (tcpPortOpen) { @@ -890,7 +920,7 @@ int SportIdent::MonitorTCPSI(WORD port, int localZeroTime) } } else - addPunch(op.CodeTime/10, op.CodeNo, op.SICardNo, 0); + addPunch(op.CodeTime, op.CodeNo, op.SICardNo, 0); } else r=-1; @@ -933,10 +963,10 @@ bool SportIdent::MonitorTEST(SI_StationInfo &si) SICard card(ConvertedTimeStatus::Hour12); card.StartPunch.Code = 1; - int t = card.StartPunch.Time = 3600*8 + rand()%1000; + int t = card.StartPunch.Time = timeConstHour*8 + rand()%1000; card.FinishPunch.Code = 2; - card.FinishPunch.Time = card.StartPunch.Time + 1800 + rand() % 3600; + card.FinishPunch.Time = card.StartPunch.Time + timeConstHour/2 + rand() % timeConstHour; card.CardNumber = tc.cardNo; for (size_t k = 0; k < tc.punches.size(); k++) { @@ -947,8 +977,8 @@ bool SportIdent::MonitorTEST(SI_StationInfo &si) } addCard(card); - //Sleep(300 + rand()%600); - Sleep(0); + Sleep(300 + rand()%600); + //Sleep(0); if (++longSleepIter > 20) { Sleep(100 + rand() % 600); longSleepIter = 0; @@ -1002,12 +1032,15 @@ bool SportIdent::MonitorSI(SI_StationInfo &si) DWORD Card=MAKELONG(MAKEWORD(bf[7], bf[6]), MAKEWORD(bf[5], bf[4])); - if (Series<=4 && Series>=1) - Card=ShortCard+100000*Series; + if (Series <= 4 && Series >= 1) + Card = ShortCard + 100000 * Series; DWORD Time=0; - if (bf[8]&0x1) Time=3600*12; - Time+=MAKEWORD(bf[10], bf[9]); + if (bf[8] & 0x1) Time = timeConstHour * 12; + Time += MAKEWORD(bf[10], bf[9])*timeConstSecond; + uint8_t tss = bf[11]; // Sub second 1/256 seconds + int tenth = (((100 * tss) / 256) + 4) / 10; + Time += tenth; #ifdef DEBUG_SI char str[128]; sprintf_s(str, "EXTENDED: Card = %d, Station = %d, StationMode = %d", Card, Station, si.StationMode); @@ -1040,9 +1073,9 @@ bool SportIdent::MonitorSI(SI_StationInfo &si) //if (Series!=1) // Card+=100000*Series; - DWORD Time=MAKEWORD(bf[8], bf[7]); + DWORD Time = MAKEWORD(bf[8], bf[7]) * timeConstSecond; BYTE p=bf[1]; - if (p&0x1) Time+=3600*12; + if (p&0x1) Time+=timeConstHour*12; #ifdef DEBUG_SI char str[128]; @@ -1100,7 +1133,7 @@ bool SportIdent::MonitorSI(SI_StationInfo &si) bf[0]=chRead; readBytes(bf+1, 200, hComm); //GetSI5DataExt(hComm); - MessageBox(NULL, L"Programmera stationen utan AUTOSEND!", NULL, MB_OK); + MessageBox(NULL, lang.tl(L"Programmera stationen utan AUTOSEND").c_str(), NULL, MB_OK); } break; @@ -1108,7 +1141,7 @@ bool SportIdent::MonitorSI(SI_StationInfo &si) BYTE bf[200]; bf[0]=chRead; readBytes(bf+1, 200, hComm); - MessageBox(NULL, L"Programmera stationen utan AUTOSEND!", NULL, MB_OK); + MessageBox(NULL, lang.tl(L"Programmera stationen utan AUTOSEND").c_str(), NULL, MB_OK); } break; case 0xE8:{ @@ -1125,7 +1158,6 @@ bool SportIdent::MonitorSI(SI_StationInfo &si) // MessageBox(NULL, "SI-card not supported", NULL, MB_OK); // break; default: - BYTE bf[128]; bf[0]=chRead; int rb=readBytes(bf+1, 120, hComm); @@ -1146,6 +1178,9 @@ bool SportIdent::MonitorSI(SI_StationInfo &si) st+=d; } } + + if (chRead == 0xEF) + MessageBox(NULL, lang.tl(L"Programmera stationen utan AUTOSEND").c_str(), NULL, MB_OK); //MessageBox(NULL, st.c_str(), "Unknown SI response", MB_OK); } } @@ -1650,8 +1685,6 @@ bool SportIdent::getCard5Data(BYTE *data, SICard &card) analyseSI5Time(data+5, card.FinishPunch.Time, card.FinishPunch.Code); analyseSI5Time(data+9, card.CheckPunch.Time, card.CheckPunch.Code); -// card.StartPunch=MAKEWORD(data[4], data[3]); -// card.FinishPunch=MAKEWORD(data[6], data[5]); card.nPunch=data[7]-1; data+=16; @@ -1660,7 +1693,7 @@ bool SportIdent::getCard5Data(BYTE *data, SICard &card) if (k<30) { DWORD basepointer=3*(k%5)+1+(k/5)*16; DWORD code=data[basepointer]; - DWORD time;//=MAKEWORD(data[basepointer+2],data[basepointer+1]); + DWORD time; DWORD slask; analyseSI5Time(data+basepointer+1, time, slask); @@ -1710,29 +1743,29 @@ bool SportIdent::getCard9Data(BYTE *data, SICard &card) int series = data[24] & 15; card.convertedTime = ConvertedTimeStatus::Hour24; - analysePunch(data+12, card.StartPunch.Time, card.StartPunch.Code); - analysePunch(data+16, card.FinishPunch.Time, card.FinishPunch.Code); - analysePunch(data+8, card.CheckPunch.Time, card.CheckPunch.Code); + analysePunch(data+12, card.StartPunch.Time, card.StartPunch.Code, useSubsecondMode); + analysePunch(data+16, card.FinishPunch.Time, card.FinishPunch.Code, useSubsecondMode); + analysePunch(data+8, card.CheckPunch.Time, card.CheckPunch.Code, false); if (series == 1) { // SI Card 9 card.nPunch=min(int(data[22]), 50); for(unsigned k=0;k> 1) & 0x1F; control=cn; - time=MAKEWORD(ptl, pth)+3600*12*(dt0&0x1); + time = MAKEWORD(ptl, pth) * timeConstSecond + timeConstHour * 12 * (dt0 & 0x1); } else { control=-1; @@ -1810,9 +1843,9 @@ bool SportIdent::getCard6Data(BYTE *data, SICard &card) // DWORD control; // DWORD time; card.convertedTime = ConvertedTimeStatus::Hour24; - analysePunch(data+8, card.StartPunch.Time, card.StartPunch.Code); - analysePunch(data+4, card.FinishPunch.Time, card.FinishPunch.Code); - analysePunch(data+12, card.CheckPunch.Time, card.CheckPunch.Code); + analysePunch(data+8, card.StartPunch.Time, card.StartPunch.Code, useSubsecondMode); + analysePunch(data+4, card.FinishPunch.Time, card.FinishPunch.Code, useSubsecondMode); + analysePunch(data+12, card.CheckPunch.Time, card.CheckPunch.Code, false); card.nPunch=min(int(data[2]), 192); int i; @@ -1843,12 +1876,12 @@ bool SportIdent::getCard6Data(BYTE *data, SICard &card) data+=128-16; for(unsigned k=0;k>6)&0x3); - time=MAKEWORD(ptl, pth)+3600*12*(ptd&0x1); + time = timeConstSecond * MAKEWORD(ptl, pth) + timeConstHour * 12 * (ptd & 0x1); + if (!subSecond) { + control = cn + 256 * ((ptd >> 6) & 0x3); + } + else { + control = 0; + uint8_t tss = data[1]; // Sub second 1/256 seconds + int tenth = (((100 * tss) / 256) + 4) / 10; + time += tenth; + } return true; } else @@ -1880,15 +1921,15 @@ bool SportIdent::analysePunch(BYTE *data, DWORD &time, DWORD &control) { } } -void SportIdent::analyseSI5Time(BYTE *data, DWORD &time, DWORD &control) +void SportIdent::analyseSI5Time(BYTE* data, DWORD& time, DWORD& control) { - if (*LPWORD(data)!=0xEEEE) { - time=MAKEWORD(data[1], data[0]); - control=0; + if (*LPWORD(data) != 0xEEEE) { + time = MAKEWORD(data[1], data[0]) * timeConstSecond; + control = 0; } else { - control=-1; - time=0; + control = -1; + time = 0; } } @@ -1906,17 +1947,17 @@ void SICard::analyseHour12Time(DWORD zeroTime) { } void SIPunch::analyseHour12Time(DWORD zeroTime) { - if (Code != -1 && Time>=0 && Time <=12*3600) { - if (zeroTime < 12 * 3600) { + if (Code != -1 && Time>=0 && Time <=12*timeConstHour) { + if (zeroTime < 12 * timeConstHour) { //Förmiddag if (Time < zeroTime) - Time += 12 * 3600; //->Eftermiddag + Time += 12 * timeConstHour; //->Eftermiddag } else { //Eftermiddag - if (Time >= zeroTime % (12 * 3600)) { + if (Time >= zeroTime % (12 * timeConstHour)) { //Eftermiddag - Time += 12 * 3600; + Time += 12 * timeConstHour; } // else Efter midnatt OK. } @@ -1985,50 +2026,67 @@ void SportIdent::addCard(const SICard &sic) PostMessage(hWndNotify, WM_USER, ClassId, 0); } -void SportIdent::addPunch(DWORD Time, int Station, int Card, int Mode) -{ +void SportIdent::addPunch(DWORD Time, int Station, int Card, int Mode) { + if (!useSubsecondMode) + Time -= (Time % timeConstSecond); + SICard sic(ConvertedTimeStatus::Hour24); - sic.CardNumber=Card; + sic.CardNumber = Card; sic.StartPunch.Code = -1; sic.CheckPunch.Code = -1; sic.FinishPunch.Code = -1; - if (Mode==0 || Mode == 11){ // 11 is dongle - if (Station>30){ - sic.Punch[0].Code=Station; - sic.Punch[0].Time=Time; - sic.nPunch=1; + auto mapPunch = [this](int code) { + if (code > 0 && code < punchMap.size() && punchMap[code] > 0) + return punchMap[code]; + else + return code; + }; + + if (Mode == 0 || Mode == 11) { // 11 is dongle + int code = (Station & 0xFFFF); + int mappedCode = mapPunch(code); + int unit = 0; + if (mappedCode != code) + unit = code; + else + unit = (Station >> 16) & 0xFFFF; + + if (mappedCode > 30) { + sic.Punch[0].Code = Station; + sic.Punch[0].Time = Time; + sic.nPunch = 1; } - else if (Station == oPunch::PunchStart) { + else if (mappedCode == oPunch::PunchStart) { sic.StartPunch.Time = Time; - sic.StartPunch.Code = oPunch::PunchStart; + sic.StartPunch.Code = unit; } - else if (Station == oPunch::PunchCheck) { + else if (mappedCode == oPunch::PunchCheck) { sic.CheckPunch.Time = Time; - sic.CheckPunch.Code = oPunch::PunchCheck; + sic.CheckPunch.Code = unit; } - else{ - sic.FinishPunch.Time=Time; - sic.FinishPunch.Code = oPunch::PunchFinish; + else { + sic.FinishPunch.Time = Time; + sic.FinishPunch.Code = unit; } } - else{ - if (Mode==0x02 || Mode == 50){ - sic.Punch[0].Code=Station; - sic.Punch[0].Time=Time; - sic.nPunch=1; + else { + if (Mode == 0x02 || Mode == 50) { + sic.Punch[0].Code = Station; + sic.Punch[0].Time = Time; + sic.nPunch = 1; } else if (Mode == 3) { - sic.StartPunch.Time=Time; - sic.StartPunch.Code = oPunch::PunchStart; + sic.StartPunch.Time = Time; + sic.StartPunch.Code = Station; } else if (Mode == 10) { - sic.CheckPunch.Time=Time; - sic.CheckPunch.Code = oPunch::PunchCheck; + sic.CheckPunch.Time = Time; + sic.CheckPunch.Code = Station; } - else{ - sic.FinishPunch.Time=Time; - sic.FinishPunch.Code = oPunch::PunchFinish; + else { + sic.FinishPunch.Time = Time; + sic.FinishPunch.Code = Station; } } sic.punchOnly = true; @@ -2084,9 +2142,8 @@ void start_si_thread(void *ptr) } } -void SportIdent::startMonitorThread(const wchar_t *com) -{ - SI_StationInfo *si = findStation(com); +void SportIdent::startMonitorThread(const wchar_t *com) { + SI_StationInfo *si = findStationInt(com); if (si && (si->hComm || si->ComPort==L"TCP" || si->ComPort == L"TEST")) { @@ -2119,7 +2176,7 @@ void checkport_si_thread(void *ptr) *port=0; //No SI found here else { bool valid = true; - SI_StationInfo *sii = si.findStation(bf); + const SI_StationInfo *sii = ((const SportIdent &)si).findStation(bf); if (sii) { if (sii->data.empty() || sii->data[0].stationNumber>=1024 || sii->data[0].stationMode>15 || !(sii->data[0].autoSend || sii->data[0].handShake)) @@ -2178,7 +2235,7 @@ bool SportIdent::autoDetect(list &ComPorts) bool SportIdent::isPortOpen(const wstring &com) { - SI_StationInfo *si = findStation(com); + const SI_StationInfo *si = findStation(com); if (si && si->ComPort==L"TCP") return tcpPortOpen && serverSocket; @@ -2186,25 +2243,61 @@ bool SportIdent::isPortOpen(const wstring &com) return si!=0 && si->hComm && si->ThreadHandle; } -void SportIdent::getInfoString(const wstring &com, vector &infov) -{ + +bool SportIdent::isAnyOpenUnkownUnit() const { + if (tcpPortOpen && serverSocket) + return true; + + for (int i = 0; i < n_SI_Info; i++) { + auto& si = SI_Info[i]; + auto& com = si.ComPort; + + if (com == L"TCP") + continue; + + if (com == L"TEST") + return true; + + if (!(si.hComm && si.ThreadHandle)) + continue; // Not open + + if (si.data.empty()) + return true; // Listen mode (no contact made) + + switch (si.data[0].stationMode) { + case 2: // Known modes + case 50: + case 4: + case 3: + case 5: + case 7: + case 10: + continue; + } + + return true; + } + return false; +} + +void SportIdent::getInfoString(const wstring &com, vector> &infov) const { infov.clear(); - SI_StationInfo *si = findStation(com); + const SI_StationInfo *si = findStation(com); if (com==L"TCP") { if (!si || !tcpPortOpen || !serverSocket) { - infov.push_back(L"TCP: "+lang.tl(L"ej aktiv.")); + infov.emplace_back(false, L"TCP: "+lang.tl(L"ej aktiv.")); return; } wchar_t bf[128]; swprintf_s(bf, lang.tl(L"TCP: Port %d, Nolltid: %s").c_str(), tcpPortOpen, L"00:00:00");//WCS - infov.push_back(bf); + infov.emplace_back(false, bf); return; } if (!(si!=0 && si->hComm && si->ThreadHandle)) { - infov.push_back(com+L": "+lang.tl(L"ej aktiv.")); + infov.emplace_back(false, com+L": "+lang.tl(L"ej aktiv.")); return; } @@ -2221,7 +2314,7 @@ void SportIdent::getInfoString(const wstring &com, vector &infov) switch(da.stationMode){ case 2: case 50: - info+=lang.tl(L"Kontrol"); + info+=lang.tl(L"Kontroll"); break; case 4: info+=lang.tl(L"MÃ¥l"); @@ -2256,12 +2349,15 @@ void SportIdent::getInfoString(const wstring &com, vector &infov) else if (da.handShake) info+=lang.tl(L"handskakning."); else info+=lang.tl(L"[VARNING] ingen/okänd."); - infov.push_back(info); + infov.emplace_back(false, info); + + if (da.autoSend && da.stationMode == 5) + infov.emplace_back(true, lang.tl("Programmera stationen utan AUTOSEND")); } } static string formatTimeN(int t) { - const wstring &wt = formatTime(t); + const wstring &wt = formatTime(t, SubSecond::Auto); string nt(wt.begin(), wt.end()); return nt; } @@ -2411,7 +2507,7 @@ unsigned int SICard::calculateHash() const { } string SICard::serializePunches() const { - string ser; + string ser = "*";// Mark of time factor if (CheckPunch.Code != -1) ser += "C-" + itos(CheckPunch.Time); @@ -2437,24 +2533,31 @@ void SICard::deserializePunches(const string &arg) { StartPunch.Code = -1; CheckPunch.Code = -1; vector out; - split(arg, ";", out); + int timeFactor = 10; + if (arg.length() > 1 && arg[0] == '*') { + split(arg.c_str() + 2, ";", out); + timeFactor = 1; + } + else { + split(arg, ";", out); + } nPunch = 0; for (size_t k = 0; k< out.size(); k++) { vector mark; split(out[k], "-", mark); - if (mark.size() != 2) + if (mark.size() != 2 || mark[0].empty()) throw std::exception("Invalid string"); DWORD *tp = 0; - if (mark[0] == "F") { - FinishPunch.Code = 1; + if (mark[0][0] == 'F') { + FinishPunch.Code = atoi(mark[0].c_str() + 1); tp = &FinishPunch.Time; } - else if (mark[0] == "S") { - StartPunch.Code = 1; + else if (mark[0][0] == 'S') { + StartPunch.Code = atoi(mark[0].c_str() + 1); tp = &StartPunch.Time; } - else if (mark[0] == "C") { - CheckPunch.Code = 1; + else if (mark[0][0] == 'C') { + CheckPunch.Code = atoi(mark[0].c_str() + 1); tp = &CheckPunch.Time; } else { @@ -2462,12 +2565,51 @@ void SICard::deserializePunches(const string &arg) { tp = &Punch[nPunch++].Time; } - *tp = atoi(mark[1].c_str()); + *tp = atoi(mark[1].c_str()) * timeFactor; } if (out.size() == 1) punchOnly = true; } +int SICard::getFirstTime() const { + if (StartPunch.Time > 0) + return StartPunch.Time; + + for (int i = 0; i < nPunch; i++) { + if (Punch[i].Time > 0) + return Punch[i].Time; + } + + if (FinishPunch.Time > 0) + return FinishPunch.Time; + + return 0; +} + + +map SportIdent::getSpecialMappings() const { + map res; + for (int j = 1; j < punchMap.size(); j++) { + if (punchMap[j] > 0) + res[j] = oPunch::SpecialPunch(punchMap[j]); + } + return res; +} + +void SportIdent::addSpecialMapping(int code, oPunch::SpecialPunch p) { + if (code > 0 && code < punchMap.size()) + punchMap[code] = p; + else + throw std::exception("Not supported"); +} + +void SportIdent::removeSpecialMapping(int code) { + if (code > 0 && code < punchMap.size()) + punchMap[code] = 0; + else + throw std::exception("Not supported"); +} + void SportIdent::addTestCard(int cardNo, const vector &punches) { testCards.emplace(cardNo, punches); } diff --git a/code/SportIdent.h b/code/SportIdent.h index 3f02d11..580d93c 100644 --- a/code/SportIdent.h +++ b/code/SportIdent.h @@ -1,19 +1,15 @@ // SportIdent.h: interface for the SportIdent class. // ////////////////////////////////////////////////////////////////////// - -#if !defined(AFX_SPORTIDENT_H__F13F5795_8FA9_4CE6_8497_7407CD590139__INCLUDED_) -#define AFX_SPORTIDENT_H__F13F5795_8FA9_4CE6_8497_7407CD590139__INCLUDED_ - -#if _MSC_VER > 1000 #pragma once -#endif // _MSC_VER > 1000 #include +#include +#include "oPunch.h" /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -108,6 +104,8 @@ struct SICard string serializePunches() const; void deserializePunches(const string &arg); + + int getFirstTime() const; }; struct SI_StationData { @@ -154,9 +152,11 @@ struct SI_StationInfo }; -class SportIdent -{ +class SportIdent { protected: + + bool useSubsecondMode = false; + bool readSI6Block(HANDLE hComm, BYTE *data); bool readSystemData(SI_StationInfo *si, int retry=2); bool readSystemDataV2(SI_StationInfo &si); @@ -195,7 +195,7 @@ protected: void getSI9DataExt(HANDLE hComm); void analyseSI5Time(BYTE *data, DWORD &time, DWORD &control); - bool analysePunch(BYTE *data, DWORD &time, DWORD &control); + bool analysePunch(BYTE *data, DWORD &time, DWORD &control, bool subSecond); void analyseTPunch(BYTE *data, DWORD &time, DWORD &control); //Card read waiting to be processed. @@ -227,13 +227,25 @@ protected: bool readVoltage; + vector punchMap; + SI_StationInfo* findStationInt(const wstring& com); + void addTestStation(const wstring& com); + public: - SI_StationInfo *findStation(const wstring &com); + + map getSpecialMappings() const; + void addSpecialMapping(int code, oPunch::SpecialPunch); + void removeSpecialMapping(int code); + + const SI_StationInfo *findStation(const wstring &com) const; /** Log debug data. */ void debugLog(const wchar_t *ptr); - void getInfoString(const wstring &com, vector &info); + void getInfoString(const wstring &com, vector> &info) const; + + bool isAnyOpenUnkownUnit() const; + bool isPortOpen(const wstring &com); bool autoDetect(list &ComPorts); void stopMonitorThread(); @@ -250,13 +262,14 @@ public: void closeCom(const wchar_t *com); bool openCom(const wchar_t *com); bool tcpAddPort(int port, DWORD zeroTime); - bool openComListen(const wchar_t *com, DWORD BaudRate); SportIdent(HWND hWnd, DWORD Id, bool readVoltage); + + void setSubSecondMode(bool subSec) { useSubsecondMode = subSec; } + void resetPunchMap(); + virtual ~SportIdent(); friend void start_si_thread(void *ptr); }; - -#endif // !defined(AFX_SPORTIDENT_H__F13F5795_8FA9_4CE6_8497_7407CD590139__INCLUDED_) diff --git a/code/StdAfx.h b/code/StdAfx.h index 1c976c3..c32655d 100644 --- a/code/StdAfx.h +++ b/code/StdAfx.h @@ -1,21 +1,12 @@ -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) -#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 +#pragma once #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #define NOMINMAX -// Windows Header Files: + #include #include +#include "timeconstants.hpp" // C RunTime Header Files @@ -51,4 +42,3 @@ const extern string _EmptyString; const extern string _VacantName; const extern wstring _EmptyWString; -#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) diff --git a/code/TabAuto.cpp b/code/TabAuto.cpp index 170f5b8..bf7ebf9 100644 --- a/code/TabAuto.cpp +++ b/code/TabAuto.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -716,7 +716,7 @@ void AutoMachine::startCancelInterval(gdioutput &gdi, const char *startCommand, void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, State state) { settingsTitle(gdi, "Resultatutskrift / export"); - wstring time = (state == State::Create && interval <= 0) ? L"10:00" : getTimeMS(interval); + wstring time = (state == State::Create && interval <= 0) ? L"10:00" : formatTimeMS(interval, false, SubSecond::Off); startCancelInterval(gdi, "Save", state, IntervalMinute, time); if (state == State::Create) { @@ -822,12 +822,12 @@ void PrintResultMachine::settings(gdioutput &gdi, oEvent &oe, State state) { } } -void PrintResultMachine::save(oEvent &oe, gdioutput &gdi, bool doProcess) { +void PrintResultMachine::save(oEvent& oe, gdioutput& gdi, bool doProcess) { AutoMachine::save(oe, gdi, doProcess); wstring minute = gdi.getText("Interval"); - int t = convertAbsoluteTimeMS(minute); + int t = convertAbsoluteTimeMS(minute) / timeConstSecond; - if (t < 2 || t>7200) { + if (t < 2 || t > 7200) { throw meosException("Intervallet mÃ¥ste anges pÃ¥ formen MM:SS."); } doExport = gdi.isChecked("DoExport"); @@ -980,7 +980,7 @@ void PrewarningMachine::settings(gdioutput &gdi, oEvent &oe, State state) { gdi.pushX(); gdi.fillDown(); vector< pair > d; - oe.fillControls(d, oEvent::CTCourseControl); + oe.fillControls(d, oEvent::ControlType::CourseControl); gdi.addItem("Controls", d); gdi.setSelection("Controls", controls); gdi.popX(); @@ -998,7 +998,7 @@ void PrewarningMachine::save(oEvent &oe, gdioutput &gdi, bool doProcess) { controlsSI.clear(); for (set::iterator it = controls.begin(); it != controls.end(); ++it) { - pControl pc = oe.getControl(*it, false); + pControl pc = oe.getControl(*it, false, false); if (pc) { vector n; pc->getNumbers(n); @@ -1152,7 +1152,7 @@ void PunchMachine::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) sic.punchOnly = true; sic.nPunch = 1; sic.Punch[0].Code = radio; - sic.Punch[0].Time = 600 + rand() % 1200 + r->getStartTime(); + sic.Punch[0].Time = timeConstHour/10 + rand() % (1200*timeConstSecond) + r->getStartTime(); si.addCard(sic); } } @@ -1205,7 +1205,7 @@ void SplitsMachine::save(oEvent &oe, gdioutput &gdi, bool doProcess) { if (doProcess) { //Try exporting. oe.exportIOFSplits(oEvent::IOF20, file.c_str(), true, false, - set(), -1, false, true, true, false); + set(), -1, false, true, true, false, false); interval = iv; synchronize = true; } @@ -1245,7 +1245,7 @@ void SplitsMachine::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) if ((interval>0 && ast==SyncTimer) || (interval==0 && ast==SyncDataUp)) { if (!file.empty()) oe->exportIOFSplits(oEvent::IOF20, file.c_str(), true, false, classes, - leg, false, true, true, false); + leg, false, true, true, false, false); } } @@ -1276,16 +1276,16 @@ void SaveMachine::status(gdioutput &gdi) { void SaveMachine::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { if (interval>0 && ast==SyncTimer) { if (!baseFile.empty()) { - wstring file = baseFile + L"meos_backup_" + oe->getDate() + L"_" + itow(saveIter++) + L".xml"; + wstring file = baseFile + L"meos_backup_" + oe->getDate() + L"_" + itow(saveIter++) + L".meosxml"; oe->autoSynchronizeLists(true); - oe->save(file); + oe->save(file, false); } } } void SaveMachine::settings(gdioutput &gdi, oEvent &oe, State state) { settingsTitle(gdi, "Säkerhetskopiering"); - wstring time=state == State::Create ? L"10:00" : getTimeMS(interval); + wstring time=state == State::Create ? L"10:00" : formatTimeMS(interval, false, SubSecond::Off); startCancelInterval(gdi, "Save", state, IntervalMinute, time); int cx = gdi.getCX(); @@ -1295,12 +1295,12 @@ void SaveMachine::settings(gdioutput &gdi, oEvent &oe, State state) { gdi.setCX(cx); } -void SaveMachine::save(oEvent &oe, gdioutput &gdi, bool doProcess) { +void SaveMachine::save(oEvent& oe, gdioutput& gdi, bool doProcess) { AutoMachine::save(oe, gdi, doProcess); - wstring minute=gdi.getText("Interval"); - int t=convertAbsoluteTimeMS(minute); + wstring minute = gdi.getText("Interval"); + int t = convertAbsoluteTimeMS(minute) / timeConstSecond; - if (t<2 || t>7200) { + if (t < 2 || t>7200) { throw meosException("Intervallet mÃ¥ste anges pÃ¥ formen MM:SS."); } wstring f = gdi.getText("BaseFile"); diff --git a/code/TabAuto.h b/code/TabAuto.h index c83054a..ce8223f 100644 --- a/code/TabAuto.h +++ b/code/TabAuto.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabBase.cpp b/code/TabBase.cpp index ffd674b..369acba 100644 --- a/code/TabBase.cpp +++ b/code/TabBase.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabBase.h b/code/TabBase.h index 707c448..2916f27 100644 --- a/code/TabBase.h +++ b/code/TabBase.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabClass.cpp b/code/TabClass.cpp index 2add24a..d882cc1 100644 --- a/code/TabClass.cpp +++ b/code/TabClass.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -76,10 +76,10 @@ TabClass::TabClass(oEvent *poe):TabBase(poe) void TabClass::clearCompetitionData() { currentResultModuleTags.clear(); pSettings.clear(); - pSavedDepth = 3600; - pFirstRestart = 3600; + pSavedDepth = timeConstHour; + pFirstRestart = timeConstHour; pTimeScaling = 1.0; - pInterval = 120; + pInterval = 2 * timeConstMinute; currentStage = -1; EditChanged = false; @@ -241,6 +241,9 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) return true; } else if (bi.id == "ApplyForking") { + int maxForking = gdi.getTextNo("MaxForkings"); + if (maxForking < 2) + throw meosException("Du mÃ¥ste ange minst tvÃ¥ gafflingsvarienater"); showForkingGuide = false; pClass pc = oe->getClass(ClassId); @@ -259,7 +262,7 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) allR[k]->setCourseId(0); } } - pair res = pc->autoForking(forkingSetup); + pair res = pc->autoForking(forkingSetup, maxForking); gdi.alert("Created X distinct forkings using Y courses.#" + itos(res.first) + "#" + itos(res.second)); loadPage(gdi); @@ -307,6 +310,10 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) gdi.setSelection("AllStages", set()); gdi.disableInput("AssignCourses"); } + else if (bi.id == "AllCourses") { + gdi.setSelection("AllCourses", { -1 }); + //gdi.enableInput("AssignCourses"); + } else if (bi.id == "ShowForking") { if (!checkClassSelected(gdi)) return false; @@ -469,7 +476,7 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) int nst = oe->convertAbsoluteTime(st); if (nst >= 0 && warnDrawStartTime(gdi, nst, true)) { - nst = 3600; + nst = timeConstHour; st = oe->getAbsTime(nst); } if (nst>0) @@ -695,8 +702,27 @@ int TabClass::multiCB(gdioutput &gdi, int type, void *data) EditChanged=true; if (ii.id=="NStage") gdi.enableInput("SetNStage"); + else if (ii.id == "CourseFilter") { + gdi.addTimeoutMilli(500, "FilterCourseTimer", MultiCB); + } //else if (ii.id=="") } + else if (type == GUI_TIMER) { + TimerInfo& ti = *(TimerInfo*)(data); + if (ti.id == "FilterCourseTimer") { + const wstring &filter = gdi.getText("CourseFilter"); + if (filter != courseFilter) { + courseFilter = filter; + vector> out; + oe->getCourses(out, courseFilter, true, false); + set sel; + gdi.getSelection("AllCourses", sel); + gdi.addItem("AllCourses", out); + gdi.setSelection("AllCourses", sel); + } + } + + } return 0; } @@ -832,8 +858,8 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) else ds.ctrl = 0; // Save settings with class - ds.firstStart = 3600; - ds.interval = 120; + ds.firstStart = timeConstHour; + ds.interval = 2 * timeConstMinute; ds.vacant = 1; res.push_back(ds); @@ -1023,7 +1049,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) gdi.pushX(); gdi.fillRight(); - gdi.addInput("FirstStart", oe->getAbsTime(3600), 10, 0, L"Första (ordinarie) start:"); + gdi.addInput("FirstStart", oe->getAbsTime(timeConstHour), 10, 0, L"Första (ordinarie) start:"); gdi.addInput("MinInterval", L"2:00", 10, 0, L"Minsta startintervall:"); gdi.addInput("Vacances", getDefaultVacant(), 10, 0, L"Andel vakanser:"); gdi.fillDown(); @@ -1085,7 +1111,8 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) } else if (bi.id == "SelectAllNoneP") { bool select = bi.getExtraInt() != 0; - for (int k = 0; k < oe->getNumClasses(); k++) { + const int nc = oe->getNumClasses(); + for (int k = 0; k < nc; k++) { gdi.check("PLUse" + itos(k), select); gdi.setInputStatus("First" + itos(k), select); } @@ -1107,8 +1134,8 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) pInterval = interval; oListParam par; - - for (int k = 0; k < oe->getNumClasses(); k++) { + const int nc = oe->getNumClasses(); + for (int k = 0; k < nc; k++) { if (!gdi.hasWidget("PLUse" + itos(k))) continue; BaseInfo *biu = gdi.setText("PLUse" + itos(k), L"", false); @@ -1260,7 +1287,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data) } else if (bi.id == "DrawAll") { int origin = bi.getExtraInt(); - wstring firstStart = oe->getAbsTime(3600); + wstring firstStart = oe->getAbsTime(timeConstHour); wstring minInterval = L"2:00"; wstring vacances = getDefaultVacant(); if (gdi.hasWidget("Vacances")) { @@ -2529,7 +2556,7 @@ void TabClass::showClassSettings(gdioutput &gdi) if (ci.hasFixedTime) { ii->setBgColor(fixedColor).setExtra(fixedColor); } - ii = &gdi.addInput(xp + classW + 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, SubSecond::Auto), 7, DrawClassesCB); if (ci.hasFixedTime) { ii->setBgColor(fixedColor).setExtra(fixedColor); } @@ -3323,9 +3350,24 @@ bool TabClass::loadPage(gdioutput &gdi) gdi.fillDown(); gdi.addListBox("Classes", 200, showAdvanced ? 512 : 420, ClassesCB, L"").isEdit(false).ignore(true); - gdi.setTabStops("Classes", 185); + gdi.setTabStops("Classes", 170); oe->fillClasses(gdi, "Classes", oEvent::extraDrawn, oEvent::filterNone); + + bool hasIgnoreStart = false; + bool hasFreeStart = false; + + if (!showAdvanced) { + vector clsList; + oe->getClasses(clsList, false); + for (auto c : clsList) { + if (c->ignoreStartPunch()) + hasIgnoreStart = true; + if (c->hasFreeStart()) + hasFreeStart = true; + } + } + gdi.newColumn(); gdi.dropLine(2); @@ -3419,11 +3461,15 @@ bool TabClass::loadPage(gdioutput &gdi) gdi.addCheckbox("NoTiming", "Utan tidtagning", 0); } - if (showAdvanced) { + if (showAdvanced || hasIgnoreStart || hasFreeStart) { 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"); + + if (showAdvanced || hasFreeStart) + gdi.addCheckbox("FreeStart", "Fri starttid", 0, false, "Klassen lottas inte, startstämpling"); + + if (showAdvanced || hasIgnoreStart) + gdi.addCheckbox("IgnoreStart", "Ignorera startstämpling", 0, false, "Uppdatera inte starttiden vid startstämpling"); gdi.dropLine(2); gdi.popX(); } @@ -3721,8 +3767,8 @@ void TabClass::drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClas return; } - int firstStart = 3600, - interval = 120, + int firstStart = timeConstHour, + interval = 2 * timeConstMinute, vac = _wtoi(lastNumVac.c_str()); int pairSize = lastPairSize; @@ -3787,12 +3833,12 @@ void TabClass::drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClas if (method == oEvent::DrawMethod::Pursuit || method == oEvent::DrawMethod::ReversePursuit) { gdi.addInput("MaxAfter", lastMaxAfter, 10, 0, L"Maxtid efter:", L"Maximal tid efter ledaren för att delta i jaktstart").setSynchData(&lastMaxAfter); - gdi.addInput("TimeRestart", oe->getAbsTime(firstStart + 3600), 8, 0, L"Första omstartstid:"); + gdi.addInput("TimeRestart", oe->getAbsTime(firstStart + timeConstHour), 8, 0, L"Första omstartstid:"); gdi.addInput("ScaleFactor", lastScaleFactor, 8, 0, L"Tidsskalning:").setSynchData(&lastScaleFactor); } if (method != oEvent::DrawMethod::Simultaneous) - gdi.addInput("Interval", formatTime(interval), 10, 0, L"Startintervall (min):").setSynchData(&lastInterval); + gdi.addInput("Interval", formatTime(interval, SubSecond::Auto), 10, 0, L"Startintervall (min):").setSynchData(&lastInterval); if ((method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || @@ -3944,9 +3990,9 @@ void TabClass::pursuitDialog(gdioutput &gdi) { gdi.fillRight(); - gdi.addInput("MaxAfter", formatTime(pSavedDepth), 10, 0, L"Maxtid efter:", L"Maximal tid efter ledaren för att delta i jaktstart"); - gdi.addInput("TimeRestart", L"+" + formatTime(pFirstRestart), 8, 0, L"Första omstartstid:", L"Ange tiden relativt klassens första start"); - gdi.addInput("Interval", formatTime(pInterval), 8, 0, L"Startintervall:", L"Ange startintervall för minutstart"); + gdi.addInput("MaxAfter", formatTime(pSavedDepth, SubSecond::Off), 10, 0, L"Maxtid efter:", L"Maximal tid efter ledaren för att delta i jaktstart"); + gdi.addInput("TimeRestart", L"+" + formatTime(pFirstRestart, SubSecond::Off), 8, 0, L"Första omstartstid:", L"Ange tiden relativt klassens första start"); + gdi.addInput("Interval", formatTime(pInterval, SubSecond::Off), 8, 0, L"Startintervall:", L"Ange startintervall för minutstart"); wchar_t bf[32]; swprintf_s(bf, L"%f", pTimeScaling); gdi.addInput("ScaleFactor", bf, 8, 0, L"Tidsskalning:"); @@ -4027,7 +4073,7 @@ void TabClass::showClassSelection(gdioutput &gdi, int &bx, int &by, GUICALLBACK int cx = gdi.getCX(); int width = gdi.scaleLength(230); gdi.addListBox("Classes", 200, 480, classesCB, L"Klasser:", L"", true); - gdi.setTabStops("Classes", 185); + gdi.setTabStops("Classes", 170); gdi.fillRight(); gdi.pushX(); @@ -4258,6 +4304,9 @@ void TabClass::defineForking(gdioutput &gdi, bool clearSettings) { gdi.dropLine(2); gdi.pushY(); + + courseFilter = L""; + gdi.addInput("CourseFilter", courseFilter, 16, MultiCB, L"Filtrera:"); gdi.addListBox("AllCourses", 180, 300, 0, L"Banor:", L"", true); oe->fillCourses(gdi, "AllCourses", true); int bxp = gdi.getCX(); @@ -4292,6 +4341,9 @@ void TabClass::defineForking(gdioutput &gdi, bool clearSettings) { } } + gdi.dropLine(); + gdi.addInput("MaxForkings", L"100", 5, nullptr, L"Max antal gaffllingsvarianter att skapa:", + L"Det uppskattade antalet startade lag i klassen är ett lämpligt värde."); gdi.dropLine(); gdi.fillRight(); gdi.addButton("ApplyForking", "Calculate and apply forking", MultiCB); @@ -4300,9 +4352,11 @@ void TabClass::defineForking(gdioutput &gdi, bool clearSettings) { gdi.setCX(bxp); gdi.setCY(byp); + gdi.addButton("AllCourses", "Välj allt", MultiCB); gdi.fillDown(); gdi.addButton("ClearCourses", "Clear selections", MultiCB); + gdi.setCX(bxp); gdi.addString("", 10, "help:assignforking"); gdi.addString("", ty, tx, boldLarge, L"Assign courses and apply forking to X#" + pc->getName()); @@ -4384,7 +4438,7 @@ void TabClass::getClassSettingsTable(gdioutput &gdi, GUICALLBACK cb) { gdi.addString("", yp, f, 1, "Direktanmälan"); vector< pair > arg; - oe->fillCourses(arg, true); + oe->getCourses(arg, L"", true); for (size_t k = 0; k < cls.size(); k++) { pClass it = cls[k]; @@ -4534,8 +4588,11 @@ void TabClass::updateStartData(gdioutput &gdi, pClass pc, int leg, bool updateDe if (st == STChange) { if (typeid(sdataBase) != typeid(ListBoxInfo)) { InputInfo sdII = dynamic_cast(sdataBase); + string rp; + gdi.getWidgetRestorePoint(sdKey, rp); gdi.removeWidget(sdKey); - gdi.addSelection(sdII.getX(), sdII.getY(), sdKey, sdII.getWidth(), 200, MultiCB); + gdi.addSelection(sdII.getX(), sdII.getY(), sdKey, int(sdII.getWidth()/gdi.getScale()), 200, MultiCB); + gdi.setWidgetRestorePoint(sdKey, rp); setParallelOptions(sdKey, gdi, pc, leg); } else if (forceWrite) { @@ -4545,9 +4602,12 @@ void TabClass::updateStartData(gdioutput &gdi, pClass pc, int leg, bool updateDe else { if (typeid(sdataBase) != typeid(InputInfo)) { ListBoxInfo sdLBI = dynamic_cast(sdataBase); + string rp; + gdi.getWidgetRestorePoint(sdKey, rp); gdi.removeWidget(sdKey); string val = "-"; gdi.addInput(sdLBI.getX(), sdLBI.getY(), sdKey, pc->getStartDataS(leg), 8, MultiCB); + gdi.setWidgetRestorePoint(sdKey, rp); } else if (forceWrite) { gdi.setText(sdKey, pc->getStartDataS(leg), true); @@ -4678,12 +4738,12 @@ void TabClass::writeDrawInfo(gdioutput &gdi, const DrawInfo &drawInfoIn) { gdi.setText("Vacances", itow(int(drawInfoIn.vacancyFactor *100.0)) + L"%"); gdi.setText("Extra", itow(int(drawInfoIn.extraFactor * 100.0) ) + L"%"); - gdi.setText("BaseInterval", formatTime(drawInfoIn.baseInterval)); + gdi.setText("BaseInterval", formatTime(drawInfoIn.baseInterval, SubSecond::Off)); gdi.check("AllowNeighbours", drawInfoIn.allowNeighbourSameCourse); gdi.check("CoursesTogether", drawInfoIn.coursesTogether); - gdi.setText("MinInterval", formatTime(drawInfoIn.minClassInterval)); - gdi.setText("MaxInterval", formatTime(drawInfoIn.maxClassInterval)); + gdi.setText("MinInterval", formatTime(drawInfoIn.minClassInterval, SubSecond::Off)); + gdi.setText("MaxInterval", formatTime(drawInfoIn.maxClassInterval, SubSecond::Off)); gdi.setText("nFields", drawInfoIn.nFields); gdi.setText("FirstStart", oe->getAbsTime(drawInfoIn.firstStart)); } @@ -4739,10 +4799,10 @@ bool TabClass::warnDrawStartTime(gdioutput &gdi, const wstring &firstStart) { bool TabClass::warnDrawStartTime(gdioutput &gdi, int time, bool absTime) { if (absTime) - time = oe->getRelativeTime(formatTimeHMS(time)); + time = oe->getRelativeTime(formatTimeHMS(time, SubSecond::Off)); - if (!hasWarnedStartTime && (time > 3600 * 11 && !oe->useLongTimes())) { - bool res = gdi.ask(L"warn:latestarttime#" + itow(time/3600)); + if (!hasWarnedStartTime && (time > timeConstHour * 11 && !oe->useLongTimes())) { + bool res = gdi.ask(L"warn:latestarttime#" + itow(time/timeConstHour)); if (res) hasWarnedStartTime = true; return !res; @@ -4812,7 +4872,7 @@ void DrawSettingsCSV::write(gdioutput &gdi, const oEvent &oe, const wstring &fn, else line.emplace_back(""); line.push_back(gdi.narrow(oe.getAbsTime(ci.firstStart))); - line.push_back(gdi.narrow(formatTime(ci.interval))); + line.push_back(gdi.narrow(formatTime(ci.interval, SubSecond::Off))); line.push_back(itos(ci.vacant)); writer.outputRow(line); } @@ -5130,8 +5190,8 @@ public: else if (type == GuiEventType::GUI_BUTTON) { if (info.id == "AddGroup") { int id = 1; - int firstStart = 3600; - int length = 3600; + int firstStart = timeConstHour; + int length = timeConstHour; for (auto &g : oe.getStartGroups(false)) { id = max(id, g.first+1); firstStart = max(firstStart, g.second.lastStart); diff --git a/code/TabClass.h b/code/TabClass.h index 4561cdc..5ab1c9e 100644 --- a/code/TabClass.h +++ b/code/TabClass.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -33,9 +33,9 @@ class TabClass : int maxTime; PursuitSettings(oClass &c) { - firstTime = 3600; + firstTime = timeConstHour; use = c.interpretClassType() != ctOpen; - maxTime = 3600; + maxTime = timeConstHour; } }; @@ -103,6 +103,9 @@ class TabClass : wstring lastScaleFactor; wstring lastMaxAfter; + // Filter for course assignment + wstring courseFilter; + bool lastHandleBibs; // Generate a table with class settings void showClassSettings(gdioutput &gdi); diff --git a/code/TabClub.cpp b/code/TabClub.cpp index 9caa2cc..a166d2a 100644 --- a/code/TabClub.cpp +++ b/code/TabClub.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -351,8 +351,8 @@ int TabClub::clubCB(gdioutput &gdi, int type, void *data) gdi.fillDown(); gdi.popX(); - - TabList::customTextLines(*oe, "IVExtra", gdi); + gdi.dropLine(2.5); + TabList::customTextLines(*oe, "IVExtra", true, gdi); gdi.dropLine(1); @@ -364,7 +364,8 @@ int TabClub::clubCB(gdioutput &gdi, int type, void *data) oe->getDI().fillDataFields(gdi); } else if (bi.id == "SaveSettings") { - oe->getDI().saveDataFields(gdi); + set modified; + oe->getDI().saveDataFields(gdi, modified); TabList::saveExtraLines(*oe, "IVExtra", gdi); diff --git a/code/TabClub.h b/code/TabClub.h index 1d286f9..a8fb9c8 100644 --- a/code/TabClub.h +++ b/code/TabClub.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabCompetition.cpp b/code/TabCompetition.cpp index 2f977ee..082751e 100644 --- a/code/TabCompetition.cpp +++ b/code/TabCompetition.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -133,6 +133,8 @@ bool TabCompetition::save(gdioutput &gdi, bool write) oe->updateStartTimes(delta); } + oe->supportSubSeconds(gdi.isChecked("SubSecond")); + oe->setDate(date, true); oe->useLongTimes(longTimes); oe->setName(gdi.getText("Name"), true); @@ -163,7 +165,7 @@ bool TabCompetition::importFile(HWND hWnd, gdioutput &gdi) return false; gdi.setWaitCursor(true); - if (oe->open(fileName, true, false)) { + if (oe->open(fileName, true, false, false)) { gdi.setWindowTitle(oe->getTitleName()); resetSaveTimer(); @@ -171,7 +173,7 @@ bool TabCompetition::importFile(HWND hWnd, gdioutput &gdi) wstring base = constructBase(L"base", L""); wchar_t newBase[_MAX_PATH]; getUserFile(newBase, base.c_str()); - oe->save(newBase); + oe->save(newBase, false); return true; } @@ -188,7 +190,7 @@ bool TabCompetition::exportFileAs(HWND hWnd, gdioutput &gdi) return false; gdi.setWaitCursor(true); - if (!oe->save(fileName.c_str())) { + if (!oe->save(fileName.c_str(), false)) { gdi.alert(L"Fel: Filen " + fileName+ L" kunde inte skrivas."); return false; } @@ -662,7 +664,9 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.dropLine(3); gdi.popX(); - gdi.addCheckbox("Clear", "Nollställ databaser", 0, true); + gdi.addCheckbox("Clear", "Nollställ databaser"); + gdi.addCheckbox("IncludeWithoutClub", "Inkludera klubblösa"); + gdi.dropLine(3); gdi.popX(); @@ -680,6 +684,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.setWaitCursor(true); gdi.addString("", 0, "Importerar..."); bool clear = gdi.isChecked("Clear"); + bool requireClub = !gdi.isChecked("IncludeWithoutClub"); wstring club = gdi.getText("ClubFile"); wstring cmp = gdi.getText("CmpFile"); if (club == cmp) @@ -698,7 +703,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) if (clubCsv) throw meosException("Klubbfil fÃ¥r inte anges vid CSV import."); - oe->importXML_IOF_Data(club, cmp, clear); + oe->importXML_IOF_Data(club, cmp, requireClub, clear); } gdi.dropLine(); @@ -780,7 +785,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) wchar_t newBase[_MAX_PATH]; getUserFile(newBase, base.c_str()); if (!fileExists(newBase)) - oe->save(newBase); + oe->save(newBase, false); loadConnectionPage(gdi); } @@ -911,7 +916,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) oEvent nextStage(gdi); if (!file.empty()) - success = nextStage.open(file.c_str(), false, false); + success = nextStage.open(file.c_str(), false, false, false); if (success) success = nextStage.getNameId(0) == oe->getDCI().getString("PostEvent"); @@ -1138,7 +1143,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) wstring startlist = getTempFile(); bool eventorUTC = oe->getPropertyInt("UseEventorUTC", 0) != 0; oe->exportIOFStartlist(oEvent::IOF30, startlist.c_str(), eventorUTC, - set(), false, false, true); + set(), false, false, true, true); vector fileList; fileList.push_back(startlist); @@ -1232,7 +1237,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) bool eventorUTC = oe->getPropertyInt("UseEventorUTC", 0) != 0; oe->exportIOFSplits(oEvent::IOF30, resultlist.c_str(), false, eventorUTC, classes, -1, false, true, - false, true); + false, true, true); vector fileList; fileList.push_back(resultlist); @@ -1332,6 +1337,8 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.dropLine(3); gdi.popX(); gdi.addCheckbox("EventorDb", "Uppdatera löpardatabasen", CompetitionCB, true); + gdi.addCheckbox("IncludeWithoutClub", "Inkludera klubblösa"); + gdi.dropLine(3); gdi.popX(); gdi.addButton("Cancel", "Avbryt", CompetitionCB); @@ -1339,14 +1346,16 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) } else if (bi.id == "EventorCmp") { gdi.setInputStatus("EventorSel", gdi.isChecked(bi.id)); - gdi.setInputStatus("EventorNext", gdi.isChecked(bi.id) | gdi.isChecked("EventorDb")); + gdi.setInputStatus("EventorNext", gdi.isChecked(bi.id) || gdi.isChecked("EventorDb")); } else if (bi.id == "EventorDb") { - gdi.setInputStatus("EventorNext", gdi.isChecked(bi.id) | gdi.isChecked("EventorCmp")); + gdi.setInputStatus("EventorNext", gdi.isChecked(bi.id) || gdi.isChecked("EventorCmp")); } else if (bi.id == "EventorNext") { bool cmp = gdi.isChecked("EventorCmp"); bool db = gdi.isChecked("EventorDb"); + bool withNoClub = gdi.isChecked("IncludeWithoutClub"); + ListBoxInfo lbi; gdi.getSelectedItem("EventorSel", lbi); const CompetitionInfo *ci = 0; @@ -1355,6 +1364,8 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.clearPage(true); gdi.setData("UpdateDB", db); + gdi.setData("IncludeWithoutClub", withNoClub); + gdi.pushX(); if (cmp && ci) { gdi.setData("EventIndex", lbi.data); @@ -1412,11 +1423,14 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.popX(); } else if (bi.id == "EventorImport") { - const int diffZeroTime = 3600; + const int diffZeroTime = timeConstHour; DWORD id; DWORD db; + DWORD withNoClub; + gdi.getData("EventorId", id); gdi.getData("UpdateDB", db); + gdi.getData("IncludeWithoutClub", withNoClub); DWORD eventIndex; gdi.getData("EventIndex", eventIndex); @@ -1444,7 +1458,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) firstStart = t; zeroTime = t - diffZeroTime; if (zeroTime<0) - zeroTime += 3600*24; + zeroTime += timeConstHour * 24; startType = gdi.getSelectedItem("StartType").first; lastEntry = gdi.getText("LastEntryDate"); @@ -1509,8 +1523,8 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) else tRunnerDB = extractedFiles[0]; } - - oe->importXML_IOF_Data(tClubs, tRunnerDB, true); + + oe->importXML_IOF_Data(tClubs, tRunnerDB, withNoClub == 0, true); removeTempFile(tClubs); if (id > 0) { @@ -1522,6 +1536,12 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) if (createNew && id>0) { gdi.addString("", 1, "Skapar ny tävling"); oe->newCompetition(L"New"); + oe->loadDefaults(); + + bool importHiredCard = true; + if (importHiredCard) + importDefaultHiredCards(gdi); + oe->importXML_EntryData(gdi, tEvent, false, false, noFilter, noType); oe->setZeroTime(formatTimeHMS(zeroTime), false); oe->getDI().setDate("OrdinaryEntry", lastEntry); @@ -1546,7 +1566,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) if (!course.empty()) { gdi.dropLine(); - TabCourse::runCourseImport(gdi, course, oe, true); + TabCourse::runCourseImport(gdi, course, oe, true, false); } set clsWithRef; @@ -1563,7 +1583,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) switch (startType) { case SMCommon: - oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"0", + oe->automaticDrawAll(gdi, formatTimeHMS(firstStart, SubSecond::Off), L"0", L"0", oEvent::VacantPosition::Mixed, false, false, oEvent::DrawMethod::Random, 1); break; @@ -1589,7 +1609,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) } } if (!skip) - oe->automaticDrawAll(gdi, formatTimeHMS(firstStart), L"2:00", + oe->automaticDrawAll(gdi, formatTimeHMS(firstStart, SubSecond::Off), L"2:00", L"2", oEvent::VacantPosition::Mixed, true, false, oEvent::DrawMethod::MeOS, 1); break; @@ -1780,7 +1800,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) if (filterIndex == ImportFormats::IOF30 || filterIndex == ImportFormats::IOF203) { bool useUTC = oe->getDCI().getInt("UTC") != 0; oe->exportIOFStartlist(filterIndex == ImportFormats::IOF30 ? oEvent::IOF30 : oEvent::IOF20, - save.c_str(), useUTC, allTransfer, individual, includeStage, false); + save.c_str(), useUTC, allTransfer, individual, includeStage, false, false); } else if (filterIndex == ImportFormats::OE) { oe->exportOECSV(save.c_str(), cSVLanguageHeaderIndex, false); @@ -1838,7 +1858,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) if (!gdi.hasWidget("LegType")) { oe->exportIOFSplits(ver, save.c_str(), true, useUTC, - allTransfer, -1, false, unroll, includeStage, false); + allTransfer, -1, false, unroll, includeStage, false, false); } else { ListBoxInfo leglbi; @@ -1860,17 +1880,17 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) for (int leg = 0; legexportIOFSplits(ver, file.c_str(), true, useUTC, - allTransfer, leg, false, unroll, includeStage, false); + allTransfer, leg, false, unroll, includeStage, false, false); } } else if (leglbi.data == 3) { oe->exportIOFSplits(ver, file.c_str(), true, useUTC, allTransfer, - -1, true, unroll, includeStage, false); + -1, true, unroll, includeStage, false, false); } else { int leg = leglbi.data == 1 ? -1 : leglbi.data - 10; oe->exportIOFSplits(ver, file.c_str(), true, useUTC, allTransfer, - leg, false, unroll, includeStage, false); + leg, false, unroll, includeStage, false, false); } } } @@ -1951,6 +1971,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) } oe->newCompetition(lang.tl(L"Ny tävling")); + oe->loadDefaults(); gdi.setWindowTitle(L""); if (useEventor()) { @@ -2077,15 +2098,18 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.disableInput("Cancel"); gdi.disableInput("BrowseCourse"); gdi.disableInput("AddClasses"); + gdi.disableInput("CreateClasses"); try { - TabCourse::runCourseImport(gdi, filename, oe, gdi.isChecked("AddClasses")); + TabCourse::runCourseImport(gdi, filename, oe, gdi.isChecked("AddClasses"), + gdi.isChecked("CreateClasses")); } - catch (std::exception &) { + catch (const std::exception &) { gdi.enableInput("DoImportCourse"); gdi.enableInput("Cancel"); gdi.enableInput("BrowseCourse"); gdi.enableInput("AddClasses"); + gdi.enableInput("CreateClasses"); throw; } gdi.dropLine(); @@ -2136,7 +2160,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.setWaitCursor(true); xml.openOutput(fileName.c_str(), false); - IOF30Interface writer(oe, false); + IOF30Interface writer(oe, false, false); writer.writeRunnerDB(oe->getRunnerDatabase(), xml); gdi.setWaitCursor(false); } @@ -2151,7 +2175,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.setWaitCursor(true); xml.openOutput(fileName.c_str(), false); - IOF30Interface writer(oe, false); + IOF30Interface writer(oe, false, false); writer.writeClubDB(oe->getRunnerDatabase(), xml); gdi.setWaitCursor(false); } @@ -2242,6 +2266,9 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.setInputStatus(gdi.narrow(fn).c_str(), !ii.text.empty()); } } + else if (ii.id == "Name") { + updateWarning(gdi); + } } else if (type==GUI_EVENT) { EventInfo ei=*(EventInfo *)data; @@ -2277,16 +2304,16 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data) gdi.setData("RunnerIx", ix); gdi.dropLine(); gdi.addSelection("Classes", 200, 300, 0, L"Klasser:"); - oe->fillClasses(gdi, "Classes", oEvent::extraNone, oEvent::filterNone); + oe->fillClasses(gdi, "Classes", oEvent::extraNone, oEvent::filterOnlySingle); - if (lastSelectedClass != -1) - gdi.selectItemByData("Classes", lastSelectedClass); - else + if (lastSelectedClass == -1 || !gdi.selectItemByData("Classes", lastSelectedClass)) gdi.selectFirstItem("Classes"); gdi.dropLine(); gdi.fillRight(); gdi.addButton("DBEntry", "Anmäl", CompetitionCB).setDefault(); + gdi.setInputStatus("DBEntry", gdi.getSelectedItem("Classes").first > 0); // Cannot change + gdi.addButton("CancelEntry", "Avbryt", CompetitionCB).setCancel(); gdi.refresh(); } @@ -2339,7 +2366,7 @@ int TabCompetition::restoreCB(gdioutput &gdi, int type, void *data) { if (ti.id == "") { wstring fi(bi.FullPath); - if (!oe->open(fi, false, false)) { + if (!oe->open(fi, false, false, false)) { gdi.alert("Kunde inte öppna tävlingen."); } else { @@ -2392,7 +2419,7 @@ void TabCompetition::copyrightLine(gdioutput &gdi) const gdi.dropLine(0.4); gdi.fillDown(); - gdi.addString("", 0, makeDash(L"#Copyright © 2007-2022 Melin Software HB")); + gdi.addString("", 0, makeDash(L"#Copyright © 2007-2023 Melin Software HB")); gdi.dropLine(1); gdi.popX(); @@ -2422,7 +2449,7 @@ void TabCompetition::loadAboutPage(gdioutput &gdi) const gdi.dropLine(1.5); gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); - gdi.addStringUT(1, makeDash(L"Copyright © 2007-2022 Melin Software HB")); + gdi.addStringUT(1, makeDash(L"Copyright © 2007-2023 Melin Software HB")); gdi.dropLine(); gdi.addStringUT(10, "The database connection used is MySQL++\nCopyright " "(c) 1998 by Kevin Atkinson, (c) 1999, 2000 and 2001 by MySQL AB," @@ -2463,6 +2490,20 @@ void TabCompetition::loadAboutPage(gdioutput &gdi) const gdi.refresh(); } +void TabCompetition::updateWarning(gdioutput &gdi) const { + const wstring &n = gdi.getText("Name"); + const wstring &w = gdi.getText("cmpwarning"); + constexpr int limit = 32; + if (n.length() < limit && !w.empty()) { + gdi.setText("warningicon", L"", true); + gdi.setText("cmpwarning", L"", true); + } + else if (n.length() >= limit && w.empty()) { + gdi.setText("warningicon", L"514", true); + gdi.setText("cmpwarning", L"Ett lÃ¥ngt tävlingsnamn kan ge oväntad nerskalning av utskrifter.", true); + } +} + bool TabCompetition::useEventor() const { return oe->getPropertyInt("UseEventor", 0) == 1; } @@ -2566,7 +2607,7 @@ bool TabCompetition::loadPage(gdioutput &gdi) gdi.pushX(); gdi.fillRight(); - gdi.addInput("Name", oe->getName(), 24, 0, L"Tävlingsnamn:"); + gdi.addInput("Name", oe->getName(), 24, CompetitionCB, L"Tävlingsnamn:"); gdi.fillDown(); gdi.addInput("Annotation", oe->getAnnotation(), 20, 0, L"Kommentar / version:") @@ -2579,12 +2620,16 @@ bool TabCompetition::loadPage(gdioutput &gdi) gdi.fillDown(); gdi.dropLine(1.2); + //int ccx = gdi.getCX(); gdi.addCheckbox("LongTimes", "Aktivera stöd för tider över 24 timmar", CompetitionCB, oe->useLongTimes()); - + gdi.addCheckbox("SubSecond", "Aktivera stöd för tiondels sekunder", CompetitionCB, oe->supportSubSeconds()); + + gdi.dropLine(0.3); if (false && oe->isClient()) { gdi.popX(); gdi.disableInput("ZeroTime"); gdi.disableInput("LongTimes"); + gdi.disableInput("SubSecond"); if (oe->useLongTimes()) gdi.disableInput("Date"); } @@ -2651,6 +2696,14 @@ bool TabCompetition::loadPage(gdioutput &gdi) gdi.fillDown(); gdi.popX(); + gdi.fillRight(); + gdi.addString("warningicon", textImage, "S25"); + gdi.dropLine(0.2); + gdi.fillDown(); + gdi.addString("cmpwarning", 0, ""); + updateWarning(gdi); + gdi.popX(); + gdi.newColumn(); gdi.dropLine(3); gdi.setCX(gdi.getCX()+gdi.scaleLength(60)); @@ -2908,26 +2961,26 @@ void TabCompetition::getEventorCompetitions(gdioutput &gdi, date.getObjectString("Clock", ci.firstStart); if (useEventorUTC()) { - int offset = getTimeZoneInfo(ci.Date); + int offset = getTimeZoneInfo(ci.Date) * timeConstSecond; int t = convertAbsoluteTimeISO(ci.firstStart); int nt = t - offset; int dayOffset = 0; if (nt < 0) { - nt += 24*3600; + nt += 24 * timeConstHour; dayOffset = -1; } - else if (nt > 24*3600) { - nt -= 24*3600; + else if (nt > 24* timeConstHour) { + nt -= 24* timeConstHour; dayOffset = 1; } - ci.firstStart = formatTimeHMS(nt); + ci.firstStart = formatTimeHMS(nt, SubSecond::Off); //TODO: Take dayoffset into account } xmlEvents[k].getObjectString("WebURL", ci.url); xmlobject aco = xmlEvents[k].getObject("Account"); if (aco) { - string type = aco.getAttrib("type").get(); + string type = aco.getAttrib("type").getStr(); wstring no; aco.getObjectString("AccountNo", no); @@ -2960,8 +3013,8 @@ void TabCompetition::getEventorCompetitions(gdioutput &gdi, SYSTEMTIME st; convertDateYMS(breakDate, st, false); - __int64 time = SystemTimeToInt64Second(st) - 1; - breakDate = convertSystemDate(Int64SecondToSystemTime(time)); + __int64 time = SystemTimeToInt64TenthSecond(st) - 10; + breakDate = convertSystemDate(Int64TenthSecondToSystemTime(time)); if (ci.lastNormalEntryDate.empty() || ci.lastNormalEntryDate >= breakDate) ci.lastNormalEntryDate = breakDate; @@ -3080,7 +3133,9 @@ void TabCompetition::getEventorCmpData(gdioutput &gdi, int id, if (dbFile.length() > 0) { gdi.addString("", 0, "Hämtar löpardatabasen..."); gdi.refreshFast(); - dwl.downloadFile(eventorBase + L"export/cachedcompetitors?organisationIds=1&includePreselectedClasses=false&zip=true" + iofExportVersion, dbFile, key); + //dwl.downloadFile(eventorBase + L"export/cachedcompetitors?organisationIds=1&includePreselectedClasses=false&zip=true" + iofExportVersion, dbFile, key); + dwl.downloadFile(eventorBase + L"export/cachedcompetitors?includePreselectedClasses=false&zip=true" + iofExportVersion, dbFile, key); + dwl.createDownloadThread(); while (dwl.isWorking()) { Sleep(100); @@ -3640,7 +3695,7 @@ FlowOperation TabCompetition::checkStageFilter(gdioutput & gdi, xml.read(fname); xmlobject xo = xml.getObject("EntryList"); set scanFilter; - IOF30Interface reader(oe, false); + IOF30Interface reader(oe, false, false); vector idProviders; if (xo) { if (xo.getAttrib("iofVersion")) { @@ -3967,9 +4022,7 @@ void TabCompetition::loadSettings(gdioutput &gdi) { gdi.addString("", 1, "Tidszon"); gdi.dropLine(0.3); - gdi.addCheckbox("UTC", "Exportera tider i UTC", 0, - oe->getDCI().getInt("UTC") == 1); - + gdi.addCheckbox("UTC", "Exportera tider i UTC", nullptr, oe->getDCI().getInt("UTC") != 0); gdi.newColumn(); gdi.popY(); @@ -3999,16 +4052,60 @@ void TabCompetition::loadSettings(gdioutput &gdi) { gdi.dropLine(3); gdi.addString("", 1, "Ã…ldersgränser, reducerad anmälningsavgift"); - fields.clear(); - fields.push_back("YouthAge"); - fields.push_back("SeniorAge"); + gdi.addString("", 0, "Ungdomar och äldre kan fÃ¥ reducerad avgift"); + gdi.dropLine(0.5); + gdi.fillRight(); - oe->getDI().buildDataFields(gdi, fields, 10); + auto yfields = oe->getDI().buildDataFields(gdi, { "YouthAge" , "SeniorAge" }, 10); + + class HandleAge : public GuiHandler { + public: + void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) final { + if (type == GuiEventType::GUI_INPUT) + update(gdi, true); + } + + void update(gdioutput &gdi, bool update) { + int youth = gdi.getTextNo("YouthAge_odc"); + int senior = gdi.getTextNo("SeniorAge_odc"); + + InputInfo &ii = dynamic_cast(gdi.getBaseInfo("SeniorAge_odc")); + bool warn = senior > 1 && senior <= youth + 1; + ii.setBgColor(warn ? colorLightRed : colorDefault); + + string i1, i2, i3; + if (youth <= 0 && senior <= 0) { + i1 = "Ingen reducerad avgift"; + } + else { + i1 = "Reducerad avgift för:"; + + if (youth > 0) + i2 = "Unga, till och med X Ã¥r#" + itos(youth); + else + i2 = "Äldre, frÃ¥n och med X Ã¥r#" + itos(senior); + + if (youth > 0 && senior > 0) + i3 = "Äldre, frÃ¥n och med X Ã¥r#" + itos(senior); + } + gdi.setText("ReduceInfo", lang.tl(i1), update); + gdi.setText("ReduceInfoL1", i2.empty() ? L"" : L"\u25AA " + lang.tl(i2), update); + gdi.setText("ReduceInfoL2", i3.empty() ? L"" : L"\u25AA " + lang.tl(i3), update); + } + }; gdi.fillDown(); gdi.popX(); - gdi.dropLine(3); + gdi.dropLine(2.8); + gdi.addString("ReduceInfo", 0, "Ingen reducerad avgift"); + gdi.addString("ReduceInfoL1", 0, "-"); + gdi.addString("ReduceInfoL2", 0, "-"); + auto handler = make_shared(); + yfields[0]->setHandler(handler); + yfields[1]->setHandler(handler); + + gdi.dropLine(0.3); gdi.addString("", 1, "Valuta"); fields.clear(); @@ -4053,6 +4150,8 @@ void TabCompetition::loadSettings(gdioutput &gdi) { oe->getDI().buildDataFields(gdi, fields, 10); oe->getDI().fillDataFields(gdi); + handler->update(gdi, false); + gdi.dropLine(1); int bottom = gdi.getCY(); @@ -4097,17 +4196,16 @@ void TabCompetition::loadSettings(gdioutput &gdi) { } void TabCompetition::saveSettings(gdioutput &gdi) { - vector fields; + vector fields = { "CardFee" ,"EliteFee" ,"EntryFee","YouthFee" }; vector fees(4); - fields.push_back("CardFee"); - fields.push_back("EliteFee"); - fields.push_back("EntryFee"); - fields.push_back("YouthFee"); - + for (int k = 0; k<4; k++) fees[k] = oe->getDCI().getInt(fields[k]); + wstring factor = oe->getDCI().getString("LateEntryFactor"); - oe->getDI().saveDataFields(gdi); + set modified; + + oe->getDI().saveDataFields(gdi, modified); bool changedFee = false; bool changedCardFee = false; @@ -4119,33 +4217,60 @@ void TabCompetition::saveSettings(gdioutput &gdi) { else { changedCardFee = true; if (oe->getDCI().getInt(fields[k]) == 0) - oe->getDI().setInt(fields[k].c_str(), -1); // Disallow zero card fee. -1 means no fee. + oe->getDI().setInt(fields[k], -1); // Disallow zero card fee. -1 means no fee. } } } if (factor != oe->getDCI().getString("LateEntryFactor")) changedFee = true; - oe->getDI().setInt("UTC", gdi.isChecked("UTC") ? 1 : 0); + if (oe->getDI().setInt("UTC", gdi.isChecked("UTC") ? 1 : 0)) + setEventorUTC(gdi.isChecked("UTC")); + + if (oe->getDI().setInt("CurrencyFactor", gdi.isChecked("UseFraction") ? 100 : 1)) + modified.insert("CurrencyFactor"); + + if (oe->getDI().setInt("CurrencyPreSymbol", gdi.isChecked("PreSymbol") ? 1 : 0)) + modified.insert("CurrencyPreSymbol"); - oe->getDI().setInt("CurrencyFactor", gdi.isChecked("UseFraction") ? 100 : 1); - oe->getDI().setInt("CurrencyPreSymbol", gdi.isChecked("PreSymbol") ? 1 : 0); oe->setCurrency(-1, L"", L"", false); - vector< pair > modes; + wstring pm = oe->getDCI().getString("PayModes"); + vector> modes; oe->getPayModes(modes); for (size_t k = 0; k < modes.size(); k++) { string field = "M"+itos(k); if (gdi.hasWidget(field)) { wstring mode = gdi.getText("M"+itos(k)); - int id = gdi.getBaseInfo(field.c_str()).getExtraInt(); + int id = gdi.getBaseInfo(field).getExtraInt(); oe->setPayMode(id, mode); } } + if (pm != oe->getDCI().getString("PayModes")) + modified.insert("PayModes"); // Read from model if (oe->isChanged()) { - oe->setProperty("Organizer", oe->getDCI().getString("Organizer")); + vector props = { "Organizer" ,"Street" ,"Address" ,"EMail" ,"Homepage" , + "YouthAge","SeniorAge","Account", "LateEntryFactor","CurrencySymbol", + "CurrencyFactor","CurrencyPreSymbol","CurrencySeparator", + "CardFee","EliteFee","EntryFee","YouthFee", "PayModes" }; + + auto dci = oe->getDCI(); + for (const string &p : props) { + if (modified.count(p)) { + if (dci.isString(p)) + oe->setProperty(p.c_str(), dci.getString(p)); + else if (dci.isInt(p)) + oe->setProperty(p.c_str(), dci.getInt(p)); + else + throw std::exception(); + } + } + + //oe->setProperty("PayModes", oe->getDCI().getString("PayModes")); + + /*oe->setProperty("Organizer", oe->getDCI().getString("Organizer")); oe->setProperty("Street", oe->getDCI().getString("Street")); oe->setProperty("Address", oe->getDCI().getString("Address")); oe->setProperty("EMail", oe->getDCI().getString("EMail")); @@ -4166,8 +4291,8 @@ void TabCompetition::saveSettings(gdioutput &gdi) { oe->setProperty("CurrencyFactor", oe->getDCI().getInt("CurrencyFactor")); oe->setProperty("CurrencyPreSymbol", oe->getDCI().getInt("CurrencyPreSymbol")); oe->setProperty("CurrencySeparator", oe->getDCI().getString("CurrencySeparator")); - - oe->setProperty("PayModes", oe->getDCI().getString("PayModes")); + */ + //oe->setProperty("PayModes", oe->getDCI().getString("PayModes")); } oe->synchronize(true); set dummy; @@ -4260,7 +4385,7 @@ void TabCompetition::mergeCompetition(gdioutput &gdi) { if (!thisFile.empty()) { wchar_t newBase[_MAX_PATH]; getUserFile(newBase, thisFile.c_str()); - mergeEvent->save(newBase); + mergeEvent->save(newBase, false); } tc->oe->merge(*mergeEvent, baseEvent.get(), allowRemove, numAdd, numRemove, numUpdate); @@ -4285,7 +4410,7 @@ void TabCompetition::mergeCompetition(gdioutput &gdi) { gdi.refresh(); } else if (bi.id == "Browse") { - wstring fn = gdi.browseForOpen({ make_pair(L"xml", L"*.xml") }, L"xml"); + wstring fn = gdi.browseForOpen({ make_pair(L"MeOS-data", L"*.meosxml;*.xml;*.bu?") }, L"meosxml"); if (fn.empty()) return; @@ -4296,7 +4421,7 @@ void TabCompetition::mergeCompetition(gdioutput &gdi) { else if (bi.id == "Read") { mergeEvent = make_shared(gdi); - if (!mergeEvent->open(tc->mergeFile, true, true)) + if (!mergeEvent->open(tc->mergeFile, true, true, false)) return; gdi.restore("merge", false); @@ -4384,7 +4509,7 @@ void TabCompetition::mergeCompetition(gdioutput &gdi) { baseEvent = make_shared(gdi); bool ok = false; try { - ok = baseEvent->open(base, true, true); + ok = baseEvent->open(base, true, true, false); ok = true; } catch (...) { diff --git a/code/TabCompetition.h b/code/TabCompetition.h index 8d7b3ca..69a89bf 100644 --- a/code/TabCompetition.h +++ b/code/TabCompetition.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -60,6 +60,8 @@ class TabCompetition : void copyrightLine(gdioutput &gdi) const; void loadAboutPage(gdioutput &gdi) const; + void updateWarning(gdioutput &gdi) const; + int organizorId; int lastChangeClassType; @@ -118,6 +120,7 @@ class TabCompetition : void meosFeatures(gdioutput &gdi, bool newGuide); void newCompetitionGuide(gdioutput &gdi, int step); + void createNewCmp(gdioutput &gdi, bool useExisting); void entryForm(gdioutput &gdi, bool isGuide); FlowOperation saveEntries(gdioutput &gdi, bool removeRemoved, bool isGuide); @@ -132,6 +135,8 @@ class TabCompetition : void entryChoice(gdioutput &gdi); void createCompetition(gdioutput &gdi); + void importDefaultHiredCards(gdioutput& gdi); + void listBackups(gdioutput &gdi); shared_ptr mergeHandler; diff --git a/code/TabControl.cpp b/code/TabControl.cpp index c80133b..d2f94d2 100644 --- a/code/TabControl.cpp +++ b/code/TabControl.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -55,35 +55,46 @@ void TabControl::selectControl(gdioutput &gdi, pControl pc) if (pc) { pc->synchronize(); - if (pc->getStatus() == oControl::StatusStart || - pc->getStatus() == oControl::StatusFinish) { + if (oControl::isSpecialControl(pc->getStatus())) { gdi.selectItemByData("Controls", pc->getId()); - gdi.selectItemByData("Status", oControl::StatusOK); + gdi.selectItemByData("Status", int(oControl::ControlStatus::StatusOK)); gdi.setText("ControlID", makeDash(L"-"), true); gdi.setText("Code", L""); gdi.setText("Name", pc->getName()); - gdi.setText("TimeAdjust", L"00:00"); + gdi.setText("TimeAdjust", pc->getTimeAdjustS()); gdi.setText("MinTime", makeDash(L"-")); gdi.setText("Point", L""); + gdi.disableInput("Visitors"); + gdi.disableInput("Courses"); + controlId = pc->getId(); gdi.enableInput("Remove"); gdi.enableInput("Save"); gdi.enableEditControls(false); gdi.enableInput("Name"); + + if (pc->getUnitCode() > 0) { + gdi.setText("Code", itow(pc->getUnitCode())); + gdi.enableInput("TimeAdjust"); + gdi.setText("Info", lang.tl("Du kan justera tiden för en viss enhet"), true); + } + else { + gdi.setText("Info", L"", true); + } } else { gdi.selectItemByData("Controls", pc->getId()); - gdi.selectItemByData("Status", pc->getStatus()); + gdi.selectItemByData("Status", int(pc->getStatus())); const int numVisit = pc->getNumVisitors(true); const int numVisitExp = pc->getNumVisitors(false); wstring info; if (numVisit > 0) { info = L"Antal besökare X, genomsnittlig bomtid Y, största bomtid Z#" + - itow(numVisit) + L" (" + itow(numVisitExp) + L")#" + getTimeMS(pc->getMissedTimeTotal() / numVisit) + - L"#" + getTimeMS(pc->getMissedTimeMax()); + itow(numVisit) + L" (" + itow(numVisitExp) + L")#" + formatTimeMS(pc->getMissedTimeTotal() / numVisit, false, SubSecond::Off) + + L"#" + formatTimeMS(pc->getMissedTimeMax(), false, SubSecond::Off); } else if (numVisitExp > 0) { info = L"Förväntat antal besökare: X#" + itow(numVisitExp); @@ -108,19 +119,20 @@ void TabControl::selectControl(gdioutput &gdi, pControl pc) gdi.enableEditControls(true); oControl::ControlStatus st = pc->getStatus(); - if (st == oControl::StatusRogaining || st == oControl::StatusNoTiming || st == oControl::StatusBadNoTiming) + if (st == oControl::ControlStatus::StatusRogaining || st == oControl::ControlStatus::StatusRogainingRequired || + st == oControl::ControlStatus::StatusNoTiming || st == oControl::ControlStatus::StatusBadNoTiming) gdi.disableInput("MinTime"); - if (st == oControl::StatusNoTiming || st == oControl::StatusBadNoTiming) + if (st == oControl::ControlStatus::StatusNoTiming || st == oControl::ControlStatus::StatusBadNoTiming) gdi.disableInput("TimeAdjust"); - if (gdi.hasWidget("Point") && st != oControl::StatusRogaining) + if (gdi.hasWidget("Point") && st != oControl::ControlStatus::StatusRogaining && st != oControl::ControlStatus::StatusRogainingRequired) gdi.disableInput("Point"); } } else { gdi.selectItemByData("Controls", -1); - gdi.selectItemByData("Status", oControl::StatusOK); + gdi.selectItemByData("Status", int(oControl::ControlStatus::StatusOK)); gdi.setText("Code", L""); gdi.setText("Name", L""); controlId = 0; @@ -145,26 +157,58 @@ int ControlsCB(gdioutput *gdi, int type, void *data) return tc.controlCB(*gdi, type, data); } -void TabControl::save(gdioutput &gdi) -{ +void TabControl::save(gdioutput &gdi) { if (controlId==0) return; - DWORD pcid = controlId; - - pControl pc; - pc = oe->getControl(pcid, false); + pControl pc = oe->getControl(controlId, false, true); if (!pc) throw std::exception("Internal error"); - if (pc->getStatus() != oControl::StatusFinish && pc->getStatus() != oControl::StatusStart) { + + if (!pc->isAddedToEvent()) { + if (gdi.getText("TimeAdjust") == pc->getTimeAdjustS() + && gdi.getText("Name") == pc->getName()) + return; // Virtual control with no change. + + oe->synchronizeList(oListId::oLControlId); + pc = oe->getControl(controlId, false, true); + + if (!pc->isAddedToEvent()) + pc = oe->addControl(*pc); + + if (!pc) + throw std::exception("Internal error"); + } + + bool defaultName = false; + + if (!oControl::isSpecialControl(pc->getStatus())) { + int oldFirst = pc->getFirstNumber(); + if (!pc->setNumbers(gdi.getText("Code"))) gdi.alert("Kodsiffran mÃ¥ste vara ett heltal. Flera kodsiffror mÃ¥ste separeras med komma."); + int newFirst = pc->getFirstNumber(); + if (oldFirst != newFirst && pc->getId() == oldFirst) { + // Update id if possible (make new control and remove old) + if (!oe->isControlUsed(pc->getId()) && oe->getControl(newFirst) == nullptr) { + pc->setName(gdi.getText("Name")); + if (!pc->hasName()) + defaultName = true; + + pc = oe->addControl(newFirst, newFirst, L""); + pc->synchronize(); + controlId = pc->getId(); + pc->setNumbers(gdi.getText("Code")); + oe->removeControl(oldFirst); + } + } + pc->setStatus(oControl::ControlStatus(gdi.getSelectedItem("Status").first)); pc->setTimeAdjust(gdi.getText("TimeAdjust")); - if (pc->getStatus() != oControl::StatusRogaining) { - if (pc->getStatus() != oControl::StatusNoTiming && pc->getStatus() != oControl::StatusBadNoTiming) + if (pc->getStatus() != oControl::ControlStatus::StatusRogaining && pc->getStatus() != oControl::ControlStatus::StatusRogainingRequired) { + if (pc->getStatus() != oControl::ControlStatus::StatusNoTiming && pc->getStatus() != oControl::ControlStatus::StatusBadNoTiming) pc->setMinTime(gdi.getText("MinTime")); pc->setRogainingPoints(0); } @@ -175,12 +219,65 @@ void TabControl::save(gdioutput &gdi) } } } + else if (pc->isUnit()) { + // Ensure cache is up-to-date + auto type = pc->getUnitType(); + int oldAdjust = oe->getUnitAdjustment(type, pc->getUnitCode()); - pc->setName(gdi.getText("Name")); + if (pc->setTimeAdjust(gdi.getText("TimeAdjust"))) { + + // Cache is not updated. No new adjustment applied + assert(oldAdjust == oe->getUnitAdjustment(type, pc->getUnitCode())); + + vector> adjustList; + + if (type == oPunch::SpecialPunch::PunchStart) { + auto pList = oe->getPunchesByType(type, pc->getUnitCode()); + for (auto p : pList) { + pRunner r = oe->getRunnerByCardNo(p->getCardNo(), p->getTimeInt(), oEvent::CardLookupProperty::Any); + if (r && !r->getCard() && r->getStartTime() == p->getTimeInt()) { + // Need not adjust runners with card. + adjustList.emplace_back(r, p); + } + } + } + else if (type == oPunch::SpecialPunch::PunchFinish) { + auto pList = oe->getPunchesByType(type, pc->getUnitCode()); + for (auto p : pList) { + pRunner r = oe->getRunnerByCardNo(p->getCardNo(), p->getTimeInt(), oEvent::CardLookupProperty::Any); + if (r && !r->getCard() && r->getFinishTime() == p->getTimeInt()) { + // Need not adjust runners with card. + adjustList.emplace_back(r, p); + } + } + } + + // Clear cache to make sure adjustment takes effect + pc->clearCache(); + oe->clearUnitAdjustmentCache(); + + // With new adjustment applied + assert(oldAdjust != oe->getUnitAdjustment(type, pc->getUnitCode())); + + for (auto& rp : adjustList) { + if (type == oPunch::SpecialPunch::PunchStart) { + rp.first->setStartTime(rp.second->getTimeInt(), true, oBase::ChangeType::Update, true); + } + else if (type == oPunch::SpecialPunch::PunchFinish) { + rp.first->setFinishTime(rp.second->getTimeInt()); + } + rp.first->synchronize(true); + } + } + } + + if (!defaultName) + pc->setName(gdi.getText("Name")); pc->synchronize(); - vector< pair > d; - oe->fillControls(d, oEvent::CTAll); + + vector> d; + oe->fillControls(d, oEvent::ControlType::All); gdi.addItem("Controls", d); oe->reEvaluateAll(set(), true); @@ -239,8 +336,8 @@ void TabControl::courseTable(Table &table) const { void TabControl::visitorTable(Table &table) const { vector c; - oe->getCards(c); - pControl pc = oe->getControl(controlId, false); + oe->getCards(c, true, false); + pControl pc = oe->getControl(controlId, false, true); if (!pc) return; @@ -297,7 +394,7 @@ void TabControl::visitorTable(Table &table) const { lang.tl("Ja") : lang.tl("Nej"), false); table.set(row++, it, TID_CARD, it.getCardNoString(), false); - table.set(row++, it, TID_STATUS, punch->getTime(), false); + table.set(row++, it, TID_STATUS, punch->getTime(false, SubSecond::Auto), false); table.set(row++, it, TID_CONTROL, punch->getType(), false); table.set(row++, it, TID_CODES, j>0 ? p[j-1]->getType() : L"-", true); } @@ -316,15 +413,24 @@ int TabControl::controlCB(gdioutput &gdi, int type, void *data) bool rogaining = false; if (controlId>0) { save(gdi); - pControl pc = oe->getControl(controlId, false); - rogaining = pc && pc->getStatus() == oControl::StatusRogaining; + pControl pc = oe->getControl(controlId, false, true); + rogaining = pc && (pc->getStatus() == oControl::ControlStatus::StatusRogaining || pc->getStatus() == oControl::ControlStatus::StatusRogainingRequired); } - pControl pc = oe->addControl(0,oe->getNextControlNumber(), L""); + int nextCtrl = oe->getNextControlNumber(); + int nextId = nextCtrl; + if (oe->getControl(nextId)) { + nextId += 1000; + if (oe->getControl(nextId)) + nextId = 0; // Use default + } + pControl pc = oe->addControl(nextId, nextCtrl, L""); if (rogaining) - pc->setStatus(oControl::StatusRogaining); + pc->setStatus(oControl::ControlStatus::StatusRogaining); + + pc->synchronize(); vector< pair > d; - oe->fillControls(d, oEvent::CTAll); + oe->fillControls(d, oEvent::ControlType::All); gdi.addItem("Controls", d); selectControl(gdi, pc); } @@ -342,7 +448,7 @@ int TabControl::controlCB(gdioutput &gdi, int type, void *data) oe->removeControl(cid); vector< pair > d; - oe->fillControls(d, oEvent::CTAll); + oe->fillControls(d, oEvent::ControlType::All); gdi.addItem("Controls", d); selectControl(gdi, 0); } @@ -409,16 +515,17 @@ int TabControl::controlCB(gdioutput &gdi, int type, void *data) if (gdi.isInputChanged("")) save(gdi); - pControl pc=oe->getControl(bi.data, false); + pControl pc=oe->getControl(bi.data, false, true); if (!pc) throw std::exception("Internal error"); selectControl(gdi, pc); } else if (bi.id == "Status" ) { - gdi.setInputStatus("MinTime", bi.data != oControl::StatusRogaining && bi.data != oControl::StatusNoTiming && bi.data != oControl::StatusBadNoTiming, true); - gdi.setInputStatus("Point", bi.data == oControl::StatusRogaining, true); - gdi.setInputStatus("TimeAdjust", bi.data != oControl::StatusNoTiming && bi.data != oControl::StatusBadNoTiming, true); + oControl::ControlStatus st = (oControl::ControlStatus)bi.data; + gdi.setInputStatus("MinTime", st != oControl::ControlStatus::StatusRogaining && st != oControl::ControlStatus::StatusRogainingRequired && st != oControl::ControlStatus::StatusNoTiming && st != oControl::ControlStatus::StatusBadNoTiming, true); + gdi.setInputStatus("Point", st == oControl::ControlStatus::StatusRogaining || st != oControl::ControlStatus::StatusRogainingRequired, true); + gdi.setInputStatus("TimeAdjust", st != oControl::ControlStatus::StatusNoTiming && st != oControl::ControlStatus::StatusBadNoTiming, true); } } else if (type==GUI_CLEAR) { @@ -454,10 +561,10 @@ bool TabControl::loadPage(gdioutput &gdi) gdi.pushY(); gdi.addListBox("Controls", 250, 530, ControlsCB).isEdit(false).ignore(true); - gdi.setTabStops("Controls", 40, 160); + gdi.setTabStops("Controls", 44, 160); vector< pair > d; - oe->fillControls(d, oEvent::CTAll); + oe->fillControls(d, oEvent::ControlType::All); gdi.addItem("Controls", d); gdi.newColumn(); @@ -515,7 +622,7 @@ bool TabControl::loadPage(gdioutput &gdi) gdi.dropLine(1.5); gdi.addString("", 10, "help:89064"); - selectControl(gdi, oe->getControl(controlId, false)); + selectControl(gdi, oe->getControl(controlId, false, true)); gdi.setOnClearCb(ControlsCB); diff --git a/code/TabControl.h b/code/TabControl.h index f8153f7..bad7b07 100644 --- a/code/TabControl.h +++ b/code/TabControl.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabCourse.cpp b/code/TabCourse.cpp index 4210314..d468ebd 100644 --- a/code/TabCourse.cpp +++ b/code/TabCourse.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -459,7 +459,7 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) ext.push_back(make_pair(L"IOF CourseData, version 3.0 (xml)", L"*.xml")); wstring save = gdi.browseForSave(ext, L"xml", FilterIndex); if (save.length()>0) { - IOF30Interface iof30(oe, false); + IOF30Interface iof30(oe, false, false); xmlparser xml; xml.openOutput(save.c_str(), false); iof30.writeCourses(xml); @@ -477,15 +477,19 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) gdi.disableInput("Cancel"); gdi.disableInput("BrowseCourse"); gdi.disableInput("AddClasses"); + gdi.disableInput("CreateClasses"); try { - TabCourse::runCourseImport(gdi, filename, oe, gdi.isChecked("AddClasses")); + TabCourse::runCourseImport(gdi, filename, oe, + gdi.isChecked("AddClasses"), + gdi.isChecked("CreateClasses")); } - catch (std::exception &) { + catch (const std::exception &) { gdi.enableInput("DoImportCourse"); gdi.enableInput("Cancel"); gdi.enableInput("BrowseCourse"); gdi.enableInput("AddClasses"); + gdi.enableInput("CreateClasses"); throw; } gdi.addButton("Cancel", "OK", CourseCB); @@ -525,8 +529,8 @@ int TabCourse::courseCB(gdioutput &gdi, int type, void *data) gdi.pushX(); gdi.fillRight(); - int firstStart = 3600; - int interval = 2*60; + int firstStart = timeConstHour; + int interval = 2*timeConstMinute; int vac = 1; gdi.addInput("FirstStart", oe->getAbsTime(firstStart), 10, 0, L"Första start:"); gdi.addInput("Interval", formatTime(interval), 10, 0, L"Startintervall (min):"); @@ -891,14 +895,14 @@ bool TabCourse::loadPage(gdioutput &gdi) { } void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename, - oEvent *oe, bool addClasses) { + oEvent *oe, bool addToClasses, bool createClasses) { if (csvparser::iscsv(filename) != csvparser::CSV::NoCSV) { gdi.fillRight(); gdi.pushX(); gdi.addString("", 0, "Importerar OCAD csv-fil..."); gdi.refreshFast(); csvparser csv; - if (csv.importOCAD_CSV(*oe, filename, addClasses)) { + if (csv.importOCAD_CSV(*oe, filename, addToClasses)) { gdi.addString("", 1, "Klart.").setColor(colorGreen); } else gdi.addString("", 0, "Operationen misslyckades.").setColor(colorRed); @@ -906,12 +910,64 @@ void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename, gdi.dropLine(2.5); gdi.fillDown(); } + else if (filename.find(L".txt") != wstring::npos || filename.find(L".TXT") != wstring::npos) { + ifstream fin(filename); + + if (!fin.good()) + throw meosException(L"Cannot read " + filename); + + char bf[2048]; + vector sw; + int importedC = oe->getNumCourses() + 1; + int line = 0; + while (fin.good()) { + fin.getline(bf, 2048); + if (strlen(bf) < 2) + continue; + + if (0 == line && uint8_t(bf[0]) == 0xEF && uint8_t(bf[1]) == 0xBB && uint8_t(bf[2]) == 0xBF) + split(bf+3, " ;,", sw); + else + split(bf, " ;,", sw); + line++; + + if (sw.size() <= 1) + continue; + + wstring name; + int first = 0; + if (atoi(sw[0].c_str()) < 30 && trim(sw[0]).length() > 2) { + name = gdioutput::fromUTF8(trim(sw[0])); + first = 1; + } + if (name.empty()) + name = lang.tl("Bana X#" + itos(importedC++)); + + string cs; + for (int i = first; i < sw.size(); i++) { + if (trim(sw[i]).empty()) + continue; + int c = atoi(sw[i].c_str()); + if (c >= 30 && c < 1000) + cs += itos(c) + " "; + else { + throw meosException("Kan inte tolka 'X' som en bana#" + string(bf)); + } + } + + pCourse pc = oe->addCourse(name); + pc->importControls(cs, true, false); + pc->synchronize(); + } + + fin.close(); + } else { set noFilter; string noType; - oe->importXML_EntryData(gdi, filename.c_str(), addClasses, false, noFilter, noType); + oe->importXML_EntryData(gdi, filename.c_str(), addToClasses, false, noFilter, noType); } - if (addClasses) { + if (addToClasses) { // There is specific course-class matching inside the import of each format, // that uses additional information. Here we try to match based on a generic approach. vector cls; @@ -1012,6 +1068,34 @@ void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename, gdi.dropLine(); } + if (createClasses) { + vector cls; + vector crs; + oe->getClasses(cls, false); + oe->getCourses(crs); + unordered_set usedCourseId; + vector usedCrs; + for (size_t k = 0; k < cls.size(); k++) { + cls[k]->getCourses(-1, usedCrs); + for (pCourse pc : usedCrs) + usedCourseId.insert(pc->getId()); + } + + set usedNames; + for (pCourse pc : crs) { + if (usedCourseId.count(pc->getId())) + continue; + + pClass matchCls = oe->getClassCreate(-1, pc->getName(), usedNames); + if (!matchCls || matchCls->getCourse(false)) { + oe->addClass(pc->getName() + lang.tl(" Bana"), pc->getId()); + } + else { + matchCls->setCourse(pc); + } + } + } + gdi.addButton(gdi.getWidth()+20, 45, gdi.scaleLength(baseButtonWidth), "Print", "Skriv ut...", CourseCB, "Skriv ut listan.", true, false); @@ -1043,6 +1127,7 @@ void TabCourse::setupCourseImport(gdioutput& gdi, GUICALLBACK cb) { gdi.fillDown(); gdi.addCheckbox("AddClasses", "Lägg till klasser", 0, true); + gdi.addCheckbox("CreateClasses", "Skapa en klass för varje bana", 0, false); gdi.dropLine(); gdi.fillRight(); @@ -1060,9 +1145,9 @@ void TabCourse::fillCourseControls(gdioutput &gdi, const wstring &ctrl) { vector< pair > item; map used; for (size_t k = 0; k < nr.size(); k++) { - pControl pc = oe->getControl(nr[k], false); + pControl pc = oe->getControl(nr[k], false, false); if (pc) { - if (pc->getStatus() == oControl::StatusOK) + if (pc->getStatus() == oControl::ControlStatus::StatusOK) ++used[pc->getFirstNumber()]; } else @@ -1085,7 +1170,7 @@ void TabCourse::fillCourseControls(gdioutput &gdi, const wstring &ctrl) { void TabCourse::fillOtherCourses(gdioutput &gdi, oCourse &crs, bool withLoops) { vector< pair > ac; - oe->fillCourses(ac, true); + oe->getCourses(ac, L"", true); set skipped; skipped.insert(crs.getId()); pCourse longer = crs.getLongerVersion(); @@ -1223,7 +1308,7 @@ wstring TabCourse::encodeCourse(const wstring &in, bool rogaining, bool firstSta } const wstring &TabCourse::formatControl(int id, wstring &bf) const { - pControl ctrl = oe->getControl(id, false); + pControl ctrl = oe->getControl(id, false, true); if (ctrl) { bf = ctrl->getString(); return bf; @@ -1231,3 +1316,4 @@ const wstring &TabCourse::formatControl(int id, wstring &bf) const { else return itow(id); } + diff --git a/code/TabCourse.h b/code/TabCourse.h index 26355aa..64e2fb9 100644 --- a/code/TabCourse.h +++ b/code/TabCourse.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -70,7 +70,7 @@ public: ~TabCourse(void); static void runCourseImport(gdioutput& gdi, const wstring &filename, - oEvent *oe, bool addClasses); + oEvent *oe, bool addToClasses, bool createClasses); static void setupCourseImport(gdioutput& gdi, GUICALLBACK cb); diff --git a/code/TabList.cpp b/code/TabList.cpp index 2e1ef1c..a547c8a 100644 --- a/code/TabList.cpp +++ b/code/TabList.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -341,7 +341,7 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) } else if (bi.id=="Copy") { ostringstream fout; - HTMLWriter::writeTableHTML(gdi, fout, L"MeOS", true, 0, 1.0); + HTMLWriter::writeTableHTML(gdi, fout, L"MeOS", false, L"", true, 0, 1.0); string res = fout.str(); gdi.copyToClipboard(res, L""); } @@ -570,32 +570,44 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) else if (bi.id=="CancelPS") { gdi.getTabs().get(TabType(bi.getExtraInt()))->loadPage(gdi); } - else if (bi.id=="SavePS") { + else if (bi.id == "SavePS") { string ctype; gdi.getData("Type", ctype); saveExtraLines(*oe, ctype.c_str(), gdi); if (gdi.hasWidget("SplitAnalysis")) { int aflag = (gdi.isChecked("SplitAnalysis") ? 0 : 1) + (gdi.isChecked("Speed") ? 0 : 2) - + (gdi.isChecked("Results") ? 0 : 4); + + (gdi.isChecked("Results") ? 0 : 4); oe->getDI().setInt("Analysis", aflag); } - - if (gdi.hasWidget("WideFormat")) { - bool wide = gdi.isChecked("WideFormat"); - oe->setProperty("WideSplitFormat", wide); - - if (wide && gdi.hasWidget("NumPerPage")) { - pair res = gdi.getSelectedItem("NumPerPage"); - if (res.second) - oe->setProperty("NumSplitsOnePage", res.first); + if (gdi.hasWidget("SplitPrintList")) { + auto res = gdi.getSelectedItem("SplitPrintList"); + if (res.second) { + if (res.first == -10) + oe->getDI().setString("SplitPrint", L""); + else { + EStdListType type = oe->getListContainer().getType(res.first); + string id = oe->getListContainer().getUniqueId(type); + oe->getDI().setString("SplitPrint", gdioutput::widen(id)); + } + } + } + + if (gdi.hasWidget("WideFormat")) { + bool wide = gdi.isChecked("WideFormat"); + oe->setProperty("WideSplitFormat", wide); - int no = gdi.getTextNo("MaxWaitTime"); - if (no >= 0) - oe->setProperty("SplitPrintMaxWait", no); - } - } + if (wide && gdi.hasWidget("NumPerPage")) { + pair res = gdi.getSelectedItem("NumPerPage"); + if (res.second) + oe->setProperty("NumSplitsOnePage", res.first); + + int no = gdi.getTextNo("MaxWaitTime"); + if (no >= 0) + oe->setProperty("SplitPrintMaxWait", no); + } + } gdi.getTabs().get(TabType(bi.getExtraInt()))->loadPage(gdi); } else if (bi.id == "PrinterSetup") { @@ -1071,11 +1083,21 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) return listCB(gdi, GUI_BUTTON, &bi); } - gdi.clearPage(false); gdi.addString("", boldLarge, "Tillgängliga listor"); - int xx = gdi.getCX() + gdi.scaleLength(360); int bx = gdi.getCX(); + int xx = bx + gdi.scaleLength(360); + TextInfo ti; + for (size_t k = 0; k < installedLists.size(); k++) { + ti.text = installedLists[k].first; + gdi.calcStringSize(ti); + xx = max(xx, bx + ti.realWidth + 10); + } + for (size_t k = 0; k < lists.size(); k++) { + ti.text = lists[k].first; + gdi.calcStringSize(ti); + xx = max(xx, bx + ti.realWidth + 10); + } if (!installedLists.empty()) { gdi.dropLine(); gdi.addString("", 1, "Listor i tävlingen"); @@ -1141,8 +1163,13 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) xmlobject xlist = xml.getObject(0); oe->synchronize(); oe->getListContainer().load(MetaListContainer::ExternalList, xlist, false); - oe->synchronize(true); + + set imgUsed; + oe->getListContainer().getUsedImages(imgUsed); + for (uint64_t id : imgUsed) + oe->saveImage(id); + oe->synchronize(true); loadPage(gdi); } } @@ -1218,6 +1245,12 @@ int TabList::listCB(gdioutput &gdi, int type, void *data) oe->synchronize(false); oe->getListContainer().load(MetaListContainer::ExternalList, xlist, false); + + set imgUsed; + oe->getListContainer().getUsedImages(imgUsed); + for (uint64_t id : imgUsed) + oe->saveImage(id); + oe->synchronize(true); oe->loadGeneralResults(true, false); } @@ -1286,7 +1319,7 @@ pair TabList::makeOwnWindow(gdioutput &gdi) { void TabList::enableFromTo(oEvent &oe, gdioutput &gdi, bool from, bool to) { vector< pair > d; - oe.fillControls(d, oEvent::CTCourseControl); + oe.fillControls(d, oEvent::ControlType::CourseControl); if (from) { gdi.enableInput("ResultSpecialFrom"); @@ -2216,7 +2249,7 @@ void TabList::settingsResultList(gdioutput &gdi) vector< pair > lists; vector< pair > dlists; const MetaListContainer &lc = oe->getListContainer(); - lc.getLists(dlists, false, true, !oe->hasTeam()); + lc.getLists(dlists, false, true, !oe->hasTeam(), false); set usedListIx; map tag2ListIx; for (size_t k = 0; k < dlists.size(); k++) { @@ -2725,31 +2758,72 @@ void TabList::splitPrintSettings(oEvent &oe, gdioutput &gdi, bool setupPrinter, gdi.addString("", boldLarge, "Inställningar startbevis"); gdi.dropLine(); - - gdi.fillRight(); gdi.pushX(); - if (setupPrinter) { - gdi.addButton("PrinterSetup", "Skrivare...", ListsCB, "Skrivarinställningar"); - gdi.dropLine(0.3); - } - - + if (!oe.empty() && type == Splits) { - bool withSplitAnalysis = (oe.getDCI().getInt("Analysis") & 1) == 0; + gdi.fillRight(); + gdi.addSelection("SplitPrintList", 200, 200, nullptr, L"Sträcktidslista:"); + if (setupPrinter) { + gdi.dropLine(0.9); + gdi.addButton("PrinterSetup", "Skrivare...", ListsCB, "Skrivarinställningar"); + gdi.dropLine(2.8); + } + else { + gdi.dropLine(3); + } + + gdi.fillDown(); + gdi.popX(); + gdi.addString("", 10, "info:customsplitprint"); + gdi.dropLine(); + vector> lists; + oe.getListContainer().getLists(lists, false, false, false, true); + lists.insert(lists.begin(), make_pair(lang.tl("Standard"), -10)); + gdi.addItem("SplitPrintList", lists); + wstring listId = oe.getDCI().getString("SplitPrint"); + EStdListType type = oe.getListContainer().getCodeFromUnqiueId(gdioutput::narrow(listId)); + if (type == EStdListType::EStdNone) + gdi.selectFirstItem("SplitPrintList"); + else { + for (auto& t : lists) { + if (type == oe.getListContainer().getType(t.second)) + gdi.selectItemByData("SplitPrintList", t.second); + } + } + //if () + /* bool withSplitAnalysis = (oe.getDCI().getInt("Analysis") & 1) == 0; bool withSpeed = (oe.getDCI().getInt("Analysis") & 2) == 0; bool withResult = (oe.getDCI().getInt("Analysis") & 4) == 0; gdi.addCheckbox("SplitAnalysis", "Med sträcktidsanalys", 0, withSplitAnalysis); gdi.addCheckbox("Speed", "Med km-tid", 0, withSpeed); - gdi.addCheckbox("Results", "Med resultat", 0, withResult); - + gdi.addCheckbox("Results", "Med resultat", 0, withResult);*/ } + else if (setupPrinter) { + gdi.dropLine(0.2); + gdi.addButton("PrinterSetup", "Skrivare...", ListsCB, "Skrivarinställningar"); + } + gdi.popX(); - gdi.fillDown(); + + RECT rc; + rc.top = gdi.getCY(); + rc.left = gdi.getCX(); + gdi.setCX(gdi.getCX() + gdi.scaleLength(8)); + gdi.dropLine(); + const char *ctype = type == Splits ? "SPExtra" : "EntryExtra"; - customTextLines(oe, ctype, gdi); + customTextLines(oe, ctype, !oe.empty(), gdi); + + gdi.dropLine(); + + rc.right = gdi.getWidth(); + rc.bottom = gdi.getCY(); + + gdi.addRectangle(rc, colorLightCyan); if (type == Splits) { + gdi.dropLine(1.5); const bool wideFormat = oe.getPropertyInt("WideSplitFormat", 0) == 1; gdi.addCheckbox("WideFormat", "Sträcktider i kolumner (för standardpapper)", ListsCB, wideFormat); @@ -2769,7 +2843,7 @@ void TabList::splitPrintSettings(oEvent &oe, gdioutput &gdi, bool setupPrinter, } } - + gdi.dropLine(); gdi.fillRight(); gdi.setData("Type", ctype); gdi.addButton("SavePS", "OK", ListsCB).setDefault().setExtra(returnMode); @@ -2791,11 +2865,12 @@ void TabList::saveExtraLines(oEvent &oe, const char *dataField, gdioutput &gdi) oe.setExtraLines(dataField, lines); } -void TabList::customTextLines(oEvent &oe, const char *dataField, gdioutput &gdi) { - gdi.dropLine(2.5); +void TabList::customTextLines(oEvent &oe, const char *dataField, bool withSymbols, gdioutput &gdi) { gdi.addString("", fontMediumPlus, "Egna textrader"); - gdi.dropLine(0.3); - gdi.addString("", 10, "help:custom_text_lines"); + if (withSymbols) { + gdi.dropLine(0.3); + gdi.addString("", 10, "help:custom_text_lines"); + } gdi.dropLine(0.8); int yp = gdi.getCY(); @@ -2825,18 +2900,20 @@ void TabList::customTextLines(oEvent &oe, const char *dataField, gdioutput &gdi) gdi.fillDown(); gdi.dropLine(2); } - gdi.pushX(); - gdi.pushY(); + if (withSymbols) { + gdi.pushX(); + gdi.pushY(); - gdi.setCX(xp); - gdi.setCY(yp); - gdi.addListBox("Symbols", 500, 160); - gdi.setTabStops("Symbols", 300); - vector < pair> symb; - MetaList::fillSymbols(symb); - gdi.addItem("Symbols", symb); - gdi.popX(); - gdi.popY(); + gdi.setCX(xp); + gdi.setCY(yp); + gdi.addListBox("Symbols", 500, 160); + gdi.setTabStops("Symbols", 300); + vector < pair> symb; + MetaList::fillSymbols(symb); + gdi.addItem("Symbols", symb); + gdi.popX(); + gdi.popY(); + } } void TabList::liveResult(gdioutput &gdi, oListInfo &li) { diff --git a/code/TabList.h b/code/TabList.h index 6d36f0c..425653a 100644 --- a/code/TabList.h +++ b/code/TabList.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -147,7 +147,7 @@ public: }; static void splitPrintSettings(oEvent &oe, gdioutput &gdi, bool setupPrinter, TabType returnMode, PrintSettingsSelection type); - static void customTextLines(oEvent &oe, const char *dataField, gdioutput &gdi); + static void customTextLines(oEvent &oe, const char *dataField, bool withSymbols, gdioutput &gdi); static void saveExtraLines(oEvent &oe, const char *dataField, gdioutput &gdi); static void enableWideFormat(gdioutput &gdi, bool wide); diff --git a/code/TabMulti.cpp b/code/TabMulti.cpp index 5b36c08..94b7280 100644 --- a/code/TabMulti.cpp +++ b/code/TabMulti.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabMulti.h b/code/TabMulti.h index 689e778..8e631bd 100644 --- a/code/TabMulti.h +++ b/code/TabMulti.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabRunner.cpp b/code/TabRunner.cpp index 48fee5c..3811781 100644 --- a/code/TabRunner.cpp +++ b/code/TabRunner.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -28,6 +28,9 @@ #include #include "oEvent.h" +#include "metalist.h" +#include "generalresult.h" + #include "xmlparser.h" #include "gdioutput.h" #include "gdiconstants.h" @@ -197,7 +200,7 @@ void TabRunner::selectRunner(gdioutput &gdi, pRunner r) { gdi.setText("Team", L""); } - gdi.setText("TimeAdjust", getTimeMS(r->getTimeAdjustment())); + gdi.setText("TimeAdjust", formatTimeMS(r->getTimeAdjustment(false), false)); gdi.setText("PointAdjust", -r->getPointAdjustment()); #ifdef _DEBUG @@ -221,13 +224,13 @@ void TabRunner::selectRunner(gdioutput &gdi, pRunner r) { out += L" (" + itow(placeAcc[k]) + L")"; if (after[k] > 0) - out += L" +" + getTimeMS(after[k]); + out += L" +" + formatTimeMS(after[k], false); if (k < afterAcc.size() && afterAcc[k]>0) - out += L" (+" + getTimeMS(afterAcc[k]) + L")"; + out += L" (+" + formatTimeMS(afterAcc[k], false) + L")"; if (delta[k] > 0) - out += L" B: " + getTimeMS(delta[k]); + out += L" B: " + formatTimeMS(delta[k], false); out += L" | "; @@ -528,7 +531,7 @@ pRunner TabRunner::save(gdioutput &gdi, int runnerId, bool willExit) { else classId = lbi.data; - int year = 0; + const wstring year; pRunner r; bool cardNoChanged = false; if (runnerId==0) { @@ -926,11 +929,12 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) if (!r) return 0; gdioutput gdiprint(2.0, gdi.getHWNDTarget(), splitPrinter); - if (bi.getExtraInt() == 0) + if (bi.getExtraInt() == 0) r->printSplits(gdiprint); else r->printStartInfo(gdiprint); - gdiprint.print(oe, 0, false, true); + + gdiprint.print(oe, nullptr, false, true); gdiprint.fetchPrinterSettings(splitPrinter); } else if (bi.id == "PrintSettings") { @@ -961,7 +965,7 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) pRunner r = oe->getRunner(runnerId, 0); if (!name.empty() && r && r->getName() != name && r->getNameRaw() != name) { if (gdi.ask(L"Vill du lägga till deltagaren 'X'?#" + name)) { - r = oe->addRunner(name, 0, 0, 0,0, false); + r = oe->addRunner(name, 0, 0, 0, L"", false); runnerId = r->getId(); } save(gdi, runnerId, false); @@ -973,7 +977,7 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) ListBoxInfo lbi; gdi.getSelectedItem("RClass", lbi); - pRunner r = oe->addRunner(oe->getAutoRunnerName(), 0,0,0,0, false); + pRunner r = oe->addRunner(oe->getAutoRunnerName(), 0, 0, 0, L"", false); int clsId = lbi.data; if (clsId > 0) { pClass tCls = oe->getClass(clsId); @@ -1095,7 +1099,7 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) r->synchronize(); card->fillPunches(gdi, "Punches", r->getCourse(true)); - gdi.setText("Time", r->getRunningTimeS(true)); + gdi.setText("Time", r->getRunningTimeS(true, SubSecond::Auto)); gdi.selectItemByData("Status", r->getStatus()); } else if (bi.id=="Check") { @@ -1123,8 +1127,46 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) gdi.clearAutoComplete(ii.id); } } + else if (ii.id == "CardNo") { + bool show = false; + if (ii.text.length() > 0) { + vector records; + getAutoCompleteUnpairedCards(gdi, ii.text, records); + if (!records.empty()) { + auto& ac = gdi.addAutoComplete(ii.id); + ac.setAutoCompleteHandler(this); + ac.setData(records); + ac.show(); + show = true; + } + } + if (!show) { + gdi.clearAutoComplete(ii.id); + } + } } - else if (type==GUI_INPUT) { + else if (type == GUI_FOCUS) { + InputInfo ii = *(InputInfo*)data; + if (ii.id == "CardNo") { + pRunner r = runnerId > 0 ? oe->getRunner(runnerId, 0) : nullptr; + if (r && r->getCard() == nullptr) { + bool show = false; + vector records; + getAutoCompleteUnpairedCards(gdi, L"", records); + if (!records.empty()) { + auto& ac = gdi.addAutoComplete(ii.id); + ac.setAutoCompleteHandler(this); + ac.setData(records); + ac.show(); + show = true; + } + if (!show) { + gdi.clearAutoComplete(ii.id); + } + } + } + } + else if (type == GUI_INPUT) { InputInfo ii=*(InputInfo *)data; if (ii.id=="CardNo") { @@ -1311,7 +1353,7 @@ int TabRunner::runnerCB(gdioutput &gdi, int type, void *data) ac.setAutoCompleteHandler(this); vector items; for (auto club : clubs) - items.emplace_back(club->getDisplayName(), club->getName(), club->getId()); + items.emplace_back(club->getDisplayName(), -int(items.size()), club->getName(), club->getId()); ac.setData(items); ac.show(); @@ -1650,12 +1692,12 @@ void TabRunner::showRunnerReport(gdioutput &gdi) { } wstring tInfo = t->getName(); - if (t->statusOK(true)) { - tInfo += L", " + t->getRunningTimeS(true) + lang.tl(", Placering: ") + t->getPlaceS(); - if (t->getTimeAfter(-1) > 0) - tInfo += L", +" + formatTime(t->getTimeAfter(-1)); + if (t->statusOK(true, true)) { + tInfo += L", " + t->getRunningTimeS(true, SubSecond::Auto) + lang.tl(", Placering: ") + t->getPlaceS(); + if (t->getTimeAfter(-1, true) > 0) + tInfo += L", +" + formatTime(t->getTimeAfter(-1, true)); } - else if (t->getStatusComputed() != StatusUnknown) { + else if (t->getStatusComputed(true) != StatusUnknown) { tInfo += L" " + t->getStatusS(true, true); } @@ -1719,7 +1761,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { gdi.dropLine(0.3); - if (r->statusOK(true)) { + if (r->statusOK(true, true)) { int total, finished, dns; r->getClassRef(true)->getNumResults(r->getLegNumber(), total, finished, dns); @@ -1736,7 +1778,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { } } } - else if (r->getStatusComputed() != StatusUnknown) { + else if (r->getStatusComputed(true) != StatusUnknown) { gdi.addStringUT(fontMediumPlus, str).setColor(colorRed); } @@ -1747,7 +1789,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { gdi.addString("", fontMedium, L"Starttid: X #" + r->getStartTimeCompact()); if (r->getFinishTime() > 0) - gdi.addString("", fontMedium, L"MÃ¥ltid: X #" + r->getFinishTimeS()); + gdi.addString("", fontMedium, L"MÃ¥ltid: X #" + r->getFinishTimeS(false, SubSecond::Auto)); const wstring &after = oe.formatListString(lRunnerTimeAfter, r); if (!after.empty()) { @@ -1811,7 +1853,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { GDICOLOR color = colorDefault; if (k < int(after.size()) ) { if (after[k] > 0) - split += L" (" + itow(place[k]) + L", +" + getTimeMS(after[k]) + L")"; + split += L" (" + itow(place[k]) + L", +" + formatTimeMS(after[k], false) + L")"; else if (place[k] == 1) split += lang.tl(" (sträckseger)"); else if (place[k] > 0) @@ -1823,11 +1865,11 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { gdi.addStringUT(yp + lh, cx, fontMedium, split, limit); if (k>0 && k < int(placeAcc.size())) { - split = r->getPunchTimeS(k, false); + split = r->getPunchTimeS(k, false, false, SubSecond::Auto); wstring pl = placeAcc[k] > 0 ? itow(placeAcc[k]) : L"-"; if (k < int(afterAcc.size()) ) { if (afterAcc[k] > 0) - split += L" (" + pl + L", +" + getTimeMS(afterAcc[k]) + L")"; + split += L" (" + pl + L", +" + formatTimeMS(afterAcc[k], false) + L")"; else if (placeAcc[k] == 1) split += lang.tl(" (ledare)"); else if (placeAcc[k] > 0) @@ -1837,7 +1879,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { } if (k < int(delta.size()) && delta[k] > 0 ) { - gdi.addString("", yp + 3*lh, cx, fontMedium, L"Bomtid: X#" + getTimeMS(delta[k])); + gdi.addString("", yp + 3*lh, cx, fontMedium, L"Bomtid: X#" + formatTimeMS(delta[k], false, SubSecond::Off)); color = (delta[k] > bestTime * 0.5 && delta[k]>60 ) ? colorMediumDarkRed : colorMediumRed; @@ -1897,7 +1939,7 @@ void TabRunner::runnerReport(oEvent &oe, gdioutput &gdi, int id, bool compact) { if (st > 0 && t > st) { wstring split = formatTimeHMS(t-st); if (lastT>0 && st != lastT && lastT < t) - split += L" (" + getTimeMS(t-lastT) + L")"; + split += L" (" + formatTimeMS(t-lastT, false) + L")"; gdi.addStringUT(yp + 2*lh, cx, normalText, split, limit); } } @@ -2267,8 +2309,8 @@ void TabRunner::updateStatus(gdioutput &gdi, pRunner r) { if (!r) return; gdi.setText("Start", r->getStartTimeS()); - gdi.setText("Finish", r->getFinishTimeS()); - gdi.setText("Time", r->getRunningTimeS(false)); + gdi.setText("Finish", r->getFinishTimeS(false, SubSecond::Auto)); + gdi.setText("Time", r->getRunningTimeS(false, SubSecond::Auto)); gdi.setText("Points", itow(r->getRogainingPoints(false, false))); gdi.selectItemByData("Status", r->getStatus()); @@ -2321,7 +2363,7 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) { wstring ptime; if (punch->getTimeInt() > 0) - ptime = punch->getTime(); + ptime = punch->getTime(false, SubSecond::Auto); gdi.setText("PTime", ptime); @@ -2386,7 +2428,7 @@ int TabRunner::punchesCB(gdioutput &gdi, int type, void *data) { if (!oc) return 0; vector nmp; - if (oc->getStatus() == oControl::StatusRogaining) { + if (oc->getStatus() == oControl::ControlStatus::StatusRogaining || oc->getStatus() == oControl::ControlStatus::StatusRogainingRequired) { r->evaluateCard(true, nmp, oc->getFirstNumber(), oBase::ChangeType::Update); //Add this punch } else { @@ -2524,6 +2566,9 @@ bool TabRunner::loadPage(gdioutput &gdi) gdi.pushX(); gdi.dropLine(0.5); + + const int hy = gdi.getCY(); + gdi.addString("", boldLarge, "Deltagare"); gdi.fillRight(); gdi.registerEvent("SearchRunner", runnerSearchCB).setKeyCommand(KC_FIND); @@ -2552,7 +2597,7 @@ bool TabRunner::loadPage(gdioutput &gdi) gdi.newColumn(); gdi.fillDown(); - gdi.dropLine(1); + gdi.setCY(hy); gdi.fillRight(); gdi.pushX(); gdi.addInput("Name", L"", 16, RunnerCB, L"Namn:"); @@ -2736,11 +2781,11 @@ bool TabRunner::loadPage(gdioutput &gdi) gdi.fillDown(); gdi.newColumn(); + gdi.setCY(hy); int hx = gdi.getCX(); - int hy = gdi.getCY(); gdi.setCX(hx + gdi.scaleLength(5)); - gdi.dropLine(2.5); + gdi.dropLine(2.0); gdi.addListBox("Punches", 150, 300, PunchesCB, L"Stämplingar:").ignore(true); gdi.addButton("RemoveC", "Ta bort stämpling >>", RunnerCB); @@ -2755,7 +2800,8 @@ bool TabRunner::loadPage(gdioutput &gdi) int contY = gdi.getCY(); gdi.newColumn(); - gdi.dropLine(2.5); + gdi.setCY(hy); + gdi.dropLine(2.0); gdi.fillDown(); gdi.addListBox("Course", 140, 300, PunchesCB, L"Banmall:").ignore(true); gdi.addButton("AddC", "<< Lägg till stämpling", PunchesCB); @@ -3136,7 +3182,7 @@ void TabRunner::loadEconomy(gdioutput &gdi, oRunner &r) { void TabRunner::handleAutoComplete(gdioutput &gdi, AutoCompleteInfo &info) { auto bi = gdi.setText(info.getTarget(), info.getCurrent().c_str()); - if (bi) { + if (bi->id == "Name") { int ix = info.getCurrentInt(); bi->setExtra(ix); if (info.getTarget() == "Name") { @@ -3153,6 +3199,10 @@ void TabRunner::handleAutoComplete(gdioutput &gdi, AutoCompleteInfo &info) { } } } + else if (bi->id == "CardNo") { + + } + gdi.clearAutoComplete(""); gdi.TabFocus(1); } @@ -3173,3 +3223,81 @@ pClub TabRunner::extractClub(oEvent *oe, gdioutput &gdi) { } return dbClub; } + +void TabRunner::getAutoCompleteUnpairedCards(gdioutput & gdi, + const wstring& w, + vector& records) { + + pCourse matchCourse = nullptr; + ListBoxInfo lbi; + auto cls = gdi.getSelectedItem("RClass"); + if (runnerId) { + pRunner r = oe->getRunner(runnerId, 0); + if (r && r->getClassId(true) == cls.first) + matchCourse = r->getCourse(false); + } + if (!matchCourse && cls.first) { + pClass cPtr = oe->getClass(cls.first); + + matchCourse = cPtr ? cPtr->getCourse(true) : nullptr; + } + + records.clear(); + int nr = _wtoi(w.c_str()); + char sIn[32]; + char sComp[32]; + + vector cards; + oe->getCards(cards, false, true); + if (w.empty()) + sIn[0] = 0; + else + sprintf_s(sIn, "%d", nr); + + vector> matchedCards; + + for (pCard c : cards) { + sprintf_s(sComp, "%d", c->getCardNo()); + bool match = true; + int i = 0; + while (sIn[i]) { + if (sComp[i] != sIn[i]) { + match = false; + break; + } + i++; + } + if (match) { + int score = 0; + if (matchCourse) { + int d = matchCourse->distance(*c); + if (d < 0) + d = min(10 - d, 20); + else + d = max(d, 10); + + int age = c->getAge(); + if (age > 10000) + age = 10000; + if (age < 0) + age = 0; + + score = d * 10000 + age; + + matchedCards.emplace_back(score, c); + } + } + } + + sort(matchedCards.begin(), matchedCards.end()); + + wstring star; + for (auto &mc : matchedCards) { + wstring m = mc.second->getModified().getUpdateTime(); + const wstring& cno = mc.second->getCardNoString(); + star = mc.first < 3 * 10000 ? L"*" : L""; // Star if contents matches course + records.emplace_back(cno + star + L", " + + lang.tl("X stämplingar#" + itos(mc.second->getNumControlPunches(-1, -1))) + + L" (" + m + L")", -int(records.size()), cno, mc.second->getCardNo()); + } +} diff --git a/code/TabRunner.h b/code/TabRunner.h index bb34f29..c65f8c8 100644 --- a/code/TabRunner.h +++ b/code/TabRunner.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -25,6 +25,7 @@ #include "autocompletehandler.h" class Table; +struct AutoCompleteRecord; class TabRunner : public TabBase, AutoCompleteHandler @@ -107,6 +108,8 @@ private: shared_ptr ecoHandler; EconomyHandler *getEconomyHandler(oRunner &r); + void getAutoCompleteUnpairedCards(gdioutput &gdi, const wstring& w, vector& records); + protected: void clearCompetitionData(); public: diff --git a/code/TabSI.cpp b/code/TabSI.cpp index 3bfd93e..b69dd57 100644 --- a/code/TabSI.cpp +++ b/code/TabSI.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -44,6 +44,7 @@ #include "meos_util.h" #include #include "TabRunner.h" +#include "onlineinput.h" #include "meosexception.h" #include "MeOSFeatures.h" #include "RunnerDB.h" @@ -52,25 +53,34 @@ constexpr bool addTestPort = false; void tabForceSync(gdioutput& gdi, pEvent oe); +wstring getHiredCardDefault(); -TabSI::TabSI(oEvent *poe):TabBase(poe), activeSIC(ConvertedTimeStatus::Unknown) { +TabSI::TabSI(oEvent* poe) :TabBase(poe), activeSIC(ConvertedTimeStatus::Unknown) { editCardData.tabSI = this; directEntryGUI.tabSI = this; - interactiveReadout=poe->getPropertyInt("Interactive", 1)!=0; - useDatabase = poe->getPropertyInt("Database", 1)!=0; + interactiveReadout = poe->getPropertyInt("Interactive", 1) != 0; + useDatabase = poe->getPropertyInt("Database", 1) != 0; printSplits = false; printStartInfo = false; - savedCardUniqueId = 1; - + savedCardUniqueId = 1; + manualInput = poe->getPropertyInt("ManualInput", 0) == 1; - mode=ModeReadOut; - currentAssignIndex=0; + mode = SIMode::ModeReadOut; + + modeName[SIMode::ModeReadOut] = "Avläsning/radiotider"; + modeName[SIMode::ModeAssignCards] = "Tilldela hyrbrickor"; + modeName[SIMode::ModeCheckCards] = "Avstämning hyrbrickor"; + modeName[SIMode::ModeRegisterCards] = "Registrera hyrbrickor"; + modeName[SIMode::ModeEntry] = "Anmälningsläge"; + modeName[SIMode::ModeCardData] = "Print card data"; + + currentAssignIndex = 0; + + lastClubId = 0; + lastClassId = 0; - lastClubId=0; - lastClassId=0; - minRunnerId = 0; inputId = 0; printErrorShown = false; @@ -83,7 +93,7 @@ TabSI::~TabSI(void) } -static void entryTips(gdioutput &gdi) { +static void entryTips(gdioutput& gdi) { gdi.fillDown(); gdi.addString("", 10, "help:21576"); gdi.dropLine(1); @@ -91,14 +101,14 @@ static void entryTips(gdioutput &gdi) { } -void TabSI::logCard(gdioutput &gdi, const SICard &card) +void TabSI::logCard(gdioutput& gdi, const SICard& card) { if (!logger) { logger = make_shared(); wstring readlog = L"sireadlog_" + getLocalTimeFileName() + L".csv"; wchar_t file[260]; wstring subfolder = makeValidFileName(oe->getName(), true); - const wchar_t *sf = subfolder.empty() ? 0 : subfolder.c_str(); + const wchar_t* sf = subfolder.empty() ? 0 : subfolder.c_str(); getDesktopFile(file, readlog.c_str(), sf); logger->openOutput(file); vector head = SICard::logHeader(); @@ -110,11 +120,11 @@ void TabSI::logCard(gdioutput &gdi, const SICard &card) logger->outputRow(log); } -extern SportIdent *gSI; +extern SportIdent* gSI; extern pEvent gEvent; -int SportIdentCB(gdioutput *gdi, int type, void *data) { - TabSI &tsi = dynamic_cast(*gdi->getTabs().get(TSITab)); +int SportIdentCB(gdioutput* gdi, int type, void* data) { + TabSI& tsi = dynamic_cast(*gdi->getTabs().get(TSITab)); return tsi.siCB(*gdi, type, data); } @@ -135,10 +145,10 @@ string TabSI::typeFromSndType(SND s) { return ""; } -int TabSI::siCB(gdioutput &gdi, int type, void *data) +int TabSI::siCB(gdioutput& gdi, int type, void* data) { if (type == GUI_BUTTON) { - ButtonInfo bi = *(ButtonInfo *)data; + ButtonInfo bi = *(ButtonInfo*)data; if (bi.id == "LockFunction") { lockedFunction = true; @@ -148,12 +158,44 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) lockedFunction = false; loadPage(gdi); } - else if (bi.id == "AllowStart") + else if (bi.id == "ChangeMapping") { + gdi.restore("Mapping", false); + gdi.dropLine(4); + gdi.popX(); + + changeMapping(gdi); + + gdi.refresh(); + } + else if (bi.id == "SaveMapping") { + int ctrl = gdi.getTextNo("Code"); + if (ctrl < 1 || ctrl > 31) + throw meosException("Ogiltig kontrollkod"); + ListBoxInfo lbi; + if (!gdi.getSelectedItem("Function", lbi)) + throw meosException("Ogiltig funktion"); + getSI(gdi).addSpecialMapping(ctrl, (oPunch::SpecialPunch)lbi.data); + fillMappings(gdi); + } + else if (bi.id == "RemoveMapping") { + set sel; + gdi.getSelection("Mappings", sel); + for (auto code : sel) { + getSI(gdi).removeSpecialMapping(code); + } + fillMappings(gdi); + } + else if (bi.id == "CloseMapping") { + gdi.restore("SIPageLoaded"); + showReadoutMode(gdi); + gdi.refresh(); + } + /*else if (bi.id == "AllowStart") allowStart = gdi.isChecked(bi.id); - else if (bi.id == "AllowControl") + else if (bi.id == "AllowControl") allowControl = gdi.isChecked(bi.id); - else if (bi.id == "AllowFinish") - allowFinish = gdi.isChecked(bi.id); + else if (bi.id == "AllowFinish") + allowFinish = gdi.isChecked(bi.id);*/ else if (bi.id == "PlaySound") { oe->setProperty("PlaySound", gdi.isChecked(bi.id) ? 1 : 0); } @@ -167,7 +209,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.pushX(); gdi.fillRight(); - auto addSoundWidget = [&gdi, this](const wchar_t *name, SND type, const wstring &label) { + auto addSoundWidget = [&gdi, this](const wchar_t* name, SND type, const wstring& label) { int itype = int(type); string nname = gdioutput::narrow(name); wstring fn = oe->getPropertyString(nname.c_str(), L""); @@ -184,13 +226,13 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.dropLine(0.8); gdi.addButton("BrowseSound", "Bläddra...", SportIdentCB).setExtra(itype); gdi.addButton("TestSound", "Testa", SportIdentCB).setExtra(itype); - + if (!doPlay) { gdi.setInputStatus("SoundFile", false, false, itype); gdi.setInputStatus("BrowseSound", false, false, itype); gdi.setInputStatus("TestSound", false, false, itype); } - + gdi.dropLine(3); gdi.popX(); }; @@ -273,8 +315,12 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) wstring port = gdi.getText("ComPortName"); if (gSI->openComListen(port.c_str(), gdi.getTextNo("BaudRate"))) { gSI->startMonitorThread(port.c_str()); - loadPage(gdi); + //loadPage(gdi); + gdi.restore("", false); gdi.addString("", 1, L"Lyssnar pÃ¥ X.#" + port).setColor(colorDarkGreen); + if (!gdi.hasData("ShowControlMapping") && gSI->isAnyOpenUnkownUnit()) { + changeMapping(gdi); + } } else gdi.addString("", 1, "FEL: Porten kunde inte öppnas").setColor(colorRed); @@ -292,6 +338,12 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.dropLine(0.5); refillComPorts(gdi); + + + if (!gdi.hasData("ShowControlMapping") && gSI->isAnyOpenUnkownUnit()) { + changeMapping(gdi); + } + gdi.refresh(); } else if (bi.id == "StartSI") { @@ -350,12 +402,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addStringUT(0, lang.tl(L"Startar SI pÃ¥ ") + port + L"..."); gdi.refresh(); + bool askListen = false; + if (gSI->openCom(port.c_str())) { gSI->startMonitorThread(port.c_str()); gdi.addStringUT(0, lang.tl(L"SI pÃ¥ ") + port + L": " + lang.tl(L"OK")); printSIInfo(gdi, port); - SI_StationInfo *si = gSI->findStation(port); + const SI_StationInfo* si = gSI->findStation(port); if (si && !si->extended()) gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } @@ -367,7 +421,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addStringUT(0, lang.tl(L"SI pÃ¥ ") + port + L": " + lang.tl(L"OK")); printSIInfo(gdi, port); - SI_StationInfo *si = gSI->findStation(port); + const SI_StationInfo* si = gSI->findStation(port); if (si && !si->extended()) gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } @@ -396,11 +450,17 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.fillDown(); gdi.addButton("Cancel", "Avbryt", SportIdentCB).setCancel(); gdi.popX(); + askListen = true; } } } gdi.popX(); gdi.dropLine(); + + if (!askListen && !gdi.hasData("ShowControlMapping") && gSI->isAnyOpenUnkownUnit()) { + changeMapping(gdi); + } + refillComPorts(gdi); } gdi.refresh(); @@ -448,7 +508,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addStringUT(0, lang.tl(L"SI pÃ¥ ") + wstring(bf) + L": " + lang.tl(L"OK")); printSIInfo(gdi, bf); - SI_StationInfo *si = gSI->findStation(bf); + const SI_StationInfo* si = gSI->findStation(bf); if (si && !si->extended()) gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } @@ -457,7 +517,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addStringUT(0, lang.tl(L"SI pÃ¥ ") + wstring(bf) + L": " + lang.tl(L"OK")); printSIInfo(gdi, bf); - SI_StationInfo *si = gSI->findStation(bf); + const SI_StationInfo* si = gSI->findStation(bf); if (si && !si->extended()) gdi.addString("", boldText, "warn:notextended").setColor(colorDarkRed); } @@ -470,7 +530,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (bi.id == "PrinterSetup") { - if (mode == ModeEntry) { + if (mode == SIMode::ModeEntry) { printStartInfo = true; TabList::splitPrintSettings(*oe, gdi, true, TSITab, TabList::StartInfo); } @@ -480,7 +540,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (bi.id == "AutoTie") { - gEvent->setProperty("AutoTie", gdi.isChecked("AutoTie")); + gEvent->setProperty("AutoTie", gdi.isChecked(bi.id)); + } + else if (bi.id == "AutoTieRent") { + gEvent->setProperty("AutoTieRent", gdi.isChecked(bi.id)); } else if (bi.id == "RentCardTie") { gEvent->setProperty("RentCard", gdi.isChecked(bi.id)); @@ -492,11 +555,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) interactiveReadout = gdi.isChecked(bi.id); gEvent->setProperty("Interactive", interactiveReadout); - if (mode == ModeAssignCards) { - gdi.restore("ManualTie", false); - showAssignCard(gdi, false); + if (mode == SIMode::ModeAssignCards) { + gdi.restore("SIPageLoaded"); + showAssignCard(gdi, true); } } + else if (bi.id == "MultipleStarts") { + multipleStarts = gdi.isChecked(bi.id); + } else if (bi.id == "Database") { useDatabase = gdi.isChecked(bi.id); gEvent->setProperty("Database", useDatabase); @@ -515,7 +581,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) showManualInput(gdi); } else if (bi.id == "ReadoutWindow") { - gdioutput *gdi_settings = getExtraWindow("readout_view", true); + gdioutput* gdi_settings = getExtraWindow("readout_view", true); if (!gdi_settings) { gdi_settings = createExtraWindow("readout_view", lang.tl("Brickavläsning"), gdi.scaleLength(800), gdi.scaleLength(600), false); } @@ -610,11 +676,11 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) for (size_t k = 0; k < punches.size(); k++) { if (dofilter && filter != punches[k].date) continue; - oe->addFreePunch(punches[k].time, type, punches[k].card, true); + oe->addFreePunch(punches[k].time, type, 0, punches[k].card, true); } punches.clear(); if (origin == 1) { - TabRunner &tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); + TabRunner& tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); tc.showInForestList(gdi); } } @@ -628,7 +694,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) oe->reEvaluateAll(set(), true); cards.clear(); if (origin == 1) { - TabRunner &tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); + TabRunner& tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); tc.showInForestList(gdi); } } @@ -639,10 +705,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) int f = convertAbsoluteTimeHMS(gdi.getText("Finish"), oe->getZeroTimeNum()); int s = convertAbsoluteTimeHMS(gdi.getText("Start"), oe->getZeroTimeNum()); if (f < s) { - f += 24 * 3600; + f += 24 * timeConstHour; } - sic.FinishPunch.Time = f % (24 * 3600); - sic.StartPunch.Time = s % (24 * 3600); + sic.FinishPunch.Time = f % (24 * timeConstHour); + sic.StartPunch.Time = s % (24 * timeConstHour); if (!gdi.isChecked("HasFinish")) { sic.FinishPunch.Code = -1; sic.FinishPunch.Time = 0; @@ -655,8 +721,8 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) double t = 0.1; for (sic.nPunch = 0; sic.nPunch(*gdi.getTabs().get(TRunnerTab)); + TabRunner& tc = dynamic_cast(*gdi.getTabs().get(TRunnerTab)); tc.showInForestList(gdi); return 0; } @@ -727,6 +793,12 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) checkMoreCardsInQueue(gdi); return 0; } + else if (bi.id == "SaveUnpaired") { + gdi.restore(); + processUnmatched(gdi, activeSIC, false); + activeSIC.clear(0); + return 0; + } else if (bi.id == "OK1") { wstring name = gdi.getText("Runners"); wstring club = gdi.getText("Club", true); @@ -804,9 +876,9 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) club = lang.tl("Klubblös"); } - int year = 0; + const wstring year; pRunner r = gEvent->addRunner(gdi.getText("Runners"), club, - classes[0]->getId(), activeSIC.CardNumber, year, true); + classes[0]->getId(), activeSIC.CardNumber, year, true); if (oe->isHiredCard(activeSIC.CardNumber)) { r->getDI().setInt("CardFee", oe->getBaseCardFee()); } @@ -820,7 +892,6 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) return 0; } - gdi.restore("restOK1", false); gdi.popX(); gdi.dropLine(2); @@ -835,16 +906,16 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.selectItemByData("Classes", classes[0]->getId()); gdi.dropLine(); - gdi.setRestorePoint("restOK2"); - gdi.addButton("Cancel", "Avbryt", SportIdentCB).setCancel(); if (oe->getNumClasses() > 0) gdi.addButton("OK2", "OK", SportIdentCB).setDefault(); - gdi.fillDown(); - + gdi.addButton("NewClass", "Skapa ny klass", SportIdentCB); + gdi.fillDown(); + gdi.addButton("Cancel", "Avbryt inläsning", SportIdentCB).setCancel(); + gdi.popX(); if (classes.size() > 0) gdi.addString("FindMatch", 0, "Press Enter to continue").setColor(colorGreen); @@ -873,10 +944,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (club.empty() && oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) club = lang.tl("Klubblös"); - int year = 0; + const wstring year; pRunner r = gEvent->addRunner(gdi.getText("Runners"), club, - lbi.data, activeSIC.CardNumber, year, true); - + lbi.data, activeSIC.CardNumber, year, true); + if (activeSIC.CardNumber > 0 && oe->isHiredCard(activeSIC.CardNumber)) { r->getDI().setInt("CardFee", oe->getBaseCardFee()); } @@ -899,9 +970,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdi.addInput("ClassName", gEvent->getAutoClassName(), 10, 0, L"Klassnamn:"); gdi.dropLine(); - gdi.addButton("Cancel", "Avbryt", SportIdentCB).setCancel(); - gdi.fillDown(); gdi.addButton("OK3", "OK", SportIdentCB).setDefault(); + gdi.fillDown(); + gdi.addButton("Cancel", "Avbryt inläsning", SportIdentCB).setCancel(); + gdi.setInputFocus("ClassName", true); gdi.refresh(); gdi.popX(); @@ -911,8 +983,8 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) pClass pclass = 0; if (oe->getNumClasses() == 1 && oe->getClass(1) != 0 && - oe->getClass(1)->getType() == L"tmp" && - oe->getClass(1)->getNumRunners(false, false, false) == 0) { + oe->getClass(1)->getType() == L"tmp" && + oe->getClass(1)->getNumRunners(false, false, false) == 0) { pclass = oe->getClass(1); pclass->setType(L""); pclass->setName(gdi.getText("ClassName"), true); @@ -927,14 +999,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) pc->addControl(activeSIC.Punch[i].Code); } if (pclass == 0) { - pclass = gEvent->addClass(gdi.getText("ClassName"), pc ? pc->getId(): 0); + pclass = gEvent->addClass(gdi.getText("ClassName"), pc ? pc->getId() : 0); } else if (pc) pclass->setCourse(pc); - int year = 0; + const wstring year; pRunner r = gEvent->addRunner(gdi.getText("Runners"), gdi.getText("Club", true), - pclass->getId(), activeSIC.CardNumber, year, true); + pclass->getId(), activeSIC.CardNumber, year, true); if (activeSIC.CardNumber > 0 && oe->isHiredCard(activeSIC.CardNumber)) { r->getDI().setInt("CardFee", oe->getBaseCardFee()); @@ -963,7 +1035,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (gdi.getData("RunnerId", rid) && rid > 0) r = gEvent->getRunner(rid, 0); - else r = gEvent->addRunner(lang.tl(L"Oparad bricka"), lang.tl("Okänd"), 0, 0, 0, false); + else r = gEvent->addRunner(lang.tl(L"Oparad bricka"), lang.tl("Okänd"), 0, 0, L"", false); r->setClassId(lbi.data, true); @@ -1006,7 +1078,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) clz->synchronize(); } bool updated = false; - int year = 0; + const wstring year; bool warnClassFull = false; if (!r || r->getClassRef(false) != clz) { int numRemMaps = clz->getNumRemainingMaps(true); @@ -1054,7 +1126,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) r->setCardNo(cardNo, true);//XXX oDataInterface di = r->getDI(); - + int cardFee = gdi.isChecked("RentCard") ? oe->getBaseCardFee() : 0; di.setInt("CardFee", cardFee); @@ -1101,7 +1173,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (r->getClubId() != 0) { swprintf_s(bf, L"%s%s, %s", cno.c_str(), r->getClub().c_str(), - r->getClass(true).c_str()); + r->getClass(true).c_str()); } else { swprintf_s(bf, L"%s%s", cno.c_str(), r->getClass(true).c_str()); @@ -1214,7 +1286,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) list> data; csv.parse(fn, data); set rentCards; - for (auto &c : data) { + for (auto& c : data) { for (wstring wc : c) { int cn = _wtoi(wc.c_str()); if (cn > 0) { @@ -1223,6 +1295,9 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } } + + writeDefaultHiredCards(); + gdi.scrollToBottom(); gdi.refresh(); vector runners; @@ -1251,6 +1326,8 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) for (int c : hc) csv.outputRow(itos(c)); csv.closeOutput(); + + writeDefaultHiredCards(); } } else if (bi.id == "RHCPrint") { @@ -1317,10 +1394,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) gdiPrint.print(oe); } } - else if (type==GUI_LISTBOX) { - ListBoxInfo bi=*(ListBoxInfo *)data; + else if (type == GUI_LISTBOX) { + ListBoxInfo bi = *(ListBoxInfo*)data; - if (bi.id=="Runners") { + if (bi.id == "Runners") { pRunner r = gEvent->getRunner(bi.data, 0); if (r) { gdi.setData("RunnerId", bi.data); @@ -1332,10 +1409,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) else if (bi.id == "PayMode") { updateEntryInfo(gdi); } - else if (bi.id=="ComPort") { + else if (bi.id == "ComPort") { wchar_t bf[64]; - if (bi.text.substr(0,3)!=L"TCP") + if (bi.text.substr(0, 3) != L"TCP") swprintf_s(bf, 64, L"COM%d", bi.getDataInt()); else wcscpy_s(bf, L"TCP"); @@ -1345,47 +1422,43 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) else gdi.setText("StartSI", lang.tl("Aktivera")); } - else if (bi.id=="ReadType") { + else if (bi.id == "ReadType") { gdi.restore("SIPageLoaded"); mode = SIMode(bi.data); - gdi.setInputStatus("StartInfo", mode == ModeEntry); - - if (mode==ModeAssignCards || mode==ModeEntry) { - if (mode==ModeAssignCards) { - gdi.dropLine(1); + //gdi.setInputStatus("StartInfo", mode == ModeEntry); + + if (mode == SIMode::ModeAssignCards || mode == SIMode::ModeEntry) { + if (mode == SIMode::ModeAssignCards) { showAssignCard(gdi, true); } else { + checkBoxToolBar(gdi, { CheckBox::UseDB, CheckBox::PrintStart }); entryTips(gdi); generateEntryLine(gdi, 0); } - gdi.setInputStatus("Interactive", mode == ModeAssignCards); + /*gdi.setInputStatus("Interactive", mode == ModeAssignCards); gdi.setInputStatus("Database", mode != ModeAssignCards, true); gdi.disableInput("PrintSplits"); - - gdi.disableInput("UseManualInput"); + + gdi.disableInput("UseManualInput");*/ } - else if (mode==ModeReadOut) { - gdi.enableInput("Interactive"); - gdi.enableInput("Database", true); - gdi.enableInput("PrintSplits"); - gdi.enableInput("UseManualInput"); + else if (mode == SIMode::ModeReadOut) { showReadoutMode(gdi); } - else if (mode == ModeCardData) { + else if (mode == SIMode::ModeCardData) { numSavedCardsOnCmpOpen = savedCards.size(); showModeCardData(gdi); } - else if (mode == ModeCheckCards) { + else if (mode == SIMode::ModeCheckCards) { showCheckCardStatus(gdi, "init"); } - else if (mode == ModeRegisterCards) { + else if (mode == SIMode::ModeRegisterCards) { showRegisterHiredCards(gdi); } updateReadoutFunction(gdi); gdi.refresh(); } - else if (bi.id=="Fee") { + else if (bi.id == "Fee") { updateEntryInfo(gdi); } else if (bi.id == "NC") { @@ -1394,7 +1467,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (type == GUI_LINK) { - TextInfo ti = *(TextInfo *)data; + TextInfo ti = *(TextInfo*)data; if (ti.id == "ChRunner") { pRunner r = oe->getRunner(ti.getExtraInt(), 0); generateEntryLine(gdi, r); @@ -1411,14 +1484,14 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (type == GUI_COMBO) { - ListBoxInfo bi=*(ListBoxInfo *)data; + ListBoxInfo bi = *(ListBoxInfo*)data; - if (bi.id=="Fee") { + if (bi.id == "Fee") { updateEntryInfo(gdi); } else if (bi.id == "Runners") { DWORD rid; - if ((gdi.getData("RunnerId", rid) && rid>0) || !gdi.getText("Club", true).empty()) + if ((gdi.getData("RunnerId", rid) && rid > 0) || !gdi.getText("Club", true).empty()) return 0; // Selected from list if (!bi.text.empty() && showDatabase()) { @@ -1435,9 +1508,9 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (type == GUI_COMBOCHANGE) { - ListBoxInfo bi=*(ListBoxInfo *)data; + ListBoxInfo bi = *(ListBoxInfo*)data; if (bi.id == "Runners") { - + if (!showDatabase()) { inputId++; gdi.addTimeoutMilli(300, "AddRunnerInteractive", SportIdentCB).setExtra(inputId); @@ -1447,7 +1520,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (showDatabase() && bi.text.length() > 1) { auto rw = oe->getRunnerDatabase().getRunnerSuggestions(bi.text, 0, 20); if (!rw.empty()) { - auto &ac = gdi.addAutoComplete(bi.id); + auto& ac = gdi.addAutoComplete(bi.id); ac.setAutoCompleteHandler(this); vector items = getRunnerAutoCompelete(oe->getRunnerDatabase(), rw, 0); ac.setData(items); @@ -1461,7 +1534,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (type == GUI_EVENT) { - EventInfo ev = *(EventInfo *)data; + EventInfo ev = *(EventInfo*)data; if (ev.id == "AutoComplete") { pRunner r = oe->getRunner(runnerMatchedId, 0); if (r) { @@ -1478,7 +1551,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (type == GUI_FOCUS) { - InputInfo &ii=*(InputInfo *)data; + InputInfo& ii = *(InputInfo*)data; if (ii.id == "FinishTime") { if (ii.getExtraInt() == 1) { @@ -1490,7 +1563,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (type == GUI_TIMER) { - TimerInfo &ti = *(TimerInfo *)(data); + TimerInfo& ti = *(TimerInfo*)(data); if (ti.id == "TieCard") { runnerMatchedId = ti.getExtraInt(); @@ -1502,7 +1575,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) return 0; if (ti.id == "RunnerId") { - const wstring &text = gdi.getText(ti.id); + const wstring& text = gdi.getText(ti.id); int nr = _wtoi(text.c_str()); pRunner r = 0; @@ -1536,7 +1609,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) tieCard(gdi); } else if (ti.id == "Manual") { - const wstring &text = gdi.getText(ti.id); + const wstring& text = gdi.getText(ti.id); int nr = _wtoi(text.c_str()); pRunner r = 0; @@ -1560,7 +1633,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } else if (ti.id == "AddRunnerInteractive") { - const wstring &text = gdi.getText("Runners"); + const wstring& text = gdi.getText("Runners"); int nr = _wtoi(text.c_str()); pRunner r = 0; @@ -1582,9 +1655,9 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } } - else if (type==GUI_INPUTCHANGE) { + else if (type == GUI_INPUTCHANGE) { - InputInfo ii=*(InputInfo *)data; + InputInfo ii = *(InputInfo*)data; if (ii.id == "RunnerId") { inputId++; gdi.addTimeoutMilli(300, ii.id, SportIdentCB).setExtra(inputId); @@ -1593,7 +1666,7 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) inputId++; gdi.addTimeoutMilli(300, ii.id, SportIdentCB).setExtra(inputId); } - else if (ii.id == "CardNo" && mode == ModeAssignCards) { + else if (ii.id == "CardNo" && mode == SIMode::ModeAssignCards) { gdi.setInputStatus("TieOK", runnerMatchedId != -1); } else if (ii.id == "SI") { @@ -1601,36 +1674,42 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) if (r && r->getStartTime() > 0) { gdi.setText("Start", r->getStartTimeS()); gdi.check("HasStart", false); - int f = r->getStartTime() + 2800 + rand()%1200; + int f = r->getStartTime() + (2800 + rand() % 1200) * timeConstSecond; gdi.setText("Finish", oe->getAbsTime(f)); pCourse pc = r->getCourse(false); if (pc) { for (int n = 0; n < pc->getNumControls(); n++) { if (pc->getControl(n) && n < NC) { - gdi.setText("C" + itos(n+1), pc->getControl(n)->getFirstNumber()); + gdi.setText("C" + itos(n + 1), pc->getControl(n)->getFirstNumber()); } } } } } } - else if (type==GUI_INPUT) { - InputInfo &ii=*(InputInfo *)data; + else if (type == GUI_INPUT) { + InputInfo& ii = *(InputInfo*)data; if (ii.id == "FinishTime") { if (ii.text.empty()) { ii.setExtra(1); ii.setFgColor(colorGreyBlue); gdi.setText(ii.id, lang.tl("Aktuell tid"), true); } + else if (oe->getRelativeTime(ii.text) > 0) { + ii.setExtra(0); + } } - else if (ii.id=="CardNo") { + else if (ii.id == "CardNo") { int cardNo = gdi.getTextNo("CardNo"); - if (mode == ModeAssignCards) { - if (runnerMatchedId != -1 && gdi.isChecked("AutoTie") && cardNo>0) + if (mode == SIMode::ModeAssignCards) { + if (gdi.hasWidget("AutoTieRent") && gdi.isChecked("AutoTieRent")) { + gdi.check("RentCardTie", oe->isHiredCard(gdi.getTextNo("CardNo"))); + } + if (runnerMatchedId != -1 && gdi.isChecked("AutoTie") && cardNo > 0) gdi.addTimeoutMilli(50, "TieCard", SportIdentCB).setExtra(runnerMatchedId); } - else if (cardNo>0) { + else if (cardNo > 0) { if (ii.changedInput() && oe->hasHiredCardData()) gdi.check("RentCard", oe->isHiredCard(cardNo)); @@ -1643,10 +1722,10 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) } } } - else if (ii.id[0]=='*') { - int si=_wtoi(ii.text.c_str()); + else if (ii.id[0] == '*') { + int si = _wtoi(ii.text.c_str()); - pRunner r=oe->getRunner(ii.getExtraInt(), 0); + pRunner r = oe->getRunner(ii.getExtraInt(), 0); r->synchronize(); if (r && r->getCardNo() != si) { @@ -1669,13 +1748,13 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) oe->setProperty(name.c_str(), ii.text); } } - else if (type==GUI_INFOBOX) { + else if (type == GUI_INFOBOX) { DWORD loaded; if (!gdi.getData("SIPageLoaded", loaded)) loadPage(gdi); } else if (type == GUI_CLEAR) { - if (mode == ModeEntry) { + if (mode == SIMode::ModeEntry) { storedInfo.clear(); storedInfo.storedName = gdi.getText("Name"); storedInfo.storedCardNo = gdi.getText("CardNo"); @@ -1687,20 +1766,40 @@ int TabSI::siCB(gdioutput &gdi, int type, void *data) storedInfo.storedClassId = lbi.data; storedInfo.storedPhone = gdi.getText("Phone"); storedInfo.storedStartTime = gdi.getText("StartTime"); - + storedInfo.allStages = gdi.isChecked("AllStages"); storedInfo.rentState = gdi.isChecked("RentCard"); storedInfo.hasPaid = gdi.isChecked("Paid"); storedInfo.payMode = gdi.hasWidget("PayMode") ? gdi.getSelectedItem("PayMode").first : 0; } + else if (mode == SIMode::ModeReadOut && interactiveReadout && !activeSIC.empty()) { + CardQueue.push_back(activeSIC); + activeSIC.clear(nullptr); + } return 1; } return 0; } +void TabSI::writeDefaultHiredCards() { + csvparser csv; + try { + oe->synchronizeList(oListId::oLPunchId); + wstring def = getHiredCardDefault(); + auto hc = oe->getHiredCards(); + csvparser csv; + csv.openOutput(def); + for (int c : hc) + csv.outputRow(itos(c)); + csv.closeOutput(); + } + catch (std::exception&) { -void TabSI::refillComPorts(gdioutput &gdi) + } +} + +void TabSI::refillComPorts(gdioutput& gdi) { if (!gSI) return; @@ -1710,103 +1809,104 @@ void TabSI::refillComPorts(gdioutput &gdi) gdi.clearList("ComPort"); ports.sort(); wchar_t bf[256]; - int active=0; - int inactive=0; - while(!ports.empty()) - { - int p=ports.front(); + int active = 0; + int inactive = 0; + while (!ports.empty()) { + int p = ports.front(); swprintf_s(bf, 256, L"COM%d", p); - if (gSI->isPortOpen(bf)){ - gdi.addItem("ComPort", wstring(bf)+L" [OK]", p); - active=p; + if (gSI->isPortOpen(bf)) { + gdi.addItem("ComPort", wstring(bf) + L" [OK]", p); + active = p; } - else{ + else { gdi.addItem("ComPort", bf, p); - inactive=p; + inactive = p; } ports.pop_front(); } - if (gSI->isPortOpen(L"TCP")) - gdi.addItem("ComPort", L"TCP [OK]"); + if (gSI->isPortOpen(L"TCP")) { + active = 10000; + gdi.addItem("ComPort", L"TCP [OK]", active); + } else gdi.addItem("ComPort", L"TCP"); if (addTestPort) gdi.addItem("ComPort", L"TEST"); - if (active){ + if (active) { gdi.selectItemByData("ComPort", active); gdi.setText("StartSI", lang.tl("Koppla ifrÃ¥n")); } - else{ + else { gdi.selectItemByData("ComPort", inactive); gdi.setText("StartSI", lang.tl("Aktivera")); } } -void TabSI::showReadPunches(gdioutput &gdi, vector &punches, set &dates) +void TabSI::showReadPunches(gdioutput& gdi, vector& punches, set& dates) { char bf[64]; int yp = gdi.getCY(); int xp = gdi.getCX(); dates.clear(); - for (size_t k=0;kgetRunnerByCardNo(punches[k].card, punches[k].time, oEvent::CardLookupProperty::Any); sprintf_s(bf, "%d", punches[k].card); - gdi.addStringUT(yp, xp+40, 0, bf, 240); + gdi.addStringUT(yp, xp + 40, 0, bf, 240); - if (r!=0) - gdi.addStringUT(yp, xp+100, 0, r->getName(), 170); + if (r != 0) + gdi.addStringUT(yp, xp + 100, 0, r->getName(), 170); if (punches[k].date[0] != 0) { - gdi.addStringUT(yp, xp+280, 0, punches[k].date, 75); + gdi.addStringUT(yp, xp + 280, 0, punches[k].date, 75); dates.insert(punches[k].date); } - if (punches[k].time>0) - gdi.addStringUT(yp, xp+360, 0, oe->getAbsTime(punches[k].time)); + if (punches[k].time > 0) + gdi.addStringUT(yp, xp + 360, 0, oe->getAbsTime(punches[k].time)); else - gdi.addStringUT(yp, xp+360, 0, makeDash(L"-")); + gdi.addStringUT(yp, xp + 360, 0, makeDash(L"-")); yp += gdi.getLineHeight(); } } -void TabSI::showReadCards(gdioutput &gdi, vector &cards) +void TabSI::showReadCards(gdioutput& gdi, vector& cards) { char bf[64]; int yp = gdi.getCY(); int xp = gdi.getCX(); - for (size_t k=0;kgetRunnerByCardNo(cards[k].CardNumber, 0, oEvent::CardLookupProperty::Any); sprintf_s(bf, "%d", cards[k].CardNumber); - gdi.addStringUT(yp, xp+40, 0, bf, 240); + gdi.addStringUT(yp, xp + 40, 0, bf, 240); - if (r!=0) - gdi.addStringUT(yp, xp+100, 0, r->getName(), 240); + if (r != 0) + gdi.addStringUT(yp, xp + 100, 0, r->getName(), 240); - gdi.addStringUT(yp, xp+300, 0, oe->getAbsTime(cards[k].FinishPunch.Time)); + gdi.addStringUT(yp, xp + 300, 0, oe->getAbsTime(cards[k].FinishPunch.Time)); yp += gdi.getLineHeight(); } } -SportIdent &TabSI::getSI(const gdioutput &gdi) { +SportIdent& TabSI::getSI(const gdioutput& gdi) { if (!gSI) { - HWND hWnd=gdi.getHWNDMain(); + HWND hWnd = gdi.getHWNDMain(); gSI = new SportIdent(hWnd, 0, true); } return *gSI; } -bool TabSI::loadPage(gdioutput &gdi) { +bool TabSI::loadPage(gdioutput& gdi) { gdi.clearPage(true); printErrorShown = false; gdi.pushX(); @@ -1823,48 +1923,48 @@ bool TabSI::loadPage(gdioutput &gdi) { gdi.fillRight(); gdi.pushX(); gdi.addInput("SI", L"", 10, SportIdentCB, L"SI"); - int s = 3600+(rand()%60)*60; - int f = s + 1800 + rand()%900; - - gdi.setCX(gdi.getCX()+gdi.getLineHeight()); - + int s = timeConstHour + (rand() % 60) * timeConstMinute; + int f = s + timeConstHour / 2 + ((rand() % 3600) / 3) * timeConstSecond; + + gdi.setCX(gdi.getCX() + gdi.getLineHeight()); + gdi.dropLine(1.4); gdi.addCheckbox("HasStart", ""); gdi.dropLine(-1.4); - gdi.setCX(gdi.getCX()-gdi.getLineHeight()); + gdi.setCX(gdi.getCX() - gdi.getLineHeight()); gdi.addInput("Start", oe->getAbsTime(s), 6, 0, L"Start"); - + gdi.dropLine(1.4); gdi.addCheckbox("HasFinish", ""); gdi.dropLine(-1.4); - gdi.setCX(gdi.getCX()-gdi.getLineHeight()); + gdi.setCX(gdi.getCX() - gdi.getLineHeight()); gdi.addInput("Finish", oe->getAbsTime(f), 6, 0, L"MÃ¥l"); gdi.addSelection("NC", 45, 200, SportIdentCB, L"NC"); - const int src[11] = {33, 34, 45, 50, 36, 38, 59, 61, 62, 67, 100}; - + const int src[11] = { 33, 34, 45, 50, 36, 38, 59, 61, 62, 67, 100 }; + for (int i = 0; i < 32; i++) gdi.addItem("NC", itow(i), i); gdi.selectItemByData("NC", NC); for (int i = 0; i < NC; i++) { - int level = min(i, NC-i)/5; + int level = min(i, NC - i) / 5; int c; - if (i < NC /2) { - int ix = i%6; + if (i < NC / 2) { + int ix = i % 6; c = src[ix] + level * 10; if (c == 100) c = 183; } else { - int ix = 10-(NC-i-1)%5; + int ix = 10 - (NC - i - 1) % 5; c = src[ix] + level * 10; } - gdi.addInput("C" + itos(i+1), itow(c), 3, 0, L"#C" + itow(i+1)); + gdi.addInput("C" + itos(i + 1), itow(c), 3, 0, L"#C" + itow(i + 1)); } - + gdi.dropLine(); gdi.addButton("Save", "Bricka", SportIdentCB); gdi.fillDown(); @@ -1893,21 +1993,31 @@ bool TabSI::loadPage(gdioutput &gdi) { int xb = gdi.getCX(); int yb = gdi.getCY(); - gdi.fillRight(); if (!oe->empty()) { gdi.setCX(xb + gdi.scaleLength(10)); gdi.setCY(yb + gdi.scaleLength(10)); - + if (!lockedFunction) { + gdi.fillRight(); gdi.addString("", fontMediumPlus, "Funktion:"); gdi.addSelection("ReadType", 200, 200, SportIdentCB); - gdi.addItem("ReadType", lang.tl("Avläsning/radiotider"), ModeReadOut); + auto addItem = [this, &gdi](SIMode mode) { + gdi.addItem("ReadType", lang.tl(modeName[mode]), int(mode)); + }; + addItem(SIMode::ModeReadOut); + addItem(SIMode::ModeEntry); + addItem(SIMode::ModeAssignCards); + addItem(SIMode::ModeCheckCards); + addItem(SIMode::ModeRegisterCards); + addItem(SIMode::ModeCardData); + /* + gdi.addItem("ReadType", lang.tl(modeName[SIMode::ModeReadOut]), SIMode::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); - gdi.selectItemByData("ReadType", mode); + gdi.addItem("ReadType", lang.tl("Print card data"), ModeCardData);*/ + gdi.selectItemByData("ReadType", int(mode)); gdi.dropLine(-0.1); gdi.addButton("LockFunction", "LÃ¥s funktion", SportIdentCB); @@ -1915,82 +2025,56 @@ bool TabSI::loadPage(gdioutput &gdi) { readoutFunctionY = gdi.getCY(); gdi.dropLine(0.3); - gdi.addCheckbox("PlaySound", "Ljud", SportIdentCB, oe->getPropertyInt("PlaySound", 1) != 0, - "Spela upp ett ljud för att indikera resultatet av brickavläsningen."); + gdi.addCheckbox("PlaySound", "Ljud", SportIdentCB, oe->getPropertyInt("PlaySound", 1) != 0, + "Spela upp ett ljud för att indikera resultatet av brickavläsningen."); gdi.dropLine(-0.3); gdi.addButton("SoundChoice", "Ljudval...", SportIdentCB); gdi.dropLine(0.3); - gdi.addString("Allow", 0, "TillÃ¥t:"); - gdi.addCheckbox("AllowStart", "Start", SportIdentCB, allowStart); - gdi.addCheckbox("AllowControl", "Radio", SportIdentCB, allowControl); - gdi.addCheckbox("AllowFinish", "MÃ¥l", SportIdentCB, allowFinish); - updateReadoutFunction(gdi); - gdi.dropLine(2.5); + gdi.dropLine(2); gdi.setCX(xb + gdi.scaleLength(10)); } else { + gdi.addString("", fontMediumPlus, lang.tl(modeName[mode])); + gdi.fillRight(); gdi.addButton("UnlockFunction", "LÃ¥s upp", SportIdentCB); gdi.dropLine(0.2); } } else { - mode = ModeCardData; + mode = SIMode::ModeCardData; } - if (!oe->empty()) - gdi.addCheckbox("Interactive", "Interaktiv inläsning", SportIdentCB, interactiveReadout); + optionBarPosY = gdi.getCY(); + optionBarPosX = gdi.getCX(); + check_toolbar_xb = xb; + check_toolbar_yb = yb; - if (oe->empty() || oe->useRunnerDb()) - gdi.addCheckbox("Database", "Använd löpardatabasen", SportIdentCB, useDatabase); - - gdi.addCheckbox("PrintSplits", "Sträcktidsutskrift[check]", SportIdentCB, printSplits); - - if (!oe->empty()) { - gdi.addCheckbox("StartInfo", "Startbevis", SportIdentCB, printStartInfo, "Skriv ut startbevis för deltagaren"); - if (mode != ModeEntry) - gdi.disableInput("StartInfo"); - } - if (!oe->empty()) - gdi.addCheckbox("UseManualInput", "Manuell inmatning", SportIdentCB, manualInput); - - gdi.fillDown(); - - if (!oe->empty()) { - RECT rc = {xb, yb, gdi.getWidth(), gdi.getHeight()}; - gdi.addRectangle(rc, colorLightBlue); - } gdi.popX(); - gdi.dropLine(2); gdi.setRestorePoint("SIPageLoaded"); - if (mode == ModeReadOut) { + if (mode == SIMode::ModeReadOut) { showReadoutMode(gdi); - - gdi.dropLine(); } - else if (mode == ModeAssignCards) { - gdi.dropLine(1); + else if (mode == SIMode::ModeAssignCards) { showAssignCard(gdi, true); } - else if (mode == ModeEntry) { + else if (mode == SIMode::ModeEntry) { + checkBoxToolBar(gdi, { CheckBox::UseDB, CheckBox::PrintStart }); entryTips(gdi); generateEntryLine(gdi, 0); - gdi.disableInput("Interactive"); - gdi.disableInput("PrintSplits"); - gdi.disableInput("UseManualInput"); } - else if (mode == ModeCardData) { + else if (mode == SIMode::ModeCardData) { showModeCardData(gdi); } - else if (mode == ModeCheckCards) { + else if (mode == SIMode::ModeCheckCards) { showCheckCardStatus(gdi, "init"); } - else if (mode == ModeRegisterCards) { + else if (mode == SIMode::ModeRegisterCards) { showRegisterHiredCards(gdi); } @@ -2002,63 +2086,192 @@ bool TabSI::loadPage(gdioutput &gdi) { return true; } -void TabSI::showReadoutMode(gdioutput & gdi) { - gdi.dropLine(0.5); - gdi.fillDown(); +void TabSI::checkBoxToolBar(gdioutput& gdi, const set& items) const { + gdi.pushX(); + gdi.setCY(optionBarPosY); + gdi.setCX(optionBarPosX); + gdi.fillRight(); + + if (!items.empty()) + gdi.dropLine(0.2); + + if (items.count(CheckBox::Interactive)) + gdi.addCheckbox("Interactive", "Interaktiv inläsning", SportIdentCB, interactiveReadout); + + if (items.count(CheckBox::UseDB) && (oe->empty() || oe->useRunnerDb())) + gdi.addCheckbox("Database", "Använd löpardatabasen", SportIdentCB, useDatabase); + + if (items.count(CheckBox::PrintSplits)) + gdi.addCheckbox("PrintSplits", "Sträcktidsutskrift[check]", SportIdentCB, printSplits); + + if (items.count(CheckBox::PrintStart)) + gdi.addCheckbox("StartInfo", "Startbevis", SportIdentCB, printStartInfo, "Skriv ut startbevis för deltagaren"); + + if (items.count(CheckBox::Manual)) + gdi.addCheckbox("UseManualInput", "Manuell inmatning", SportIdentCB, manualInput); + + if (items.count(CheckBox::SeveralTurns)) + gdi.addCheckbox("MultipleStarts", "Flera starter per deltagare", SportIdentCB, multipleStarts, + "info:multiple_start"); + + if (items.count(CheckBox::AutoTie)) + gdi.addCheckbox("AutoTie", "Knyt automatiskt efter inläsning", SportIdentCB, oe->getPropertyInt("AutoTie", 1) != 0); + + if (items.count(CheckBox::AutoTieRent) && oe->hasHiredCardData()) { + gdi.addCheckbox("AutoTieRent", "Automatisk hyrbrickshantering genom registrerade hyrbrickor", SportIdentCB, oe->getPropertyInt("AutoTieRent", 1) != 0); + } + + if (!items.empty()) + gdi.dropLine(2); + + gdi.fillDown(); + + if (!oe->empty()) { + RECT rc = { check_toolbar_xb, check_toolbar_yb, gdi.getWidth(), gdi.getHeight() }; + gdi.addRectangle(rc, colorLightBlue); + } + gdi.popX(); + gdi.dropLine(1.5); + + gdi.fillDown(); + // gdi.popY(); + gdi.popX(); +} + +void TabSI::showReadoutMode(gdioutput& gdi) { + if (!oe->empty()) + checkBoxToolBar(gdi, { CheckBox::Interactive, CheckBox::UseDB, CheckBox::PrintSplits, + CheckBox::SeveralTurns, CheckBox::Manual }); + else { + checkBoxToolBar(gdi, { CheckBox::UseDB, CheckBox::PrintSplits }); + } + + gdi.dropLine(0.5); gdi.pushX(); - gdi.addString("", boldLarge, "Avläsning/radiotider"); - gdi.dropLine(0.2); - gdi.fillRight(); gdi.addButton("Import", "Importera frÃ¥n fil...", SportIdentCB); - gdi.fillDown(); - gdi.addButton("ReadoutWindow", "Eget fönster", SportIdentCB); + gdi.addButton("ReadoutWindow", "Öppna avläsningsfönster", SportIdentCB, "info:readoutwindow"); + + if (oe->empty() || !getSI(gdi).isAnyOpenUnkownUnit()) + gdi.dropLine(3); + else { + gdi.setRestorePoint("Mapping"); + gdi.setData("ShowControlMapping", 1); + gdi.fillRight(); + int cx = gdi.getCX() + gdi.scaleLength(10); + int cy = gdi.getCY(); + gdi.setCX(cx); + gdi.setCY(cy); + gdi.addString("", boldText, "Tolkning av radiostämplingar med okänd typ"); + int maxX = gdi.getCX(); + + gdi.setCX(cx); + gdi.dropLine(1.1); + auto mappings = getSI(gdi).getSpecialMappings(); + wstring check, start, finish; + auto add = [](int code, wstring& dst) { + if (!dst.empty()) + dst += L", "; + dst += itow(code); + }; + + auto complete = [](wstring& dst) { + if (dst.empty()) + dst = makeDash(L"-"); + }; + + for (auto& v : mappings) { + if (v.second == oPunch::SpecialPunch::PunchFinish) + add(v.first, finish); + else if (v.second == oPunch::SpecialPunch::PunchStart) + add(v.first, start); + else if (v.second == oPunch::SpecialPunch::PunchCheck) + add(v.first, check); + } + complete(finish); + complete(start); + complete(check); + gdi.addString("", 1, "Enhetskod:"); + + if (!check.empty()) { + gdi.addString("", 0, "Check:"); + gdi.setCX(gdi.getCX() - gdi.scaleLength(2)); + gdi.addStringUT(0, check); + } + if (!start.empty()) { + gdi.addString("", 0, "Start:"); + gdi.setCX(gdi.getCX() - gdi.scaleLength(2)); + gdi.addStringUT(0, start); + } + if (!finish.empty()) { + gdi.addString("", 0, "MÃ¥l:"); + gdi.setCX(gdi.getCX() - gdi.scaleLength(2)); + gdi.addStringUT(0, finish); + } + maxX = max(maxX, gdi.getCX()); + gdi.setCX(maxX); + gdi.setCY(cy); + + gdi.addButton("ChangeMapping", "Ändra", SportIdentCB); + maxX = max(maxX, gdi.getCX()) + gdi.scaleLength(10); + + RECT rc; + rc.left = cx - gdi.scaleLength(5); + rc.top = cy - gdi.scaleLength(10); + rc.right = maxX; + rc.bottom = gdi.getHeight() + gdi.scaleLength(4);//gdi.getCY() + gdi.scaleLength(5); + gdi.fillDown(); + gdi.addRectangle(rc, GDICOLOR::colorLightCyan); + gdi.dropLine(1); + } - gdi.dropLine(); - gdi.popX(); - gdi.setRestorePoint("Help"); gdi.fillDown(); + gdi.popX(); + + gdi.setRestorePoint("Help"); + gdi.fillRight(); gdi.addString("", 10, "info:readoutbase"); + gdi.fillDown(); + gdi.addString("", 10, "info:readoutmore"); gdi.popX(); gdi.setCY(gdi.getHeight()); + gdi.dropLine(0.5); + + renderReadCard(gdi, 100); if (gdi.isChecked("UseManualInput")) showManualInput(gdi); } -void TabSI::updateReadoutFunction(gdioutput &gdi) { +void TabSI::updateReadoutFunction(gdioutput& gdi) { bool hide = mode != SIMode::ModeReadOut; - gdi.hideWidget("Allow", hide); - gdi.hideWidget("AllowStart", hide); - gdi.hideWidget("AllowControl", hide); - gdi.hideWidget("AllowFinish", hide); gdi.hideWidget("SoundChoice", hide); gdi.hideWidget("PlaySound", hide); } -void InsertSICard(gdioutput &gdi, SICard &sic) +void InsertSICard(gdioutput& gdi, SICard& sic) { - TabSI &tsi = dynamic_cast(*gdi.getTabs().get(TSITab)); + TabSI& tsi = dynamic_cast(*gdi.getTabs().get(TSITab)); tsi.insertSICard(gdi, sic); } -pRunner TabSI::autoMatch(const SICard &sic, pRunner db_r) +pRunner TabSI::autoMatch(const SICard& sic, pRunner db_r) { assert(useDatabase); //Look up in database. if (!db_r) db_r = gEvent->dbLookUpByCard(sic.CardNumber); - pRunner r=0; + pRunner r = 0; if (db_r) { r = gEvent->getRunnerByName(db_r->getName(), db_r->getClub()); - if ( !r ) { + if (!r) { vector classes; int dist = gEvent->findBestClass(sic, classes); - if (classes.size()==1 && dist>=-1 && dist<=1) { //Almost perfect match found. Assume it is it! + if (classes.size() == 1 && dist >= -1 && dist <= 1) { //Almost perfect match found. Assume it is it! r = gEvent->addRunnerFromDB(db_r, classes[0]->getId(), true); r->setCardNo(sic.CardNumber, false); @@ -2066,27 +2279,27 @@ pRunner TabSI::autoMatch(const SICard &sic, pRunner db_r) r->getDI().setInt("CardFee", oe->getBaseCardFee()); } } - else r=0; //Do not assume too much... + else r = 0; //Do not assume too much... } } - if (r && r->getCard()==0) + if (r && r->getCard() == 0) return r; else return 0; } -void TabSI::insertSICard(gdioutput &gdi, SICard &sic) +void TabSI::insertSICard(gdioutput& gdi, SICard& sic) { wstring msg; try { insertSICardAux(gdi, sic); } - catch (meosException &ex) { + catch (meosException& ex) { msg = ex.wwhat(); } - catch(std::exception &ex) { + catch (std::exception& ex) { msg = gdi.widen(ex.what()); } - catch(...) { + catch (...) { msg = L"Ett okänt fel inträffade."; } @@ -2094,7 +2307,7 @@ void TabSI::insertSICard(gdioutput &gdi, SICard &sic) gdi.alert(msg); } -void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) +void TabSI::insertSICardAux(gdioutput& gdi, SICard& sic) { if (oe->isReadOnly()) { gdi.makeEvent("ReadCard", "insertSICard", sic.CardNumber, 0, true); @@ -2102,9 +2315,9 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } DWORD loaded; - bool pageLoaded=gdi.getData("SIPageLoaded", loaded); + bool pageLoaded = gdi.getData("SIPageLoaded", loaded); - if (pageLoaded && manualInput && mode == ModeReadOut) + if (pageLoaded && manualInput && mode == SIMode::ModeReadOut) gdi.restore("ManualInput"); if (!pageLoaded && !insertCardNumberField.empty()) { @@ -2112,7 +2325,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) return; } - if (mode==ModeAssignCards) { + if (mode == SIMode::ModeAssignCards) { if (!pageLoaded) { CardQueue.push_back(sic); gdi.addInfoBox("SIREAD", L"Inläst bricka ställd i kö"); @@ -2120,7 +2333,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) else assignCard(gdi, sic); return; } - else if (mode==ModeEntry) { + else if (mode == SIMode::ModeEntry) { if (!pageLoaded) { CardQueue.push_back(sic); gdi.addInfoBox("SIREAD", L"Inläst bricka ställd i kö"); @@ -2128,16 +2341,16 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) else entryCard(gdi, sic); return; } - if (mode==ModeCheckCards) { + if (mode == SIMode::ModeCheckCards) { if (!pageLoaded) { CardQueue.push_back(sic); gdi.addInfoBox("SIREAD", L"Inläst bricka ställd i kö"); } - else + else checkCard(gdi, sic, true); return; } - else if (mode == ModeRegisterCards) { + else if (mode == SIMode::ModeRegisterCards) { if (!pageLoaded) { CardQueue.push_back(sic); gdi.addInfoBox("SIREAD", L"Inläst bricka ställd i kö"); @@ -2147,7 +2360,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } return; } - else if (mode == ModeCardData) { + else if (mode == SIMode::ModeCardData) { if (sic.convertedTime == ConvertedTimeStatus::Hour12) { int locTime = getLocalAbsTime(); int st = -1; @@ -2155,22 +2368,22 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) st = sic.StartPunch.Time; else if (sic.nPunch > 0 && sic.Punch[0].Code != -1) st = sic.Punch[0].Time; - + if (st == -1) - st = (locTime + 3600 * 20) % (12 * 3600); + st = (locTime + timeConstHour * 20) % (12 * timeConstHour); else { // We got a start time. Calculate running time if (sic.FinishPunch.Code != -1) { - int rt = (sic.FinishPunch.Time - st + 12 * 3600) % (12 * 3600); - + int rt = (sic.FinishPunch.Time - st + 12 * timeConstHour) % (12 * timeConstHour); + // Adjust to local time at start; - locTime = (locTime - rt + (24 * 3600)) % (24 * 3600); + locTime = (locTime - rt + (24 * timeConstHour)) % (24 * timeConstHour); } } - int zt1 = (st + 23 * 3600) % (24 * 3600); - int zt2 = st + 11 * 3600; - int d1 = min(abs(locTime - zt1), abs(locTime - zt1 + 3600 * 24)); - int d2 = min(abs(locTime - zt2), abs(locTime - zt2 + 3600 * 24)); + int zt1 = (st + 23 * timeConstHour) % (24 * timeConstHour); + int zt2 = st + 11 * timeConstHour; + int d1 = min(abs(locTime - zt1), abs(locTime - zt1 + timeConstHour * 24)); + int d2 = min(abs(locTime - zt2), abs(locTime - zt2 + timeConstHour * 24)); if (d1 < d2) sic.analyseHour12Time(zt1); @@ -2183,18 +2396,18 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) bool first = savedCards.size() == numSavedCardsOnCmpOpen; savedCards.push_back(make_pair(savedCardUniqueId++, sic)); - + if (printSplits) { generateSplits(savedCards.back().first, gdi); } - gdioutput *gdi_settings = getExtraWindow("readout_view", true); + gdioutput* gdi_settings = getExtraWindow("readout_view", true); if (gdi_settings) { showReadoutStatus(*gdi_settings, nullptr, nullptr, &savedCards.back().second, L""); } if (savedCards.size() > 1 && pageLoaded) { - RECT rc = {30, gdi.getCY(), gdi.scaleLength(250), gdi.getCY() + 3}; + RECT rc = { 30, gdi.getCY(), gdi.scaleLength(250), gdi.getCY() + 3 }; gdi.addRectangle(rc); } @@ -2219,8 +2432,46 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) return; } pRunner r; - if (sic.runnerId == 0) - r = gEvent->getRunnerByCardNo(sic.CardNumber, 0, oEvent::CardLookupProperty::ForReadout); + if (sic.runnerId == 0) { + r = oe->getRunnerByCardNo(sic.CardNumber, 0, oEvent::CardLookupProperty::ForReadout); + + if (!r && multipleStarts && !oe->isCardRead(sic)) { + // Convert punch times to relative times. + oe->convertTimes(nullptr, sic); + int time = sic.getFirstTime(); + pRunner rOld = oe->getRunnerByCardNo(sic.CardNumber, time, oEvent::CardLookupProperty::Any); + + if (rOld) { + // New entry + vector classes; + oe->findBestClass(sic, classes); + int classId = rOld->getClassId(false); + if (classes.size() == 1) + classId = classes[0]->getId(); + + wstring given = rOld->getGivenName(); + wstring family = rOld->getFamilyName(); + size_t ep = family.find_last_of(')'); + size_t sp = family.find_last_of('('); + + int num = 1; + if (ep != string::npos && sp != string::npos && sp + 1 < ep) { + num = _wtoi(family.data() + sp + 1); + if (num > 0) { + family = trim(family.substr(0, ep - 2)); + } + } + if (classId == rOld->getClassId(false)) + family += + L" (" + itow(num + 1) + L")"; + + r = oe->addRunner(L"tmp", rOld->getClub(), + classId, sic.CardNumber, rOld->getBirthDate(), false); + + r->setName(family + L", " + given, true); + r->setFlag(oAbstractRunner::TransferFlags::FlagNoDatabase, true); + } + } + } else { r = gEvent->getRunner(sic.runnerId, 0); sic.CardNumber = r->getCardNo(); @@ -2236,7 +2487,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) //SIPage not loaded... if (!r && showDatabase()) - r=autoMatch(sic, 0); + r = autoMatch(sic, 0); // Assign a class if not already done autoAssignClass(r, sic); @@ -2249,7 +2500,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } else { CardQueue.push_back(sic); - gdi.addInfoBox("SIREAD", L"info:readout_action#" + gEvent->getCurrentTimeS()+L"#"+itow(sic.CardNumber), 0, SportIdentCB); + gdi.addInfoBox("SIREAD", L"info:readout_action#" + gEvent->getCurrentTimeS() + L"#" + itow(sic.CardNumber), 0, SportIdentCB); playReadoutSound(SND::ActionNeeded); return; } @@ -2293,7 +2544,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) if (readBefore) { //We stop processing of new cards, while working... // Thus cannot be in interactive mode - activeSIC=sic; + activeSIC = sic; wchar_t bf[256]; if (interactiveReadout) { @@ -2314,8 +2565,8 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } else { if (printSplits) { - pRunner runner = getRunnerForCardSplitPrint(sic); - + pRunner runner = getRunnerForCardSplitPrint(sic); + if (runner) generateSplits(runner, gdi); } @@ -2385,7 +2636,7 @@ void TabSI::insertSICardAux(gdioutput &gdi, SICard &sic) } } -pRunner TabSI::getRunnerForCardSplitPrint(const SICard &sic) const { +pRunner TabSI::getRunnerForCardSplitPrint(const SICard& sic) const { pRunner runner = 0; vector out; oe->getRunnersByCardNo(sic.CardNumber, false, oEvent::CardLookupProperty::SkipNoStart, out); @@ -2407,7 +2658,7 @@ pRunner TabSI::getRunnerForCardSplitPrint(const SICard &sic) const { return runner; } -void TabSI::startInteractive(gdioutput &gdi, const SICard &sic, pRunner r, pRunner db_r) +void TabSI::startInteractive(gdioutput& gdi, const SICard& sic, pRunner r, pRunner db_r) { if (!r) { gdi.setRestorePoint(); @@ -2425,11 +2676,11 @@ void TabSI::startInteractive(gdioutput &gdi, const SICard &sic, pRunner r, pRunn gdi.addCombo("Runners", 300, 300, SportIdentCB, L"Namn:"); gEvent->fillRunners(gdi, "Runners", false, oEvent::RunnerFilterOnlyNoResult); - if (db_r){ + if (db_r) { gdi.setText("Runners", db_r->getName()); //Data from DB } - else if (sic.firstName[0] || sic.lastName[0]){ //Data from SI-card - gdi.setText("Runners", wstring(sic.lastName) + L", " + sic.firstName); + else if (sic.firstName[0] || sic.lastName[0]) { //Data from SI-card + gdi.setText("Runners", wstring(sic.lastName) + L", " + sic.firstName); } if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { gdi.addCombo("Club", 200, 300, 0, L"Klubb:").setHandler(&directEntryGUI); @@ -2444,29 +2695,31 @@ void TabSI::startInteractive(gdioutput &gdi, const SICard &sic, pRunner r, pRunn gdi.setInputFocus("Club"); //Process this card. - activeSIC=sic; + activeSIC = sic; gdi.dropLine(); gdi.setRestorePoint("restOK1"); gdi.addButton("OK1", "OK", SportIdentCB).setDefault(); + gdi.addButton("SaveUnpaired", "Spara oparad bricka", SportIdentCB); gdi.fillDown(); - gdi.addButton("Cancel", "Avbryt", SportIdentCB).setCancel(); + gdi.addButton("Cancel", "Avbryt inläsning", SportIdentCB).setCancel(); gdi.popX(); gdi.addString("FindMatch", 0, "").setColor(colorGreen); gdi.registerEvent("AutoComplete", SportIdentCB).setKeyCommand(KC_AUTOCOMPLETE); gdi.dropLine(); gdi.scrollToBottom(); + gdi.setOnClearCb(SportIdentCB); gdi.refresh(); } else { //Process this card. - activeSIC=sic; + activeSIC = sic; //No class. Select... gdi.setRestorePoint(); wchar_t bf[256]; swprintf_s(bf, 256, L"SI X inläst. Brickan tillhör Y som saknar klass.#%d#%s", - sic.CardNumber, r->getName().c_str()); + sic.CardNumber, r->getName().c_str()); gdi.dropLine(); gdi.addString("", 1, bf); @@ -2491,12 +2744,13 @@ void TabSI::startInteractive(gdioutput &gdi, const SICard &sic, pRunner r, pRunn gdi.popX(); gdi.setData("RunnerId", r->getId()); gdi.scrollToBottom(); + gdi.setOnClearCb(SportIdentCB); gdi.refresh(); } } // Insert card without converting times and with/without runner -void TabSI::processInsertCard(const SICard &sic) +void TabSI::processInsertCard(const SICard& sic) { if (oe->isCardRead(sic)) return; @@ -2507,17 +2761,17 @@ void TabSI::processInsertCard(const SICard &sic) card->setCardNo(sic.CardNumber); card->setMeasuredVoltage(sic.miliVolt); - if (sic.CheckPunch.Code!=-1) - card->addPunch(oPunch::PunchCheck, sic.CheckPunch.Time, 0); + if (sic.CheckPunch.Code != -1) + card->addPunch(oPunch::PunchCheck, sic.CheckPunch.Time, 0, sic.CheckPunch.Code); - if (sic.StartPunch.Code!=-1) - card->addPunch(oPunch::PunchStart, sic.StartPunch.Time, 0); + if (sic.StartPunch.Code != -1) + card->addPunch(oPunch::PunchStart, sic.StartPunch.Time, 0, sic.StartPunch.Code); - for(unsigned i=0;iaddPunch(sic.Punch[i].Code, sic.Punch[i].Time, 0); + for (unsigned i = 0; i < sic.nPunch; i++) + card->addPunch(sic.Punch[i].Code, sic.Punch[i].Time, 0, 0); - if (sic.FinishPunch.Code!=-1) - card->addPunch(oPunch::PunchFinish, sic.FinishPunch.Time,0 ); + if (sic.FinishPunch.Code != -1) + card->addPunch(oPunch::PunchFinish, sic.FinishPunch.Time, 0, sic.FinishPunch.Code); //Update to SQL-source card->synchronize(); @@ -2528,16 +2782,16 @@ void TabSI::processInsertCard(const SICard &sic) } } -bool TabSI::processUnmatched(gdioutput &gdi, const SICard &csic, bool silent) { +bool TabSI::processUnmatched(gdioutput& gdi, const SICard& csic, bool silent) { SICard sic(csic); - pCard card=gEvent->allocateCard(0); + StoredReadout rout; + pCard card = gEvent->allocateCard(0); card->setReadId(csic); card->setCardNo(csic.CardNumber); card->setMeasuredVoltage(csic.miliVolt); - - wstring info=lang.tl(L"Okänd bricka ") + itow(sic.CardNumber) + L"."; - wstring warnings; + + rout.info = lang.tl(L"Okänd bricka ") + itow(sic.CardNumber) + L"."; // Write read card to log logCard(gdi, sic); @@ -2545,67 +2799,49 @@ bool TabSI::processUnmatched(gdioutput &gdi, const SICard &csic, bool silent) { // Convert punch times to relative times. gEvent->convertTimes(nullptr, sic); - if (sic.CheckPunch.Code!=-1) - card->addPunch(oPunch::PunchCheck, sic.CheckPunch.Time, 0); + if (sic.CheckPunch.Code != -1) + card->addPunch(oPunch::PunchCheck, sic.CheckPunch.Time, 0, sic.CheckPunch.Code); - if (sic.StartPunch.Code!=-1) - card->addPunch(oPunch::PunchStart, sic.StartPunch.Time, 0); + if (sic.StartPunch.Code != -1) + card->addPunch(oPunch::PunchStart, sic.StartPunch.Time, 0, sic.StartPunch.Code); - for(unsigned i=0;iaddPunch(sic.Punch[i].Code, sic.Punch[i].Time, 0); + for (unsigned i = 0; i < sic.nPunch; i++) + card->addPunch(sic.Punch[i].Code, sic.Punch[i].Time, 0, 0); - if (sic.FinishPunch.Code!=-1) - card->addPunch(oPunch::PunchFinish, sic.FinishPunch.Time, 0); + if (sic.FinishPunch.Code != -1) + card->addPunch(oPunch::PunchFinish, sic.FinishPunch.Time, 0, sic.FinishPunch.Code); else - warnings+=lang.tl("MÃ¥lstämpling saknas."); + rout.warnings += lang.tl("MÃ¥lstämpling saknas."); //Update to SQL-source card->synchronize(); - gdioutput *gdi_settings = getExtraWindow("readout_view", true); + gdioutput* gdi_settings = getExtraWindow("readout_view", true); if (gdi_settings) { showReadoutStatus(*gdi_settings, nullptr, card, nullptr, L""); } - RECT rc; - rc.left=15; - rc.right=gdi.getWidth()-10; - rc.top=gdi.getCY()+gdi.getLineHeight()-5; - rc.bottom=rc.top+gdi.getLineHeight()*2+14; + rout.color = colorLightRed; if (!silent) { - gdi.fillDown(); - //gdi.dropLine(); - gdi.addRectangle(rc, colorLightRed, true); - gdi.addStringUT(rc.top+6, rc.left+20, 1, info); - //gdi.dropLine(); + rout.render(gdi, rout.computeRC(gdi)); + if (gdi.isChecked("UseManualInput")) showManualInput(gdi); gdi.scrollToBottom(); } else { - gdi.addInfoBox("SIINFO", L"#" + info, 10000); + gdi.addInfoBox("SIINFO", L"#" + rout.info, 10000); } + readCards.push_back(std::move(rout)); gdi.makeEvent("DataUpdate", "sireadout", 0, 0, true); playReadoutSound(SND::ActionNeeded); checkMoreCardsInQueue(gdi); return true; } -void TabSI::rentCardInfo(gdioutput &gdi, int width) -{ - RECT rc; - rc.left=15; - rc.right=rc.left+width; - rc.top=gdi.getCY()-7; - rc.bottom=rc.top+gdi.getLineHeight()+5; - - gdi.addRectangle(rc, colorYellow, true); - gdi.addString("", rc.top+2, rc.left+width/2, 1|textCenter, "Vänligen Ã¥terlämna hyrbrickan."); -} - -bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool silent) +bool TabSI::processCard(gdioutput& gdi, pRunner runner, const SICard& csic, bool silent) { if (!runner) return false; @@ -2614,7 +2850,7 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool runner = runner->getMatchedRunner(csic); - int lh=gdi.getLineHeight(); + int lh = gdi.getLineHeight(); //Update from SQL-source runner->synchronize(); @@ -2625,9 +2861,9 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool pClass cls = runner->getClassRef(false); pClass pclass = runner->getClassRef(true); if (cls && cls->hasCoursePool()) { - unsigned leg=runner->legToRun(); + unsigned leg = runner->legToRun(); - if (leggetNumStages()) { + if (leg < cls->getNumStages()) { pCourse c = cls->selectCourseFromPool(leg, csic); if (c) runner->setCourseId(c->getId()); @@ -2650,16 +2886,16 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool if (!runner->getCourse(false) && !csic.isManualInput() && !oe->getMeOSFeatures().hasFeature(MeOSFeatures::NoCourses)) { if (pclass && !pclass->hasMultiCourse() && !pclass->hasDirectResult()) { - pCourse pcourse=gEvent->addCourse(pclass->getName()); + pCourse pcourse = gEvent->addCourse(pclass->getName()); pclass->setCourse(pcourse); - for(unsigned i=0;iaddControl(csic.Punch[i].Code); wchar_t msg[256]; swprintf_s(msg, lang.tl(L"Skapade en bana för klassen %s med %d kontroller frÃ¥n brickdata (SI-%d)").c_str(), - pclass->getName().c_str(), csic.nPunch, csic.CardNumber); + pclass->getName().c_str(), csic.nPunch, csic.CardNumber); if (silent) gdi.addInfoBox("SIINFO", wstring(L"#") + msg, 15000); @@ -2668,7 +2904,7 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool } else { if (!(pclass && pclass->hasDirectResult())) { - const wchar_t *msg=L"Löpare saknar klass eller bana"; + const wchar_t* msg = L"Löpare saknar klass eller bana"; if (silent) gdi.addInfoBox("SIINFO", msg, 15000); @@ -2678,30 +2914,28 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool } } - pCourse pcourse=runner->getCourse(false); + pCourse pcourse = runner->getCourse(false); if (pcourse) pcourse->synchronize(); else if (pclass && pclass->hasDirectResult()) runner->setStatus(StatusOK, true, oBase::ChangeType::Update, false); - //silent=true; SICard sic(csic); - wstring info, warnings, cardno; - vector MP; + StoredReadout rout; if (!csic.isManualInput()) { - pCard card=gEvent->allocateCard(runner); + pCard card = gEvent->allocateCard(runner); card->setReadId(csic); card->setCardNo(sic.CardNumber); card->setMeasuredVoltage(sic.miliVolt); - cardno = itow(sic.CardNumber); + rout.cardno = itow(sic.CardNumber); - info = runner->getName() + L" (" + cardno + L"), "; + rout.info = runner->getName() + L" (" + rout.cardno + L"), "; if (!runner->getClub().empty()) - info += runner->getClub() + +L", "; - info += runner->getClass(true); + rout.info += runner->getClub() + +L", "; + rout.info += runner->getClass(true); // Write read card to log logCard(gdi, sic); @@ -2712,33 +2946,34 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool const int finishPT = prelCourse ? prelCourse->getFinishPunchType() : oPunch::PunchFinish; bool hasFinish = false; - if (sic.CheckPunch.Code!=-1) - card->addPunch(oPunch::PunchCheck, sic.CheckPunch.Time,0); + if (sic.CheckPunch.Code != -1) + card->addPunch(oPunch::PunchCheck, sic.CheckPunch.Time, 0, sic.CheckPunch.Code); - if (sic.StartPunch.Code!=-1) - card->addPunch(oPunch::PunchStart, sic.StartPunch.Time,0); + if (sic.StartPunch.Code != -1) + card->addPunch(oPunch::PunchStart, sic.StartPunch.Time, 0, sic.StartPunch.Code); - for(unsigned i=0;iaddPunch(sic.Punch[i].Code, sic.Punch[i].Time,0); + card->addPunch(sic.Punch[i].Code, sic.Punch[i].Time, 0, 0); } - if (sic.FinishPunch.Code!=-1) { - card->addPunch(oPunch::PunchFinish, sic.FinishPunch.Time,0); + if (sic.FinishPunch.Code != -1) { + card->addPunch(oPunch::PunchFinish, sic.FinishPunch.Time, 0, sic.FinishPunch.Code); if (finishPT == oPunch::PunchFinish) hasFinish = true; } if (!hasFinish) - warnings+=lang.tl(L"MÃ¥lstämpling saknas."); + rout.warnings += lang.tl(L"MÃ¥lstämpling saknas."); card->synchronize(); - runner->addPunches(card, MP); + runner->addPunches(card, rout.MP); + runner->synchronize(true); runner->hasManuallyUpdatedTimeStatus(); } else { //Manual input - info = runner->getName() + L", " + runner->getClub() + L", " + runner->getClass(true); + rout.info = runner->getName() + L", " + runner->getClub() + L", " + runner->getClass(true); runner->setCard(0); if (csic.statusOK) { @@ -2754,8 +2989,8 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool runner->setFinishTime(csic.relativeFinishTime); } - cardno = makeDash(L"-"); - runner->evaluateCard(true, MP, 0, oBase::ChangeType::Update); + rout.cardno = makeDash(L"-"); + runner->evaluateCard(true, rout.MP, 0, oBase::ChangeType::Update); runner->synchronizeAll(true); runner->hasManuallyUpdatedTimeStatus(); } @@ -2763,15 +2998,6 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool //Update to SQL-source runner->synchronize(); - RECT rc; - rc.left=15; - rc.right=gdi.getWidth()-10; - rc.top=gdi.getCY()+gdi.getLineHeight()-5; - rc.bottom=rc.top+gdi.getLineHeight()*2+14; - - if (!warnings.empty()) - rc.bottom+=gdi.getLineHeight(); - set clsSet; wstring mpList; @@ -2781,50 +3007,41 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool if (runner->getTeam()) gEvent->calculateTeamResults(clsSet, oEvent::ResultType::ClassResult); - if (runner->getStatusComputed()==StatusOK || isPossibleResultStatus(runner->getStatusComputed())) { + if (runner->getStatusComputed(true) == StatusOK || isPossibleResultStatus(runner->getStatusComputed(true))) { wstring placeS = getPlace(runner); - if (placeS == L"1") + if (placeS == L"1") playReadoutSound(SND::Leader); else playReadoutSound(SND::OK); + rout.color = colorLightGreen; + rout.statusline = lang.tl(L"Status OK, ") + + lang.tl(L"Tid: ") + getTimeString(runner); + if (!placeS.empty()) + rout.statusline += lang.tl(L", Prel. placering: ") + placeS; + rout.statusline += lang.tl(L", Prel. bomtid: ") + runner->getMissedTimeS(); + + rout.rentCard = runner->isHiredCard() || oe->isHiredCard(sic.CardNumber); + if (!silent) { - gdi.fillDown(); - //gdi.dropLine(); - gdi.addRectangle(rc, colorLightGreen, true); - - gdi.addStringUT(rc.top+6, rc.left+20, 1, info); - if (!warnings.empty()) - gdi.addStringUT(rc.top+6+2*lh, rc.left+20, 0, warnings); - - wstring statusline = lang.tl(L"Status OK, ") + - lang.tl(L"Tid: ") + getTimeString(runner); - - if (!placeS.empty()) - statusline += lang.tl(L", Prel. placering: ") + placeS; - - statusline += lang.tl(L", Prel. bomtid: ") + runner->getMissedTimeS(); - gdi.addStringUT(rc.top+6+lh, rc.left+20, 0, statusline); - - if (runner->isHiredCard() || oe->isHiredCard(sic.CardNumber)) - rentCardInfo(gdi, rc.right-rc.left); + rout.render(gdi, rout.computeRC(gdi)); gdi.scrollToBottom(); } else { - wstring msg = L"#" + runner->getName() + L" (" + cardno + L")\n"+ - runner->getClub() + L". " + runner->getClass(true) + - L"\n" + lang.tl("Tid: ") + runner->getRunningTimeS(true) + lang.tl(L", Plats ") + placeS; + wstring msg = L"#" + runner->getName() + L" (" + rout.cardno + L")\n" + + runner->getClub() + L". " + runner->getClass(true) + + L"\n" + lang.tl("Tid: ") + runner->getRunningTimeS(true, SubSecond::Auto) + lang.tl(L", Plats ") + placeS; gdi.addInfoBox("SIINFO", msg, 10000); } } else { - wstring msg=lang.tl(L"Status: ") + runner->getStatusS(true, true); + rout.statusline = lang.tl(L"Status: ") + runner->getStatusS(true, true); playReadoutSound(SND::NotOK); - if (!MP.empty()) { - for (int c : MP) { + if (!rout.MP.empty()) { + for (int c : rout.MP) { if (!mpList.empty()) mpList += L", "; mpList = mpList + itow(c); @@ -2832,35 +3049,26 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool mpList += lang.tl(" saknas."); } - if (!mpList.empty()) - msg += L", (" + mpList + + L")"; + if (!mpList.empty()) + rout.statusline += L", (" + mpList + +L")"; + + rout.color = colorLightRed; + rout.rentCard = runner->isHiredCard() || oe->isHiredCard(sic.CardNumber); if (!silent) { - gdi.fillDown(); - gdi.dropLine(); - gdi.addRectangle(rc, colorLightRed, true); - - gdi.addStringUT(rc.top+6, rc.left+20, 1, info); - if (!warnings.empty()) - gdi.addStringUT(rc.top+6+lh*2, rc.left+20, 1, warnings); - - gdi.addStringUT(rc.top+6+lh, rc.left+20, 0, msg); - - if (runner->isHiredCard()) - rentCardInfo(gdi, rc.right-rc.left); - + rout.render(gdi, rout.computeRC(gdi)); gdi.scrollToBottom(); } else { - wstring statusmsg = L"#" + runner->getName() + L" (" + cardno + L")\n"+ - runner->getClub() + L". "+ runner->getClass(true) + - L"\n" + msg; + wstring statusmsg = L"#" + runner->getName() + L" (" + rout.cardno + L")\n" + + runner->getClub() + L". " + runner->getClass(true) + + L"\n" + rout.statusline; gdi.addInfoBox("SIINFO", statusmsg, 10000); } } - gdioutput *gdi_settings = getExtraWindow("readout_view", true); + gdioutput* gdi_settings = getExtraWindow("readout_view", true); if (gdi_settings) { showReadoutStatus(*gdi_settings, runner, nullptr, nullptr, mpList); } @@ -2874,11 +3082,70 @@ bool TabSI::processCard(gdioutput &gdi, pRunner runner, const SICard &csic, bool activeSIC.clear(&csic); + readCards.push_back(std::move(rout)); checkMoreCardsInQueue(gdi); return true; } -wstring TabSI::getPlace(const oRunner *runner) { +RECT TabSI::StoredReadout::computeRC(gdioutput& gdi) const { + RECT rc; + rc.left = gdi.scaleLength(15); + rc.right = gdi.getWidth() - gdi.scaleLength(10); + rc.top = gdi.getCY() + gdi.getLineHeight() - gdi.scaleLength(5); + rc.bottom = rc.top + gdi.getLineHeight() * 2 + gdi.scaleLength(14); + + if (!warnings.empty()) + rc.bottom += gdi.getLineHeight(); + + return rc; +} + +void TabSI::StoredReadout::render(gdioutput& gdi, const RECT& rc) const { + gdi.fillDown(); + gdi.addRectangle(rc, color, true); + int lh = gdi.getLineHeight(); + int marg = gdi.scaleLength(20); + int tmarg = gdi.scaleLength(6); + gdi.addStringUT(rc.top + 6, rc.left + marg, 1, info); + if (!warnings.empty()) + gdi.addStringUT(rc.top + tmarg + 2 * lh, rc.left + marg, 0, warnings); + + gdi.addStringUT(rc.top + tmarg + lh, rc.left + marg, 0, statusline); + + if (rentCard) + rentCardInfo(gdi, rc); +} + +void TabSI::StoredReadout::rentCardInfo(gdioutput& gdi, const RECT& rcIn) { + RECT rc; + rc.left = rcIn.left; + rc.right = rcIn.right; + rc.top = rcIn.bottom; + rc.bottom = rc.top + gdi.getLineHeight() + gdi.scaleLength(5); + + gdi.addRectangle(rc, colorYellow, true); + gdi.addString("", rc.top + gdi.scaleLength(2), (rc.left + rc.right) / 2, 1 | textCenter, "Vänligen Ã¥terlämna hyrbrickan."); +} + +void TabSI::renderReadCard(gdioutput& gdi, int maxNumber) { + if (readCards.empty()) + return; + + auto it = readCards.begin(); + int N = readCards.size(); + if (N > maxNumber) + it = std::next(it, N - maxNumber); + + while (it != readCards.end()) { + it->render(gdi, it->computeRC(gdi)); + ++it; + } + + gdi.scrollToBottom(); +} + + +wstring TabSI::getPlace(const oRunner* runner) { bool qfClass = runner->getClassId(false) != runner->getClassId(true); wstring placeS = (runner->getTeam() && !qfClass) ? runner->getTeam()->getLegPlaceS(runner->getLegNumber(), false) : @@ -2887,36 +3154,36 @@ wstring TabSI::getPlace(const oRunner *runner) { return placeS; } -wstring TabSI::getTimeString(const oRunner *runner) { +wstring TabSI::getTimeString(const oRunner* runner) { bool qfClass = runner->getClassId(false) != runner->getClassId(true); - wstring ts = runner->getRunningTimeS(true); + wstring ts = runner->getRunningTimeS(true, SubSecond::Auto); if (!qfClass && runner->getTeam()) { cTeam t = runner->getTeam(); if (t->getLegStatus(runner->getLegNumber(), true, false) == StatusOK) { - ts += L" (" + t->getLegRunningTimeS(runner->getLegNumber(), true, false) + L")"; + ts += L" (" + t->getLegRunningTimeS(runner->getLegNumber(), true, false, SubSecond::Auto) + L")"; } } return ts; } -wstring TabSI::getTimeAfterString(const oRunner *runner) { +wstring TabSI::getTimeAfterString(const oRunner* runner) { bool qfClass = runner->getClassId(false) != runner->getClassId(true); int ta = runner->getTimeAfter(); - + wstring ts; if (ta > 0) ts = L"+" + formatTime(ta); - + if (!qfClass && runner->getTeam()) { cTeam t = runner->getTeam(); if (t->getLegStatus(runner->getLegNumber(), true, false) == StatusOK) { - int tat = t->getTimeAfter(runner->getLegNumber()); + int tat = t->getTimeAfter(runner->getLegNumber(), true); if (tat > 0) { - /* if (ta == 0) - ts = L"0:00"; + /* if (ta == 0) + ts = L"0:00"; - ts += L" (+" + formatTime(tat) + L")"; - */ + ts += L" (+" + formatTime(tat) + L")"; + */ ts = L"+" + formatTime(tat); } } @@ -2924,37 +3191,28 @@ wstring TabSI::getTimeAfterString(const oRunner *runner) { return ts; } -void TabSI::processPunchOnly(gdioutput &gdi, const SICard &csic) +void TabSI::processPunchOnly(gdioutput& gdi, const SICard& csic) { SICard sic = csic; DWORD loaded; gEvent->convertTimes(nullptr, sic); - oFreePunch *ofp = 0; + oFreePunch* ofp = 0; wstring accessError; if (sic.nPunch == 1) { - if (allowControl) - ofp = gEvent->addFreePunch(sic.Punch[0].Time, sic.Punch[0].Code, sic.CardNumber, true); - else - accessError = L"Radio tillÃ¥ts inte (X)#" + itow(sic.CardNumber); + ofp = gEvent->addFreePunch(sic.Punch[0].Time, sic.Punch[0].Code, 0, sic.CardNumber, true); } else if (sic.FinishPunch.Time > 0) { - if (allowFinish) - ofp = gEvent->addFreePunch(sic.FinishPunch.Time, oPunch::PunchFinish, sic.CardNumber, true); - else - accessError = L"MÃ¥lstämpling tillÃ¥ts inte (X)#" + itow(sic.CardNumber); + ofp = gEvent->addFreePunch(sic.FinishPunch.Time, oPunch::PunchFinish, sic.FinishPunch.Code, sic.CardNumber, true); } else if (sic.StartPunch.Time > 0) { - if (allowStart) - ofp = gEvent->addFreePunch(sic.StartPunch.Time, oPunch::PunchStart, sic.CardNumber, true); - else - accessError = L"Startstämpling tillÃ¥ts inte (X)#" + itow(sic.CardNumber); + ofp = gEvent->addFreePunch(sic.StartPunch.Time, oPunch::PunchStart, sic.StartPunch.Code, sic.CardNumber, true); } else { - ofp = gEvent->addFreePunch(sic.CheckPunch.Time, oPunch::PunchCheck, sic.CardNumber, true); + ofp = gEvent->addFreePunch(sic.CheckPunch.Time, oPunch::PunchCheck, sic.CheckPunch.Code, sic.CardNumber, true); } if (ofp) { pRunner r = ofp->getTiedRunner(); - if (gdi.getData("SIPageLoaded", loaded)){ + if (gdi.getData("SIPageLoaded", loaded)) { //gEvent->getRunnerByCard(sic.CardNumber); if (r) { @@ -2963,7 +3221,7 @@ void TabSI::processPunchOnly(gdioutput &gdi, const SICard &csic) gdi.dropLine(); } else { - wstring str= itow(sic.CardNumber) + lang.tl(" (okänd) stämplade vid ") + ofp->getSimpleString(); + wstring str = itow(sic.CardNumber) + lang.tl(" (okänd) stämplade vid ") + ofp->getSimpleString(); gdi.addStringUT(0, str); gdi.dropLine(0.3); } @@ -2974,8 +3232,9 @@ void TabSI::processPunchOnly(gdioutput &gdi, const SICard &csic) gdi.makeEvent("DataUpdate", "sireadout", r ? r->getId() : 0, 0, true); } else if (!accessError.empty()) { + playReadoutSound(SND::ActionNeeded); if (gdi.getData("SIPageLoaded", loaded)) { - gdi.addString("", 0, accessError).setColor(colorDarkRed); + gdi.addString("", fontLarge, accessError).setColor(colorDarkRed); gdi.dropLine(0.3); gdi.scrollToBottom(); } @@ -2987,7 +3246,7 @@ void TabSI::processPunchOnly(gdioutput &gdi, const SICard &csic) return; } -void TabSI::entryCard(gdioutput &gdi, const SICard &sic) +void TabSI::entryCard(gdioutput& gdi, const SICard& sic) { gdi.setText("CardNo", sic.CardNumber); @@ -2998,18 +3257,18 @@ void TabSI::entryCard(gdioutput &gdi, const SICard &sic) wstring club; int age = 0; if (showDatabase()) { - pRunner db_r=oe->dbLookUpByCard(sic.CardNumber); + pRunner db_r = oe->dbLookUpByCard(sic.CardNumber); if (db_r) { - name=db_r->getNameRaw(); - club=db_r->getClub(); + name = db_r->getNameRaw(); + club = db_r->getClub(); age = db_r->getBirthAge(); } } //Else get name from card if (name.empty() && (sic.firstName[0] || sic.lastName[0])) - name=wstring(sic.lastName) + L", " + wstring(sic.firstName); + name = wstring(sic.lastName) + L", " + wstring(sic.firstName); gdi.setText("Name", name); if (gdi.hasWidget("Club") && !club.empty()) @@ -3032,7 +3291,7 @@ void TabSI::entryCard(gdioutput &gdi, const SICard &sic) } } -void TabSI::assignCard(gdioutput &gdi, const SICard &sic) +void TabSI::assignCard(gdioutput& gdi, const SICard& sic) { if (interactiveReadout) { @@ -3049,34 +3308,34 @@ void TabSI::assignCard(gdioutput &gdi, const SICard &sic) int storedAssigneIndex = currentAssignIndex; //Try first current focus - BaseInfo *ii=gdi.getInputFocus(); + BaseInfo* ii = gdi.getInputFocus(); wstring sicode = itow(sic.CardNumber); - if (ii && ii->id[0]=='*') { - currentAssignIndex=atoi(ii->id.c_str()+1); + if (ii && ii->id[0] == '*') { + currentAssignIndex = atoi(ii->id.c_str() + 1); } else { //If not correct focus, use internal counter char id[32]; sprintf_s(id, "*%d", currentAssignIndex++); - ii=gdi.setInputFocus(id); + ii = gdi.setInputFocus(id); if (!ii) { - currentAssignIndex=0; + currentAssignIndex = 0; sprintf_s(id, "*%d", currentAssignIndex++); - ii=gdi.setInputFocus(id); + ii = gdi.setInputFocus(id); } } if (ii && ii->getExtraInt()) { - pRunner r=oe->getRunner(ii->getExtraInt(), 0); + pRunner r = oe->getRunner(ii->getExtraInt(), 0); if (r) { if (oe->checkCardUsed(gdi, *r, sic.CardNumber)) { currentAssignIndex = storedAssigneIndex; return; } if (r->getCardNo() == 0 || - gdi.ask(L"Skriv över existerande bricknummer?")) { + gdi.ask(L"Skriv över existerande bricknummer?")) { r->setCardNo(sic.CardNumber, false); r->getDI().setInt("CardFee", oe->getBaseCardFee()); @@ -3090,7 +3349,7 @@ void TabSI::assignCard(gdioutput &gdi, const SICard &sic) checkMoreCardsInQueue(gdi); } -void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { +void TabSI::generateEntryLine(gdioutput& gdi, pRunner r) { oe->synchronizeList({ oListId::oLRunnerId, oListId::oLCardId }); gdi.restore("EntryLine", false); @@ -3108,7 +3367,7 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { gdi.addInput("CardNo", storedInfo.storedCardNo, 8, SportIdentCB, L"Bricka:"); if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { - gdi.addCombo("Club", 180, 200, 0, L"Klubb:", + gdi.addCombo("Club", 180, 200, 0, L"Klubb:", L"Skriv första bokstaven i klubbens namn och tryck pil-ner för att leta efter klubben") .setHandler(&directEntryGUI); oe->fillClubs(gdi, "Club"); @@ -3129,7 +3388,7 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { } gdi.addItem("Class", d); } - + if (storedInfo.storedClassId > 0 && gdi.selectItemByData("Class", storedInfo.storedClassId)) { } else if (!gdi.selectItemByData("Class", lastClassId)) { @@ -3139,7 +3398,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", true, false); - + if (!storedInfo.storedFee.empty() && storedInfo.storedFee != L"@") gdi.setText("Fee", storedInfo.storedFee); else @@ -3153,7 +3412,7 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { gdi.popX(); gdi.dropLine(3.1); - gdi.addString("",0, "Starttid:"); + gdi.addString("", 0, "Starttid:"); gdi.dropLine(-0.2); gdi.addInput("StartTime", storedInfo.storedStartTime, 5, 0, L""); @@ -3164,24 +3423,24 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { gdi.dropLine(-0.2); gdi.addInput("Bib", L"", 5, 0, L""); - gdi.setCX(gdi.getCX()+gdi.scaleLength(20)); + gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); gdi.dropLine(0.2); - + gdi.addString("", 0, "Telefon:"); gdi.dropLine(-0.2); gdi.addInput("Phone", storedInfo.storedPhone, 12, 0, L""); gdi.dropLine(0.2); - gdi.setCX(gdi.getCX()+gdi.scaleLength(20)); + gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); gdi.addCheckbox("RentCard", "Hyrbricka", SportIdentCB, storedInfo.rentState); gdi.addCheckbox("NoTiming", "Utan tidtagning", nullptr, false); if (oe->hasNextStage()) gdi.addCheckbox("AllStages", "Anmäl till efterföljande etapper", SportIdentCB, storedInfo.allStages); - - if (r!=0) { - if (r->getCardNo()>0) + + if (r != 0) { + if (r->getCardNo() > 0) gdi.setText("CardNo", r->getCardNo()); gdi.setText("Name", r->getNameRaw()); @@ -3200,7 +3459,7 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { gdi.check("NoTiming", r->hasFlag(oAbstractRunner::TransferFlags::FlagNoTiming)); gdi.check("RentCard", dci.getInt("CardFee") != 0); if (gdi.hasWidget("Paid")) - gdi.check("Paid", dci.getInt("Paid")>0); + gdi.check("Paid", dci.getInt("Paid") > 0); else if (gdi.hasWidget("PayMode")) { int paidId = dci.getInt("Paid") > 0 ? r->getPaymentMode() : 1000; gdi.selectItemByData("PayMode", paidId); @@ -3220,15 +3479,15 @@ void TabSI::generateEntryLine(gdioutput &gdi, pRunner r) { updateEntryInfo(gdi); gdi.setInputFocus("CardNo"); gdi.dropLine(2); - - RECT rc = {xb, yb, gdi.getWidth(), gdi.getHeight()}; + + RECT rc = { xb, yb, gdi.getWidth(), gdi.getHeight() }; gdi.addRectangle(rc, colorLightCyan); gdi.scrollToBottom(); gdi.popX(); gdi.setOnClearCb(SportIdentCB); } -void TabSI::updateEntryInfo(gdioutput &gdi) +void TabSI::updateEntryInfo(gdioutput& gdi) { int fee = oe->interpretCurrency(gdi.getText("Fee", true)); if (gdi.isChecked("RentCard")) { @@ -3248,7 +3507,7 @@ void TabSI::updateEntryInfo(gdioutput &gdi) wstring method; if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy)) { - bool invoice = true; + bool invoice = true; if (gdi.hasWidget("PayMode")) { invoice = gdi.getSelectedItem("PayMode").first == 1000; } @@ -3261,7 +3520,7 @@ void TabSI::updateEntryInfo(gdioutput &gdi) method = lang.tl(L"Faktureras"); gdi.setText("EntryInfo", lang.tl(L"X: Y. Tryck för att spara#" + - method + L"#" + oe->formatCurrency(fee)), true); + method + L"#" + oe->formatCurrency(fee)), true); } else { gdi.setText("EntryInfo", lang.tl("Press Enter to continue"), true); @@ -3269,12 +3528,12 @@ void TabSI::updateEntryInfo(gdioutput &gdi) } } -void TabSI::generateSplits(const pRunner r, gdioutput &gdi) +void TabSI::generateSplits(const pRunner r, gdioutput& gdi) { const bool wideFormat = oe->getPropertyInt("WideSplitFormat", 0) == 1; if (wideFormat) { addToPrintQueue(r); - while(checkpPrintQueue(gdi)); + while (checkpPrintQueue(gdi)); } else { gdioutput gdiprint(2.0, gdi.getHWNDTarget(), splitPrinter); @@ -3286,7 +3545,7 @@ void TabSI::generateSplits(const pRunner r, gdioutput &gdi) } } -void TabSI::generateStartInfo(gdioutput &gdi, const oRunner &r) { +void TabSI::generateStartInfo(gdioutput& gdi, const oRunner& r) { if (printStartInfo) { gdioutput gdiprint(2.0, gdi.getHWNDTarget(), splitPrinter); r.printStartInfo(gdiprint); @@ -3295,15 +3554,15 @@ void TabSI::generateStartInfo(gdioutput &gdi, const oRunner &r) { } } -void TabSI::printerSetup(gdioutput &gdi) +void TabSI::printerSetup(gdioutput& gdi) { gdi.printSetup(splitPrinter); splitPrinter.onlyChanged = false; } -void TabSI::checkMoreCardsInQueue(gdioutput &gdi) { +void TabSI::checkMoreCardsInQueue(gdioutput& gdi) { // Create a local list to avoid stack overflow - list cards = CardQueue; + list cards = std::move(CardQueue); CardQueue.clear(); std::exception storedEx; bool fail = false; @@ -3315,7 +3574,7 @@ void TabSI::checkMoreCardsInQueue(gdioutput &gdi) { gdi.RemoveFirstInfoBox("SIREAD"); insertSICard(gdi, c); } - catch (std::exception &ex) { + catch (std::exception& ex) { fail = true; storedEx = ex; } @@ -3325,19 +3584,19 @@ void TabSI::checkMoreCardsInQueue(gdioutput &gdi) { throw storedEx; } -bool TabSI::autoAssignClass(pRunner r, const SICard &sic) { - if (r && r->getClassId(false)==0) { +bool TabSI::autoAssignClass(pRunner r, const SICard& sic) { + if (r && r->getClassId(false) == 0) { vector classes; int dist = oe->findBestClass(sic, classes); - if (classes.size() == 1 && dist>=-1 && dist<=1) // Allow at most one wrong punch + if (classes.size() == 1 && dist >= -1 && dist <= 1) // Allow at most one wrong punch r->setClassId(classes[0]->getId(), true); } return r && r->getClassId(false) != 0; } -void TabSI::showManualInput(gdioutput &gdi) { +void TabSI::showManualInput(gdioutput& gdi) { runnerMatchedId = -1; gdi.setRestorePoint("ManualInput"); gdi.fillDown(); @@ -3346,7 +3605,7 @@ void TabSI::showManualInput(gdioutput &gdi) { int x = gdi.getCX(); int y = gdi.getCY(); - gdi.setCX(x+gdi.scaleLength(15)); + gdi.setCX(x + gdi.scaleLength(15)); gdi.dropLine(); gdi.addString("", 1, "Manuell inmatning"); gdi.fillRight(); @@ -3366,17 +3625,16 @@ void TabSI::showManualInput(gdioutput &gdi) { gdi.dropLine(); RECT rc; - rc.left=x; - rc.right=gdi.getWidth()-10; - rc.top=y; - rc.bottom=gdi.getCY()+gdi.scaleLength(5); + rc.left = x; + rc.right = gdi.getWidth() - 10; + rc.top = y; + rc.bottom = gdi.getCY() + gdi.scaleLength(5); gdi.dropLine(); gdi.addRectangle(rc, colorLightBlue); - //gdi.refresh(); gdi.scrollToBottom(); } -void TabSI::tieCard(gdioutput &gdi) { +void TabSI::tieCard(gdioutput& gdi) { int card = gdi.getTextNo("CardNo"); pRunner r = oe->getRunner(runnerMatchedId, 0); @@ -3391,9 +3649,14 @@ void TabSI::tieCard(gdioutput &gdi) { return; } - bool rent = gdi.isChecked("RentCardTie"); + bool rent = false; + if (!gdi.hasWidget("AutoTieRent") || !gdi.isChecked("AutoTieRent")) + rent = gdi.isChecked("RentCardTie"); + else + rent = oe->isHiredCard(card); + + r->synchronize(); r->setCardNo(card, true, false); - r->getDI().setInt("CardFee", rent ? oe->getBaseCardFee() : 0); r->synchronize(true); @@ -3411,6 +3674,9 @@ void TabSI::tieCard(gdioutput &gdi) { gdi.addStringUT(0, L"(" + r->getClub() + L")", 0); gdi.addStringUT(1, itos(r->getCardNo()), 0).setColor(colorDarkGreen); + if (rent) + gdi.addStringUT(0, L" (" + lang.tl("Hyrd") + L") "); + gdi.addString("EditAssign", 0, "Ändra", SportIdentCB).setExtra(r->getId()); gdi.dropLine(1.5); gdi.popX(); @@ -3418,21 +3684,30 @@ void TabSI::tieCard(gdioutput &gdi) { showAssignCard(gdi, false); } -void TabSI::showAssignCard(gdioutput &gdi, bool showHelp) { - gdi.enableInput("Interactive"); - gdi.disableInput("Database", true); - gdi.disableInput("PrintSplits"); - gdi.disableInput("StartInfo"); - gdi.disableInput("UseManualInput"); - gdi.setRestorePoint("ManualTie"); - gdi.fillDown(); +void TabSI::showAssignCard(gdioutput& gdi, bool showHelp) { + //gdi.fillDown(); + //gdi.setRestorePoint("AssignCardBase"); if (interactiveReadout) { - if (showHelp) + if (showHelp) { + checkBoxToolBar(gdi, { CheckBox::Interactive, CheckBox::AutoTie, CheckBox::AutoTieRent }); gdi.addString("", 10, L"Avmarkera 'X' för att hantera alla bricktildelningar samtidigt.#" + lang.tl("Interaktiv inläsning")); + /*gdi.dropLine(0.5); + gdi.addCheckbox("AutoTie", "Knyt automatiskt efter inläsning", SportIdentCB, oe->getPropertyInt("AutoTie", 1) != 0); + gdi.addCheckbox("AutoTieRent", "Automatisk hyrbrickshantering genom registrerade hyrbrickor", SportIdentCB, oe->getPropertyInt("AutoTieRent", 1) != 0); + if (!oe->hasHiredCardData()) { + gdi.disableInput("AutoTieRent"); + gdi.check("AutoTieRent", false); + }*/ + gdi.dropLine(0.5); + gdi.setRestorePoint("ManualTie"); + } } else { - if (showHelp) + if (showHelp) { + checkBoxToolBar(gdi, { CheckBox::Interactive }); gdi.addString("", 10, L"Markera 'X' för att hantera deltagarna en och en.#" + lang.tl("Interaktiv inläsning")); + } + gEvent->assignCardInteractive(gdi, SportIdentCB); gdi.refresh(); return; @@ -3445,7 +3720,7 @@ void TabSI::showAssignCard(gdioutput &gdi, bool showHelp) { int x = gdi.getCX(); int y = gdi.getCY(); - gdi.setCX(x+gdi.scaleLength(15)); + gdi.setCX(x + gdi.scaleLength(15)); gdi.dropLine(); gdi.addString("", 1, "Knyt bricka / deltagare"); gdi.fillRight(); @@ -3454,7 +3729,7 @@ void TabSI::showAssignCard(gdioutput &gdi, bool showHelp) { gdi.addInput("RunnerId", L"", 20, SportIdentCB, L"Nummerlapp, lopp-id eller namn:"); gdi.addInput("CardNo", L"", 8, SportIdentCB, L"Bricknr:"); gdi.dropLine(1.2); - gdi.addCheckbox("AutoTie", "Knyt automatiskt efter inläsning", SportIdentCB, oe->getPropertyInt("AutoTie", 1) != 0); + //gdi.addCheckbox("AutoTie", "Knyt automatiskt efter inläsning", SportIdentCB, oe->getPropertyInt("AutoTie", 1) != 0); gdi.addCheckbox("RentCardTie", "Hyrd", SportIdentCB, oe->getPropertyInt("RentCard", 0) != 0); gdi.dropLine(-0.3); @@ -3468,10 +3743,10 @@ void TabSI::showAssignCard(gdioutput &gdi, bool showHelp) { gdi.dropLine(); RECT rc; - rc.left=x; - rc.right=gdi.getWidth()+gdi.scaleLength(5); - rc.top=y; - rc.bottom=gdi.getCY()+gdi.scaleLength(5); + rc.left = x; + rc.right = gdi.getWidth() + gdi.scaleLength(5); + rc.top = y; + rc.bottom = gdi.getCY() + gdi.scaleLength(5); gdi.dropLine(); gdi.addRectangle(rc, colorLightBlue); gdi.scrollToBottom(); @@ -3497,7 +3772,7 @@ pRunner TabSI::getRunnerByIdentifier(int identifier) const { vector runners; oe->autoSynchronizeLists(false); oe->getRunners(0, 0, runners, false); - for ( size_t k = 0; k< runners.size(); k++) { + for (size_t k = 0; k < runners.size(); k++) { if (runners[k]->getRaceNo() == 0) { int i = runners[k]->getRaceIdentifier(); identifierToRunnerId.insert(i, runners[k]->getId()); @@ -3509,22 +3784,23 @@ pRunner TabSI::getRunnerByIdentifier(int identifier) const { return ret; } -bool TabSI::askOverwriteCard(gdioutput &gdi, pRunner r) const { +bool TabSI::askOverwriteCard(gdioutput& gdi, pRunner r) const { return gdi.ask(L"ask:overwriteresult#" + r->getCompleteIdentification()); } -void TabSI::showModeCardData(gdioutput &gdi) { - gdi.disableInput("Interactive", true); - gdi.enableInput("Database", true); - gdi.enableInput("PrintSplits"); - gdi.disableInput("StartInfo", true); - gdi.disableInput("UseManualInput", true); +void TabSI::showModeCardData(gdioutput& gdi) { + // gdi.disableInput("Interactive", true); + // gdi.enableInput("Database", true); + // gdi.enableInput("PrintSplits"); + // gdi.disableInput("StartInfo", true); + // gdi.disableInput("UseManualInput", true); + checkBoxToolBar(gdi, { CheckBox::UseDB, CheckBox::PrintSplits }); gdi.dropLine(0.5); - gdi.fillDown(); + //gdi.fillDown(); gdi.pushX(); - gdi.addStringUT(boldLarge, lang.tl(L"Print card data", true)); - gdi.dropLine(0.2); + //gdi.addStringUT(boldLarge, lang.tl(L"Print card data", true)); + //gdi.dropLine(0.2); gdi.fillRight(); gdi.addButton("ClearMemory", "Clear Memory", SportIdentCB); gdi.addButton("SaveMemory", "Spara...", SportIdentCB); @@ -3543,14 +3819,14 @@ void TabSI::showModeCardData(gdioutput &gdi) { gdi.dropLine(); gdi.popX(); gdi.addString("", 10, "help:analyzecard"); - + gdi.dropLine(3); gdi.popX(); bool first = true; for (auto it = savedCards.begin(); it != savedCards.end(); ++it) { gdi.dropLine(0.5); if (!first) { - RECT rc = {30, gdi.getCY(), gdi.scaleLength(250), gdi.getCY() + 3}; + RECT rc = { 30, gdi.getCY(), gdi.scaleLength(250), gdi.getCY() + 3 }; gdi.addRectangle(rc); } first = false; @@ -3559,11 +3835,11 @@ void TabSI::showModeCardData(gdioutput &gdi) { } } -void TabSI::EditCardData::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { +void TabSI::EditCardData::handle(gdioutput& gdi, BaseInfo& info, GuiEventType type) { if (type == GUI_LINK) { - TextInfo &ti = dynamic_cast(info); + TextInfo& ti = dynamic_cast(info); int cardId = ti.getExtraInt(); - SICard &card = tabSI->getCard(cardId); + SICard& card = tabSI->getCard(cardId); ti.id = "card" + itos(cardId); gdi.removeWidget("CardName"); gdi.removeWidget("ClubName"); @@ -3581,24 +3857,24 @@ void TabSI::EditCardData::handle(gdioutput &gdi, BaseInfo &info, GuiEventType ty if (noClub) club = lang.tl("Klubb"); - InputInfo &ii = gdi.addInput(ti.xp-2, ti.yp-2, "CardName", name, 18, 0); + InputInfo& ii = gdi.addInput(ti.xp - 2, ti.yp - 2, "CardName", name, 18, 0); ii.setHandler(this); - InputInfo &ii2 = gdi.addInput(ti.xp + ii.getWidth(), ti.yp-2, "ClubName", club, 22, 0); + InputInfo& ii2 = gdi.addInput(ti.xp + ii.getWidth(), ti.yp - 2, "ClubName", club, 22, 0); ii2.setExtra(noClub).setHandler(this); - ButtonInfo &bi = gdi.addButton(ii2.getX() + 2 + ii2.getWidth(), ti.yp-4, "OKCard", "OK", 0); + ButtonInfo& bi = gdi.addButton(ii2.getX() + 2 + ii2.getWidth(), ti.yp - 4, "OKCard", "OK", 0); bi.setExtra(cardId).setHandler(this); bi.setDefault(); int w, h; bi.getDimension(gdi, w, h); - gdi.addButton(bi.xp + w + 4, ti.yp-4, "CancelCard", "Avbryt", 0).setCancel().setHandler(this); + gdi.addButton(bi.xp + w + 4, ti.yp - 4, "CancelCard", "Avbryt", 0).setCancel().setHandler(this); gdi.setInputFocus(ii.id, noName); } else if (type == GUI_BUTTON) { - ButtonInfo bi = dynamic_cast(info); + ButtonInfo bi = dynamic_cast(info); //OKCard or CancelCard if (bi.id == "OKCard") { int cardId = bi.getExtraInt(); - SICard &card = tabSI->getCard(cardId); + SICard& card = tabSI->getCard(cardId); wstring name = gdi.getText("CardName"); wstring club = gdi.getBaseInfo("ClubName").getExtra() ? L"" : gdi.getText("ClubName"); wstring given = getGivenName(name); @@ -3606,7 +3882,7 @@ void TabSI::EditCardData::handle(gdioutput &gdi, BaseInfo &info, GuiEventType ty wcsncpy_s(card.firstName, given.c_str(), 20); wcsncpy_s(card.lastName, familty.c_str(), 20); wcsncpy_s(card.club, club.c_str(), 40); - + wstring s = name; if (!club.empty()) s += L", " + club; @@ -3619,7 +3895,7 @@ void TabSI::EditCardData::handle(gdioutput &gdi, BaseInfo &info, GuiEventType ty gdi.removeWidget("CancelCard"); } else if (type == GUI_FOCUS) { - InputInfo &ii = dynamic_cast(info); + InputInfo& ii = dynamic_cast(info); if (ii.getExtraInt()) { ii.setExtra(0); gdi.setInputFocus(ii.id, true); @@ -3627,11 +3903,11 @@ void TabSI::EditCardData::handle(gdioutput &gdi, BaseInfo &info, GuiEventType ty } } -void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, bool forPrinter) const { +void TabSI::printCard(gdioutput& gdi, int lineBreak, int cardId, SICard* crdRef, bool forPrinter) const { if (crdRef == nullptr) crdRef = &getCard(cardId); - - SICard &c = *crdRef; + + SICard& c = *crdRef; if (c.readOutTime[0] == 0) strcpy_s(c.readOutTime, getLocalTimeN().c_str()); @@ -3643,10 +3919,10 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, clubName = c.club; } else if (useDatabase) { - const RunnerWDBEntry *r = oe->getRunnerDatabase().getRunnerByCard(c.CardNumber); + const RunnerWDBEntry* r = oe->getRunnerDatabase().getRunnerByCard(c.CardNumber); if (r) { r->getName(name); - const oClub *club = oe->getRunnerDatabase().getClub(r->dbe().clubNo); + const oClub* club = oe->getRunnerDatabase().getClub(r->dbe().clubNo); if (club) { clubName = club->getName(); wcsncpy_s(c.club, clubName.c_str(), 20); @@ -3665,11 +3941,11 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, if (!name.empty()) { if (!clubName.empty()) - name += L", " + clubName; + name += L", " + clubName; gdi.fillDown(); - auto &res = gdi.addStringUT(0, name); + auto& res = gdi.addStringUT(0, name); if (cardId >= 0) - res.setExtra(cardId).setHandler(&editCardData); + res.setExtra(cardId).setHandler(&editCardData); gdi.popX(); } gdi.fillDown(); @@ -3710,7 +3986,7 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, int xp4 = xp3 + gdi.scaleLength(60); int xp5 = xp4 + gdi.scaleLength(45); const int off = xp5 - xp + gdi.scaleLength(80); - + int baseCY = gdi.getCY(); int maxCY = baseCY; int baseCX = gdi.getCX(); @@ -3724,21 +4000,21 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, for (unsigned k = 0; k < c.nPunch; k++) { int cy = gdi.getCY(); - gdi.addStringUT(cy, xp, 0, itos(k+1) + "."); + gdi.addStringUT(cy, xp, 0, itos(k + 1) + "."); gdi.addStringUT(cy, xp2, 0, itos(c.Punch[k].Code)); - gdi.addStringUT(cy, xp3, 0, formatTimeHMS(c.Punch[k].Time % (24*3600))); + gdi.addStringUT(cy, xp3, 0, formatTimeHMS(c.Punch[k].Time % (24 * timeConstHour))); if (start != NOTIME) { int legTime = analyzePunch(c.Punch[k], start, accTime, days); if (legTime > 0) - gdi.addStringUT(cy, xp5-gdi.scaleLength(10), textRight, formatTime(legTime)); + gdi.addStringUT(cy, xp5 - gdi.scaleLength(10), textRight, formatTime(legTime)); - gdi.addStringUT(cy, xp5 + gdi.scaleLength(40), textRight, formatTime(days*3600*24 + accTime)); + gdi.addStringUT(cy, xp5 + gdi.scaleLength(40), textRight, formatTime(days * timeConstHour * 24 + accTime)); } else { start = c.Punch[k].Time; } - if (lineBreak > 0 && (k%lineBreak) == lineBreak-1) { + if (lineBreak > 0 && (k % lineBreak) == lineBreak - 1) { maxCY = max(maxCY, gdi.getCY()); RECT rcc; rcc.top = baseCY; @@ -3758,14 +4034,14 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, if (c.FinishPunch.Code != -1) { int cy = gdi.getCY(); gdi.addString("", cy, xp, 0, "MÃ¥l"); - gdi.addStringUT(cy, xp3, 0, formatTimeHMS(c.FinishPunch.Time % (24*3600))); + gdi.addStringUT(cy, xp3, 0, formatTimeHMS(c.FinishPunch.Time % (24 * timeConstHour))); if (start != NOTIME) { int legTime = analyzePunch(c.FinishPunch, start, accTime, days); if (legTime > 0) - gdi.addStringUT(cy, xp5-gdi.scaleLength(10), textRight, formatTime(legTime)); + gdi.addStringUT(cy, xp5 - gdi.scaleLength(10), textRight, formatTime(legTime)); - gdi.addStringUT(cy, xp5 + gdi.scaleLength(40), textRight, formatTime(days*3600*24 + accTime)); + gdi.addStringUT(cy, xp5 + gdi.scaleLength(40), textRight, formatTime(days * timeConstHour * 24 + accTime)); } maxCY = max(maxCY, gdi.getCY()); @@ -3775,7 +4051,7 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, gdi.setCX(baseCX); } - gdi.addString("", 1, L"Time: X#" + formatTime(days*3600*24 + accTime)); + gdi.addString("", 1, L"Time: X#" + formatTime(days * timeConstHour * 24 + accTime)); } maxCY = max(maxCY, gdi.getCY()); @@ -3789,27 +4065,27 @@ void TabSI::printCard(gdioutput &gdi, int lineBreak, int cardId, SICard *crdRef, if (forPrinter) { gdi.dropLine(1); - vector< pair > lines; + vector> lines; oe->getExtraLines("SPExtra", lines); for (size_t k = 0; k < lines.size(); k++) { gdi.addStringUT(lines[k].second, lines[k].first); } - if (lines.size()>0) + if (lines.size() > 0) gdi.dropLine(0.5); gdi.addString("", fontSmall, "Av MeOS: www.melin.nu/meos"); } } -int TabSI::analyzePunch(SIPunch &p, int &start, int &accTime, int &days) { +int TabSI::analyzePunch(SIPunch& p, int& start, int& accTime, int& days) { int newAccTime = p.Time - start; if (newAccTime < 0) { - newAccTime += 3600 * 24; - if (accTime > 12 * 3600) + newAccTime += timeConstHour * 24; + if (accTime > 12 * timeConstHour) days++; } - else if (newAccTime < accTime - 12 * 3600) { + else if (newAccTime < accTime - 12 * timeConstHour) { days++; } int legTime = newAccTime - accTime; @@ -3817,17 +4093,17 @@ int TabSI::analyzePunch(SIPunch &p, int &start, int &accTime, int &days) { return legTime; } -void TabSI::generateSplits(int cardId, gdioutput &gdi) { +void TabSI::generateSplits(int cardId, gdioutput& gdi) { gdioutput gdiprint(2.0, gdi.getHWNDTarget(), splitPrinter); printCard(gdiprint, 0, cardId, nullptr, true); printProtected(gdi, gdiprint); } -void TabSI::printProtected(gdioutput &gdi, gdioutput &gdiprint) { +void TabSI::printProtected(gdioutput& gdi, gdioutput& gdiprint) { try { gdiprint.print(splitPrinter, oe, false, true); } - catch (meosException &ex) { + catch (meosException& ex) { DWORD loaded; if (gdi.getData("SIPageLoaded", loaded)) { gdi.dropLine(); @@ -3846,23 +4122,24 @@ void TabSI::printProtected(gdioutput &gdi, gdioutput &gdiprint) { } } -void TabSI::createCompetitionFromCards(gdioutput &gdi) { +void TabSI::createCompetitionFromCards(gdioutput& gdi) { oe->newCompetition(lang.tl(L"Ny tävling")); + oe->loadDefaults(); gdi.setWindowTitle(L""); map hashCount; - vector< pair > cards; - int zeroTime = 3600 * 24; + vector< pair > cards; + int zeroTime = timeConstHour * 24; for (list >::iterator it = savedCards.begin(); it != savedCards.end(); ++it) { size_t hash = 0; if (it->second.StartPunch.Code != -1 && it->second.StartPunch.Time > 0) zeroTime = min(zeroTime, it->second.StartPunch.Time); for (unsigned k = 0; k < it->second.nPunch; k++) { - hash = 997 * hash + (it->second.Punch[k].Code-30); + hash = 997 * hash + (it->second.Punch[k].Code - 30); if (it->second.Punch[k].Code != -1 && it->second.Punch[k].Time > 0) zeroTime = min(zeroTime, it->second.Punch[k].Time); } - pair p(hash, &it->second); + pair p(hash, &it->second); ++hashCount[hash]; cards.push_back(p); } @@ -3872,7 +4149,7 @@ void TabSI::createCompetitionFromCards(gdioutput &gdi) { if (!hashCount.count(cards[k].first)) continue; int count = hashCount[cards[k].first]; - if (count < 5 && count < int(cards.size()) /2) + if (count < 5 && count < int(cards.size()) / 2) continue; pCourse pc = oe->addCourse(lang.tl("Bana ") + itow(++course)); @@ -3905,10 +4182,10 @@ void TabSI::createCompetitionFromCards(gdioutput &gdi) { } // Define a new zero time - zeroTime -= 3600; + zeroTime -= timeConstHour; if (zeroTime < 0) - zeroTime += 3600 * 24; - zeroTime -= zeroTime % 1800; + zeroTime += timeConstHour * 24; + zeroTime -= zeroTime % (timeConstHour / 2); oe->setZeroTime(formatTime(zeroTime), false); // Add competitors @@ -3930,20 +4207,20 @@ void TabSI::createCompetitionFromCards(gdioutput &gdi) { name = lang.tl(L"Bricka X#" + itow(cards[k].second->CardNumber)); oe->addRunner(name, wstring(cards[k].second->club), cls[0]->getId(), - cards[k].second->CardNumber, 0, true); + cards[k].second->CardNumber, L"", true); processInsertCard(*cards[k].second); } } - TabList &tc = dynamic_cast(*gdi.getTabs().get(TListTab)); + TabList& tc = dynamic_cast(*gdi.getTabs().get(TListTab)); tc.loadPage(gdi, "ResultIndividual"); } void TabSI::StoredStartInfo::checkAge() { DWORD t = GetTickCount(); const int minuteLimit = 3; - if (t > age && (t - age) > (1000*60*minuteLimit)) { + if (t > age && (t - age) > (1000 * 60 * minuteLimit)) { clear(); } age = t; @@ -3969,7 +4246,7 @@ void TabSI::clearCompetitionData() { interactiveReadout = oe->getPropertyInt("Interactive", 1) != 0; useDatabase = oe->getPropertyInt("Database", 1) != 0; printSplits = false; - printStartInfo = false; + printStartInfo = false; manualInput = oe->getPropertyInt("ManualInput", 0) == 1; savedCardUniqueId = 1; @@ -3978,34 +4255,33 @@ void TabSI::clearCompetitionData() { warnedClassOutOfMaps.clear(); lockedFunction = false; - allowControl = true; - allowFinish = true; - allowStart = false; - if (mode == ModeCardData) + if (mode == SIMode::ModeCardData) mode = SIMode::ModeReadOut; + readCards.clear(); + logger.reset(); numSavedCardsOnCmpOpen = savedCards.size(); } -SICard &TabSI::getCard(int id) const { +SICard& TabSI::getCard(int id) const { if (id < int(savedCards.size() / 2)) { - for (list< pair >::const_iterator it = savedCards.begin(); it != savedCards.end(); ++it){ - if (it->first==id) - return const_cast(it->second); + for (list< pair >::const_iterator it = savedCards.begin(); it != savedCards.end(); ++it) { + if (it->first == id) + return const_cast(it->second); } } else { - for (list< pair >::const_reverse_iterator it = savedCards.rbegin(); it != savedCards.rend(); ++it){ - if (it->first==id) - return const_cast(it->second); + for (list< pair >::const_reverse_iterator it = savedCards.rbegin(); it != savedCards.rend(); ++it) { + if (it->first == id) + return const_cast(it->second); } } throw meosException("Interal error"); } -bool compareCardNo(const pRunner &r1, const pRunner &r2) { +bool compareCardNo(const pRunner& r1, const pRunner& r2) { int c1 = r1->getCardNo(); int c2 = r2->getCardNo(); if (c1 != c2) @@ -4018,34 +4294,36 @@ bool compareCardNo(const pRunner &r1, const pRunner &r2) { return false; } -wstring TabSI::getCardInfo(bool param, vector &count) const { +wstring TabSI::getCardInfo(bool param, vector& count) const { if (!param) { assert(count.size() == 8); - return L"Totalt antal unika avbockade brickor: X#" + itow(count[CNFCheckedAndUsed] + - count[CNFChecked] + - count[CNFCheckedNotRented] + - count[CNFCheckedRentAndNotRent]); + return L"Totalt antal unika avbockade brickor: X#" + itow(count[CNFCheckedAndUsed] + + count[CNFChecked] + + count[CNFCheckedNotRented] + + count[CNFCheckedRentAndNotRent]); } count.clear(); count.resize(8); - for (map::const_iterator it = checkedCardFlags.begin(); + for (map::const_iterator it = checkedCardFlags.begin(); it != checkedCardFlags.end(); ++it) { - ++count[it->second]; + ++count[it->second]; } - wstring msg = L"Uthyrda: X, Egna: Y, Avbockade uthyrda: Z#" + itow(count[CNFUsed] + count[CNFCheckedAndUsed]) + - L"#" + itow(count[CNFNotRented] + count[CNFCheckedNotRented]) + - L"#" + itow(count[CNFCheckedAndUsed]); + wstring msg = L"Uthyrda: X, Egna: Y, Avbockade uthyrda: Z#" + itow(count[CNFUsed] + count[CNFCheckedAndUsed]) + + L"#" + itow(count[CNFNotRented] + count[CNFCheckedNotRented]) + + L"#" + itow(count[CNFCheckedAndUsed]); return msg; } -void TabSI::showRegisterHiredCards(gdioutput &gdi) { - gdi.disableInput("Interactive"); +void TabSI::showRegisterHiredCards(gdioutput& gdi) { + checkBoxToolBar(gdi, { }); + + /*gdi.disableInput("Interactive"); gdi.disableInput("Database", true); gdi.disableInput("PrintSplits"); gdi.disableInput("UseManualInput"); - + */ gdi.fillDown(); gdi.addString("", 10, "help:registerhiredcards"); @@ -4069,27 +4347,24 @@ void TabSI::showRegisterHiredCards(gdioutput &gdi) { gdi.refresh(); } -void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { +void TabSI::showCheckCardStatus(gdioutput& gdi, const string& cmd) { vector r; const int cx = gdi.getCX(); const int col1 = gdi.scaleLength(50); const int col2 = gdi.scaleLength(200); - + if (cmd == "init") { - gdi.disableInput("Interactive"); - gdi.disableInput("Database"); - gdi.disableInput("PrintSplits"); - gdi.disableInput("UseManualInput"); - - gdi.fillDown(); + checkBoxToolBar(gdi, { }); + + gdi.fillDown(); gdi.addString("", 10, "help:checkcards"); gdi.dropLine(); gdi.fillRight(); gdi.pushX(); gdi.addButton("CCSReport", "Rapport", SportIdentCB); - gdi.addButton("CCSClear", "Nollställ", SportIdentCB, - "Nollställ minnet; markera alla brickor som icke avbockade"); + gdi.addButton("CCSClear", "Nollställ", SportIdentCB, + "Nollställ minnet; markera alla brickor som icke avbockade"); gdi.addButton("CCSPrint", "Skriv ut...", SportIdentCB); gdi.popX(); @@ -4129,10 +4404,10 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { for (size_t k = 0; k < r.size(); k++) { int cno = r[k]->getCardNo(); if (cno == 0 || r[k]->getRaceNo() > 0) - continue; + continue; if (checkedCardFlags[cno] == CNFCheckedRentAndNotRent || - checkedCardFlags[cno] == CNFRentAndNotRent) { + checkedCardFlags[cno] == CNFRentAndNotRent) { int yp = gdi.getCY(); wstring cp = r[k]->getCompleteIdentification(); bool hire = r[k]->isHiredCard(); @@ -4163,7 +4438,7 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { gdi.fillDown(); gdi.dropLine(0.5); showHead = true; - } + } int yp = gdi.getCY(); gdi.addStringUT(yp, cx, 0, itos(++count)); gdi.addStringUT(yp, cx + col1, 0, itos(cno)); @@ -4176,9 +4451,9 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { int s = r[k]->getStartTime(); int f = r[k]->getFinishTime(); - if (s> 0 || f>0) { - cp += L", " + (s>0 ? r[k]->getStartTimeS() : wstring(L"?")) + makeDash(L" - ") - + (f>0 ? r[k]->getFinishTimeS() : wstring(L"?")); + if (s > 0 || f > 0) { + cp += L", " + (s > 0 ? r[k]->getStartTimeS() : wstring(L"?")) + makeDash(L" - ") + + (f > 0 ? r[k]->getFinishTimeS(false, SubSecond::Auto) : wstring(L"?")); } gdi.addStringUT(yp, cx + col2, 0, cp); } @@ -4192,8 +4467,8 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { } else if (cmd == "tickoff") { SICard sic(ConvertedTimeStatus::Hour24); - for (map::const_iterator it = checkedCardFlags.begin(); - it != checkedCardFlags.end(); ++it) { + for (map::const_iterator it = checkedCardFlags.begin(); + it != checkedCardFlags.end(); ++it) { int stat = it->second; if (stat & CNFChecked) { sic.CardNumber = it->first; @@ -4209,12 +4484,12 @@ void TabSI::showCheckCardStatus(gdioutput &gdi, const string &cmd) { class ResetHiredCard : public GuiHandler { - oEvent *oe; - + oEvent* oe; + public: - void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { + void handle(gdioutput& gdi, BaseInfo& info, GuiEventType type) { if (type == GuiEventType::GUI_LINK) { - TextInfo &ti = dynamic_cast(info); + 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); @@ -4225,17 +4500,17 @@ public: } } - ResetHiredCard(oEvent *oe) : oe(oe) {} + ResetHiredCard(oEvent* oe) : oe(oe) {} }; -GuiHandler *TabSI::getResetHiredCardHandler() { +GuiHandler* TabSI::getResetHiredCardHandler() { if (!resetHiredCardHandler) resetHiredCardHandler = make_shared(oe); - - return resetHiredCardHandler.get(); + + return resetHiredCardHandler.get(); } -void TabSI::registerHiredCard(gdioutput &gdi, const SICard &sic) { +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()); @@ -4243,7 +4518,7 @@ void TabSI::registerHiredCard(gdioutput &gdi, const SICard &sic) { gdi.refresh(); } -void TabSI::checkCard(gdioutput &gdi, const SICard &card, bool updateAll) { +void TabSI::checkCard(gdioutput& gdi, const SICard& card, bool updateAll) { bool wasChecked = (checkedCardFlags[card.CardNumber] & CNFChecked) != 0 && updateAll; checkedCardFlags[card.CardNumber] = CardNumberFlags(checkedCardFlags[card.CardNumber] | CNFChecked); @@ -4263,7 +4538,7 @@ void TabSI::checkCard(gdioutput &gdi, const SICard &card, bool updateAll) { gdi.setTextTranslate("CardInfo", getCardInfo(true, count)); gdi.setTextTranslate("CardTicks", getCardInfo(false, count)); } - TextInfo &ti = gdi.addStringUT(cardPosY, cardPosX + cardCurrentCol * cardOffsetX, 0, itos(card.CardNumber)); + TextInfo& ti = gdi.addStringUT(cardPosY, cardPosX + cardCurrentCol * cardOffsetX, 0, itos(card.CardNumber)); if (wasChecked) ti.setColor(colorRed); if (++cardCurrentCol >= cardNumCol) { @@ -4277,7 +4552,7 @@ void TabSI::checkCard(gdioutput &gdi, const SICard &card, bool updateAll) { } } -void TabSI::generatePayModeWidget(gdioutput &gdi) const { +void TabSI::generatePayModeWidget(gdioutput& gdi) const { vector< pair > pm; oe->getPayModes(pm); assert(pm.size() > 0); @@ -4294,10 +4569,10 @@ void TabSI::generatePayModeWidget(gdioutput &gdi) const { } } -bool TabSI::writePayMode(gdioutput &gdi, int amount, oRunner &r) { +bool TabSI::writePayMode(gdioutput& gdi, int amount, oRunner& r) { int paid = 0; bool hasPaid = false; - + if (gdi.hasWidget("PayMode")) hasPaid = gdi.getSelectedItem("PayMode").first != 1000; @@ -4318,13 +4593,13 @@ void TabSI::addToPrintQueue(pRunner r) { printPunchRunnerIdQueue.push_back(make_pair(t, r->getId())); } -bool TabSI::checkpPrintQueue(gdioutput &gdi) { +bool TabSI::checkpPrintQueue(gdioutput& gdi) { if (printPunchRunnerIdQueue.empty()) return false; size_t printLen = oe->getPropertyInt("NumSplitsOnePage", 3); if (printPunchRunnerIdQueue.size() < printLen) { unsigned t = GetTickCount(); - unsigned diff = abs(int(t - printPunchRunnerIdQueue.front().first))/1000; + unsigned diff = abs(int(t - printPunchRunnerIdQueue.front().first)) / 1000; if (diff < (unsigned)oe->getPropertyInt("SplitPrintMaxWait", 60)) return false; // Wait a little longer @@ -4342,27 +4617,31 @@ bool TabSI::checkpPrintQueue(gdioutput &gdi) { } gdiprint.dropLine(4); } - + printProtected(gdi, gdiprint); //gdiprint.print(splitPrinter, oe, false, true); return true; } -void TabSI::printSIInfo(gdioutput &gdi, const wstring &port) const { - vector info; +void TabSI::printSIInfo(gdioutput& gdi, const wstring& port) const { + vector> info; gdi.fillDown(); gSI->getInfoString(port, info); - for (size_t j = 0; j < info.size(); j++) - gdi.addStringUT(0, info[j]); + for (size_t j = 0; j < info.size(); j++) { + if (info[j].first) + gdi.addStringUT(1, info[j].second).setColor(colorDarkRed); + else + gdi.addStringUT(0, info[j].second); + } } -oClub *TabSI::extractClub(gdioutput &gdi) const { - auto &db = oe->getRunnerDatabase(); - oClub *dbClub = nullptr; +oClub* TabSI::extractClub(gdioutput& gdi) const { + auto& db = oe->getRunnerDatabase(); + oClub* dbClub = nullptr; if (gdi.hasWidget("Club")) { int clubId = gdi.getExtraInt("Club"); if (clubId > 0) { - dbClub = db.getClub(clubId-1); + dbClub = db.getClub(clubId - 1); if (dbClub && !stringMatch(dbClub->getName(), gdi.getText("Club"))) dbClub = nullptr; } @@ -4373,14 +4652,14 @@ oClub *TabSI::extractClub(gdioutput &gdi) const { return dbClub; } -RunnerWDBEntry *TabSI::extractRunner(gdioutput &gdi) const { - auto &db = oe->getRunnerDatabase(); +RunnerWDBEntry* TabSI::extractRunner(gdioutput& gdi) const { + auto& db = oe->getRunnerDatabase(); int rId = gdi.getExtraInt("Name"); wstring name = gdi.getText("Name"); - RunnerWDBEntry *dbR = nullptr; + RunnerWDBEntry* dbR = nullptr; if (rId > 0) { - dbR = db.getRunnerByIndex(rId-1); + dbR = db.getRunnerByIndex(rId - 1); if (dbR) { wstring fname = dbR->getFamilyName(); wstring gname = dbR->getGivenName(); @@ -4390,13 +4669,13 @@ RunnerWDBEntry *TabSI::extractRunner(gdioutput &gdi) const { } } if (dbR == nullptr) { - oClub * dbClub = extractClub(gdi); + oClub* dbClub = extractClub(gdi); dbR = db.getRunnerByName(name, dbClub ? dbClub->getId() : 0, 0); } return dbR; } -void TabSI::DirectEntryGUI::updateFees(gdioutput &gdi, const pClass cls, int age) { +void TabSI::DirectEntryGUI::updateFees(gdioutput& gdi, const pClass cls, int age) { int fee = cls->getEntryFee(getLocalDate(), age); auto fees = cls->getAllFees(); gdi.addItem("Fee", fees); @@ -4410,16 +4689,16 @@ void TabSI::DirectEntryGUI::updateFees(gdioutput &gdi, const pClass cls, int age tabSI->updateEntryInfo(gdi); } -void TabSI::DirectEntryGUI::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { +void TabSI::DirectEntryGUI::handle(gdioutput& gdi, BaseInfo& info, GuiEventType type) { if (type == GUI_FOCUS) { - InputInfo &ii = dynamic_cast(info); + InputInfo& ii = dynamic_cast(info); /*if (ii.getExtraInt()) { ii.setExtra(0); gdi.setInputFocus(ii.id, true); }*/ } else if (type == GUI_LISTBOX) { - ListBoxInfo &lbi = dynamic_cast(info); + ListBoxInfo& lbi = dynamic_cast(info); if (lbi.id == "Class") { int clsId = lbi.data; pClass cls = tabSI->oe->getClass(clsId); @@ -4436,16 +4715,16 @@ void TabSI::DirectEntryGUI::handle(gdioutput &gdi, BaseInfo &info, GuiEventType } } else if (type == GUI_COMBOCHANGE) { - ListBoxInfo &combo = dynamic_cast(info); + ListBoxInfo& combo = dynamic_cast(info); bool show = false; if (tabSI->useDatabase && combo.id == "Club" && combo.text.length() > 1) { auto clubs = tabSI->oe->getRunnerDatabase().getClubSuggestions(combo.text, 20); if (!clubs.empty()) { - auto &ac = gdi.addAutoComplete(combo.id); + auto& ac = gdi.addAutoComplete(combo.id); ac.setAutoCompleteHandler(this->tabSI); vector items; for (auto club : clubs) - items.emplace_back(club->getDisplayName(), club->getName(), club->getId()); + items.emplace_back(club->getDisplayName(), -int(items.size()), club->getName(), club->getId()); ac.setData(items); ac.show(); @@ -4457,17 +4736,17 @@ void TabSI::DirectEntryGUI::handle(gdioutput &gdi, BaseInfo &info, GuiEventType } } else if (type == GUI_INPUTCHANGE) { - InputInfo &ii = dynamic_cast(info); + InputInfo& ii = dynamic_cast(info); bool show = false; if (tabSI->showDatabase() && ii.id == "Name") { - auto &db = tabSI->oe->getRunnerDatabase(); + auto& db = tabSI->oe->getRunnerDatabase(); if (ii.text.length() > 1) { auto dbClub = tabSI->extractClub(gdi); auto rw = db.getRunnerSuggestions(ii.text, dbClub ? dbClub->getId() : 0, 10); if (!rw.empty()) { - auto &ac = gdi.addAutoComplete(ii.id); + auto& ac = gdi.addAutoComplete(ii.id); ac.setAutoCompleteHandler(this->tabSI); - + ac.setData(getRunnerAutoCompelete(db, rw, dbClub)); ac.show(); show = true; @@ -4480,7 +4759,7 @@ void TabSI::DirectEntryGUI::handle(gdioutput &gdi, BaseInfo &info, GuiEventType } } -vector TabSI::getRunnerAutoCompelete(RunnerDB &db, const vector< pair> &rw, pClub dbClub) { +vector TabSI::getRunnerAutoCompelete(RunnerDB& db, const vector< pair>& rw, pClub dbClub) { vector items; set used; wstring ns; @@ -4509,7 +4788,7 @@ vector TabSI::getRunnerAutoCompelete(RunnerDB &db, const vec ns += L" (" + itow(getThisYear() - y) + L")"; } - items.emplace_back(ns, r.first->getNameCstr(), r.second); + items.emplace_back(ns, -int(items.size()), r.first->getNameCstr(), r.second); } if (!needRerun) break; @@ -4520,12 +4799,12 @@ vector TabSI::getRunnerAutoCompelete(RunnerDB &db, const vec return items; } -void TabSI::handleAutoComplete(gdioutput &gdi, AutoCompleteInfo &info) { +void TabSI::handleAutoComplete(gdioutput& gdi, AutoCompleteInfo& info) { auto bi = gdi.setText(info.getTarget(), info.getCurrent().c_str()); if (bi) { int ix = info.getCurrentInt(); - - bi->setExtra(ix+1); + + bi->setExtra(ix + 1); if (bi->id == "Name" && ix >= 0) { auto r = oe->getRunnerDatabase().getRunnerByIndex(ix); int year = r ? r->getBirthYear() : 0; @@ -4618,7 +4897,7 @@ void TabSI::playReadoutSound(SND type) { } void TabSI::playSoundResource(int res) const { - PlaySound(MAKEINTRESOURCE(res), GetModuleHandle(nullptr), SND_RESOURCE| SND_ASYNC); + PlaySound(MAKEINTRESOURCE(res), GetModuleHandle(nullptr), SND_RESOURCE | SND_ASYNC); //OutputDebugString((L"Play: " + itow(res)).c_str()); } @@ -4628,15 +4907,15 @@ void TabSI::playSoundFile(const wstring& file) const { //OutputDebugString(file.c_str()); } -void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, - const oCard *oCrd, SICard *siCrd, - const wstring &missingPunchList) { +void TabSI::showReadoutStatus(gdioutput& gdi, const oRunner* r, + const oCard* oCrd, SICard* siCrd, + const wstring& missingPunchList) { gdi.clearPage(false); gdi.hideBackground(true); int w, h; gdi.getTargetDimension(w, h); double minS = min(h / 500.0, w / 700.0); - gdi.scaleSize(minS/gdi.getScale(), false, false); + gdi.scaleSize(minS / gdi.getScale(), false, gdioutput::ScaleOperation::NoRefresh); int mrg = 20; int lh = gdi.getLineHeight(boldHuge, nullptr); @@ -4665,14 +4944,14 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, bt = r->getCard()->isCriticalCardVoltage(); } - if (r->isStatusUnknown(true)) { + if (r->isStatusUnknown(true, true)) { gdi.fillRight(); if (r->getStartTime() > 0) { gdi.addString("", fontMediumPlus, "Start:"); gdi.addStringUT(boldHuge, r->getStartTimeS()); } } - else if (r->isStatusOK(true)) { + else if (r->isStatusOK(true, true)) { bgColor = colorLightGreen; gdi.fillRight(); gdi.addStringUT(boldLarge, r->getClass(true)); @@ -4680,11 +4959,11 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, gdi.addString("", fontLarge, " Start:").setColor(colorGreyBlue); gdi.addStringUT(boldLarge, r->getStartTimeS()); gdi.addString("", fontLarge, " MÃ¥l:").setColor(colorGreyBlue); - gdi.addStringUT(boldLarge, r->getFinishTimeS()); + gdi.addStringUT(boldLarge, r->getFinishTimeS(false, SubSecond::Auto)); gdi.popX(); gdi.dropLine(3); - if (r->getStatusComputed() == StatusNoTiming || + if (r->getStatusComputed(true) == StatusNoTiming || (r->getClassRef(false) && r->getClassRef(true)->getNoTiming())) { gdi.addString("", fontMediumPlus, "Status:").setColor(colorGreyBlue); gdi.fillDown(); @@ -4693,13 +4972,13 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, } else { gdi.addString("", fontMediumPlus, " Tid:").setColor(colorGreyBlue); - bool showPlace = r->getStatusComputed() == StatusOK; + bool showPlace = r->getStatusComputed(true) == StatusOK; if (!showPlace) gdi.fillDown(); wstring ts = getTimeString(r); gdi.addStringUT(boldHuge, ts); - + if (showPlace) { gdi.addString("", fontMediumPlus, " Placering:").setColor(colorGreyBlue); gdi.fillDown(); @@ -4712,7 +4991,7 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, gdi.addString("", fontMediumPlus, " Efter:").setColor(colorGreyBlue); gdi.addStringUT(boldHuge, ta + L" "); } - + int miss = r->getMissedTime(); if (miss > 0) { gdi.addString("", fontMediumPlus, " Bomtid:").setColor(colorGreyBlue); @@ -4729,7 +5008,7 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, gdi.addString("", fontMediumPlus, "Start:").setColor(colorGreyBlue); gdi.addStringUT(boldHuge, r->getStartTimeS()); gdi.addString("", fontMediumPlus, " MÃ¥l:").setColor(colorGreyBlue); - gdi.addStringUT(boldHuge, r->getFinishTimeS()); + gdi.addStringUT(boldHuge, r->getFinishTimeS(false, SubSecond::Auto)); gdi.addString("", fontMediumPlus, " Status:").setColor(colorGreyBlue); gdi.fillDown(); gdi.addString("", boldHuge, r->getStatusS(true, true)); @@ -4748,8 +5027,8 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, rentalCard = true; bgColor = colorLightRed; - gdi.addString("", h / 3, mrg, boldHuge | textCenter, "Okänd bricka", w - 2 * mrg); - gdi.addString("", h / 3 + lh*2, mrg, boldHuge | textCenter, itow(oCrd->getCardNo()), w - 2 * mrg); + gdi.addString("", h / 3, mrg, boldHuge | textCenter, "Okänd bricka", w - 2 * mrg); + gdi.addString("", h / 3 + lh * 2, mrg, boldHuge | textCenter, itow(oCrd->getCardNo()), w - 2 * mrg); cardVoltage = oCrd->getCardVoltage(); bt = oCrd->isCriticalCardVoltage(); @@ -4765,7 +5044,7 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, addAutoClear = true; } else { - gdi.addString("", h / 3, w/2-64, textImage, "513"); + gdi.addString("", h / 3, w / 2 - 64, textImage, "513"); } gdi.dropLine(3); @@ -4781,15 +5060,15 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, if (rentalCard) { gdi.addRectangle(rc, colorYellow); - gdi.addStringUT(rc.top + (rc.bottom- rc.top)/3, mrg, boldHuge | textCenter, - "Vänligen Ã¥terlämna hyrbrickan.", w - 3 * mrg); + gdi.addStringUT(rc.top + (rc.bottom - rc.top) / 3, mrg, boldHuge | textCenter, + "Vänligen Ã¥terlämna hyrbrickan.", w - 3 * mrg); } if (!cardVoltage.empty()) { rc.top = cyBelow; rc.right -= mrg; rc.left += mrg; - rc.bottom = h - mrg*3; + rc.bottom = h - mrg * 3; if (bt == oCard::BatteryStatus::OK) gdi.addRectangle(rc, colorMediumGreen); @@ -4798,8 +5077,8 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, else gdi.addRectangle(rc, colorMediumRed); - gdi.setCX(w/3); - gdi.setCY(min(rc.top + mrg, rc.bottom - gdi.scaleLength(mrg*5))); + gdi.setCX(w / 3); + gdi.setCY(min(rc.top + mrg, rc.bottom - gdi.scaleLength(mrg * 5))); gdi.fillDown(); gdi.pushX(); gdi.addString("", gdi.getCY(), rc.left + mrg, fontMediumPlus, "Batteristatus"); @@ -4820,13 +5099,13 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, } if (addAutoClear) { - TimerInfo &ti = gdi.addTimeoutMilli(20000, "", nullptr); + TimerInfo& ti = gdi.addTimeoutMilli(20000, "", nullptr); class LoadDef : public GuiHandler { public: - TabSI * si; - LoadDef(TabSI *si) : si(si) {} - void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) final { + TabSI* si; + LoadDef(TabSI* si) : si(si) {} + void handle(gdioutput& gdi, BaseInfo& info, GuiEventType type) final { si->showReadoutStatus(gdi, nullptr, nullptr, nullptr, L""); } }; @@ -4836,3 +5115,28 @@ void TabSI::showReadoutStatus(gdioutput &gdi, const oRunner *r, gdi.refresh(); } + +void TabSI::changeMapping(gdioutput& gdi) const { + gdi.addString("", fontMediumPlus, "Kontrollmappning"); + gdi.dropLine(0.5); + + gdi.addString("", 10, "info:mapcontrol"); + gdi.pushX(); + OnlineInput::controlMappingView(gdi, SportIdentCB, 0); + fillMappings(gdi); + + gdi.popX(); + gdi.fillDown(); + gdi.setCY(gdi.getHeight()); + gdi.addButton("CloseMapping", "Stäng", SportIdentCB); + gdi.dropLine(); + gdi.scrollToBottom(); +} + +void TabSI::fillMappings(gdioutput& gdi) const { + gdi.clearList("Mappings"); + auto mapping = getSI(gdi).getSpecialMappings(); + for (auto &mp : mapping) { + gdi.addItem("Mappings", itow(mp.first) + L" \u21A6 " + oPunch::getType(mp.second), mp.first); + } +} diff --git a/code/TabSI.h b/code/TabSI.h index 5159290..ef769c0 100644 --- a/code/TabSI.h +++ b/code/TabSI.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -30,9 +30,9 @@ struct PunchInfo; class csvparser; struct AutoCompleteRecord; -class TabSI : public TabBase, AutoCompleteHandler { +class TabSI final : public TabBase, AutoCompleteHandler { public: - enum SIMode { + enum class SIMode { ModeReadOut, ModeAssignCards, ModeCheckCards, @@ -40,9 +40,15 @@ public: ModeCardData, ModeRegisterCards, }; - + + map modeName; + void setMode(SIMode m) { mode = m; } private: + + + + /** Try to automatcally assign a class to runner (if none is given) Return true if runner has a class on exist */ bool autoAssignClass(pRunner r, const SICard &sic); @@ -57,13 +63,13 @@ private: bool silent=false); bool processUnmatched(gdioutput &gdi, const SICard &csic, bool silent); - void rentCardInfo(gdioutput &gdi, int width); - bool interactiveReadout; bool useDatabase; bool printSplits; bool printStartInfo; bool manualInput; + bool multipleStarts = false; + PrinterObject splitPrinter; list< pair > printPunchRunnerIdQueue; void addToPrintQueue(pRunner r); @@ -97,10 +103,9 @@ private: //Operation mode SIMode mode; bool lockedFunction = false; - bool allowControl = true; - bool allowFinish = true; - bool allowStart = false; + void changeMapping(gdioutput& gdi) const; + void fillMappings(gdioutput& gdi) const; int currentAssignIndex; void printSIInfo(gdioutput &gdi, const wstring &port) const; @@ -219,10 +224,47 @@ private: int readoutFunctionX = 0; int readoutFunctionY = 0; + int optionBarPosY = 0; + int optionBarPosX = 0; + int check_toolbar_xb = 0; + int check_toolbar_yb = 0; + + enum class CheckBox { + Interactive, + UseDB, + PrintSplits, + PrintStart, + Manual, + SeveralTurns, + AutoTie, + AutoTieRent + }; + + void checkBoxToolBar(gdioutput& gdi, const set &items) const; + + void playSoundResource(int res) const; void playSoundFile(const wstring& file) const; + + struct StoredReadout { + wstring info; + wstring warnings; + wstring cardno; + wstring statusline; + vector MP; + GDICOLOR color; + bool rentCard = false; + + RECT computeRC(gdioutput &gdi) const; + void render(gdioutput &gdi, const RECT &rc) const; + static void rentCardInfo(gdioutput &gdi, const RECT &rcIn); + }; + + list readCards; + void renderReadCard(gdioutput &gdi, int maxNumber); + protected: - void clearCompetitionData(); + void clearCompetitionData() final; static wstring getPlace(const oRunner *r); static wstring getTimeString(const oRunner *r); @@ -271,6 +313,8 @@ public: int siCB(gdioutput &gdi, int type, void *data); + void writeDefaultHiredCards(); + void logCard(gdioutput &gdi, const SICard &card); void setCardNumberField(const string &fieldId) {insertCardNumberField=fieldId;} @@ -286,7 +330,7 @@ public: void clearQueue() { CardQueue.clear(); } void refillComPorts(gdioutput &gdi); - bool loadPage(gdioutput &gdi); + bool loadPage(gdioutput &gdi) final; void showReadoutMode(gdioutput & gdi); void showReadoutStatus(gdioutput &gdi, const oRunner *r, diff --git a/code/TabSpeaker.cpp b/code/TabSpeaker.cpp index a5d0ef6..c2ea23d 100644 --- a/code/TabSpeaker.cpp +++ b/code/TabSpeaker.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -176,7 +176,7 @@ int TabSpeaker::processButton(gdioutput &gdi, const ButtonInfo &bu) gdi.fillDown(); vector< pair > d; - oe->fillControls(d, oEvent::CTCourseControl); + oe->fillControls(d, oEvent::ControlType::CourseControl); gdi.addItem("Controls", d); gdi.setSelection("Controls", controlsToWatch); @@ -382,7 +382,7 @@ int TabSpeaker::processButton(gdioutput &gdi, const ButtonInfo &bu) controlsToWatch.insert(-2); // Non empty but no control for (set::iterator it=controlsToWatch.begin();it!=controlsToWatch.end();++it) { - pControl pc=oe->getControl(*it, false); + pControl pc=oe->getControl(*it, false, false); if (pc) { pc->setRadio(true); pc->synchronize(true); @@ -722,7 +722,7 @@ void TabSpeaker::splitAnalysis(gdioutput &gdi, int xp, int yp, pRunner r) else first = false; - timeloss += pc->getControlOrdinal(j) + L". " + formatTime(delta[j]); + timeloss += pc->getControlOrdinal(j) + L". " + formatTime(delta[j], SubSecond::Auto); } if (timeloss.length() > charlimit || (!timeloss.empty() && !first && j+1 == delta.size())) { gdi.addStringUT(yp, xp, 0, timeloss).setColor(colorDarkRed); @@ -1223,7 +1223,7 @@ void TabSpeaker::storeManualTime(gdioutput &gdi) throw std::exception(bf); } - oe->addFreePunch(itime, punch, sino, true); + oe->addFreePunch(itime, punch, 0, sino, true); gdi.restore("manual", false); gdi.addString("", 0, L"Löpare: X, kontroll: Y, kl Z#" + Name + L"#" + oPunch::getType(punch) + L"#" + oe->getAbsTime(itime)); @@ -1432,7 +1432,7 @@ void TabSpeaker::loadSettings(vector< multimap > &settings) { xmlList allS; s.getObjects(allS); for (auto &prop : allS) { - settings.back().insert(make_pair(prop.getName(), prop.getw())); + settings.back().insert(make_pair(prop.getName(), prop.getWStr())); } } } diff --git a/code/TabSpeaker.h b/code/TabSpeaker.h index a880975..aeade65 100644 --- a/code/TabSpeaker.h +++ b/code/TabSpeaker.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/TabTeam.cpp b/code/TabTeam.cpp index 74f801b..b6edc48 100644 --- a/code/TabTeam.cpp +++ b/code/TabTeam.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -274,9 +274,9 @@ void TabTeam::updateTeamStatus(gdioutput &gdi, pTeam t) } gdi.setText("Start", t->getStartTimeS()); - gdi.setText("Finish",t->getFinishTimeS()); - gdi.setText("Time", t->getRunningTimeS(true)); - gdi.setText("TimeAdjust", getTimeMS(t->getTimeAdjustment())); + gdi.setText("Finish",t->getFinishTimeS(false, SubSecond::Auto)); + gdi.setText("Time", t->getRunningTimeS(true, SubSecond::Auto)); + gdi.setText("TimeAdjust", formatTimeMS(t->getTimeAdjustment(false), false, SubSecond::Auto)); gdi.setText("PointAdjust", -t->getPointAdjustment()); gdi.selectItemByData("Status", t->getStatus()); @@ -474,7 +474,7 @@ bool TabTeam::save(gdioutput &gdi, bool dontReloadTeams) { } } else - r = oe->addRunner(name, t->getClubId(), t->getClassId(false), cardNo, 0, false); + r = oe->addRunner(name, t->getClubId(), t->getClassId(false), cardNo, L"", false); r->setName(name, true); r->setCardNo(cardNo, true); @@ -659,7 +659,7 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) bool rent = gdi.isChecked("DirRent"); if (r == 0) { - r = oe->addRunner(name, clb ? clb->getId() : t->getClubId(), t->getClassId(false), card, 0, false); + r = oe->addRunner(name, clb ? clb->getId() : t->getClubId(), t->getClassId(false), card, L"", false); } if (rent) r->getDI().setInt("CardFee", oe->getBaseCardFee()); @@ -728,7 +728,7 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) pTeam t = oe->getTeam(teamId); if (!t || !t->getClassRef(false)) return 0; - + t->synchronize(); pClass pc = t->getClassRef(false); int nf = pc->getNumForks(); ListBoxInfo lbi; @@ -745,7 +745,13 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) continue; int currentKey = max(newSno-1, 0) % nf; - if (currentKey == lbi.data) { + if (currentKey == lbi.data) { + for (int j = 0; j < t->getNumRunners(); j++) { + if (t->getRunner(j)) { + t->getRunner(j)->setCourseId(0); + t->synchronize(true); + } + } t->setStartNo(newSno, oBase::ChangeType::Update); t->synchronize(true); t->evaluate(oBase::ChangeType::Update); @@ -1163,6 +1169,9 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) } break; } + else if (ii.id == "R" + itos(i)) { + enableRunner(gdi, i, !ii.text.empty()); + } } } @@ -1177,7 +1186,7 @@ int TabTeam::teamCB(gdioutput &gdi, int type, void *data) ac.setAutoCompleteHandler(this); vector items; for (auto club : clubs) - items.emplace_back(club->getDisplayName(), club->getName(), club->getId()); + items.emplace_back(club->getDisplayName(), -int(items.size()), club->getName(), club->getId()); ac.setData(items); ac.show(); @@ -1228,32 +1237,37 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) int yp = gdi.getCY(); int numberPos = xp; xp += gdi.scaleLength(25); - int dx[6] = {0, 184, 220, 290, 316, 364}; - dx[1] = gdi.getInputDimension(18).first + gdi.scaleLength(4); - for (int i = 2; i<6; i++) - dx[i] = dx[1] + gdi.scaleLength(dx[i]-188); + const int dxIn[6] = {0, 184, 220, 300, 326, 374}; + int dx[6]; + dx[0] = 0; + for (int i = 1; i < 6; i++) { + if (i == 1) + dx[i] = gdi.getInputDimension(18).first + gdi.scaleLength(4); + else if (i == 3) + dx[i] = dx[i-1] + gdi.getInputDimension(7).first + gdi.scaleLength(4); + else + dx[i] = dx[i-1] + gdi.scaleLength(dxIn[i] - dxIn[i-1]); + } gdi.addString("", yp, xp + dx[0], 0, "Namn:"); gdi.addString("", yp, xp + dx[2], 0, "Bricka:"); - gdi.addString("", yp, xp + dx[3], 0, "Hyrd:"); + gdi.addString("", yp, xp + dx[3]- gdi.scaleLength(5), 0, "Hyrd:"); gdi.addString("", yp, xp + dx[5], 0, "Status:"); gdi.dropLine(0.5); - + const int textOffY = gdi.scaleLength(4); for (unsigned i = 0; i < pc->getNumStages(); i++) { yp = gdi.getCY() - gdi.scaleLength(3); sprintf_s(bf, "R%d", i); gdi.pushX(); bool hasSI = false; - gdi.addStringUT(yp, numberPos, 0, pc->getLegNumber(i) + L"."); + gdi.addStringUT(yp + textOffY, numberPos, 0, pc->getLegNumber(i) + L"."); if (pc->getLegRunner(i) == i) { - gdi.addInput(xp + dx[0], yp, bf, L"", 18, TeamCB);//Name gdi.addButton(xp + dx[1], yp - 2, gdi.scaleLength(28), "DR" + itos(i), "<>", TeamCB, "Knyt löpare till sträckan.", false, false); // Change sprintf_s(bf_si, "SI%d", i); hasSI = true; - gdi.addInput(xp + dx[2], yp, bf_si, L"", 5, TeamCB).setExtra(i); //Si - + gdi.addInput(xp + dx[2], yp, bf_si, L"", 7, TeamCB).setExtra(i); //Si gdi.addCheckbox(xp + dx[3], yp + gdi.scaleLength(10), "RENT" + itos(i), "", 0, false); //Rentcard } else { @@ -1262,16 +1276,17 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) } gdi.addButton(xp + dx[4], yp - 2, gdi.scaleLength(38), "MR" + itos(i), "...", TeamCB, "Redigera deltagaren.", false, false); // Change - gdi.addString(("STATUS" + itos(i)).c_str(), yp + gdi.scaleLength(5), xp + dx[5], 0, "#MMMMMMMMMMMMMMMM"); + gdi.addString(("STATUS" + itos(i)).c_str(), yp + textOffY, xp + dx[5], 0, "#MMMMMMMMMMMMMMMM"); gdi.setText("STATUS" + itos(i), L"", false); gdi.dropLine(0.5); gdi.popX(); if (t) { pRunner r = t->getRunner(i); + enableRunner(gdi, i, r != nullptr); if (r) { gdi.setText(bf, r->getNameRaw())->setExtra(r->getId()); - + r->getPlace(true); // Ensure computed status is up-to-date if (hasSI) { int cno = r->getCardNo(); gdi.setText(bf_si, cno > 0 ? itow(cno) : L""); @@ -1279,13 +1294,13 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) gdi.check("RENT" + itos(i), r->getDCI().getInt("CardFee") != 0); } string sid = "STATUS" + itos(i); - if (r->statusOK(true)) { - TextInfo * ti = (TextInfo *)gdi.setText(sid, L"OK, " + r->getRunningTimeS(true), false); + if (r->statusOK(true, true)) { + TextInfo * ti = (TextInfo *)gdi.setText(sid, L"OK, " + r->getRunningTimeS(true, SubSecond::Auto), false); if (ti) ti->setColor(colorGreen); } - else if (r->getStatusComputed() != StatusUnknown) { - TextInfo * ti = (TextInfo *)gdi.setText(sid, r->getStatusS(false, true) + L", " + r->getRunningTimeS(true), false); + else if (r->getStatusComputed(true) != StatusUnknown) { + TextInfo * ti = (TextInfo *)gdi.setText(sid, r->getStatusS(false, true) + L", " + r->getRunningTimeS(true, SubSecond::Auto), false); if (ti) ti->setColor(colorRed); } @@ -1334,6 +1349,15 @@ void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t) gdi.refresh(); } +void TabTeam::enableRunner(gdioutput& gdi, int index, bool enable) { + string ix = itos(index); + string si = ("SI" + ix); + bool hasSI = enable || !gdi.getText(si, true).empty(); + gdi.setInputStatus(si.c_str(), hasSI, true); + gdi.setInputStatus(("RENT" + ix).c_str(), hasSI, true); + gdi.setInputStatus(("MR" + ix).c_str(), enable, true); +} + bool TabTeam::loadPage(gdioutput &gdi, int id) { teamId = id; return loadPage(gdi); @@ -1457,12 +1481,13 @@ bool TabTeam::loadPage(gdioutput &gdi) gdi.popX(); gdi.selectItemByData("Status", 0); - gdi.addString("TeamInfo", 0, "").setColor(colorRed); gdi.dropLine(0.4); if (oe->hasAnyRestartTime()) { gdi.addCheckbox("NoRestart", "Förhindra omstart", 0, false, "Förhindra att laget deltar i nÃ¥gon omstart"); } + + gdi.addString("TeamInfo", 0, " ").setColor(colorRed); gdi.dropLine(1.5); const bool multiDay = oe->hasPrevStage(); @@ -1694,7 +1719,7 @@ void TabTeam::saveTeamImport(gdioutput &gdi, bool useExisting) { } } else { - r = oe->addRunner(member.name, member.club, 0, member.cardNo, 0, false); + r = oe->addRunner(member.name, member.club, 0, member.cardNo, L"", false); if (r && !member.course.empty()) { pCourse pc = oe->getCourse(member.course); @@ -1783,7 +1808,7 @@ void TabTeam::doAddTeamMembers(gdioutput &gdi) { continue; pRunner r = 0; if (withFee) { - r = oe->addRunner(nn, mt->getClubId(), 0, 0, 0, false); + r = oe->addRunner(nn, mt->getClubId(), 0, 0, L"", false); r->synchronize(); mt->setRunner(j, r, false); r->addClassDefaultFee(true); @@ -1849,7 +1874,7 @@ void TabTeam::showRunners(gdioutput &gdi, const char *title, void TabTeam::processChangeRunner(gdioutput &gdi, pTeam t, int leg, pRunner r) { if (r && t && leg < t->getNumRunners()) { pRunner oldR = t->getRunner(leg); - gdioutput::AskAnswer ans = gdioutput::AnswerNo; + gdioutput::AskAnswer ans = gdioutput::AskAnswer::AnswerNo; if (r == oldR) { gdi.restore("SelectR"); return; @@ -1868,9 +1893,9 @@ void TabTeam::processChangeRunner(gdioutput &gdi, pTeam t, int leg, pRunner r) { ans = gdi.askCancel(L"Vill du att X gÃ¥r in i laget?#" + r->getName()); } - if (ans == gdioutput::AnswerNo) + if (ans == gdioutput::AskAnswer::AnswerNo) return; - else if (ans == gdioutput::AnswerCancel) { + else if (ans == gdioutput::AskAnswer::AnswerCancel) { gdi.restore("SelectR"); return; } diff --git a/code/TabTeam.h b/code/TabTeam.h index 64a1027..51d9f26 100644 --- a/code/TabTeam.h +++ b/code/TabTeam.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -74,6 +74,10 @@ private: void switchRunners(pTeam team, int leg, pRunner r, pRunner oldR); + /** Enable or disable edit for a team runner*/ + void enableRunner(gdioutput &gdi, int index, bool enable); + + protected: void clearCompetitionData(); diff --git a/code/Table.cpp b/code/Table.cpp index 4317919..90628f7 100644 --- a/code/Table.cpp +++ b/code/Table.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -278,7 +278,8 @@ void Table::filter(int col, const wstring &filt, bool forceFilter) sortIndex.resize(2); for (size_t k=2;k int { + int ix = 0; + int out = 0; + while (ix < size && data[ix] >= '0' && data[ix] <= '9') { + out = (out << 1) + (out << 3) + data[ix] - '0'; + ix++; + } + while (data[ix] && (data[ix] == ' ' || data[ix] == '-' || data[ix] == ':')) { + ix++; + } + next = data + ix; + return out; + }; + //const char *ptr=s.c_str(); //sscanf(s.c_str(), "%4hd%2hd%2hd%2hd%2hd%2hd", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); - st.wYear=atoi(s.substr(0, 4).c_str()); + /*st.wYear = atoi(s.substr(0, 4).c_str()); st.wMonth=atoi(s.substr(4, 2).c_str()); st.wDay=atoi(s.substr(6, 2).c_str()); st.wHour=atoi(s.substr(8, 2).c_str()); st.wMinute=atoi(s.substr(10, 2).c_str()); st.wSecond=atoi(s.substr(12, 2).c_str()); + */ + const char* ptr = s.data(); + st.wYear = parse(ptr, 4, ptr); + st.wMonth = parse(ptr, 2, ptr); + st.wDay = parse(ptr, 2, ptr); + st.wHour = parse(ptr, 2, ptr); + st.wMinute = parse(ptr, 2, ptr); + st.wSecond = parse(ptr, 2, ptr); FILETIME ft; SystemTimeToFileTime(&st, &ft); __int64 ¤ttime=*(__int64*)&ft; - Time = unsigned((currenttime/10000000)-minYearConstant*365*24*3600); + Time = unsigned((currenttime/10000000)-minYearConstant*365*24* timeConstSecPerHour); } diff --git a/code/TimeStamp.h b/code/TimeStamp.h index f8ede9a..ec3b2e9 100644 --- a/code/TimeStamp.h +++ b/code/TimeStamp.h @@ -10,7 +10,7 @@ #endif // _MSC_VER > 1000 /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -39,6 +39,8 @@ public: const string &getStamp() const; const string &getStamp(const string &sqlStampIn) const; + const wstring getUpdateTime() const; + wstring getStampString() const; string getStampStringN() const; int getAge() const; diff --git a/code/animationdata.cpp b/code/animationdata.cpp index 5e27476..37ab197 100644 --- a/code/animationdata.cpp +++ b/code/animationdata.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 @@ -42,7 +42,7 @@ AnimationData::AnimationData(gdioutput &gdi, int timePerPage, int nCol, double w = (gdi.getWidth() + 20) *nCol + margin; double s = width / w; if ((fabs(s - 1.0) > 1e-3)) { - gdi.scaleSize(s, true, false); + gdi.scaleSize(s, true, gdioutput::ScaleOperation::NoRefresh); } pageInfo.topMargin = 20; pageInfo.scaleX = 1.0f; diff --git a/code/animationdata.h b/code/animationdata.h index 81cc60a..eab4b8a 100644 --- a/code/animationdata.h +++ b/code/animationdata.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 diff --git a/code/autocomplete.cpp b/code/autocomplete.cpp index 622427b..04b3113 100644 --- a/code/autocomplete.cpp +++ b/code/autocomplete.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 @@ -176,7 +176,10 @@ void AutoCompleteInfo::enter() { } } -void AutoCompleteInfo::setData(const vector &items) { +void AutoCompleteInfo::setData(const vector &itemsIn) { + vector items = itemsIn; + stable_sort(items.begin(), items.end()); + int newDataIx = -1; if (modifedAutoComplete && size_t(currentIx) < data.size()) { for (size_t k = 0; k < items.size(); k++) { @@ -188,6 +191,6 @@ void AutoCompleteInfo::setData(const vector &items) { newDataIx = 0; modifedAutoComplete = false; } - data = items; + data = std::move(items); currentIx = newDataIx; } diff --git a/code/autocomplete.h b/code/autocomplete.h index ca5eb62..103a160 100644 --- a/code/autocomplete.h +++ b/code/autocomplete.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -29,10 +29,17 @@ class AutoCompleteHandler; struct AutoCompleteRecord { AutoCompleteRecord() : id(-1) {} - AutoCompleteRecord(const wstring &display, const wstring &name, int id) : display(display), name(name), id(id) {} + AutoCompleteRecord(const wstring &display, int prio, const wstring &name, int id) : display(display), prio(prio), name(name), id(id) {} wstring display; wstring name; int id; + int prio = 0; + bool operator<(const AutoCompleteRecord& rec) const { + if (prio != rec.prio) + return prio > rec.prio; + else + return display < rec.display; + } }; class AutoCompleteInfo { diff --git a/code/autocompletehandler.h b/code/autocompletehandler.h index d398c36..a34a3b0 100644 --- a/code/autocompletehandler.h +++ b/code/autocompletehandler.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/autotask.cpp b/code/autotask.cpp index 7335f2c..ed8f0e6 100644 --- a/code/autotask.cpp +++ b/code/autotask.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/autotask.h b/code/autotask.h index eae701c..0943c1c 100644 --- a/code/autotask.h +++ b/code/autotask.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/binencoder.cpp b/code/binencoder.cpp new file mode 100644 index 0000000..0ebd2ab --- /dev/null +++ b/code/binencoder.cpp @@ -0,0 +1,204 @@ +/************************************************************************ + 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 "meos_util.h" +#include +#include +#include "binencoder.h" + +Encoder92::Encoder92() { + int d = 32; + fill(reverse_table, reverse_table + 128, 0); + fill(used, used + 128, false); + for (int j = 0; j < 92; j++) { + table[j] = d++; + while (d == '"' || d == '<' || d == '&' || d=='>') + d++; + } + table[91] = '\t'; + swap(table[0], table[1]); + + for (int j = 0; j < 92; j++) { + reverse_table[table[j]] = j; + used[table[j]] = true; + } +} + +void Encoder92::encode92(const uint8_t datain[13], uint8_t dataout[16]) { + uint64_t w64bitA = *(uint64_t*)&datain[0]; + uint32_t w32bitB = *(uint32_t*)&datain[8]; + uint32_t w8bitC = datain[12]; + + // Consume 60 bits of data from w64bitA (4 bits will remain) + for (int i = 0; i < 10; i++) { + dataout[i] = w64bitA & 0b00111111; + w64bitA = w64bitA >> 6; + } + + // Consume 30 bits of data from w32bitB (2 bits will remain) + for (int i = 10; i < 15; i++) { + dataout[i] = w32bitB & 0b00111111; + w32bitB = w32bitB >> 6; + } + + // Consume remaining 2 + 4 bits + dataout[15] = int8_t(w32bitB | (w64bitA << 2)); + + for (int i = 0; i < 16; i+=2) { + int extraData = (w8bitC & 0x1); + w8bitC = w8bitC >> 1; + int combined = (extraData << 12) | dataout[i] | (dataout[i+1] << 6); // 13 bits 0 -- 8192 + dataout[i] = combined % 92; + dataout[i+1] = combined / 92; + } +} + +void Encoder92::decode92(const uint8_t datain[16], uint8_t dataout[13]) { + uint8_t datain_64[16]; + uint32_t w8bitC = 0; + + for (int i = 0; i < 16; i += 2) { + int combined = datain[i] + datain[i + 1] * 92; + datain_64[i] = combined & 0b00111111; + combined = combined >> 6; + datain_64[i+1] = combined & 0b00111111; + combined = combined >> 6; + w8bitC |= (combined << (i/2)); + } + + uint64_t w64bitA = 0; + uint32_t w32bitB = 0; + + // Reconstruct 60 bits of data to w64bitA + for (int i = 9; i >= 0; i--) { + w64bitA <<= 6; + w64bitA |= datain_64[i]; + } + + // Reconstruct 30 bits of data to w32bitB + for (int i = 14; i >= 10; i--) { + w32bitB <<= 6; + w32bitB |= datain_64[i]; + } + + // Add remaining bits + w32bitB = w32bitB | ((datain_64[15] & 0b11) << 30); + uint64_t highBits = (datain_64[15] >> 2) & 0b1111; + w64bitA = w64bitA | (highBits << 60ll); + + *((uint64_t*)&dataout[0]) = w64bitA; + *((uint32_t*)&dataout[8]) = w32bitB; + dataout[12] = int8_t(w8bitC); +} + + +void Encoder92::encode92(const vector& bytesIn, string& encodedString) { + int m13 = bytesIn.size()%13; + int extra = m13 > 0 ? 13 - m13 : 0; + int blocks = (bytesIn.size() + extra) / 13; + + encodedString = itos(bytesIn.size()) + ":"; + int outLen = encodedString.length(); + encodedString.resize(encodedString.size() + blocks * 16, ' '); + const uint8_t* inPtr = bytesIn.data(); + //uint8_t* outPtr = encodedString.data() + outLen; + uint8_t datain[13]; + fill(datain, datain + 13, 0); + uint8_t dataout[16]; + int fullBlocks = blocks; + if (extra > 0) + fullBlocks--; + + for (int i = 0; i < fullBlocks; i++) { + //for (int j = 0; j < 13; j++) + encode92(inPtr, dataout); + inPtr += 13; + for (int j = 0; j < 16; j++) + encodedString[outLen++] = table[dataout[j]]; + } + + if (extra > 0) { + for (int j = 0; j < m13; j++) { + datain[j] = inPtr[j]; + } + encode92(datain, dataout); + for (int j = 0; j < 16; j++) + encodedString[outLen++] = table[dataout[j]]; + } +} + +void Encoder92::decode92(const string& encodedString, vector& bytesOut) { + bytesOut.clear(); + if (encodedString.empty()) + return; + + unsigned size = atoi(encodedString.c_str()); + string start = itos(size); + int len = start.length(); + if (encodedString.size() < len || encodedString[len] != ':' || size<0) + throw std::exception("Invalid data"); + + int m13 = size % 13; + int extra = m13 > 0 ? 13 - m13 : 0; + int blocks = (size + extra) / 13; + + int inDataSize = blocks * 16; + if (encodedString.size() < len + 1 + inDataSize) + throw std::exception("Invalid data"); + + bytesOut.resize(size); + auto outPtr = bytesOut.data(); + + uint8_t datain[16]; + uint8_t dataout[13]; + int fullBlocks = blocks; + if (extra > 0) + fullBlocks--; + + const char *inPtr = encodedString.c_str() + len + 1; + + for (int i = 0; i < fullBlocks; i++) { + for (int j = 0; j < 16; j++) { + int v = inPtr[j]; + if (v < 0 || v > 127 || !used[v]) + throw std::exception("Invalid data"); + datain[j] = reverse_table[inPtr[j]]; + } + decode92(datain, outPtr); + inPtr += 16; + outPtr += 13; + } + + if (extra > 0) { + for (int j = 0; j < 16; j++) { + int v = inPtr[j]; + if (v < 0 || v > 127 || !used[v]) + throw std::exception("Invalid data"); + datain[j] = reverse_table[inPtr[j]]; + } + decode92(datain, dataout); + for (int j = 0; j < m13; j++) + outPtr[j] = dataout[j]; + } +} + diff --git a/code/binencoder.h b/code/binencoder.h new file mode 100644 index 0000000..2acd2f2 --- /dev/null +++ b/code/binencoder.h @@ -0,0 +1,39 @@ +#pragma once + +/************************************************************************ + 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 + +class Encoder92 { + int8_t table[92]; + int8_t reverse_table[128]; + bool used[128]; +public: + Encoder92(); + + void encode92(const uint8_t datain[13], uint8_t dataout[16]); + void decode92(const uint8_t datain[16], uint8_t dataout[13]); + + void encode92(const vector& bytesIn, string& encodedString); + void decode92(const string& encodedString, vector& bytesOut); +}; \ No newline at end of file diff --git a/code/classconfiginfo.cpp b/code/classconfiginfo.cpp index 65f2371..1122965 100644 --- a/code/classconfiginfo.cpp +++ b/code/classconfiginfo.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -220,7 +220,7 @@ void oEvent::getClassConfigurationInfo(ClassConfigInfo &cnf) const for (size_t k = 0; k < it->getNumStages(); k++) { StartTypes st = it->getStartType(k); - if (st == STDrawn || st == STHunting) { + if (st == STDrawn || st == STPursuit) { cnf.legNStart[k].push_back(it->getId()); if (cnf.timeStart.size() <= k) cnf.timeStart.resize(k+1); @@ -243,7 +243,7 @@ void oEvent::getClassConfigurationInfo(ClassConfigInfo &cnf) const for (size_t k = 0; k < it->getNumStages(); k++) { StartTypes st = it->getStartType(k); - if (st == STDrawn || st == STHunting) { + if (st == STDrawn || st == STPursuit) { cnf.raceNStart[k].push_back(it->getId()); if (cnf.timeStart.size() <= k) cnf.timeStart.resize(k+1); diff --git a/code/classconfiginfo.h b/code/classconfiginfo.h index 77d1b07..5dac208 100644 --- a/code/classconfiginfo.h +++ b/code/classconfiginfo.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/csvparser.cpp b/code/csvparser.cpp index f2481de..22203bb 100644 --- a/code/csvparser.cpp +++ b/code/csvparser.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -189,11 +189,10 @@ bool csvparser::importOS_CSV(oEvent &event, const wstring &file) //Import runners! int runner=0; while( (rindex+OSRrentcard)0 ){ - int year = extendYear(wtoi(sp[rindex+OSRyb])); int cardNo = wtoi(sp[rindex+OSRcard]); wstring sname = sp[rindex+OSRsname] + L", " + sp[rindex+OSRfname]; pRunner r = event.addRunner(sname, ClubId, - ClassId, cardNo, year, false); + ClassId, cardNo, sp[rindex + OSRyb], false); r->setEntrySource(externalSourceId); oDataInterface DI=r->getDI(); @@ -697,10 +696,10 @@ bool csvparser::importRAID(oEvent &event, const wstring &file) } //Import runners! - pRunner r1=event.addRunner(sp[RAIDrunner1], ClubId, ClassId, 0, 0, false); + pRunner r1=event.addRunner(sp[RAIDrunner1], ClubId, ClassId, 0, L"", false); team->setRunner(0, r1, false); - pRunner r2=event.addRunner(sp[RAIDrunner2], ClubId, ClassId, 0, 0, false); + pRunner r2=event.addRunner(sp[RAIDrunner2], ClubId, ClassId, 0, L"", false); team->setRunner(1, r2, false); team->evaluate(oBase::ChangeType::Update); @@ -812,15 +811,16 @@ bool csvparser::importPunches(const oEvent &oe, const wstring &file, vector 0) { - int card = wtoi(sp[cardIndex]); - int time = oe.getRelativeTime(processedTime); + const int card = wtoi(sp[cardIndex]); + const int time = oe.getRelativeTime(processedTime); if (card>0) { PunchInfo pi; pi.card = card; pi.time = time; - string pd(processedDate.begin(), processedDate.end()); - strncpy_s(pi.date, sizeof(pi.date), pd.c_str(), 26); + //string pd(processedDate.begin(), processedDate.end()); + string pd = gdioutput::narrow(processedDate); + strncpy_s(pi.date, pd.c_str(), 26); pi.date[26] = 0; punches.push_back(pi); nimport++; @@ -1204,7 +1204,7 @@ void csvparser::parse(const wstring &file, list< vector > &data) { string rbf; if (!fin.good()) - throw meosException("Failed to read file"); + throw meosException(L"Failed to read file, " + file); bool isUTF8 = false; bool firstLine = true; diff --git a/code/csvparser.h b/code/csvparser.h index cd51110..e883de6 100644 --- a/code/csvparser.h +++ b/code/csvparser.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/download.cpp b/code/download.cpp index dc63882..894fbfd 100644 --- a/code/download.cpp +++ b/code/download.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/download.h b/code/download.h index a2389f0..216079a 100644 --- a/code/download.h +++ b/code/download.h @@ -7,7 +7,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/english.lng b/code/english.lng index 6619268..a405682 100644 --- a/code/english.lng +++ b/code/english.lng @@ -162,10 +162,11 @@ ClassResultFraction = Fraction of class complete ClassStartName = Start name ClassStartTime = Class, start time, name ClassStartTimeClub = Class, start time, club +ClubClassStartTime = Club, class, start time ClassTotalResult = Class, total result Club = Club ClubName = Club -ClubRunner = Club (competitor) +ClubRunner = Club (Competitors) CmpDate = Competition date CmpName = Competition name CourseClimb = Course climb @@ -282,7 +283,7 @@ FilterHasCard = With card FilterNoCard = Without card FilterNotVacant = Not vacant FilterOnlyVacant = Only vacant -FilterRentCard = Borrowed card +FilterRentCard = Hired card FilterResult = With result FilterStarted = Has started Filtrering = Filtering @@ -357,10 +358,10 @@ Hjälp = Help Hoppar över stafettklass: X = Skipping relay class: X Huvudlista = Main list Hyravgift = Card hire -Hyrbricka = Borrowed card -Hyrbricksrapport = Report with Borrowed Cards -Hyrbricksrapport - %s = Borrowed Cards - %s -Hyrd = Borrowed +Hyrbricka = Hired card +Hyrbricksrapport = Report with Hired Cards +Hyrbricksrapport - %s = Hired Cards - %s +Hyrd = Hired Hämta (efter)anmälningar frÃ¥n Eventor = Fetch (late) entries from Eventor Hämta data frÃ¥n Eventor = Fetch Data from Eventor Hämta efteranmälningar = Fetch Late Entries @@ -393,7 +394,7 @@ Importera = Import Importera IOF (xml) = Import IOF (xml) Importera anmälningar = Import Entries Importera banor = Import Courses -Importera banor/klasser = Import courses/classes +Importera banor/klasser = Import Courses and Classes Importera en tävling frÃ¥n fil = Import competition from file Importera fil = Import File Importera frÃ¥n OCAD = Import from OCAD @@ -1041,7 +1042,7 @@ TeamClub = Team's club TeamLegTimeAfter = Team's time behind on leg TeamLegTimeStatus = Team's time / status on leg TeamName = Team name -TeamPlace = Team's place +TeamPlace = Team's place after leg TeamRogainingPoint = Team's rogaining points TeamRunner = Name of team member TeamRunnerCard = Card number of team member @@ -1242,7 +1243,7 @@ Välj vilka kolumner du vill visa = Choose columns to show Välj vy = Choose view Välkommen till MeOS = Welcome to MeOS Vänligen betala senast = Please pay at latest -Vänligen Ã¥terlämna hyrbrickan = Please return your borrowed card +Vänligen Ã¥terlämna hyrbrickan = Please return your hired card Växling = Changeover Webb = Web Document Webbdokument = Web document @@ -1319,7 +1320,7 @@ help:41072 = Select a punch from the list to change or remove it. You can add mi help:41641 = Enter a first start time and start interval. Draw random gives an unconditionally random start order. Swedish draw method uses special rules to distribute runners from the same club. Clumped start means the whole class starts in small groups during the specified interval (extended mass start). In the field leg, you can specify which leg is to be drawn, if the class has several. help:425188 = You can handle runners that didn't start automatically by reading out SI stations (clear/check/start/controls) in SportIdent Config+. Save the readout as a semi colon separated text file and import this file into MeOS. Runners in this import get a registration. Then you can give DNS status to all runners without registration. If you later import more runners, you can reset the status (from DNS to Unknown) on the runners now imported. info:readoutbase = Activate the SI unit by selecting its COM-port, or by searching for installed SI units. Press Information to get status for the selected port.\n\nInteractive readout lets you directly handle problems, such as wrong card number. Do not use this option when runners with problems are handled separately.\n\nThe runner database is used if you want to automatically add new runners. The punches are used to find (guess) the right class. -info:readoutmore = Sound selection lets you enable sound signals to be played back on reading out a card.\n\nShow readout in window opens a new window designed to be shown on a screen turned towards the competitor.\n\nIf you have connected a punching unit (for example finish punch) you can decide if you want to accept start, radio, or finish punches. This is a filter to prevent (for example) unexpected start punches from being recieved by mistake, which could overwrite the drawn starting time. +info:readoutmore = Lock the function to prevent accidental changes.\n\nSound selection lets you enable sound signals to be played back on reading out a card.\n\nOpen Readout Window shows a new window designed to be shown on a screen turned towards the competitor, showing information about the last readout.\n\nSeveral races per competitor can be used if you are allowed do several attempts on the course. A new entry is created for each readout. help:50431 = You are now connected to a server. To open a competition on the server, select it in the list and click open. Do add a competition to the server, open the competition locally and select upload. When you have opened a competition on the server, you will see all other connected MeOS clients. help:52726 = Connect to a server below.\n\nInstallation\nDownload and install MySQL 5 (Community Edition) from www.mysql.com. You can use default settings. It is only necessary to install MySQL on the computer acting server. When MySQL is installed, start MySQL Command Line Client and create a user account for MeOS. You write like this:\n\n> CREATE USER meos;\nGRANT ALL ON *.* TO meos;\n\nYou have now created a user meos with no password. Enter the name of the server below (you may have to configure firewalls to let through the traffic).\n\nAs an alternative you can use the built-in root account of MySQL. User name is 'root' and password is the one you provided when installing MySQL. help:5422 = Found no SI unit. Are they connected and started? @@ -2596,3 +2597,64 @@ Duplicerad nummerlapp: X, Y = Duplicated bib: X, Y Saknat lag mellan X och Y = Missing team between X and Y Lag utan nummerlapp: X = Team with no bib: X Brickavläsning = Card Readout +Ett lÃ¥ngt tävlingsnamn kan ge oväntad nerskalning av utskrifter = A long competition name may cause unwanted downscale of printed lists +Ingen reducerad avgift = No reduced fee +Reducerad avgift = Reduced fee +Reducerad avgift för = Reduced fee for +Unga, till och med X Ã¥r = Young people, up to X years +Ungdomar och äldre kan fÃ¥ reducerad avgift = Elder and younger people can have a reduced fee +Äldre, frÃ¥n och med X Ã¥r = Elders, X years and above +Programmera stationen utan AUTOSEND = Program the unit with AUTO SEND off +Aktivera stöd för tiondels sekunder = Activate support for subsecond timing. +Automatisk hyrbrickshantering genom registrerade hyrbrickor = Automatic handling of hired cards using pre-registered card numbers +prefsAutoTieRent = Automatic handling of hired cards +prefsExpResFilename = Default export filename +prefsExpTypeIOF = Default export type +prefsExpWithRaceNo = Include race number when exporting +Ignorerade X duplikat = Ignored X duplicates +info:customsplitprint = You can use a cusom list for the split printout. Design the list and use the function 'For Split Times' in the list editor to make changes for split printout.\n\nYou can control the list to use per class by using the table mode. +Sträcktidslista = Split time list +info:nosplitprint = Cannot load the specified list.\n\nUsing defaults instead. +Välj deltagare för förhandsgranskning = Select competitor for preview +Inkludera bomanalys = Include analysis of lost time +Inkludera individuellt resultat = Include indiviual result +Inkludera sträcktider = Include splits +Inkludera tempo = Include tempo +Skapa en klass för varje bana = Create a class for each course +Ingen[competition] = None +Basera pÃ¥ en tidigare tävling = Base on existing competition +Baserad pÃ¥ X = Based on X +info:multiple_start = A competitor may do multiple starts with the same card. Automatic new entry for each readout. +Flera starter per deltagare = Several races per competitor +Image = Image +NumEntries = Number of entries +NumStarts = Numer of starts +TotalRunLength = Total running distance +TotalRunTime = Total running time +X stämplingar = X punches +RunnerBirthDate = Birth date +Avbryt inläsning = Cancel readout +Spara oparad bricka = Save unpaired card +warn:changeid = The field External Id is usually used to match entities with other databases (such as entry, result, or economy systems). If you make incomaptible changes, various and hard-to-understand problems might arise. +Cannot represent ID X = Cannot represent ID 'X' +Batteridatum = Battery date +Enhet = Unit +Öppna avläsningsfönster = Open readout window +info:readoutwindow = The readout window shows information on the latest card readout. +info:mapcontrol = MeOS is unable to determine what function a unit has unless it is directly attached to the computer. Therefore the programmed punch code is used to determine the type. You can control the interpretation below. Numbers higher than 30 are always interpreted as controls.\n\nBe careful when using start punches; they may permanently overwrite the drawn starting time. +Checkenhet = Check unit +MÃ¥lenhet = Finish unit +Startenhet = Start unit +Du kan justera tiden för en viss enhet = You can adjust the time for a single unit +ClubTeam = Club (Teams) +CourseNumber = Course number +Enhetskod = Unit code +Tolkning av radiostämplingar med okänd typ = Interpretation of punches of unknown type +TeamCourseName = Course name for team/leg +TeamCourseNumber = Course number for team/leg +TeamLegName = Namn pÃ¥ sträcka +Du mÃ¥ste ange minst tvÃ¥ gafflingsvarienater = You must specify at least 2 variants +Max antal gaffllingsvarianter att skapa = Maximum number of forking keys to create +Det uppskattade antalet startade lag i klassen är ett lämpligt värde = The estimated number of teams in the class is a suitable value +Lägg till bild = Add image +Använd listan för sträcktidsutskrift = Use list for split time printing diff --git a/code/gdiconstants.h b/code/gdiconstants.h index 40fbc51..dd3ca73 100644 --- a/code/gdiconstants.h +++ b/code/gdiconstants.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/gdifonts.h b/code/gdifonts.h index c765d16..a72a06c 100644 --- a/code/gdifonts.h +++ b/code/gdifonts.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -61,6 +61,7 @@ constexpr int absolutePosition = 1 << 17; constexpr int skipBoundingBox = 1 << 18; constexpr int hiddenText = 1 << 19; constexpr int textLimitEllipsis = 1 << 20; +constexpr int imageNoUpdatePos = 1 << 21; enum GDICOLOR { colorBlack = RGB(0, 0, 0), diff --git a/code/gdiimpl.h b/code/gdiimpl.h index c2ffa18..137fb60 100644 --- a/code/gdiimpl.h +++ b/code/gdiimpl.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/gdioutput.cpp b/code/gdioutput.cpp index b9b2647..d8e6d7a 100644 --- a/code/gdioutput.cpp +++ b/code/gdioutput.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -79,7 +79,7 @@ extern Image image; #endif extern int defaultCodePage; - + GuiHandler &BaseInfo::getHandler() const { if (managedHandler) return *managedHandler; @@ -182,7 +182,8 @@ void gdioutput::constructor(double _scale) void gdioutput::setFont(int size, const wstring &font) { - double s = 1 + double(size)*0.25; + double ss = size * sqrt(size); + double s = 1 + double(ss)*0.25; initCommon(s, font); } @@ -206,7 +207,7 @@ int transformX(int x, double scale) { return int((x-40) * scale + 0.5) + 40; } -void gdioutput::scaleSize(double scale_, bool allowSmallScale, bool doRefresh) { +void gdioutput::scaleSize(double scale_, bool allowSmallScale, ScaleOperation op) { if (fabs(scale_ - 1.0) < 1e-4) return; // No scaling double ns = scale*scale_; @@ -217,6 +218,9 @@ void gdioutput::scaleSize(double scale_, bool allowSmallScale, bool doRefresh) { } initCommon(ns, currentFont); + if (op == ScaleOperation::NoUpdate) + return; + for (list::iterator it = TL.begin(); it!=TL.end(); ++it) { it->xlimit = int(it->xlimit * scale_ + 0.5); it->xp = transformX(it->xp, scale_); @@ -289,7 +293,7 @@ void gdioutput::scaleSize(double scale_, bool allowSmallScale, bool doRefresh) { r.sOY = int (r.sOY * scale_ + 0.5); } - if (doRefresh) { + if (op == ScaleOperation::Refresh) { refresh(); } else { @@ -303,6 +307,7 @@ void gdioutput::scaleSize(double scale_, bool allowSmallScale, bool doRefresh) { void gdioutput::initCommon(double _scale, const wstring &font) { + guiMeasure.reset(); dbErrorState = false; currentFontSet = 0; scale = _scale; @@ -589,13 +594,12 @@ void gdioutput::setDBErrorState(bool state) { } } -void gdioutput::draw(HDC hDC, RECT &rc, RECT &drawArea) -{ +void gdioutput::draw(HDC hDC, RECT& rc, RECT& drawArea) { #ifdef DEBUGRENDER if (debugDrawColor) { string ds = "DebugDraw" + itos(drawArea.left) + "-" + itos(drawArea.right) + ", " + itos(drawArea.top) + "-" + itos(drawArea.bottom) + "\n"; OutputDebugString(ds.c_str()); - SelectObject(hDC,GetStockObject(DC_BRUSH)); + SelectObject(hDC, GetStockObject(DC_BRUSH)); SetDCBrushColor(hDC, debugDrawColor); Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); return; @@ -617,21 +621,26 @@ void gdioutput::draw(HDC hDC, RECT &rc, RECT &drawArea) return; } - SelectObject(hDC,GetStockObject(DC_BRUSH)); + SelectObject(hDC, GetStockObject(DC_BRUSH)); - for (auto &rit : Rectangles) + for (auto& rit : Rectangles) renderRectangle(hDC, 0, rit); - + if (useTables) - for(list::iterator tit=Tables.begin();tit!=Tables.end(); ++tit){ + for (list::iterator tit = Tables.begin(); tit != Tables.end(); ++tit) { tit->table->draw(*this, hDC, tit->xp, tit->yp, rc); } resetLast(); TIList::iterator it; - int BoundYup=OffsetY-100 + drawArea.top; - int BoundYdown=OffsetY+ drawArea.bottom + 2; + int BoundYup = OffsetY - maxTextBlockHeight - 2 + drawArea.top; + int BoundYupTight = OffsetY - 2 + drawArea.top; + int BoundYdown = OffsetY + drawArea.bottom + 2; + + for (auto imgTL : imageReferences) { + RenderString(*imgTL, hDC); + } if (!renderOptimize || itTL == TL.end()) { #ifdef DEBUGRENDER @@ -639,27 +648,30 @@ void gdioutput::draw(HDC hDC, RECT &rc, RECT &drawArea) // DebugBreak(); OutputDebugString(("Raw render" + itos(size_t(this)) + "\n").c_str()); #endif - for(it=TL.begin();it!=TL.end(); ++it){ - TextInfo &ti=*it; - if ( ti.yp > BoundYup && ti.yp < BoundYdown) + for (it = TL.begin(); it != TL.end(); ++it) { + TextInfo& ti = *it; + if ((ti.format & 0xFF) == textImage) + continue; + if ((ti.yp > BoundYup || ti.textRect.bottom > BoundYupTight) && ti.yp < BoundYdown) RenderString(*it, hDC); } } else { - #ifdef DEBUGRENDER - OutputDebugString((itos(++counterRender) + " opt render " + itos(size_t(this)) + "\n").c_str()); - #endif +#ifdef DEBUGRENDER + OutputDebugString((itos(++counterRender) + " opt render " + itos(size_t(this)) + "\n").c_str()); +#endif - while( itTL != TL.end() && itTL->yp < BoundYup) + while (itTL != TL.end() && itTL->yp < BoundYup) ++itTL; - if (itTL!=TL.end()) - while( itTL != TL.begin() && itTL->yp > BoundYup) + if (itTL != TL.end()) + while (itTL != TL.begin() && itTL->yp > BoundYup) --itTL; - it=itTL; - while( it != TL.end() && it->yp < BoundYdown) { - RenderString(*it, hDC); + it = itTL; + while (it != TL.end() && it->yp < BoundYdown) { + if ((it->format & 0xFF) != textImage) + RenderString(*it, hDC); ++it; } } @@ -769,6 +781,9 @@ void CALLBACK gdiTimerProc(HWND hWnd, UINT a, UINT_PTR ptr, DWORD b) { it->parent->timerProc(*it, b); } } + catch (const meosCancel&) { + return; + } catch (meosException &ex) { msg = ex.wwhat(); } @@ -797,7 +812,14 @@ void gdioutput::timerProc(TimerInfo &timer, DWORD timeout) { else if (timer.callBack) timer.callBack(this, GUI_TIMER, &timer); - timers.erase(remove_if(timers.begin(), timers.end(), [timerId](TimerInfo &x) {return x.getId() == timerId; }), timers.end()); + for (auto it = timers.begin(); it != timers.end(); ++it) { + if (it->getId() == timerId) { + timers.erase(it); + break; + } + } + + //timers.erase(remove_if(timers.begin(), timers.end(), [timerId](TimerInfo &x) {return x.getId() == timerId; }), timers.end()); } void gdioutput::removeHandler(GuiHandler *h) { @@ -840,7 +862,7 @@ void gdioutput::removeTimeoutMilli(const string &id) { TimerInfo &gdioutput::addTimeoutMilli(int timeOut, const string &id, GUICALLBACK cb) { removeTimeoutMilli(id); - timers.push_back(TimerInfo(this, cb)); + timers.emplace_back(this, cb); timers.back().id = id; SetTimer(hWndTarget, (UINT_PTR)&timers.back(), timeOut, gdiTimerProc); timers.back().setWnd = hWndTarget; @@ -853,6 +875,54 @@ TimerInfo:: ~TimerInfo() { if (setWnd) KillTimer(setWnd, (UINT_PTR)this); } + +TextInfo& gdioutput::addImage(const string& id, int yp, int xp, int format, + const wstring& imageId, int width, int height, GUICALLBACK cb) { + bool skipBBCalc = (format & skipBoundingBox) == skipBoundingBox; + format &= ~skipBoundingBox; + + int oldYP = TL.empty() ? -1 : TL.back().yp; + TL.emplace_back(); + TextInfo& TI = TL.back(); + itTL = TL.begin(); + + imageReferences.push_back(&TI); + + TI.format = format | textImage; + TI.xp = xp; + TI.yp = yp; + TI.text = L"L" + imageId; + TI.callBack = cb; + + if (width == 0 || height == 0) { + uint64_t imgId = _wcstoui64(imageId.c_str(), nullptr, 10); + width = image.getWidth(imgId); + height = image.getHeight(imgId); + } + + //if (skipBBCalc) { + TI.textRect.left = xp; + TI.textRect.top = yp; + TI.textRect.right = xp + width; + TI.textRect.bottom = yp + height; + TI.realWidth = width; + + FlowDirection oldDir = flowDirection; + + if (format & imageNoUpdatePos) + flowDirection = FlowDirection::None; + + updatePos(TI.xp, TI.yp, width + scaleLength(10), + height + scaleLength(2)); + + flowDirection = oldDir; + + if (oldYP > TI.yp) + renderOptimize = false; + + return TL.back(); +} + TextInfo &gdioutput::addStringUT(int yp, int xp, int format, const string &text, int xlimit, GUICALLBACK cb, const wchar_t *fontFace) { return addStringUT(yp, xp, format, widen(text), xlimit, cb, fontFace); @@ -879,26 +949,30 @@ int gdioutput::getFontHeight(int format, const wstring &fontFace) const { return h; } -TextInfo &gdioutput::addStringUT(int yp, int xp, int format, const wstring &text, - int xlimit, GUICALLBACK cb, const wchar_t *fontFace) +TextInfo& gdioutput::addStringUT(int yp, int xp, int format, const wstring& text, + int xlimit, GUICALLBACK cb, const wchar_t* fontFace) { bool skipBBCalc = (format & skipBoundingBox) == skipBoundingBox; format &= ~skipBoundingBox; + int oldYP = TL.empty() ? -1 : TL.back().yp; TL.emplace_back(); - TextInfo &TI = TL.back(); + TextInfo& TI = TL.back(); itTL = TL.begin(); - TI.format=format; - TI.xp=xp; - TI.yp=yp; - TI.text=text; - TI.xlimit=xlimit; - TI.callBack=cb; + if ((format & 0xFF) == textImage) + imageReferences.push_back(&TI); + + TI.format = format; + TI.xp = xp; + TI.yp = yp; + TI.text = text; + TI.xlimit = xlimit; + TI.callBack = cb; if (fontFace) TI.font = fontFace; if (!skipTextRender(format)) { - + if (skipBBCalc) { assert(xlimit > 0); int h = getFontHeight(format, fontFace); @@ -908,8 +982,10 @@ TextInfo &gdioutput::addStringUT(int yp, int xp, int format, const wstring &text TI.textRect.bottom = yp + h; TI.realWidth = xlimit; - updatePos(TI.textRect.right + OffsetX, TI.yp, scaleLength(10), - TI.textRect.bottom - TI.textRect.top + scaleLength(2)); + updatePos(TI.xp, TI.yp, TI.realWidth + scaleLength(10), + TI.textRect.bottom - TI.textRect.top + scaleLength(2)); + + maxTextBlockHeight = max(maxTextBlockHeight, h + 1); } else { HDC hDC = GetDC(hWndTarget); @@ -921,19 +997,18 @@ TextInfo &gdioutput::addStringUT(int yp, int xp, int format, const wstring &text if (xlimit == 0 || (format & (textRight | textCenter)) == 0) { updatePos(TI.textRect.right + OffsetX, TI.yp, scaleLength(10), - TI.textRect.bottom - TI.textRect.top + scaleLength(2)); + TI.textRect.bottom - TI.textRect.top + scaleLength(2)); } else { updatePos(TI.xp, TI.yp, TI.realWidth + scaleLength(10), - TI.textRect.bottom - TI.textRect.top + scaleLength(2)); + TI.textRect.bottom - TI.textRect.top + scaleLength(2)); } ReleaseDC(hWndTarget, hDC); + maxTextBlockHeight = max(maxTextBlockHeight, 1 + TI.textRect.bottom - TI.textRect.top); } - - if (renderOptimize && !TL.empty()) { - if (TL.back().yp > TI.yp) - renderOptimize=false; - } + + if (oldYP > TI.yp) + renderOptimize = false; } else { TI.textRect.left = xp; @@ -951,44 +1026,52 @@ TextInfo &gdioutput::addString(const char *id, int yp, int xp, int format, const return addString(id, yp, xp, format, widen(text), xlimit, cb, fontFace); } -TextInfo &gdioutput::addString(const char *id, int yp, int xp, int format, const wstring &text, - int xlimit, GUICALLBACK cb, const wchar_t *fontFace) +TextInfo& gdioutput::addString(const char* id, int yp, int xp, int format, const wstring& text, + int xlimit, GUICALLBACK cb, const wchar_t* fontFace) { - TextInfo TI; - TI.format=format; - TI.xp=xp; - TI.yp=yp; - TI.text=lang.tl(text); - if ( (format & Capitalize) == Capitalize && lang.capitalizeWords()) + int oldYP = TL.empty() ? -1 : TL.back().yp; + + TL.emplace_back(); + itTL = TL.begin(); + TextInfo& TI = TL.back(); + + if ((format & 0xFF) == textImage) + imageReferences.push_back(&TI); + + TI.format = format; + TI.xp = xp; + TI.yp = yp; + TI.text = lang.tl(text); + if ((format & Capitalize) == Capitalize && lang.capitalizeWords()) capitalizeWords(TI.text); - TI.id=id; - TI.xlimit=xlimit; - TI.callBack=cb; + TI.id = id; + TI.xlimit = xlimit; + TI.callBack = cb; if (fontFace) TI.font = fontFace; if (!skipTextRender(format)) { - HDC hDC=GetDC(hWndTarget); + HDC hDC = GetDC(hWndTarget); if (hWndTarget && !manualUpdate) RenderString(TI, hDC); else calcStringSize(TI, hDC); - if (xlimit == 0 || (format & (textRight|textCenter)) == 0) { - updatePos(TI.textRect.right+OffsetX, yp, scaleLength(10), - TI.textRect.bottom - TI.textRect.top + scaleLength(2)); + if (xlimit == 0 || (format & (textRight | textCenter)) == 0) { + updatePos(TI.textRect.right + OffsetX, yp, scaleLength(10), + TI.textRect.bottom - TI.textRect.top + scaleLength(2)); } else { updatePos(TI.xp, TI.yp, TI.realWidth + scaleLength(10), - TI.textRect.bottom - TI.textRect.top + scaleLength(2)); + TI.textRect.bottom - TI.textRect.top + scaleLength(2)); } ReleaseDC(hWndTarget, hDC); - if (renderOptimize && !TL.empty()) { - if (TL.back().yp > TI.yp) - renderOptimize=false; - } + maxTextBlockHeight = max(maxTextBlockHeight, TI.textRect.bottom - TI.textRect.top + 1); + + if (oldYP > TI.yp) + renderOptimize = false; } else { TI.textRect.left = xp; @@ -997,9 +1080,6 @@ TextInfo &gdioutput::addString(const char *id, int yp, int xp, int format, const TI.textRect.top = yp; } - TL.push_back(TI); - itTL=TL.begin(); - return TL.back(); } @@ -1031,7 +1111,6 @@ TextInfo &gdioutput::addString(const char *id, int format, const wstring &text, return addString(id, CurrentY, CurrentX, format, text, 0, cb); } - TextInfo &gdioutput::addStringUT(int format, const string &text, GUICALLBACK cb) { return addStringUT(CurrentY, CurrentX, format, text, 0, cb); @@ -1111,10 +1190,6 @@ ButtonInfo &ButtonInfo::setDefault() return *this; } -int gdioutput::getButtonHeight() const { - return int(scale * 24)+0; -} - void ButtonInfo::moveButton(gdioutput &gdi, int nxp, int nyp) { /*WINDOWPLACEMENT wpl; GetWindowPlacement(hWnd, &wpl); @@ -1289,9 +1364,12 @@ ButtonInfo &gdioutput::addCheckbox(int x, int y, const string &id, const wstring bi.checked = Checked; - if (!AbsPos) - updatePos(x, y, size.cx+int(30*scale), size.cy+int(scale * 12)+3); - + if (!AbsPos) { + if (ttext.empty()) + updatePos(x, y, size.cx + int(30 * scale), size.cy + int(scale * 12) + 3); + else + updatePos(x, y, size.cx + int(30 * scale), desc.textRect.bottom - desc.textRect.top + scaleLength(4)); + } if (tooltip.length()>0) { addToolTip(id, tooltip, bi.hWnd); addToolTip(desc.id, tooltip, 0, &desc.textRect); @@ -1357,14 +1435,34 @@ HFONT gdioutput::getGUIFont() const } pair gdioutput::getInputDimension(int length) const { - HDC hDC = GetDC(hWndTarget); - SelectObject(hDC, getGUIFont()); - SIZE size; - GetTextExtentPoint32(hDC, L"M", 1, &size); - ReleaseDC(hWndTarget, hDC); - return make_pair(length*size.cx + scaleLength(8), size.cy + scaleLength(6)); + if (!guiMeasure) { + HDC hDC = GetDC(hWndTarget); + SelectObject(hDC, getGUIFont()); + SIZE size; + GetTextExtentPoint32(hDC, L"M", 1, &size); + + SIZE sizeAvg; + wstring avgText = L"123456789ABCDEFGHIJHKLMNOPQRSTUVXYZ abcdefghijklmnopqrstuvxyz"; + GetTextExtentPoint32(hDC, avgText.c_str(), avgText.length(), &sizeAvg); + ReleaseDC(hWndTarget, hDC); + + int dy = GetSystemMetrics(SM_CYEDGE); + int dx = GetSystemMetrics(SM_CXEDGE); + guiMeasure = make_shared(); + guiMeasure->letterWidth = size.cx; + guiMeasure->extraX = 2 * dx; + guiMeasure->height = 4 + dy * 2 + size.cy; + guiMeasure->avgCharWidth = float(sizeAvg.cx) / float(avgText.length()); + } + + return make_pair(length * guiMeasure->letterWidth + guiMeasure->extraX, guiMeasure->height); } +int gdioutput::getButtonHeight() const { + return int(getInputDimension(0).second * 1.2);//int(scale * 24) + 0; +} + + InputInfo &gdioutput::addInput(int x, int y, const string &id, const wstring &text, int length, GUICALLBACK cb, const wstring &explanation, const wstring &help) { @@ -2102,6 +2200,9 @@ LRESULT gdioutput::ProcessMsg(UINT iMessage, LPARAM lParam, WPARAM wParam) try { return ProcessMsgWrp(iMessage, lParam, wParam); } + catch (const meosCancel&) { + return false; + } catch (meosException & ex) { msg = ex.wwhat(); } @@ -2180,6 +2281,8 @@ void gdioutput::processEditMessage(InputInfo &bi, WPARAM wParam) getWindowText(bi.hWnd, bi.text); if (bi.handler) bi.handler->handle(*this, bi, GUI_INPUTCHANGE); + else if (bi.managedHandler) + bi.managedHandler->handle(*this, bi, GUI_INPUTCHANGE); else if (bi.callBack) bi.callBack(this, GUI_INPUTCHANGE, &bi); //it may be destroyed here... @@ -2194,6 +2297,8 @@ void gdioutput::processEditMessage(InputInfo &bi, WPARAM wParam) string cmd = "input(\"" + bi.id + "\", \"" + toUTF8(bi.text) + "\");"; if (bi.handler) bi.handler->handle(*this, bi, GUI_INPUT); + else if (bi.managedHandler) + bi.managedHandler->handle(*this, bi, GUI_INPUT); else if (bi.callBack) bi.callBack(this, GUI_INPUT, &bi); if (!equal) @@ -2207,6 +2312,8 @@ void gdioutput::processEditMessage(InputInfo &bi, WPARAM wParam) bi.focusText = bi.text; if (bi.handler) bi.handler->handle(*this, bi, GUI_FOCUS); + else if (bi.managedHandler) + bi.managedHandler->handle(*this, bi, GUI_FOCUS); else if (bi.callBack) bi.callBack(this, GUI_FOCUS, &bi); break; @@ -2281,8 +2388,6 @@ void gdioutput::processComboMessage(ListBoxInfo &bi, WPARAM wParam) } } -#ifndef MEOSDB - void gdioutput::keyCommand(KeyCommandCode code) { if (hasCommandLock()) return; @@ -2312,6 +2417,9 @@ void gdioutput::keyCommand(KeyCommandCode code) { } } } + catch (const meosCancel&) { + return; + } catch (meosException & ex) { msg = ex.wwhat(); } @@ -2328,8 +2436,6 @@ void gdioutput::keyCommand(KeyCommandCode code) { alert(msg); } -#endif - void gdioutput::processListMessage(ListBoxInfo &bi, WPARAM wParam) { WORD hwParam = HIWORD(wParam); @@ -2891,6 +2997,9 @@ void gdioutput::enter() try { doEnter(); } + catch (const meosCancel&) { + return; + } catch (meosException & ex) { msg = ex.wwhat(); } @@ -2951,6 +3060,9 @@ bool gdioutput::upDown(int direction) try { return doUpDown(direction); } + catch (const meosCancel&) { + return false; + } catch (meosException & ex) { msg = ex.wwhat(); } @@ -2993,6 +3105,9 @@ void gdioutput::escape() try { doEscape(); } + catch (const meosCancel&) { + return; + } catch (meosException & ex) { msg = ex.wwhat(); } @@ -3039,6 +3154,7 @@ void gdioutput::doEscape() } void gdioutput::clearPage(bool autoRefresh, bool keepToolbar) { + maxTextBlockHeight = getLineHeight(); animationData.reset(); lockUpDown = false; hasAnyTimer = false; @@ -3062,6 +3178,8 @@ void gdioutput::clearPage(bool autoRefresh, bool keepToolbar) { currentFocus = 0; TL.clear(); itTL = TL.end(); + updateImageReferences(); + listDescription.clear(); if (hWndTarget && autoRefresh) @@ -3151,6 +3269,8 @@ void gdioutput::clearPage(bool autoRefresh, bool keepToolbar) { if (postClear) postClear(this, GUI_POSTCLEAR, 0); } + catch (const meosCancel&) { + } catch (meosException & ex) { if (isTestMode) throw ex; @@ -3163,11 +3283,22 @@ void gdioutput::clearPage(bool autoRefresh, bool keepToolbar) { string msg(ex.what()); alert(msg); } - postClear = 0; + postClear = nullptr; manualUpdate = !autoRefresh; } +void gdioutput::updateImageReferences() { + if (imageReferences.size() > 0) { + imageReferences.clear(); + for (auto& ti : TL) { + if ((ti.format & 0xFF) == textImage) { + imageReferences.push_back(&ti); + } + } + } +} + void gdioutput::getWindowText(HWND hWnd, wstring &text) { TCHAR bf[1024]; @@ -3185,32 +3316,28 @@ void gdioutput::getWindowText(HWND hWnd, wstring &text) delete[] bptr; } -BaseInfo &gdioutput::getBaseInfo(const char *id) const { - for(list::const_iterator it=II.begin(); - it != II.end(); ++it){ - if (it->id==id){ - return const_cast(*it); +BaseInfo& gdioutput::getBaseInfo(const char* id, int requireExtraMatch) const { + for (auto& ii : II) { + if (ii.id == id && ii.matchExtra(requireExtraMatch)) { + return const_cast(ii); } } - for(list::const_iterator it=LBI.begin(); - it != LBI.end(); ++it){ - if (it->id==id){ - return const_cast(*it); + for (auto& lbi : LBI) { + if (lbi.id == id && lbi.matchExtra(requireExtraMatch)) { + return const_cast(lbi); } } - for(list::const_iterator it=BI.begin(); - it != BI.end(); ++it){ - if (it->id==id) { - return const_cast(*it); + for (auto& bi : BI) { + if (bi.id == id && bi.matchExtra(requireExtraMatch)) { + return const_cast(bi); } } - for(list::const_iterator it=TL.begin(); - it != TL.end(); ++it){ - if (it->id==id) { - return const_cast(*it); + for (auto& tl : TL) { + if (tl.id == id && tl.matchExtra(requireExtraMatch)) { + return const_cast(tl); } } @@ -3218,8 +3345,7 @@ BaseInfo &gdioutput::getBaseInfo(const char *id) const { throw std::exception(err.c_str()); } -const wstring &gdioutput::getText(const char *id, bool acceptMissing, int requireExtraMatch) const -{ +const wstring &gdioutput::getText(const char *id, bool acceptMissing, int requireExtraMatch) const { TCHAR bf[1024]; TCHAR *bptr=bf; @@ -3494,14 +3620,14 @@ bool gdioutput::hasData(const char *id) const { bool gdioutput::updatePos(int x, int y, int width, int height) { - int ox=MaxX; - int oy=MaxY; + int ox = MaxX; + int oy = MaxY; - MaxX=max(x+width, MaxX); - MaxY=max(y+height, MaxY); - bool changed = (ox!=MaxX || oy!=MaxY); + MaxX = max(x + width, MaxX); + MaxY = max(y + height, MaxY); + bool changed = (ox != MaxX || oy != MaxY); - if (changed && hWndTarget && !manualUpdate) { + if (changed && hWndTarget && !manualUpdate) { RECT rc; if (ox == MaxX) { rc.top = oy - CurrentY - 5; @@ -3515,16 +3641,15 @@ bool gdioutput::updatePos(int x, int y, int width, int height) { } GetClientRect(hWndTarget, &rc); - if (MaxX>rc.right || MaxY>rc.bottom) //Update scrollbars + if (MaxX > rc.right || MaxY > rc.bottom) //Update scrollbars SendMessage(hWndTarget, WM_SIZE, 0, MAKELONG(rc.right, rc.bottom)); - } - if (Direction==1) { - CurrentY=max(y+height, CurrentY); + if (flowDirection == FlowDirection::Down) { + CurrentY = max(y + height, CurrentY); } - else if (Direction==0) { - CurrentX=max(x+width, CurrentX); + else if (flowDirection == FlowDirection::Right) { + CurrentX = max(x + width, CurrentX); } return changed; } @@ -3647,11 +3772,11 @@ gdioutput::AskAnswer gdioutput::askCancel(const wstring &s) string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans == "cancel") - return AnswerCancel; + return AskAnswer::AnswerCancel; else if (ans == "yes") - return AnswerYes; + return AskAnswer::AnswerYes; else if (ans == "no") - return AnswerNo; + return AskAnswer::AnswerNo; } throw meosException(s + L"--yes/no/cancel"); } @@ -3661,25 +3786,53 @@ gdioutput::AskAnswer gdioutput::askCancel(const wstring &s) int a = MessageBox(hWndAppMain, lang.tl(s).c_str(), L"MeOS", MB_YESNOCANCEL|MB_ICONQUESTION); liftCommandLock(); if (a == IDYES) - return AnswerYes; + return AskAnswer::AnswerYes; else if (a == IDNO) - return AnswerNo; + return AskAnswer::AnswerNo; else - return AnswerCancel; + return AskAnswer::AnswerCancel; } - -void gdioutput::setTabStops(const string &Name, int t1, int t2) +gdioutput::AskAnswer gdioutput::askOkCancel(const wstring& s) { - DWORD array[2]; - int n=1; - LONG bu=GetDialogBaseUnits(); - int baseunitX=LOWORD(bu); - array[0]=int(t1 * 4.2) / baseunitX ; - array[1]=int(t2 * 4.2) / baseunitX ; + if (isTestMode) { + if (!cmdAnswers.empty()) { + string ans = cmdAnswers.front(); + cmdAnswers.pop_front(); + if (ans == "cancel") + return AskAnswer::AnswerCancel; + else if (ans == "ok") + return AskAnswer::AnswerOK; + } + throw meosException(s + L"--ok/cancel"); + } + + setCommandLock(); + SetForegroundWindow(hWndAppMain); + int a = MessageBox(hWndAppMain, lang.tl(s).c_str(), L"MeOS", MB_OKCANCEL | MB_ICONINFORMATION); + liftCommandLock(); + if (a == IDOK) + return AskAnswer::AnswerOK; + else + return AskAnswer::AnswerCancel; +} + +void gdioutput::setTabStops(const string& name, int t1, int t2) { + getInputDimension(0); + double relTextScale = scale / guiMeasure->avgCharWidth; + + DWORD ptr[2]; + int n = 1; + //LONG bu=GetDialogBaseUnits(); + //int baseunitX=LOWORD(bu); + //array[0]=int(t1 * 4.2 * scale) / baseunitX ; + //array[1]=int(t2 * 4.2 * scale) / baseunitX ; + ptr[0] = int(t1 * relTextScale * 6.4 * 4.2 / 8.0); + ptr[1] = int(t2 * relTextScale * 6.4 * 4.2 / 8.0); + int lastTabStop = 0; - if (t2>0) { - n=2; + if (t2 > 0) { + n = 2; lastTabStop = t2; } else { @@ -3687,10 +3840,10 @@ void gdioutput::setTabStops(const string &Name, int t1, int t2) } list::iterator it; - for(it=LBI.begin(); it != LBI.end(); ++it){ - if (it->id==Name){ + for (it = LBI.begin(); it != LBI.end(); ++it) { + if (it->id == name) { if (!it->IsCombo) { - SendMessage(it->hWnd, LB_SETTABSTOPS, n, LPARAM(array)); + SendMessage(it->hWnd, LB_SETTABSTOPS, n, LPARAM(ptr)); it->lastTabStop = lastTabStop; } return; @@ -4055,40 +4208,35 @@ void gdioutput::refreshSmartFromSnapshot(bool allowMoveOffset) { } } - -void gdioutput::removeString(string id) -{ - list::iterator it; - for (it=TL.begin(); it != TL.end(); ++it) { - if (it->id==id) { - HDC hDC=GetDC(hWndTarget); - RECT rc; - rc.left=it->xp; - rc.top=it->yp; - - DrawText(hDC, it->text.c_str(), -1, &rc, DT_CALCRECT|DT_NOPREFIX); - SelectObject(hDC, GetStockObject(NULL_PEN)); - SelectObject(hDC, Background); - Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); - ReleaseDC(hWndTarget, hDC); +void gdioutput::removeString(string id) { + int cnt = 0; + for (auto it = TL.begin(); it != TL.end(); ++it, ++cnt) { + if (it->id == id) { + InvalidateRect(hWndTarget, &it->textRect, true); TL.erase(it); itTL = TL.end(); shownStrings.clear(); + + updateImageReferences(); + + // Update restorepoints + for (auto& rp : restorePoints) { + if (rp.second.nTL > cnt) + rp.second.nTL--; + } return; } } } -bool gdioutput::selectFirstItem(const string &id) -{ - list::iterator it; - for(it=LBI.begin(); it != LBI.end(); ++it) - if (it->id==id) { +bool gdioutput::selectFirstItem(const string& id) { + for (auto it = LBI.begin(); it != LBI.end(); ++it) + if (it->id == id) { bool ret; if (it->IsCombo) - ret = SendMessage(it->hWnd, CB_SETCURSEL, 0,0)>=0; + ret = SendMessage(it->hWnd, CB_SETCURSEL, 0, 0) >= 0; else - ret = SendMessage(it->hWnd, LB_SETCURSEL, 0,0)>=0; + ret = SendMessage(it->hWnd, LB_SETCURSEL, 0, 0) >= 0; getSelectedItem(*it); it->original = it->text; it->originalIdx = it->data; @@ -4186,8 +4334,7 @@ void gdioutput::fadeOut(string Id, int ms) } } -void gdioutput::RenderString(TextInfo &ti, HDC hDC) -{ +void gdioutput::RenderString(TextInfo &ti, HDC hDC) { if (skipTextRender(ti.format)) return; @@ -4217,15 +4364,38 @@ void gdioutput::RenderString(TextInfo &ti, HDC hDC) if (format == textImage) { // Image int id = _wtoi(ti.text.c_str()); - image.loadImage(id, Image::ImageMethod::Default); - int w = image.getWidth(id); - int h = image.getHeight(id); - image.drawImage(id, Image::ImageMethod::Default, hDC, rc.left, rc.top, w, h); + bool fixedRect = false; + int h = 16, w = 16; + if (id > 0) { + image.loadImage(id, Image::ImageMethod::Default); + w = image.getWidth(id); + h = image.getHeight(id); + image.drawImage(id, Image::ImageMethod::Default, hDC, rc.left, rc.top, w, h); + } + else if (ti.text.size()>1) { + + if (ti.text[0] == 'S') { // Icon + w = _wtoi(ti.text.c_str() + 1); + h = getLineHeight(); + } + else if (ti.text[0] == 'L') { + fixedRect = true; + uint64_t imgId = _wcstoui64(ti.text.c_str() + 1, nullptr, 10); + w = ti.textRect.right - ti.textRect.left; + h = ti.textRect.bottom - ti.textRect.top; - ti.textRect.left = rc.left; - ti.textRect.right = rc.left + w + 5; - ti.textRect.top = rc.top; - ti.textRect.bottom = rc.bottom + h + 5; + image.drawImage(imgId, Image::ImageMethod::Default, hDC, rc.left, rc.top, w, h); + + //width = image.getWidth(imgId); + //height = image.getHeight(imgId); + } + } + if (!fixedRect) { + ti.textRect.left = rc.left; + ti.textRect.right = rc.left + w + 5; + ti.textRect.top = rc.top; + ti.textRect.bottom = rc.bottom + h + 5; + } } else if (format != 10 && (breakLines&ti.format) == 0) { if (ti.xlimit == 0) { @@ -4470,8 +4640,7 @@ void gdioutput::formatString(const TextInfo &ti, HDC hDC) const SetTextColor(hDC, ti.color); } -void gdioutput::calcStringSize(TextInfo &ti, HDC hDC_in) const -{ +void gdioutput::calcStringSize(TextInfo &ti, HDC hDC_in) const { RECT rc; rc.left=ti.xp-OffsetX; @@ -4479,11 +4648,22 @@ void gdioutput::calcStringSize(TextInfo &ti, HDC hDC_in) const rc.right = rc.left; rc.bottom = rc.top; - if (ti.format == textImage) { + if ((ti.format & 0xFF) == textImage) { // Image int id = _wtoi(ti.text.c_str()); - int w = image.getWidth(id); - int h = image.getHeight(id); + int w = 16, h = 16; + if (id > 0) { + w = image.getWidth(id); + h = image.getHeight(id); + } + else if (ti.text.size()>1 && ti.text[0] == 'S') { // Icon + w = _wtoi(ti.text.c_str() + 1); + h = getLineHeight(); + } + else if (ti.text[0] == 'L') { + return; + } + ti.textRect.left = rc.left; ti.textRect.right = rc.left + w + 5; ti.textRect.top = rc.top; @@ -4768,13 +4948,13 @@ wstring gdioutput::getTimerText(TextInfo *tit, DWORD T) else swprintf_s(bf, 16, L"%d", t); } - else if ((tit->format & timeWithTenth) && rt < 3600) { - swprintf_s(bf, 16, L"%02d:%02d.%d", t/60, t%60, tenth); + else if ((tit->format & timeWithTenth) && rt < timeConstSecPerHour) { + swprintf_s(bf, 16, L"%02d:%02d.%d", t/ timeConstSecPerMin, t%timeConstSecPerMin, tenth); } - else if (rt>=3600 || (tit->format&fullTimeHMS)) - swprintf_s(bf, 16, L"%02d:%02d:%02d", t/3600, (t/60)%60, t%60); + else if (rt>=timeConstSecPerHour || (tit->format&fullTimeHMS)) + swprintf_s(bf, 16, L"%02d:%02d:%02d", t/ timeConstSecPerHour, (t/ timeConstSecPerMin)% timeConstSecPerMin, t%timeConstSecPerMin); else - swprintf_s(bf, 16, L"%d:%02d", (t/60), t%60); + swprintf_s(bf, 16, L"%d:%02d", (t/ timeConstMinPerHour), t%timeConstMinPerHour); if (rt>0 || ((tit->format&fullTimeHMS) && rt>=0) ) if (tit->format&timerCanBeNegative) @@ -4866,49 +5046,71 @@ void gdioutput::CheckInterfaceTimeouts(DWORD T) bool gdioutput::removeWidget(const string &id) { - list::iterator it=BI.begin(); + { + auto it = BI.begin(); + int cnt = 0; + while (it != BI.end()) { + if (it->id == id) { + DestroyWindow(it->hWnd); + biByHwnd.erase(it->hWnd); - while (it!=BI.end()) { - if (it->id==id) { - DestroyWindow(it->hWnd); - biByHwnd.erase(it->hWnd); - - if (it->isCheckbox) - removeString("T" + id); - BI.erase(it); - return true; + if (it->isCheckbox) + removeString("T" + id); + BI.erase(it); + // Update restorepoints + for (auto& rp : restorePoints) { + if (rp.second.nBI > cnt) + rp.second.nBI--; + } + return true; + } + ++it; + ++cnt; + } + } + { + auto lit = LBI.begin(); + int cnt = 0; + while (lit != LBI.end()) { + if (lit->id == id) { + DestroyWindow(lit->hWnd); + lbiByHwnd.erase(lit->hWnd); + removeString(id + "_label"); + if (lit->writeLock) + hasCleared = true; + LBI.erase(lit); + // Update restorepoints + for (auto& rp : restorePoints) { + if (rp.second.nLBI > cnt) + rp.second.nLBI--; + } + return true; + } + ++lit; + cnt++; } - ++it; } - list::iterator lit=LBI.begin(); - - while (lit!=LBI.end()) { - if (lit->id==id) { - DestroyWindow(lit->hWnd); - lbiByHwnd.erase(lit->hWnd); - removeString(id + "_label"); - if (lit->writeLock) - hasCleared = true; - LBI.erase(lit); - return true; + { + auto iit = II.begin(); + int cnt = 0; + while (iit != II.end()) { + if (iit->id == id) { + DestroyWindow(iit->hWnd); + iiByHwnd.erase(iit->hWnd); + II.erase(iit); + removeString(id + "_label"); + // Update restorepoints + for (auto& rp : restorePoints) { + if (rp.second.nII > cnt) + rp.second.nII--; + } + return true; + } + ++iit; + cnt++; } - ++lit; } - - list::iterator iit=II.begin(); - - while (iit!=II.end()) { - if (iit->id==id) { - DestroyWindow(iit->hWnd); - iiByHwnd.erase(iit->hWnd); - II.erase(iit); - removeString(id + "_label"); - return true; - } - ++iit; - } - removeString(id); return false; } @@ -4960,14 +5162,12 @@ bool gdioutput::hideWidget(const string &id, bool hide) { return false; } -void gdioutput::setRestorePoint() -{ +void gdioutput::setRestorePoint() { setRestorePoint(""); } -void gdioutput::setRestorePoint(const string &id) -{ +void gdioutput::setRestorePoint(const string &id) { RestoreInfo ri; ri.id = id; @@ -4981,6 +5181,9 @@ void gdioutput::setRestorePoint(const string &id) ri.nTables = Tables.size(); ri.nHWND = FocusList.size(); ri.nData = DataInfo.size(); + + for (auto& rp : restorePoints) + ri.restorePoints.insert(rp.first); ri.sCX=CurrentX; ri.sCY=CurrentY; @@ -4994,6 +5197,141 @@ void gdioutput::setRestorePoint(const string &id) restorePoints[id]=ri; } + +bool gdioutput::getWidgetRestorePoint(const string& id, string& restorePoint) const { + int count; + + count = 0; + for (auto& lbi : LBI) { + if (lbi.id == id) { + const RestoreInfo* bestRestoreInfo = nullptr; + for (auto& rp : restorePoints) { + if (count <= rp.second.nLBI && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { + bestRestoreInfo = &rp.second; + restorePoint = rp.first; + } + } + return bestRestoreInfo != nullptr; + } + count++; + } + + count = 0; + for (auto& ii : II) { + if (ii.id == id) { + const RestoreInfo* bestRestoreInfo = nullptr; + for (auto& rp : restorePoints) { + if (count <= rp.second.nII && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { + bestRestoreInfo = &rp.second; + restorePoint = rp.first; + } + } + return bestRestoreInfo != nullptr; + } + count++; + } + + + count = 0; + for (auto& bi : BI) { + if (bi.id == id) { + const RestoreInfo* bestRestoreInfo = nullptr; + for (auto& rp : restorePoints) { + if (count <= rp.second.nBI && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { + bestRestoreInfo = &rp.second; + restorePoint = rp.first; + } + } + return bestRestoreInfo != nullptr; + } + count++; + } + + count = 0; + for (auto& ti : TL) { + if (ti.id == id) { + const RestoreInfo* bestRestoreInfo = nullptr; + for (auto& rp : restorePoints) { + if (count <= rp.second.nTL && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { + bestRestoreInfo = &rp.second; + restorePoint = rp.first; + } + } + return bestRestoreInfo != nullptr; + } + count++; + } + + + return false; +} + +void gdioutput::setWidgetRestorePoint(const string& id, const string& restorePoint) { + for (auto it = LBI.begin(); it != LBI.end(); ++it) { + if (it->id == id) { + auto& rpTarget = restorePoints[restorePoint]; + auto itNew = LBI.begin(); + int numNew = rpTarget.nLBI; + advance(itNew, numNew); + LBI.splice(itNew, LBI, it); + for (auto& rp : restorePoints) { + if (rp.second.nLBI >= numNew) { + rp.second.nLBI++; + } + } + return; + } + } + + for (auto it = II.begin(); it != II.end(); ++it) { + if (it->id == id) { + auto& rpTarget = restorePoints[restorePoint]; + auto itNew = II.begin(); + int numNew = rpTarget.nII; + advance(itNew, numNew); + II.splice(itNew, II, it); + for (auto& rp : restorePoints) { + if (rp.second.nII >= numNew) { + rp.second.nII++; + } + } + return; + } + } + + for (auto it = BI.begin(); it != BI.end(); ++it) { + if (it->id == id) { + auto& rpTarget = restorePoints[restorePoint]; + auto itNew = BI.begin(); + int numNew = rpTarget.nBI; + advance(itNew, numNew); + BI.splice(itNew, BI, it); + for (auto& rp : restorePoints) { + if (rp.second.nBI >= numNew) { + rp.second.nBI++; + } + } + return; + } + } + + for (auto it = TL.begin(); it != TL.end(); ++it) { + if (it->id == id) { + auto& rpTarget = restorePoints[restorePoint]; + auto itNew = TL.begin(); + int numNew = rpTarget.nTL; + advance(itNew, numNew); + TL.splice(itNew, TL, it); + for (auto& rp : restorePoints) { + if (rp.second.nTL >= numNew) { + rp.second.nTL++; + } + } + return; + } + } +} + void gdioutput::restoreInternal(const RestoreInfo &ri) { int toolRemove=toolTips.size()-ri.nTooltip; @@ -5009,9 +5347,8 @@ void gdioutput::restoreInternal(const RestoreInfo &ri) int lbiRemove=LBI.size()-ri.nLBI; while (lbiRemove>0 && LBI.size()>0) { ListBoxInfo &lbi=LBI.back(); - lbi.callBack = 0; // Avoid kill focus event here - lbi.setHandler(0); - + lbi.callBack = nullptr; // Avoid kill focus event here + lbi.clearHandler(); DestroyWindow(lbi.hWnd); if (lbi.writeLock) hasCleared = true; @@ -5026,13 +5363,16 @@ void gdioutput::restoreInternal(const RestoreInfo &ri) tlRemove--; } itTL=TL.begin(); + updateImageReferences(); + // Clear cache of shown strings shownStrings.clear(); int biRemove=BI.size()-ri.nBI; while (biRemove>0 && BI.size()>0) { ButtonInfo &bi=BI.back(); - + bi.callBack = nullptr; + bi.clearHandler(); DestroyWindow(bi.hWnd); biByHwnd.erase(bi.hWnd); BI.pop_back(); @@ -5043,8 +5383,8 @@ void gdioutput::restoreInternal(const RestoreInfo &ri) while (iiRemove>0 && II.size()>0) { InputInfo &ii=II.back(); - ii.callBack = 0; // Avoid kill focus event here - ii.setHandler(0); + ii.callBack = nullptr; // Avoid kill focus event here + ii.clearHandler(); DestroyWindow(ii.hWnd); iiByHwnd.erase(ii.hWnd); II.pop_back(); @@ -5080,6 +5420,13 @@ void gdioutput::restoreInternal(const RestoreInfo &ri) CurrentY=ri.sCY; onClear = ri.onClear; postClear = ri.postClear; + + for (auto it = restorePoints.begin(); it != restorePoints.end(); ) { + if (!ri.restorePoints.count(it->first) && (& it->second != &ri)) + it = restorePoints.erase(it); + else + ++it; + } } void gdioutput::restore(const string &id, bool DoRefresh) @@ -5132,6 +5479,9 @@ bool gdioutput::canClear() try { return onClear(this, GUI_CLEAR, 0)!=0; } + catch (const meosCancel&) { + return false; + } catch (meosException & ex) { if (isTestMode) throw ex; @@ -5241,7 +5591,7 @@ RectangleInfo &RectangleInfo::changeDimension(gdioutput &gdi, int dx, int dy) { return *this; } -RectangleInfo &gdioutput::addRectangle(RECT &rc, GDICOLOR color, bool drawBorder, bool addFirst) { +RectangleInfo &gdioutput::addRectangle(const RECT &rc, GDICOLOR color, bool drawBorder, bool addFirst) { RectangleInfo ri; ri.rc.left = min(rc.left, rc.right); @@ -5867,7 +6217,6 @@ void gdioutput::tableCB(ButtonInfo &bu, Table *t) void gdioutput::enableTables() { useTables=true; -#ifndef MEOSDB if (!Tables.empty()) { auto &t = Tables.front().table; if (toolbar == 0) @@ -5897,7 +6246,6 @@ void gdioutput::enableTables() toolbar->show(); } } -#endif } void gdioutput::processToolbarMessage(const string &id, Table *tbl) { @@ -5914,6 +6262,8 @@ void gdioutput::processToolbarMessage(const string &id, Table *tbl) { tableCB(bi, tbl); getRecorder().record(cmd); } + catch (const meosCancel&) { + } catch (meosException &ex) { msg = ex.wwhat(); } @@ -5930,8 +6280,6 @@ void gdioutput::processToolbarMessage(const string &id, Table *tbl) { alert(msg); } -#ifndef MEOSDB - HWND gdioutput::getToolbarWindow() const { if (!toolbar) return 0; @@ -5949,17 +6297,6 @@ void gdioutput::activateToolbar(bool active) { return; toolbar->activate(active); } -#else - HWND gdioutput::getToolbarWindow() const { - return 0; - } - - bool gdioutput::hasToolbar() const { - return false; - } -#endif - - void gdioutput::disableTables() { @@ -7051,8 +7388,16 @@ int gdioutput::dbGetStringCount(const string &str, bool subString) const { count++; } else { - if (it->text.find(wstr) != string::npos) - count++; + size_t off = 0; + while(off < it->text.size()) { + off = it->text.find(wstr, off); + if (off != string::npos) { + count++; + off++; + } + else + break; + } } } return count; diff --git a/code/gdioutput.h b/code/gdioutput.h index 434bce7..a2240e5 100644 --- a/code/gdioutput.h +++ b/code/gdioutput.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -99,7 +99,7 @@ class AutoCompleteInfo; class Recorder; -class gdioutput { +class gdioutput { protected: string tag; // Database error state warning @@ -109,30 +109,30 @@ protected: bool useTables; // Set to true when in test mode bool isTestMode; - + bool highContrast; void deleteFonts(); void constructor(double _scale); void updateStringPosCache(); - vector shownStrings; + vector shownStrings; - void enableCheckBoxLink(TextInfo &ti, bool enable); + void enableCheckBoxLink(TextInfo& ti, bool enable); //void CalculateCS(TextInfo &text); //void printPage(PrinterObject &po, int StartY, int &EndY, bool calculate); - void printPage(PrinterObject &po, const PageInfo &pageInfo, RenderedPage &page); - bool startDoc(PrinterObject &po); + void printPage(PrinterObject& po, const PageInfo& pageInfo, RenderedPage& page); + bool startDoc(PrinterObject& po); - bool getSelectedItem(ListBoxInfo &lbi); - bool doPrint(PrinterObject &po, PageInfo &pageInfo, pEvent oe, bool respectPageBreak); + bool getSelectedItem(ListBoxInfo& lbi); + bool doPrint(PrinterObject& po, PageInfo& pageInfo, pEvent oe, bool respectPageBreak); - PrinterObject *po_default; + PrinterObject* po_default; - void restoreInternal(const RestoreInfo &ri); + void restoreInternal(const RestoreInfo& ri); - void drawCloseBox(HDC hDC, RECT &Close, bool pressed); + void drawCloseBox(HDC hDC, RECT& Close, bool pressed); void setFontCtrl(HWND hWnd); @@ -145,14 +145,20 @@ protected: //by avoiding to loop through complete TL. list::iterator itTL; + // References into TL for all images + vector imageReferences; + + // Needed after removing images to clear unused references + void updateImageReferences(); + list BI; - unordered_map biByHwnd; + unordered_map biByHwnd; list II; - unordered_map iiByHwnd; + unordered_map iiByHwnd; list LBI; - unordered_map lbiByHwnd; + unordered_map lbiByHwnd; list DataInfo; list Events; @@ -160,7 +166,7 @@ protected: list Tables; list timers; - Toolbar *toolbar; + Toolbar* toolbar; ToolList toolTips; map restorePoints; @@ -191,10 +197,10 @@ protected: mutable map, int> fontHeightCache; map fonts; - const GDIImplFontSet &getCurrentFont() const; - const GDIImplFontSet &getFont(const wstring &font) const; - const GDIImplFontSet &loadFont(const wstring &font); - mutable const GDIImplFontSet *currentFontSet; + const GDIImplFontSet& getCurrentFont() const; + const GDIImplFontSet& getFont(const wstring& font) const; + const GDIImplFontSet& loadFont(const wstring& font); + mutable const GDIImplFontSet* currentFontSet; int MaxX; int MaxY; @@ -203,16 +209,25 @@ protected: int SX; int SY; - int Direction; + enum class FlowDirection { + Down, + Right, + None, + }; + + FlowDirection flowDirection; int OffsetY; //Range 0 -- MaxY int OffsetX; //Range 0 -- MaxX + // Maximum height of a text block of the current view. + int maxTextBlockHeight = 0; + //Set to true if we should not update window during "addText" operations bool manualUpdate; LRESULT ProcessMsgWrp(UINT iMessage, LPARAM lParam, WPARAM wParam); - void getWindowText(HWND hWnd, wstring &text); + void getWindowText(HWND hWnd, wstring& text); double scale; HFONT getGUIFont() const; @@ -223,18 +238,18 @@ protected: mutable DWORD lastColor; mutable wstring lastFont; - void initCommon(double scale, const wstring &font); + void initCommon(double scale, const wstring& font); - void processButtonMessage(ButtonInfo &bi, WPARAM wParam); - void processEditMessage(InputInfo &bi, WPARAM wParam); - void processComboMessage(ListBoxInfo &bi, WPARAM wParam); - void processListMessage(ListBoxInfo &bi, WPARAM wParam); + void processButtonMessage(ButtonInfo& bi, WPARAM wParam); + void processEditMessage(InputInfo& bi, WPARAM wParam); + void processComboMessage(ListBoxInfo& bi, WPARAM wParam); + void processListMessage(ListBoxInfo& bi, WPARAM wParam); void doEnter(); void doEscape(); bool doUpDown(int direction); - FixedTabs *tabs; + FixedTabs* tabs; wstring currentFont; vector< GDIImplFontEnum > enumeratedFonts; @@ -265,7 +280,7 @@ protected: wstring str; bool reached; - ScreenStringInfo(const RECT &r, const wstring &s) { + ScreenStringInfo(const RECT& r, const wstring& s) { rc = r; str = s; reached = false; @@ -283,146 +298,156 @@ protected: friend class TestMeOS; // Recorder, the second member is true if the recorder is owned and should be deleted - pair recorder; - list< pair > subCommands; + pair recorder; + list< pair > subCommands; shared_ptr animationData; shared_ptr autoCompleteInfo; wstring delayedAlert; + + struct GuiMeasure { + int height = 0; + int extraX = 0; + int letterWidth = 0; + float avgCharWidth = 0; + }; + + mutable shared_ptr guiMeasure; + public: - AutoCompleteInfo &addAutoComplete(const string &key); - void clearAutoComplete(const string &key); + AutoCompleteInfo& addAutoComplete(const string& key); + void clearAutoComplete(const string& key); bool hasAutoComplete() const { return autoCompleteInfo != nullptr; } // Return the bounding dimension of the desktop - void getVirtualScreenSize(RECT &rc); + void getVirtualScreenSize(RECT& rc); - void getWindowsPosition(RECT &rc) const; - void setWindowsPosition(const RECT &rc); + void getWindowsPosition(RECT& rc) const; + void setWindowsPosition(const RECT& rc); - - void initRecorder(Recorder *rec); - Recorder &getRecorder(); - string dbPress(const string &id, int extra); - string dbPress(const string &id, const char *extra); - - string dbSelect(const string &id, int data); - void dbInput(const string &id, const string &test); - void dbCheck(const string &id, bool state); - string dbClick(const string &id, int extra); - void dbDblClick(const string &id, int data); - void dbRegisterSubCommand(const SubCommand *cmd, const string &action); + void initRecorder(Recorder* rec); + Recorder& getRecorder(); + string dbPress(const string& id, int extra); + string dbPress(const string& id, const char* extra); + + string dbSelect(const string& id, int data); + void dbInput(const string& id, const string& test); + void dbCheck(const string& id, bool state); + string dbClick(const string& id, int extra); + void dbDblClick(const string& id, int data); + + void dbRegisterSubCommand(const SubCommand* cmd, const string& action); void runSubCommand(); // Add the next answer for a dialog popup - void dbPushDialogAnswer(const string &answer); + void dbPushDialogAnswer(const string& answer); mutable list cmdAnswers; - int dbGetStringCount(const string &str, bool subString) const; + int dbGetStringCount(const string& str, bool subString) const; // Ensure list of stored answers is empty void clearDialogAnswers(bool checkEmpty); - void internalSelect(ListBoxInfo &bi); + void internalSelect(ListBoxInfo& bi); - bool isTest() const {return isTestMode;} - const string &getTag() const {return tag;} - bool hasTag(const string &t) const {return tag == t;} - static const wstring &recodeToWide(const string &input); - static const string &recodeToNarrow(const wstring &input); - - static const wstring &widen(const string &input); - static const string &narrow(const wstring &input); - - static const string &toUTF8(const wstring &input); - static const wstring &fromUTF8(const string &input); + bool isTest() const { return isTestMode; } + const string& getTag() const { return tag; } + bool hasTag(const string& t) const { return tag == t; } + static const wstring& recodeToWide(const string& input); + static const string& recodeToNarrow(const wstring& input); + + static const wstring& widen(const string& input); + static const string& narrow(const wstring& input); + + static const string& toUTF8(const wstring& input); + static const wstring& fromUTF8(const string& input); //void setEncoding(FontEncoding encoding); //FontEncoding getEncoding() const; - void getFontInfo(const TextInfo &ti, FontInfo &fi) const; + void getFontInfo(const TextInfo& ti, FontInfo& fi) const; /** Return true if rendering text should be skipped for this format. */ static bool skipTextRender(int format); - const list &getTL() const {return TL;} + const list& getTL() const { return TL; } - void getEnumeratedFonts(vector< pair > &output) const; - const wstring &getFontName(int id); - double getRelativeFontScale(gdiFonts font, const wchar_t *fontFace) const; + void getEnumeratedFonts(vector< pair >& output) const; + const wstring& getFontName(int id); + double getRelativeFontScale(gdiFonts font, const wchar_t* fontFace) const; - bool isFullScreen() const {return fullScreen;} + bool isFullScreen() const { return fullScreen; } void setFullScreen(bool useFullScreen); - void setColorMode(DWORD bgColor1, DWORD bgColor2 = -1, - DWORD fgColor = -1, const wstring &bgImage = L""); - + void setColorMode(DWORD bgColor1, DWORD bgColor2 = -1, + DWORD fgColor = -1, const wstring& bgImage = L""); + DWORD getFGColor() const; DWORD getBGColor() const; DWORD getBGColor2() const; - const wstring &getBGImage() const; + const wstring& getBGImage() const; - void setAnimationMode(const shared_ptr &mode); + void setAnimationMode(const shared_ptr& mode); void setAutoScroll(double speed); - void getAutoScroll(double &speed, double &pos) const; + void getAutoScroll(double& speed, double& pos) const; void storeAutoPos(double pos); - int getAutoScrollDir() const {return (autoSpeed > 0 ? 1:-1);} + int getAutoScrollDir() const { return (autoSpeed > 0 ? 1 : -1); } int setHighContrastMaxWidth(); - void hideBackground(bool hide) {hideBG = hide;} + void hideBackground(bool hide) { hideBG = hide; } HWND getToolbarWindow() const; bool hasToolbar() const; void activateToolbar(bool active); - void processToolbarMessage(const string &id, Table *data); + void processToolbarMessage(const string& id, Table* data); - void synchronizeListScroll(const string &id1, const string &id2); + void synchronizeListScroll(const string& id1, const string& id2); - FixedTabs &getTabs(); + FixedTabs& getTabs(); // True if up/down is locked, i.e, don't move page bool lockUpDown; - double getScale() const {return scale;} + double getScale() const { return scale; } void enableEditControls(bool enable, bool processAll = false); bool hasEditControl() const; - void setFont(int size, const wstring &font); + void setFont(int size, const wstring& font); int getButtonHeight() const; - int scaleLength(int input) const {return int(scale*input + 0.5);} - + int scaleLength(int input) const { return int(scale * input + 0.5); } + // Fill in current printer settings - void fetchPrinterSettings(PrinterObject &po) const; + void fetchPrinterSettings(PrinterObject& po) const; - void tableCB(ButtonInfo &bu, Table *t); + void tableCB(ButtonInfo& bu, Table* t); - wchar_t *getExtra(const char *id) const; - int getExtraInt(const char *id) const; + wchar_t* getExtra(const char* id) const; + int getExtraInt(const char* id) const; void enableTables(); void disableTables(); - void pasteText(const char *id); - void print(pEvent oe, Table *t = 0, bool printMeOSHeader = true, bool noMargin = false, bool respectPageBreak = true); - void print(PrinterObject &po, pEvent oe, bool printMeOSHeader = true, bool noMargin=false, bool respectPageBreak = true); - void printSetup(PrinterObject &po); - void destroyPrinterDC(PrinterObject &po); + void pasteText(const char* id); + void print(pEvent oe, Table* t = nullptr, bool printMeOSHeader = true, bool noMargin = false, bool respectPageBreak = true); + void print(PrinterObject& po, pEvent oe, bool printMeOSHeader = true, bool noMargin = false, bool respectPageBreak = true); + void printSetup(PrinterObject& po); + void destroyPrinterDC(PrinterObject& po); - void setSelection(const string &id, const set &selection); - void setSelection(const wstring &id, const set &selection) { + void setSelection(const string& id, const set& selection); + void setSelection(const wstring& id, const set& selection) { setSelection(narrow(id), selection); } - - void getSelection(const string &id, set &selection); - HWND getHWNDTarget() const {return hWndTarget;} - HWND getHWNDMain() const {return hWndAppMain;} + void getSelection(const string& id, set& selection); + + HWND getHWNDTarget() const { return hWndTarget; } + HWND getHWNDMain() const { return hWndAppMain; } void scrollToBottom(); void scrollTo(int x, int y); @@ -430,84 +455,88 @@ public: void selectTab(int Id); - void addTable(const shared_ptr
&table, int x, int y); - Table &getTable() const; //Get the (last) table. If needed, add support for named tables... + void addTable(const shared_ptr
& table, int x, int y); + Table& getTable() const; //Get the (last) table. If needed, add support for named tables... - ToolInfo &addToolTip(const string &id, const wstring &tip, HWND hWnd, RECT *rc=0); - ToolInfo *getToolTip(const string &id); - ToolInfo &updateToolTip(const string &id, const wstring &tip); - - HWND getToolTip(){return hWndToolTip;} + ToolInfo& addToolTip(const string& id, const wstring& tip, HWND hWnd, RECT* rc = nullptr); + ToolInfo* getToolTip(const string& id); + ToolInfo& updateToolTip(const string& id, const wstring& tip); + + HWND getToolTip() { return hWndToolTip; } void init(HWND hWnd, HWND hMainApp, HWND hTab); - bool openDoc(const wchar_t *doc); - wstring browseForSave(const vector< pair > &filter, - const wstring &defext, int &FilterIndex); - wstring browseForOpen(const vector< pair > &filter, - const wstring &defext); - wstring browseForFolder(const wstring &folderStart, const wchar_t *descr); - - bool clipOffset(int PageX, int PageY, int &MaxOffsetX, int &MaxOffsetY); - RectangleInfo &addRectangle(RECT &rc, GDICOLOR Color = GDICOLOR(-1), - bool DrawBorder = true, bool addFirst = false); - - RectangleInfo &getRectangle(const char *id); - - DWORD makeEvent(const string &id, const string &origin, - DWORD data, int extraData, bool flushEvent); + bool openDoc(const wchar_t* doc); + wstring browseForSave(const vector< pair >& filter, + const wstring& defext, int& FilterIndex); + wstring browseForOpen(const vector< pair >& filter, + const wstring& defext); + wstring browseForFolder(const wstring& folderStart, const wchar_t* descr); - void unregisterEvent(const string &id); - EventInfo ®isterEvent(const string &id, GUICALLBACK cb); + bool clipOffset(int PageX, int PageY, int& MaxOffsetX, int& MaxOffsetY); + RectangleInfo& addRectangle(const RECT& rc, GDICOLOR Color = GDICOLOR(-1), + bool DrawBorder = true, bool addFirst = false); - int sendCtrlMessage(const string &id); + RectangleInfo& getRectangle(const char* id); + + DWORD makeEvent(const string& id, const string& origin, + DWORD data, int extraData, bool flushEvent); + + void unregisterEvent(const string& id); + EventInfo& registerEvent(const string& id, GUICALLBACK cb); + + int sendCtrlMessage(const string& id); bool canClear(); void setOnClearCb(GUICALLBACK cb); void setPostClearCb(GUICALLBACK cb); - void restore(const string &id="", bool DoRefresh=true); + void restore(const string& id = "", bool DoRefresh = true); /// Restore, but do not update client area size, /// position, zoom, scrollbars, and do not refresh - void restoreNoUpdate(const string &id); + void restoreNoUpdate(const string& id); void setRestorePoint(); - void setRestorePoint(const string &id); + void setRestorePoint(const string& id); - bool removeWidget(const string &id); - bool hideWidget(const string &id, bool hide = true); + bool removeWidget(const string& id); + bool hideWidget(const string& id, bool hide = true); + + // Get and set restore point presence for a widget + bool getWidgetRestorePoint(const string& id, string& restorePoint) const; + void setWidgetRestorePoint(const string& id, const string& restorePoint); void CheckInterfaceTimeouts(DWORD T); - bool RemoveFirstInfoBox(const string &id); - void drawBoxText(HDC hDC, RECT &tr, InfoBox &Box, bool highligh); - void drawBoxes(HDC hDC, RECT &rc); - void drawBox(HDC hDC, InfoBox &Box, RECT &pos); - void addInfoBox(string id, wstring text, int TimeOut=0, GUICALLBACK cb=0); + bool RemoveFirstInfoBox(const string& id); + void drawBoxText(HDC hDC, RECT& tr, InfoBox& Box, bool highligh); + void drawBoxes(HDC hDC, RECT& rc); + void drawBox(HDC hDC, InfoBox& Box, RECT& pos); + void addInfoBox(string id, wstring text, int TimeOut = 0, GUICALLBACK cb = nullptr); void updateObjectPositions(); - void drawBackground(HDC hDC, RECT &rc); - void renderRectangle(HDC hDC, RECT *clipRegion, const RectangleInfo &ri); + void drawBackground(HDC hDC, RECT& rc); + void renderRectangle(HDC hDC, RECT* clipRegion, const RectangleInfo& ri); void updateScrollbars() const; - void setOffsetY(int oy) {OffsetY=oy;} - void setOffsetX(int ox) {OffsetX=ox;} + void setOffsetY(int oy) { OffsetY = oy; } + void setOffsetX(int ox) { OffsetX = ox; } int getPageY() const; int getPageX() const; - int getOffsetY() const {return OffsetY;} - int getOffsetX() const {return OffsetX;} + int getOffsetY() const { return OffsetY; } + int getOffsetX() const { return OffsetX; } - void RenderString(TextInfo &ti, const wstring &text, HDC hDC); - void RenderString(TextInfo &ti, HDC hDC=0); - void calcStringSize(TextInfo &ti, HDC hDC=0) const; - void formatString(const TextInfo &ti, HDC hDC) const; + void RenderString(TextInfo& ti, const wstring& text, HDC hDC); + void RenderString(TextInfo& ti, HDC hDC = 0); + void calcStringSize(TextInfo& ti, HDC hDC = 0) const; + void formatString(const TextInfo& ti, HDC hDC) const; - static wstring getTimerText(TextInfo *tit, DWORD T); + static wstring getTimerText(TextInfo* tit, DWORD T); static wstring getTimerText(int ZeroTime, int format); void fadeOut(string Id, int ms); void setWaitCursor(bool wait); - void setWindowTitle(const wstring &title); - bool selectFirstItem(const string &name); + void setWindowTitle(const wstring& title); + bool selectFirstItem(const string& name); void removeString(string Id); void refresh() const; void refreshFast() const; @@ -515,269 +544,295 @@ public: void takeShownStringsSnapshot(); void refreshSmartFromSnapshot(bool allowMoveOffset); - void dropLine(double lines=1){CurrentY+=int(lineHeight*lines); MaxY=max(MaxY, CurrentY);} - int getCX() const {return CurrentX;} - int getCY() const {return CurrentY;} - int getWidth() const {return MaxX;} - int getHeight() const {return MaxY;} - void getTargetDimension(int &x, int &y) const; + void dropLine(double lines = 1) { CurrentY += int(lineHeight * lines); MaxY = max(MaxY, CurrentY); } + int getCX() const { return CurrentX; } + int getCY() const { return CurrentY; } + int getWidth() const { return MaxX; } + int getHeight() const { return MaxY; } + void getTargetDimension(int& x, int& y) const; pair getPos() const { return make_pair(CurrentX, CurrentY); } - void setPos(const pair &p) { CurrentX = p.first; CurrentY = p.second; } + void setPos(const pair& p) { CurrentX = p.first; CurrentY = p.second; } - void setCX(int cx){CurrentX=cx;} - void setCY(int cy){CurrentY=cy;} - int getLineHeight() const {return lineHeight;} - int getLineHeight(gdiFonts font, const wchar_t *face) const; + void setCX(int cx) { CurrentX = cx; } + void setCY(int cy) { CurrentY = cy; } + int getLineHeight() const { return lineHeight; } + int getLineHeight(gdiFonts font, const wchar_t* face) const; - BaseInfo *setInputFocus(const string &id, bool select=false); - InputInfo *getInputFocus(); + BaseInfo* setInputFocus(const string& id, bool select = false); + InputInfo* getInputFocus(); - void enableInput(const char *id, bool acceptMissing = false, - int requireExtraMatch = -1) {setInputStatus(id, true, acceptMissing, requireExtraMatch);} - void disableInput(const char *id, bool acceptMissing = false, - int requireExtraMatch = -1) {setInputStatus(id, false, acceptMissing, requireExtraMatch);} - void setInputStatus(const char *id, bool status, bool acceptMissing = false, int requireExtraMatch = -1); - void setInputStatus(const string &id, bool status, bool acceptMissing = false, int requireExtraMatch = -1) - {setInputStatus(id.c_str(), status, acceptMissing, requireExtraMatch);} + void enableInput(const char* id, bool acceptMissing = false, + int requireExtraMatch = -1) { + setInputStatus(id, true, acceptMissing, requireExtraMatch); + } + void disableInput(const char* id, bool acceptMissing = false, + int requireExtraMatch = -1) { + setInputStatus(id, false, acceptMissing, requireExtraMatch); + } + void setInputStatus(const char* id, bool status, bool acceptMissing = false, int requireExtraMatch = -1); + void setInputStatus(const string& id, bool status, bool acceptMissing = false, int requireExtraMatch = -1) + { + setInputStatus(id.c_str(), status, acceptMissing, requireExtraMatch); + } - void setTabStops(const string &Name, int t1, int t2=-1); - void setData(const string &id, DWORD data); - void setData(const string &id, void *data); - void setData(const string &id, const string &data); + void setTabStops(const string& name, int t1, int t2 = -1); + void setData(const string& id, DWORD data); + void setData(const string& id, void* data); + void setData(const string& id, const string& data); - void *getData(const string &id) const; - int getDataInt(const string &id) const { return int(size_t(getData(id))); } + void* getData(const string& id) const; + int getDataInt(const string& id) const { return int(size_t(getData(id))); } - bool getData(const string &id, string &out) const; + bool getData(const string& id, string& out) const; - DWORD selectColor(wstring &def, DWORD input); + DWORD selectColor(wstring& def, DWORD input); - void autoRefresh(bool flag) {manualUpdate = !flag;} + void autoRefresh(bool flag) { manualUpdate = !flag; } - bool getData(const string &id, DWORD &data) const; - bool hasData(const char *id) const; + bool getData(const string& id, DWORD& data) const; + bool hasData(const char* id) const; - int getItemDataByName(const char *id, const char *name) const; - bool selectItemByData(const char *id, int data); - bool selectItemByIndex(const char *id, int index); + 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); + void removeSelected(const char* id); - bool selectItemByData(const string &id, int data) { + bool selectItemByData(const string& id, int data) { return selectItemByData(id.c_str(), data); } - enum AskAnswer {AnswerNo = 0, AnswerYes = 1, AnswerCancel = 2}; - bool ask(const wstring &s); - AskAnswer askCancel(const wstring &s); + enum class AskAnswer { AnswerNo = 0, AnswerYes = 1, AnswerCancel = 2, AnswerOK = 3 }; + bool ask(const wstring& s); + AskAnswer askOkCancel(const wstring& s); + AskAnswer askCancel(const wstring& s); - void alert(const string &msg) const; - void alert(const wstring &msg) const; + void alert(const string& msg) const; + void alert(const wstring& msg) const; // Alert from main thread (via callback) void delayAlert(const wstring& msg); // Get and clear any delayed alert wstring getDelayedAlert(); - void fillDown(){Direction=1;} - void fillRight(){Direction=0;} - void fillNone(){Direction=-1;} - void newColumn(){CurrentY=START_YP; CurrentX=MaxX+10;} - void newRow(){CurrentY=MaxY; CurrentX=10;} + void fillDown() { flowDirection = FlowDirection::Down; } + void fillRight() { flowDirection = FlowDirection::Right; } + void fillNone() { flowDirection = FlowDirection::None; } - void pushX(){SX=CurrentX;} - void pushY(){SY=CurrentY;} - void popX(){CurrentX=SX;} - void popY(){CurrentY=SY;} + void newColumn() { CurrentY = START_YP; CurrentX = MaxX + 10; } + void newRow() { CurrentY = MaxY; CurrentX = 10; } + + void pushX() { SX = CurrentX; } + void pushY() { SY = CurrentY; } + void popX() { CurrentX = SX; } + void popY() { CurrentY = SY; } bool updatePos(int x, int y, int width, int height); void adjustDimension(int width, int height); /** Return a selected item*/ - bool getSelectedItem(const string &id, ListBoxInfo &lbi); + bool getSelectedItem(const string& id, ListBoxInfo& lbi); /** Get number of items in a list box.'*/ - int getNumItems(const char *id); + 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); + pair getSelectedItem(const string& id); + pair getSelectedItem(const char* id); - bool addItem(const string &id, const wstring &text, size_t data = 0); - bool addItem(const string &id, const vector< pair > &items); - - void filterOnData(const string &id, const unordered_set &filter); + bool addItem(const string& id, const wstring& text, size_t data = 0); + bool addItem(const string& id, const vector< pair >& items); - bool clearList(const string &id); + void filterOnData(const string& id, const unordered_set& filter); - bool hasWidget(const string &id) const; - - const wstring &getText(const char *id, bool acceptMissing = false, int requireExtraMatch = -1) const; - - BaseInfo &getBaseInfo(const string &id) const { - return getBaseInfo(id.c_str()); + bool clearList(const string& id); + + bool hasWidget(const string& id) const; + + const wstring& getText(const char* id, bool acceptMissing = false, int requireExtraMatch = -1) const; + + BaseInfo& getBaseInfo(const string& id, int requireExtraMatch = -1) const { + return getBaseInfo(id.c_str(), requireExtraMatch); } - BaseInfo &getBaseInfo(const char *id) const; - BaseInfo &getBaseInfo(const wchar_t *id) const { - return getBaseInfo(narrow(id).c_str()); + BaseInfo& getBaseInfo(const char* id, int requireExtraMatch = -1) const; + BaseInfo& getBaseInfo(const wchar_t* id, int requireExtraMatch = -1) const { + return getBaseInfo(narrow(id).c_str(), requireExtraMatch); } - int getTextNo(const char *id, bool acceptMissing = false) const; - int getTextNo(const string &id, bool acceptMissing = false) const - {return getTextNo(id.c_str(), acceptMissing);} + int getTextNo(const char* id, bool acceptMissing = false) const; + int getTextNo(const string& id, bool acceptMissing = false) const + { + return getTextNo(id.c_str(), acceptMissing); + } - const wstring &getText(const string &id, bool acceptMissing = false) const - {return getText(id.c_str(), acceptMissing);} + const wstring& getText(const string& id, bool acceptMissing = false) const + { + return getText(id.c_str(), acceptMissing); + } - // Insert text and notify "focusList" - bool insertText(const string &id, const wstring &text); + // Insert text and notify "focusList" + bool insertText(const string& id, const wstring& text); - int getFontHeight(int format, const wstring &fontFace) const; + int getFontHeight(int format, const wstring& fontFace) const; // The html version should be UTF-8. - void copyToClipboard(const string &html, - const wstring &txt) const; + void copyToClipboard(const string& html, + const wstring& txt) const; - BaseInfo *setTextTranslate(const char *id, const wstring &text, bool update=false); - BaseInfo *setTextTranslate(const char *id, const wchar_t *text, bool update=false); - BaseInfo *setTextTranslate(const string &id, const wstring &text, bool update=false); + BaseInfo* setTextTranslate(const char* id, const wstring& text, bool update = false); + BaseInfo* setTextTranslate(const char* id, const wchar_t* text, bool update = false); + BaseInfo* setTextTranslate(const string& id, const wstring& text, bool update = false); - BaseInfo *setText(const char *id, const wstring &text, bool update=false, int requireExtraMatch = -1); - BaseInfo *setText(const wchar_t *id, const wstring &text, bool update=false) { + BaseInfo* setText(const char* id, const wstring& text, bool update = false, int requireExtraMatch = -1); + BaseInfo* setText(const wchar_t* id, const wstring& text, bool update = false) { return setText(narrow(id), text, update); } - BaseInfo *setText(const char *id, int number, bool update=false); - BaseInfo *setText(const string &id, const wstring &text, bool update=false) - {return setText(id.c_str(), text, update);} - BaseInfo *setTextZeroBlank(const char *id, int number, bool update=false); - BaseInfo *setText(const string &id, int number, bool update=false) - {return setText(id.c_str(), number, update);} + BaseInfo* setText(const char* id, int number, bool update = false); + BaseInfo* setText(const string& id, const wstring& text, bool update = false) + { + return setText(id.c_str(), text, update); + } + BaseInfo* setTextZeroBlank(const char* id, int number, bool update = false); + BaseInfo* setText(const string& id, int number, bool update = false) + { + return setText(id.c_str(), number, update); + } void clearPage(bool autoRefresh, bool keepToolbar = false); - void TabFocus(int direction=1); + void TabFocus(int direction = 1); void enter(); void escape(); bool upDown(int direction); void keyCommand(KeyCommandCode code); LRESULT ProcessMsg(UINT iMessage, LPARAM lParam, WPARAM wParam); - void setWindow(HWND hWnd){hWndTarget=hWnd;} + void setWindow(HWND hWnd) { hWndTarget = hWnd; } - void scaleSize(double scale, bool allowSmallScale = false, bool doRefresh = true); + enum class ScaleOperation { + NoRefresh, + Refresh, + NoUpdate + }; - ButtonInfo &addButton(const string &id, const wstring &text, GUICALLBACK cb = 0, - const wstring &tooltip = L""); + void scaleSize(double scale, bool allowSmallScale = false, ScaleOperation type = ScaleOperation::Refresh); - ButtonInfo &addButton(int x, int y, const string &id, const wstring &text, - GUICALLBACK cb = 0, const wstring &tooltop=L""); + ButtonInfo& addButton(const string& id, const wstring& text, GUICALLBACK cb = nullptr, + const wstring& tooltip = L""); - ButtonInfo &addButton(int x, int y, int w, const string &id, const wstring &text, - GUICALLBACK cb, const wstring &tooltop, bool absPos, bool hasState); + ButtonInfo& addButton(int x, int y, const string& id, const wstring& text, + GUICALLBACK cb = nullptr, const wstring& tooltop = L""); + + ButtonInfo& addButton(int x, int y, int w, const string& id, const wstring& text, + GUICALLBACK cb, const wstring& tooltop, bool absPos, bool hasState); + + ButtonInfo& addCheckbox(const string& id, const wstring& text, GUICALLBACK cb = nullptr, + bool Checked = true, const wstring& tooltip = L""); + ButtonInfo& addCheckbox(int x, int y, const string& id, + const wstring& text, GUICALLBACK cb = nullptr, + bool checked = true, const wstring& tooltop = L"", bool absPos = false); - ButtonInfo &addCheckbox(const string &id, const wstring &text, GUICALLBACK cb=0, - bool Checked=true, const wstring &tooltip = L""); - ButtonInfo &addCheckbox(int x, int y, const string &id, - const wstring &text, GUICALLBACK cb=0, - bool checked=true, const wstring &tooltop = L"", bool absPos=false); - /// XXX Temporary - ButtonInfo &addButton(const string &id, const string &text, GUICALLBACK cb = 0, const string &tooltip=""); + ButtonInfo& addButton(const string& id, const string& text, GUICALLBACK cb = nullptr, const string& tooltip = ""); - ButtonInfo &addButton(int x, int y, const string &id, const string &text, - GUICALLBACK cb = 0, const string &tooltop=""); - ButtonInfo &addButton(int x, int y, int w, const string &id, const string &text, - GUICALLBACK cb, const string &tooltop, bool AbsPos, bool hasState); + ButtonInfo& addButton(int x, int y, const string& id, const string& text, + GUICALLBACK cb = nullptr, const string& tooltop = ""); + ButtonInfo& addButton(int x, int y, int w, const string& id, const string& text, + GUICALLBACK cb, const string& tooltop, bool AbsPos, bool hasState); - ButtonInfo &addCheckbox(const string &id, const string &text, GUICALLBACK cb=0, bool Checked=true, const string &Help=""); - ButtonInfo &addCheckbox(int x, int y, const string &id, const string &text, GUICALLBACK cb=0, bool Checked=true, const string &Help="", bool AbsPos=false); + ButtonInfo& addCheckbox(const string& id, const string& text, GUICALLBACK cb = nullptr, bool Checked = true, const string& Help = ""); + ButtonInfo& addCheckbox(int x, int y, const string& id, const string& text, GUICALLBACK cb = nullptr, bool Checked = true, const string& Help = "", bool AbsPos = false); /// XXX - - bool isChecked(const string &id); - void check(const string &id, bool state, bool keepOriginalState = false); - bool isInputChanged(const string &exclude); + bool isChecked(const string& id); + void check(const string& id, bool state, bool keepOriginalState = false); + + bool isInputChanged(const string& exclude); /** Get width of input widget with specified length (chars)*/ - pair getInputDimension(int length) const; + pair getInputDimension(int length) const; - InputInfo &addInput(const string &id, const wstring &text = L"", int length=16, GUICALLBACK cb=0, - const wstring &Explanation = L"", const wstring &tooltip=L""); - InputInfo &addInput(int x, int y, const string &id, const wstring &text, int length, - GUICALLBACK cb=0, const wstring &Explanation = L"", const wstring &tooltip=L""); + InputInfo& addInput(const string& id, const wstring& text = L"", int length = 16, GUICALLBACK cb = nullptr, + const wstring& Explanation = L"", const wstring& tooltip = L""); + InputInfo& addInput(int x, int y, const string& id, const wstring& text, int length, + GUICALLBACK cb = nullptr, const wstring& Explanation = L"", const wstring& tooltip = L""); - InputInfo *replaceSelection(const char *id, const wstring &text); + InputInfo* replaceSelection(const char* id, const wstring& text); - InputInfo &addInputBox(const string &id, int width, int height, const wstring &text, - GUICALLBACK cb, const wstring &explanation); + InputInfo& addInputBox(const string& id, int width, int height, const wstring& text, + GUICALLBACK cb, const wstring& explanation); - InputInfo &addInputBox(const string &id, int x, int y, int width, int height, - const wstring &text, GUICALLBACK cb, const wstring &explanation); + InputInfo& addInputBox(const string& id, int x, int y, int width, int height, + const wstring& text, GUICALLBACK cb, const wstring& explanation); - ListBoxInfo &addListBox(const string &id, int width, int height, GUICALLBACK cb=0, const wstring &explanation=L"", const wstring &tooltip=L"", bool multiple=false); - ListBoxInfo &addListBox(int x, int y, const string &id, int width, int height, GUICALLBACK cb=0, const wstring &explanation=L"", const wstring &tooltip=L"", bool multiple=false); + ListBoxInfo& addListBox(const string& id, int width, int height, GUICALLBACK cb = nullptr, const wstring& explanation = L"", const wstring& tooltip = L"", bool multiple = false); + ListBoxInfo& addListBox(int x, int y, const string& id, int width, int height, GUICALLBACK cb = nullptr, const wstring& explanation = L"", const wstring& tooltip = L"", bool multiple = false); - ListBoxInfo &addSelection(const string &id, int width, int height, GUICALLBACK cb=0, const wstring &explanation=L"", const wstring &tooltip=L""); - ListBoxInfo &addSelection(int x, int y, const string &id, int width, int height, GUICALLBACK cb=0, const wstring &explanation=L"", const wstring &tooltip=L""); + ListBoxInfo& addSelection(const string& id, int width, int height, GUICALLBACK cb = nullptr, const wstring& explanation = L"", const wstring& tooltip = L""); + ListBoxInfo& addSelection(int x, int y, const string& id, int width, int height, GUICALLBACK cb = nullptr, const wstring& explanation = L"", const wstring& tooltip = L""); - ListBoxInfo &addCombo(const string &id, int width, int height, GUICALLBACK cb=0, const wstring &explanation=L"", const wstring &tooltip=L""); - ListBoxInfo &addCombo(int x, int y, const string &id, int width, int height, GUICALLBACK cb=0, const wstring &explanation=L"", const wstring &tooltip=L""); + ListBoxInfo& addCombo(const string& id, int width, int height, GUICALLBACK cb = nullptr, const wstring& explanation = L"", const wstring& tooltip = L""); + ListBoxInfo& addCombo(int x, int y, const string& id, int width, int height, GUICALLBACK cb = nullptr, const wstring& explanation = L"", const wstring& tooltip = L""); // Grows a listbox, selection, combo in X-direction to fit current contents. Returns true if changed. - bool autoGrow(const char *id); + bool autoGrow(const char* id); + + void setListDescription(const wstring& desc); - void setListDescription(const wstring &desc); - // Wide versions - TextInfo &addString(const string &id, int format, const wstring &text, GUICALLBACK cb=0); - TextInfo &addString(const string &id, int yp, int xp, int format, const wstring &text, - int xlimit=0, GUICALLBACK cb=0, const wchar_t *fontFace = 0); - TextInfo &addString(const char *id, int format, const wstring &text, GUICALLBACK cb=0); - TextInfo &addString(const char *id, int yp, int xp, int format, const wstring &text, - int xlimit=0, GUICALLBACK cb=0, const wchar_t *fontFace = 0); + TextInfo& addString(const string& id, int format, const wstring& text, GUICALLBACK cb = nullptr); + TextInfo& addString(const string& id, int yp, int xp, int format, const wstring& text, + int xlimit = 0, GUICALLBACK cb = nullptr, const wchar_t* fontFace = nullptr); + TextInfo& addString(const char* id, int format, const wstring& text, GUICALLBACK cb = nullptr); + TextInfo& addString(const char* id, int yp, int xp, int format, const wstring& text, + int xlimit = 0, GUICALLBACK cb = nullptr, const wchar_t* fontFace = nullptr); // Untranslated versions - TextInfo &addStringUT(int yp, int xp, int format, const wstring &text, - int xlimit=0, GUICALLBACK cb=0, const wchar_t *fontFace = 0); - TextInfo &addStringUT(int format, const wstring &text, GUICALLBACK cb=0); + TextInfo& addStringUT(int yp, int xp, int format, const wstring& text, + int xlimit = 0, GUICALLBACK cb = nullptr, const wchar_t* fontFace = nullptr); + TextInfo& addStringUT(int format, const wstring& text, GUICALLBACK cb = nullptr); // Temporary XXX - TextInfo &addString(const string &id, int format, const string &text, GUICALLBACK cb=0); - TextInfo &addString(const string &id, int yp, int xp, int format, const string &text, - int xlimit=0, GUICALLBACK cb=0, const wchar_t *fontFace = 0); - TextInfo &addString(const char *id, int format, const string &text, GUICALLBACK cb=0); - TextInfo &addString(const char *id, int yp, int xp, int format, const string &text, - int xlimit=0, GUICALLBACK cb=0, const wchar_t *fontFace = 0); + TextInfo& addString(const string& id, int format, const string& text, GUICALLBACK cb = nullptr); + TextInfo& addString(const string& id, int yp, int xp, int format, const string& text, + int xlimit = 0, GUICALLBACK cb = nullptr, const wchar_t* fontFace = nullptr); + TextInfo& addString(const char* id, int format, const string& text, GUICALLBACK cb = nullptr); + TextInfo& addString(const char* id, int yp, int xp, int format, const string& text, + int xlimit = 0, GUICALLBACK cb = nullptr, const wchar_t* fontFace = nullptr); // Untranslated versions - TextInfo &addStringUT(int yp, int xp, int format, const string &text, - int xlimit=0, GUICALLBACK cb=0, const wchar_t *fontFace = 0); - TextInfo &addStringUT(int format, const string &text, GUICALLBACK cb=0); + TextInfo& addStringUT(int yp, int xp, int format, const string& text, + int xlimit = 0, GUICALLBACK cb = nullptr, const wchar_t* fontFace = nullptr); + TextInfo& addStringUT(int format, const string& text, GUICALLBACK cb = nullptr); // XXX Temporary - TextInfo &addTimer(int yp, int xp, int format, DWORD ZeroTime, - int xlimit=0, GUICALLBACK cb=0, int TimeOut=NOTIMEOUT, const wchar_t *fontFace = 0); - TextInfo &addTimeout(int TimeOut, GUICALLBACK cb); + TextInfo& addImage(const string& id, int yp, int xp, int format, const wstring& imageId, + int width = 0, int height = 0, GUICALLBACK cb = nullptr); - void removeTimeoutMilli(const string &id); - TimerInfo &addTimeoutMilli(int timeOut, const string &id, GUICALLBACK cb); - void timerProc(TimerInfo &timer, DWORD timeout); + TextInfo& addTimer(int yp, int xp, int format, DWORD ZeroTime, + int xlimit = 0, GUICALLBACK cb = nullptr, int TimeOut = NOTIMEOUT, const wchar_t* fontFace = nullptr); - void removeHandler(GuiHandler *h); + TextInfo& addTimeout(int TimeOut, GUICALLBACK cb); - void draw(HDC hDC, RECT &windowArea, RECT &drawArea); + void removeTimeoutMilli(const string& id); + TimerInfo& addTimeoutMilli(int timeOut, const string& id, GUICALLBACK cb); + void timerProc(TimerInfo& timer, DWORD timeout); + + void removeHandler(GuiHandler* h); + + void draw(HDC hDC, RECT& windowArea, RECT& drawArea); void closeWindow(); - int popupMenu(int x, int y, const vector> &menuItems) const; + int popupMenu(int x, int y, const vector>& menuItems) const; void setDBErrorState(bool state); friend class Table; - friend gdioutput *createExtraWindow(const string &tag, const wstring &title, int max_x, int max_y, bool fixedSize); + friend gdioutput* createExtraWindow(const string& tag, const wstring& title, int max_x, int max_y, bool fixedSize); - gdioutput(const string &tag, double _scale); - gdioutput(double _scale, HWND hWndTarget, const PrinterObject &defprn); + gdioutput(const string& tag, double _scale); + gdioutput(double _scale, HWND hWndTarget, const PrinterObject& defprn); virtual ~gdioutput(); }; diff --git a/code/gdistructures.h b/code/gdistructures.h index f143f33..c325d9f 100644 --- a/code/gdistructures.h +++ b/code/gdistructures.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -82,6 +82,10 @@ public: GuiHandler &getHandler() const; BaseInfo &setHandler(const GuiHandler *h) {handler = const_cast(h); return *this;} BaseInfo &setHandler(const shared_ptr &h) { managedHandler = h; return *this; } + void clearHandler() { + handler = nullptr; + managedHandler.reset(); + } }; class RestoreInfo : public BaseInfo @@ -108,6 +112,12 @@ public: GUICALLBACK onClear; GUICALLBACK postClear; + set restorePoints; + + bool operator<(const RestoreInfo &r) const { + return nLBI < r.nLBI || nBI < r.nBI || nII < r.nII || nTL < r.nTL || nRect < r.nRect || nData < r.nData; + } + HWND getControlWindow() const {throw std::exception("Unsupported");} }; @@ -387,11 +397,12 @@ private: DWORD dataInt; wstring dataString; gdioutput *parent; - TimerInfo(gdioutput *gdi, GUICALLBACK cb) : parent(gdi), callBack(cb), setWnd(0), timerId(++globalTimerId) {} HWND setWnd; public: ~TimerInfo(); - + TimerInfo(gdioutput* gdi, GUICALLBACK cb) : parent(gdi), callBack(cb), setWnd(0), timerId(++globalTimerId) {} + TimerInfo(const TimerInfo&) = delete; + TimerInfo& operator=(const TimerInfo&) = delete; int getId() const { return timerId; } BaseInfo &setExtra(const wchar_t *e) {return BaseInfo::setExtra(e);} BaseInfo &setExtra(int e) {return BaseInfo::setExtra(e);} diff --git a/code/generalresult.cpp b/code/generalresult.cpp index fae37d3..40a86e1 100644 --- a/code/generalresult.cpp +++ b/code/generalresult.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -266,7 +266,7 @@ template void GeneralResult::sort(vector &rt, SortOrder so) const ps = None; vector< pair > arr(rt.size()); - const int maxT = 3600 * 100; + const int maxT = timeConstHour * 100; for (size_t k = 0; k < rt.size(); k++) { arr[k].first = 0; if (ps == ClassWise) { @@ -293,7 +293,7 @@ template void GeneralResult::sort(vector &rt, SortOrder so) const ord = maxT - ord; } else if (so == SortByStartTime || so == ClassStartTime || - so == ClassStartTimeClub) { + so == ClassStartTimeClub || so == ClubClassStartTime) { ord = tr.getStartTime(); } @@ -593,7 +593,7 @@ int TotalResultAtControl::deduceTime(oRunner &runner, int startTime) const { pair TotalResultAtControl::score(oRunner &runner, RunnerStatus st, int time, int points, bool asTeamMember) const { if (asTeamMember) return make_pair(runner.getLegNumber(), 0); - const int TK = 3600 * 24 * 7; + const int TK = timeConstHour * 24 * 7; RunnerStatus inputStatus = StatusOK; if (runner.getTeam()) { @@ -781,7 +781,7 @@ RunnerStatus DynamicResult::toStatus(int status) const { pair DynamicResult::score(oTeam &team, RunnerStatus st, int time, int points) const { if (getMethod(MTScore)) { - parser.addSymbol("ComputedTime", time); + parser.addSymbol("ComputedTime", time / timeConstSecond); parser.addSymbol("ComputedStatus", st); parser.addSymbol("ComputedPoints", points); return make_pair(0, getMethod(MTScore)->evaluate(parser)); @@ -801,7 +801,7 @@ RunnerStatus DynamicResult::deduceStatus(oTeam &team) const { int DynamicResult::deduceTime(oTeam &team) const { if (getMethod(MDeduceTTime)) - return getMethod(MDeduceTTime)->evaluate(parser); + return getMethod(MDeduceTTime)->evaluate(parser) * timeConstSecond + team.getSubSeconds(); else if (getMethodSource(MDeduceTTime).empty()) return GeneralResult::deduceTime(team); else throw meosException("Syntax error"); @@ -817,7 +817,7 @@ int DynamicResult::deducePoints(oTeam &team) const { pair DynamicResult::score(oRunner &runner, RunnerStatus st, int time, int points, bool asTeamMember) const { if (getMethod(MRScore)) { - parser.addSymbol("ComputedTime", time); + parser.addSymbol("ComputedTime", time / timeConstSecond); parser.addSymbol("ComputedStatus", st); parser.addSymbol("ComputedPoints", points); return make_pair(0, getMethod(MRScore)->evaluate(parser)); @@ -837,7 +837,7 @@ RunnerStatus DynamicResult::deduceStatus(oRunner &runner) const { int DynamicResult::deduceTime(oRunner &runner, int startTime) const { if (getMethod(MDeduceRTime)) - return getMethod(MDeduceRTime)->evaluate(parser); + return getMethod(MDeduceRTime)->evaluate(parser) * timeConstSecond + runner.getSubSeconds(); else if (getMethodSource(MDeduceRTime).empty()) return GeneralResult::deduceTime(runner, startTime); else throw meosException("Syntax error"); @@ -1160,24 +1160,24 @@ void DynamicResult::prepareCommon(oAbstractRunner &runner, bool classResult) con if (st == StatusUnknown && ft > 0) st = StatusOK; parser.addSymbol("Status", st); - parser.addSymbol("Start", runner.getStartTime()); - parser.addSymbol("Finish", ft); - parser.addSymbol("Time", runner.getRunningTime(useComputed)); + parser.addSymbol("Start", runner.getStartTime() / timeConstSecond); + parser.addSymbol("Finish", ft / timeConstSecond); + parser.addSymbol("Time", runner.getRunningTime(useComputed) / timeConstSecond); parser.addSymbol("Place", runner.getPlace(false)); parser.addSymbol("Points", runner.getRogainingPoints(useComputed, false)); parser.addSymbol("PointReduction", runner.getRogainingReduction(useComputed)); - parser.addSymbol("PointOvertime", runner.getRogainingOvertime(useComputed)); + parser.addSymbol("PointOvertime", runner.getRogainingOvertime(useComputed) / timeConstSecond); parser.addSymbol("PointGross", runner.getRogainingPointsGross(useComputed)); parser.addSymbol("PointAdjustment", runner.getPointAdjustment()); - parser.addSymbol("TimeAdjustment", runner.getTimeAdjustment()); + parser.addSymbol("TimeAdjustment", runner.getTimeAdjustment(true) / timeConstSecond); - parser.addSymbol("TotalStatus", runner.getTotalStatus()); - parser.addSymbol("TotalTime", runner.getTotalRunningTime()); + parser.addSymbol("TotalStatus", runner.getTotalStatus(false)); + parser.addSymbol("TotalTime", runner.getTotalRunningTime()/timeConstSecond); parser.addSymbol("TotalPlace", runner.getTotalPlace(false)); parser.addSymbol("InputStatus", runner.getInputStatus()); - parser.addSymbol("InputTime", runner.getInputTime()); + parser.addSymbol("InputTime", runner.getInputTime() / timeConstSecond); parser.addSymbol("InputPlace", runner.getInputPlace()); parser.addSymbol("InputPoints", runner.getInputPoints()); parser.addSymbol("Shorten", runner.getNumShortening()); @@ -1191,13 +1191,16 @@ void DynamicResult::prepareCommon(oAbstractRunner &runner, bool classResult) con for (RunnerStatus s : inst) iinst.push_back(s); + for (int &t : times) + t /= timeConstSecond; + parser.addSymbol("StageStatus", iinst); parser.addSymbol("StageTime", times); parser.addSymbol("StagePlace", places); parser.addSymbol("StagePoints", points); parser.addSymbol("InputStatus", runner.getInputStatus()); - parser.addSymbol("InputTime", runner.getInputTime()); + parser.addSymbol("InputTime", runner.getInputTime() / timeConstSecond); parser.addSymbol("InputPlace", runner.getInputPlace()); parser.addSymbol("InputPoints", runner.getInputPoints()); @@ -1226,13 +1229,13 @@ void DynamicResult::prepareCalculations(oTeam &team, bool classResult) const { for (int k = 0; k < nr; k++) { pRunner r = team.getRunner(k); if (r) { - oAbstractRunner::TempResult &res = r->getTempResult(); + const oAbstractRunner::TempResult &res = r->getTempResult(); status[k] = res.getStatus(); - time[k] = res.getRunningTime(); + time[k] = res.getRunningTime() / timeConstSecond; if (time[k] > 0 && status[k] == StatusUnknown) status[k] = StatusOK; - start[k] = res.getStartTime(); - finish[k] = res.getFinishTime(); + start[k] = res.getStartTime() / timeConstSecond; + finish[k] = res.getFinishTime() / timeConstSecond; points[k] = res.getPoints(); if (classResult) { r->updateComputedResultFromTemp(); @@ -1281,7 +1284,7 @@ void DynamicResult::prepareCalculations(oTeam &team, bool classResult) const { pClass cls = team.getClassRef(true); if (cls) { int nl = max(1, cls->getNumStages() - 1); - parser.addSymbol("ShortestClassTime", cls->getTotalLegLeaderTime(oClass::AllowRecompute::Yes, nl, false, false)); + parser.addSymbol("ShortestClassTime", cls->getTotalLegLeaderTime(oClass::AllowRecompute::Yes, nl, false, false) / timeConstSecond); } } @@ -1303,7 +1306,7 @@ void DynamicResult::prepareCalculations(oRunner &runner, bool classResult) const int ip = 0; for (size_t k = 0; k < punches.size(); k++) { if (punches[k]->getTypeCode() >= 30) { - times[ip] = punches[k]->getAdjustedTime(); + times[ip] = punches[k]->getAdjustedTime() / timeConstSecond; codes[ip] = punches[k]->getTypeCode(); controls[ip] = punches[k]->isUsedInCourse() ? punches[k]->getControlId() : -1; ip++; @@ -1345,16 +1348,16 @@ void DynamicResult::prepareCalculations(oRunner &runner, bool classResult) const if (ctrl->isSingleStatusOK()) { eCrs.push_back(ctrl->getFirstNumber()); if (size_t(k) < sp.size()) { - if (sp[k].status == SplitData::OK) { - eAccTime.push_back(sp[k].time - start); - eSplitTime.push_back(sp[k].time - st); - st = sp[k].time; + if (sp[k].hasTime()) { + eAccTime.push_back((sp[k].getTime(false) - start) / timeConstSecond); + eSplitTime.push_back((sp[k].getTime(false) - st) / timeConstSecond); + st = sp[k].getTime(false); } - else if (sp[k].status == SplitData::NoTime) { - eAccTime.push_back(st - start); + else if (sp[k].getStatus() == SplitData::SplitStatus::NoTime) { + eAccTime.push_back( (st - start) / timeConstSecond); eSplitTime.push_back(0); } - else if (sp[k].status == SplitData::Missing) { + else if (sp[k].isMissing()) { eAccTime.push_back(0); eSplitTime.push_back(-1); } @@ -1362,8 +1365,8 @@ void DynamicResult::prepareCalculations(oRunner &runner, bool classResult) const } } if (runner.getFinishTime() > 0) { - eAccTime.push_back(runner.getFinishTime() - start); - eSplitTime.push_back(runner.getFinishTime() - st); + eAccTime.push_back((runner.getFinishTime() - start) / timeConstSecond); + eSplitTime.push_back((runner.getFinishTime() - st) / timeConstSecond); } else if (!eAccTime.empty()) { eAccTime.push_back(0); @@ -1394,7 +1397,7 @@ void DynamicResult::prepareCalculations(oRunner &runner, bool classResult) const pClass cls = runner.getClassRef(true); if (cls) { int nl = runner.getLegNumber(); - parser.addSymbol("ShortestClassTime", cls->getBestLegTime(oClass::AllowRecompute::Yes, nl, false)); + parser.addSymbol("ShortestClassTime", cls->getBestLegTime(oClass::AllowRecompute::Yes, nl, false) / timeConstSecond); } vector delta; vector place; @@ -1523,7 +1526,7 @@ void GeneralResult::calculateIndividualResults(vector &runners, else { oe.calculateResults(set(), oEvent::ResultType::TotalResult, inclPreliminary); for (pRunner r : runners) { - ri.status = r->getTotalStatus(); + ri.status = r->getTotalStatus(false); if (ri.status == StatusUnknown && r->getInputStatus() == StatusOK) { if (r->getFinishTime() == 0) { if (!inclForestRunners) diff --git a/code/generalresult.h b/code/generalresult.h index 0e3c402..4601a1b 100644 --- a/code/generalresult.h +++ b/code/generalresult.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/guihandler.h b/code/guihandler.h index e44b1e4..93e9b03 100644 --- a/code/guihandler.h +++ b/code/guihandler.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/image.cpp b/code/image.cpp index efbe381..c451f1a 100644 --- a/code/image.cpp +++ b/code/image.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -89,9 +89,9 @@ vector Image::loadResourceToMemory(LPCTSTR lpName, LPCTSTR lpType) { return result; } -HBITMAP Image::read_png(vector &inData, int &width, int &height, ImageMethod method) { +HBITMAP Image::read_png(vector &&inData, int &width, int &height, ImageMethod method) { PngData inputStream; - inputStream.memory.swap(inData); + inputStream.memory = std::move(inData); png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) return nullptr; @@ -196,25 +196,57 @@ HBITMAP Image::read_png(vector &inData, int &width, int &height, ImageM row[x + 3] = src[x + 3]; } } + else if (method == ImageMethod::WhiteTransparent) { + for (size_t x = 0; x < cbStride; x += 4) { + row[x + 2] = src[x + 0]; // Red + row[x + 1] = src[x + 1]; // Green + row[x + 0] = src[x + 2]; // Blue + row[x + 3] = src[x + 3]; + if (src[x + 0] == 0xFF && src[x + 1] == 0xFF && src[x + 2] == 0xFF) { + row[x + 3] = 0; + row[x + 2] = 0; + row[x + 1] = 0; + row[x + 0] = 0; + } + } + } } return hbmp; } -HBITMAP Image::read_png_file(const wstring &filename, int &width, int &height, ImageMethod method) { - width = 0; - height = 0; - PngData inputStream; - inputStream.memory; +uint64_t Image::computeHash(const vector& data) { + uint64_t h = data.size(); + size_t siz4 = data.size() / 4; + const uint32_t* ptr = (const uint32_t *)data.data(); + size_t lim = siz4 * 4; + for (size_t e = siz4 * 4; e < data.size(); e++) + h = h * 256 + data[e]; + for (size_t e = 0; e < siz4; e++) { + h = 997 * h + ptr[e]; + } + + return h; +} + +void Image::read_file(const wstring& filename, vector& data) { ifstream fin; fin.open(filename, ios::binary); fin.seekg(0, ios::end); int p2 = (int)fin.tellg(); fin.seekg(0, ios::beg); - inputStream.memory.resize(p2); - fin.read((char *)&inputStream.memory[0], inputStream.memory.size()); + data.resize(p2); + fin.read((char*)data.data(), data.size()); fin.close(); - return read_png(inputStream.memory, width, height, method); +} + +HBITMAP Image::read_png_file(const wstring &filename, int &width, int &height, uint64_t &hash, ImageMethod method) { + width = 0; + height = 0; + PngData inputStream; + read_file(filename, inputStream.memory); + hash = computeHash(inputStream.memory); + return read_png(std::move(inputStream.memory), width, height, method); } HBITMAP Image::read_png_resource(LPCTSTR lpName, LPCTSTR lpType, int &width, int &height, ImageMethod method) { @@ -224,7 +256,7 @@ HBITMAP Image::read_png_resource(LPCTSTR lpName, LPCTSTR lpType, int &width, int inputStream.memory = loadResourceToMemory(lpName, lpType); if (inputStream.memory.empty()) return nullptr; - return read_png(inputStream.memory, width, height, method); + return read_png(std::move(inputStream.memory), width, height, method); } Image::Image() @@ -236,7 +268,7 @@ Image::~Image() } // Loads the PNG containing the splash image into a HBITMAP. -HBITMAP Image::loadImage(int resource, ImageMethod method) { +HBITMAP Image::loadImage(uint64_t resource, ImageMethod method) { if (images.count(resource)) return images[resource].image; @@ -250,18 +282,22 @@ HBITMAP Image::loadImage(int resource, ImageMethod method) { return hbmp; } -int Image::getWidth(int resource) { +int Image::getWidth(uint64_t resource) { loadImage(resource, ImageMethod::Default); return images[resource].width; } -int Image::getHeight(int resource) { +int Image::getHeight(uint64_t resource) { loadImage(resource, ImageMethod::Default); return images[resource].height; } -void Image::drawImage(int resource, ImageMethod method, HDC hDC, int x, int y, int width, int height) { +void Image::drawImage(uint64_t resource, ImageMethod method, HDC hDC, int x, int y, int width, int height) { HBITMAP bmp = loadImage(resource, method); + auto res = images.find(resource); + if (res == images.end()) + return; + HDC memdc = CreateCompatibleDC(hDC); SelectObject(memdc, bmp); @@ -270,7 +306,130 @@ void Image::drawImage(int resource, ImageMethod method, HDC hDC, int x, int y, i bf.BlendFlags = 0; bf.SourceConstantAlpha =0xFF; bf.AlphaFormat = AC_SRC_ALPHA; - AlphaBlend(hDC, x, y, width, height, memdc, 0, 0, width, height, bf); + AlphaBlend(hDC, x, y, width, height, memdc, 0, 0, res->second.width, res->second.height, bf); DeleteDC(memdc); } + +uint64_t Image::loadFromFile(const wstring& path, ImageMethod method) { + vector bytes; + read_file(path, bytes); + + uint64_t hash = computeHash(bytes); + + auto res = images.emplace(hash, Bmp()); + if (res.second) { + wchar_t drive[20]; + wchar_t dir[MAX_PATH]; + wchar_t name[MAX_PATH]; + wchar_t ext[MAX_PATH]; + _wsplitpath_s(path.c_str(), drive, dir, name, ext); + Bmp &out = res.first->second; + out.fileName = wstring(name) + ext; + out.rawData = bytes; + out.image = read_png(std::move(bytes), out.width, out.height, method); + } + return hash; +} + +uint64_t Image::loadFromMemory(const wstring& fileName, const vector& bytes, ImageMethod method) { + uint64_t hash = computeHash(bytes); + return hash; +} + +void Image::provideFromMemory(uint64_t id, const wstring& fileName, const vector& bytes) { + uint64_t hash = computeHash(bytes); + if (id != hash) + throw meosException(L"Corrupted image: " + fileName); + + images[id].fileName = fileName; + images[id].rawData = bytes; +} + +void Image::addImage(uint64_t id, const wstring& fileName) { + images[id].fileName = fileName; +} + +void Image::reloadImage(uint64_t imgId, ImageMethod method) { + auto res = images.find(imgId); + if (res != images.end() && res->second.rawData.size() > 0) { + auto copy = res->second.rawData; + res->second.destroy(); + res->second.image = read_png(std::move(copy), res->second.width, res->second.height, method); + return; + } + throw meosException("Unknown image " + itos(imgId)); +} + +void Image::clearLoaded() { + for (auto iter = images.begin(); iter != images.end(); ) { + if (iter->second.fileName.empty()) + ++iter; + else { + iter = images.erase(iter); + } + } +} + +Image::Bmp::~Bmp() { + destroy(); +} + +void Image::Bmp::destroy() { + if (image) { + DeleteObject(image); + image = nullptr; + } +} + + +void Image::enumerateImages(vector>& img) const { + img.clear(); + for (auto& bmp : images) { + if (bmp.second.fileName.size() > 0) + img.emplace_back(bmp.second.fileName, img.size()); + } + sort(img.begin(), img.end()); +} + +uint64_t Image::getIdFromEnumeration(int enumerationIx) const { + int ix = 0; + for (auto& bmp : images) { + if (bmp.second.fileName.size() > 0) { + if (enumerationIx == ix++) { + return bmp.first; + } + } + } + throw meosException("Internal error"); +} + +int Image::getEnumerationIxFromId(uint64_t imgId) const { + int ix = 0; + for (auto& bmp : images) { + if (bmp.second.fileName.size() > 0) { + if (imgId == bmp.first) + return ix; + ix++; + } + } + return -1; +} + +const wstring& Image::getFileName(uint64_t imgId) const { + if (!hasImage(imgId)) + throw meosException("Missing image: " + itos(imgId)); + return images.at(imgId).fileName; +} + +const vector& Image::getRawData(uint64_t imgId) const { + if (!hasImage(imgId)) + throw meosException("Missing image: " + itos(imgId)); + return images.at(imgId).rawData; +} + + +bool Image::hasImage(uint64_t imgId) const { + auto res = images.find(imgId); + return res != images.end() && res->second.rawData.size() > 0; +} diff --git a/code/image.h b/code/image.h index 790e1b7..f9f1d25 100644 --- a/code/image.h +++ b/code/image.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -29,34 +29,57 @@ class Image { public: enum class ImageMethod { Default, - MonoAlpha + MonoAlpha, + WhiteTransparent }; private: - static HBITMAP read_png_file(const wstring &filename, int &width, int &height, ImageMethod method); + static uint64_t computeHash(const vector& data); + + static void read_file(const wstring& filename, vector& data); + + static HBITMAP read_png_file(const wstring &filename, int &width, int &height, uint64_t &hash, ImageMethod method); static HBITMAP read_png_resource(LPCTSTR lpName, LPCTSTR lpType, int &width, int &height, ImageMethod method); - static HBITMAP read_png(vector &data, int &width, int &height, ImageMethod method); - - + static HBITMAP read_png(vector &&data, int &width, int &height, ImageMethod method); struct Bmp { - HBITMAP image; - int width; - int height; + HBITMAP image = nullptr; + int width = -1; + int height = -1; + wstring fileName; + vector rawData; + ~Bmp(); + void destroy(); }; - map images; + map images; public: - HBITMAP loadImage(int resource, ImageMethod method); + HBITMAP loadImage(uint64_t resource, ImageMethod method); static vector loadResourceToMemory(LPCTSTR lpName, LPCTSTR lpType); - int getWidth(int resource); - int getHeight(int resource); - void drawImage(int resource, ImageMethod method, HDC hDC, int x, int y, int width, int height); + int getWidth(uint64_t resource); + int getHeight(uint64_t resource); + void drawImage(uint64_t resource, ImageMethod method, HDC hDC, int x, int y, int width, int height); + uint64_t loadFromFile(const wstring& path, ImageMethod method); + uint64_t loadFromMemory(const wstring& fileName, const vector &bytes, ImageMethod method); + void provideFromMemory(uint64_t id, const wstring& fileName, const vector& bytes); + void addImage(uint64_t id, const wstring& fileName); + + void clearLoaded(); + + void enumerateImages(vector>& img) const; + uint64_t getIdFromEnumeration(int enumerationIx) const; + int getEnumerationIxFromId(uint64_t imgId) const; + + bool hasImage(uint64_t imgId) const; + void reloadImage(uint64_t imgId, ImageMethod method); + + const wstring& getFileName(uint64_t imgId) const; + const vector &getRawData(uint64_t imgId) const; Image(); ~Image(); }; diff --git a/code/importformats.cpp b/code/importformats.cpp index 63952d4..bf99387 100644 --- a/code/importformats.cpp +++ b/code/importformats.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/info24.png b/code/info24.png new file mode 100644 index 0000000000000000000000000000000000000000..b656542053a6c2d9f6e714fba829ab69e358dc92 GIT binary patch literal 874 zcmV-w1C{)VP)n7v_gvM6k!_Z%%?BE3)8gw+kKr{M}0Ep?~!Q^Wj0ObSr z;L6*1Ru_wca1K#xE>m@c0cGa-py!i3wuJL|5!VrSfJ6mwb`g>}P@Xcw4VIecyP&I# zCOu6?p_@6I5ev_s8groq4=dzW_oMQZDqPZU)57Yf(cime!|F6Lg02IAfu#WefA`{O z3V!V_Lehf(6I@&okj&+2B5!a{5?dfn$f8i*h>QEI!;tJ1(i?oDbej};8G@e9d4+Bd z#WQ@M_nQ?Gwa|*TE5+!lPR{L-la?j?Wrc}Ffer8PnG3A>A2oET64baWk;{z}%jl!i zABzGO@9X{=K$xP16y&PoP}6QXx5oEuw= z2|$>D4Hw<}G)#%5`d9!LvK1WQY!Kk^2?AKZcLW!j*?Sw#^MlE0QNAxNm2XmK^b z^oW*ZAc|6P(-YQ?0=_BY_lzKx>3maP%KNwFC&w*_u9w~R+W-In07*qoM6N<$f*kvp AK>z>% literal 0 HcmV?d00001 diff --git a/code/infoserver.cpp b/code/infoserver.cpp index b71f905..57b5d0d 100644 --- a/code/infoserver.cpp +++ b/code/infoserver.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -501,9 +501,9 @@ bool InfoBaseCompetitor::synchronizeBase(oAbstractRunner &bc) { ch = true; } - RunnerStatus s = bc.getStatusComputed(); + RunnerStatus s = bc.getStatusComputed(true); - int rt = bc.getRunningTime(true) * 10; + int rt = bc.getRunningTime(true) * (10/timeConstSecond); if (rt > 0) { if (s == RunnerStatus::StatusUnknown) s = RunnerStatus::StatusOK; @@ -521,7 +521,7 @@ bool InfoBaseCompetitor::synchronizeBase(oAbstractRunner &bc) { int st = -1; if (bc.startTimeAvailable()) - st = convertRelativeTime(bc, bc.getStartTime()) * 10; + st = convertRelativeTime(bc, bc.getStartTime()) * (10 / timeConstSecond); if (st != startTime) { startTime = st; @@ -564,7 +564,7 @@ bool InfoCompetitor::synchronize(bool useTotalResults, bool useCourse, oRunner & pTeam t = r.getTeam(); if (useTotalResults) { - legInput = r.getTotalTimeInput() * 10; + legInput = r.getTotalTimeInput() * (10 / timeConstSecond); s = r.getTotalStatusInput(); } else if (t && !isQF && r.getLegNumber() > 0) { @@ -578,7 +578,7 @@ bool InfoCompetitor::synchronize(bool useTotalResults, bool useCourse, oRunner & } } if (ltu > 0) { - legInput = t->getLegRunningTime(ltu - 1, true, false) * 10; + legInput = t->getLegRunningTime(ltu - 1, true, false) * (10 / timeConstSecond); s = t->getLegStatus(ltu - 1, true, false); } } @@ -625,7 +625,7 @@ bool InfoCompetitor::synchronize(const InfoCompetition &cmp, oRunner &r) { } vector newRT; - if (r.getClassId(false) > 0 && r.getStatusComputed() != RunnerStatus::StatusNoTiming) { + if (r.getClassId(false) > 0 && r.getStatusComputed(true) != RunnerStatus::StatusNoTiming) { const vector &radios = cmp.getControls(r.getClassId(true), r.getLegNumber()); for (size_t k = 0; k < radios.size(); k++) { RadioTime radioTime; @@ -634,7 +634,7 @@ bool InfoCompetitor::synchronize(const InfoCompetition &cmp, oRunner &r) { r.getSplitTime(radioTime.radioId, s_split, radioTime.runningTime); if (radioTime.runningTime > 0) { - radioTime.runningTime*=10; + radioTime.runningTime *= (10 / timeConstSecond); newRT.push_back(radioTime); } } diff --git a/code/infoserver.h b/code/infoserver.h index ecd631c..6e6d2e0 100644 --- a/code/infoserver.h +++ b/code/infoserver.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -224,8 +224,8 @@ private: protected: bool forceComplete; - bool includeTotal; - bool withCourse; + bool includeTotal = false; + bool withCourse = false; list toCommit; diff --git a/code/inthashmap.h b/code/inthashmap.h index 4e6b405..cea8183 100644 --- a/code/inthashmap.h +++ b/code/inthashmap.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/intkeymap.hpp b/code/intkeymap.hpp index e15de70..86f4eb4 100644 --- a/code/intkeymap.hpp +++ b/code/intkeymap.hpp @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/intkeymapimpl.hpp b/code/intkeymapimpl.hpp index 7b9fedb..d3c544b 100644 --- a/code/intkeymapimpl.hpp +++ b/code/intkeymapimpl.hpp @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/iof30interface.cpp b/code/iof30interface.cpp index db055e9..57e6104 100644 --- a/code/iof30interface.cpp +++ b/code/iof30interface.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -54,9 +54,9 @@ vector parseSGTimes(const oEvent &oe, const wstring &name) { return times; } -IOF30Interface::IOF30Interface(oEvent *oe, bool forceSplitFee) : oe(*oe), useGMT(false), teamsAsIndividual(false), +IOF30Interface::IOF30Interface(oEvent *oe, bool forceSplitFee, bool useEventorQuirks) : oe(*oe), useGMT(false), teamsAsIndividual(false), entrySourceId(1), unrollLoops(true), - includeStageRaceInfo(true) { + includeStageRaceInfo(true), useEventorQuirks(useEventorQuirks) { cachedStageNumber = -1; splitLateFee = forceSplitFee || oe->getPropertyInt("SplitLateFees", false) == 1; } @@ -705,7 +705,7 @@ void IOF30Interface::assignTeamCourse(gdioutput &gdi, oTeam *team, int iClass, i if (team) { pRunner r = team->getRunner(legId); if (r == 0) { - r = oe.addRunner(lang.tl(L"N.N."), team->getClubId(), team->getClassId(false), 0, 0, false); + r = oe.addRunner(lang.tl(L"N.N."), team->getClubId(), team->getClassId(false), 0, L"", false); if (r) { r->setEntrySource(entrySourceId); r->flagEntryTouched(true); @@ -959,7 +959,8 @@ void IOF30Interface::prescanCompetitorList(xmlobject &xo) { } } -void IOF30Interface::readCompetitorList(gdioutput &gdi, const xmlobject &xo, int &personCount) { +void IOF30Interface::readCompetitorList(gdioutput &gdi, const xmlobject &xo, + bool onlyWithClub, int &personCount, int &duplicateCount) { if (!xo) return; @@ -971,11 +972,10 @@ void IOF30Interface::readCompetitorList(gdioutput &gdi, const xmlobject &xo, int xmlList xl; xo.getObjects(xl); - xmlList::const_iterator it; - - for (it = xl.begin(); it != xl.end(); ++it) { - if (it->is("Competitor")) { - if (readXMLCompetitorDB(*it)) + unordered_multimap duplicateCheck; + for (auto &it : xl) { + if (it.is("Competitor")) { + if (readXMLCompetitorDB(it, onlyWithClub, duplicateCheck, duplicateCount)) personCount++; } } @@ -1409,12 +1409,17 @@ void IOF30Interface::readStartList(gdioutput &gdi, xmlobject &xo, int &entRead, if (raceToInfo.size() == 1) { RaceInfo &raceInfo = raceToInfo.begin()->second; if (raceInfo.courseId > 0) { - if (pc->getCourse() == 0) { - pCourse crs = oe.addCourse(pc->getName(), raceInfo.length, raceInfo.courseId); - crs->setStart(raceInfo.startName, false); - crs->getDI().setInt("Climb", raceInfo.climb); - pc->setCourse(crs); - crs->synchronize(); + if (pc->getCourse() == nullptr) { + pCourse crs = raceInfo.courseId > 0 ? oe.getCourse(raceInfo.courseId) : nullptr; + if (crs == nullptr) + crs = oe.addCourse(pc->getName(), raceInfo.length, raceInfo.courseId); + + if (crs != nullptr) { + crs->setStart(raceInfo.startName, false); + crs->getDI().setInt("Climb", raceInfo.climb); + pc->setCourse(crs); + crs->synchronize(); + } } } } @@ -1639,12 +1644,12 @@ void IOF30Interface::readEvent(gdioutput &gdi, const xmlobject &xo, timeStr.swap(tTime); int t = convertAbsoluteTimeISO(timeStr); if (t >= 0 && oe.getNumRunners() == 0) { - int zt = t - 3600; + int zt = t - timeConstHour; if (zt < 0) - zt += 3600*24; + zt += timeConstHour *24; if (!oe.hasFlag(oEvent::TransferFlags::FlagManualDateTime)) - oe.setZeroTime(formatTimeHMS(zt), false); + oe.setZeroTime(formatTimeHMS(zt, SubSecond::Auto), false); } } if (!oe.hasFlag(oEvent::TransferFlags::FlagManualDateTime)) @@ -1762,7 +1767,7 @@ void IOF30Interface::readEvent(gdioutput &gdi, const xmlobject &xo, xmlList nameList; s.getObjects("Name", nameList); for (auto s : nameList) { - services.emplace_back(id, s.getw()); + services.emplace_back(id, s.getWStr()); } } } @@ -2079,7 +2084,7 @@ pRunner IOF30Interface::readPersonEntry(gdioutput &gdi, xmlobject &xo, pTeam tea if (cardNo > 0 && r == 0 && team) { // We got no person, but a card number. Add the runner anonymously. - r = oe.addRunner(lang.tl(L"N.N."), team->getClubId(), team->getClassId(false), cardNo, 0, false); + r = oe.addRunner(lang.tl(L"N.N."), team->getClubId(), team->getClassId(false), cardNo, L"", false); r->flagEntryTouched(true); r->setEntrySource(entrySourceId); r->synchronize(); @@ -2197,7 +2202,7 @@ pRunner IOF30Interface::readPersonEntry(gdioutput &gdi, xmlobject &xo, pTeam tea if (ts >= 2 && times[ts - 2] < times[ts - 1]) oe.setStartGroup(groupId, times[ts - 2], times[ts - 1], groupName); else - oe.setStartGroup(groupId, 3600, 3600 * 2, groupName); + oe.setStartGroup(groupId, timeConstHour, timeConstHour * 2, groupName); } r->setStartGroup(groupId); } @@ -2415,14 +2420,14 @@ pRunner IOF30Interface::readPersonResult(gdioutput &gdi, pClass pc, xmlobject &x int time = split.getObjectInt("Time"); split.getObjectString("status", s); if (s != L"missing") - card->addPunch(code, st + time, 0); + card->addPunch(code, st + time, 0, 0); if (s != L"additional") controls.push_back(code); } if (ft > 0) - card->addPunch(oPunch::PunchFinish, ft, 0); + card->addPunch(oPunch::PunchFinish, ft, 0, 0); //Update to SQL-source card->synchronize(); @@ -2469,11 +2474,11 @@ void IOF30Interface::readId(const xmlobject &person, int &pid, __int64 &extId) c person.getObjects("Id", sids); for (auto &x : sids) { auto type = x.getAttrib("type"); - if (type && type.get() == preferredIdProvider) { - sid = x.getw(); + if (type && type.getPtr() == preferredIdProvider) { + sid = x.getWStr(); } else if (bsid.empty()) - bsid = x.getw(); + bsid = x.getWStr(); } if (sid.empty()) pid = oBase::idFromExtId(oBase::converExtIdentifierString(bsid)); @@ -2583,9 +2588,8 @@ pRunner IOF30Interface::readPerson(gdioutput &gdi, const xmlobject &person) { if (s != sUnknown) r->setSex(s); person.getObjectString("BirthDate", tmp); - if (tmp.length()>=4) { - tmp = tmp.substr(0, 4); - r->setBirthYear(_wtoi(tmp.c_str())); + if (tmp.length()>=4) { + r->setBirthDate(tmp); } getNationality(person.getObject("Nationality"), DI); @@ -2884,9 +2888,9 @@ void IOF30Interface::FeeInfo::add(IOF30Interface::FeeInfo &fi) { if (!fi.toTime.empty()) { SYSTEMTIME st; convertDateYMS(fi.toTime, st, false); - __int64 sec = SystemTimeToInt64Second(st); - sec -= 3600; - fi.toTime = convertSystemDate(Int64SecondToSystemTime(sec)); + __int64 sec = SystemTimeToInt64TenthSecond(st); + sec -= timeConstHour; + fi.toTime = convertSystemDate(Int64TenthSecondToSystemTime(sec)); } } //if (fi.fromTime.empty() || (fi.fromTime < toTime && !toTime.empty())) @@ -3069,7 +3073,7 @@ void IOF30Interface::setupRelayClass(pClass pc, const vector &legs) { pc->setNumStages(nStage); pc->setStartType(0, STTime, false); - pc->setStartData(0, oe.getAbsTime(3600)); + pc->setStartData(0, oe.getAbsTime(timeConstHour)); int ix = 0; for (size_t k = 0; k < legs.size(); k++) { @@ -3097,10 +3101,24 @@ wstring IOF30Interface::getCurrentTime() const { return getLocalDate() + L"T" + getLocalTimeOnly(); } + +wstring IOF30Interface::formatRelTime(int rt) { + wchar_t bf[32]; + if (oe.useSubSecond()) + swprintf_s(bf, L"%d.%d", rt / timeConstSecond, rt % timeConstSecond); + else + swprintf_s(bf, L"%d", rt / timeConstSecond); + + return bf; +} + + int IOF30Interface::parseISO8601Time(const xmlobject &xo) { if (!xo) return 0; - const char *t = xo.getRaw(); + const char *t = xo.getRawPtr(); + if (!t) + return 0; int tIx = -1; int zIx = -1; for (int k = 0; t[k] != 0; k++) { @@ -3160,9 +3178,9 @@ void IOF30Interface::getLocalDateTime(const string &date, const string &time, 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; + st.wHour = atime / timeConstHour; + st.wMinute = (atime / timeConstMinute) % 60; + st.wSecond = (atime / timeConstSecond) % 60; SYSTEMTIME localTime; memset(&localTime, 0, sizeof(SYSTEMTIME)); @@ -3219,26 +3237,24 @@ void IOF30Interface::getLocalDateTime(const wstring &date, const wstring &time, SYSTEMTIME st; memset(&st, 0, sizeof(SYSTEMTIME)); - int atime = convertAbsoluteTimeISO(wTime); + const 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; + st.wHour = atime / timeConstHour; + st.wMinute = (atime / timeConstMinute) % 60; + st.wSecond = (atime / timeConstSecond) % 60; SYSTEMTIME localTime; memset(&localTime, 0, sizeof(SYSTEMTIME)); SystemTimeToTzSpecificLocalTime(0, &st, &localTime); - atime = localTime.wHour * 3600 + localTime.wMinute * 60 + localTime.wSecond; + //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; @@ -3460,16 +3476,16 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o xml.write("StartTime", oe.getAbsDateTimeISO(r.getStartTime(), true, useGMT)); bool hasTiming = (!r.getClassRef(false) || r.getClassRef(true)->getNoTiming() == false) && - r.getStatusComputed() != RunnerStatus::StatusNoTiming && !r.noTiming(); + r.getStatusComputed(true) != RunnerStatus::StatusNoTiming && !r.noTiming(); int finishTime, runningTime, place, after; RunnerStatus status; if (!patrolResult) { place = r.getPlace(); - finishTime = r.getFinishTimeAdjusted(); + finishTime = r.getFinishTimeAdjusted(false); runningTime = r.getRunningTime(true); after = r.getTimeAfter(); - status = r.getStatusComputed(); + status = r.getStatusComputed(true); } else { int pl = r.getParResultLeg(); @@ -3480,7 +3496,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o else finishTime = 0; - after = r.getTeam()->getTimeAfter(pl); + after = r.getTeam()->getTimeAfter(pl, true); status = r.getTeam()->getLegStatus(pl, true, false); } @@ -3494,23 +3510,23 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o xml.write("FinishTime", oe.getAbsDateTimeISO(finishTime, true, useGMT)); if (runningTime > 0) - xml.write("Time", runningTime); + xml.write("Time", formatRelTime(runningTime)); if (after >= 0) { if (teamMember) { - xml.write("TimeBehind", "type", L"Leg", itow(after)); + xml.write("TimeBehind", "type", L"Leg", formatRelTime(after)); int afterCourse = r.getTimeAfterCourse(); if (afterCourse >= 0) - xml.write("TimeBehind", "type", L"Course", itow(afterCourse)); + xml.write("TimeBehind", "type", L"Course", formatRelTime(afterCourse)); } else - xml.write("TimeBehind", after); + xml.write("TimeBehind", formatRelTime(after)); } if (r.getClassRef(false)) { - if (r.statusOK(true) && hasTiming) { + if (r.statusOK(true, true) && hasTiming) { if (!teamMember && place > 0 && place < 50000) { xml.write("Position", place); } @@ -3536,7 +3552,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o int rt = r.getTotalRunningTime(); if (rt > 0 && hasTiming) - xml.write("Time", rt); + xml.write("Time", formatRelTime(rt)); RunnerStatus stat = r.getTotalStatus(); @@ -3546,7 +3562,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o int after = r.getTotalRunningTime() - r.getClassRef(true)->getTotalLegLeaderTime(oClass::AllowRecompute::Yes, tleg, true, true); if (after >= 0) - xml.write("TimeBehind", after); + xml.write("TimeBehind", formatRelTime(after)); } if (stat == StatusOK && hasTiming) @@ -3563,7 +3579,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o writeCourse(xml, *crs); const vector &sp = r.getSplitTimes(doUnroll); - RunnerStatus st = r.getStatusComputed(); + RunnerStatus st = r.getStatusComputed(true); if (r.getStatus()>0 && st != StatusDNS && st != StatusCANCEL && st != StatusNotCompetiting) { @@ -3579,7 +3595,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o break; if (crs->getControl(k)->isRogaining(hasRogaining)) { if (sp[k].hasTime()) { - int time = sp[k].time - r.getStartTime(); + int time = sp[k].getTime(false) - r.getStartTime(); int control = crs->getControl(k)->getFirstNumber(); rogaining.insert(make_pair(time, control)); } @@ -3596,7 +3612,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o xml.startTag("SplitTime"); xml.write("ControlCode", crs->getControl(k)->getFirstNumber()); if (sp[k].hasTime() && hasTiming) - xml.write("Time", sp[k].time - r.getStartTime()); + xml.write("Time", formatRelTime(sp[k].getTime(false) - r.getStartTime())); xml.endTag(); } @@ -3604,7 +3620,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o xml.startTag("SplitTime", "status", "Additional"); xml.write("ControlCode", it->second); if (it->first != -1) - xml.write("Time", it->first); + xml.write("Time", formatRelTime(it->first)); xml.endTag(); } @@ -3617,7 +3633,7 @@ void IOF30Interface::writeResult(xmlparser &xml, const oRunner &rPerson, const o xml.startTag("SplitTime", "status", "Additional"); xml.write("ControlCode", p->getTypeCode()); if (p->getTimeInt() > r.getStartTime()) - xml.write("Time", p->getTimeInt() - r.getStartTime()); + xml.write("Time", formatRelTime(p->getTimeInt() - r.getStartTime())); xml.endTag(); } } @@ -3653,7 +3669,8 @@ void IOF30Interface::writeFees(xmlparser &xml, const oRunner &r) const { void IOF30Interface::writeTeamResult(xmlparser &xml, const oTeam &t, bool hasInputTime) { xml.startTag("TeamResult"); - xml.write("EntryId", t.getId()); + writeTeamEntryId(t, xml); + xml.write("Name", t.getName()); if (t.getClubRef()) @@ -3672,6 +3689,24 @@ void IOF30Interface::writeTeamResult(xmlparser &xml, const oTeam &t, bool hasInp xml.endTag(); } +void IOF30Interface::writeTeamEntryId(const oTeam& t, xmlparser& xml) +{ + bool isImported = t.getEntrySource() != 0; + wstring id; + if (t.getExtIdentifier() != 0) { + id = t.getExtIdentifierString(); + isImported = true; + } + else + id = itow(t.getId()); + + if (isImported) + xml.write("EntryId", id); + else if (!useEventorQuirks) { + xml.write("EntryId", "type", L"MeOS", id); + } +} + int IOF30Interface::getStageNumber() { if (cachedStageNumber >= 0) return cachedStageNumber; @@ -4002,7 +4037,8 @@ void IOF30Interface::writeTeamNoPersonStart(xmlparser &xml, const oTeam &t, int void IOF30Interface::writeTeamStart(xmlparser &xml, const oTeam &t) { xml.startTag("TeamStart"); - xml.write("EntryId", t.getId()); + writeTeamEntryId(t, xml); + xml.write("Name", t.getName()); if (t.getClubRef()) @@ -4072,7 +4108,25 @@ void IOF30Interface::writeLegOrder(xmlparser &xml, const oClass *pc, int legNo) } } -bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) { +size_t hash_entry(const wstring& name, int clubId, int card, const wstring &birth) { + size_t h = 0; + for (int j = 0; j < name.size(); j++) { + h = h * 37 + name[j]; + } + + h = h * 997 + clubId; + h = h * 997 + card; + for (int j = 0; j < birth.size(); j++) { + if (birth[j] >= '0' && birth[j] <= '9') + h = h * 997 + birth[j]; + } + return h; +} + +bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor, + bool onlyWithClub, + unordered_multimap& duplicateCheck, + int &duplicateCount) { if (!xCompetitor) return false; @@ -4083,10 +4137,7 @@ bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) { int pidI; long long pid; readId(person, pidI, pid); - /* - wstring pidS; - person.getObjectString("Id", pidS);xxx - long long pid = oBase::converExtIdentifierString(pidS);*/ + xmlobject pname = person.getObject("Name"); if (!pname) return false; @@ -4100,7 +4151,7 @@ bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) { xmlobject &card = cards[k]; if (card) { xmlattrib pSystem = card.getAttrib("punchingSystem"); - if (!pSystem || _stricmp(pSystem.get(), "SI") == 0) { + if (!pSystem || _stricmp(pSystem.getPtr(), "SI") == 0) { cardno = card.getObjectInt(0); break; } @@ -4116,13 +4167,13 @@ bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) { if (given.empty() || family.empty()) return false; - //string name(given+" "+family); wstring name(family + L", " + given); char sex[2]; person.getObjectString("sex", sex, 2); - int birth = person.getObjectInt("BirthDate"); + wstring birth; + person.getObjectString("BirthDate", birth); xmlobject nat=person.getObject("Nationality"); @@ -4137,8 +4188,26 @@ bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) { clubId = xClub.getObjectInt("Id"); } + if (onlyWithClub && clubId == 0) + return false; + + size_t eHash = hash_entry(name, clubId, cardno, birth); + RunnerDB &runnerDB = oe.getRunnerDatabase(); + + auto dupCheck = duplicateCheck.equal_range(eHash); + wstring dupName; + for (auto it = dupCheck.first; it != dupCheck.second; ++it) { + int ix = it->second; + RunnerWDBEntry* dupCand = runnerDB.getRunnerByIndex(ix); + dupCand->getName(dupName); + if (dupName == name && clubId == dupCand->dbe().clubNo && cardno == dupCand->dbe().cardNo) { + duplicateCount++; + return false; // Duplicate person + } + } + RunnerWDBEntry *rde = runnerDB.getRunnerById(pid); if (!rde) { @@ -4152,10 +4221,12 @@ bool IOF30Interface::readXMLCompetitorDB(const xmlobject &xCompetitor) { } if (rde) { + duplicateCheck.emplace(eHash, rde->getIndex()); rde->setExtId(pid); rde->setName(name.c_str()); + rde->dbe().cardNo = cardno; rde->dbe().clubNo = clubId; - rde->dbe().birthYear = extendYear(birth); + rde->dbe().setBirthDate(birth); rde->dbe().sex = sex[0]; memcpy(rde->dbe().national, national, 3); } @@ -4276,14 +4347,14 @@ bool IOF30Interface::readControl(const xmlobject &xControl) { pControl pc = 0; if (type == 0) { - pc = oe.getControl(code, true); + pc = oe.getControl(code, true, false); } else if (type == 1) { wstring start = getStartName(trim(idStr)); - pc = oe.getControl(getStartIndex(idStr), true); + pc = oe.getControl(getStartIndex(idStr), true, false); pc->setNumbers(L""); pc->setName(start); - pc->setStatus(oControl::StatusStart); + pc->setStatus(oControl::ControlStatus::StatusStart); } else if (type == 2) { wstring finish = trim(idStr); @@ -4294,10 +4365,10 @@ bool IOF30Interface::readControl(const xmlobject &xControl) { finish = lang.tl(L"MÃ¥l ") + itow(num); else finish = lang.tl(L"MÃ¥l"); - pc = oe.getControl(getFinishIndex(num), true); + pc = oe.getControl(getFinishIndex(num), true, false); pc->setNumbers(L""); pc->setName(finish); - pc->setStatus(oControl::StatusFinish); + pc->setStatus(oControl::ControlStatus::StatusFinish); } if (pc) { @@ -4395,7 +4466,7 @@ pCourse IOF30Interface::readCourse(const xmlobject &xcrs) { if (type == "Start" && startName.empty()) { wstring idStr; xControls[k].getObjectString("Control", idStr); - pControl pStart = oe.getControl(getStartIndex(idStr), false); + pControl pStart = oe.getControl(getStartIndex(idStr), false, false); if (pStart) startName = pStart->getName(); } @@ -4407,14 +4478,14 @@ pCourse IOF30Interface::readCourse(const xmlobject &xcrs) { xControls[k].getObjects("Control", xPunchControls); pControl pCtrl = 0; if (xPunchControls.size() == 1) { - pCtrl = oe.getControl(xPunchControls[0].getInt(), true); + pCtrl = oe.getControl(xPunchControls[0].getInt(), true, false); } else if (xPunchControls.size()>1) { pCtrl = oe.addControl(1000*cid + xPunchControls[0].getInt(),xPunchControls[0].getInt(), L""); if (pCtrl) { wstring cc; for (size_t j = 0; j < xPunchControls.size(); j++) - cc += wstring(xPunchControls[j].getw()) + L" "; + cc += xPunchControls[j].getWStr() + L" "; pCtrl->setNumbers(cc); } @@ -4426,7 +4497,7 @@ pCourse IOF30Interface::readCourse(const xmlobject &xcrs) { int score = xControls[k].getObjectInt("Score"); if (score > 0) { pCtrl->getDI().setInt("Rogaining", score); - pCtrl->setStatus(oControl::StatusRogaining); + pCtrl->setStatus(oControl::ControlStatus::StatusRogaining); hasRogaining = true; } } @@ -4459,7 +4530,7 @@ pCourse IOF30Interface::readCourse(const xmlobject &xcrs) { if (hasRogaining) { int mt = oe.getMaximalTime(); if (mt == 0) - mt = 3600; + mt = timeConstHour; pc->setMaximumRogainingTime(mt); } @@ -4508,7 +4579,7 @@ void IOF30Interface::writeCourses(xmlparser &xml) { xml.endTag(); set ids; for (size_t k = 0; k < ctrl.size(); k++) { - if (ctrl[k]->getStatus() != oControl::StatusFinish && ctrl[k]->getStatus() != oControl::StatusStart) { + if (!oControl::isSpecialControl(ctrl[k]->getStatus())) { wstring id = writeControl(xml, *ctrl[k], ids); ctrlId2ExportId[ctrl[k]->getId()] = id; } diff --git a/code/iof30interface.h b/code/iof30interface.h index 2beaf7d..84b68e9 100644 --- a/code/iof30interface.h +++ b/code/iof30interface.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -25,6 +25,7 @@ #include #include #include +#include class oEvent; class xmlobject; @@ -72,7 +73,8 @@ class IOF30Interface { bool unrollLoops; // Include data on stage number bool includeStageRaceInfo; - void operator=(const IOF30Interface &); + + const IOF30Interface &operator=(const IOF30Interface &) = delete; set matchedClasses; @@ -193,6 +195,7 @@ class IOF30Interface { int parseISO8601Time(const xmlobject &xo); wstring getCurrentTime() const; + wstring formatRelTime(int rt); static void getNationality(const xmlobject &xCountry, oDataInterface &di); @@ -220,6 +223,8 @@ class IOF30Interface { void writeTeamResult(xmlparser &xml, const oTeam &t, bool hasInputTime); + void writeTeamEntryId(const oTeam& t, xmlparser& xml); + void writeResult(xmlparser &xml, const oRunner &rPerson, const oRunner &rResultCarrier, bool includeCourse, bool includeRaceNumber, bool teamMember, bool hasInputTime); @@ -249,7 +254,10 @@ class IOF30Interface { // Returns zero if no stage number int getStageNumber(); - bool readXMLCompetitorDB(const xmlobject &xCompetitor); + bool readXMLCompetitorDB(const xmlobject &xCompetitor, + bool onlyWithClub, + unordered_multimap &duplicateCheck, + int &duplicateCount); void writeXMLCompetitorDB(xmlparser &xml, const RunnerDB &db, const RunnerWDBEntry &rde) const; int getStartIndex(const wstring &startId); @@ -258,7 +266,7 @@ class IOF30Interface { pCourse readCourse(const xmlobject &xcrs); void readCourseGroups(xmlobject xClassCourse, vector< vector > &crs); - void bindClassCourse(oClass &pc, const vector< vector > &crs); + void bindClassCourse(oClass &pc, const vector> &crs); static wstring constructCourseName(const xmlobject &xcrs); static wstring constructCourseName(const wstring &family, const wstring &name); @@ -290,8 +298,10 @@ class IOF30Interface { set readCrsIds; + bool useEventorQuirks; + public: - IOF30Interface(oEvent *oe, bool forceSplitFee); + IOF30Interface(oEvent *oe, bool forceSplitFee, bool useEventorQuirks); virtual ~IOF30Interface() {} static void getLocalDateTime(const wstring &datetime, wstring &dateOut, wstring &timeOut); @@ -321,7 +331,8 @@ public: void readClassList(gdioutput &gdi, xmlobject &xo, int &entRead, int &entFail); void prescanCompetitorList(xmlobject &xo); - void readCompetitorList(gdioutput &gdi, const xmlobject &xo, int &personCount); + void readCompetitorList(gdioutput &gdi, const xmlobject &xo, + bool onlyWithClub, int &personCount, int& duplicateCount); void readClubList(gdioutput &gdi, const xmlobject &xo, int &clubCount); diff --git a/code/license.txt b/code/license.txt index 6754e03..21cefe3 100644 --- a/code/license.txt +++ b/code/license.txt @@ -7,7 +7,7 @@ Third Party Code. Additional copyright notices and license terms applicable to p All trademarks and registered trademarks mentioned herein are the property of their respective owners. ------------------------------------ -Copyright 2007-2020 Melin Software HB. +Copyright 2007-2023 Melin Software HB. ------------------------------------ diff --git a/code/listeditor.cpp b/code/listeditor.cpp index 6fa50a0..18439f6 100644 --- a/code/listeditor.cpp +++ b/code/listeditor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -37,6 +37,9 @@ #include "generalresult.h" #include "gdiconstants.h" #include "autocomplete.h" +#include "image.h" + +extern Image image; ListEditor::ListEditor(oEvent *oe_) { oe = oe_; @@ -62,13 +65,6 @@ void ListEditor::setCurrentList(MetaList *lst) { delete currentList; currentList = lst; } -/* -void ListEditor::load(MetaList *list) { - currentList = list; - currentIndex = -1; - dirtyInt = true; - dirtyExt = true; -}*/ void ListEditor::load(const MetaListContainer &mlc, int index) { const MetaList &mc = mlc.getList(index); @@ -117,6 +113,8 @@ void ListEditor::show(gdioutput &gdi) { gdi.fillRight(); gdi.addButton("EditList", "Egenskaper", editListCB); + gdi.addButton("SplitPrint", "Sträcktidslista", editListCB); + gdi.setCX(gdi.getCX() + gdi.scaleLength(32)); gdi.addButton("OpenFile", "Öppna fil", editListCB); gdi.addButton("OpenInside", "Öppna frÃ¥n aktuell tävling", editListCB); @@ -137,6 +135,21 @@ void ListEditor::show(gdioutput &gdi) { gdi.dropLine(2); int dx = gdi.getCX(); + + if (currentList && currentList->isSplitPrintList()) { + gdi.setCX(bx); + gdi.addString("", 0, "Välj deltagare för förhandsgranskning:"); + gdi.addSelection("Runner", 300, 300, editListCB); + oe->fillRunners(gdi, "Runner", false, 0); + if (currentRunnerId > 0 && oe->getRunner(currentRunnerId, 0)) + gdi.selectItemByData("Runner", currentRunnerId); + else { + gdi.selectFirstItem("Runner"); + currentRunnerId = gdi.getSelectedItem("Runner").first; + } + gdi.dropLine(2); + } + int dy = gdi.getCY(); RECT rc; @@ -156,6 +169,7 @@ void ListEditor::show(gdioutput &gdi) { makeDirty(gdi, NoTouch, NoTouch); if (!currentList) { gdi.disableInput("EditList"); + gdi.disableInput("SplitPrint"); gdi.disableInput("SaveFile"); gdi.disableInput("SaveFileCopy", true); gdi.disableInput("SaveInside"); @@ -223,8 +237,17 @@ void ListEditor::show(gdioutput &gdi) { gdi.addButton("AddLine3", "Lägg till rad", editListCB); gdi.setRestorePoint("EditList"); + renderListPreview(gdi); + gdi.refresh(); +} +void ListEditor::renderListPreview(gdioutput &gdi) { gdi.dropLine(2); + gdi.fillDown(); + RECT rc; + pRunner splitPrintR = nullptr; + if (currentList->isSplitPrintList() && currentRunnerId) + splitPrintR = oe->getRunner(currentRunnerId, 0); oListInfo li; oListParam par; @@ -232,30 +255,59 @@ void ListEditor::show(gdioutput &gdi) { par.splitAnalysis = true; par.setLegNumberCoded(-1); par.inputNumber = 0; - gdi.fillDown(); + if (splitPrintR) { + par.selection.insert(splitPrintR->getClassId(true)); + par.showInterTimes = false; + par.setLegNumberCoded(splitPrintR->getLegNumber()); + par.filterMaxPer = 3; + par.alwaysInclude = splitPrintR; + par.showHeader = false; + } + double originalScale = 0; try { - currentList->interpret(oe, gdi, par, li); rc.left = gdi.getCX(); - rc.right = gdi.getCX() + gdi.getWidth() - 20; + rc.right = gdi.getCX() + gdi.getWidth() - gdi.scaleLength(20); rc.top = gdi.getCY(); rc.bottom = rc.top + 4; - gdi.addRectangle(rc, colorDarkGreen, false, false); gdi.dropLine(); - oe->generateList(gdi, false, li, true); + currentList->interpret(oe, gdi, par, li); + + if (splitPrintR) { + auto& sp = *li.getSplitPrintInfo(); + li.getParam().filterMaxPer = sp.numClassResults; + + const bool wideFormat = oe->getPropertyInt("WideSplitFormat", 0) == 1; + if (!wideFormat) + li.shrinkSize(); + + rc.left = gdi.getCX(); + rc.top = gdi.getCY(); + gdi.setCX(gdi.getCX() + gdi.scaleLength(10)); + gdi.dropLine(); + + splitPrintR->printSplits(gdi, &li); + + gdi.dropLine(); + rc.right = rc.left + gdi.scaleLength(250); + rc.bottom = gdi.getHeight(); + gdi.addRectangle(rc, GDICOLOR::colorLightYellow); + gdi.refresh(); + } + else { + oe->generateList(gdi, false, li, true); + } } - catch (meosException &ex) { + catch (meosException& ex) { gdi.addString("", 1, "Listan kan inte visas").setColor(colorRed); gdi.addString("", 0, ex.wwhat()); } - catch (std::exception &ex) { + catch (std::exception& ex) { gdi.addString("", 1, "Listan kan inte visas").setColor(colorRed); gdi.addString("", 0, ex.what()); } - - gdi.refresh(); } int editListCB(gdioutput *gdi, int type, void *data) @@ -274,7 +326,8 @@ void ListEditor::showLine(gdioutput &gdi, const vector &line, int addButton(gdi, line[k], gdi.getCX(), gdi.getCY(), ix, k); } - gdi.addButton("AddPost" + itos(ix), "Lägg till ny", editListCB); + gdi.addButton("AddPost", "Lägg till ny", editListCB).setExtra(ix); + gdi.addButton("AddImage", "Lägg till bild", editListCB).setExtra(ix); } ButtonInfo &ListEditor::addButton(gdioutput &gdi, const MetaListPost &mlp, int x, int y, int lineIx, int ix) const { @@ -282,6 +335,17 @@ ButtonInfo &ListEditor::addButton(gdioutput &gdi, const MetaListPost &mlp, int x if (mlp.getType() == L"String") { cap = L"Text: X#" + mlp.getText(); } + else if (mlp.getType() == L"Image") { + if (mlp.getText().empty()) + cap = L"Image"; + else { + uint64_t imgId = mlp.getImageId(); + if (!image.hasImage(imgId)) + cap = L"Error"; + else + cap = image.getFileName(imgId); + } + } else { const wstring &text = mlp.getText(); if (text.length() > 0) { @@ -364,6 +428,26 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { if (ii.id == "Text" && ii.text != lastShownExampleText) { showExample(gdi); } + else if (ii.id == "ImgWidth" || ii.id == "ImgHeight") { + if (gdi.isChecked("PreserveAspectRatio")) { + auto sel = gdi.getSelectedItem("Image"); + if (sel.second && sel.first >= 0) { + auto imgId = image.getIdFromEnumeration(sel.first); + int h = image.getHeight(imgId); + int w = image.getWidth(imgId); + int ww, hh; + if (ii.id == "ImgWidth") { + ww = _wtoi(ii.text.c_str()); + hh = (ww * h + w/2) / w; + gdi.setText("ImgHeight", hh); + } else { + hh = _wtoi(ii.text.c_str()); + ww = (hh * w + h / 2) / h; + gdi.setText("ImgWidth", ww); + } + } + } + } } else if (type == GUI_BUTTON) { ButtonInfo bi = dynamic_cast(data); @@ -376,66 +460,64 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { biSrc.setExtra(res); oe->setProperty("Colors", c); } - /*CHOOSECOLOR cc; - memset(&cc, 0, sizeof(cc)); - cc.lStructSize = sizeof(cc); - cc.hwndOwner = gdi.getHWND(); - cc.rgbResult = COLORREF(bi.getExtra()); - if (GDICOLOR((int)bi.getExtra()) != colorDefault) - cc.Flags |= CC_RGBINIT; - - COLORREF staticColor[16]; - memset(staticColor, 0, 16*sizeof(COLORREF)); - - const string &c = oe->getPropertyString("Colors", ""); - const char *end = c.c_str() + c.length(); - const char * pEnd = c.c_str(); - int pix = 0; - while(pEnd < end && pix < 16) { - staticColor[pix++] = strtol(pEnd,(char **)&pEnd,16); - } - - cc.lpCustColors = staticColor; - if (ChooseColor(&cc)) { - data.setExtra((int)cc.rgbResult); - - wstring co; - for (ix = 0; ix < 16; ix++) { - wchar_t bf[16]; - swprintf_s(bf, L"%x ", staticColor[ix]); - co += bf; - } - oe->setProperty("Colors", co); - }*/ } - if ( bi.id.substr(0, 8) == "EditPost" ) { + else if (bi.id == "NewImage") { + vector> ext = { make_pair(L">Bilder", L"*.png") }; + wstring fn = gdi.browseForOpen(ext, L"png"); + if (!fn.empty()) { + bool transparent = gdi.isChecked("TransparentWhite"); + + uint64_t imgId = image.loadFromFile(fn, transparent ? Image::ImageMethod::WhiteTransparent : Image::ImageMethod::Default); + int selIx = selectImage(gdi, imgId); + previewImage(gdi, selIx); + updateImageStatus(gdi, selIx); + + } + } + else if (bi.id == "TransparentWhite") { + bool transparent = gdi.isChecked("TransparentWhite"); + int data = gdi.getSelectedItem("Image").first; + + if (data >= 0) { + uint64_t imgId = image.getIdFromEnumeration(data); + image.reloadImage(imgId, transparent ? Image::ImageMethod::WhiteTransparent : Image::ImageMethod::Default); + gdi.refresh(); + } + } + else if ( bi.id.substr(0, 8) == "EditPost" ) { if (gdi.hasData("CurrentId")) { - DWORD id; - gdi.getData("CurrentId", id); - getPosFromId(id, groupIx, lineIx, ix); - MetaListPost &mlp = currentList->getMLP(groupIx, lineIx, ix); - saveListPost(gdi, mlp); + checkUnsaved(gdi); } int id = atoi(bi.id.substr(8).c_str()); getPosFromId(id, groupIx, lineIx, ix); MetaListPost &mlp = currentList->getMLP(groupIx, lineIx, ix); - editListPost(gdi, mlp, id); + if (mlp.getTypeRaw() == EPostType::lImage) + editImage(gdi, mlp, id); + else + editListPost(gdi, mlp, id); } - else if ( bi.id.substr(0, 7) == "AddPost" ) { + else if (bi.id == "AddPost" || bi.id == "AddImage") { checkUnsaved(gdi); gdi.restore("EditList", true); gdi.pushX(); - lineIx = atoi(bi.id.substr(7).c_str()); + lineIx = bi.getExtraInt(); groupIx = (lineIx / 100) - 1; int ixOutput = 0; MetaListPost &mlp = currentList->addNew(groupIx, lineIx % 100, ixOutput); - int xp = bi.xp; - int yp = bi.yp; + if (bi.id == "AddImage") + mlp.setType(EPostType::lImage); + + auto& post = dynamic_cast(gdi.getBaseInfo("AddPost", lineIx)); + + int xp = post.xp; + int yp = post.yp; ButtonInfo &nb = addButton(gdi, mlp, xp, yp, lineIx, ixOutput); - //gdi.addButton(xp, yp, string("Foo"), string("FoooBar"), 0); int w, h; nb.getDimension(gdi, w, h); - biSrc.moveButton(gdi, xp+w, yp); + post.moveButton(gdi, xp + w, yp); + int w2, h2; + post.getDimension(gdi, w2, h2); + dynamic_cast(gdi.getBaseInfo("AddImage", lineIx)).moveButton(gdi, xp + w + w2, yp); gdi.popX(); gdi.setRestorePoint("EditList"); makeDirty(gdi, MakeDirty, MakeDirty); @@ -463,9 +545,13 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { else if (bi.id == "UseLeg") { gdi.setInputStatus("Leg", gdi.isChecked(bi.id)); } + else if (bi.id == "UseForSplit") { + statusSplitPrint(gdi, gdi.isChecked(bi.id)); + } else if (bi.id == "Cancel") { gdi.restore("EditList"); gdi.enableInput("EditList"); + gdi.enableInput("SplitPrint"); } else if (bi.id == "CancelNew") { gdi.clearPage(false); @@ -473,26 +559,35 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { show(gdi); } else if (bi.id == "Apply" || bi.id == "MoveLeft" || bi.id == "MoveRight") { + bool image = gdi.hasData("IsEditingImage"); DWORD id; gdi.getData("CurrentId", id); getPosFromId(id, groupIx, lineIx, ix); - if (bi.id == "MoveLeft") + if (bi.id == "MoveLeft") { currentList->moveOnRow(groupIx, lineIx, ix, -1); - else if (bi.id == "MoveRight") + id--; + } + else if (bi.id == "MoveRight") { currentList->moveOnRow(groupIx, lineIx, ix, 1); - + id++; + } + gdi.setData("CurrentId", id); MetaListPost &mlp = currentList->getMLP(groupIx, lineIx, ix); - bool force = saveListPost(gdi, mlp); + bool force = checkUnsaved(gdi);//saveListPost(gdi, mlp); if (!gdi.hasData("NoRedraw") || force) { gdi.restore("BeginListEdit", false); show(gdi); } - if (bi.id != "Apply") - editListPost(gdi, mlp, bi.getExtraInt()); + if (bi.id != "Apply") { + if (image) + editImage(gdi, mlp, bi.getExtraInt()); + else + editListPost(gdi, mlp, bi.getExtraInt()); + } } else if (bi.id == "ApplyListProp") { wstring name = gdi.getText("Name"); @@ -532,7 +627,6 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { list.setSubFilters(subFiltersOut); - for (int k = 0; k < 4; k++) { list.setFontFace(k, gdi.getText("Font" + itos(k)), gdi.getTextNo("FontFactor" + itos(k))); @@ -541,7 +635,6 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { list.setExtraSpace(k, f); } - list.setSupportFromTo(gdi.isChecked("SupportFrom"), gdi.isChecked("SupportTo")); list.setSupportLegSelection(gdi.isChecked("SupportLegSelection")); @@ -552,9 +645,32 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { show(gdi); } } + else if (bi.id == "ApplySplitList") { + MetaList& list = *currentList; + if (!gdi.isChecked("UseForSplit")) + list.setSplitPrintInfo(nullptr); + else { + auto spInfo = make_shared(); + spInfo->includeSplitTimes = gdi.isChecked("Split"); + spInfo->withSpeed = gdi.isChecked("Speed"); + spInfo->withResult = gdi.isChecked("Result"); + spInfo->withAnalysis = gdi.isChecked("Analysis"); + list.setSplitPrintInfo(spInfo); + } + + makeDirty(gdi, MakeDirty, MakeDirty); + + if (!gdi.hasData("NoRedraw")) { + gdi.clearPage(false); + show(gdi); + } + } else if (bi.id == "EditList") { editListProp(gdi, false); } + else if (bi.id == "SplitPrint") { + splitPrintList(gdi); + } else if (bi.id == "NewList") { if (!checkSave(gdi)) return 0; @@ -645,7 +761,7 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { gdi.pushX(); vector< pair > lists; - oe->getListContainer().getLists(lists, true, false, false); + oe->getListContainer().getLists(lists, true, false, false, false); gdi.fillRight(); gdi.addSelection("OpenList", 250, 400, editListCB, L"Välj lista:"); @@ -691,6 +807,11 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { savedFileName.clear(); oe->synchronize(false); + set imgUsed; + currentList->getUsedImages(imgUsed); + for (uint64_t id : imgUsed) + oe->saveImage(id); + if (currentIndex != -1) { oe->getListContainer().saveList(currentIndex, *currentList); } @@ -733,15 +854,6 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { } return 0; } - /*else if (bi.id == "BrowseFont") { - InitCommonControls(); - CHOOSEFONT cf; - memset(&cf, 0, sizeof(cf)); - cf.lStructSize = sizeof(cf); - cf.hwndOwner = gdi.getHWND(); - ChooseFont(&cf); - EnumFontFamilies( - }*/ } else if (type == GUI_LISTBOX) { ListBoxInfo &lbi = dynamic_cast(data); @@ -758,6 +870,10 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { else gdi.setText("AlignText", L""); } + else if (lbi.id == "Image") { + previewImage(gdi, lbi.data); + updateImageStatus(gdi, lbi.data); + } else if (lbi.id == "Type") { updateType(lbi.data, gdi); } @@ -784,6 +900,12 @@ int ListEditor::editList(gdioutput &gdi, int type, BaseInfo &data) { else if (lbi.id == "OpenList") { enableOpen(gdi); } + else if (lbi.id == "Runner") { + currentRunnerId = lbi.getDataInt(); + gdi.restore("EditList", false); + renderListPreview(gdi); + gdi.refresh(); + } } else if (type==GUI_CLEAR) { return checkSave(gdi); @@ -845,6 +967,26 @@ bool ListEditor::saveListPost(gdioutput &gdi, MetaListPost &mlp) { return force; } +bool ListEditor::saveImage(gdioutput& gdi, MetaListPost& mlp) { + ListBoxInfo lbi; + int selIx = gdi.getSelectedItem("Image").first; + + if (selIx >= 0) { + auto imgId = image.getIdFromEnumeration(selIx); + mlp.setText(itow(imgId)); + mlp.setImageDimension(gdi.getTextNo("ImgWidth"), gdi.getTextNo("ImgHeight")); + mlp.setImageOffset(gdi.getTextNo("ImgOffsetX"), gdi.getTextNo("ImgOffsetY")); + mlp.setImageStyle(gdi.isChecked("TransparentWhite") ? 1 : 0); + mlp.imageUnderText(gdi.isChecked("ImageUnderText")); + } + else { + mlp.setText(L""); + } + makeDirty(gdi, MakeDirty, MakeDirty); + + return false; +} + int ListEditor::readLeg(gdioutput &gdi, EPostType newType, bool checkError) const { if (MetaList::isResultModuleOutput(newType)) { int leg = gdi.getTextNo("Leg"); @@ -863,6 +1005,13 @@ int ListEditor::readLeg(gdioutput &gdi, EPostType newType, bool checkError) cons else return -1; } + else if (MetaList::isAllLegType(newType)) { + int leg = gdi.getSelectedItem("LegSel").first; + if (leg >= 0) + return leg - 1; // -1 -> automatic + else + return -2; // All legs + } else { if (gdi.isChecked("UseLeg")) { int leg = gdi.getTextNo("Leg"); @@ -899,7 +1048,7 @@ void ListEditor::updateType(int iType, gdioutput & gdi) { if (gdi.getText("Leg").empty()) gdi.setText("Leg", L"0"); } - else if (MetaList::isAllStageType(type)) { + else if (MetaList::isAllStageType(type) || MetaList::isAllLegType(type)) { } else { @@ -915,23 +1064,36 @@ void ListEditor::updateType(int iType, gdioutput & gdi) { showExample(gdi, type); } -void ListEditor::checkUnsaved(gdioutput &gdi) { - if (gdi.hasData("IsEditing")) { - if (gdi.hasData("CurrentId")) { - DWORD id; - gdi.getData("CurrentId", id); - int groupIx, lineIx, ix; - getPosFromId(id, groupIx, lineIx, ix); - MetaListPost &mlp = currentList->getMLP(groupIx, lineIx, ix); - saveListPost(gdi, mlp); - } +bool ListEditor::checkUnsaved(gdioutput& gdi) { + if (gdi.hasData("IsEditing")) { + DWORD id; + gdi.getData("CurrentId", id); + int groupIx, lineIx, ix; + getPosFromId(id, groupIx, lineIx, ix); + MetaListPost& mlp = currentList->getMLP(groupIx, lineIx, ix); + return saveListPost(gdi, mlp); } - if (gdi.hasData("IsEditingList")) { + else if (gdi.hasData("IsEditingImage")) { + DWORD id; + gdi.getData("CurrentId", id); + int groupIx, lineIx, ix; + getPosFromId(id, groupIx, lineIx, ix); + MetaListPost& mlp = currentList->getMLP(groupIx, lineIx, ix); + return saveImage(gdi, mlp); + } + else if (gdi.hasData("IsEditingList")) { if (gdi.isInputChanged("")) { gdi.setData("NoRedraw", 1); gdi.sendCtrlMessage("ApplyListProp"); } } + else if (gdi.hasData("IsSplitListEdit")) { + if (gdi.isInputChanged("")) { + gdi.setData("NoRedraw", 1); + gdi.sendCtrlMessage("ApplySplitList"); + } + } + return false; } void ListEditor::updateAlign(gdioutput &gdi, int val) { @@ -949,38 +1111,17 @@ void ListEditor::updateAlign(gdioutput &gdi, int val) { } void ListEditor::editListPost(gdioutput &gdi, const MetaListPost &mlp, int id) { - checkUnsaved(gdi); - gdi.restore("EditList", false); - gdi.dropLine(); - - gdi.enableInput("EditList"); int groupIx, lineIx, ix; getPosFromId(id, groupIx, lineIx, ix); - const bool hasResultModule = currentList && !currentList->getResultModule().empty(); - int x1 = gdi.getCX(); - int y1 = gdi.getCY(); - int margin = gdi.scaleLength(10); - gdi.setCX(x1+margin); - gdi.dropLine(); - gdi.pushX(); - gdi.fillRight(); - gdi.addString("", boldLarge, "Listpost").setColor(colorDarkGrey); - gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); + int x1, y1, boxY; + editDlgStart(gdi, id, "Listpost", x1, y1, boxY); - gdi.addButton("MoveLeft", "<< Flytta vänster", editListCB).setExtra(id-1); - if (ix == 0) - gdi.setInputStatus("MoveLeft", false); - - gdi.addButton("MoveRight", "Flytta höger >>", editListCB).setExtra(id+1); - if (ix + 1 == currentList->getNumPostsOnLine(groupIx, lineIx)) - gdi.setInputStatus("MoveRight", false); - - gdi.dropLine(1); - int boxY = gdi.getCY(); - gdi.dropLine(2); + int maxX = gdi.getCX(); gdi.popX(); + const bool hasResultModule = currentList && !currentList->getResultModule().empty(); + vector< pair > types; int currentType; mlp.getTypes(types, currentType); @@ -1039,22 +1180,7 @@ void ListEditor::editListPost(gdioutput &gdi, const MetaListPost &mlp, int id) { int leg = mlp.getLeg(); legStageTypeIndex(gdi, storedType, leg); - /*gdi.addCheckbox(xpUseLeg, ypUseLeg, "UseLeg", getIndexDescription(storedType), editListCB, leg != -1); - //gdi.dropLine(-0.2); - int dx = gdi.scaleLength(250); - int dy = -gdi.getLineHeight() / 5; - - //gdi.setCX(gdi.getCX() + gdi.scaleLength(100)); - if (MetaList::isResultModuleOutput(storedType)) - gdi.addInput(xpUseLeg + dx, ypUseLeg + dy, "Leg", leg >= 0 ? itow(leg) : L"0", 4); - else - gdi.addInput(xpUseLeg + dx, ypUseLeg + dy, "Leg", leg >= 0 ? itow(leg + 1) : L"", 4); - - gdi.setInputStatus("Leg", leg != -1); - */ if (MetaList::isResultModuleOutput(storedType)) { - //gdi.check("UseLeg", true); - //gdi.disableInput("UseLeg"); if (gdi.hasWidget("UseResultModule")) { gdi.check("UseResultModule", true); gdi.disableInput("UseResultModule"); @@ -1083,9 +1209,6 @@ void ListEditor::editListPost(gdioutput &gdi, const MetaListPost &mlp, int id) { gdi.dropLine(1.9); gdi.popX(); gdi.fillRight(); - - - gdi.dropLine(2); int maxY = gdi.getCY(); @@ -1115,7 +1238,7 @@ void ListEditor::editListPost(gdioutput &gdi, const MetaListPost &mlp, int id) { gdi.addInput("MinIndent", itow(mlp.getMinimalIndent()), 7, 0, L"Justering i sidled:"); - int maxX = gdi.getCX(); + maxX = max(maxX, gdi.getCX()); gdi.popX(); gdi.dropLine(3); vector< pair > fonts; @@ -1136,8 +1259,6 @@ void ListEditor::editListPost(gdioutput &gdi, const MetaListPost &mlp, int id) { gdi.addItem("TextAdjust", lang.tl("Centrera"), textCenter); gdi.selectItemByData("TextAdjust", mlp.getTextAdjustNum()); - //gdi.popX(); - //gdi.dropLine(2); gdi.dropLine(); gdi.addButton("Color", "Färg...", editListCB).setExtra(mlp.getColorValue()); @@ -1190,6 +1311,166 @@ void ListEditor::editListPost(gdioutput &gdi, const MetaListPost &mlp, int id) { gdi.refresh(); } +void ListEditor::editDlgStart(gdioutput& gdi, int id, const char *title, int &x1, int &y1, int &boxY) { + int groupIx, lineIx, ix; + getPosFromId(id, groupIx, lineIx, ix); + + checkUnsaved(gdi); + gdi.restore("EditList", false); + gdi.dropLine(); + gdi.enableInput("EditList"); + gdi.enableInput("SplitPrint"); + + x1 = gdi.getCX(); + y1 = gdi.getCY(); + int margin = gdi.scaleLength(10); + gdi.setCX(x1 + margin); + + gdi.dropLine(); + gdi.pushX(); + gdi.fillRight(); + gdi.addString("", boldLarge, title).setColor(colorDarkGrey); + gdi.setCX(gdi.getCX() + gdi.scaleLength(20)); + + gdi.addButton("MoveLeft", "<< Flytta vänster", editListCB).setExtra(id - 1); + if (ix == 0) + gdi.setInputStatus("MoveLeft", false); + + gdi.addButton("MoveRight", "Flytta höger >>", editListCB).setExtra(id + 1); + if (ix + 1 == currentList->getNumPostsOnLine(groupIx, lineIx)) + gdi.setInputStatus("MoveRight", false); + + gdi.dropLine(1); + boxY = gdi.getCY(); + gdi.dropLine(2); +} + +void ListEditor::editImage(gdioutput& gdi, const MetaListPost& mlp, int id) { + int groupIx, lineIx, ix; + getPosFromId(id, groupIx, lineIx, ix); + + uint64_t imgId = mlp.getImageId(); + + int x1, y1, boxY; + editDlgStart(gdi, id, "Bild", x1, y1, boxY); + + int maxX = gdi.getCX(); + gdi.popX(); + gdi.fillRight(); + gdi.dropLine(1); + + gdi.addSelection("Image", 200, 200, editListCB, L"Välj bild:", L"Välj bland befintliga bilder"); + + int selIx = selectImage(gdi, imgId); + + gdi.dropLine(1); + gdi.addButton("NewImage", "Ny bild...", editListCB); + + int maxY = 0; + maxX = max(maxX, gdi.getCX()); + int innerBoxLowerCX = maxX + gdi.scaleLength(6); + maxX += gdi.scaleLength(12); + + gdi.popX(); + gdi.dropLine(3); + + wstring wh, ww, xoff, yoff; + bool keepRatio = true; + if (imgId) { + int h = mlp.getImageHeight(); + int w = mlp.getImageWidth(); + wh = itow(h); + ww = itow(w); + + int hImg = image.getHeight(imgId); + int wImg = image.getWidth(imgId); + + int hComputed = (w * hImg + wImg / 2) / wImg; + int wComputed = (h * wImg + hImg / 2) / hImg; + + keepRatio = std::abs(h - hComputed) <= 1 || std::abs(w - wComputed) <= 1; + + xoff = itow(mlp.getImageOffsetX()); + yoff = itow(mlp.getImageOffsetY()); + } + + gdi.addInput("ImgWidth", ww, 5, editListCB, L"Bredd:"); + gdi.addInput("ImgHeight", wh, 5, editListCB, L"Höjd:"); + gdi.dropLine(); + gdi.addCheckbox("PreserveAspectRatio", "Bevara höjd/bredd-relationen", nullptr, keepRatio); + gdi.popX(); + gdi.dropLine(2); + + gdi.dropLine(1); + gdi.addString("", 0, "Förskjutning:"); + gdi.dropLine(-1); + + gdi.addInput("ImgOffsetX", xoff, 5, editListCB, L"Horizontell:"); + gdi.addInput("ImgOffsetY", yoff, 5, editListCB, L"Vertikal:"); + + gdi.popX(); + gdi.dropLine(3); + + gdi.addCheckbox("TransparentWhite", "Tolka vitt som genomskinligt", editListCB, mlp.getImageStyle() == 1); + gdi.addCheckbox("ImageUnderText", "Bild under text", editListCB, mlp.imageUnderText()); + + bool hasImg = imgId != 0; + gdi.setInputStatus("ImgWidth", hasImg); + gdi.setInputStatus("ImgHeight", hasImg); + gdi.setInputStatus("ImgOffsetX", hasImg); + gdi.setInputStatus("ImgOffsetY", hasImg); + + gdi.setInputStatus("PreserveAspectRatio", hasImg); + + gdi.dropLine(3); + gdi.setData("CurrentId", id); + gdi.addButton("Remove", "Radera", editListCB, "Ta bort listposten"); + gdi.addButton("Cancel", "Avbryt", editListCB).setCancel(); + + gdi.updatePos(gdi.getCX(), gdi.getCY(), gdi.scaleLength(20), 0); + gdi.addButton("Apply", "OK", editListCB).setDefault(); + + gdi.dropLine(1); + maxY = max(maxY, gdi.getCY()); + maxX = max(gdi.getCX(), maxX); + + RECT rc; + + rc.top = y1; + rc.left = x1; + rc.right = maxX + gdi.scaleLength(6); + rc.bottom = maxY + gdi.scaleLength(6) + gdi.getLineHeight(); + + gdi.addRectangle(rc, colorLightBlue, true, false); + + gdi.setData("IsEditingImage", 1); + + gdi.scrollToBottom(); + gdi.setCX(rc.right + gdi.scaleLength(10)); + gdi.setCY(boxY); + + if (imgId != 0) { + previewImage(gdi, selIx); + } + else + gdi.refresh(); +} + +int ListEditor::selectImage(gdioutput &gdi, uint64_t imgId) { + vector> img; + image.enumerateImages(img); + img.emplace(img.begin(), lang.tl("Ingen[bild]"), -2); + gdi.addItem("Image", img); + + int ix = image.getEnumerationIxFromId(imgId); + if (ix >= 0) + gdi.selectItemByData("Image", ix); + else + gdi.selectFirstItem("Image"); + + return ix; +} + void ListEditor::showExample(gdioutput &gdi, EPostType type) { if (type == EPostType::lLastItem) { type = EPostType(gdi.getSelectedItem("Type").first); @@ -1275,6 +1556,46 @@ void ListEditor::showExample(gdioutput &gdi, const MetaListPost &mlp) { gdi.addRectangle(rrInner, color, true); } +void ListEditor::previewImage(gdioutput& gdi, int data) const { + gdi.restoreNoUpdate("image_preview"); + gdi.setRestorePoint("image_preview"); + if (data >= 0) { + auto imgId = image.getIdFromEnumeration(data); + oe->loadImage(imgId); + bool transparent = gdi.isChecked("TransparentWhite"); + image.reloadImage(imgId, transparent ? Image::ImageMethod::WhiteTransparent : Image::ImageMethod::Default); + gdi.addImage("", gdi.getCY(), gdi.getCX(), 0, itow(imgId)); + } + gdi.refreshFast(); +} + +void ListEditor::updateImageStatus(gdioutput& gdi, int data) { + bool hasImg = int(data) >= 0; + gdi.setInputStatus("ImgWidth", hasImg); + gdi.setInputStatus("ImgHeight", hasImg); + gdi.setInputStatus("PreserveAspectRatio", hasImg); + gdi.setInputStatus("ImgOffsetX", hasImg); + gdi.setInputStatus("ImgOffsetY", hasImg); + + if (hasImg) { + auto imgId = image.getIdFromEnumeration(data); + int h = image.getHeight(imgId); + int w = image.getWidth(imgId); + gdi.setText("ImgWidth", w); + gdi.setText("ImgHeight", h); + + if (gdi.getTextNo("ImgOffsetX") == 0 && gdi.getTextNo("ImgOffsetY") == 0) { + // Change from blank to "0" + gdi.setText("ImgOffsetX", 0); + gdi.setText("ImgOffsetY", 0); + } + } + else { + gdi.setText("ImgWidth", L""); + gdi.setText("ImgHeight", L""); + } +} + const wchar_t *ListEditor::getIndexDescription(EPostType type) { if (type == lResultModuleTime || type == lResultModuleTimeTeam) return L"Index in X[index]#OutputTimes"; @@ -1310,7 +1631,7 @@ bool ListEditor::legStageTypeIndex(gdioutput &gdi, EPostType type, int leg) { gdi.setText("Leg", legW); } } - else if (MetaList::isAllStageType(type)) { + else if (MetaList::isAllStageType(type) || MetaList::isAllLegType(type)) { if (gdi.hasWidget("UseLeg")) gdi.removeWidget("UseLeg"); @@ -1322,9 +1643,22 @@ bool ListEditor::legStageTypeIndex(gdioutput &gdi, EPostType type, int leg) { else gdi.addString("TUseLeg", ypUseLeg, xpUseLeg, 0, getIndexDescription(type)); - if (!gdi.hasWidget("LegSel")) { + if (!gdi.hasWidget("LegSel")) gdi.addSelection(xpUseLeg + dx, ypUseLeg + dy, "LegSel", 160, gdi.scaleLength(300), editListCB); - vector> items; + vector> items; + if (MetaList::isAllLegType(type)) { + items.emplace_back(lang.tl("Automatisk"), 0); + items.emplace_back(lang.tl("Alla sträckor"), -2); + for (int j = 1; j <= 50; j++) { + items.emplace_back(lang.tl("Sträcka X#" + itos(j)), j); + } + gdi.addItem("LegSel", items); + if (leg >= -1) + gdi.selectItemByData("LegSel", leg + 1); + else if (leg == -2) + gdi.selectItemByData("LegSel", -2); + } + else { items.emplace_back(lang.tl("Alla tidigare etapper"), -2); for (int j = 1; j < 20; j++) { items.emplace_back(lang.tl("Etapp X#" + itos(j)), j); @@ -1373,6 +1707,7 @@ void ListEditor::editListProp(gdioutput &gdi, bool newList) { if (!newList) { gdi.restore("EditList", false); gdi.disableInput("EditList"); + gdi.disableInput("SplitPrint"); } gdi.dropLine(0.8); @@ -1552,6 +1887,80 @@ void ListEditor::editListProp(gdioutput &gdi, bool newList) { gdi.setInputFocus("Name"); } +void ListEditor::statusSplitPrint(gdioutput& gdi, bool status) { + gdi.setInputStatus("Split", status); + gdi.setInputStatus("Speed", status); + gdi.setInputStatus("Result", status); + gdi.setInputStatus("Analysis", status); +} + +void ListEditor::splitPrintList(gdioutput& gdi) { + checkUnsaved(gdi); + + if (!currentList) + return; + + MetaList& list = *currentList; + + gdi.restore("EditList", false); + gdi.disableInput("EditList"); + gdi.disableInput("SplitPrint"); + + gdi.dropLine(0.8); + + int x1 = gdi.getCX(); + int y1 = gdi.getCY(); + int margin = gdi.scaleLength(10); + gdi.setCX(x1 + margin); + + gdi.dropLine(); + gdi.fillDown(); + gdi.addString("", boldLarge, "Sträcktidsutskrift").setColor(colorDarkGrey); + gdi.dropLine(); + + gdi.fillRight(); + gdi.pushX(); + + bool isSP = list.isSplitPrintList(); + auto sp = list.getSplitPrintInfo(); + gdi.fillDown(); + + gdi.addCheckbox("UseForSplit", "Använd listan för sträcktidsutskrift", editListCB, isSP); + + gdi.dropLine(0.5); + gdi.fillRight(); + gdi.addCheckbox("Split", "Inkludera sträcktider", nullptr, isSP ? sp->includeSplitTimes : true); + gdi.addCheckbox("Speed", "Inkludera tempo", nullptr, isSP ? sp->withSpeed : true); + gdi.addCheckbox("Result", "Inkludera individuellt resultat", nullptr, isSP ? sp->withResult : true); + gdi.addCheckbox("Analysis", "Inkludera bomanalys", nullptr, isSP ? sp->withAnalysis : true); + + statusSplitPrint(gdi, isSP); + + gdi.dropLine(0.8); + gdi.setCX(gdi.getCX() + 20); + gdi.addButton("ApplySplitList", "OK", editListCB); + gdi.addButton("Cancel", "Avbryt", editListCB); + + gdi.dropLine(3); + int maxY = gdi.getCY(); + int maxX = gdi.getCX(); + + gdi.fillDown(); + gdi.popX(); + gdi.setData("IsSplitListEdit", 1); + + RECT rc; + rc.top = y1; + rc.left = x1; + rc.right = maxX + gdi.scaleLength(6); + rc.bottom = maxY; + + gdi.addRectangle(rc, colorLightBlue, true); + + gdi.scrollToBottom(); + gdi.refresh(); +} + void ListEditor::makeDirty(gdioutput &gdi, DirtyFlag inside, DirtyFlag outside) { if (inside == MakeDirty) dirtyInt = true; @@ -1575,10 +1984,10 @@ void ListEditor::makeDirty(gdioutput &gdi, DirtyFlag inside, DirtyFlag outside) bool ListEditor::checkSave(gdioutput &gdi) { if (dirtyInt || dirtyExt) { gdioutput::AskAnswer answer = gdi.askCancel(L"Vill du spara ändringar?"); - if (answer == gdioutput::AnswerCancel) + if (answer == gdioutput::AskAnswer::AnswerCancel) return false; - if (answer == gdioutput::AnswerYes) { + if (answer == gdioutput::AskAnswer::AnswerYes) { if (currentIndex >= 0) gdi.sendCtrlMessage("SaveInside"); else if (gdi.sendCtrlMessage("SaveFile") == 0) diff --git a/code/listeditor.h b/code/listeditor.h index f4cfb60..f04c6d7 100644 --- a/code/listeditor.h +++ b/code/listeditor.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -43,6 +43,7 @@ private: MetaList *currentList; void setCurrentList(MetaList *lst); int currentIndex; + int currentRunnerId = 0; wstring savedFileName; bool dirtyExt; bool dirtyInt; @@ -54,12 +55,22 @@ private: void updateType(int iType, gdioutput &gdi); bool saveListPost(gdioutput &gdi, MetaListPost &mlp); + bool saveImage(gdioutput& gdi, MetaListPost& mlp); + + static int selectImage(gdioutput& gdi, uint64_t imgId); + + void updateImageStatus(gdioutput& gdi, int data); ButtonInfo &addButton(gdioutput &gdi, const MetaListPost &mlp, int x, int y, int lineIx, int ix) const; + + void ListEditor::editDlgStart(gdioutput& gdi, int id, const char* title, int& x1, int& y1, int& boxY); + void editListPost(gdioutput &gdi, const MetaListPost &mlp, int id); - + void editImage(gdioutput& gdi, const MetaListPost& mlp, int id); + void previewImage(gdioutput &gdi, int data) const; + void showExample(gdioutput &gdi, EPostType type = EPostType::lLastItem); void showExample(gdioutput &gdi, const MetaListPost &mlp); @@ -68,10 +79,14 @@ private: void editListProp(gdioutput &gdi, bool newList); + void statusSplitPrint(gdioutput& gdi, bool status); + + void splitPrintList(gdioutput& gdi); + enum DirtyFlag {MakeDirty, ClearDirty, NoTouch}; - /// Check (and autosave) if there are unsaved changes in a dialog box - void checkUnsaved(gdioutput &gdi); + /// Check (and autosave) if there are unsaved changes in a dialog box. Return force flag + bool checkUnsaved(gdioutput &gdi); /// Check and ask if there are changes to save bool checkSave(gdioutput &gdi); @@ -91,6 +106,7 @@ private: bool legStageTypeIndex(gdioutput &gdi, EPostType type, int leg); + void renderListPreview(gdioutput& gdi); public: ListEditor(oEvent *oe); diff --git a/code/liveresult.cpp b/code/liveresult.cpp index 2d21b29..fcad772 100644 --- a/code/liveresult.cpp +++ b/code/liveresult.cpp @@ -1,6 +1,6 @@ /********************i**************************************************** MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -89,10 +89,21 @@ void LiveResult::showTimer(gdioutput &gdi, const oListInfo &liIn) { 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; + vector pp; oe->synchronizeList({ oListId::oLRunnerId, oListId::oLPunchId }); - + oe->getLatestPunches(lastTime, pp); processedPunches.clear(); @@ -103,14 +114,14 @@ void LiveResult::showTimer(gdioutput &gdi, const oListInfo &liIn) { 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 @@ -125,9 +136,9 @@ void LiveResult::showTimer(gdioutput &gdi, const oListInfo &liIn) { 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; + 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(); @@ -141,22 +152,15 @@ void LiveResult::showTimer(gdioutput &gdi, const oListInfo &liIn) { } } if (time < 100000000 && r->getStatus() <= StatusOK) { -// results.push_back(Result()); -// results.back().r = r; -// results.back().time = time; + // 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(); } @@ -352,20 +356,41 @@ void LiveResult::handle(gdioutput &gdi, BaseInfo &bu, GuiEventType type) { 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) { - 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); + showResults(gdi); } else if (type == GUI_TIMER) { if (size_t(showResultList) >= results.size()) @@ -379,9 +404,10 @@ void LiveResult::handle(gdioutput &gdi, BaseInfo &bu, GuiEventType type) { 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; @@ -400,6 +426,22 @@ void LiveResult::handle(gdioutput &gdi, BaseInfo &bu, GuiEventType type) { } } +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(); diff --git a/code/liveresult.h b/code/liveresult.h index 9ed9dc8..a6e7ca3 100644 --- a/code/liveresult.h +++ b/code/liveresult.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -49,14 +49,17 @@ class LiveResult : public GuiHandler { int place; int runnerId; int time; + wstring name; bool operator<(const Result &b) const { return time < b.time; } }; - vector< Result > results; + vector results; void calculateResults(); + void readoutResult(); + void showResults(gdioutput& gdi); public: LiveResult(oEvent *oe); diff --git a/code/localizer.cpp b/code/localizer.cpp index 0d5703c..211f017 100644 --- a/code/localizer.cpp +++ b/code/localizer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/localizer.h b/code/localizer.h index ef13f96..ec247ce 100644 --- a/code/localizer.h +++ b/code/localizer.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/machinecontainer.cpp b/code/machinecontainer.cpp index 745688e..66ce905 100644 --- a/code/machinecontainer.cpp +++ b/code/machinecontainer.cpp @@ -54,7 +54,7 @@ void MachineContainer::AbstractMachine::load(const xmlobject &data) { xmlList out; data.getObjects(out); for (auto &x : out) { - props[x.getName()] = x.getw(); + props[x.getName()] = x.getWStr(); } } diff --git a/code/meos.cpp b/code/meos.cpp index a7f73b5..e5cd546 100644 --- a/code/meos.cpp +++ b/code/meos.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -1832,14 +1832,13 @@ bool getMeOSFile(wchar_t *FileNamePath, const wchar_t *FileName) { return true; } -bool getUserFile(wchar_t *FileNamePath, const wchar_t *FileName) -{ +bool getUserFile(wchar_t* FileNamePath, const wchar_t* FileName) { wchar_t Path[MAX_PATH]; wchar_t AppPath[MAX_PATH]; - if (SHGetSpecialFolderPath(hWndMain, Path, CSIDL_APPDATA, 1)!=NOERROR) { - int i=wcslen(Path); - if (Path[i-1]!='\\') + if (SHGetSpecialFolderPath(hWndMain, Path, CSIDL_APPDATA, 1) != NOERROR) { + int i = wcslen(Path); + if (Path[i - 1] != '\\') wcscat_s(Path, MAX_PATH, L"\\"); wcscpy_s(AppPath, MAX_PATH, Path); diff --git a/code/meos.rc b/code/meos.rc index df19e9e..c2f3092 100644 --- a/code/meos.rc +++ b/code/meos.rc @@ -52,6 +52,7 @@ IDB_ECO BITMAP "bmp00001.bmp" IDI_SPLASHIMAGE PNG "meos.png" IDI_MEOSIMAGE PNG "title.png" +IDI_MEOSINFO PNG "info24.png" ///////////////////////////////////////////////////////////////////////////// // English (United States) resources diff --git a/code/meos_util.cpp b/code/meos_util.cpp index 76002f3..3e7a27b 100644 --- a/code/meos_util.cpp +++ b/code/meos_util.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -152,7 +152,7 @@ int getRelativeDay() { return int(qp); } -__int64 SystemTimeToInt64Second(const SYSTEMTIME &st) { +__int64 SystemTimeToInt64TenthSecond(const SYSTEMTIME &st) { FILETIME ft; SystemTimeToFileTime(&st, &ft); @@ -160,16 +160,16 @@ __int64 SystemTimeToInt64Second(const SYSTEMTIME &st) { u.HighPart = ft.dwHighDateTime; u.LowPart = ft.dwLowDateTime; __int64 qp = u.QuadPart; - qp /= __int64(10) * 1000 * 1000; + qp /= __int64(1000 * 1000 * timeUnitsPerSecond); return qp; } -SYSTEMTIME Int64SecondToSystemTime(__int64 time) { +SYSTEMTIME Int64TenthSecondToSystemTime(__int64 time) { SYSTEMTIME st; FILETIME ft; ULARGE_INTEGER u; - u.QuadPart = time * __int64(10) * 1000 * 1000; + u.QuadPart = time * __int64(1000 * 1000 * timeUnitsPerSecond); ft.dwHighDateTime = u.HighPart; ft.dwLowDateTime = u.LowPart; @@ -257,44 +257,87 @@ int convertDateYMS(const wstring &m, SYSTEMTIME &st, bool checkValid) { return convertDateYMS(ms, st, checkValid); } //Absolute time string to SYSTEM TIME -int convertDateYMS(const string &m, SYSTEMTIME &st, bool checkValid) { +int convertDateYMS(const string& m, SYSTEMTIME& st, bool checkValid) { memset(&st, 0, sizeof(st)); - if (m.length()==0) + if (m.length() == 0) return -1; - int len=m.length(); - for (int k=0;k='0' && b<='9')) ) + if (!(b == '-' || b == ' ' || (b >= '0' && b <= '9'))) return -1; + + if (b == '-') + dashCount++; } - int year=atoi(m.c_str()); - if (year<1900 || year>3000) - return -1; + int year = atoi(m.c_str()); - int month=0; - int day=0; - int kp=m.find_first_of('-'); + if (dashCount == 0) { + int day = year % 100; + year /= 100; + int month = year % 100; + year /= 100; - if (kp!=string::npos) { - string mtext=m.substr(kp+1); - month=atoi(mtext.c_str()); + if ((year > 0 && year < 100) || (year == 0 && m.size() > 2 && m[0] == '0' && m[1] == '0')) + year = extendYear(year); - if (month<1 || month>12) { + if (year < 1900 || year>3000) + return -1; + + if (month < 1 || month>12) { if (checkValid) return -1; month = 1; } - kp=mtext.find_last_of('-'); + if (day < 1 || day>31) { + if (checkValid) + return -1; + day = 1; + } - if (kp!=string::npos) { - day=atoi(mtext.substr(kp+1).c_str()); - if (day<1 || day>31) { + st.wYear = year; + st.wMonth = month; + st.wDay = day; + + int t = year * 100 * 100 + month * 100 + day; + if (t < 0) + return -1; + + return t; + } + + if ((year > 0 && year < 100) || (year == 0 && m.size() > 2 && m[0] == '0' && m[1] == '0')) + year = extendYear(year); + + if (year < 1900 || year>3000) + return -1; + + int month = 0; + int day = 0; + int kp = m.find_first_of('-'); + + if (kp != string::npos) { + string mtext = m.substr(kp + 1); + month = atoi(mtext.c_str()); + + if (month < 1 || month>12) { + if (checkValid) + return -1; + month = 1; + } + + kp = mtext.find_last_of('-'); + + if (kp != string::npos) { + day = atoi(mtext.substr(kp + 1).c_str()); + if (day < 1 || day>31) { if (checkValid) return -1; day = 1; @@ -306,8 +349,8 @@ int convertDateYMS(const string &m, SYSTEMTIME &st, bool checkValid) { st.wDay = day; - int t = year*100*100+month*100+day; - if (t<0) return -1; + int t = year * 100 * 100 + month * 100 + day; + if (t < 0) return -1; return t; } @@ -356,7 +399,7 @@ int convertAbsoluteTimeHMS(const string &m, int daysZeroTime) { return -1; if (tpart < daysZeroTime) days--; - return days * 3600 * 24 + tpart; + return days * timeConstHour * 24 + tpart; } return -1; } @@ -364,7 +407,7 @@ int convertAbsoluteTimeHMS(const string &m, int daysZeroTime) { int plusIndex = -1; for (int k=0;k='0' && b<='9')) ) { + if ( !(isspace(b) || b==':' || (b>='0' && b<='9') || b == '.' || b == ',') ) { if (b=='+' && plusIndex ==-1 && k>0) plusIndex = k; else @@ -377,7 +420,7 @@ int convertAbsoluteTimeHMS(const string &m, int daysZeroTime) { int d = atoi(m.c_str()); if (d>0 && t>=0) - return d*24*3600 + t; + return d*24* timeConstHour + t; else return -1; } @@ -389,6 +432,7 @@ int convertAbsoluteTimeHMS(const string &m, int daysZeroTime) { int minute=0; int second=0; + int tenth = 0; int kp=m.find_first_of(':'); if (kp!=string::npos) { @@ -401,13 +445,27 @@ int convertAbsoluteTimeHMS(const string &m, int daysZeroTime) { kp=mtext.find_last_of(':'); if (kp!=string::npos) { - second=atoi(mtext.substr(kp+1).c_str()); + second=atoi(mtext.c_str() + kp+1); if (second<0 || second>60) second=0; + + if (timeConstSecond > 1) { + kp = mtext.find_last_of('.'); + if (kp == string::npos) + kp = mtext.find_last_of(','); + if (kp != string::npos) { + tenth = atoi(mtext.c_str() + kp + 1); + if (tenth < 0 || tenth >= 10) + tenth = 0; + } + } } } - int t=hour*3600+minute*60+second; - if (t<0) return 0; + + int t = hour * timeConstHour + minute * timeConstMinute + second * timeConstSecond + tenth; + + if (t<0) + return 0; return t; } @@ -477,7 +535,7 @@ int convertAbsoluteTimeISO(const string &m) if (second<0 || second>60) return -1; - int t = hour*3600 + minute*60 + second; + int t = hour * timeConstHour + minute * timeConstMinute + second; return t; } @@ -509,31 +567,50 @@ int convertAbsoluteTimeMS(const string &m) mtext=m; minute=atoi(mtext.c_str()); - + int hour = 0; if (minute<0 || minute>60*24) minute=0; int kp=mtext.find_first_of(':'); - + bool gotSecond = false; if (kp!=string::npos) { mtext = mtext.substr(kp+1); second = atoi(mtext.c_str()); + gotSecond = true; if (second<0 || second>60) second=0; } - int t=minute*60+second; - - kp=mtext.find_first_of(':'); - if (kp!=string::npos) { + int t; + kp = mtext.find_first_of(':'); + if (kp != string::npos) { //Allow also for format +-HH:MM:SS - mtext = mtext.substr(kp+1); - second=atoi(mtext.c_str()); - if (second<0 || second>60) - second=0; - else - t = t*60 + second; + hour = minute; + minute = second; + + mtext = mtext.substr(kp + 1); + second = atoi(mtext.c_str()); + if (second < 0 || second>60) + second = 0; } + + int tenth = 0; + if (timeConstSecond > 1) { + kp = mtext.find_first_of('.'); + if (kp == string::npos) + kp = mtext.find_last_of(','); + if (kp != string::npos) { + tenth = atoi(mtext.c_str() + kp + 1); + if (!gotSecond) { // Reinterpret minute as second (no minute was specified) + second = minute; + minute = 0; + } + if (tenth < 0 || tenth >= 10) + tenth = 0; + } + } + t = hour * timeConstHour + minute * timeConstMinute + second * timeConstSecond + tenth; + return sign*t; } @@ -543,13 +620,37 @@ int convertAbsoluteTimeMS(const wstring &m) { } //Generate +-MM:SS or +-HH:MM:SS -const wstring &getTimeMS(int m) { +const wstring &formatTimeMS(int m, bool force2digit, SubSecond mode) { wchar_t bf[32]; int am = abs(m); - if (am < 3600 || !MeOSUtil::useHourFormat) - swprintf_s(bf, L"-%02d:%02d", am/60, am%60); - else if (am < 3600*48) - swprintf_s(bf, L"-%02d:%02d:%02d", am/3600, (am/60)%60, am%60); + if (am < timeConstHour || !MeOSUtil::useHourFormat) { + if (force2digit) { + if (mode == SubSecond::Off || (mode == SubSecond::Auto && m % 10 == 0)) + swprintf_s(bf, L"-%02d:%02d", am / timeConstMinute, (am / timeConstSecond) % 60); + else + swprintf_s(bf, L"-%02d:%02d.%d", am / timeConstMinute, (am / timeConstSecond) % 60, am % timeConstSecond); + } + else { + if (mode == SubSecond::Off || (mode == SubSecond::Auto && m % 10 == 0)) + swprintf_s(bf, L"-%d:%02d", am / timeConstMinute, (am / timeConstSecond) % 60); + else + swprintf_s(bf, L"-%d:%02d.%d", am / timeConstMinute, (am / timeConstSecond) % 60, am % timeConstSecond); + } + } + else if (am < timeConstHour * 48) { + if (force2digit) { + if (mode == SubSecond::Off || (mode == SubSecond::Auto && m % 10 == 0)) + swprintf_s(bf, L"-%02d:%02d:%02d", am / timeConstHour, (am / timeConstMinute) % 60, (am / timeConstSecond) % 60); + else + swprintf_s(bf, L"-%02d:%02d:%02d.%d", am / timeConstHour, (am / timeConstMinute) % 60, (am / timeConstSecond) % 60, am % timeConstSecond); + } + else { + if (mode == SubSecond::Off || (mode == SubSecond::Auto && m % 10 == 0)) + swprintf_s(bf, L"-%d:%02d:%02d", am / timeConstHour, (am / timeConstMinute) % 60, (am / timeConstSecond) % 60); + else + swprintf_s(bf, L"-%d:%02d:%02d.%d", am / timeConstHour, (am / timeConstMinute) % 60, (am / timeConstSecond) % 60, am % timeConstSecond); + } + } else { m = 0; bf[0] = 0x2013; @@ -564,15 +665,25 @@ const wstring &getTimeMS(int m) { return res; } -const wstring &formatTime(int rt) { +const wstring &formatTime(int rt, SubSecond mode) { wstring &res = StringCache::getInstance().wget(); - if (rt>0 && rt<3600*999) { - wchar_t bf[16]; - if (rt>=3600 && MeOSUtil::useHourFormat) - swprintf_s(bf, 16, L"%d:%02d:%02d", rt/3600,(rt/60)%60, rt%60); - else - swprintf_s(bf, 16, L"%d:%02d", (rt/60), rt%60); + if (rt>0 && rt= timeConstHour && MeOSUtil::useHourFormat) + swprintf_s(bf, L"%d:%02d:%02d", rt / timeConstHour, (rt / timeConstMinute) % 60, (rt / timeConstSecond) % 60); + else + swprintf_s(bf, L"%d:%02d", (rt / timeConstMinute), (rt / timeConstSecond) % 60); + } + else { + if (rt >= timeConstHour && MeOSUtil::useHourFormat) + swprintf_s(bf, L"%d:%02d:%02d.%d", rt / timeConstHour, (rt / timeConstMinute) % 60, (rt / timeConstSecond) % 60, rt%timeConstSecond); + else + swprintf_s(bf, L"%d:%02d.%d", (rt / timeConstMinute), (rt / timeConstSecond) % 60, rt%timeConstSecond); + + + } res = bf; return res; } @@ -583,12 +694,12 @@ const wstring &formatTime(int rt) { const string &formatTimeN(int rt) { string &res = StringCache::getInstance().get(); - if (rt>0 && rt<3600*999) { + if (rt>0 && rt=3600 && MeOSUtil::useHourFormat) - sprintf_s(bf, 16, "%d:%02d:%02d", rt/3600,(rt/60)%60, rt%60); + if (rt>= timeConstHour && MeOSUtil::useHourFormat) + sprintf_s(bf, 16, "%d:%02d:%02d", rt/ timeConstHour,(rt/timeConstMinute)%60, (rt/timeConstSecond)%60); else - sprintf_s(bf, 16, "%d:%02d", (rt/60), rt%60); + sprintf_s(bf, 16, "%d:%02d", (rt/timeConstMinute), (rt/timeConstSecond)%60); res = bf; return res; @@ -597,11 +708,15 @@ const string &formatTimeN(int rt) { return res; } -const wstring &formatTimeHMS(int rt) { +const wstring &formatTimeHMS(int rt, SubSecond mode) { wstring &res = StringCache::getInstance().wget(); if (rt>=0) { - wchar_t bf[32]; - swprintf_s(bf, 16, L"%02d:%02d:%02d", rt/3600,(rt/60)%60, rt%60); + wchar_t bf[40]; + if (mode == SubSecond::Off || (mode == SubSecond::Auto && rt%10 == 0)) + swprintf_s(bf, 16, L"%02d:%02d:%02d", rt/timeConstHour,(rt/timeConstMinute)%60, (rt/timeConstSecond)%60); + else + swprintf_s(bf, 16, L"%02d:%02d:%02d.%d", rt / timeConstHour, (rt / timeConstMinute) % 60, (rt / timeConstSecond) % 60, rt % timeConstSecond); + res = bf; return res; } @@ -612,10 +727,10 @@ const wstring &formatTimeHMS(int rt) { wstring formatTimeIOF(int rt, int zeroTime) { - if (rt>0 && rt<(3600*24*10)) { - rt+=zeroTime; + if (rt > 0) { + rt += zeroTime; wchar_t bf[16]; - swprintf_s(bf, 16, L"%02d:%02d:%02d", (rt/3600)%24,(rt/60)%60, rt%60); + swprintf_s(bf, 16, L"%02d:%02d:%02d", (rt / timeConstHour) % 24, (rt / timeConstMinute) % 60, (rt / timeConstSecond) % 60); return bf; } @@ -766,7 +881,7 @@ wstring itow(int64_t i) { wstring itow(uint64_t i) { wchar_t bf[32]; - _i64tow_s(i, bf, 32, 10); + _ui64tow_s(i, bf, 32, 10); return bf; } @@ -819,14 +934,19 @@ bool filterMatchString(const string &c, const char *filt_lc) return strstr(key, filt_lc)!=0; } -bool filterMatchString(const wstring &c, const wchar_t *filt_lc) { +bool filterMatchString(const wstring &c, const wchar_t *filt_lc, int &score) { + score = 0; if (filt_lc[0] == 0) return true; wchar_t key[2048]; wcscpy_s(key, c.c_str()); CharLowerBuff(key, c.length()); - - return wcsstr(key, filt_lc)!=0; + bool match = wcsstr(key, filt_lc) != 0; + if (match) { + while (filt_lc[score] && key[score] && filt_lc[score] == key[score]) + score++; + } + return match; } @@ -1936,8 +2056,8 @@ int getTimeZoneInfo(const wstring &date) { else if (datecodeUTC < datecode) daydiff = -1; - int t = st.wHour * 3600; - int tUTC = daydiff * 24 * 3600 + utc.wHour * 3600 + utc.wMinute * 60 + utc.wSecond; + int t = st.wHour * timeConstSecPerHour; + int tUTC = daydiff * 24 * timeConstSecPerHour + utc.wHour * timeConstSecPerHour + utc.wMinute * timeConstSecPerMin + utc.wSecond; lastValue = tUTC - t; return lastValue; @@ -1949,12 +2069,12 @@ wstring getTimeZoneString(const wstring &date) { return L"+00:00"; else if (a>0) { wchar_t bf[12]; - swprintf_s(bf, L"-%02d:%02d", a/3600, (a/60)%60); + swprintf_s(bf, L"-%02d:%02d", a/timeConstSecPerHour, (a/timeConstMinPerHour)%60); return bf; } else { wchar_t bf[12]; - swprintf_s(bf, L"+%02d:%02d", a/-3600, (a/-60)%60); + swprintf_s(bf, L"+%02d:%02d", a/-timeConstSecPerHour, (a/-timeConstMinPerHour)%60); return bf; } } @@ -2291,3 +2411,97 @@ const char* meosException::narrow(const wstring& msg) { static string nmsg(msg.begin(), msg.end()); return nmsg.c_str(); } + +int parseRelativeTime(const char *data) { + if (data) { + int ret = atoi(data); + if (timeConstSecond > 1) { + int j = 0; + while (data[j]) { + if (data[j] == '.') { + int t = data[j + 1] - '0'; + if (t > 0 && t < 10) { + if (ret < 0 || data[0] == '-') + return ret * timeConstSecond - t; + else + return ret * timeConstSecond + t; + } + break; + } + j++; + } + } + if (ret == -1) + return ret; // Special value + + return ret * timeConstSecond; + } + return 0; +} + +int parseRelativeTime(const wchar_t *data) { + if (data) { + int ret = _wtoi(data); + if (timeConstSecond > 1) { + int j = 0; + while (data[j]) { + if (data[j] == '.') { + int t = data[j + 1] - '0'; + if (t > 0 && t < 10) { + if (ret < 0 || data[0] == '-') + return ret * timeConstSecond - t; + else + return ret * timeConstSecond + t; + } + break; + } + j++; + } + } + if (ret == -1) + return ret; // Special value + + return ret * timeConstSecond; + } + return 0; +} + +const wstring &codeRelativeTimeW(int rt) { + wchar_t bf[32]; + int subSec = timeConstSecond == 1 ? 0 : rt % timeConstSecond; + + if (timeConstSecond == 1 || rt == -1) + return itow(rt); + else if (subSec == 0 && rt != -10) + return itow(rt / timeConstSecond); + else if (rt > 0) { + swprintf_s(bf, L"%d.%d", rt / timeConstSecond, rt % timeConstSecond); + } + else { + rt = -rt; + swprintf_s(bf, L"-%d.%d", rt / timeConstSecond, rt % timeConstSecond); + } + wstring &res = StringCache::getInstance().wget(); + res = bf; + return res; +} + +const string &codeRelativeTime(int rt) { + char bf[32]; + int subSec = timeConstSecond == 1 ? 0 : rt % timeConstSecond; + + if (timeConstSecond == 1 || rt == -1) + return itos(rt); + else if (subSec == 0 && rt != -10) + return itos(rt / timeConstSecond); + else if (rt > 0) { + sprintf_s(bf, "%d.%d", rt / timeConstSecond, rt % timeConstSecond); + } + else { + rt = -rt; + sprintf_s(bf, "-%d.%d", rt / timeConstSecond, rt % timeConstSecond); + } + string &res = StringCache::getInstance().get(); + res = bf; + return res; +} diff --git a/code/meos_util.h b/code/meos_util.h index 90bc1c3..3195ca5 100644 --- a/code/meos_util.h +++ b/code/meos_util.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -72,9 +72,22 @@ int getRelativeDay(); /// Get time and date in a format that forms a part of a filename wstring getLocalTimeFileName(); -const wstring &getTimeMS(int m); -const wstring &formatTime(int rt); -const wstring &formatTimeHMS(int rt); +enum class SubSecond { + Off, + On, + Auto +}; + +int parseRelativeTime(const char *data); +int parseRelativeTime(const wchar_t *data); + +const wstring &codeRelativeTimeW(int rt); +const string &codeRelativeTime(int rt); + +// Format time MM:SS.t (force2digit=true) or M:SS.t (force2digit=false) +const wstring &formatTimeMS(int m, bool force2digit, SubSecond mode = SubSecond::Auto); +const wstring &formatTime(int rt, SubSecond mode = SubSecond::Auto); +const wstring &formatTimeHMS(int rt, SubSecond mode = SubSecond::Auto); wstring formatTimeIOF(int rt, int zeroTime); @@ -91,8 +104,8 @@ void processGeneralTime(const wstring &generalTime, wstring &meosTime, wstring & //string formatDate(int m, bool useIsoFormat); wstring formatDate(int m, bool useIsoFormat); -__int64 SystemTimeToInt64Second(const SYSTEMTIME &st); -SYSTEMTIME Int64SecondToSystemTime(__int64 time); +__int64 SystemTimeToInt64TenthSecond(const SYSTEMTIME &st); +SYSTEMTIME Int64TenthSecondToSystemTime(__int64 time); #define NOTIME 0x7FFFFFFF @@ -141,7 +154,7 @@ wstring itow(uint64_t i); ///Lower case match (filt_lc must be lc) bool filterMatchString(const string &c, const char *filt_lc); -bool filterMatchString(const wstring &c, const wchar_t *filt_lc); +bool filterMatchString(const wstring &c, const wchar_t *filt_lc, int &score); bool matchNumber(int number, const wchar_t *key); diff --git a/code/meosexception.h b/code/meosexception.h index 7d98d9e..95dd394 100644 --- a/code/meosexception.h +++ b/code/meosexception.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -43,3 +43,6 @@ public: } }; +class meosCancel : public meosException { + +}; diff --git a/code/meosvc15.vcxproj b/code/meosvc15.vcxproj index 95d7666..0bd6c96 100644 --- a/code/meosvc15.vcxproj +++ b/code/meosvc15.vcxproj @@ -462,6 +462,7 @@ + %(PreprocessorDefinitions) @@ -669,6 +670,7 @@ + @@ -710,6 +712,7 @@ + diff --git a/code/meosversion.cpp b/code/meosversion.cpp index 014bfe3..3a702a2 100644 --- a/code/meosversion.cpp +++ b/code/meosversion.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -23,23 +23,23 @@ #include #include "meos_util.h" -//ABCDEFGHIJKLMNO +//ABCDEFGHIJKLMNOP int getMeosBuild() { - string revision("$Rev: 1152 $"); + string revision("$Rev: 1225 $"); return 174 + atoi(revision.substr(5, string::npos).c_str()); } wstring getMeosDate() { - wstring date(L"$Date: 2022-05-04 14:47:48 +0200 (ons, 04 maj 2022) $"); + wstring date(L"$Date: 2023-02-08 19:30:10 +0100 (ons, 08 feb 2023) $"); return date.substr(7,10); } wstring getBuildType() { - return L"U1"; // No parantheses (...) + return L"Beta 1"; // No parantheses (...) } wstring getMajorVersion() { - return L"3.8"; + return L"3.9"; } wstring getMeosFullVersion() { @@ -67,31 +67,8 @@ wstring getMeosCompectVersion() { } void getSupporters(vector &supp, vector &developSupp) -{ - developSupp.emplace_back(L"Almby IK, Örebro");//2019-- - 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"); - supp.emplace_back(L"Kexholm SK"); - supp.emplace_back(L"Utby IK"); - supp.emplace_back(L"JWOC 2019"); - developSupp.emplace_back(L"OK Nackhe"); - supp.emplace_back(L"OK Rodhen"); - developSupp.emplace_back(L"SongTao Wang / Henan Zhixing Exploration Sports Culture Co., Ltd."); - developSupp.emplace_back(L"Australian and Oceania Orienteering Championships 2019"); - supp.emplace_back(L"Järfälla OK"); - supp.emplace_back(L"TJ Slávia Farmaceut Bratislava"); - supp.emplace_back(L"Magnus Thornell, Surahammars SOK"); - supp.emplace_back(L"Mariager Fjord OK"); - supp.emplace_back(L"Nässjö OK"); - supp.emplace_back(L"Ringsjö OK"); - supp.emplace_back(L"Big Foot Orienteers"); - supp.emplace_back(L"Erik Hulthen, Mölndal Outdoor IF"); - supp.emplace_back(L"Bay Area Orienteering Club"); - supp.emplace_back(L"FinspÃ¥ngs SOK"); - supp.emplace_back(L"OK Gorm, Denmark"); +{ + supp.emplace_back(L"OK Gorm, Denmark");//2020- supp.emplace_back(L"Nyköpings OK"); supp.emplace_back(L"Thomas Engberg, VK Uvarna"); supp.emplace_back(L"LG Axmalm, Sävedalens AIK"); @@ -113,11 +90,9 @@ void getSupporters(vector &supp, vector &developSupp) supp.emplace_back(L"Fredrik Magnusson, Laholms IF"); supp.emplace_back(L"KOB ATU KoÅ¡ice"); supp.emplace_back(L"Alfta-Ösa OK"); - supp.emplace_back(L"HEYRIES, ACA Aix en Provence"); supp.emplace_back(L"IFK Kiruna"); supp.emplace_back(L"Smedjebackens OK"); supp.emplace_back(L"Gunnar Persson, Svanesunds GIF"); - supp.emplace_back(L"Kamil Pipek, OK Lokomotiva Pardubice"); supp.emplace_back(L"Køge Orienteringsklub"); supp.emplace_back(L"Simrishamns OK"); supp.emplace_back(L"OK Fryksdalen"); @@ -137,6 +112,7 @@ void getSupporters(vector &supp, vector &developSupp) supp.emplace_back(L"Hans Carlstedt, Sävedalens AIK"); supp.emplace_back(L"O-Liceo, Spain"); developSupp.emplace_back(L"Västerviks OK"); + supp.emplace_back(L"Aarhus 1900 Orientering"); supp.emplace_back(L"Ljusne Ala OK"); supp.emplace_back(L"Sävedalens AIK"); supp.emplace_back(L"Foothills Wanderers Orienteering Club"); @@ -144,6 +120,22 @@ void getSupporters(vector &supp, vector &developSupp) supp.emplace_back(L"Per Ã…gren, OK Enen"); supp.emplace_back(L"OK Roslagen"); supp.emplace_back(L"OK KolmÃ¥rden"); - + developSupp.emplace_back(L"Orienteering Queensland Inc."); + supp.emplace_back(L"Eksjö SOK"); + supp.emplace_back(L"Kolding OK"); + developSupp.emplace_back(L"Alfta-Ösa OK"); + supp.emplace_back(L"Erik Almséus, IFK Hedemora OK"); + supp.emplace_back(L"IK Gandvik, Skara"); + supp.emplace_back(L"Mats KÃ¥geson"); + supp.emplace_back(L"Lerums SOK"); + supp.emplace_back(L"OSC Hamburg"); + supp.emplace_back(L"HEYRIES, ACA Aix en Provence"); + developSupp.emplace_back(L"IFK Mora OK"); + supp.emplace_back(L"OK Rodhen"); + supp.emplace_back(L"Big Foot Orienteers"); + developSupp.emplace_back(L"OK MÃ¥sen"); + supp.emplace_back(L"Ligue PACA"); + supp.emplace_back(L"Kamil Pipek, OK Lokomotiva Pardubice"); + reverse(supp.begin(), supp.end()); } diff --git a/code/metalist.cpp b/code/metalist.cpp index f145dff..05de9c4 100644 --- a/code/metalist.cpp +++ b/code/metalist.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -37,8 +37,11 @@ #include "localizer.h" #include "gdifonts.h" #include "autocomplete.h" +#include "image.h" +#include "binencoder.h" extern oEvent *gEvent; +extern Image image; const int MAXLISTPARAMID = 10000000; @@ -438,9 +441,11 @@ int checksum(const wstring &str) { return ret; } - void MetaList::initUniqueIndex() const { - __int64 ix = 0; + int64_t ix = 0; + + if (splitPrintInfo) + ix = splitPrintInfo->checkSum(); for (int i = 0; i<4; i++) { const vector< vector > &lines = data[i]; @@ -461,6 +466,15 @@ void MetaList::initUniqueIndex() const { value = value * 31 + mp.mergeWithPrevious; value = value * 31 + mp.color; value = value * 31 + mp.textAdjust; + if (mp.type == lImage) { + value = value * 31 + mp.imageHeight; + value = value * 31 + mp.imageWidth; + value = value * 31 + mp.imageStyle; + value = value * 31 + mp.imageOffsetX; + value = value * 31 + mp.imageOffsetY; + value = value * 31 + mp.imageStyleUnderText; + } + ix = ix * 997 + value; } } @@ -579,12 +593,23 @@ void MetaList::addRow(int ix) { static void setFixedWidth(oPrintPost &added, const map, int> &indexPosToWidth, - int type, int j, int k) { - map, int>::const_iterator res = indexPosToWidth.find(tuple(type, j, k)); - if (res != indexPosToWidth.end()) - added.fixedWidth = res->second; - else - added.fixedWidth = 0; + int type, int j, int k, + const MetaListPost &mlp) { + if (added.type == lImage) { + added.fixedWidth = mlp.getImageWidth(); + added.fixedHeight = mlp.getImageHeight(); + added.dx += mlp.getImageOffsetX(); + added.dy += mlp.getImageOffsetY(); + if (mlp.imageUnderText()) + added.imageNoUpdatePos = true; + } + else { + map, int>::const_iterator res = indexPosToWidth.find(tuple(type, j, k)); + if (res != indexPosToWidth.end()) + added.fixedWidth = res->second; + else + added.fixedWidth = 0; + } } void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par, oListInfo &li) const { @@ -592,6 +617,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par PositionVer2 pos; const bool large = par.useLargeSize; li.lp = par; + li.setSplitPrintInfo(getSplitPrintInfo()); gdiFonts normal, header, small, italic; double s_factor; oe->calculateResults({}, oEvent::ResultType::ClassResult, false); @@ -668,7 +694,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par }; for (int i = 0; i<4; i++) { - const vector< vector > &lines = mList.data[i]; + const vector> &lines = mList.data[i]; gdiFonts defaultFont = normal; switch (i) { case 0: @@ -687,6 +713,10 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par return MetaList::isAllStageType(mp.type) && mp.leg == -1; }; + auto isAllLegType = [](const MetaListPost& mp) { + return MetaList::isAllLegType(mp.type) && mp.leg == -2; + }; + for (size_t j = 0; j &lineCopy = dataCopy[i].back(); - int firstStage = -1; + int firstStage = -1, firstLeg = -1; for (size_t k = 0; k < lines[j].size(); k++) { + if (lines[j][k].type == lImage) { + auto id = lines[j][k].getImageId(); + if (id > 0) { + oe->loadImage(id); + image.reloadImage(id, lines[j][k].getImageStyle() ? Image::ImageMethod::WhiteTransparent : Image::ImageMethod::Default); + } + } + if (isAllStageType(lines[j][k])) { if (firstStage == -1) firstStage = k; @@ -711,6 +749,26 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par firstStage = -1; } } + if (isAllLegType(lines[j][k])) { + if (firstLeg == -1) + firstLeg = k; + + if (k + 1 == lines[j].size() || !isAllLegType(lines[j][k + 1])) { + int ns = 0; + vector allC; + oe->getClasses(allC, false); + for (pClass c : allC) + ns = max(ns, c->getNumStages()); + + for (int s = 1; s <= ns; s++) { + for (size_t f = firstLeg; f <= k; f++) { + lineCopy.push_back(lines[j][f]); + lineCopy.back().leg = s - 1; + } + } + firstLeg = -1; + } + } else { lineCopy.push_back(lines[j][k]); } @@ -786,12 +844,18 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par } if (mp.limitWidth && mp.blockWidth > 0) width = gdi.scaleLength(mp.blockWidth); - else + else if (mp.type == lImage) { + if (mp.imageUnderText()) + width = 1; + else + width = gdi.scaleLength(mp.getImageWidth()); + } + else { width = li.getMaxCharWidth(oe, gdi, par.selection, typeFormats, font, - oPrintPost::encodeFont(fontFaces[i].font, - fontFaces[i].scale).c_str(), - large, max(mp.blockWidth, extraMinWidth)); - + oPrintPost::encodeFont(fontFaces[i].font, + fontFaces[i].scale).c_str(), + large, max(mp.blockWidth, extraMinWidth)); + } ++linePostCount[make_pair(i, j)]; // Count how many positions on this line indexPosToWidth[tuple(i, j, k)] = width; } @@ -911,10 +975,10 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par fontFaces[MLHead].scale); added.resultModuleIndex = getResultModuleIndex(oe, li, mp); - setFixedWidth(added, indexPosToWidth, MLHead, j, k); + setFixedWidth(added, indexPosToWidth, MLHead, j, k, mp); added.xlimit = indexPosToWidthSrc[tuple(MLHead, j, k)]; added.color = mp.color; - if (!mp.mergeWithPrevious) + if (!mp.mergeWithPrevious && mp.type != lImage) base = &added; added.useStrictWidth = mp.getLimitBlockWidth(); if (added.useStrictWidth) @@ -928,9 +992,14 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par added.fixedWidth = 0; } } - last = &added; + last = mp.type != lImage ? &added : nullptr; - next_dy = max(next_dy, fontHeight[make_pair(font, MLHead)]); + if (mp.type == lImage) { + if (!mp.imageUnderText()) + next_dy = max(next_dy, gdi.scaleLength(mp.getImageHeight())); + } + else + next_dy = max(next_dy, fontHeight[make_pair(font, MLHead)]); } dy += next_dy; next_dy = lineHeight; @@ -965,10 +1034,10 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par fontFaces[MLSubHead].scale); added.resultModuleIndex = getResultModuleIndex(oe, li, mp); - setFixedWidth(added, indexPosToWidth, MLSubHead, j, k); + setFixedWidth(added, indexPosToWidth, MLSubHead, j, k, mp); added.xlimit = indexPosToWidthSrc[tuple(MLSubHead, j, k)]; added.color = mp.color; - if (!mp.mergeWithPrevious) + if (!mp.mergeWithPrevious && mp.type != lImage) base = &added; added.useStrictWidth = mp.getLimitBlockWidth(); @@ -983,9 +1052,14 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par added.fixedWidth = 0; } } - last = &added; + last = mp.type != lImage ? &added : nullptr; - next_dy = max(next_dy, fontHeight[make_pair(font, MLSubHead)]); + if (mp.type == lImage) { + if (!mp.imageUnderText()) + next_dy = max(next_dy, gdi.scaleLength(mp.getImageHeight())); + } + else + next_dy = max(next_dy, fontHeight[make_pair(font, MLSubHead)]); } dy += next_dy; } @@ -1007,7 +1081,13 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par if (mp.font != formatIgnore) font = mp.font; - next_dy = max(next_dy, fontHeight[make_pair(font, MLList)]); + if (mp.type == lImage) { + if (!mp.imageUnderText()) + next_dy = max(next_dy, gdi.scaleLength(mp.getImageHeight())); + } + else + next_dy = max(next_dy, fontHeight[make_pair(font, MLList)]); + bool dmy; oPrintPost &added = li.addListPost(oPrintPost(mp.type, encode(mp.type, mp.text, dmy), font|mp.textAdjust, pos.get(label, s_factor), @@ -1016,14 +1096,14 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par fontFaces[MLList].scale); added.resultModuleIndex = getResultModuleIndex(oe, li, mp); - setFixedWidth(added, indexPosToWidth, MLList, j, k); + setFixedWidth(added, indexPosToWidth, MLList, j, k, mp); added.xlimit = indexPosToWidthSrc[tuple(MLList, j, k)]; added.useStrictWidth = mp.getLimitBlockWidth(); if (added.useStrictWidth) added.format |= textLimitEllipsis; added.color = mp.color; - if (!mp.mergeWithPrevious) + if (!mp.mergeWithPrevious && mp.type != lImage) base = &added; if (last && mp.mergeWithPrevious) { @@ -1033,7 +1113,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par added.fixedWidth = 0; } } - last = &added; + last = mp.type != lImage ? &added : nullptr; } dy += next_dy; } @@ -1062,7 +1142,13 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par else xp = pos.get(label, s_factor); - next_dy = max(next_dy, fontHeight[make_pair(font, MLSubList)]); + if (mp.type == lImage) { + if (!mp.imageUnderText()) + next_dy = max(next_dy, gdi.scaleLength(mp.getImageHeight())); + } + else + next_dy = max(next_dy, fontHeight[make_pair(font, MLSubList)]); + bool dmy; oPrintPost &added = li.addSubListPost(oPrintPost(mp.type, encode(mp.type, mp.text, dmy), font|mp.textAdjust, xp, dy+sublist_dy, mp.leg == -1 ? parLegNumber : make_pair(mp.leg, true))). @@ -1070,11 +1156,11 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par fontFaces[MLSubList].scale); added.color = mp.color; - if (!mp.mergeWithPrevious) + if (!mp.mergeWithPrevious && mp.type != lImage) base = &added; added.resultModuleIndex = getResultModuleIndex(oe, li, mp); - setFixedWidth(added, indexPosToWidth, MLSubList, j, k); + setFixedWidth(added, indexPosToWidth, MLSubList, j, k, mp); added.xlimit = indexPosToWidthSrc[tuple(MLSubList, j, k)]; added.useStrictWidth = mp.getLimitBlockWidth(); if (added.useStrictWidth) @@ -1087,7 +1173,7 @@ void MetaList::interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par added.fixedWidth = 0; } } - last = &added; + last = mp.type != lImage ? &added : nullptr; } dy += next_dy; } @@ -1387,11 +1473,11 @@ bool Position::postAdjust() { void MetaList::save(const wstring &file, const oEvent *oe) const { xmlparser xml; xml.openOutput(file.c_str(), true); - save(xml, oe); + save(xml, true, oe); xml.closeOut(); } -void MetaList::save(xmlparser &xml, const oEvent *oe) const { +void MetaList::save(xmlparser &xml, bool includeImages, const oEvent *oe) const { initSymbols(); xml.startTag("MeOSListDefinition", "version", getMajorVersion()); // xml.write("Title", defaultTitle); @@ -1401,6 +1487,13 @@ void MetaList::save(xmlparser &xml, const oEvent *oe) const { xml.write("ListOrigin", listOrigin); xml.write("Tag", tag); xml.write("UID", getUniqueId()); + + if (splitPrintInfo) { + xml.startTag("SplitPrint"); + splitPrintInfo->serialize(xml); + xml.endTag(); + } + xml.write("SortOrder", orderToSymbol[sortOrder]); xml.write("ListType", baseTypeToSymbol[listType]); xml.write("SubListType", baseTypeToSymbol[listSubType]); @@ -1454,6 +1547,22 @@ void MetaList::save(xmlparser &xml, const oEvent *oe) const { serialize(xml, "List", getList()); serialize(xml, "SubList", getSubList()); + if (includeImages) { + set img; + getUsedImages(img); + Encoder92 binEncoder; + for (auto imgId : img) { + wstring fileName = image.getFileName(imgId); + auto rawData = image.getRawData(imgId); + string encoded; + binEncoder.encode92(rawData, encoded); + vector> props; + props.emplace_back("filename", fileName); + props.emplace_back("id", itow(imgId)); + xml.writeAscii("Image", props, encoded); + } + } + xml.endTag(); } @@ -1475,6 +1584,11 @@ void MetaList::load(const xmlobject &xDef) { xDef.getObjectString("UID", uniqueIndex); xDef.getObjectString("ResultModule", resultModule); + auto xSplitPrint = xDef.getObject("SplitPrint"); + if (xSplitPrint) { + splitPrintInfo = make_shared(); + splitPrintInfo->deserialize(xSplitPrint); + } // string db = "Specified res mod: " + resultModule + "\n"; // OutputDebugString(db.c_str()); @@ -1523,29 +1637,29 @@ void MetaList::load(const xmlobject &xDef) { xmlobject xSubListFont = xDef.getObject("SubListFont"); if (xHeadFont) { - const wchar_t *f = xHeadFont.getw(); - fontFaces[MLHead].font = f != 0 ? f : L"arial"; + const wchar_t *f = xHeadFont.getWPtr(); + fontFaces[MLHead].font = f != nullptr ? f : L"arial"; fontFaces[MLHead].scale = xHeadFont.getObjectInt("scale"); fontFaces[MLHead].extraSpaceAbove = xHeadFont.getObjectInt("above"); } if (xSubHeadFont) { - const wchar_t *f = xSubHeadFont.getw(); - fontFaces[MLSubHead].font = f != 0 ? f : L"arial"; + const wchar_t *f = xSubHeadFont.getWPtr(); + fontFaces[MLSubHead].font = f != nullptr ? f : L"arial"; fontFaces[MLSubHead].scale = xSubHeadFont.getObjectInt("scale"); fontFaces[MLSubHead].extraSpaceAbove = xSubHeadFont.getObjectInt("above"); } if (xListFont) { - const wchar_t *f = xListFont.getw(); - fontFaces[MLList].font = f != 0 ? f : L"arial"; + const wchar_t *f = xListFont.getWPtr(); + fontFaces[MLList].font = f != nullptr ? f : L"arial"; fontFaces[MLList].scale = xListFont.getObjectInt("scale"); fontFaces[MLList].extraSpaceAbove = xListFont.getObjectInt("above"); } if (xSubListFont) { - const wchar_t *f = xSubListFont.getw(); - fontFaces[MLSubList].font = f != 0 ? f : L"arial"; + const wchar_t *f = xSubListFont.getWPtr(); + fontFaces[MLSubList].font = f != nullptr ? f : L"arial"; fontFaces[MLSubList].scale = xSubListFont.getObjectInt("scale"); fontFaces[MLSubList].extraSpaceAbove = xSubListFont.getObjectInt("above"); } @@ -1605,7 +1719,7 @@ void MetaList::load(const xmlobject &xDef) { xDef.getObjects("Filter", f); for (size_t k = 0; k bytes; + for (auto& img : imgs) { + wstring fileName, id; + img.getObjectString("filename", fileName); + img.getObjectString("id", id); + uint64_t imgId = _wcstoui64(id.c_str(), nullptr, 10); + string data = img.getRawStr(); + binEncoder.decode92(data, bytes); + image.provideFromMemory(imgId, fileName, bytes); + } + } void MetaList::getDynamicResults(vector &resultModules) const { @@ -1729,6 +1859,14 @@ bool MetaListContainer::updateResultModule(const DynamicResult &dr, bool updateS return changed; } +void MetaListContainer::getUsedImages(set& imgId) const { + for (size_t i = 0; i < data.size(); i++) { + if (data[i].first == ExternalList) { + data[i].second.getUsedImages(imgId); + } + } +} + void MetaList::serialize(xmlparser &xml, const string &tagp, const vector< vector > &lp) const { xml.startTag(tagp.c_str()); @@ -1802,6 +1940,8 @@ void MetaListPost::getTypes(vector< pair > &types, int ¤t continue; if (it->first == lAlignNext) continue; + if (it->first == lImage) + continue; types.push_back(make_pair(lang.tl(it->second), it->first)); } @@ -1895,6 +2035,21 @@ void MetaListPost::serialize(xmlparser &xml) const { xml.write("PackPrevious", packPrevious); xml.write("LimitWidth", limitWidth); + if (imageWidth != 0) + xml.write("ImageWidth", imageWidth); + if (imageHeight != 0) + xml.write("ImageHeight", imageHeight); + + if (imageOffsetX != 0) + xml.write("ImageOffsetX", imageOffsetX); + if (imageOffsetY != 0) + xml.write("ImageOffsetY", imageOffsetY); + + if (imageStyle != 0) + xml.write("ImageStyle", imageStyle); + + if (imageStyleUnderText) + xml.writeBool("UnderText", imageStyleUnderText); if (font != formatIgnore) xml.write("Font", getFont()); @@ -1914,7 +2069,7 @@ void MetaListPost::deserialize(const xmlobject &xml) { if (!xml) throw meosException("Ogiltigt filformat"); - wstring tp = xml.getAttrib("Type").wget(); + wstring tp = xml.getAttrib("Type").getWStr(); if (MetaList::symbolToType.count(tp) == 0) { wstring err = L"Invalid type X#" + tp; throw meosException(err); @@ -1946,6 +2101,15 @@ void MetaListPost::deserialize(const xmlobject &xml) { limitWidth = xml.getObjectBool("LimitWidth"); mergeWithPrevious = xml.getObjectInt("MergePrevious") != 0; + + imageWidth = xml.getObjectInt("ImageWidth"); + imageHeight = xml.getObjectInt("ImageHeight"); + imageOffsetX = xml.getObjectInt("ImageOffsetX"); + imageOffsetY = xml.getObjectInt("ImageOffsetY"); + + imageStyle = xml.getObjectInt("ImageStyle"); + imageStyleUnderText = xml.getObjectBool("UnderText"); + xml.getObjectString("TextAdjust", at); if (at == L"Right") @@ -1970,6 +2134,28 @@ void MetaListPost::deserialize(const xmlobject &xml) { } } +void MetaList::getUsedImages(set& imgId) const { + for (auto& v1 : data) { + for (auto& v2 : v1) { + for (auto& v3 : v2) { + if (v3.type == lImage) { + auto id = v3.getImageId(); + if (id != 0) + imgId.insert(id); + } + } + } + } +} + +uint64_t MetaListPost::getImageId() const { + if (type == lImage) { + uint64_t imgId = _wcstoui64(getText().c_str(), nullptr, 10); + return imgId; + } + throw meosException("Internal error"); +} + map MetaList::typeToSymbol; map MetaList::symbolToType; map MetaList::baseTypeToSymbol; @@ -2006,6 +2192,7 @@ void MetaList::initSymbols() { typeToSymbol[lClassNumEntries] = L"ClassNumEntries"; typeToSymbol[lCourseLength] = L"CourseLength"; typeToSymbol[lCourseName] = L"CourseName"; + typeToSymbol[lCourseNumber] = L"CourseNumber"; typeToSymbol[lCourseClimb] = L"CourseClimb"; typeToSymbol[lCourseUsage] = L"CourseUsage"; typeToSymbol[lCourseUsageNoVacant] = L"CourseUsageNoVacant"; @@ -2066,6 +2253,7 @@ void MetaList::initSymbols() { typeToSymbol[lResultModuleNumberTeam] = L"ResultModuleNumberTeam"; typeToSymbol[lRunnerBirthYear] = L"RunnerBirthYear"; + typeToSymbol[lRunnerBirthDate] = L"RunnerBirthDate"; typeToSymbol[lRunnerAge] = L"RunnerAge"; typeToSymbol[lRunnerSex] = L"RunnerSex"; typeToSymbol[lRunnerNationality] = L"RunnerNationality"; @@ -2080,6 +2268,10 @@ void MetaList::initSymbols() { typeToSymbol[lTeamName] = L"TeamName"; typeToSymbol[lTeamStart] = L"TeamStart"; + typeToSymbol[lTeamCourseName] = L"TeamCourseName"; + typeToSymbol[lTeamCourseNumber] = L"TeamCourseNumber"; + typeToSymbol[lTeamLegName] = L"TeamLegName"; + typeToSymbol[lTeamStartCond] = L"TeamStartCond"; typeToSymbol[lTeamStartZero] = L"TeamStartZero"; @@ -2160,7 +2352,13 @@ void MetaList::initSymbols() { typeToSymbol[lControlRunnersLeft] = L"ControlRunnersLeft"; typeToSymbol[lControlCodes] = L"ControlCodes"; + typeToSymbol[lNumEntries] = L"NumEntries"; + typeToSymbol[lNumStarts] = L"NumStarts"; + typeToSymbol[lTotalRunLength] = L"TotalRunLength"; + typeToSymbol[lTotalRunTime] = L"TotalRunTime"; + typeToSymbol[lLineBreak] = L"LineBreak"; + typeToSymbol[lImage] = L"Image"; for (map::iterator it = typeToSymbol.begin(); it != typeToSymbol.end(); ++it) { @@ -2175,7 +2373,8 @@ void MetaList::initSymbols() { baseTypeToSymbol[oListInfo::EBaseTypeRunner] = "Runner"; baseTypeToSymbol[oListInfo::EBaseTypeTeam] = "Team"; - baseTypeToSymbol[oListInfo::EBaseTypeClub] = "ClubRunner"; + baseTypeToSymbol[oListInfo::EBaseTypeClubRunner] = "ClubRunner"; + baseTypeToSymbol[oListInfo::EBaseTypeClubTeam] = "ClubTeam"; baseTypeToSymbol[oListInfo::EBaseTypeCoursePunches] = "CoursePunches"; baseTypeToSymbol[oListInfo::EBaseTypeAllPunches] = "AllPunches"; baseTypeToSymbol[oListInfo::EBaseTypeNone] = "None"; @@ -2199,6 +2398,7 @@ void MetaList::initSymbols() { orderToSymbol[ClassStartTime] = "ClassStartTime"; orderToSymbol[ClassStartTimeClub] = "ClassStartTimeClub"; + orderToSymbol[ClubClassStartTime] = "ClubClassStartTime"; orderToSymbol[ClassResult] = "ClassResult"; orderToSymbol[ClassDefaultResult] = "ClassDefaultResult"; orderToSymbol[ClassCourseResult] = "ClassCourseResult"; @@ -2323,6 +2523,15 @@ MetaList &MetaListContainer::getList(int index) { return data[index].second; } +const MetaList& MetaListContainer::getList(EStdListType type) const { + auto res = globalIndex.find(type); + if (res != globalIndex.end()) + return getList(res->second); + + throw meosException("List Error: X#Unknown type " + itos(type)); +} + + MetaList &MetaListContainer::addExternal(const MetaList &ml) { data.push_back(make_pair(ExternalList, ml)); if (owner) @@ -2343,7 +2552,7 @@ void MetaListContainer::save(MetaListType type, xmlparser &xml, const oEvent *oe for (size_t k = 0; k majVer) { newVersion = true; } @@ -2451,6 +2660,7 @@ bool MetaListContainer::load(MetaListType type, const xmlobject &xDef, bool igno it->second.nextList = 0; // Clear relation } } + if (!pushLevel) { xDef.getObjects("MeOSResultCalculationSet", xList); decltype(freeResultModules) copy; @@ -2467,7 +2677,7 @@ bool MetaListContainer::load(MetaListType type, const xmlobject &xDef, bool igno freeResultModules.emplace(dr->getTag(), GeneralResultCtr(dr->getTag().c_str(), dr->getName(false), dr)); } - + if (owner) owner->updateChanged(); } @@ -2661,7 +2871,7 @@ EStdListType MetaListContainer::getType(const int index) const { } void MetaListContainer::getLists(vector > &lists, bool markBuiltIn, - bool resultListOnly, bool noTeamList) const { + bool resultListOnly, bool noTeamList, bool onlyForSplitPrint) const { lists.clear(); size_t currentIx = 0; for (bool i : {false, true}) { @@ -2677,6 +2887,9 @@ void MetaListContainer::getLists(vector > &lists, bool mar if ((data[k].first == InternalList) == (i != markBuiltIn)) continue; + if (onlyForSplitPrint && !data[k].second.isSplitPrintList()) + continue; + if (data[k].first == InternalList) { if (markBuiltIn) lists.push_back(make_pair(L"[" + lang.tl(data[k].second.getListName()) + L"]", k)); @@ -2790,11 +3003,15 @@ void MetaListContainer::saveList(int index, const MetaList &ml) { if (data[index].first == InternalList) throw meosException("Invalid list type"); + string oldId = data[index].second.getUniqueId(); + data[index].first = ExternalList; data[index].second = ml; data[index].second.initUniqueIndex(); - if (owner) + if (owner) { + owner->updateListReferences(oldId, data[index].second.getUniqueId()); owner->updateChanged(); + } } void MetaList::getFilters(vector< pair > &filters) const { @@ -2894,6 +3111,7 @@ void MetaList::getSortOrder(bool forceIncludeCustom, vector< pairfirst != Custom || forceIncludeCustom || !resultModule.empty() || currentOrder == Custom) orders.push_back(make_pair(lang.tl(it->second), it->first)); } + sort(orders.begin(), orders.end()); currentOrder = sortOrder; } @@ -2906,7 +3124,7 @@ void MetaList::getBaseType(vector< pair > &types, int ¤tT continue; types.push_back(make_pair(lang.tl(it->second), it->first)); } - + sort(types.begin(), types.end()); currentType = listType; } @@ -2937,6 +3155,7 @@ void MetaList::getSubType(vector< pair > &types, int ¤tTy types.push_back(make_pair(lang.tl(baseTypeToSymbol[t]), t)); } + sort(types.begin()+1, types.end()); currentType = listSubType; } @@ -3098,7 +3317,8 @@ EPostType MetaList::getTypeFromSymbol(wstring &symb) noexcept { void MetaList::fillSymbols(vector < pair> &symb) { for (auto s : symbolToType) { - if (s.second == lAlignNext || s.second == lNone || s.second == lLineBreak) + if (s.second == lAlignNext || s.second == lNone + || s.second == lLineBreak || s.second == lImage) continue; wstring desc = L"[" + s.first + L"]\t" + lang.tl(s.first); symb.emplace_back(desc, size_t(s.second)); @@ -3224,19 +3444,44 @@ void MetaListContainer::updateGeneralResult(string tag, const shared_ptrupdateChanged(); } -void MetaList::getAutoComplete(const wstring &w, vector &records) { +void MetaList::getAutoComplete(const wstring& w, vector& records) { records.clear(); - wchar_t s_lc[1024]; - wcscpy_s(s_lc, w.c_str()); - CharLowerBuff(s_lc, w.length()); + vector ws, ws2; + split(w, L" ", ws); + + vector> s_lc(ws.size()); + + for (int j = 0; j < ws.size(); j++) { + s_lc[j].resize(ws[j].size() + 1); + ws[j] = trim(ws[j]); + wcscpy_s(s_lc[j].data(), s_lc[j].size(), ws[j].c_str()); + CharLowerBuff(s_lc[j].data(), ws[j].length()); + } + wstring tl; - for (auto &ts : typeToSymbol) { - if (ts.first == lNone || ts.first == lAlignNext) + for (auto& ts : typeToSymbol) { + if (ts.first == lNone || ts.first == lAlignNext || ts.first == lImage) continue; - + tl = lang.tl(ts.second); - if (filterMatchString(ts.second, s_lc) || filterMatchString(tl, s_lc)) { - records.emplace_back(tl, ts.second, ts.first); + int score = 0; + int fScore; + for (int j = 0; j < ws.size(); j++) { + if (filterMatchString(ts.second, s_lc[j].data(), fScore)) + score += 1 + fScore * (ws.size() == 1) ? 2 : 0; + + if (filterMatchString(tl, s_lc[j].data(), fScore)) { + score++; + split(tl, L" ", ws2); + for (int i = 0; i < ws2.size(); i++) { + if (filterMatchString(ws2[i], s_lc[j].data(), fScore)) { + int diff = std::abs(int(ws2[i].length() - ws[j].length())); + score += max(5 - diff, 0) + fScore; + } + } + } } + if (score > 0) + records.emplace_back(tl, score, ts.second, ts.first); } } diff --git a/code/metalist.h b/code/metalist.h index 2618b6b..5e95b21 100644 --- a/code/metalist.h +++ b/code/metalist.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -94,6 +94,15 @@ private: gdiFonts font; int textAdjust; // 0, textRight, textCenter GDICOLOR color; + + // Image + int imageWidth = 0; + int imageHeight = 0; + int imageStyle = 0; + int imageOffsetX = 0; + int imageOffsetY = 0; + bool imageStyleUnderText = false; + public: MetaListPost(EPostType type_, EPostType align_ = lNone, int leg_ = -1); @@ -109,11 +118,47 @@ public: MetaListPost &limitBlockWidth(bool lim = true) { limitWidth = lim; return *this; } MetaListPost &packWithPrevious(bool pack = true) { packPrevious = pack; return *this; } - MetaListPost &indent(int ind) {minimalIndent = ind; return *this;} void getTypes(vector< pair > &types, int ¤tType) const; + uint64_t getImageId() const; + void setImageDimension(int x, int y) { + imageWidth = x; + imageHeight = y; + } + int getImageWidth() const { + return imageWidth; + } + int getImageHeight() const { + return imageHeight; + } + void setImageOffset(int x, int y) { + imageOffsetX = x; + imageOffsetY = y; + } + int getImageOffsetX() const { + return imageOffsetX; + } + int getImageOffsetY() const { + return imageOffsetY; + } + int getImageStyle() const { + return imageStyle; + } + + void setImageStyle(int style) { + imageStyle = style; + } + + bool imageUnderText() const { + return imageStyleUnderText; + } + + void imageUnderText(bool ut) { + imageStyleUnderText = ut; + } + const wstring &getType() const; MetaListPost &setType(EPostType type_) {type = type_; return *this;} EPostType getTypeRaw() const { return type; } @@ -236,8 +281,20 @@ private: static bool isBreak(int x); + shared_ptr splitPrintInfo; + public: + bool isSplitPrintList() const { return splitPrintInfo != nullptr; } + + const shared_ptr& getSplitPrintInfo() const { + return splitPrintInfo; + } + + void setSplitPrintInfo(const shared_ptr& info) { + splitPrintInfo = info; + } + static wstring encode(EPostType type, const wstring &input, bool &foundSymbol); static const wstring &fromResultModuleNumber(const wstring &in, int nr, wstring &out); @@ -246,12 +303,21 @@ public: MetaList(); virtual ~MetaList() {} + void getUsedImages(set& imgId) const; + static constexpr bool isAllStageType(EPostType type) { return type == lRunnerStagePlace || type == lRunnerStageStatus || type == lRunnerStageTime || type == lRunnerStageTimeStatus || type == lRunnerStagePoints || type == lRunnerStageNumber; } + static constexpr bool isAllLegType(EPostType type) { + return type == lTeamCourseName || type == lTeamCourseNumber || + type == lTeamLegName || type == lTeamRunner || type == lTeamRunnerCard || + type == lTeamLegTimeStatus || type == lTeamLegTimeAfter || + type == lTeamPlace || type ==lTeamStart; + } + static constexpr bool isResultModuleOutput(EPostType type) { return type == lResultModuleNumber || type == lResultModuleTime || type == lResultModuleTimeTeam || type == lResultModuleNumberTeam; @@ -329,7 +395,7 @@ public: bool isValidIx(size_t gIx, size_t lIx, size_t ix) const; - void save(xmlparser &xml, const oEvent *oe) const; + void save(xmlparser &xml, bool includeImages, const oEvent *oe) const; void load(const xmlobject &xDef); void interpret(oEvent *oe, const gdioutput &gdi, const oListParam &par, oListInfo &li) const; @@ -371,6 +437,8 @@ public: friend class MetaListPost; }; +class Image; + class MetaListContainer { public: enum MetaListType {InternalList, ExternalList, RemovedList}; @@ -391,7 +459,6 @@ public: virtual ~MetaListContainer(); - void getFreeResultModules(vector>> &res) const; string getUniqueId(EStdListType code) const; @@ -400,6 +467,8 @@ public: bool updateResultModule(const DynamicResult &res, bool updateSimilar); + void getUsedImages(set& imgId) const; + int getNumParam() const {return listParam.size();} int getNumLists() const {return data.size();} int getNumLists(MetaListType) const; @@ -409,6 +478,7 @@ public: const MetaList &getList(int index) const; MetaList &getList(int index); + const MetaList& getList(EStdListType type) const; const oListParam &getParam(int index) const; oListParam &getParam(int index); @@ -421,7 +491,8 @@ public: void getLists(vector< pair > &lists, bool markBuiltIn, bool resultListOnly, - bool noTeamList) const; + bool noTeamList, + bool onlyForSplitPrint) const; const string &getTag(int index) const; diff --git a/code/methodeditor.cpp b/code/methodeditor.cpp index dd92eae..0042b16 100644 --- a/code/methodeditor.cpp +++ b/code/methodeditor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -563,14 +563,14 @@ int MethodEditor::methodCb(gdioutput &gdi, int type, BaseInfo &data) { w[i] = gdi.scaleLength(w[i]); set errors; - currentResult->prepareCalculations(*oe, false, {}, rr, tr, inputNumber); + currentResult->prepareCalculations(*oe, true, {}, rr, tr, inputNumber); for (size_t k = 0; k < rr.size(); k++) { int txp = xp; int wi = 0; gdi.addStringUT(yp, txp, 0, rr[k]->getCompleteIdentification(), w[wi]-diff); txp += w[wi++]; - currentResult->prepareCalculations(*rr[k], false); + currentResult->prepareCalculations(*rr[k], true); int rt = 0, pt = 0; RunnerStatus st = StatusUnknown; @@ -674,7 +674,7 @@ int MethodEditor::methodCb(gdioutput &gdi, int type, BaseInfo &data) { int wi = 0; gdi.addStringUT(yp, txp, 0, tr[k]->getName(), w[wi]-diff); txp += w[wi++]; - currentResult->prepareCalculations(*tr[k], false); + currentResult->prepareCalculations(*tr[k], true); int rt = 0, pt = 0; RunnerStatus st = StatusUnknown; { @@ -920,10 +920,10 @@ void MethodEditor::makeDirty(gdioutput &gdi, DirtyFlag inside) { bool MethodEditor::checkSave(gdioutput &gdi) { if (dirtyInt) { gdioutput::AskAnswer answer = gdi.askCancel(L"Vill du spara ändringar?"); - if (answer == gdioutput::AnswerCancel) + if (answer == gdioutput::AskAnswer::AnswerCancel) return false; - if (answer == gdioutput::AnswerYes) { + if (answer == gdioutput::AskAnswer::AnswerYes) { gdi.sendCtrlMessage("SaveInside"); } makeDirty(gdi, ClearDirty); @@ -1004,11 +1004,11 @@ void MethodEditor::debug(gdioutput &gdi_in, int id, bool isTeam) { if (isTeam) oe->getTeams(art->getClassId(true), tt, false); - currentResult->prepareCalculations(*oe, false, {art->getClassId(true)}, rr, tt, inputNumber); + currentResult->prepareCalculations(*oe, true, {art->getClassId(true)}, rr, tt, inputNumber); gdi_new->addString("", fontMediumPlus, "Debug Output"); if (!isTeam) { oRunner &r = *pRunner(art); - currentResult->prepareCalculations(r, false); + currentResult->prepareCalculations(r, true); int rt = 0, pt = 0; RunnerStatus st = StatusUnknown; gdi.dropLine(); @@ -1065,7 +1065,7 @@ void MethodEditor::debug(gdioutput &gdi_in, int id, bool isTeam) { } else { oTeam &t = *pTeam(art); - currentResult->prepareCalculations(t, false); + currentResult->prepareCalculations(t, true); int rt = 0, pt = 0; RunnerStatus st = StatusUnknown; gdi.dropLine(); diff --git a/code/methodeditor.h b/code/methodeditor.h index 65b3b2e..cae33ca 100644 --- a/code/methodeditor.h +++ b/code/methodeditor.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/mysqldaemon.cpp b/code/mysqldaemon.cpp index 661cf5e..ddbe23e 100644 --- a/code/mysqldaemon.cpp +++ b/code/mysqldaemon.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -29,7 +29,7 @@ #include "MeosSQL.h" #include -MySQLReconnect::MySQLReconnect(const wstring &errorIn) : AutoMachine("MySQL-daemon", Machines::mMySQLReconnect), error(errorIn) { +MySQLReconnect::MySQLReconnect(const wstring &errorIn) : AutoMachine("MySQL-service", Machines::mMySQLReconnect), error(errorIn) { timeError = getLocalTime(); hThread=0; } @@ -43,7 +43,7 @@ bool MySQLReconnect::stop() { if (interval==0) return true; - return MessageBox(0, L"If this daemon is stopped, then MeOS will not reconnect to the network. Continue?", + return MessageBox(0, L"If this service is stopped, MeOS will not reconnect to the network. Continue?", L"Warning", MB_YESNO|MB_ICONWARNING)==IDYES; } diff --git a/code/mysqlwrapper.cpp b/code/mysqlwrapper.cpp index 72e7bd0..5df8298 100644 --- a/code/mysqlwrapper.cpp +++ b/code/mysqlwrapper.cpp @@ -38,13 +38,27 @@ CellWrapper::operator bool() const { return int(*this) != 0; } -int64_t CellWrapper::ulonglong() const { +int64_t CellWrapper::longlong() const { char *out; if (data != nullptr) return strtoll(data, &out, 10); return 0; } +uint64_t CellWrapper::ulonglong() const { + char* out; + if (data != nullptr) + return strtoull(data, &out, 10); + return 0; +} + +void CellWrapper::storeBlob(std::vector& d) const { + if (data != nullptr) { + d.resize(length); + memcpy(d.data(), data, length); + } +} + bool CellWrapper::is_null() const { return data == nullptr; } @@ -347,7 +361,9 @@ void ConnectionWrapper::create_db(const string &db) { } void ConnectionWrapper::drop_db(const string &db) { - throw Exception("Not implemented"); + string sql = "DROP DATABASE " + db + ""; + if (mysql_query(get(), sql.c_str()) != 0) + throw Exception(mysql_error(mysql)); } MYSQL *ConnectionWrapper::get() const { @@ -357,7 +373,10 @@ MYSQL *ConnectionWrapper::get() const { } string ConnectionWrapper::server_info() const { - return mysql_get_server_info(get()); + const char *ptr = mysql_get_server_info(get()); + if (ptr) + return ptr; + return ""; } bool ConnectionWrapper::connected() const { diff --git a/code/mysqlwrapper.h b/code/mysqlwrapper.h index 6ae10db..0523172 100644 --- a/code/mysqlwrapper.h +++ b/code/mysqlwrapper.h @@ -2,6 +2,7 @@ #include #include +#include #include "mysql/mysql.h" using std::string; @@ -26,7 +27,9 @@ namespace sqlwrapper { operator int() const; operator unsigned int() const; operator bool() const; - int64_t ulonglong() const; + int64_t longlong() const; + uint64_t ulonglong() const; + void storeBlob(std::vector& d) const; bool is_null() const; }; diff --git a/code/newcompetition.cpp b/code/newcompetition.cpp index 0b25f46..4bad5c4 100644 --- a/code/newcompetition.cpp +++ b/code/newcompetition.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -41,6 +41,7 @@ #include #include #include +#include "csvparser.h" int CompetitionCB(gdioutput *gdi, int type, void *data); @@ -68,7 +69,9 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) gdi.refresh(); } else if (bi.id == "DoImportEntries") { + auto baseOnId = gdi.getSelectedItem("CmpSel"); createCompetition(gdi); + try { gdi.autoRefresh(true); FlowOperation res = saveEntries(gdi, false, true); @@ -87,11 +90,18 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) gdi.pushX(); gdi.setRestorePoint("entrychoice"); - newCompetitionGuide(gdi, 2); + if (!baseOnId.second || baseOnId.first <= 0) + newCompetitionGuide(gdi, 2); + else + createNewCmp(gdi, true); } else if (bi.id == "NoEntries") { gdi.restore("entrychoice"); - newCompetitionGuide(gdi, 2); + auto baseOnId = gdi.getSelectedItem("CmpSel"); + if (baseOnId.second && baseOnId.first <= 0) + newCompetitionGuide(gdi, 2); + else + createNewCmp(gdi, false); } else if (bi.id == "Cancel") { oe->clear(); @@ -101,14 +111,9 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) else if (bi.id == "FAll") { if (gdi.hasWidget("Name")) createCompetition(gdi); - gdi.clearPage(true); - gdi.fillRight(); - gdi.addString("", fontMediumPlus, "Skapar tävling..."); - gdi.refresh(); - Sleep(400); + oe->getMeOSFeatures().useAll(*oe); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } else if (bi.id == "FBasic") { if (gdi.hasWidget("Name")) @@ -117,8 +122,7 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) oe->getMeOSFeatures().useFeature(MeOSFeatures::Clubs, true, *oe); oe->getMeOSFeatures().useFeature(MeOSFeatures::RunnerDb, true, *oe); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } else if (bi.id == "FSelect") { newCompetitionGuide(gdi, 3); @@ -127,13 +131,7 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) if (gdi.hasWidget("Name")) createCompetition(gdi); saveMeosFeatures(gdi, true); - gdi.clearPage(true); - gdi.fillRight(); - gdi.addString("", fontMediumPlus, "Skapar tävling..."); - gdi.refresh(); - Sleep(400); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } else if (bi.id == "FIndividual") { if (gdi.hasWidget("Name")) @@ -148,13 +146,7 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) oe->getMeOSFeatures().useFeature(MeOSFeatures::Bib, true, *oe); oe->getMeOSFeatures().useFeature(MeOSFeatures::RunnerDb, true, *oe); - gdi.clearPage(true); - gdi.fillRight(); - gdi.addString("", fontMediumPlus, "Skapar tävling..."); - gdi.refresh(); - Sleep(400); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } else if (bi.id == "FNoCourses" || bi.id == "FNoCoursesRelay") { if (gdi.hasWidget("Name")) @@ -172,13 +164,7 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) if (bi.id == "FNoCoursesRelay") oe->getMeOSFeatures().useFeature(MeOSFeatures::Relay, true, *oe); - gdi.clearPage(true); - gdi.fillRight(); - gdi.addString("", fontMediumPlus, "Skapar tävling..."); - gdi.refresh(); - Sleep(400); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } else if (bi.id == "FForked") { if (gdi.hasWidget("Name")) @@ -194,13 +180,7 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) oe->getMeOSFeatures().useFeature(MeOSFeatures::RunnerDb, true, *oe); oe->getMeOSFeatures().useFeature(MeOSFeatures::ForkedIndividual, true, *oe); - gdi.clearPage(true); - gdi.fillRight(); - gdi.addString("", fontMediumPlus, "Skapar tävling..."); - gdi.refresh(); - Sleep(400); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } else if (bi.id == "FTeam") { if (gdi.hasWidget("Name")) @@ -219,13 +199,7 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) if (oe->hasMultiRunner()) oe->getMeOSFeatures().useFeature(MeOSFeatures::MultipleRaces, true, *oe); - gdi.clearPage(true); - gdi.fillRight(); - gdi.addString("", fontMediumPlus, "Skapar tävling..."); - gdi.refresh(); - Sleep(400); - oe->updateTabs(true, false); - loadPage(gdi); + createNewCmp(gdi, true); } } else if (type == GUI_INPUT) { @@ -248,11 +222,11 @@ int TabCompetition::newGuideCB(gdioutput &gdi, int type, void *data) gdi.setTextTranslate("AllowedInterval", L"Felaktigt datum/tid", true); } else { - long long absT = SystemTimeToInt64Second(st); - absT += max(0, t - 3600); - long long stopT = absT + 23 * 3600; - SYSTEMTIME start = Int64SecondToSystemTime(absT); - SYSTEMTIME end = Int64SecondToSystemTime(stopT); + long long absT = SystemTimeToInt64TenthSecond(st); + absT += max(0, t - timeConstHour); + long long stopT = absT + 23 * timeConstHour; + SYSTEMTIME start = Int64TenthSecondToSystemTime(absT); + SYSTEMTIME end = Int64TenthSecondToSystemTime(stopT); wstring s = L"Tävlingen mÃ¥ste avgöras mellan X och Y.#" + convertSystemTime(start) + L"#" + convertSystemTime(end); gdi.setTextTranslate("AllowedInterval", s, true); } @@ -295,6 +269,13 @@ void TabCompetition::newCompetitionGuide(gdioutput &gdi, int step) { gdi.addString("AllowedInterval", 0, ""); newGuideCB(gdi, GUI_INPUT, &date); gdi.dropLine(2); + + gdi.addSelection("CmpSel", 300, 400, CompetitionCB, L"Basera pÃ¥ en tidigare tävling:"); + gdi.addItem("CmpSel", lang.tl("Ingen[competition]"), -1); + oe->fillCompetitions(gdi, "CmpSel", 0, L"", false); + gdi.autoGrow("CmpSel"); + gdi.selectFirstItem("CmpSel"); + rc.right = rc.left + gdi.scaleLength(width); rc.bottom = gdi.getCY(); gdi.addRectangle(rc, colorLightBlue, true); @@ -412,10 +393,24 @@ void TabCompetition::newCompetitionGuide(gdioutput &gdi, int step) { gdi.fillDown(); gdi.refresh(); - } } +void TabCompetition::createNewCmp(gdioutput& gdi, bool useExisting) { + if (!useExisting) + createCompetition(gdi); + + gdi.clearPage(true); + gdi.fillRight(); + gdi.addString("", fontMediumPlus, "Skapar tävling..."); + gdi.refresh(); + gdi.setWaitCursor(true); + Sleep(400); + oe->updateTabs(true, false); + gdi.setWaitCursor(false); + loadPage(gdi); +} + void TabCompetition::entryChoice(gdioutput &gdi) { gdi.fillRight(); gdi.pushX(); @@ -429,21 +424,84 @@ void TabCompetition::entryChoice(gdioutput &gdi) { gdi.dropLine(2); } +wstring getHiredCardDefault() { + wchar_t path[_MAX_PATH]; + getUserFile(path, L"hired_card_default.csv"); + return path; +} + +void resetSaveTimer(); + void TabCompetition::createCompetition(gdioutput &gdi) { wstring name = gdi.getText("Name"); wstring date = gdi.getText("Date"); wstring start = gdi.getText("FirstStart"); - oe->newCompetition(L"tmp"); - oe->setName(name, true); - oe->setDate(date, true); - int t = convertAbsoluteTimeHMS(start, -1); - if (t > 0 && t < 3600*24) { - t = max(0, t-3600); - oe->setZeroTime(formatTimeHMS(t), true); + if (t > 0 && t < timeConstHour * 24) { + t = max(0, t - timeConstHour); } else throw meosException("Ogiltig tid"); + + int baseOnId = gdi.getSelectedItem("CmpSel").first; + + if (baseOnId <= 0) { + oe->newCompetition(L"tmp"); + oe->loadDefaults(); + } + else { + openCompetition(gdi, baseOnId); + wstring cmpCopy = getTempFile(); + oe->save(cmpCopy, false); + wstring rawName = oe->getName(); + size_t posPar = rawName.find_first_of('('); + if (posPar != string::npos) + rawName = rawName.substr(0, posPar); + + if (rawName.length() > 40) + rawName = trim(rawName.substr(0, 38)) + L"..."; + + wstring oldName = trim(rawName) + L" (" + oe->getDate() + L")"; + oe->open(cmpCopy, true, false, true); + removeTempFile(cmpCopy); + + oe->clearData(true, true); + oe->setAnnotation(lang.tl(L"Baserad pÃ¥ X#" + oldName)); + resetSaveTimer(); + } + + oe->setName(name, true); + oe->setDate(date, true); + oe->setZeroTime(formatTimeHMS(t), true); + + bool importHiredCard = true; + if (importHiredCard) + importDefaultHiredCards(gdi); } +void TabCompetition::importDefaultHiredCards(gdioutput& gdi) { + csvparser csv; + list> data; + wstring fn = getHiredCardDefault(); + if (fileExists(fn)) { + try { + 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); + } + } + } + } + catch (const meosException& ex) { + gdi.addString("", 0, ex.wwhat()).setColor(colorRed); + } + catch (std::exception& ex) { + gdi.addString("", 0, ex.what()).setColor(colorRed); + } + } +} diff --git a/code/oBase.cpp b/code/oBase.cpp index 35daa78..9a529b2 100644 --- a/code/oBase.cpp +++ b/code/oBase.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -81,7 +81,7 @@ oBase::oBase(oBase &&in) { localObject = in.localObject; implicitlyAdded = in.implicitlyAdded; addedToEvent = in.addedToEvent; - sqlUpdated = in.sqlUpdated; + sqlUpdated = std::move(in.sqlUpdated); localObject = in.localObject; transientChanged = in.transientChanged; if (in.myReference) { @@ -239,6 +239,7 @@ void oBase::changeId(int newId) { void oBase::addToEvent(oEvent *e, const oBase *src) { oe = e; addedToEvent = true; + localObject = false; oe->updateFreeId(this); if (src) Modified = src->Modified; diff --git a/code/oBase.h b/code/oBase.h index 2bd7bd9..03cc209 100644 --- a/code/oBase.h +++ b/code/oBase.h @@ -5,7 +5,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -154,7 +154,8 @@ public: wstring getTimeStamp() const; string getTimeStampN() const; const string &getStamp() const; - + const TimeStamp& getModified() const { return Modified; } + bool existInDB() const { return !sqlUpdated.empty(); } void setImplicitlyCreated() { implicitlyAdded = true; } diff --git a/code/oCard.cpp b/code/oCard.cpp index 6911f3d..4f74900 100644 --- a/code/oCard.cpp +++ b/code/oCard.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -70,6 +70,7 @@ bool oCard::Write(xmlparser &xml) xml.write("Punches", getPunchString()); xml.write("ReadId", int(readId)); xml.write("Voltage", miliVolt); + xml.write("BatteryDate", batteryDate); xml.write("Id", Id); xml.write("Updated", getStamp()); xml.endTag(); @@ -87,20 +88,23 @@ void oCard::Set(const xmlobject &xo) if (it->is("CardNo")){ cardNo = it->getInt(); } - if (it->is("Voltage")) { + else if (it->is("Voltage")) { miliVolt = it->getInt(); } + else if (it->is("BatteryDate")) { + batteryDate = it->getInt(); + } else if (it->is("Punches")){ - importPunches(it->getRaw()); + importPunches(it->getRawStr()); } else if (it->is("ReadId")){ - readId = it->getInt(); // COded as signed int + readId = it->getInt(); // Coded as signed int } else if (it->is("Id")){ Id = it->getInt(); } else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); } } } @@ -110,8 +114,8 @@ pair oCard::getCardHash() const { int b = readId; for (auto &p : punches) { - a = a * 31 + p.getTimeInt() * 997 + p.getTypeCode(); - b = b * 41 + p.getTimeInt() * 97 + p.getTypeCode(); + a = a * 31 + p.punchTime * 997 + p.getTypeCode(); + b = b * 41 + p.punchTime * 97 + p.getTypeCode(); } return make_pair(a, b); } @@ -128,13 +132,13 @@ const wstring &oCard::getCardNoString() const { return itow(cardNo); } -void oCard::addPunch(int type, int time, int matchControlId) -{ +void oCard::addPunch(int type, int time, int matchControlId, int unit) { oPunch p(oe); - p.Time = time; - p.Type = type; + p.punchTime = time; + p.type = type; p.tMatchControlId = matchControlId; p.isUsed = matchControlId!=0; + p.punchUnit = unit; if (punches.empty()) punches.push_back(p); @@ -161,18 +165,18 @@ const string &oCard::getPunchString() const { } void oCard::importPunches(const string &s) { - int startpos=0; + int startpos = 0; int endpos; - endpos=s.find_first_of(';', startpos); + endpos = s.find_first_of(';', startpos); punches.clear(); - while(endpos!=string::npos) { + while (endpos != string::npos) { oPunch p(oe); - p.decodeString(s.substr(startpos, endpos)); + p.decodeString(s.c_str() + startpos); punches.push_back(p); - startpos=endpos+1; - endpos=s.find_first_of(';', startpos); + startpos = endpos + 1; + endpos = s.find_first_of(';', startpos); } return; } @@ -194,7 +198,7 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, oCourse *crs) { bool hasFinish=false; bool extra=false; int k=0; - + int currentTimeAdjust = 0; pControl ctrl=0; int matchPunch=0; @@ -283,7 +287,16 @@ bool oCard::fillPunches(gdioutput &gdi, const string &name, oCourse *crs) { } } - gdi.addItem(name, it->getString(), it->tCardIndex); + wstring tadj; + if (currentTimeAdjust != it->tTimeAdjust) { + int adj = it->tTimeAdjust - currentTimeAdjust; + currentTimeAdjust = it->tTimeAdjust; + if (adj > 0) + tadj = L" +" + formatTime(adj); + else + tadj = L" -" + formatTime(-adj); + } + gdi.addItem(name, it->getString() + tadj, it->tCardIndex); if (!(it->isFinish() || it->isStart())) { punchRemain--; @@ -363,8 +376,8 @@ void oCard::insertPunchAfter(int pos, int type, int time) oPunchList::iterator it; oPunch punch(oe); - punch.Time=time; - punch.Type=type; + punch.punchTime=time; + punch.type=type; int k=-1; for (it=punches.begin(); it != punches.end(); ++it) { @@ -423,7 +436,7 @@ oPunch *oCard::getPunchByType(int Type) const oPunchList::const_iterator it; for (it=punches.begin(); it != punches.end(); ++it) - if (it->Type==Type) + if (it->type==Type) return pPunch(&*it); return 0; @@ -476,8 +489,8 @@ void oCard::getSICard(SICard &card) const { card.convertedTime = ConvertedTimeStatus::Done; oPunchList::const_iterator it; for (it = punches.begin(); it != punches.end(); ++it) { - if (it->Type>30) - card.Punch[card.nPunch++].Code = it->Type; + if (it->type>30) + card.Punch[card.nPunch++].Code = it->type; } } @@ -486,15 +499,15 @@ pRunner oCard::getOwner() const { return tOwner && !tOwner->isRemoved() ? tOwner : 0; } -bool oCard::setPunchTime(const pPunch punch, const wstring &time) +bool oCard::setPunchTime(const pPunch punch, const wstring& time) { - oPunch *op=getPunch(punch); + oPunch* op = getPunch(punch); if (!op) return false; - DWORD ot=op->Time; + DWORD ot = op->punchTime; op->setTime(time); - if (ot!=op->Time) + if (ot != op->punchTime) updateChanged(); return true; @@ -518,14 +531,20 @@ pCard oEvent::getCard(int Id) const return 0; } -void oEvent::getCards(vector &c) { - synchronizeList(oListId::oLCardId); - c.clear(); - c.reserve(Cards.size()); +void oEvent::getCards(vector& cards, bool synchronize, bool onlyUnpaired) { + if (synchronize) + synchronizeList(oListId::oLCardId); - for (oCardList::iterator it = Cards.begin(); it != Cards.end(); ++it) { - if (!it->isRemoved()) - c.push_back(&*it); + cards.clear(); + cards.reserve(Cards.size()); + + for (auto &c : Cards) { + if (!c.isRemoved()) { + if (onlyUnpaired && c.getOwner() != nullptr) + continue; + + cards.push_back(&c); + } } } @@ -581,6 +600,7 @@ const shared_ptr
&oCard::getTable(oEvent *oe) { table->addColumn("Bricka", 120, true); table->addColumn("Deltagare", 200, false); table->addColumn("Spänning", 70, false); + table->addColumn("Batteridatum", 100, false); table->addColumn("Starttid", 70, false); table->addColumn("Måltid", 70, false); @@ -629,17 +649,20 @@ void oCard::addTableRow(Table &table) const { table.set(row++, it, TID_VOLTAGE, getCardVoltage(), false, cellAction); + table.set(row++, it, TID_BATTERYDATE, getBatteryDate(), false, cellAction); + + oPunch *p=getPunchByType(oPunch::PunchStart); wstring time; if (p) - time = p->getTime(); + time = p->getTime(false, SubSecond::Auto); else time = makeDash(L"-"); table.set(row++, it, TID_START, time, false, cellEdit); p = getPunchByType(oPunch::PunchFinish); if (p) - time = p->getTime(); + time = p->getTime(false, SubSecond::Auto); else time = makeDash(L"-"); @@ -699,11 +722,12 @@ bool oCard::canRemove() const } pair oCard::getTimeRange() const { - pair t(24*3600, 0); + pair t(24*timeConstHour, 0); for(oPunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) { - if (it->Time > 0) { - t.first = min(t.first, it->Time); - t.second = max(t.second, it->Time); + if (it->hasTime()) { + int pt = it->getTimeInt(); + t.first = min(t.first, pt); + t.second = max(t.second, pt); } } return t; @@ -723,7 +747,7 @@ void oCard::setupFromRadioPunches(oRunner &r) { oe->getPunchesForRunner(r.getId(), true, p); for (size_t k = 0; k < p.size(); k++) - addPunch(p[k]->Type, p[k]->Time, 0); + addPunch(p[k]->type, p[k]->punchTime, 0, p[k]->punchUnit); cardNo = r.getCardNo(); readId = ConstructedFromPunches; //Indicates @@ -751,8 +775,8 @@ void oCard::adaptTimes(int startTime) { int st = -1; oPunchList::iterator it = punches.begin(); while (it != punches.end()) { - if (it->Time > 0) { - st = it->Time; + if (it->hasTime()) { + st = it->getTimeInt(); break; } ++it; @@ -761,18 +785,17 @@ void oCard::adaptTimes(int startTime) { if (st == -1) return; - const int h24 = 24 * 3600; + const int h24 = 24 * timeConstHour; int offset = st / h24; if (offset > 0) { for (it = punches.begin(); it != punches.end(); ++it) { - if (it->Time > 0 && it->Time < offset * h24) + if (it->hasTime() && it->getTimeInt() < offset * h24) return; // Inconsistent, do nothing } - - + for (it = punches.begin(); it != punches.end(); ++it) { - if (it->Time > 0) - it->Time -= offset * h24; + if (it->hasTime()) + it->punchTime -= offset * h24; } updateChanged(); } @@ -780,8 +803,8 @@ void oCard::adaptTimes(int startTime) { if (startTime >= h24) { offset = startTime / h24; for (it = punches.begin(); it != punches.end(); ++it) { - if (it->Time > 0) - it->Time += offset * h24; + if (it->hasTime()) + it->punchTime += offset * h24; } updateChanged(); } @@ -815,3 +838,8 @@ oCard::BatteryStatus oCard::isCriticalCardVoltage(int miliVolt) { return BatteryStatus::OK; } +wstring oCard::getBatteryDate() const { + if (batteryDate > 0) + return formatDate(batteryDate, false); + return _EmptyWString; +} diff --git a/code/oCard.h b/code/oCard.h index f0df2b0..988adab 100644 --- a/code/oCard.h +++ b/code/oCard.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -54,7 +54,7 @@ protected: oPunchList punches; int cardNo; int miliVolt = 0; // Measured voltage of SIAC, if not zero. - + int batteryDate = 0; // Battery replace date (for SIAC) unsigned int readId; //Identify a specific read-out const static int ConstructedFromPunches = 1; @@ -85,6 +85,8 @@ public: BatteryStatus isCriticalCardVoltage() const; static BatteryStatus isCriticalCardVoltage(int miliVolt); + wstring getBatteryDate() const; + static const shared_ptr
&getTable(oEvent *oe); // Returns true if the card was constructed from punches. @@ -123,7 +125,7 @@ public: bool fillPunches(gdioutput &gdi, const string &name, oCourse *crs); - void addPunch(int type, int time, int matchControlId); + void addPunch(int type, int time, int matchControlId, int unit); oPunch *getPunchByType(int type) const; //Get punch by (matched) control punch id. diff --git a/code/oClass.cpp b/code/oClass.cpp index f3e9b6e..2f609ce 100644 --- a/code/oClass.cpp +++ b/code/oClass.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -145,7 +145,7 @@ void oClass::Set(const xmlobject *xo) Id = it->getInt(); } else if (it->is("Name")){ - Name = it->getw(); + Name = it->getWStr(); if (Name.size() > 1 && Name.at(0) == '%') { Name = lang.tl(Name.substr(1)); } @@ -156,17 +156,17 @@ void oClass::Set(const xmlobject *xo) else if (it->is("MultiCourse")){ set cid; vector< vector > multi; - parseCourses(it->getRaw(), multi, cid); + parseCourses(it->getRawStr(), multi, cid); importCourses(multi); } else if (it->is("LegMethod")){ - importLegMethod(it->getRaw()); + importLegMethod(it->getRawStr()); } else if (it->is("oData")){ getDI().set(*it); } else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); } } @@ -267,62 +267,88 @@ void oClass::parseCourses(const string &courses, } } -string oLegInfo::codeLegMethod() const -{ +string oLegInfo::codeLegMethod() const { + char bsd[16], bret[16], brot[16]; + + auto codeTime = [](int t, char *b) -> const char * { + if (timeConstSecond == 1 || t <= 0) + sprintf_s(b, 16, "%d", t); + else + sprintf_s(b, 16, "%d.%d", (t / timeConstSecond),(t % timeConstSecond)); + + return b; + }; + char bf[256]; - sprintf_s(bf, "(%s:%s:%d:%d:%d:%d)", StartTypeNames[startMethod], - LegTypeNames[legMethod], - legStartData, legRestartTime, - legRopeTime, duplicateRunner); + + if (isStartDataTime()) { + sprintf_s(bf, "(%s:%s:%s:%s:%s:%d)", StartTypeNames[startMethod], + LegTypeNames[legMethod], + codeTime(legStartData, bsd), + codeTime(legRestartTime, bret), + codeTime(legRopeTime, brot), + duplicateRunner); + } + else { + sprintf_s(bf, "(%s:%s:%d:%s:%s:%d)", StartTypeNames[startMethod], + LegTypeNames[legMethod], + legStartData, + codeTime(legRestartTime, bret), + codeTime(legRopeTime, brot), + duplicateRunner); + } return bf; } -void oLegInfo::importLegMethod(const string &leg) -{ +void oLegInfo::importLegMethod(const string &leg) { //Defaults - startMethod=STTime; - legMethod=LTNormal; + startMethod = STTime; + legMethod = LTNormal; legStartData = 0; legRestartTime = 0; - size_t begin=leg.find_first_of('('); + size_t begin = leg.find_first_of('('); - if (begin==leg.npos) + if (begin == string::npos) return; begin++; - string coreLeg=leg.substr(begin, leg.find_first_of(')')-begin); + string coreLeg = leg.substr(begin, leg.find_first_of(')') - begin); - vector< string > legsplit; + vector legsplit; split(coreLeg, ":", legsplit); - if (legsplit.size()>=1) { - for( int st = 0 ; st < nStartTypes ; ++st ) { - if ( legsplit[0]==StartTypeNames[st] ) { - startMethod=(StartTypes)st; + if (legsplit.size() >= 1) { + for (int st = 0; st < nStartTypes; ++st) { + if (legsplit[0] == StartTypeNames[st]) { + startMethod = (StartTypes)st; break; } } } - if (legsplit.size()>=2) { - for( int t = 0 ; t < nLegTypes ; ++t ) { - if ( legsplit[1]==LegTypeNames[t] ) { - legMethod=(LegTypes)t; + if (legsplit.size() >= 2) { + for (int t = 0; t < nLegTypes; ++t) { + if (legsplit[1] == LegTypeNames[t]) { + legMethod = (LegTypes)t; break; } } } - if (legsplit.size()>=3) - legStartData = atoi(legsplit[2].c_str()); + if (legsplit.size() >= 3) { + if (isStartDataTime()) + legStartData = parseRelativeTime(legsplit[2].c_str()); + else + legStartData = atoi(legsplit[2].c_str()); + } - if (legsplit.size()>=4) - legRestartTime = atoi(legsplit[3].c_str()); + if (legsplit.size() >= 4) + legRestartTime = parseRelativeTime(legsplit[3].c_str()); - if (legsplit.size()>=5) - legRopeTime = atoi(legsplit[4].c_str()); + if (legsplit.size() >= 5) + legRopeTime = parseRelativeTime(legsplit[4].c_str()); - if (legsplit.size()>=6) + if (legsplit.size() >= 6) duplicateRunner = atoi(legsplit[5].c_str()); } @@ -549,6 +575,15 @@ oDataContainer &oClass::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr & return *oe->oClassData; } +int oEvent::getNumClasses() const { + int nc = 0; + for (auto& c : Classes) { + if (!c.isRemoved()) + nc++; + } + return nc; +} + pClass oEvent::getClassCreate(int Id, const wstring &createName, set &exactMatch) { if (Id>0) { oClassList::iterator it; @@ -595,11 +630,7 @@ pClass oEvent::getClassCreate(int Id, const wstring &createName, set &e oClass c(this, Id); c.Name = createName; exactMatch.insert(createName); - //No! Create class with this Id - pClass pc=addClass(c); - - //Not found. Auto add... - return pc; + return addClass(c); } } @@ -615,6 +646,9 @@ bool oEvent::getClassesFromBirthYear(int year, PersonSex sex, vector &class if (it->isRemoved()) continue; + if (it->getClassType() == ClassType::oClassRelay) + continue; + PersonSex clsSex = it->getSex(); if (clsSex == sFemale && sex == sMale) continue; @@ -921,7 +955,7 @@ void oClass::fillStartTypes(gdioutput &gdi, const string &name, bool firstLeg) gdi.addItem(name, lang.tl("Växling"), STChange); gdi.addItem(name, lang.tl("Tilldelad"), STDrawn); if (!firstLeg) - gdi.addItem(name, lang.tl("Jaktstart"), STHunting); + gdi.addItem(name, lang.tl("Jaktstart"), STPursuit); } StartTypes oClass::getStartType(int leg) const @@ -981,7 +1015,7 @@ wstring oClass::getStartDataS(int leg) const int s=getStartData(leg); StartTypes t=getStartType(leg); - if (t==STTime || t==STHunting) { + if (t==STTime || t==STPursuit) { if (s>0) return oe->getAbsTime(s); else return makeDash(L"-"); @@ -999,7 +1033,7 @@ wstring oClass::getRestartTimeS(int leg) const int s=getRestartTime(leg); StartTypes t=getStartType(leg); - if (t==STChange || t==STHunting) { + if (t==STChange || t==STPursuit) { if (s>0) return oe->getAbsTime(s); else return makeDash(L"-"); @@ -1017,7 +1051,7 @@ wstring oClass::getRopeTimeS(int leg) const int s=getRopeTime(leg); StartTypes t=getStartType(leg); - if (t==STChange || t==STHunting) { + if (t==STChange || t==STPursuit) { if (s>0) return oe->getAbsTime(s); else return makeDash(L"-"); @@ -1093,7 +1127,7 @@ bool oClass::checkStartMethod() { for (size_t j = 0; j < legInfo.size(); j++) { if (!legInfo[j].isParallel()) st = legInfo[j].startMethod; - else if ((legInfo[j].startMethod == STChange || legInfo[j].startMethod == STHunting) && st != legInfo[j].startMethod) { + else if ((legInfo[j].startMethod == STChange || legInfo[j].startMethod == STPursuit) && st != legInfo[j].startMethod) { legInfo[j].startMethod = STDrawn; error = true; } @@ -1152,7 +1186,7 @@ void oClass::setLegType(int leg, LegTypes lt) bool oClass::setStartData(int leg, const wstring &s) { int rt; StartTypes styp=getStartType(leg); - if (styp==STTime || styp==STHunting) + if (styp==STTime || styp==STPursuit) rt=oe->getRelativeTime(s); else rt=_wtoi(s.c_str()); @@ -1162,11 +1196,11 @@ bool oClass::setStartData(int leg, const wstring &s) { bool oClass::setStartData(int leg, int value) { bool changed = false; - if (unsigned(leg)=0) { + if (unsigned(leg) < legInfo.size()) + changed = legInfo[leg].legStartData != value; + else if (leg >= 0) { changed = true; - legInfo.resize(leg+1); + legInfo.resize(leg + 1); } legInfo[leg].legStartData = value; @@ -1569,9 +1603,7 @@ pCourse oClass::selectParallelCourse(const oRunner &r, const SICard &sic) { return rc; } - -pCourse oClass::getCourse(int leg, unsigned fork, bool getSampleFromRunner) const -{ +pCourse oClass::getCourse(int leg, unsigned fork, bool getSampleFromRunner) const { leg = mapLeg(leg); if (size_t(leg) < MultiCourse.size()) { @@ -1586,7 +1618,7 @@ pCourse oClass::getCourse(int leg, unsigned fork, bool getSampleFromRunner) cons } if (!getSampleFromRunner) - return 0; + return nullptr; else { pCourse res = 0; for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { @@ -2180,10 +2212,10 @@ public: if (t > 0) return t; else - return 3600 * 24 * 8; + return timeConstHour * 24 * 8; } else { - return 3600 * 24 * 8 + r.getId(); + return timeConstHour * 24 * 8 + r.getId(); } } @@ -2763,8 +2795,8 @@ ClassMetaType oClass::interpretClassType() const { xmlList xtypes; cType.getObjects("Type", xtypes); for (size_t k = 0; k > &out, size_t &sel if (id==TID_COURSE) { out.clear(); - oe->fillCourses(out, true); + oe->getCourses(out, L"", true); out.push_back(make_pair(lang.tl(L"Ingen bana"), 0)); pCourse c = getCourse(false); selected = c ? c->getId() : 0; @@ -3340,7 +3372,7 @@ void oClass::getStartRange(int leg, int &firstStart, int &lastStart) const { size_t s = getLastStageIndex() + 1; assert(s>0); vector lFirstStart, lLastStart; - lFirstStart.resize(s, 3600 * 24 * 365); + lFirstStart.resize(s, timeConstHour * 24 * 365); lLastStart.resize(s, 0); for (oRunnerList::iterator it = oe->Runners.begin(); it != oe->Runners.end(); ++it) { if (it->isRemoved() || it->getClassRef(true) != this) @@ -3445,10 +3477,10 @@ void oClass::calculateSplits() { const int s = min(nc, sp.size()); for (int k = 0; k < s; k++) { - if (sp[k].time > 0 && acceptMissingPunch[k]) { + if (sp[k].getTime(true) > 0 && acceptMissingPunch[k]) { pControl ctrl = tpc->getControl(k); // If there is a - if (ctrl && ctrl->getStatus() != oControl::StatusBad && ctrl->getStatus() != oControl::StatusOptional) + if (ctrl && ctrl->getStatus() != oControl::ControlStatus::StatusBad && ctrl->getStatus() != oControl::ControlStatus::StatusOptional) acceptMissingPunch[k] = false; } } @@ -3468,16 +3500,16 @@ void oClass::calculateSplits() { bool ok = true; for (int k = 0; k < s; k++) { - if (sp[k].time > 0) { + if (sp[k].getTime(true) > 0) { if (ok) { // Store accumulated times - int t = sp[k].time - it->tStartTime; + int t = sp[k].getTime(true) - it->tStartTime; if (it->tStartTime>0 && t>0) splitsAcc[k].push_back(t); } if (k == 0) { // start -> first - int t = sp[0].time - it->tStartTime; + int t = sp[0].getTime(true) - it->tStartTime; if (it->tStartTime>0 && t>0) { splits[k].push_back(t); tLegTimes[k] = t; @@ -3486,8 +3518,8 @@ void oClass::calculateSplits() { tLegTimes[k] = 0; } else { // control -> control - int t = sp[k].time - sp[k-1].time; - if (sp[k-1].time>0 && t>0) { + int t = sp[k].getTime(true) - sp[k-1].getTime(true); + if (sp[k-1].getTime(true)>0 && t>0) { splits[k].push_back(t); tLegTimes[k] = t; } @@ -3500,12 +3532,12 @@ void oClass::calculateSplits() { } // last -> finish - if (sp.size() == nc && sp[nc-1].time>0 && it->FinishTime > 0) { - int t = it->FinishTime - sp[nc-1].time; + if (sp.size() == nc && sp[nc-1].getTime(true)>0 && it->FinishTime > 0) { + int t = it->FinishTime - sp[nc-1].getTime(true); if (t>0) { splits[nc].push_back(t); tLegTimes[nc] = t; - if (it->statusOK(true) && (it->FinishTime - it->tStartTime) > 0) { + if (it->statusOK(true, false) && (it->FinishTime - it->tStartTime) > 0) { splitsAcc[nc].push_back(it->FinishTime - it->tStartTime); } } @@ -4012,15 +4044,113 @@ long long oClass::setupForkKey(const vector indices, const vector< vector< return key; } -pair oClass::autoForking(const vector< vector > &inputCourses) { +void maximizeSpread(const vector &coursesFirst, const vector& coursesLast, vector& order, int numToGenerate) { + + struct Node { + map children; + vector courses; + int readIx = 0; + + void insert(int ix, const pCourse& course, int position) { + if (course->getNumControls() == position) + courses.push_back(ix); + else { + children[course->getControl(position)->getId()].insert(ix, course, position + 1); + } + } + + void insertBack(int ix, const pCourse& course, int position) { + if (course->getNumControls() == position) + courses.push_back(ix); + else { + children[course->getControl(course->getNumControls() - 1 - position)->getId()].insertBack(ix, course, position + 1); + } + } + + void shuffleOrder() { + if (!courses.empty()) { + permute(courses); + } + for (auto c : children) + c.second.shuffleOrder(); + } + + int getNextForking(int inKey, unordered_map& controlUsage) { + if (readIx 1) + key = mKey + it->first; + else + key = inKey; + + int best = controlUsage[it->first] + controlUsage[key]; + auto next = it; + while (++it != children.end()) { + key = mKey + it->first; + int c = controlUsage[it->first] + controlUsage[key]; + if (c < best) { + best = c; + next = it; + } + } + + if (children.size() > 1) + key = mKey + next->first; + else + key = inKey; + + int res = next->second.getNextForking(key, controlUsage); + if (res != -1) { + if (children.size() > 1) + ++controlUsage[key]; + ++controlUsage[next->first]; + return res; + } + + // Empty + children.erase(next); + } + return -1; + } + }; + + Node courseOrderLast; + for (size_t i = 0; i < coursesLast.size(); i++) + courseOrderLast.insertBack(i, coursesLast[i], 0); + + courseOrderLast.shuffleOrder(); + unordered_map controlUsage; + order.resize(coursesLast.size()); + for (int i = 0; i < order.size(); i++) + order[i] = courseOrderLast.getNextForking(0, controlUsage); + + Node courseOrderFirst; + for (size_t i : order) + courseOrderFirst.insert(i, coursesFirst[i], 0); + + controlUsage.clear(); + int ns = min(coursesFirst.size(), numToGenerate); + order.resize(ns); + for (int i = 0; i < ns; i++) + order[i] = courseOrderFirst.getNextForking(0, controlUsage); +} + +pair oClass::autoForking(const vector> &inputCourses, int numToGenerate) { if (inputCourses.size() != getNumStages()) throw meosException("Internal error"); + int legs = inputCourses.size(); vector nf(legs); vector prod(legs); vector ix(legs); vector< vector< vector > > courseKeys(legs); - vector< vector > pCourses(legs); + vector> pCourses(legs); unsigned long long N = 1; for (int k = 0; k < legs; k++) { @@ -4056,9 +4186,9 @@ pair oClass::autoForking(const vector< vector > &inputCourses) { size_t Ns = size_t(N); map count; vector ws; - for (size_t k = 0; k < Ns; k ++) { + for (size_t k = 0; k < Ns; k++) { + uint64_t D = uint64_t(k) * sampleFactor; for (int j = 0; j < legs; j++) { - uint64_t D = uint64_t(k) * sampleFactor; if (nf[j]>0) { ix[j] = int((D/prod[j] + j) % nf[j]); } @@ -4085,10 +4215,10 @@ pair oClass::autoForking(const vector< vector > &inputCourses) { clearStageCourses(j); } } - set coursesUsed; set generatedForkKeys; + int genLimit = max(numToGenerate * 2, numToGenerate + 100); - vector< vector > courseMatrix(legs); + vector> courseMatrix(legs); for (size_t k = 0; k < Ns; k++) { long long forkKey = 0; for (int j = 0; j < legs; j++) { @@ -4104,53 +4234,51 @@ pair oClass::autoForking(const vector< vector > &inputCourses) { generatedForkKeys.insert(forkKey); for (int j = 0; j < legs; j++) { if (nf[j] > 0) { - coursesUsed.insert(pCourses[j][ix[j]]->getId()); + // coursesUsed.insert(pCourses[j][ix[j]]->getId()); courseMatrix[j].push_back(pCourses[j][ix[j]]); - //addStageCourse(j, pCourses[j][ix[j]]); } } } - if (generatedForkKeys.size() > 200) + if (generatedForkKeys.size() >= genLimit) break; } + vector fperm; for (size_t j = 0; j < courseMatrix.size(); j++) { if (courseMatrix[j].empty()) continue; + int jj = courseMatrix.size() - 1; + while (courseMatrix[jj].empty()) + jj--; + + maximizeSpread(courseMatrix[j], courseMatrix[jj], fperm, numToGenerate); // Take the first used course. - fperm.resize(courseMatrix[j].size()); + /*fperm.resize(courseMatrix[j].size()); for (size_t i = 0; i < fperm.size(); i++) - fperm[i] = i; + fperm[i] = i;*/ break; } - permute(fperm); + + set coursesUsed; int lastSet = -1; for (int j = 0; j < legs; j++) { if (nf[j] > 0) { lastSet = j; - for (size_t k = 0; k < courseMatrix[j].size(); k++) { - if (k < fperm.size()) { - addStageCourse(j, courseMatrix[j][fperm[k]], -1); - } - else { - addStageCourse(j, courseMatrix[j][k], -1); - } + for (size_t k = 0; k < fperm.size(); k++) { + coursesUsed.insert(courseMatrix[j][fperm[k]]->getId()); + addStageCourse(j, courseMatrix[j][fperm[k]], -1); } } else if (lastSet >= 0 && getLegType(j) == LTExtra) { MultiCourse[j] = MultiCourse[lastSet]; - // getCourses(lastSet, courses); - //clearStageCourses(j); - //for (size_t k = 0; k < courses.size(); k++) - // addStageCourse(j, courses[k]->getId()); } else { lastSet = -1; } } - return make_pair(generatedForkKeys.size(), coursesUsed.size()); + return make_pair(fperm.size(), coursesUsed.size()); } int oClass::extractBibPattern(const wstring &bibInfo, wchar_t pattern[32]) { @@ -4515,7 +4643,7 @@ void oClass::drawSeeded(ClassSeedMethod seed, int leg, int firstStart, bool oClass::hasClassGlobalDependence() const { for (size_t k = 0; k < legInfo.size(); k++) { - if (legInfo[k].startMethod == STHunting) + if (legInfo[k].startMethod == STPursuit) return true; } return false; @@ -4620,6 +4748,18 @@ int oClass::getPreceedingLeg(int leg) const { return -1; } +int oClass::getResultDefining(int leg) const { + int res = leg; + while (size_t(res+1) < legInfo.size() && + (legInfo[res+1].isParallel() || legInfo[res+1].isOptional())) + res++; + + if (size_t(res) >= legInfo.size()) + res = legInfo.size() - 1; + + return res; +} + bool oClass::lockedForking() const { return (getDCI().getInt("Locked") & 1) == 1; } @@ -4867,7 +5007,7 @@ void oClass::updateFinalClasses(oRunner *causingResult, bool updateStartNumbers) int lastPlace = 0, orderPlace = 1; int numEqual = 0; for (size_t k = 0; k < classSplit[i].size(); k++) { - auto &res = classSplit[i][k]->getTempResult(); + const auto &res = classSplit[i][k]->getTempResult(); //int heat = 0; if (res.getStatus() == StatusOK) { int place = res.getPlace(); diff --git a/code/oClass.h b/code/oClass.h index 3b721a8..5b0b35a 100644 --- a/code/oClass.h +++ b/code/oClass.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -40,7 +40,7 @@ enum StartTypes { STTime=0, STChange, STDrawn, - STHunting, + STPursuit, ST_max }; enum { nStartTypes = ST_max }; @@ -101,13 +101,16 @@ struct oLegInfo { LegTypes legMethod; bool isParallel() const {return legMethod == LTParallel || legMethod == LTParallelOptional;} bool isOptional() const {return legMethod == LTParallelOptional || legMethod == LTExtra || legMethod == LTIgnore;} - //Interpreteation depends. Can be starttime/first start - //or number of earlier legs to consider. + //Interpreteation depends. Can be starttime/first start (if styp==STTime || styp==STPursuit) + // or number of earlier legs to consider. int legStartData; int legRestartTime; int legRopeTime; int duplicateRunner; + /** Return true if start data should be interpreted as a time.*/ + bool isStartDataTime() const { return startMethod == STTime || startMethod == STPursuit; } + // Transient, deducable data int trueSubLeg; int trueLeg; @@ -571,6 +574,10 @@ public: // Get the linear leg number of the preceeding leg int getPreceedingLeg(int leg) const; + // Get result defining leg (for parallel legs, the last leg in the currrent parallel set) + int getResultDefining(int leg) const; + + /// Get a string 1, 2a, etc describing the number of the leg wstring getLegNumber(int leg) const; @@ -600,7 +607,9 @@ public: void setDirectResult(bool directResult); bool hasDirectResult() const; - + bool isValidLeg(int legIndex) const { + return legIndex == -1 || legIndex == 0 || (legIndex > 0 && legIndex - pair autoForking(const vector< vector > &inputCourses); + pair autoForking(const vector< vector > &inputCourses, int numToGenerateMax); bool hasUnorderedLegs() const; void setUnorderedLegs(bool order); diff --git a/code/oClub.cpp b/code/oClub.cpp index 6757ace..0ca9492 100644 --- a/code/oClub.cpp +++ b/code/oClub.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -96,16 +96,16 @@ void oClub::set(const xmlobject &xo) Id=it->getInt(); } else if (it->is("Name")){ - internalSetName(it->getw()); + internalSetName(it->getWStr()); } else if (it->is("oData")){ getDI().set(*it); } else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); } else if (it->is("AltName")) { - altNames.push_back(it->getw()); + altNames.push_back(it->getWStr()); } } } @@ -154,15 +154,14 @@ oDataContainer &oClub::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &s return *oe->oClubData; } -pClub oEvent::getClub(int Id) const -{ - if (Id<=0) - return 0; +pClub oEvent::getClub(int Id) const { + if (Id <= 0) + return nullptr; pClub value; if (clubIdIndex.lookup(Id, value)) return value; - return 0; + return nullptr; } pClub oEvent::getClub(const wstring &pname) const @@ -509,19 +508,19 @@ void oClub::addRunnerInvoiceLine(const pRunner r, bool inTeam, if (type == oClassIndividRelay || type == oClassRelay) { int leg = r->getLegNumber(); if (t->getLegStatus(leg, true, false) == StatusOK) - ts = t->getLegPlaceS(leg, false)+ L" (" + r->getRunningTimeS(true) + L")"; + ts = t->getLegPlaceS(leg, false)+ L" (" + r->getRunningTimeS(true, SubSecond::Auto) + L")"; else - ts = t->getLegStatusS(leg, true, false)+ L" (" + r->getRunningTimeS(true) +L")"; + ts = t->getLegStatusS(leg, true, false)+ L" (" + r->getRunningTimeS(true, SubSecond::Auto) +L")"; } else - ts = r->getPrintPlaceS(true)+ L" (" + r->getRunningTimeS(true) + L")"; + ts = r->getPrintPlaceS(true)+ L" (" + r->getRunningTimeS(true, SubSecond::Auto) + L")"; } else ts = r->getStatusS(true, true); } else { if (r->getTotalStatus()==StatusOK) { - ts = r->getPrintTotalPlaceS(true) + L" (" + r->getTotalRunningTimeS() + L")"; + ts = r->getPrintTotalPlaceS(true) + L" (" + r->getTotalRunningTimeS(SubSecond::Auto) + L")"; } else if (r->getTotalStatus()!=StatusNotCompetiting) ts = r->getStatusS(true, true); @@ -580,7 +579,7 @@ void oClub::addTeamInvoiceLine(const pTeam t, const map &definedPa ts = L"-"; else { if (t->getStatus()==StatusOK) { - ts = t->getPrintPlaceS(true) + L" (" + t->getRunningTimeS(true) + L")"; + ts = t->getPrintPlaceS(true) + L" (" + t->getRunningTimeS(true, SubSecond::Auto) + L")"; } else ts = t->getStatusS(true, true); diff --git a/code/oClub.h b/code/oClub.h index 06f5b53..b72354e 100644 --- a/code/oClub.h +++ b/code/oClub.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/oControl.cpp b/code/oControl.cpp index 636008e..36b1447 100644 --- a/code/oControl.cpp +++ b/code/oControl.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -42,11 +42,11 @@ using namespace std; -oControl::oControl(oEvent *poe): oBase(poe) +oControl::oControl(oEvent* poe) : oBase(poe) { getDI().initData(); - nNumbers=0; - Status=StatusOK; + nNumbers = 0; + Status = ControlStatus::StatusOK; tMissedTimeMax = 0; tMissedTimeTotal = 0; tNumVisitorsActual = 0; @@ -60,12 +60,12 @@ oControl::oControl(oEvent *poe): oBase(poe) tNumberDuplicates = 0; } -oControl::oControl(oEvent *poe, int id): oBase(poe) +oControl::oControl(oEvent* poe, int id) : oBase(poe) { Id = id; getDI().initData(); - nNumbers=0; - Status=StatusOK; + nNumbers = 0; + Status = ControlStatus::StatusOK; tMissedTimeMax = 0; tMissedTimeTotal = 0; @@ -104,7 +104,7 @@ bool oControl::write(xmlparser &xml) xml.write("Updated", getStamp()); xml.write("Name", Name); xml.write("Numbers", codeNumbers()); - xml.write("Status", Status); + xml.write("Status", int(Status)); getDI().write(xml); xml.endTag(); @@ -130,48 +130,46 @@ void oControl::setStatus(ControlStatus st){ } } -void oControl::setName(wstring name) -{ - if (name!=getName()){ - Name=name; +void oControl::setName(wstring name) { + if (name != getName()) { + Name = name; updateChanged(); } } -void oControl::set(const xmlobject *xo) -{ +void oControl::set(const xmlobject* xo) { xmlList xl; xo->getObjects(xl); - nNumbers=0; - Numbers[0]=0; + nNumbers = 0; + Numbers[0] = 0; xmlList::const_iterator it; - for(it=xl.begin(); it != xl.end(); ++it){ - if (it->is("Id")){ - Id=it->getInt(); + for (it = xl.begin(); it != xl.end(); ++it) { + if (it->is("Id")) { + Id = it->getInt(); } - else if (it->is("Number")){ - Numbers[0]=it->getInt(); - nNumbers=1; + else if (it->is("Number")) { + Numbers[0] = it->getInt(); + nNumbers = 1; } - else if (it->is("Numbers")){ - decodeNumbers(it->getRaw()); + else if (it->is("Numbers")) { + decodeNumbers(it->getRawStr()); } - else if (it->is("Status")){ - Status=(ControlStatus)it->getInt(); + else if (it->is("Status")) { + Status = (ControlStatus)it->getInt(); } - else if (it->is("Name")){ - Name=it->getw(); + else if (it->is("Name")) { + Name = it->getWStr(); if (Name.size() > 1 && Name.at(0) == '%') { Name = lang.tl(Name.substr(1)); } } - else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + else if (it->is("Updated")) { + Modified.setStamp(it->getRawStr()); } - else if (it->is("oData")){ + else if (it->is("oData")) { getDI().set(*it); } } @@ -185,44 +183,48 @@ int oControl::getFirstNumber() const { } wstring oControl::getString() { - wchar_t bf[32]; - if (Status==StatusOK || Status==StatusNoTiming) - return codeNumbers('|'); - else if (Status==StatusMultiple) - return codeNumbers('+'); - else if (Status==StatusRogaining) - return codeNumbers('|') + L", " + itow(getRogainingPoints()) + L"p"; + wstring num; + if (Status == ControlStatus::StatusMultiple) + num = codeNumbers('+'); + else if (Status == ControlStatus::StatusRogaining || Status == ControlStatus::StatusRogainingRequired) + num = codeNumbers('|') + L", " + itow(getRogainingPoints()) + L"p"; else - swprintf_s(bf, 32, L"~%s", codeNumbers().c_str()); - return bf; + num = codeNumbers('|'); + + if (Status == ControlStatus::StatusBad || Status == ControlStatus::StatusBadNoTiming) + return L"\u26A0" + num; + + if (Status == ControlStatus::StatusOptional) + return L"\u2b59" + num; + + return num; } wstring oControl::getLongString() { - if (Status==StatusOK || Status==StatusNoTiming){ - if (nNumbers==1) + if (Status == ControlStatus::StatusOK || Status == ControlStatus::StatusNoTiming) { + if (nNumbers == 1) return codeNumbers('|'); else - return wstring(lang.tl("VALFRI("))+codeNumbers(',')+L")"; + return wstring(lang.tl("VALFRI(")) + codeNumbers(',') + L")"; } - else if (Status == StatusMultiple) { - return wstring(lang.tl("ALLA("))+codeNumbers(',')+L")"; + else if (Status == ControlStatus::StatusMultiple) { + return wstring(lang.tl("ALLA(")) + codeNumbers(',') + L")"; } - else if (Status == StatusRogaining) - return wstring(lang.tl("RG("))+codeNumbers(',') + L"|" + itow(getRogainingPoints()) + L"p)"; + else if (Status == ControlStatus::StatusRogaining || Status == ControlStatus::StatusRogainingRequired) + return wstring(lang.tl("RG(")) + codeNumbers(',') + L"|" + itow(getRogainingPoints()) + L"p)"; else - return wstring(lang.tl("TRASIG("))+codeNumbers(',')+L")"; + return wstring(lang.tl("TRASIG(")) + codeNumbers(',') + L")"; } -bool oControl::hasNumber(int i) -{ - for(int n=0;n0) + if (nNumbers > 0) return false; else return true; } @@ -254,7 +256,7 @@ bool oControl::hasNumberUnchecked(int i) int oControl::getNumMulti() { - if (Status==StatusMultiple) + if (Status== ControlStatus::StatusMultiple) return nNumbers; else return 1; @@ -291,9 +293,9 @@ bool oControl::decodeNumbers(string s) Numbers[nNumbers++]=cid; } - if (Numbers==0){ - Numbers[0]=0; - nNumbers=1; + if (nNumbers==0){ + Numbers[0] = Id; + nNumbers = 1; return false; } else return true; @@ -351,68 +353,80 @@ oDataContainer &oControl::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr return *oe->oControlData; } -const vector< pair > &oEvent::fillControls(vector< pair > &out, oEvent::ControlType type) -{ +const vector>& oEvent::fillControls(vector< pair>& out, + oEvent::ControlType type) { out.clear(); - oControlList::iterator it; - synchronizeList(oListId::oLControlId); + synchronizeList({ oListId::oLControlId, oListId::oLCardId, oListId::oLPunchId }); Controls.sort(); - if (type == oEvent::CTCourseControl) { + if (type == oEvent::ControlType::CourseControl) { vector dmy; - getControls(dmy, true); + getControls(dmy, true); // Update data } + map, pControl> existingTypeUnits; + wstring b; wchar_t bf[256]; - for (it=Controls.begin(); it != Controls.end(); ++it) { - if (!it->Removed){ + for (auto it = Controls.begin(); it != Controls.end(); ++it) { + if (!it->Removed) { b.clear(); - if (type == oEvent::CTAll) { - if (it->Status == oControl::StatusFinish || it->Status == oControl::StatusStart) { + if (type == oEvent::ControlType::All) { + if (it->isUnit()) { + existingTypeUnits.emplace(make_pair(it->getUnitType(), it->getUnitCode()), &*it); + continue; + } + if (oControl::isSpecialControl(it->Status)) { b += it->Name; } else { - if (it->Status == oControl::StatusOK || it->Status == oControl::StatusNoTiming) - b += L"[OK]\t"; - else if (it->Status == oControl::StatusMultiple) - b += L"[M]\t"; - else if (it->Status == oControl::StatusRogaining) - b += L"[R]\t"; - else if (it->Status == oControl::StatusBad) - b += makeDash(L"[-]\t"); - else if (it->Status == oControl::StatusBadNoTiming) - b += L"[!]\t"; - else if (it->Status == oControl::StatusOptional) - b += L"[O]\t"; + if (it->Status == oControl::ControlStatus::StatusOK) + b += L"OK\t"; + else if (it->Status == oControl::ControlStatus::StatusNoTiming) + b += L"\u231B\t"; + else if (it->Status == oControl::ControlStatus::StatusMultiple) + b += L"\u25ef\u25ef\t"; + else if (it->Status == oControl::ControlStatus::StatusRogaining) + b += L"R\u25ef\t"; + else if (it->Status == oControl::ControlStatus::StatusRogainingRequired) + b += L"R!\u25ef\t"; + else if (it->Status == oControl::ControlStatus::StatusBad) + b += L"\u26A0\t"; + else if (it->Status == oControl::ControlStatus::StatusBadNoTiming) + b += L"\u26A0\u231B\t"; + else if (it->Status == oControl::ControlStatus::StatusOptional) + b += L"\u26aa\u2b59\t"; else b += L"[ ]\t"; - swprintf_s(bf, L" %s", it->codeNumbers(' ').c_str()); - b += bf; + b += it->codeNumbers(' '); + if (it->nNumbers == 0 || it->Id != it->Numbers[0]) { + b += L" (" + itow(it->Id) + L")"; + } - if (it->Status == oControl::StatusRogaining) + if (it->Status == oControl::ControlStatus::StatusRogaining || it->Status == oControl::ControlStatus::StatusRogainingRequired) b += L"\t(" + itow(it->getRogainingPoints()) + L"p)"; else if (it->Name.length() > 0) { b += L"\t(" + it->Name + L")"; } + } - out.push_back(make_pair(b, it->Id)); + out.emplace_back(b, it->Id); } - else if (type==oEvent::CTRealControl) { - if (it->Status == oControl::StatusFinish || it->Status == oControl::StatusStart) + else if (type == oEvent::ControlType::RealControl) { + if (oControl::isSpecialControl(it->Status)) continue; swprintf_s(bf, lang.tl("Kontroll %s").c_str(), it->codeNumbers(' ').c_str()); - b=bf; + b = bf; if (!it->Name.empty()) b += L" (" + it->Name + L")"; - out.push_back(make_pair(b, it->Id)); + out.emplace_back(b, it->Id); } - else if (type==oEvent::CTCourseControl) { - if (it->Status == oControl::StatusFinish || it->Status == oControl::StatusStart) + else if (type == oEvent::ControlType::CourseControl) { + if (oControl::isSpecialControl(it->Status)) continue; for (int i = 0; i < it->getNumberDuplicates(); i++) { @@ -420,16 +434,38 @@ const vector< pair > &oEvent::fillControls(vector< pairgetNumberDuplicates() > 1) - b += L"-" + itow(i+1); + b += L"-" + itow(i + 1); if (!it->Name.empty()) b += L" (" + it->Name + L")"; - out.push_back(make_pair(b, oControl::getCourseControlIdFromIdIndex(it->Id, i))); + out.emplace_back(b, oControl::getCourseControlIdFromIdIndex(it->Id, i)); } } } } + if (type == oEvent::ControlType::All) { + vector> typeUnit; + oe->getExistingUnits(typeUnit); + for (auto& tu : typeUnit) { + auto res = existingTypeUnits.find(tu); + if (res == existingTypeUnits.end()) { + wstring name; + if (tu.first == oPunch::SpecialPunch::PunchFinish) + name = lang.tl("Målenhet") + L" " + itow(tu.second); + else if (tu.first == oPunch::SpecialPunch::PunchStart) + name = lang.tl("Startenhet") + L" " + itow(tu.second); + else if (tu.first == oPunch::SpecialPunch::PunchCheck) + name = lang.tl("Checkenhet") + L" " + itow(tu.second); + out.emplace_back(name, tu.first * 1100000 + tu.second); + } + else { + wstring name = res->second->getName() + L"\t" + res->second->getTimeAdjustS(); + out.emplace_back(name, res->second->getId()); + } + } + } + return out; } @@ -474,9 +510,13 @@ void oControl::setupCache() const { } } +void oControl::clearCache() { + tCache.dataRevision = -1; +} + int oControl::getMinTime() const { - if (Status == StatusNoTiming || Status == StatusBadNoTiming) + if (Status == ControlStatus::StatusNoTiming || Status == ControlStatus::StatusBadNoTiming) return 0; setupCache(); return tCache.minTime; @@ -490,13 +530,13 @@ int oControl::getTimeAdjust() const wstring oControl::getTimeAdjustS() const { - return getTimeMS(getTimeAdjust()); + return formatTimeMS(getTimeAdjust(), false, SubSecond::Auto); } wstring oControl::getMinTimeS() const { if (getMinTime()>0) - return getTimeMS(getMinTime()); + return formatTimeMS(getMinTime(), false, SubSecond::Auto); else return makeDash(L"-"); } @@ -512,9 +552,8 @@ wstring oControl::getRogainingPointsS() const return pt != 0 ? itow(pt) : L""; } -void oControl::setTimeAdjust(int v) -{ - getDI().setInt("TimeAdjust", v); +bool oControl::setTimeAdjust(int v) { + return getDI().setInt("TimeAdjust", v); } void oControl::setRadio(bool r) @@ -527,14 +566,13 @@ bool oControl::isValidRadio() const { int flag = getDCI().getInt("Radio"); if (flag == 0) - return (tHasFreePunchLabel || hasName()) && getStatus() == oControl::StatusOK; + return (tHasFreePunchLabel || hasName()) && getStatus() == oControl::ControlStatus::StatusOK; else return flag == 1; } -void oControl::setTimeAdjust(const wstring &s) -{ - setTimeAdjust(convertAbsoluteTimeMS(s)); +bool oControl::setTimeAdjust(const wstring &s) { + return setTimeAdjust(convertAbsoluteTimeMS(s)); } void oControl::setMinTime(int v) @@ -580,7 +618,7 @@ void oControl::addUncheckedPunches(vector &mp, bool supportRogaining) const if (!checkedNumbers[k]) { mp.push_back(Numbers[k]); - if (Status!=StatusMultiple) + if (Status!= ControlStatus::StatusMultiple) return; } } @@ -597,7 +635,7 @@ int oControl::getMissingNumber() const bool oControl::controlCompleted(bool supportRogaining) const { - if (Status==StatusOK || Status==StatusNoTiming || ((Status == StatusRogaining) && !supportRogaining)) { + if (Status== ControlStatus::StatusOK || Status== ControlStatus::StatusNoTiming || ((Status == ControlStatus::StatusRogaining || Status == ControlStatus::StatusRogainingRequired) && !supportRogaining)) { //Check if any number is used. for (int k=0;k > &oEvent::fillControlStatus(vector< pair > &out) const { out.clear(); - out.push_back(make_pair(lang.tl(L"OK"), oControl::StatusOK)); - out.push_back(make_pair(lang.tl(L"Multipel"), oControl::StatusMultiple)); + out.emplace_back(lang.tl(L"OK"), size_t(oControl::ControlStatus::StatusOK)); + out.emplace_back(lang.tl(L"Multipel"), size_t(oControl::ControlStatus::StatusMultiple)); - if (getMeOSFeatures().hasFeature(MeOSFeatures::Rogaining)) - out.push_back(make_pair(lang.tl(L"Rogaining"), oControl::StatusRogaining)); - out.push_back(make_pair(lang.tl(L"Utan tidtagning"), oControl::StatusNoTiming)); - out.push_back(make_pair(lang.tl(L"Trasig"), oControl::StatusBad)); - out.push_back(make_pair(lang.tl(L"Försvunnen"), oControl::StatusBadNoTiming)); - out.push_back(make_pair(lang.tl(L"Valfri"), oControl::StatusOptional)); + if (getMeOSFeatures().hasFeature(MeOSFeatures::Rogaining)) { + out.emplace_back(lang.tl(L"Rogaining"), size_t(oControl::ControlStatus::StatusRogaining)); + out.emplace_back(lang.tl(L"Rogaining Obligatorisk"), size_t(oControl::ControlStatus::StatusRogainingRequired)); + } + out.emplace_back(lang.tl(L"Utan tidtagning"), size_t(oControl::ControlStatus::StatusNoTiming)); + out.emplace_back(lang.tl(L"Trasig"), size_t(oControl::ControlStatus::StatusBad)); + out.emplace_back(lang.tl(L"Försvunnen"), size_t(oControl::ControlStatus::StatusBadNoTiming)); + out.emplace_back(lang.tl(L"Valfri"), size_t(oControl::ControlStatus::StatusOptional)); return out; } @@ -852,7 +896,7 @@ void oControl::addTableRow(Table &table) const { table.set(row++, it, TID_MODIFIED, getTimeStamp(), false); table.set(row++, it, TID_CONTROL, getName(), true); - bool canEdit = getStatus() != oControl::StatusFinish && getStatus() != oControl::StatusStart; + bool canEdit = !isSpecialControl(getStatus()); table.set(row++, it, TID_STATUS, getStatusS(), canEdit, cellSelection); table.set(row++, it, TID_CODES, codeNumbers(), true); @@ -903,7 +947,7 @@ void oControl::fillInput(int id, vector< pair > &out, size_t &s if (id==TID_STATUS) { oe->fillControlStatus(out); - selected = getStatus(); + selected = size_t(getStatus()); } } @@ -1020,3 +1064,172 @@ int oControl::getControlIdByName(const oEvent &oe, const string &name) { return 0; } +bool oControl::isUnit() const { + if (isSpecialControl(getStatus())) { + return getUnitCode() > 0; + } + return false; +} + +int oControl::getUnitCode() const { + return getDCI().getInt("Unit"); +} + +oPunch::SpecialPunch oControl::getUnitType() const { + switch (getStatus()) { + case ControlStatus::StatusFinish: + return oPunch::SpecialPunch::PunchFinish; + case ControlStatus::StatusStart: + return oPunch::SpecialPunch::PunchStart; + case ControlStatus::StatusCheck: + return oPunch::SpecialPunch::PunchCheck; + } + throw exception(); +} + +void oEvent::clearUnitAdjustmentCache() { + typeUnitPunchTimeAdjustment.first = -1; +} + +int oEvent::getUnitAdjustment(oPunch::SpecialPunch type, int unit) const { + if (typeUnitPunchTimeAdjustment.first != dataRevision) { + typeUnitPunchTimeAdjustment.second.clear(); + for (auto& c : Controls) { + if (!c.isRemoved() && c.isUnit()) { + int adjust = c.getTimeAdjust(); + if (adjust != 0) + typeUnitPunchTimeAdjustment.second.emplace(make_pair(c.getUnitType(), c.getUnitCode()), adjust); + } + } + typeUnitPunchTimeAdjustment.first = dataRevision; + } + auto res = typeUnitPunchTimeAdjustment.second.find(make_pair(type, unit)); + return res != typeUnitPunchTimeAdjustment.second.end() ? res->second : 0; +} + +pControl oEvent::getControl(int Id) const { + return const_cast(this)->getControl(Id, false, 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, bool includeVirtual) { + oControlList::const_iterator it; + + for (it = Controls.begin(); it != Controls.end(); ++it) { + if (it->Id == Id && !it->isRemoved()) + return pControl(&*it); + } + + if (!create && Id > 1100000 && includeVirtual) { + int unit = Id % 10000; + int type = (Id - unit) / 1100000; + if ((type == oPunch::SpecialPunch::PunchFinish || + type == oPunch::SpecialPunch::PunchStart || + type == oPunch::SpecialPunch::PunchCheck) && Id == type * 1100000 + unit) { + tmpControl = make_shared(this); + tmpControl->setLocalObject(); + string name; + oControl::ControlStatus st; + switch (type) { + case oPunch::SpecialPunch::PunchFinish: + name = "Målenhet"; + st = oControl::ControlStatus::StatusFinish; + break; + case oPunch::SpecialPunch::PunchStart: + name = "Startenhet"; + st = oControl::ControlStatus::StatusStart; + break; + case oPunch::SpecialPunch::PunchCheck: + name = "Checkenhet"; + st = oControl::ControlStatus::StatusCheck; + break; + default: + throw 0; + } + tmpControl->setName(lang.tl(name) + L" " + itow(unit)); + tmpControl->getDI().setInt("Unit", unit); + tmpControl->setNumbers(itow(type)); + tmpControl->Id = Id; + tmpControl->setStatus(st); + return tmpControl.get(); + } + } + + if (!create || Id <= 0) + return nullptr; + + //Not found. Auto add... + return addControl(Id, Id, L""); +} + +void oEvent::getExistingUnits(vector>& typeUnit) { + oFreePunch::rehashPunches(*oe, 0, nullptr); + + typeUnit.clear(); + set startUnit, finishUnit, checkUnit; + auto start = punchIndex.find(oPunch::SpecialPunch::PunchStart); + if (start != punchIndex.end()) { + for (auto& p : start->second) { + int pu = p.second->getPunchUnit(); + if (pu != 0) + startUnit.insert(pu); + } + } + + auto finish = punchIndex.find(oPunch::SpecialPunch::PunchFinish); + if (finish != punchIndex.end()) { + for (auto& p : finish->second) { + int pu = p.second->getPunchUnit(); + if (pu != 0) + finishUnit.insert(pu); + } + } + + auto check = punchIndex.find(oPunch::SpecialPunch::PunchCheck); + if (check != punchIndex.end()) { + for (auto& p : check->second) { + int pu = p.second->getPunchUnit(); + if (pu != 0) + checkUnit.insert(pu); + } + } + + for (auto& c : Cards) { + if (c.isRemoved()) + continue; + for (auto &p : c.punches) { + if (p.getTypeCode() == oPunch::SpecialPunch::PunchStart) { + int pu = p.getPunchUnit(); + if (pu != 0) + startUnit.insert(pu); + } + else if (p.getTypeCode() == oPunch::SpecialPunch::PunchFinish) { + int pu = p.getPunchUnit(); + if (pu != 0) + finishUnit.insert(pu); + } + if (p.getTypeCode() == oPunch::SpecialPunch::PunchCheck) { + int pu = p.getPunchUnit(); + if (pu != 0) + checkUnit.insert(pu); + } + } + } + + for (int u : startUnit) + typeUnit.emplace_back(oPunch::SpecialPunch::PunchStart, u); + + for (int u : finishUnit) + typeUnit.emplace_back(oPunch::SpecialPunch::PunchFinish, u); + + for (int u : checkUnit) + typeUnit.emplace_back(oPunch::SpecialPunch::PunchCheck, u); +} + diff --git a/code/oControl.h b/code/oControl.h index 424d013..7bd6fcc 100644 --- a/code/oControl.h +++ b/code/oControl.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -24,18 +24,14 @@ // ////////////////////////////////////////////////////////////////////// -#if !defined(AFX_OCONTROL_H__E86192B9_78D2_4EEF_AAE1_3BD4A8EB16F0__INCLUDED_) -#define AFX_OCONTROL_H__E86192B9_78D2_4EEF_AAE1_3BD4A8EB16F0__INCLUDED_ - -#if _MSC_VER > 1000 #pragma once -#endif // _MSC_VER > 1000 +#include +#include #include "xmlparser.h" #include "oBase.h" -#include -#include +#include "oPunch.h" class oControl; @@ -61,12 +57,20 @@ public: static pair getIdIndexFromCourseControlId(int courseControlId); static int getCourseControlIdFromIdIndex(int controlId, int index); - enum ControlStatus {StatusOK=0, StatusBad=1, StatusMultiple=2, - StatusStart = 4, StatusFinish = 5, StatusRogaining = 6, - StatusNoTiming = 7, StatusOptional = 8, - StatusBadNoTiming = 9}; + enum class ControlStatus { + StatusOK = 0, StatusBad = 1, StatusMultiple = 2, + StatusStart = 4, StatusFinish = 5, StatusRogaining = 6, + StatusNoTiming = 7, StatusOptional = 8, + StatusBadNoTiming = 9, StatusRogainingRequired = 10, StatusCheck = 11 + }; bool operator<(const oControl &b) const {return minNumber() &getTable(oEvent *oe); static int getControlIdByName(const oEvent &oe, const string &name); @@ -133,7 +138,7 @@ public: wstring getInfo() const; - bool isSingleStatusOK() const {return Status == StatusOK || Status == StatusNoTiming;} + bool isSingleStatusOK() const {return Status == ControlStatus::StatusOK || Status == ControlStatus::StatusNoTiming;} int getMissedTimeTotal() const; int getMissedTimeMax() const; @@ -182,11 +187,16 @@ public: /// Get name or id wstring getIdS() const; - bool isRogaining(bool useRogaining) const {return useRogaining && (Status == StatusRogaining);} + bool isRogaining(bool useRogaining) const {return useRogaining && + (Status == ControlStatus::StatusRogaining || Status == ControlStatus::StatusRogainingRequired);} void setStatus(ControlStatus st); void setName(wstring name); + bool isUnit() const; + int getUnitCode() const; + oPunch::SpecialPunch getUnitType() const; + //Returns true if control has number and checks it. bool hasNumber(int i); //Return true if it has number i and it is unchecked. @@ -204,8 +214,8 @@ public: int getTimeAdjust() const; wstring getTimeAdjustS() const; - void setTimeAdjust(int v); - void setTimeAdjust(const wstring &t); + bool setTimeAdjust(int v); + bool setTimeAdjust(const wstring &t); int getMinTime() const; wstring getMinTimeS() const; @@ -239,5 +249,3 @@ public: friend class TabAuto; friend class TabSpeaker; }; - -#endif // !defined(AFX_OCONTROL_H__E86192B9_78D2_4EEF_AAE1_3BD4A8EB16F0__INCLUDED_) diff --git a/code/oCourse.cpp b/code/oCourse.cpp index a02e870..9a6d6d5 100644 --- a/code/oCourse.cpp +++ b/code/oCourse.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -111,19 +111,19 @@ void oCourse::Set(const xmlobject *xo) Length=it->getInt(); } else if (it->is("Name")){ - Name=it->getw(); + Name=it->getWStr(); } else if (it->is("Controls")){ - importControls(it->getRaw(), false, false); + importControls(it->getRawStr(), false, false); } else if (it->is("Legs")) { - importLegLengths(it->getRaw(), false); + importLegLengths(it->getRawStr(), false); } else if (it->is("oData")){ getDI().set(*it); } else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); } } } @@ -240,7 +240,7 @@ pControl oCourse::addControl(int Id) pControl oCourse::doAddControl(int Id) { if (nControlsgetControl(Id, true); + pControl c=oe->getControl(Id, true, false); if (c==0) throw meosException("Felaktig kontroll"); Controls[nControls++]=c; @@ -394,7 +394,10 @@ bool oCourse::fillCourse(gdioutput &gdi, const string &name) int submulti = 0; wchar_t bf[256]; if (Controls[k]->isRogaining(rogaining)) { - swprintf_s(bf, 64, L"R\t%s", c.c_str()); + if (Controls[k]->getStatus() == oControl::ControlStatus::StatusRogainingRequired) + swprintf_s(bf, 64, L"R!\t%s", c.c_str()); + else + swprintf_s(bf, 64, L"R\t%s", c.c_str()); offset--; } else if (multi == 1) { @@ -433,8 +436,34 @@ vector oCourse::getControlNumbers() const { return ret; } -int oCourse::distance(const SICard &card) -{ +int oCourse::distance(const SICard& card) const { + if (card.nPunch >= 192) + return -100; + + int punches[192]; + for (int i = 0; i < card.nPunch; i++) { + punches[i] = card.Punch[i].Code; + } + return distance(punches, card.nPunch); +} + +int oCourse::distance(const oCard& card) const { + int np = card.getNumPunches(); + if (np >= 192) + return -100; + vector p; + card.getPunches(p); + int punches[192]; + int numRealPunch = 0; + for (int i = 0; i < np; i++) { + int c = p[i]->getControlNumber(); + if (c > 0) + punches[numRealPunch++] = c; + } + return distance(punches, numRealPunch); +} + +int oCourse::distance(int *punches, int numPunches) const { int matches=0; set rogaining; @@ -454,12 +483,12 @@ int oCourse::distance(const SICard &card) size_t orderIndex = 0; for (int k=0;kisRogaining(hasRogaining()) || - Controls[k]->getStatus() == oControl::StatusBad || - Controls[k]->getStatus() == oControl::StatusOptional || - Controls[k]->getStatus() == oControl::StatusBadNoTiming) + Controls[k]->getStatus() == oControl::ControlStatus::StatusBad || + Controls[k]->getStatus() == oControl::ControlStatus::StatusOptional || + Controls[k]->getStatus() == oControl::ControlStatus::StatusBadNoTiming) continue; - if (Controls[k]->getStatus() == oControl::StatusMultiple) { + if (Controls[k]->getStatus() == oControl::ControlStatus::StatusMultiple) { for (int j = 0; j < Controls[k]->nNumbers; j++) { if (allowedControls.size() <= orderIndex) allowedControls.resize(orderIndex+1); @@ -488,25 +517,25 @@ int oCourse::distance(const SICard &card) } size_t matchIndex = 0; - for (unsigned k=0; k 0) { - --allowedControls[matchIndex][card.Punch[j].Code]; + allowedControls[matchIndex].count(punches[j]) && + allowedControls[matchIndex][punches[j]] > 0) { + --allowedControls[matchIndex][punches[j]]; k = j; matches++; break; } } matchIndex++; - if (commonCode.count(card.Punch[k].Code)) + if (commonCode.count(punches[k])) matchIndex = 0; } if (matches==toMatch) { //This course is OK. Extra controls? - return card.nPunch-toMatch; //Positive return + return numPunches-toMatch; //Positive return } else { return matches-toMatch; //Negative return; @@ -583,48 +612,60 @@ pCourse oEvent::getCourse(const wstring &n) const { return 0; } -void oEvent::fillCourses(gdioutput &gdi, const string &id, bool simple) -{ +void oEvent::fillCourses(gdioutput &gdi, const string &id, bool simple) { vector< pair > d; - oe->fillCourses(d, simple); + oe->getCourses(d, L"", simple, true); gdi.addItem(id, d); } -const vector< pair > &oEvent::fillCourses(vector< pair > &out, bool simple) -{ +const vector< pair > &oEvent::getCourses(vector>& out, + const wstring& filter, bool simple, bool synchronize) { out.clear(); oCourseList::iterator it; - synchronizeList(oListId::oLCourseId); + if (synchronize) { + synchronizeList(oListId::oLCourseId); + Courses.sort(); + } - Courses.sort(); - - vector< pair> > ac; + vector> > ac; ac.reserve(Courses.size()); map id2ix; for (it=Courses.begin(); it != Courses.end(); ++it) { - if (!it->Removed){ - id2ix[it->getId()] = ac.size(); - ac.push_back(make_pair(pCourse(&*it), make_pair(pCourse(0), false))); + if (!it->Removed) { + if (!simple) + id2ix[it->getId()] = ac.size(); + ac.emplace_back(pCourse(&*it), make_pair(pCourse(0), false)); } } - for (size_t k = 0; k < ac.size(); k++) { - pCourse sh = ac[k].first->getShorterVersion().second; - if (sh != 0) { - int ix = id2ix[sh->getId()]; - if (!ac[ix].second.first) - ac[ix].second.first = ac[k].first; - else - ac[ix].second.second = true; + if (!simple) { + for (size_t k = 0; k < ac.size(); k++) { + pCourse sh = ac[k].first->getShorterVersion().second; + if (sh != 0) { + int ix = id2ix[sh->getId()]; + if (!ac[ix].second.first) + ac[ix].second.first = ac[k].first; + else + ac[ix].second.second = true; + } } } + vector filt_lc(filter.length() + 1); + wcscpy_s(filt_lc.data(), filt_lc.size(), filter.c_str()); + CharLowerBuff(filt_lc.data(), filter.length()); + int score; wstring b; for (size_t k = 0; k < ac.size(); k++) { pCourse it = ac[k].first; - if (simple) //gdi.addItem(name, it->Name, it->Id); - out.push_back(make_pair(it->Name, it->Id)); + if (!filter.empty()) { + if (!filterMatchString(it->Name, filt_lc.data(), score)) + continue; + } + + if (simple) + out.emplace_back(it->Name, it->Id); else { b = it->Name; if (ac[k].second.first) { @@ -635,19 +676,17 @@ const vector< pair > &oEvent::fillCourses(vector< pairnControls) + L")"; if (!it->getCourseProblems().empty()) b = L"[!] " + b; - out.push_back(make_pair(b, it->Id)); + out.emplace_back(b, it->Id); } } return out; } -void oCourse::setNumberMaps(int block) -{ +void oCourse::setNumberMaps(int block) { getDI().setInt("NumberMaps", block); } -int oCourse::getNumberMaps() const -{ +int oCourse::getNumberMaps() const { return getDCI().getInt("NumberMaps"); } @@ -866,9 +905,9 @@ int oCourse::calculateReduction(int overTime) const if (overTime > 0) { int method = getDCI().getInt("RReductionMethod"); if (method == 0) // Linear model - reduction = (59 + overTime * getRogainingPointsPerMinute()) / 60; + reduction = (59 * timeConstSecond + overTime * getRogainingPointsPerMinute()) / timeConstMinute; else // Time (minute) discrete model - reduction = ((59 + overTime) / 60) * getRogainingPointsPerMinute(); + reduction = ((59 * timeConstSecond + overTime) / timeConstMinute) * getRogainingPointsPerMinute(); } return reduction; } @@ -1143,6 +1182,7 @@ pCourse oCourse::getAdapetedCourse(const oCard &card, oCourse &tmpCourse, int &n } assert(checksum == 0 && loopOrder.size() == ccIndex.size()); + tmpCourse.setLocalObject(); tmpCourse.cacheDataRevision = cacheDataRevision; tmpCourse.cachedControlOrdinal.clear(); tmpCourse.cachedHasRogaining = cachedHasRogaining; diff --git a/code/oCourse.h b/code/oCourse.h index 1d81e1a..620ef96 100644 --- a/code/oCourse.h +++ b/code/oCourse.h @@ -11,7 +11,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -204,7 +204,10 @@ public: Positive return = extra controls Negative return = missing controls Zero return = exact match */ - int distance(const SICard &card); + int distance(const SICard &card) const; + int distance(const oCard &card) const; + int distance(int *punches, int numPunches) const; + bool fillCourse(gdioutput &gdi, const string &name); diff --git a/code/oDataContainer.cpp b/code/oDataContainer.cpp index 1eb2e3e..cf44214 100644 --- a/code/oDataContainer.cpp +++ b/code/oDataContainer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -181,12 +181,6 @@ const oDataInfo *oDataContainer::findVariable(const char *name) const { if (name == 0) return 0; - /*map::const_iterator it=index.find(Name); - - if (it == index.end()) - return 0; - else return &(it->second); - */ int res; if (index.lookup(hash(name), res)) { return &ordered[res]; @@ -213,6 +207,24 @@ void oDataContainer::initData(oBase *ob, int datasize) { } } +bool oDataContainer::isInt(const char *name) const { + const oDataInfo *odi = findVariable(name); + + if (!odi) + throw std::exception("oDataContainer: Variable not found."); + + return odi->Type == oDTInt; +} + +bool oDataContainer::isString(const char *name) const { + const oDataInfo *odi = findVariable(name); + + if (!odi) + throw std::exception("oDataContainer: Variable not found."); + + return odi->Type == oDTString || odi->Type == oDTStringDynamic; +} + bool oDataContainer::setInt(void *data, const char *Name, int V) { oDataInfo *odi=findVariable(Name); @@ -387,27 +399,38 @@ bool oDataContainer::setDate(void *data, const char *Name, const wstring &V) else return false;//Not modified } -const wstring &oDataContainer::getDate(const void *data, - const char *Name) const +const wstring& oDataContainer::getDate(const void* data, + const char* Name) const { - const oDataInfo *odi=findVariable(Name); + const oDataInfo* odi = findVariable(Name); if (!odi) throw std::exception("oDataContainer: Variable not found."); - if (odi->Type!=oDTInt) + if (odi->Type != oDTInt) throw std::exception("oDataContainer: Variable of wrong type."); - LPBYTE vd=LPBYTE(data)+odi->Index; - int C=*((int *)vd); + LPBYTE vd = LPBYTE(data) + odi->Index; + int C = *((int*)vd); wchar_t bf[24]; - if (C%10000!=0 || C==0) - swprintf_s(bf, L"%04d-%02d-%02d", C/10000, (C/100)%100, C%100); - else - swprintf_s(bf, L"%04d", C/10000); - - wstring &res = StringCache::getInstance().wget(); + if (odi->SubType == oISDateOrYear) { + if (C > 9999 && C % 10000 != 0) + swprintf_s(bf, L"%04d-%02d-%02d", C / 10000, (C / 100) % 100, C % 100); + else if (C > 9999) + swprintf_s(bf, L"%04d", C / 10000); + else if (C > 1900) + swprintf_s(bf, L"%04d", C ); + else + swprintf_s(bf, L""); + } + else { + if (C % 10000 != 0 || C == 0) + swprintf_s(bf, L"%04d-%02d-%02d", C / 10000, (C / 100) % 100, C % 100); + else + swprintf_s(bf, L"%04d", C / 10000); + } + wstring& res = StringCache::getInstance().wget(); res = bf; return res; } @@ -416,15 +439,29 @@ bool oDataContainer::write(const oBase *ob, xmlparser &xml) const { void *data, *oldData; vector< vector > *strptr; ob->getDataBuffers(data, oldData, strptr); - xml.startTag("oData"); - for (size_t kk = 0; kk < ordered.size(); kk++) { const oDataInfo &di=ordered[kk]; if (di.Type==oDTInt){ LPBYTE vd=LPBYTE(data)+di.Index; - if (di.SubType != oIS64) { + if (di.SubType == oISTime) { + int nr; + memcpy(&nr, vd, sizeof(int)); + xml.writeTime(di.Name, nr); + } + else if (di.SubType == oISDateOrYear) { + int nr; + memcpy(&nr, vd, sizeof(int)); + if (nr < 9999) + xml.write(di.Name, nr); + else { + char date[20]; + sprintf_s(date, "%d-%02d-%02d", nr / 10000, (nr / 100) % 100, nr % 100); + xml.write(di.Name, date); + } + } + else if (di.SubType != oIS64) { int nr; memcpy(&nr, vd, sizeof(int)); xml.write(di.Name, nr); @@ -468,18 +505,29 @@ void oDataContainer::set(oBase *ob, const xmlobject &xo) { if (odi) { if (odi->Type == oDTInt){ LPBYTE vd=LPBYTE(data)+odi->Index; - if (odi->SubType != oIS64) + if (odi->SubType == oISTime) + *((int *)vd) = it->getRelativeTime(); + else if (odi->SubType == oISDateOrYear) { + int val = convertDateYMS(it->getStr(), true); + if (val > 0) + *((int*)vd) = val; + else + *((int*)vd) = it->getInt(); + } + else if (odi->SubType != oIS64) *((int *)vd) = it->getInt(); else *((__int64 *)vd) = it->getInt64(); } else if (odi->Type == oDTString) { LPBYTE vd=LPBYTE(data)+odi->Index; - wcsncpy_s((wchar_t *)vd, odi->Size/sizeof(wchar_t), it->getw(), (odi->Size-1)/sizeof(wchar_t)); + const wchar_t* ptr = it->getWPtr(); + if (ptr) + wcsncpy_s((wchar_t *)vd, odi->Size/sizeof(wchar_t), ptr, (odi->Size-1)/sizeof(wchar_t)); } else if (odi->Type == oDTStringDynamic) { wstring &str = (*strptr)[0][odi->Index]; - str = it->getw(); + str = it->getWStr(); } } } @@ -487,17 +535,18 @@ void oDataContainer::set(oBase *ob, const xmlobject &xo) { allDataStored(ob); } -void oDataContainer::buildDataFields(gdioutput &gdi, int maxFieldSize) const +vector oDataContainer::buildDataFields(gdioutput &gdi, int maxFieldSize) const { vector fields; for (size_t k = 0; k < ordered.size(); k++) fields.push_back(ordered[k].Name); - buildDataFields(gdi, fields, maxFieldSize); + return buildDataFields(gdi, fields, maxFieldSize); } -void oDataContainer::buildDataFields(gdioutput &gdi, const vector &fields, int maxFieldSize) const +vector oDataContainer::buildDataFields(gdioutput &gdi, const vector &fields, int maxFieldSize) const { + vector out; for (size_t k=0;k::const_iterator it=index.find(fields[k]); const oDataInfo *odi = findVariable(fields[k].c_str()); @@ -510,18 +559,19 @@ void oDataContainer::buildDataFields(gdioutput &gdi, const vector &field string Id=di.Name+string("_odc"); if (di.Type==oDTInt){ - if (di.SubType == oISDate || di.SubType == oISTime) - gdi.addInput(Id, L"", 10, 0, gdi.widen(di.Description) + L":"); + if (di.SubType == oISDate || di.SubType == oISTime || di.SubType == oISDateOrYear) + out.push_back(&gdi.addInput(Id, L"", 10, 0, gdi.widen(di.Description) + L":")); else - gdi.addInput(Id, L"", 6, 0, gdi.widen(di.Description) + L":"); + out.push_back(&gdi.addInput(Id, L"", 6, 0, gdi.widen(di.Description) + L":")); } else if (di.Type==oDTString){ - gdi.addInput(Id, L"", min(di.Size+2, maxFieldSize), 0, gdi.widen(di.Description) + L":"); + out.push_back(&gdi.addInput(Id, L"", min(di.Size+2, maxFieldSize), 0, gdi.widen(di.Description) + L":")); } else if (di.Type==oDTStringDynamic){ - gdi.addInput(Id, L"", maxFieldSize, 0, gdi.widen(di.Description) + L":"); + out.push_back(&gdi.addInput(Id, L"", maxFieldSize, 0, gdi.widen(di.Description) + L":")); } } + return out; } int oDataContainer::getDataAmountMeasure(const void *data) const @@ -591,9 +641,9 @@ void oDataContainer::fillDataFields(const oBase *ob, gdioutput &gdi) const } } -bool oDataContainer::saveDataFields(oBase *ob, gdioutput &gdi) { +bool oDataContainer::saveDataFields(oBase *ob, gdioutput &gdi, std::set &modified) { void *data, *oldData; - vector< vector > *strptr; + vector> *strptr; ob->getDataBuffers(data, oldData, strptr); for (size_t kk = 0; kk < ordered.size(); kk++) { @@ -611,6 +661,16 @@ bool oDataContainer::saveDataFields(oBase *ob, gdioutput &gdi) { } else if (di.SubType == oISDate) { no = convertDateYMS(gdi.getText(Id.c_str()), true); + if (no <= 0) + no = 0; + } + else if (di.SubType == oISDateOrYear) { + no = convertDateYMS(gdi.getText(Id.c_str()), true); + if (no <= 0) { + no = gdi.getTextNo(Id.c_str()); + if (no < 1900 || no > 9999) + no = 0; + } } else if (di.SubType == oISTime) { no = convertAbsoluteTimeHMS(gdi.getText(Id.c_str()), -1); @@ -635,6 +695,7 @@ bool oDataContainer::saveDataFields(oBase *ob, gdioutput &gdi) { if (oldNo != no) { *((int *)vd)=no; ob->updateChanged(); + modified.insert(di.Name); } } else if (di.Type == oDTString) { @@ -644,6 +705,7 @@ bool oDataContainer::saveDataFields(oBase *ob, gdioutput &gdi) { if (oldS != newS) { wcsncpy_s((wchar_t *)vd, di.Size/sizeof(wchar_t), newS.c_str(), (di.Size-1)/sizeof(wchar_t)); ob->updateChanged(); + modified.insert(di.Name); } } else if (di.Type == oDTStringDynamic) { @@ -651,6 +713,7 @@ bool oDataContainer::saveDataFields(oBase *ob, gdioutput &gdi) { wstring newS = gdi.getText(Id.c_str()); if (oldS != newS) { oldS = newS; + modified.insert(di.Name); ob->updateChanged(); } } @@ -759,7 +822,7 @@ string oDataContainer::generateSQLDefinition(const std::set &exclude) co string name = di.Name; if (di.Type==oDTInt){ if (di.SubType==oIS32 || di.SubType==oISDate || di.SubType==oISCurrency || - di.SubType==oISTime || di.SubType==oISDecimal) + di.SubType==oISTime || di.SubType==oISDecimal || di.SubType == oISDateOrYear) sql+=C_INT(name); else if (di.SubType==oIS16) sql+=C_SMALLINT(name); @@ -1088,7 +1151,7 @@ void oDataContainer::buildTableCol(Table *table) } else if (di.Type == oDTInt) { bool right = di.SubType == oISCurrency; - bool numeric = di.SubType != oISDate && di.SubType != oISTime; + bool numeric = di.SubType != oISDate && di.SubType != oISTime && di.SubType != oISDateOrYear; int w; if (di.SubType == oISDecimal) w = max( (di.decimalSize+4)*10, 70); @@ -1120,7 +1183,7 @@ void oDataContainer::buildTableCol(Table *table) bool oDataContainer::formatNumber(int nr, const oDataInfo &di, wchar_t bf[64]) const { if (di.SubType == oISDate) { - if (nr>0) { + if (nr>9999 && nr < 99999999) { swprintf_s(bf, 64, L"%d-%02d-%02d", nr/(100*100), (nr/100)%100, nr%100); } else { @@ -1128,15 +1191,34 @@ bool oDataContainer::formatNumber(int nr, const oDataInfo &di, wchar_t bf[64]) c bf[1] = 0; } return true; + } + else if (di.SubType == oISDateOrYear) { + if (nr > 1900 && nr < 9999) { + swprintf_s(bf, 64, L"%d", nr); + } + else if (nr > 9999 && nr < 99999999) { + swprintf_s(bf, 64, L"%d-%02d-%02d", nr / (100 * 100), (nr / 100) % 100, nr % 100); + } + else { + bf[0] = '-'; + bf[1] = 0; + } + return true; } else if (di.SubType == oISTime) { - if (nr>0 && nr<(30*24*3600)) { - if (nr < 24*3600) - swprintf_s(bf, 64, L"%02d:%02d:%02d", nr/3600, (nr/60)%60, nr%60); + if (nr>0 && nr<(30*24*timeConstHour)) { + int cnt = 0; + if (nr < 24*timeConstHour) + cnt = swprintf_s(bf, 64, L"%02d:%02d:%02d", nr/timeConstHour, (nr/timeConstMinute)%60, (nr/timeConstSecond)%60); else { - int days = nr / (24*3600); - nr = nr % (24*3600); - swprintf_s(bf, 64, L"%d+%02d:%02d:%02d", days, nr/3600, (nr/60)%60, nr%60); + int days = nr / (24*timeConstHour); + nr = nr % (24*timeConstHour); + cnt = swprintf_s(bf, 64, L"%d+%02d:%02d:%02d", days, nr/timeConstHour, (nr/timeConstSecond)%60, (nr/timeConstSecond)%60); + } + if (timeConstSecond > 1) { + if (nr % 10 != 0) { + swprintf_s(bf + cnt, 64-cnt, L".%01d", nr % 10); + } } } else { @@ -1260,6 +1342,19 @@ pair oDataContainer::inputData(oBase *ob, int id, } else if (di.SubType == oISDate) { no = convertDateYMS(input, true); + if (no <= 0) + no = 0; + } + else if (di.SubType == oISDateOrYear) { + no = convertDateYMS(input, true); + if (no <= 0) { + no = _wtoi(input.c_str()); + if (input.length() >= 2 && (no > 0 && no < 100) || (no == 0 && input[0] == '0' && input[1] == '0')) + no = extendYear(no); + + if (no < 1900 || no > 9999) + no = 0; + } } else if (di.SubType == oISTime) { no = convertAbsoluteTimeHMS(input, -1); @@ -1276,11 +1371,25 @@ pair oDataContainer::inputData(oBase *ob, int id, no = int(di.decimalScale * val); } else if (di.SubType == oIS64) { - //__int64 no64 = _atoi64(input.c_str()); + oEvent* oe = ob->getEvent(); __int64 k64; memcpy(&k64, vd, sizeof(__int64)); + __int64 no64 = oBase::converExtIdentifierString(input); + wchar_t bf[16]; + oBase::converExtIdentifierString(no64, bf); + + if (compareStringIgnoreCase(bf, input)) { + throw meosException(L"Cannot represent ID X#" + input); + } + + if (no64 != k64 && !oe->hasWarnedModifiedId()) { + if (oe->gdiBase().askOkCancel(L"warn:changeid") == gdioutput::AskAnswer::AnswerCancel) + throw meosCancel(); + oe->hasWarnedModifiedId(true); + } + memcpy(vd, &no64, sizeof(__int64)); __int64 out64 = no64; if (k64 != no64) { diff --git a/code/oDataContainer.h b/code/oDataContainer.h index fd78060..1f262c9 100644 --- a/code/oDataContainer.h +++ b/code/oDataContainer.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -29,7 +29,9 @@ #include "oBase.h" #include "inthashmap.h" + class Table; +class InputInfo; enum CellType; class oDataDefiner { @@ -103,7 +105,6 @@ protected: int dataPointer; size_t stringIndexPointer; size_t stringArrayIndexPointer; - inthashmap index; vector ordered; @@ -131,7 +132,10 @@ protected: static string C_STRING(const string & name, int len); static string SQL_quote(const wchar_t *in); public: - enum oIntSize{oISDecimal = 28, oISTime = 29, oISCurrency = 30, oISDate = 31, oIS64=64, oIS32=32, oIS16=16, oIS8=8, oIS16U=17, oIS8U=9}; + enum oIntSize{oISDecimal = 28, oISTime = 29, oISCurrency = 30, + oISDate = 31, oISDateOrYear = 27, oIS64=64, + oIS32=32, oIS16=16, oIS8=8, oIS16U=17, oIS8U=9}; + enum oStringSubType {oSSString = 0, oSSEnum = 1}; string generateSQLDefinition(const std::set &exclude) const; string generateSQLDefinition() const { @@ -162,6 +166,9 @@ public: void initData(oBase *ob, int datasize); + bool isInt(const char *name) const; + bool isString(const char *name) const; + bool setInt(void *data, const char *Name, int V); int getInt(const void *data, const char *Name) const; @@ -181,11 +188,11 @@ public: // Get a measure of how much data is stored in this record. int getDataAmountMeasure(const void *data) const; - void buildDataFields(gdioutput &gdi, int maxFieldSize) const; - void buildDataFields(gdioutput &gdi, const vector &fields, int maxFieldSize) const; + vector buildDataFields(gdioutput &gdi, int maxFieldSize) const; + vector buildDataFields(gdioutput &gdi, const vector &fields, int maxFieldSize) const; void fillDataFields(const oBase *ob, gdioutput &gdi) const; - bool saveDataFields(oBase *ob, gdioutput &gdi); + bool saveDataFields(oBase *ob, gdioutput &gdi, std::set &modified); int fillTableCol(const oBase &owner, Table &table, bool canEdit) const; void buildTableCol(Table *table); @@ -210,20 +217,23 @@ private: oDataContainer *oDC; oBase *oB; public: - + bool merge(const oBase &source, const oBase *base) { return oDC->merge(*oB, source, base); } - inline bool setInt(const char *Name, int Value) - { - if (oDC->setInt(Data, Name, Value)){ + inline bool setInt(const char *name, int value) { + if (oDC->setInt(Data, name, value)) { oB->updateChanged(); return true; } else return false; } + inline bool setInt(const string &name, int value) { + return setInt(name.c_str(), value); + } + inline bool setInt64(const char *Name, __int64 Value) { if (oDC->setInt64(Data, Name, Value)){ @@ -233,26 +243,47 @@ public: else return false; } - inline int getInt(const char *Name) const - {return oDC->getInt(Data, Name);} + bool isInt(const string &name) const { + return oDC->isInt(name.c_str()); + } + + bool isString(const string &name) const { + return oDC->isString(name.c_str()); + } + + inline int getInt(const char *Name) const { + return oDC->getInt(Data, Name); + } + + inline int getInt(const string &name) const { + return oDC->getInt(Data, name.c_str()); + } inline __int64 getInt64(const char *Name) const {return oDC->getInt64(Data, Name);} - inline bool setStringNoUpdate(const char *Name, const wstring &Value) - {return oDC->setString(oB, Name, Value);} + inline bool setStringNoUpdate(const char *name, const wstring &value) + {return oDC->setString(oB, name, value);} - inline bool setString(const char *Name, const wstring &Value) - { - if (oDC->setString(oB, Name, Value)){ + inline bool setString(const char *name, const wstring &value) { + if (oDC->setString(oB, name, value)) { oB->updateChanged(); return true; } else return false; } - inline const wstring &getString(const char *Name) const - {return oDC->getString(oB, Name);} + inline bool setString(const string &name, const wstring &value) { + return setString(name.c_str(), value); + } + + inline const wstring &getString(const char *name) const { + return oDC->getString(oB, name); + } + + inline const wstring &getString(const string &name) const { + return oDC->getString(oB, name.c_str()); + } inline const wstring &formatString(const oBase *oB, const char *name) const { return oDC->formatString(oB, name); @@ -270,17 +301,17 @@ public: inline const wstring &getDate(const char *Name) const {return oDC->getDate(Data, Name);} - inline void buildDataFields(gdioutput &gdi, int maxFieldSize) const - {oDC->buildDataFields(gdi, maxFieldSize);} + inline vector buildDataFields(gdioutput &gdi, int maxFieldSize) const + {return oDC->buildDataFields(gdi, maxFieldSize);} - inline void buildDataFields(gdioutput &gdi, const vector &fields, int maxFieldSize) const - {oDC->buildDataFields(gdi, fields, maxFieldSize);} + inline vector buildDataFields(gdioutput &gdi, const vector &fields, int maxFieldSize) const + {return oDC->buildDataFields(gdi, fields, maxFieldSize);} inline void fillDataFields(gdioutput &gdi) const {oDC->fillDataFields(oB, gdi);} - inline bool saveDataFields(gdioutput &gdi) - {return oDC->saveDataFields(oB, gdi);} + inline bool saveDataFields(gdioutput &gdi, std::set &modified) + {return oDC->saveDataFields(oB, gdi, modified);} inline string generateSQLDefinition() const {return oDC->generateSQLDefinition(std::set());} @@ -337,8 +368,21 @@ private: const oBase *oB; public: - inline int getInt(const char *Name) const - {return oDC->getInt(Data, Name);} + bool isInt(const string &name) const { + return oDC->isInt(name.c_str()); + } + + bool isString(const string &name) const { + return oDC->isString(name.c_str()); + } + + inline int getInt(const char *Name) const { + return oDC->getInt(Data, Name); + } + + inline int getInt(const string &name) const { + return oDC->getInt(Data, name.c_str()); + } inline __int64 getInt64(const char *Name) const {return oDC->getInt64(Data, Name);} @@ -346,6 +390,10 @@ public: inline const wstring &getString(const char *Name) const {return oDC->getString(oB, Name);} + inline const wstring &getString(const string &name) const { + return oDC->getString(oB, name.c_str()); + } + inline const wstring &formatString(const oBase *oB, const char *name) const { return oDC->formatString(oB, name); } @@ -353,15 +401,9 @@ public: inline const wstring &getDate(const char *Name) const {return oDC->getDate(Data, Name);} - inline int getInt(const string &name) const - {return oDC->getInt(Data, name.c_str());} - inline __int64 getInt64(const string &name) const {return oDC->getInt64(Data, name.c_str());} - inline const wstring &getString(const string &name) const - {return oDC->getString(oB, name.c_str());} - inline const wstring &getDate(const string &name) const {return oDC->getDate(Data, name.c_str());} diff --git a/code/oEvent.cpp b/code/oEvent.cpp index b7ec586..39e3fe9 100644 --- a/code/oEvent.cpp +++ b/code/oEvent.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -54,6 +54,9 @@ #include "oEventDraw.h" #include "MeosSQL.h" #include "TabAuto.h" +#include "TabSI.h" +#include "binencoder.h" +#include "image.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// @@ -64,8 +67,33 @@ #include #include "Table.h" +extern Image image; + //Version of database -int oEvent::dbVersion = 87; +int oEvent::dbVersion = 90; + +bool oEvent::useSubSecond() const { + if (useSubsecondsVersion == dataRevision) + return useSubSecondsCache; + + auto check = [](int rt) { + return rt > 0 && (rt % timeConstSecond) != 0; + }; + + for (auto &r : Runners) { + if (!r.isRemoved()) { + if (check(r.getFinishTime()) || check(r.getStartTime())) { + useSubSecondsCache = true; + useSubsecondsVersion = dataRevision; + return true; + } + } + } + + useSubSecondsCache = false; + useSubsecondsVersion = dataRevision; + return false; +} class RelativeTimeFormatter : public oDataDefiner { string name; @@ -320,6 +348,73 @@ public: } }; +class SplitPrintListFormatter : public oDataDefiner { +public: + + const wstring& formatData(const oBase* obj) const override { + wstring listId = obj->getDCI().getString("SplitPrint"); + if (listId.empty()) { + return lang.tl("Standard"); + } + try { + const MetaListContainer& lc = obj->getEvent()->getListContainer(); + EStdListType type = lc.getCodeFromUnqiueId(gdioutput::narrow(listId)); + const MetaList& ml = lc.getList(type); + return ml.getListName(); + } + catch (meosException&) { + return _EmptyWString; + } + } + + void fillInput(const oBase* obj, vector>& out, size_t& selected) const { + oEvent* oe = obj->getEvent(); + oe->getListContainer().getLists(out, false, false, false, true); + out.insert(out.begin(), make_pair(lang.tl("Standard"), -10)); + wstring listId = obj->getDCI().getString("SplitPrint"); + EStdListType type = oe->getListContainer().getCodeFromUnqiueId(gdioutput::narrow(listId)); + if (type == EStdListType::EStdNone) + selected = -10; + else { + for (auto& t : out) { + if (type == oe->getListContainer().getType(t.second)) { + selected = t.second; + break; + } + } + } + } + + CellType getCellType() const final { + return CellType::cellSelection; + } + + pair setData(oBase* obj, const wstring& input, wstring& output, int inputId) const override { + if (inputId == -10) + obj->getDI().setString("SplitPrint", L""); + else { + EStdListType type = obj->getEvent()->getListContainer().getType(inputId); + string id = obj->getEvent()->getListContainer().getUniqueId(type); + obj->getDI().setString("SplitPrint", gdioutput::widen(id)); + } + + output = formatData(obj); + return make_pair(0, false); + } + + int addTableColumn(Table* table, const string& description, int minWidth) const override { + oEvent* oe = table->getEvent(); + vector> out; + oe->getListContainer().getLists(out, false, false, false, true); + + for (auto& t : out) { + minWidth = max(minWidth, t.first.size() * 6); + } + + return table->addColumn(description, max(minWidth, 90), false, true); + } +}; + oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) { readOnly = false; @@ -357,7 +452,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) swprintf_s(bf, 64, L"%d-%02d-%02d", st.wYear, st.wMonth, st.wDay); Date=bf; - ZeroTime=st.wHour*3600; + ZeroTime=st.wHour*timeConstHour; oe=this; runnerDB = make_shared(this); @@ -384,7 +479,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oEventData->addVariableCurrency("CardFee", "Brickhyra"); oEventData->addVariableCurrency("EliteFee", "Elitavgift"); oEventData->addVariableCurrency("EntryFee", "Normalavgift"); - oEventData->addVariableCurrency("YouthFee", "Ungdomsavgift"); + oEventData->addVariableCurrency("YouthFee", "Reducerad avgift"); oEventData->addVariableInt("YouthAge", oDataContainer::oIS8U, "Åldersgräns ungdom"); oEventData->addVariableInt("SeniorAge", oDataContainer::oIS8U, "Åldersgräns äldre"); @@ -437,13 +532,14 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oEventData->addVariableInt("NumStages", oDataContainer::oIS8, "Antal etapper"); oEventData->addVariableInt("BibGap", oDataContainer::oIS8U, "Nummerlappshopp"); oEventData->addVariableInt("LongTimes", oDataContainer::oIS8U, "Långa tider"); + oEventData->addVariableInt("SubSeconds", oDataContainer::oIS8U, "Tiondelar"); oEventData->addVariableString("PayModes", "Betalsätt"); oEventData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring"); oEventData->addVariableDate("InvoiceDate", "Fakturadatum"); oEventData->addVariableString("StartGroups", "Startgrupper"); oEventData->addVariableString("MergeTag", 12, "Tag"); oEventData->addVariableString("MergeInfo", "MergeInfo"); - + oEventData->addVariableString("SplitPrint", 40, "Sträcktidslista"); // Id from MetaListContainer::getUniqueId oEventData->initData(this, dataSize); oClubData=new oDataContainer(oClub::dataSize); @@ -477,12 +573,12 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oRunnerData->addVariableCurrency("Paid", "Betalat"); oRunnerData->addVariableInt("PayMode", oDataContainer::oIS8U, "Betalsätt", make_shared()); oRunnerData->addVariableCurrency("Taxable", "Skattad avgift"); - oRunnerData->addVariableInt("BirthYear", oDataContainer::oIS32, "Födelseår"); + oRunnerData->addVariableInt("BirthYear", oDataContainer::oISDateOrYear, "RunnerBirthDate"); oRunnerData->addVariableString("Bib", 8, "Nummerlapp").zeroSortPadding = 5; oRunnerData->addVariableInt("Rank", oDataContainer::oIS16U, "Ranking"); oRunnerData->addVariableDate("EntryDate", "Anm. datum"); - oRunnerData->addVariableInt("EntryTime", oDataContainer::oIS32, "Anm. tid", make_shared("EntryTime")); + oRunnerData->addVariableInt("EntryTime", oDataContainer::oISTime, "Anm. tid", make_shared("EntryTime")); vector< pair > sex; sex.push_back(make_pair(L"M", L"Man")); @@ -498,7 +594,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oRunnerData->addVariableInt("RaceId", oDataContainer::oIS32, "Lopp-id", make_shared()); - oRunnerData->addVariableInt("TimeAdjust", oDataContainer::oIS16, "Tidsjustering"); + oRunnerData->addVariableInt("TimeAdjust", oDataContainer::oISTime, "Tidsjustering"); oRunnerData->addVariableInt("PointAdjust", oDataContainer::oIS32, "Poängjustering"); oRunnerData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring"); oRunnerData->addVariableInt("Shorten", oDataContainer::oIS8U, "Avkortning"); @@ -511,8 +607,8 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oRunnerData->addVariableInt("Family", oDataContainer::oIS32, "Familj"); oControlData=new oDataContainer(oControl::dataSize); - oControlData->addVariableInt("TimeAdjust", oDataContainer::oIS32, "Tidsjustering"); - oControlData->addVariableInt("MinTime", oDataContainer::oIS32, "Minitid"); + oControlData->addVariableInt("TimeAdjust", oDataContainer::oISTime, "Tidsjustering"); + oControlData->addVariableInt("MinTime", oDataContainer::oISTime, "Minitid"); oControlData->addVariableDecimal("xpos", "x", 1); oControlData->addVariableDecimal("ypos", "y", 1); oControlData->addVariableDecimal("latcrd", "Latitud", 6); @@ -520,13 +616,14 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oControlData->addVariableInt("Rogaining", oDataContainer::oIS32, "Poäng"); oControlData->addVariableInt("Radio", oDataContainer::oIS8U, "Radio"); + oControlData->addVariableInt("Unit", oDataContainer::oIS16U, "Enhet"); - oCourseData=new oDataContainer(oCourse::dataSize); + oCourseData = new oDataContainer(oCourse::dataSize); oCourseData->addVariableInt("NumberMaps", oDataContainer::oIS16, "Kartor"); oCourseData->addVariableString("StartName", 16, "Start"); oCourseData->addVariableInt("Climb", oDataContainer::oIS16, "Stigning"); oCourseData->addVariableInt("RPointLimit", oDataContainer::oIS32, "Poänggräns"); - oCourseData->addVariableInt("RTimeLimit", oDataContainer::oIS32, "Tidsgräns"); + oCourseData->addVariableInt("RTimeLimit", oDataContainer::oISTime, "Tidsgräns"); oCourseData->addVariableInt("RReduction", oDataContainer::oIS32, "Poängreduktion"); oCourseData->addVariableInt("RReductionMethod", oDataContainer::oIS8U, "Reduktionsmetod"); @@ -536,7 +633,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oCourseData->addVariableInt("CControl", oDataContainer::oIS16U, "Varvningskontroll"); //Common control index oCourseData->addVariableInt("Shorten", oDataContainer::oIS32, "Avkortning"); - oClassData=new oDataContainer(oClass::dataSize); + oClassData = new oDataContainer(oClass::dataSize); oClassData->addVariableInt("ExtId", oDataContainer::oIS64, "Externt Id"); oClassData->addVariableString("LongName", 32, "Långt namn"); oClassData->addVariableInt("LowAge", oDataContainer::oIS8U, "Undre ålder"); @@ -559,8 +656,8 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oClassData->addVariableInt("FreeStart", oDataContainer::oIS8U, "Fri starttid", make_shared("FreeStart")); oClassData->addVariableInt("IgnoreStart", oDataContainer::oIS8U, "Ej startstämpling", make_shared("IgnoreStart")); - oClassData->addVariableInt("FirstStart", oDataContainer::oIS32, "Första start", make_shared("FirstStart")); - oClassData->addVariableInt("StartInterval", oDataContainer::oIS16, "Intervall", make_shared("StartInterval")); + oClassData->addVariableInt("FirstStart", oDataContainer::oISTime, "Första start", make_shared("FirstStart")); + oClassData->addVariableInt("StartInterval", oDataContainer::oISTime, "Intervall", make_shared("StartInterval")); oClassData->addVariableInt("Vacant", oDataContainer::oIS8U, "Vakanser"); oClassData->addVariableInt("Reserved", oDataContainer::oIS16U, "Extraplatser"); @@ -591,6 +688,7 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oClassData->addVariableInt("NumberMaps", oDataContainer::oIS16, "Kartor"); oClassData->addVariableString("Result", 24, "Result module", make_shared()); oClassData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring", make_shared()); + oClassData->addVariableString("SplitPrint", 40, "Sträcktidslista", make_shared()); oTeamData = new oDataContainer(oTeam::dataSize); oTeamData->addVariableCurrency("Fee", "Anm. avgift"); @@ -598,14 +696,14 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi) oTeamData->addVariableInt("PayMode", oDataContainer::oIS8U, "Betalsätt"); oTeamData->addVariableCurrency("Taxable", "Skattad avgift"); oTeamData->addVariableDate("EntryDate", "Anm. datum"); - oTeamData->addVariableInt("EntryTime", oDataContainer::oIS32, "Anm. tid", make_shared("EntryTime")); + oTeamData->addVariableInt("EntryTime", oDataContainer::oISTime, "Anm. tid", make_shared("EntryTime")); oTeamData->addVariableString("Nationality", 3, "Nationalitet"); oTeamData->addVariableString("Country", 23, "Land"); oTeamData->addVariableString("Bib", 8, "Nummerlapp").zeroSortPadding = 5; oTeamData->addVariableInt("ExtId", oDataContainer::oIS64, "Externt Id"); oTeamData->addVariableInt("Priority", oDataContainer::oIS8U, "Prioritering"); oTeamData->addVariableInt("SortIndex", oDataContainer::oIS16, "Sortering"); - oTeamData->addVariableInt("TimeAdjust", oDataContainer::oIS16, "Tidsjustering"); + oTeamData->addVariableInt("TimeAdjust", oDataContainer::oISTime, "Tidsjustering"); oTeamData->addVariableInt("PointAdjust", oDataContainer::oIS32, "Poängjustering"); oTeamData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring"); oTeamData->addVariableInt("EntrySource", oDataContainer::oIS32, "Källa"); @@ -706,6 +804,8 @@ void oEvent::listProperties(bool userProps, vector< pair > b.insert("DrawInterlace"); b.insert("PlaySound"); b.insert("showheader"); + b.insert("AutoTieRent"); + b.insert("ExpWithRaceNo"); // Integers i.insert("YouthFee"); @@ -772,16 +872,15 @@ int oEvent::getNextControlNumber() const return c; } -pControl oEvent::addControl(const oControl &oc) -{ +pControl oEvent::addControl(const oControl &oc) { if (oc.Id<=0) - return 0; - - if (getControl(oc.Id, false)) - return 0; - - qFreeControlId = max (qFreeControlId, Id); + return nullptr; + if (&oc != tmpControl.get()) { + if (getControl(oc.Id, false, false)) + return nullptr; + } + qFreeControlId = max(qFreeControlId, Id); Controls.push_back(oc); oe->Controls.back().addToEvent(this, &oc); @@ -795,33 +894,6 @@ DirectSocket &oEvent::getDirectSocket() { return *directSocket; } -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 && !it->isRemoved()) - return pControl(&*it); - } - - if (!create || Id<=0) - return nullptr; - - //Not found. Auto add... - return addControl(Id, Id, L""); -} - bool oEvent::writeControls(xmlparser &xml) { oControlList::iterator it; @@ -1040,7 +1112,7 @@ bool oEvent::save() oldAge*=2; if (k==maxBackup-3) - oldAge = 24*3600; // Allow a few old copies + oldAge = 24*timeConstSecPerHour; // Allow a few old copies } else { toDelete = k; // File does not exist. No file need be deleted @@ -1062,13 +1134,13 @@ bool oEvent::save() } bool res; if (finalRenameTarget.empty()) { - res = save(CurrentFile); + res = save(CurrentFile, true); if (!(hasDBConnection() || hasPendingDBConnection)) openFileLock->lockFile(CurrentFile); } else { wstring tmpName = wstring(CurrentFile) + L".~tmp"; - res = save(tmpName); + res = save(tmpName, true); if (res) { openFileLock->unlockFile(); _wrename(CurrentFile, finalRenameTarget.c_str()); @@ -1082,8 +1154,8 @@ bool oEvent::save() return res; } -bool oEvent::save(const wstring &fileIn) { - if (gdibase.isTest()) +bool oEvent::save(const wstring &fileIn, bool isAutoSave) { + if (isAutoSave && gdibase.isTest()) return true; const wchar_t *file = fileIn.c_str(); @@ -1097,7 +1169,7 @@ bool oEvent::save(const wstring &fileIn) { xml.startTag("meosdata", "version", getMajorVersion()); xml.write("Name", Name); xml.write("Date", Date); - xml.write("ZeroTime", itos(ZeroTime)); + xml.writeTime("ZeroTime", ZeroTime); xml.write("NameId", currentNameId); xml.write("Annotation", Annotation); xml.write("Id", Id); @@ -1151,6 +1223,30 @@ bool oEvent::save(const wstring &fileIn) { listContainer->save(MetaListContainer::ExternalList, xml, this); xml.endTag(); + set img; + listContainer->getUsedImages(img); + if (!img.empty()) { + xml.startTag("Images"); + Encoder92 binEncoder; + for (auto imgId : img) { + if (!image.hasImage(imgId)) + loadImage(imgId); + + if (!image.hasImage(imgId)) + continue; + + wstring fileName = image.getFileName(imgId); + auto rawData = image.getRawData(imgId); + string encoded; + binEncoder.encode92(rawData, encoded); + vector> props; + props.emplace_back("filename", fileName); + props.emplace_back("id", itow(imgId)); + xml.writeAscii("Image", props, encoded); + } + xml.endTag(); + } + if (machineContainer) { xml.startTag("Machines"); machineContainer->save(xml); @@ -1210,7 +1306,11 @@ bool oEvent::open(int id) if (it->Server.empty()) { if (id == it->Id) { CompetitionInfo ci=*it; //Take copy - return open(ci.FullPath.c_str(), false, false); + if (open(ci.FullPath.c_str(), false, false, false)) { + supportSubSeconds(supportSubSeconds()); + return true; + } + return false; } } else if (!it->Server.empty()) { @@ -1218,6 +1318,7 @@ bool oEvent::open(int id) CompetitionInfo ci=*it; //Take copy if (readSynchronize(ci)) { getMergeTag(); + supportSubSeconds(supportSubSeconds()); return true; } return false; @@ -1276,8 +1377,8 @@ namespace { } } -bool oEvent::open(const wstring &file, bool Import, bool forMerge) { - if (!Import) +bool oEvent::open(const wstring &file, bool doImport, bool forMerge, bool forceNew) { + if (!doImport) openFileLock->lockFile(file); xmlparser xml; @@ -1303,7 +1404,7 @@ bool oEvent::open(const wstring &file, bool Import, bool forMerge) { xmlattrib ver = xml.getObject(0).getAttrib("version"); if (ver) { - wstring vs = ver.wget(); + wstring vs = ver.getWStr(); if (vs > getMajorVersion()) { // Tävlingen är skapad i MeOS X. Data kan gå förlorad om du öppnar tävlingen.\n\nVill du fortsätta? bool cont = gdibase.ask(L"warn:opennewversion#" + vs); @@ -1314,8 +1415,8 @@ bool oEvent::open(const wstring &file, bool Import, bool forMerge) { toc("parse"); //This generates a new file name newCompetition(L"-"); - - if (!Import) { + auto newNameId = currentNameId; + if (!doImport) { wcscpy_s(CurrentFile, MAX_PATH, file.c_str()); //Keep new file name, if imported wchar_t CurrentNameId[64]; @@ -1331,10 +1432,13 @@ bool oEvent::open(const wstring &file, bool Import, bool forMerge) { currentNameId = CurrentNameId; } bool res = open(xml); - if (res && !Import) + if (res && !doImport) openFileLock->lockFile(file); - if (Import) { + if (forceNew) { + newNameId.swap(currentNameId); + } + else if (doImport && !oe->gdiBase().isTest()) { for (auto &cmp : cinfo) { if (cmp.NameId == currentNameId) { if (!gdibase.ask(L"ask:importcopy#" + cmp.Name + L", " + cmp.Date)) { @@ -1346,15 +1450,69 @@ bool oEvent::open(const wstring &file, bool Import, bool forMerge) { } } - getMergeTag(Import && !forMerge); + getMergeTag(doImport && !forMerge); - if (Import && !forMerge) { + if (forceNew) { + getDI().setString("ImportStamp", L""); + } + else if (doImport && !forMerge) { getDI().setString("ImportStamp", gdibase.widen(getLastModified())); } return res; } +void oEvent::clearData(bool runnerTeam, bool courses) { + Cards.clear(); + + list op; + for (auto& p : punches) { + if (p.isHiredCard()) + op.push_back(p); + } + punchIndex.clear(); + punches.clear(); + punches.swap(op); + + if (courses) { + Controls.clear(); + Courses.clear(); + } + + if (runnerTeam) { + Clubs.clear(); + Runners.clear(); + Teams.clear(); + } + + if (courses) { + for (auto& c : Classes) { + c.setCourse(nullptr); + for (auto& mc : c.MultiCourse) + mc.clear(); + } + + for (auto& r : Runners) + r.Course = nullptr; + } + + for (auto& r : Runners) { + r.Card = nullptr; + r.setFinishTime(0); + r.setStatus(StatusUnknown, true, oBase::ChangeType::Update, false); + } + + clubIdIndex.clear(); + runnerById.clear(); + teamById.clear(); + cardToRunnerHash.reset(); + classIdToRunnerHash.reset(); + classIdToRunnerHash.reset(); + readPunchHash.clear(); + courseIdIndex.clear(); + updateFreeId(); +} + void oEvent::restoreBackup() { wstring cfile = wstring(CurrentFile) + L".meos"; @@ -1367,23 +1525,23 @@ bool oEvent::open(const xmlparser &xml) { xo = xml.getObject("Date"); if (xo) { - wstring fDate = xo.getw(); + wstring fDate = xo.getWStr(); if (convertDateYMS(fDate, true) > 0) Date = fDate; } Name.clear(); xo = xml.getObject("Name"); - if (xo) Name=xo.getw(); + if (xo) Name=xo.getWStr(); if (Name.empty()) { Name = lang.tl("Ny tävling"); } xo = xml.getObject("Annotation"); - if (xo) Annotation = xo.getw(); + if (xo) Annotation = xo.getWStr(); xo=xml.getObject("ZeroTime"); - if (xo) ZeroTime=xo.getInt(); + if (xo) ZeroTime=xo.getRelativeTime(); xo=xml.getObject("Id"); if (xo) Id=xo.getInt(); @@ -1397,7 +1555,7 @@ bool oEvent::open(const xmlparser &xml) { xo = xml.getObject("NameId"); if (xo) - currentNameId = xo.getw(); + currentNameId = xo.getWStr(); toc("event"); //Get controls @@ -1546,7 +1704,7 @@ bool oEvent::open(const xmlparser &xml) { try { for(it=xl.begin(); it != xl.end(); ++it){ if (it->is("Punch")){ - oFreePunch p(this, 0, 0, 0); + oFreePunch p(this, 0, 0, 0, 0); p.Set(&*it); addFreePunch(p); } @@ -1581,7 +1739,7 @@ bool oEvent::open(const xmlparser &xml) { toc("card"); xo=xml.getObject("Updated"); - if (xo) Modified.setStamp(xo.getRaw()); + if (xo) Modified.setStamp(xo.getRawStr()); adjustTeamMultiRunners(0); updateFreeId(); @@ -1609,7 +1767,34 @@ bool oEvent::open(const xmlparser &xml) { getMeOSFeatures().deserialize(getDCI().getString("Features"), *this); + xmlobject xImage = xml.getObject("Images"); + if (xImage) { + xmlList imgs; + xImage.getObjects("Image", imgs); + Encoder92 binEncoder; + vector bytes; + for (auto& img : imgs) { + try { + wstring fileName, id; + img.getObjectString("filename", fileName); + img.getObjectString("id", id); + uint64_t imgId = _wcstoui64(id.c_str(), nullptr, 10); + string data = img.getRawStr(); + binEncoder.decode92(data, bytes); + image.provideFromMemory(imgId, fileName, bytes); + } + catch (const meosException& ex) { + if (err.empty()) + err = ex.wwhat(); + } + catch (const std::exception& ex) { + if (err.empty()) + err = gdibase.widen(ex.what()); + } + } + } + try { xmlobject xMachine = xml.getObject("Machines"); if (xMachine) { @@ -1774,19 +1959,23 @@ void oEvent::updateRunnerDatabase() return; if (useRunnerDb()) { - oRunnerList::iterator it; map clubIdMap; - for (it = Runners.begin(); it != Runners.end(); ++it) { + for (auto it = Runners.begin(); it != Runners.end(); ++it) { + if (it->isRemoved()) + continue; + if (it->hasFlag(oAbstractRunner::TransferFlags::FlagNoDatabase)) + continue; + if (it->Card && it->Card->cardNo == it->cardNumber && it->getDI().getInt("CardFee") == 0 && it->Card->getNumPunches() > 5) - updateRunnerDatabase(&*it, clubIdMap); + updateRunnerDatabase(&*it, clubIdMap); } runnerDB->refreshTables(); } if (listContainer) { for (int k = 0; k < listContainer->getNumLists(); k++) { if (listContainer->isExternal(k)) { - MetaList &ml = listContainer->getList(k); + MetaList& ml = listContainer->getList(k); wstring uid = gdibase.widen(ml.getUniqueId()) + L".meoslist"; wchar_t file[260]; getUserFile(file, uid.c_str()); @@ -1794,7 +1983,8 @@ void oEvent::updateRunnerDatabase() ml.save(file, this); } } - }vector>> freeMod; + } + vector>> freeMod; listContainer->getFreeResultModules(freeMod); for (size_t k = 0; k < freeMod.size(); k++) { @@ -1893,11 +2083,14 @@ void oEvent::autoRemoveTeam(pRunner pr) } pRunner oEvent::addRunner(const wstring &name, int clubId, int classId, - int cardNo, int birthYear, bool autoAdd) + int cardNo, const wstring &birthDate, bool autoAdd) { - if (birthYear != 0) - birthYear = extendYear(birthYear); - + int birthYear = 0; + if (!birthDate.empty()) { + int numY = _wtoi(birthDate.c_str()); + if (numY > 0 || (numY==0 && birthDate[0]=='0')) + birthYear = extendYear(numY); + } pRunner db_r = oe->dbLookUpByCard(cardNo); if (db_r && !db_r->matchName(name)) @@ -1915,7 +2108,7 @@ pRunner oEvent::addRunner(const wstring &name, int clubId, int classId, if (cardNo>0) db_r->cardNumber = cardNo; if (birthYear>0) - db_r->setBirthYear(birthYear); + db_r->setBirthDate(birthDate); return addRunnerFromDB(db_r, classId, autoAdd); } oRunner r(this); @@ -1927,7 +2120,7 @@ pRunner oEvent::addRunner(const wstring &name, int clubId, int classId, if (cardNo>0) r.cardNumber = cardNo; if (birthYear>0) - r.setBirthYear(birthYear); + r.setBirthDate(birthDate); pRunner pr = addRunner(r, true); if (pr->getDI().getInt("EntryDate") == 0 && !pr->isVacant()) { @@ -1948,14 +2141,19 @@ pRunner oEvent::addRunner(const wstring &name, int clubId, int classId, } pRunner oEvent::addRunner(const wstring &pname, const wstring &pclub, int classId, - int cardNo, int birthYear, bool autoAdd) + int cardNo, const wstring &birthDate, bool autoAdd) { if (!pclub.empty() || getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) { - pClub club = getClubCreate(0, pclub); - return addRunner(pname, club->getId(), classId, cardNo, birthYear, autoAdd); + + int clubId = 0; + if (pclub.empty()) + clubId = getVacantClubIfExist(true); + else + clubId = getClubCreate(0, pclub)->getId(); + return addRunner(pname, clubId, classId, cardNo, birthDate, autoAdd); } else - return addRunner(pname, 0, classId, cardNo, birthYear, autoAdd); + return addRunner(pname, 0, classId, cardNo, birthDate, autoAdd); } pRunner oEvent::addRunnerFromDB(const pRunner db_r, @@ -2044,7 +2242,7 @@ pRunner oEvent::addRunner(const oRunner &r, bool updateStartNo) { } pRunner oEvent::addRunnerVacant(int classId) { - pRunner r=addRunner(lang.tl(L"Vakant"), getVacantClub(false), classId, 0,0, true); + pRunner r = addRunner(lang.tl(L"Vakant"), getVacantClub(false), classId, 0, L"", true); if (r) { r->apply(ChangeType::Update, nullptr); r->synchronize(true); @@ -2424,16 +2622,16 @@ void oEvent::setDate(const wstring &m, bool manualSet) } } -const wstring &oEvent::getAbsTime(DWORD time) const { +const wstring &oEvent::getAbsTime(DWORD time, SubSecond mode) const { DWORD t = ZeroTime + time; if (int(t)<0) t = 0; - int days = time/(3600*24); + int days = time/(timeConstHour*24); if (days <= 0) - return formatTimeHMS(t % (24*3600)); + return formatTimeHMS(t % (24*timeConstHour), mode); else { wstring &res = StringCache::getInstance().wget(); - res = itow(days) + L"D " + formatTimeHMS(t % (24*3600)); + res = itow(days) + L"D " + formatTimeHMS(t % (24*timeConstHour), mode); return res; } } @@ -2457,38 +2655,50 @@ wstring oEvent::getAbsDateTimeISO(DWORD time, bool includeDate, bool useGMT) con } else { int extraDay; - if (useGMT) { - int offset = ::getTimeZoneInfo(Date); + int offset = ::getTimeZoneInfo(Date) * timeConstSecond; t += offset; if (t < 0) { extraDay = -1; - t += 3600 * 24; + t += timeConstHour * 24; } else { - extraDay = t / (3600*24); + extraDay = t / (timeConstHour*24); } wchar_t bf[64]; - swprintf_s(bf, L"%02d:%02d:%02dZ", (t/3600)%24, (t/60)%60, t%60); + swprintf_s(bf, L"%02d:%02d:%02d", (t/timeConstHour)%24, (t/timeConstMinute)%60, (t/timeConstSecond)%60); timeS = bf; } else { wchar_t bf[64]; - extraDay = t / (3600*24); - swprintf_s(bf, L"%02d:%02d:%02d", (t/3600)%24, (t/60)%60, t%60); - timeS = bf + getTimeZoneString(); + extraDay = t / (timeConstHour*24); + swprintf_s(bf, L"%02d:%02d:%02d", (t/timeConstHour)%24, (t/timeConstMinute)%60, (t/timeConstSecond)%60); + timeS = bf; } - if (extraDay == 0 ) { - dateS = Date; + if (timeConstSecond > 1 && useSubSecond()) { + wchar_t bf[64]; + swprintf_s(bf, L".%03d", (t%10) * (1000/timeConstSecond)); + timeS += bf; } - else { - SYSTEMTIME st; - convertDateYMS(Date, st, false); - __int64 sec = SystemTimeToInt64Second(st); - sec = sec + (extraDay * 3600 * 24); - st = Int64SecondToSystemTime(sec); - dateS = convertSystemDate(st); + + if (useGMT) + timeS += L"Z"; + else + timeS += getTimeZoneString(); + + if (includeDate) { + if (extraDay == 0) { + dateS = Date; + } + else { + SYSTEMTIME st; + convertDateYMS(Date, st, false); + __int64 sec = SystemTimeToInt64TenthSecond(st); + sec = sec + (extraDay * timeConstHour * 24); + st = Int64TenthSecondToSystemTime(sec); + dateS = convertSystemDate(st); + } } } @@ -2506,7 +2716,7 @@ const wstring &oEvent::getAbsTimeHM(DWORD time) const return makeDash(L"-"); wchar_t bf[32]; - swprintf_s(bf, L"%02d:%02d", (t/3600)%24, (t/60)%60); + swprintf_s(bf, L"%02d:%02d", (t/timeConstHour)%24, (t/timeConstMinute)%60); wstring &res = StringCache::getInstance().wget(); res = bf; @@ -2520,14 +2730,14 @@ int oEvent::convertAbsoluteTime(const string &m) return -1; int len=m.length(); - bool firstComma = false; + int firstComma = -1; for (int k=0;k='0' && b<='9')) ) { - if (b==':' && firstComma == false) + if (b==':' && firstComma < 0) continue; - else if ((b==',' || b=='.') && firstComma == false) { - firstComma = true; + else if ((b==',' || b=='.') && firstComma < 0) { + firstComma = k; continue; } return -1; @@ -2561,7 +2771,13 @@ int oEvent::convertAbsoluteTime(const string &m) second=0; } } - int t=hour*3600+minute*60+second; + int t=hour*timeConstHour+minute*timeConstMinute+second*timeConstSecond; + + if (timeConstSecond > 1 && firstComma > 0) { + int sub = std::abs(atoi(m.c_str() + firstComma + 1)); + while (sub >= timeConstSecond) + sub /= timeConstSecond; + } if (t<0) return 0; @@ -2574,17 +2790,17 @@ int oEvent::convertAbsoluteTime(const wstring &m) return -1; int len=m.length(); - bool firstComma = false; + int firstComma = -1; bool anyColon = false; for (int k = 0; k < len; k++) { wchar_t b = m[k]; if (!(b == ' ' || (b >= '0' && b <= '9'))) { - if (b == ':' && firstComma == false) { + if (b == ':' && firstComma < 0) { anyColon = true; continue; } - else if ((b == ',' || b == '.') && firstComma == false) { - firstComma = true; + else if ((b == ',' || b == '.') && firstComma < 0) { + firstComma = k; continue; } return -1; @@ -2600,7 +2816,7 @@ int oEvent::convertAbsoluteTime(const wstring &m) hour /= 100; if (hour > 23 || minute >=60 || second >= 60) return -1; - return hour * 3600 + minute * 60 + second; + return hour * timeConstHour + minute * timeConstMinute + second * timeConstSecond; } if (hour<0 || hour>23) @@ -2628,7 +2844,14 @@ int oEvent::convertAbsoluteTime(const wstring &m) second=0; } } - int t=hour*3600+minute*60+second; + int t = hour * timeConstHour + minute * timeConstMinute + second * timeConstSecond; + + if (timeConstSecond > 1 && firstComma > 0) { + int sub = std::abs(_wtoi(m.c_str() + firstComma + 1)); + while (sub >= timeConstSecond) + sub /= timeConstSecond; + t += sub; + } if (t<0) return 0; @@ -2637,31 +2860,33 @@ int oEvent::convertAbsoluteTime(const wstring &m) int oEvent::getRelativeTime(const string &date, const string &absoluteTime, const string &timeZone) const { - int atime=convertAbsoluteTime(absoluteTime); + int atime = convertAbsoluteTime(absoluteTime); - if (timeZone == "Z" || timeZone == "z") { + if ((timeZone == "Z" || timeZone == "z") && atime >= 0) { SYSTEMTIME st; convertDateYMS(date, st, false); - st.wHour = atime / 3600; - st.wMinute = (atime / 60) % 60; - st.wSecond = atime % 60; - + st.wHour = atime / timeConstHour; + st.wMinute = (atime / timeConstMinute) % 60; + st.wSecond = (atime / timeConstSecond) % 60; + if (timeConstSecond > 1) + st.wMilliseconds = (atime % timeConstSecond) * (1000 / timeConstSecond); SYSTEMTIME localTime; memset(&localTime, 0, sizeof(SYSTEMTIME)); SystemTimeToTzSpecificLocalTime(0, &st, &localTime); - atime = localTime.wHour*3600 + localTime.wMinute * 60 + localTime.wSecond; + atime = localTime.wHour*timeConstHour + localTime.wMinute * timeConstMinute + + localTime.wSecond * timeConstSecond + localTime.wMilliseconds / (1000 / timeConstSecond); } - if (atime>=0 && atime<3600*24){ - int rtime=atime-ZeroTime; + if (atime >= 0 && atime < timeConstHour * 24) { + int rtime = atime - ZeroTime; - if (rtime<=0) - rtime+=3600*24; + if (rtime <= 0) + rtime += timeConstHour * 24; //Don't allow times just before zero time. - if (rtime>3600*23) + if (rtime > timeConstHour * 23) return -1; return rtime; @@ -2687,17 +2912,14 @@ int oEvent::getRelativeTime(const wstring &m) const { atime = convertAbsoluteTime(m.substr(dayIndex)); days = _wtoi(m.c_str()); } - if (atime>=0 && atime <= 3600*24){ + if (atime>=0 && atime <= timeConstHour*24){ int rtime = atime-ZeroTime; if (rtime < 0) - rtime += 3600*24; - - rtime += days * 3600 * 24; - //Don't allow times just before zero time. - //if (rtime>3600*22) - // return -1; + rtime += timeConstHour*24; + rtime += days * timeConstHour * 24; + return rtime; } else return -1; @@ -3487,12 +3709,12 @@ bool oEvent::enumerateCompetitions(const wchar_t *file, const wchar_t *filetype) const xmlobject date=xp.getObject("Date"); - if (date) ci.Date=date.getw(); + if (date) ci.Date=date.getWStr(); const xmlobject name=xp.getObject("Name"); if (name) { - ci.Name=name.getw(); + ci.Name = name.getWStr(); if (ci.Name.size() > 1 && ci.Name.at(0) == '%') { ci.Name = lang.tl(ci.Name.substr(1)); } @@ -3500,25 +3722,25 @@ bool oEvent::enumerateCompetitions(const wchar_t *file, const wchar_t *filetype) const xmlobject annotation=xp.getObject("Annotation"); if (annotation) - ci.Annotation=annotation.getw(); + ci.Annotation=annotation.getWStr(); const xmlobject nameid = xp.getObject("NameId"); if (nameid) - ci.NameId = nameid.getw(); + ci.NameId = nameid.getWStr(); auto oData = xp.getObject("oData"); if (oData) { auto preEvent = oData.getObject("PreEvent"); if (preEvent) - ci.preEvent = preEvent.getw(); + ci.preEvent = preEvent.getWStr(); auto postEvent = oData.getObject("PostEvent"); if (postEvent) - ci.postEvent = postEvent.getw(); + ci.postEvent = postEvent.getWStr(); auto importStamp = oData.getObject("ImportStamp"); if (importStamp) - ci.importTimeStamp = importStamp.getw(); + ci.importTimeStamp = importStamp.getWStr(); } cinfo.push_front(ci); } @@ -3718,12 +3940,12 @@ bool oEvent::enumerateBackups(const wstring &file, const wstring &filetype, int //xmlobject *xo=xp.getObject("meosdata"); const xmlobject date=xp.getObject("Date"); - if (date) ci.Date=date.getw(); + if (date) ci.Date=date.getWStr(); const xmlobject name=xp.getObject("Name"); if (name) { - ci.Name=name.getw(); + ci.Name=name.getWStr(); if (ci.Name.size() > 1 && ci.Name.at(0) == '%') { ci.Name = lang.tl(ci.Name.substr(1)); } @@ -3745,7 +3967,8 @@ bool oEvent::enumerateBackups(const wstring &file, const wstring &filetype, int bool oEvent::fillCompetitions(gdioutput &gdi, const string &name, int type, - const wstring &select) { + const wstring &select, + bool doClear) { cinfo.sort(); cinfo.reverse(); list::iterator it; @@ -3773,7 +3996,8 @@ bool oEvent::fillCompetitions(gdioutput &gdi, } }; - gdi.clearList(name); + if (doClear) + gdi.clearList(name); string b; //char bf[128]; for (it=cinfo.begin(); it!=cinfo.end(); ++it) { @@ -3930,6 +4154,10 @@ void oEvent::clear() MeOSUtil::useHourFormat = getPropertyInt("UseHourFormat", 1) != 0; currentNameMode = (NameMode) getPropertyInt("NameMode", FirstLast); + + hasWarnedModifiedExtId = false; + + useSubsecondsVersion = -1; } const shared_ptr
&oEvent::getTable(const string &key) const { @@ -3965,7 +4193,7 @@ void oEvent::newCompetition(const wstring &name) GetLocalTime(&st); Date = convertSystemDate(st); - ZeroTime = st.wHour*3600; + ZeroTime = st.wHour*timeConstHour; Name = name; oEventData->initData(this, sizeof(oData)); @@ -3973,6 +4201,16 @@ void oEvent::newCompetition(const wstring &name) if (!name.empty() && name != L"-") getMergeTag(); + setCurrency(-1, L"", L"", 0); + + wstring file; + getNewFileName(file, currentNameId); + wcscpy_s(CurrentFile, MAX_PATH, file.c_str()); + + oe->updateTabs(); +} + +void oEvent::loadDefaults() { getDI().setString("Organizer", getPropertyString("Organizer", L"")); getDI().setString("Street", getPropertyString("Street", L"")); getDI().setString("Address", getPropertyString("Address", L"")); @@ -3995,14 +4233,9 @@ void oEvent::newCompetition(const wstring &name) getDI().setInt("CurrencyFactor", getPropertyInt("CurrencyFactor", 1)); getDI().setInt("CurrencyPreSymbol", getPropertyInt("CurrencyPreSymbol", 0)); getDI().setString("PayModes", getPropertyString("PayModes", L"")); - setCurrency(-1, L"", L"", 0); - wstring file; - getNewFileName(file, currentNameId); - wcscpy_s(CurrentFile, MAX_PATH, file.c_str()); - - oe->updateTabs(); + getDI().setInt("UTC", oe->getPropertyInt("UseEventorUTC", 0) != 0); } void oEvent::reEvaluateCourse(int CourseId, bool doSync) @@ -4344,23 +4577,23 @@ void oEvent::convertTimes(pRunner runner, SICard &sic) const if (sic.convertedTime == ConvertedTimeStatus::Hour12) { - int startTime = ZeroTime + 2*3600; //Add two hours. Subtracted below + int startTime = ZeroTime + 2*timeConstHour; //Add two hours. Subtracted below if (useLongTimes()) - startTime = 7 * 3600; // Avoid midnight as default. Prefer morning + startTime = 7 * timeConstHour; // Avoid midnight as default. Prefer morning int st = -1; if (runner) { st = runner->getStartTime(); if (st > 0) { if (sic.StartPunch.Code == -1) - startTime = (ZeroTime + st) % (3600 * 24); // No start punch + startTime = (ZeroTime + st) % (timeConstHour * 24); // No start punch else { // Got start punch. If this is close to specified start time, // use specified start time const int stPunch = sic.StartPunch.Time; // 12 hour - const int stStart = startTime = (ZeroTime + st) % (3600 * 12); // 12 hour - if (std::abs(stPunch - stStart) < 1800) { - startTime = (ZeroTime + st) % (3600 * 24); // Use specified start time (for conversion) + const int stStart = startTime = (ZeroTime + st) % (timeConstHour * 12); // 12 hour + if (std::abs(stPunch - stStart) < timeConstHour / 2) { + startTime = (ZeroTime + st) % (timeConstHour * 24); // Use specified start time (for conversion) } else { st = -1; // Ignore start time @@ -4382,24 +4615,24 @@ void oEvent::convertTimes(pRunner runner, SICard &sic) const } if (st >= 0) { // Optimize local zero time w.r.t first punch - int relT12 = (st - ZeroTime + 3600 * 24) % (3600 * 12); - startTime = (ZeroTime + relT12) % (3600 * 24); + int relT12 = (st - ZeroTime + timeConstHour * 24) % (timeConstHour * 12); + startTime = (ZeroTime + relT12) % (timeConstHour * 24); } } - int zt = (startTime + 22 * 3600) % (24 * 3600); // Subtract two hours from start time + int zt = (startTime + 22 * timeConstHour) % (24 * timeConstHour); // Subtract two hours from start time sic.analyseHour12Time(zt); } sic.convertedTime = ConvertedTimeStatus::Done; if (sic.CheckPunch.Code!=-1){ if (sic.CheckPunch.Time 0) { @@ -4424,13 +4657,13 @@ void oEvent::convertTimes(pRunner runner, SICard &sic) const if (!times.empty()) { int dayOffset = 0; if (times.front().first < int(ZeroTime)) { - dayOffset = 3600 * 24; + dayOffset = timeConstHour * 24; times.front().first += dayOffset; } for (size_t k = 1; k < times.size(); k++) { int delta = times[k].first - (times[k-1].first - dayOffset); - if (delta < (maxLegTime - 24 * 3600)) { - dayOffset += 24 * 3600; + if (delta < (maxLegTime - 24 * timeConstHour)) { + dayOffset += 24 * timeConstHour; } times[k].first += dayOffset; } @@ -4450,7 +4683,7 @@ void oEvent::convertTimes(pRunner runner, SICard &sic) const if (sic.StartPunch.Code != -1) { if (sic.StartPunch.TimeisRemoved() && (classId == 0 || it->getClassId(true) == classId)) { @@ -4488,8 +4721,8 @@ int oEvent::getFirstStart(int classId) const { ++it; } - if (minTime==3600*24) - minTime=0; + if (minTime == timeConstHour * 24) + minTime = 0; cf.first = dataRevision; cf.second = minTime; @@ -5005,7 +5238,7 @@ void oEvent::loadProperties(const wchar_t *file) { xmlList list; xo.getObjects(list); for (size_t k = 0; kgetStartTime()>0 && - (it->getStartTime()+ZeroTime)%60!=0 ) { - tUseStartSeconds=true; + for (it = Runners.begin(); it != Runners.end(); ++it) { + if (it->getStartTime() > 0 && + (it->getStartTime() + ZeroTime) % timeConstMinute != 0) { + tUseStartSeconds = true; return; } + } } const wstring &oEvent::formatStatus(RunnerStatus status, bool forPrint) @@ -5215,7 +5449,7 @@ void oEvent::generateTestCard(SICard &sic) const if (it->Class && it->tLeg>0) { StartTypes st = it->Class->getStartType(it->tLeg); - if (st == STHunting) { + if (st == STPursuit) { if (it->Class->tResultInfo[it->tLeg-1].nUnknown > 0) cardNo = 0; // Wait with this leg } @@ -5246,7 +5480,7 @@ void oEvent::generateTestCard(SICard &sic) const if (it->Class && it->tLeg>0) { StartTypes st = it->Class->getStartType(it->tLeg); - if (st == STHunting) { + if (st == STPursuit) { if (it->Class->tResultInfo[it->tLeg-1].nUnknown > 0) cardNo = 0; // Wait with this leg } @@ -5293,22 +5527,22 @@ void oEvent::generateTestCard(SICard &sic) const if (rand()%5 == 3) sic.CardNumber = 100000; - int s = sic.StartPunch.Time = r->tStartTime>0 ? r->tStartTime+ZeroTime : ZeroTime+3600+rand()%(3600*3); - int tomiss = rand()%(60*10); - if (tomiss>60*9) - tomiss = rand()%30; + int s = sic.StartPunch.Time = r->tStartTime>0 ? r->tStartTime+ZeroTime : ZeroTime+timeConstHour+rand()%(timeConstHour*3); + int tomiss = rand()%(timeConstMinute *10); + if (tomiss>timeConstMinute *9) + tomiss = rand()%30*timeConstSecond; else if (rand()%20 == 3) tomiss *= rand()%3; - - int f = sic.FinishPunch.Time = s+(30+pc->getLength()/200)*60+ rand()%(60*10) + tomiss; + + int f = sic.FinishPunch.Time = s+((30+pc->getLength()/200)*timeConstMinute+ rand()%(60*10))*timeUnitsPerSecond + tomiss; if (rand()%40==0 || r->tStartTime>0) sic.StartPunch.Code=-1; - if (rand()%50==31) + if (rand()%250==31) sic.FinishPunch.Code=-1; - if (rand()%70==31) + if (rand()%200==31) sic.CardNumber++; sic.nPunch=0; @@ -5317,11 +5551,10 @@ void oEvent::generateTestCard(SICard &sic) const int missed = 0; for(int k=0;knControls;k++) { - if (rand()%130!=50) { + if (rand()%330 != 50) { sic.Punch[sic.nPunch].Code=pc->getControl(k)->Numbers[0]; double cc=(k+1)*dt; - if (missed < tomiss) { int left = pc->nControls - k; if (rand() % left == 1) @@ -5419,6 +5652,7 @@ void oEvent::generateTestCompetition(int nClasses, int nRunners, bool generateTeams) { if (nClasses > 0) { oe->newCompetition(L"!TESTTÄVLING"); + oe->loadDefaults(); oe->setZeroTime(L"05:00:00", true); oe->getMeOSFeatures().useAll(*oe); } @@ -5507,7 +5741,7 @@ void oEvent::generateTestCompetition(int nClasses, int nRunners, if (cls->getNumDistinctRunners()==1) { for (int i=0;igetId(), 0, 0, true); + rand()%nClubs+1, cls->getId(), 0, L"", true); r->setStartNo(startno++, ChangeType::Update); r->setCardNo(500001+Runners.size()*97+rand()%97, false); @@ -5529,7 +5763,7 @@ void oEvent::generateTestCompetition(int nClasses, int nRunners, t->setStartNo(startno++, ChangeType::Update); for (int j=0;jsetCardNo(500001+Runners.size()*97+rand()%97, false); t->setRunner(j, r, false); } @@ -5677,7 +5911,7 @@ void oEvent::generateTableData(const string &tname, Table &table, TableUpdateInf if (tname == "runners") { if (tui.doRefresh && !tui.doAdd) return; - pRunner r = tui.doAdd ? addRunner(getAutoRunnerName(),0,0,0,0,false) : pRunner(tui.object); + pRunner r = tui.doAdd ? addRunner(getAutoRunnerName(), 0, 0, 0, L"", false) : pRunner(tui.object); generateRunnerTableData(table, r); return; } @@ -5718,7 +5952,7 @@ void oEvent::generateTableData(const string &tname, Table &table, TableUpdateInf if (tui.doRefresh && !tui.doAdd) return; - pFreePunch c = tui.doAdd ? addFreePunch(0,0,0, false) : pFreePunch(tui.object); + pFreePunch c = tui.doAdd ? addFreePunch(0,0,0,0, false) : pFreePunch(tui.object); generatePunchTableData(table, c); return; } @@ -6013,7 +6247,7 @@ void oEvent::sanityCheck(gdioutput &gdi, bool expectResult, int onlyThisClass) { for (unsigned k=0;kgetNumStages(); k++) { StartTypes st = it->getStartType(k); LegTypes lt = it->getLegType(k); - if (k==0 && (st == STChange || st == STHunting) && !warnBadStart) { + if (k==0 && (st == STChange || st == STPursuit) && !warnBadStart) { warnBadStart = true; gdi.alert(L"Klassen 'X' har jaktstart/växling på första sträckan.#" + it->getName()); } @@ -6147,6 +6381,25 @@ MetaListContainer &oEvent::getListContainer() const { return *listContainer; } +void oEvent::updateListReferences(const string& oldId, const string& newId) { + wstring oldIdW = gdioutput::widen(oldId); + wstring newIdW = gdioutput::widen(newId); + + if (getDI().getString("SplitPrint") == oldIdW) { + if (getDI().setString("SplitPrint", newIdW)) + synchronize(); + } + + for (auto& c : Classes) { + if (!c.isRemoved()) { + if (c.getDI().getString("SplitPrint") == oldIdW) { + if (c.getDI().setString("SplitPrint", newIdW)) + c.synchronize(); + } + } + } +} + void oEvent::setExtraLines(const char *attrib, const vector< pair > &lines) { wstring str; @@ -6304,15 +6557,16 @@ void oEvent::useLongTimes(bool use) { getDI().setInt("LongTimes", use ? 1 : 0); } -int oEvent::convertToFullTime(int inTime) { - if (inTime < 0 || !useLongTimes() || inTime > 24*3600) - return inTime; - - return inTime; +bool oEvent::supportSubSeconds() const { + return getDCI().getInt("SubSeconds") != 0; } +void oEvent::supportSubSeconds(bool use) { + TabSI::getSI(gdiBase()).setSubSecondMode(use); + getDI().setInt("SubSeconds", use ? 1 : 0); +} -void oEvent::getPayModes(vector< pair > &modes) { +void oEvent::getPayModes(vector> &modes) { modes.clear(); modes.reserve(10); vector< pair > lines; @@ -6396,15 +6650,14 @@ static void checkValid(oEvent &oe, int &time, int delta, const wstring &name) { int srcTime = time; time += delta; if (time <= 0) - time += 24 * 3600; - if (time > 24 * 3600) - time -= 24 * 3600; - if (time < 0 || time > 22 * 3600) { + time += 24 * timeConstHour; + if (time > 24 * timeConstHour) + time -= 24 * timeConstHour; + if (time < 0 || time > 22 * timeConstHour) { throw meosException(L"X har en tid (Y) som inte är kompatibel med förändringen.#" + name + L"#" + oe.getAbsTime(srcTime)); } } - void oEvent::updateStartTimes(int delta) { for (int pass = 0; pass <= 1; pass++) { for (oClass &c : Classes) { @@ -6453,7 +6706,7 @@ void oEvent::updateStartTimes(int delta) { continue; wstring desc = L"Bricka X#" + c.getCardNoString(); for (oPunch &p : c.punches) { - int t = p.Time; + int t = p.punchTime; if (t > 0) { if (c.getOwner() != 0) checkValid(*oe, t, delta, desc); @@ -6461,7 +6714,7 @@ void oEvent::updateStartTimes(int delta) { // Skip check t += delta; if (t <= 0) - t += 24 * 3600; + t += 24 * timeConstHour; } if (pass == 1) { @@ -6495,12 +6748,12 @@ void oEvent::updateStartTimes(int delta) { } for (oFreePunch &p : punches) { - int t = p.Time; + int t = p.punchTime; if (t > 0) { if (pass == 1) { t += delta; if (t <= 0) - t += 24 * 3600; + t += 24 * timeConstHour; p.setTimeInt(t, false); // Skip check } diff --git a/code/oEvent.h b/code/oEvent.h index 127449a..f1c50dd 100644 --- a/code/oEvent.h +++ b/code/oEvent.h @@ -6,7 +6,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -39,6 +39,8 @@ #include "oTeam.h" #include "intkeymap.hpp" +#include "meos_util.h" + #include #include @@ -107,7 +109,6 @@ private: int classId; int ID; public: - oTimeLine &setMessage(const wstring &msg_) {msg = msg_; return *this;} oTimeLine &setDetail(const wstring &detail_) {detail = detail_; return *this;} @@ -271,6 +272,7 @@ protected: bool writeTeams(xmlparser &xml); oControlList Controls; + shared_ptr tmpControl; oCourseList Courses; intkeymap courseIdIndex; oClassList Classes; @@ -295,6 +297,12 @@ protected: and index on course to a second maps, that maps cardNo to punches. */ map punchIndex; + /** Defined map from a pair (type, unit) of punches to a time adjustment (for that unit)*/ + mutable pair, int>> typeUnitPunchTimeAdjustment; + + /** Return start/finish/check units existing in punches/cards*/ + void getExistingUnits(vector>& typeUnit); + oTeamList Teams; intkeymap teamById; @@ -379,6 +387,9 @@ protected: //Protected speaker functions. int computerTime; + // True if warning has beend issued for manual id modification + bool hasWarnedModifiedExtId = false; + multimap timeLineEvents; int timeLineRevision; set timelineClasses; @@ -403,7 +414,7 @@ protected: bool tUseStartSeconds; - set< pair > readPunchHash; + set> readPunchHash; void insertIntoPunchHash(int card, int code, int time); void removeFromPunchHash(int card, int code, int time); bool isInPunchHash(int card, int code, int time); @@ -475,6 +486,10 @@ protected: bool disableRecalculate; public: + /** Get adjustment for a specific unit*/ + int getUnitAdjustment(oPunch::SpecialPunch type, int unit) const; + void clearUnitAdjustmentCache(); + void setStartGroup(int id, int firstStart, int lastStart, @@ -581,9 +596,16 @@ public: MeOSFeatures &getMeOSFeatures() const {return *meosFeatures;} void getDBRunnersInEvent(intkeymap &runners) const; MetaListContainer &getListContainer() const; + + /** Load image with specified id from database. Call before accessing user image. */ + void loadImage(uint64_t id) const; + + /** Save image with specified id to database. Call when importing new user image. */ + void saveImage(uint64_t id) const; + wstring getNameId(int id) const; const wstring &getFileNameFromId(int id) const; - + void updateListReferences(const string &oldId, const string &newId); // Adjust team size to class size and create multi runners. void adjustTeamMultiRunners(pClass cls); @@ -620,7 +642,7 @@ public: void loadProperties(const wchar_t *file); /** Get number of classes*/ - int getNumClasses() const {return Classes.size();} + int getNumClasses() const; /** Get number of runners */ int getNumRunners() const {return runnerById.size();} @@ -694,7 +716,6 @@ public: void getFreeImporter(oFreeImport &fi); void init(oFreeImport &fi); - void calculateSplitResults(int controlIdFrom, int controlIdTo); pTeam findTeam(const wstring &s, int lastId, unordered_set &filter) const; @@ -753,6 +774,8 @@ public: int getFreeStartNo() const; void generatePreReport(gdioutput &gdi); + // Format the header of a list + void formatHeader(gdioutput& gdi, const oListInfo& li, const pRunner rInput); void generateList(gdioutput &gdi, bool reEvaluate, const oListInfo &li, bool updateScrollBars); void generateListInfo(oListParam &par, oListInfo &li); @@ -811,14 +834,14 @@ public: // Get set of controls with registered punches void getFreeControls(set &controlId) const; // Returns the added punch, of null of already added. - pFreePunch addFreePunch(int time, int type, int card, bool updateRunner); + pFreePunch addFreePunch(int time, int type, int unit, int card, bool updateRunner); pFreePunch addFreePunch(oFreePunch &fp); bool useLongTimes() const; void useLongTimes(bool use); - /** Use the current computer time to convert the specified time to a long time, if long times are used. */ - int convertToFullTime(int inTime); + bool supportSubSeconds() const; + void supportSubSeconds(bool use); struct ResultEvent { ResultEvent() {} @@ -942,6 +965,7 @@ public: pFreePunch getPunch(int id) const; pFreePunch getPunch(int runnerId, int courseControlId, int card) const; void getPunchesForRunner(int runnerId, bool sort, vector &punches) const; + vector getPunchesByType(int type, int unit) const; //Returns true if data is changed. bool autoSynchronizeLists(bool syncPunches); @@ -969,7 +993,7 @@ public: pCard getCard(int Id) const; pCard getCardByNumber(int cno) const; bool isCardRead(const SICard &card) const; - void getCards(vector &cards); + void getCards(vector &cards, bool synchronize, bool onlyUnpaired); int getNumCards() const { return Cards.size(); } /** Try to find the class that best matches the card. Negative return = missing controls @@ -978,7 +1002,6 @@ public: int findBestClass(const SICard &card, vector &classes) const; wstring getCurrentTimeS() const; - //void reEvaluateClass(const set &classId, bool doSync); void reEvaluateCourse(int courseId, bool doSync); void reEvaluateAll(const set &classId, bool doSync); void reEvaluateChanged(); @@ -990,23 +1013,29 @@ public: bool teamsAsIndividual, bool unrollLoops, bool includeStageData, - bool forceSplitFee); + bool forceSplitFee, + bool useEventorQuirks); void exportIOFStartlist(IOFVersion version, const wchar_t *file, bool useUTC, const set &classes, bool teamsAsIndividual, bool includeStageInfo, - bool forceSplitFee); + bool forceSplitFee, + bool useEventorQuirks); bool exportOECSV(const wchar_t *file, int LanguageTypeIndex, bool includeSplits); bool save(); void duplicate(const wstring &annotation, bool keepTags = false); - void newCompetition(const wstring &Name); + + void newCompetition(const wstring &name); + void loadDefaults(); + void clearListedCmp(); bool enumerateCompetitions(const wchar_t *path, const wchar_t *extension); bool fillCompetitions(gdioutput &gdi, const string &name, - int type, const wstring &select = L""); + int type, const wstring &select = L"", + bool doClear = true); bool enumerateBackups(const wstring &path); bool listBackups(gdioutput &gdi, GUICALLBACK cb); @@ -1036,20 +1065,16 @@ public: /// Convert a clock time string to time relative zero time int getRelativeTime(const wstring &absoluteTime) const; - //int getRelativeTime(const string &absoluteTime) const {return getRelativeTime(toWide(absoluteTime));} - + /// Convert a clock time string to time relative zero time int getRelativeTime(const string &date, const string &absoluteTime, const string &timeZone) const; - // Convert a clock time string (SI5 12 Hour clock) to time relative zero time - //int getRelativeTimeFrom12Hour(const wstring &absoluteTime) const; - /// Convert c clock time string to absolute time (after 00:00:00) static int convertAbsoluteTime(const string &m); static int convertAbsoluteTime(const wstring &m); /// Get clock time from relative time - const wstring &getAbsTime(DWORD relativeTime) const; + const wstring &getAbsTime(DWORD relativeTime, SubSecond mode = SubSecond::Auto) const; wstring getAbsDateTimeISO(DWORD relativeTime, bool includeDate, bool useGMT) const; @@ -1125,9 +1150,9 @@ public: pTeam addTeam(const wstring &pname, int clubId=0, int classId=0); pTeam getTeam(int Id) const; pTeam getTeamByName(const wstring &pname) const; - const vector< pair > &fillTeams(vector< pair > &out, int classId=0); - static const vector< pair > &fillStatus(vector< pair > &out); - const vector< pair > &fillControlStatus(vector< pair > &out) const; + const vector> &fillTeams(vector< pair> &out, int classId=0); + static const vector> &fillStatus(vector< pair> &out); + const vector> &fillControlStatus(vector< pair> &out) const; void fillTeams(gdioutput &gdi, const string &id, int ClassId=0); static void fillStatus(gdioutput &gdi, const string &id); @@ -1137,10 +1162,10 @@ public: wstring getAutoRunnerName() const; pRunner addRunner(const wstring &pname, int clubId, int classId, - int cardNo, int birthYear, bool autoAdd); + int cardNo, const wstring &birthDate, bool autoAdd); pRunner addRunner(const wstring &pname, const wstring &pclub, int classId, - int cardNo, int birthYear, bool autoAdd); + int cardNo, const wstring& birthDate, bool autoAdd); pRunner addRunnerFromDB(const pRunner db_r, int classId, bool autoAdd); pRunner addRunner(const oRunner &r, bool updateStartNo); @@ -1185,9 +1210,9 @@ public: RunnerFilterWithResult = 4, RunnerCompactMode = 8}; - const vector< pair > &fillRunners(vector< pair > &out, - bool longName, int filter, - const unordered_set &personFilter); + const vector> &fillRunners(vector> &out, + bool longName, int filter, + const unordered_set &personFilter); void fillRunners(gdioutput &gdi, const string &id, bool longName = false, int filter = 0); const shared_ptr
&getTable(const string &key) const; @@ -1219,7 +1244,7 @@ public: pClub getClub(int Id) const; pClub getClub(const wstring &pname) const; - const vector< pair > &fillClubs(vector< pair > &out); + const vector> &fillClubs(vector> &out); void fillClubs(gdioutput &gdi, const string &id); void getClubs(vector &c, bool sort); @@ -1258,13 +1283,13 @@ public: extraNumMaps, }; - const vector< pair > &fillClasses(vector< pair > &out, + const vector> &fillClasses(vector> &out, ClassExtra extended, ClassFilter filter); void fillClasses(gdioutput &gdi, const string &id, ClassExtra extended, ClassFilter filter); bool fillClassesTB(gdioutput &gdi); - const vector< pair > &fillStarts(vector< pair > &out); - const vector< pair > &fillClassTypes(vector< pair > &out); + const vector> &fillStarts(vector> &out); + const vector> &fillClassTypes(vector> &out); void fillStarts(gdioutput &gdi, const string &id); void fillClassTypes(gdioutput &gdi, const string &id); @@ -1284,23 +1309,28 @@ public: void getControls(vector &controls, bool calculateCourseControls) const; void fillCourses(gdioutput &gdi, const string &id, bool simple = false); - const vector< pair > &fillCourses(vector< pair > &out, bool simple = false); + const vector> &getCourses(vector> &out, + const wstring &filter, + bool simple = false, + bool synchronize = true); 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}; + pControl getControl(int Id, bool create, bool includeVirtual); + enum class ControlType {All, RealControl, CourseControl}; - const vector< pair > &fillControls(vector< pair > &out, ControlType type); - const vector< pair > &fillControlTypes(vector< pair > &out); + const vector> &fillControls(vector> &out, ControlType type); + const vector> &fillControlTypes(vector> &out); bool open(int id); - bool open(const wstring &file, bool import, bool forMerge); + bool open(const wstring &file, bool doImport, bool forMerge, bool forceNew); bool open(const xmlparser &xml); - bool save(const wstring &file); + void clearData(bool runnerTeam, bool courses); + + bool save(const wstring &file, bool isAutoSave); pControl addControl(int id, int number, const wstring &name); pControl addControl(const oControl &oc); int getNextControlNumber() const; @@ -1331,7 +1361,8 @@ protected: Ambivalent }; - bool addXMLRank(const xmlobject &xrank, const map<__int64, int> &externIdToRunnerId, map> &output); + bool addXMLRank(const xmlobject &xrank, const map<__int64, int> &externIdToRunnerId, + map> &output); bool addXMLEvent(const xmlobject &xevent); bool addXMLCourse(const xmlobject &xcourse, bool addClasses, set &matchedClasses); @@ -1340,8 +1371,14 @@ protected: void merge(const oBase &src, const oBase *base) final; + mutable bool useSubSecondsCache = false; + mutable int useSubsecondsVersion = -1; + public: + /** Return true if subseconds are used*/ + bool useSubSecond() const; + const shared_ptr &getGeneralResult(const string &tag, wstring &sourceFileOut) const; void getGeneralResults(bool onlyEditable, vector>> &tagNameList, bool includeDateInName) const; void loadGeneralResults(bool forceReload, bool loadFromDisc) const; @@ -1413,7 +1450,10 @@ public: vector &known, vector &unknown, bool &hasSetDNS); void importOECSV_Data(const wstring &oecsvfile, bool clear); - void importXML_IOF_Data(const wstring &clubfile, const wstring &competitorfile, bool clear); + void importXML_IOF_Data(const wstring &clubfile, + const wstring &competitorfile, + bool onlyWithClub, + bool clear); void generateTestCard(SICard &sic) const; pClass generateTestClass(int nlegs, int nrunners, @@ -1424,8 +1464,12 @@ public: int checkChanged(vector &out) const; void checkDB(); //Check database for consistancy... oEvent(gdioutput &gdi); - oEvent &operator=(const oEvent &oe); + oEvent &operator=(const oEvent &oe) = delete; virtual ~oEvent(); + + void hasWarnedModifiedId(bool hasWarned) { hasWarnedModifiedExtId = hasWarned; } + bool hasWarnedModifiedId() const { return hasWarnedModifiedExtId; } + friend class oAbstractRunner; friend class oCourse; friend class oClass; diff --git a/code/oEventDraw.cpp b/code/oEventDraw.cpp index 5261057..fe882e9 100644 --- a/code/oEventDraw.cpp +++ b/code/oEventDraw.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -52,11 +52,11 @@ DrawInfo::DrawInfo() { extraFactor = 0.1; minVacancy = 1; maxVacancy = 10; - baseInterval = 60; - minClassInterval = 120; - maxClassInterval = 180; + baseInterval = timeConstMinute; + minClassInterval = 2*timeConstMinute; + maxClassInterval = 3*timeConstMinute; nFields = 10; - firstStart = 3600; + firstStart = timeConstHour; maxCommonControl = 3; allowNeighbourSameCourse = true; coursesTogether = false; @@ -913,8 +913,8 @@ void oEvent::optimizeStartOrder(vector> &outLines, DrawInfo & } void oEvent::loadDrawSettings(const set &classes, DrawInfo &drawInfo, vector &cInfo) const { - drawInfo.firstStart = 3600 * 22; - drawInfo.minClassInterval = 3600; + drawInfo.firstStart = timeConstHour * 22; + drawInfo.minClassInterval = timeConstHour; drawInfo.maxClassInterval = 1; drawInfo.minVacancy = 10; drawInfo.maxVacancy = 1; @@ -1785,10 +1785,10 @@ void oEvent::drawList(const vector &spec, } else { // Find first/last start in class and interval: - vector first(spec.size(), 7*24*3600); + vector first(spec.size(), 7*24*timeConstHour); vector last(spec.size(), 0); set cinterval; - int baseInterval = 10*60; + int baseInterval = 10*timeConstMinute; for (it=Runners.begin(); it != Runners.end(); ++it) { if (!it->isRemoved() && clsId2Ix.count(it->getClassId(true))) { @@ -1828,8 +1828,8 @@ void oEvent::drawList(const vector &spec, if (last[k] == 0 || spec[k].firstStart<=0 || baseInterval == 10*60) { // Fallback if incorrect specification. - spec[k].firstStart = 3600; - spec[k].interval = 2*60; + spec[k].firstStart = timeConstHour; + spec[k].interval = 2*timeConstMinute; } } } @@ -2406,7 +2406,7 @@ void oEvent::drawPersuitList(int classId, int firstTime, int restartTime, times[k].first = adjustedTimes[k]; } else { - times[k].first = 3600 * 24 * 7 + runner[k]->inputStatus; + times[k].first = timeConstHour * 24 * 7 + runner[k]->inputStatus; if (runner[k]->isVacant()) times[k].first += 10; // Vacansies last } @@ -2416,7 +2416,7 @@ void oEvent::drawPersuitList(int classId, int firstTime, int restartTime, int delta = times[0].first; - if (delta >= 3600*24*7) + if (delta >= timeConstHour*24*7) delta = 0; int reverseDelta = 0; diff --git a/code/oEventDraw.h b/code/oEventDraw.h index 3f9d3d5..51e6aaa 100644 --- a/code/oEventDraw.h +++ b/code/oEventDraw.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/oEventResult.cpp b/code/oEventResult.cpp index 84a3ea9..ef7d895 100644 --- a/code/oEventResult.cpp +++ b/code/oEventResult.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -112,7 +112,7 @@ void oEvent::calculateSplitResults(int controlIdFrom, int controlIdTo) { if (controlIdTo == 0 || controlIdTo == oPunch::PunchFinish) { it->tempRT = max(0, it->FinishTime - (st + it->tStartTime) ); if (it->tempRT > 0) - it->tempRT += it->getTimeAdjustment(); + it->tempRT += it->getTimeAdjustment(false); it->tempStatus = it->tStatus; } else { @@ -339,26 +339,26 @@ void oEvent::calculateRunnerResults(ResultType resultType, if (rgClasses.count(clsId)) { RunnerStatus st; - if (totalResults) - st = useComputedResult ? it->getStatusComputed() : it->getStatus(); + if (!totalResults) + st = useComputedResult ? it->getStatusComputed(false) : it->getStatus(); else - st = it->getTotalStatus(); + st = it->getTotalStatus(false); if (st == StatusOK) - score = numeric_limits::max() - (3600 * 24 * 7 * max(1, 1 + it->getRogainingPoints(useComputedResult, totalResults)) - it->getRunningTime(false)); + score = numeric_limits::max() - (timeConstHour * 24 * 7 * max(1, 1 + it->getRogainingPoints(useComputedResult, totalResults)) - it->getRunningTime(false)); else score = -1; } else if (!totalResults) { - RunnerStatus st = useComputedResult ? it->getStatusComputed() : it->getStatus(); + RunnerStatus st = useComputedResult ? it->getStatusComputed(false) : it->getStatus(); if (st == StatusOK || (includePreliminary && st == StatusUnknown && it->FinishTime > 0)) - score = it->getRunningTime(useComputedResult) + it->getNumShortening() * 3600 * 24 * 8; + score = it->getRunningTime(useComputedResult) + it->getNumShortening() * timeConstHour * 24 * 8; else score = -1; } else { int tt = it->getTotalRunningTime(it->FinishTime, useComputedResult, true); - RunnerStatus totStat = it->getTotalStatus(); + RunnerStatus totStat = it->getTotalStatus(false); if (totStat == StatusOK || (includePreliminary && totStat == StatusUnknown && it->inputStatus == StatusOK) && tt > 0) score = tt; else @@ -602,7 +602,7 @@ void oEvent::calculateModuleTeamResults(const set &cls, vector &te int clsId = t->getClassId(true); if (t->tComputedStatus == StatusOK && t->inputStatus == StatusOK) { if (rgClasses.count(clsId)) - totScore = numeric_limits::max() - (7 * 24 * 3600 * max(1, (1 + t->getRogainingPoints(true, true))) - (t->tComputedTime + t->inputTime)); + totScore = numeric_limits::max() - (7 * 24 * timeConstHour * max(1, (1 + t->getRogainingPoints(true, true))) - (t->tComputedTime + t->inputTime)); else totScore = t->tComputedTime + t->inputTime; } @@ -657,7 +657,7 @@ void oEvent::calculateModuleTeamResults(const set &cls, vector &te int legScore = -1; if (res.status == StatusOK) { if (rgClasses.count(clsId)) - legScore = numeric_limits::max() - (7 * 24 * 3600 * max(1, (1 + t->Runners[i]->tComputedPoints)) - res.time); + legScore = numeric_limits::max() - (7 * 24 * timeConstHour * max(1, (1 + t->Runners[i]->tComputedPoints)) - res.time); else legScore = res.time; } @@ -1118,7 +1118,7 @@ void oEvent::computePreliminarySplitResults(const set &classes) const { negLeg = -1000; //Finish, smallest number for (int j = 0; j < nRun; j++) { auto r = rr[j]; - if (r->prelStatusOK(true, false)) { + if (r->prelStatusOK(true, false, false)) { int time; if (!r->tInTeam || !totRes) time = r->getRunningTime(true); diff --git a/code/oEventSQL.cpp b/code/oEventSQL.cpp index 4ba1a64..bb81b1e 100644 --- a/code/oEventSQL.cpp +++ b/code/oEventSQL.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -39,13 +39,16 @@ #include "meosexception.h" #include "meos_util.h" #include "MeosSQL.h" +#include "generalresult.h" +#include "metalist.h" +#include "image.h" #include "meos.h" #include typedef bool (__cdecl* OPENDB_FCN)(void); typedef int (__cdecl* SYNCHRONIZE_FCN)(oBase *obj); - +extern Image image; bool oEvent::connectToServer() { @@ -378,9 +381,9 @@ bool oEvent::uploadSynchronize() if (it->FullPath == currentNameId && it->Server.length()>0) { gdioutput::AskAnswer ans = gdibase.askCancel(L"ask:overwrite_server"); - if (ans == gdioutput::AnswerCancel) + if (ans == gdioutput::AskAnswer::AnswerCancel) return false; - else if (ans == gdioutput::AnswerNo) { + else if (ans == gdioutput::AskAnswer::AnswerNo) { int len = currentNameId.length(); wchar_t ex[10]; swprintf_s(ex, L"_%05XZ", (GetTickCount()/97) & 0xFFFFF); @@ -432,6 +435,16 @@ bool oEvent::uploadSynchronize() gdibase.addInfoBox("", wstring(L"Kunde inte ladda upp löpardatabasen (X).#") + lang.tl(err), 5000); } + set img; + listContainer->getUsedImages(img); + if (!img.empty()) { + for (auto imgId : img) { + wstring fileName = image.getFileName(imgId); + auto rawData = image.getRawData(imgId); + sqlConnection->storeImage(imgId, fileName, rawData); + } + } + isConnectedToServer = true; // Save local version of database @@ -491,6 +504,16 @@ bool oEvent::readSynchronize(const CompetitionInfo &ci) } updateFreeId(); + + image.clearLoaded(); + + // Publish list of images (no loading) + vector> img; + sqlConnection->enumerateImages(img); + for (auto& i : img) { + image.addImage(i.second, i.first); + } + isConnectedToServer = false; openRunnerDatabase(currentNameId.c_str()); @@ -611,6 +634,25 @@ bool oEvent::readSynchronize(const CompetitionInfo &ci) return true; } +void oEvent::loadImage(uint64_t id) const { + if (image.hasImage(id)) + return; + if (sqlConnection && isConnectedToServer) { + wstring fn; + vector data; + if (sqlConnection->getImage(id, fn, data) == OpFailStatus::opStatusOK) + image.provideFromMemory(id, fn, data); + } +} + +void oEvent::saveImage(uint64_t id) const { + if (sqlConnection && isConnectedToServer) { + wstring fn = image.getFileName(id); + auto &data = image.getRawData(id); + sqlConnection->storeImage(id, fn, data); + } +} + bool oEvent::reConnectRaw() { if (!sqlConnection) return false; @@ -765,7 +807,7 @@ int oEvent::checkChanged(vector &out) const it!=oe->punches.end(); ++it) if (it->isChanged()) { changed++; - swprintf_s(bf, L"Punch SI=%d, %d", it->CardNo, it->Type); + swprintf_s(bf, L"Punch SI=%d, %d", it->CardNo, it->type); out.push_back(bf); it->synchronize(); } diff --git a/code/oEventSpeaker.cpp b/code/oEventSpeaker.cpp index d0a947b..6c76b84 100644 --- a/code/oEventSpeaker.cpp +++ b/code/oEventSpeaker.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -769,7 +769,7 @@ void oEvent::updateComputerTime() { SYSTEMTIME st; GetLocalTime(&st); - computerTime=(((24+2+st.wHour)*3600+st.wMinute*60+st.wSecond-ZeroTime)%(24*3600)-2*3600) * 1000 + st.wMilliseconds; + computerTime=(((24+2+st.wHour)*timeConstHour+st.wMinute*timeConstMinute+st.wSecond*timeConstSecond - ZeroTime)%(24*timeConstHour)-2*timeConstHour) * (1000/timeConstSecond) + st.wMilliseconds; } void oEvent::clearPrewarningSounds() @@ -797,7 +797,7 @@ void oEvent::playPrewarningSounds(const wstring &basedir, set &controls) oFreePunchList::reverse_iterator it; for (it=punches.rbegin(); it!=punches.rend() && !it->hasBeenPlayed; ++it) { - if (controls.count(it->Type)==1 || controls.empty()) { + if (controls.count(it->type)==1 || controls.empty()) { pRunner r = getRunnerByCardNo(it->CardNo, it->getAdjustedTime(), oEvent::CardLookupProperty::ForReadout); if (r){ @@ -893,7 +893,7 @@ wstring getNumber(int k) { struct BestTime { static const int nt = 4; - static const int maxtime = 3600*24*7; + static const int maxtime = timeConstHour*24*7; int times[nt]; BestTime() { @@ -1014,7 +1014,7 @@ int oEvent::setupTimeLineEvents(int currentTime) currentTime = getComputerTime(); } - int nextKnownEvent = 3600*48; + int nextKnownEvent = timeConstHour*48; vector started; started.reserve(Runners.size()); timeLineEvents.clear(); @@ -1033,7 +1033,7 @@ int oEvent::setupTimeLineEvents(int classId, int currentTime) // leg -> started on leg vector< vector > started; started.reserve(32); - int nextKnownEvent = 3600*48; + int nextKnownEvent = timeConstHour*48; int classSize = 0; pClass pc = getClass(classId); @@ -1231,7 +1231,7 @@ void oEvent::timeLinePrognose(TempResultMap &results, TimeRunner &tr, int prelT, int oEvent::setupTimeLineEvents(vector &started, const vector< pair > &rc, int currentTime, bool finish) { - int nextKnownEvent = 48*3600; + int nextKnownEvent = 48*timeConstHour; vector< vector > radioResults(rc.size()); vector bestLegTime(rc.size() + 1); vector bestTotalTime(rc.size() + 1); @@ -1722,12 +1722,12 @@ void oEvent::getResultEvents(const set &classFilter, const set &punchF if (r.isRemoved() || !classFilter.count(r.getClassId(true))) continue; - if (r.getStatusComputed() == StatusOutOfCompetition || r.getStatusComputed() == StatusNoTiming) + if (r.getStatusComputed(true) == StatusOutOfCompetition || r.getStatusComputed(true) == StatusNoTiming) continue; bool wroteResult = false; - if (r.prelStatusOK(true, false) || r.getStatusComputed() != StatusUnknown) { - RunnerStatus stat = r.prelStatusOK(true, false) ? StatusOK : r.getStatusComputed(); + if (r.prelStatusOK(true, false, true) || r.getStatusComputed(true) != StatusUnknown) { + RunnerStatus stat = r.prelStatusOK(true, false, true) ? StatusOK : r.getStatusComputed(true); wroteResult = true; results.push_back(ResultEvent(pRunner(&r), r.getFinishTime(), oPunch::PunchFinish, stat)); } @@ -1761,7 +1761,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 || fp.Type == oPunch::HiredCard) + 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); @@ -1774,7 +1774,7 @@ void oEvent::getResultEvents(const set &classFilter, const set &punchF if (!punchFilter.count(ctrl)) continue; - results.push_back(ResultEvent(r, fp.Time, courseControlId, StatusOK)); + results.push_back(ResultEvent(r, fp.getTimeInt(), courseControlId, StatusOK)); if (r->tInTeam && r->tLeg > 0) { map::iterator res = teamStatusPos.find(r->tInTeam->getId()); @@ -1790,7 +1790,7 @@ void oEvent::getResultEvents(const set &classFilter, const set &punchF for (map, oFreePunch>::const_iterator it = advanceInformationPunches.begin(); it != advanceInformationPunches.end(); ++it) { const oFreePunch &fp = it->second; - 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) continue; pRunner r = getRunner(fp.tRunnerId, 0); if (r == 0 || !classFilter.count(r->getClassId(true))) @@ -1800,7 +1800,7 @@ void oEvent::getResultEvents(const set &classFilter, const set &punchF if (!punchFilter.count(ctrl)) continue; - results.push_back(ResultEvent(r, fp.Time, courseControlId, StatusOK)); + results.push_back(ResultEvent(r, fp.getTimeInt(), courseControlId, StatusOK)); if (r->tInTeam && r->tLeg > 0) { map::iterator res = teamStatusPos.find(r->tInTeam->getId()); diff --git a/code/oFreeImport.cpp b/code/oFreeImport.cpp index fa32b1c..87bb0c0 100644 --- a/code/oFreeImport.cpp +++ b/code/oFreeImport.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -829,7 +829,7 @@ bool oFreeImport::isTime(const wstring &m) const return false; } } - int t=hour*3600+minute*60+second; + int t = hour * timeConstHour + minute * timeConstMinute + second * timeConstSecond; if (t<=0) return false; @@ -1766,7 +1766,7 @@ void oFreeImport::addEntries(pEvent oe, const vector &entries) if (nr==1) { pRunner r=oe->addRunner(entries[k].getName(0), entries[k].getClub(0), pc->getId(), - entries[k].getCard(0), 0, true); + entries[k].getCard(0), L"", true); r->setStartTimeS(entries[k].eStartTime); r->setCardNo(entries[k].getCard(0), false); @@ -1787,7 +1787,7 @@ void oFreeImport::addEntries(pEvent oe, const vector &entries) for (int j=0;jaddRunner(entries[k].getName(j), entries[k].getClub(j), - pc->getId(), entries[k].getCard(j), 0, false); + pc->getId(), entries[k].getCard(j), L"", false); r->setCardNo(entries[k].getCard(j), false); r->addClassDefaultFee(false); t->setRunner(j, r, true); diff --git a/code/oFreeImport.h b/code/oFreeImport.h index 9cf6ae2..3e0a40d 100644 --- a/code/oFreeImport.h +++ b/code/oFreeImport.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/oFreePunch.cpp b/code/oFreePunch.cpp index 9a48ccf..a5e00ee 100644 --- a/code/oFreePunch.cpp +++ b/code/oFreePunch.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -35,26 +35,24 @@ bool oFreePunch::disableHashing = false; -oFreePunch::oFreePunch(oEvent *poe, int card, int time, int type): oPunch(poe) -{ +oFreePunch::oFreePunch(oEvent *poe, int card, int time, int inType, int unit): oPunch(poe) { Id=oe->getFreePunchId(); CardNo = card; - Time = time; - Type = type; + punchTime = time; + punchUnit = unit; + type = inType; iHashType = 0; tRunnerId = 0; } -oFreePunch::oFreePunch(oEvent *poe, int id): oPunch(poe) -{ +oFreePunch::oFreePunch(oEvent *poe, int id): oPunch(poe) { Id=id; oe->qFreePunchId = max(id, oe->qFreePunchId); iHashType = 0; tRunnerId = 0; } -oFreePunch::~oFreePunch(void) -{ +oFreePunch::~oFreePunch(void) { } bool oFreePunch::Write(xmlparser &xml) @@ -62,8 +60,9 @@ bool oFreePunch::Write(xmlparser &xml) if (Removed) return true; xml.startTag("Punch"); xml.write("CardNo", CardNo); - xml.write("Time", Time); - xml.write("Type", Type); + xml.writeTime("Time", punchTime); + xml.write("Type", type); + xml.write("Unit", punchUnit); xml.write("Id", Id); xml.write("Updated", getStamp()); xml.endTag(); @@ -82,17 +81,20 @@ void oFreePunch::Set(const xmlobject *xo) if (it->is("CardNo")){ CardNo=it->getInt(); } - if (it->is("Type")){ - Type=it->getInt(); + else if (it->is("Type")){ + type=it->getInt(); } - if (it->is("Time")){ - Time=it->getInt(); + else if (it->is("Time")){ + punchTime=it->getRelativeTime(); + } + else if (it->is("Unit")) { + punchUnit = it->getInt(); } else if (it->is("Id")){ Id=it->getInt(); } else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); } } } @@ -112,11 +114,11 @@ bool oFreePunch::setCardNo(int cno, bool databaseUpdate) { } ++it; } - oe->removeFromPunchHash(CardNo, Type, Time); + oe->removeFromPunchHash(CardNo, type, punchTime); rehashPunches(*oe, CardNo, 0); CardNo = cno; - oe->insertIntoPunchHash(CardNo, Type, Time); + oe->insertIntoPunchHash(CardNo, type, punchTime); rehashPunches(*oe, CardNo, this); pRunner r2 = oe->getRunner(tRunnerId, 0); @@ -152,6 +154,7 @@ const shared_ptr
&oFreePunch::getTable(oEvent *oe) { table->addColumn("Ändrad", 150, false); table->addColumn("Bricka", 70, true); table->addColumn("Kontroll", 70, true); + table->addColumn("Enhet", 70, true); table->addColumn("Tid", 70, false); table->addColumn("Löpare", 170, false); table->addColumn("Lag", 170, false); @@ -187,10 +190,12 @@ void oFreePunch::addTableRow(Table &table) const { table.set(row++, it, TID_MODIFIED, getTimeStamp(), false, cellEdit); table.set(row++, it, TID_CARD, itow(getCardNo()), true, cellEdit); table.set(row++, it, TID_CONTROL, getType(), true, cellEdit); - table.set(row++, it, TID_TIME, getTime(), true, cellEdit); + table.set(row++, it, TID_UNIT, punchUnit > 0 ? itow(punchUnit) : _EmptyWString, true, cellEdit); + + table.set(row++, it, TID_TIME, getTime(false, SubSecond::Auto), true, cellEdit); pRunner r = 0; if (CardNo > 0) - r = oe->getRunnerByCardNo(CardNo, Time, oEvent::CardLookupProperty::Any); + r = oe->getRunnerByCardNo(CardNo, getTimeInt(), oEvent::CardLookupProperty::Any); table.set(row++, it, TID_RUNNER, r ? r->getName() : L"?", false, cellEdit); @@ -216,13 +221,20 @@ pair oFreePunch::inputData(int id, const wstring &input, case TID_TIME: setTime(input); synchronize(true); - output = getTime(); + output = getTime(false, SubSecond::Auto); break; case TID_CONTROL: setType(input); synchronize(true); output = getType(); + break; + + case TID_UNIT: + setPunchUnit(_wtoi(input.c_str())); + synchronize(true); + output = punchUnit > 0 ? itow(punchUnit) : _EmptyWString; + break; } return make_pair(0, false); } @@ -232,10 +244,10 @@ void oFreePunch::fillInput(int id, vector< pair > &out, size_t } void oFreePunch::setTimeInt(int t, bool databaseUpdate) { - if (t != Time) { - oe->removeFromPunchHash(CardNo, Type, Time); - Time = t; - oe->insertIntoPunchHash(CardNo, Type, Time); + if (t != punchTime) { + oe->removeFromPunchHash(CardNo, type, punchTime); + punchTime = t; + oe->insertIntoPunchHash(CardNo, type, punchTime); rehashPunches(*oe, CardNo, 0); if (!databaseUpdate) updateChanged(); @@ -243,10 +255,10 @@ void oFreePunch::setTimeInt(int t, bool databaseUpdate) { } bool oFreePunch::setType(const wstring &t, bool databaseUpdate) { - int type = _wtoi(t.c_str()); + int inputType = _wtoi(t.c_str()); int ttype = 0; - if (type>0 && type<10000) - ttype = type; + if (inputType >0 && inputType <10000) + ttype = inputType; else { if (t == lang.tl("Check")) ttype = oPunch::PunchCheck; @@ -255,10 +267,10 @@ bool oFreePunch::setType(const wstring &t, bool databaseUpdate) { if (t == lang.tl("Start")) ttype = oPunch::PunchStart; } - if (ttype > 0 && ttype != Type) { - oe->removeFromPunchHash(CardNo, Type, Time); - Type = ttype; - oe->insertIntoPunchHash(CardNo, Type, Time); + if (ttype > 0 && ttype != type) { + oe->removeFromPunchHash(CardNo, type, punchTime); + type = ttype; + oe->insertIntoPunchHash(CardNo, type, punchTime); int oldControlId = tMatchControlId; rehashPunches(*oe, CardNo, 0); @@ -297,7 +309,7 @@ void oFreePunch::rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch) { try { for (size_t j = 0; j < fp.size(); j++) { pFreePunch punch = fp[j]; - punch->iHashType = oe.getControlIdFromPunch(punch->Time, punch->Type, punch->CardNo, true, + punch->iHashType = oe.getControlIdFromPunch(punch->getTimeInt(), punch->type, punch->CardNo, true, *punch); oEvent::PunchIndexType &card2Punch = oe.punchIndex[punch->iHashType]; @@ -338,7 +350,7 @@ void oFreePunch::rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch) { if (j>0 && fp[j-1] == fp[j]) continue; //Skip duplicates pFreePunch punch = fp[j]; - punch->iHashType = oe.getControlIdFromPunch(punch->Time, punch->Type, cardNo, true, *punch); + punch->iHashType = oe.getControlIdFromPunch(punch->getTimeInt(), punch->type, cardNo, true, *punch); oEvent::PunchIndexType &card2Punch = oe.punchIndex[punch->iHashType]; card2Punch.insert(make_pair(punch->CardNo, punch)); } @@ -377,7 +389,7 @@ int oEvent::getControlIdFromPunch(int time, int type, int card, if (ctrl && ctrl->hasNumber(type)) { int courseControlId = c->getCourseControlId(k); pFreePunch p = getPunch(r->getId(), courseControlId, card); - if (!p || (p && abs(p->Time-time)<60)) { + if (!p || (p && abs(p->getTimeInt() - time)<60)) { ctrl->tHasFreePunchLabel = true; punch.tMatchControlId = ctrl->getId(); punch.tIndex = k; @@ -438,10 +450,10 @@ bool oEvent::isInPunchHash(int card, int code, int time) { return readPunchHash.count(make_pair(p1, p2)) > 0; } -pFreePunch oEvent::addFreePunch(int time, int type, int card, bool updateStartFinish) { +pFreePunch oEvent::addFreePunch(int time, int type, int unit, int card, bool updateStartFinish) { if (time > 0 && isInPunchHash(card, type, time)) return 0; - oFreePunch ofp(this, card, time, type); + oFreePunch ofp(this, card, time, type, unit); punches.push_back(ofp); pFreePunch fp=&punches.back(); @@ -481,12 +493,15 @@ pFreePunch oEvent::addFreePunch(int time, int type, int card, bool updateStartFi if (tr->getStatus() == StatusUnknown && time > 0) { tr->synchronize(); if (type == startType) { - if (tr->getClassRef(false) && !tr->getClassRef(true)->ignoreStartPunch()) - tr->setStartTime(time, true, ChangeType::Update); + if (tr->getClassRef(false) && !tr->getClassRef(true)->ignoreStartPunch()) { + int adjust = oe->getUnitAdjustment(oPunch::SpecialPunch(startType), unit); + tr->setStartTime(time + adjust, true, ChangeType::Update); + } + } + else { + int adjust = oe->getUnitAdjustment(oPunch::SpecialPunch(finishType), unit); + tr->setFinishTime(time + adjust); } - else - tr->setFinishTime(time); - // Direct result if (type == finishType && tr->getClassRef(false) && tr->getClassRef(true)->hasDirectResult()) { if (tr->getCourse(false) == 0 && tr->getCard() == 0) { @@ -512,7 +527,7 @@ pFreePunch oEvent::addFreePunch(int time, int type, int card, bool updateStartFi } pFreePunch oEvent::addFreePunch(oFreePunch &fp) { - insertIntoPunchHash(fp.CardNo, fp.Type, fp.Time); + insertIntoPunchHash(fp.CardNo, fp.type, fp.punchTime); punches.push_back(fp); pFreePunch fpz=&punches.back(); fpz->addToEvent(this, &fp); @@ -552,7 +567,7 @@ void oEvent::removeFreePunch(int Id) { } int cardNo = fp->CardNo; - removeFromPunchHash(cardNo, fp->Type, fp->Time); + removeFromPunchHash(cardNo, fp->type, fp->punchTime); punches.erase(it); oFreePunch::rehashPunches(*this, cardNo, 0); dataRevision++; @@ -610,6 +625,17 @@ pFreePunch oEvent::getPunch(int runnerId, int courseControlId, int card) const return 0; } +vector oEvent::getPunchesByType(int type, int unit) const { + vector out; + for (auto& p : punches) { + if (!p.isRemoved() && p.getTypeCode() == type) { + if (unit == 0 || p.getPunchUnit() == unit) + out.push_back(pFreePunch(&p)); + } + } + return out; +} + void oEvent::getPunchesForRunner(int runnerId, bool doSort, vector &runnerPunches) const { runnerPunches.clear(); pRunner r = getRunner(runnerId, 0); @@ -633,13 +659,13 @@ void oEvent::getPunchesForRunner(int runnerId, bool doSort, vector & assert(punch && punch->CardNo == card); if (punch->tRunnerId == runnerId || runnerId == 0) runnerPunches.push_back(punch); - ++pIter; } + ++pIter; } } if (doSort) { - sort(runnerPunches.begin(), runnerPunches.end(), [](const oPunch *p1, const oPunch *p2)->bool {return p1->Time < p2->Time; }); + sort(runnerPunches.begin(), runnerPunches.end(), [](const oPunch *p1, const oPunch *p2)->bool {return p1->getTimeInt() < p2->getTimeInt(); }); } } @@ -669,14 +695,14 @@ bool oEvent::advancePunchInformation(const vector &gdi, vectorgetCardNo()) == 0) { - oFreePunch fp(this, 0, pi[k].time, pi[k].iHashType); + oFreePunch fp(this, 0, pi[k].time, pi[k].iHashType, 0); fp.tRunnerId = pi[k].runnerId; fp.iHashType = pi[k].iHashType; fp.tIndex = 0; fp.tMatchControlId = oFreePunch::getControlIdFromHash(fp.iHashType, false); fp.changed = false; - pair hc(pi[k].iHashType, r->getCardNo()); - advanceInformationPunches.insert(make_pair(hc,fp)); + pair hc(pi[k].iHashType, r->getCardNo()); + advanceInformationPunches.insert(make_pair(hc, fp)); if (r->Class) { r->markClassChanged(oFreePunch::getControlIdFromHash(pi[k].iHashType, false)); classChanged(r->Class, true); @@ -745,7 +771,7 @@ void oEvent::setHiredCard(int cardNo, bool flag) { if (isHiredCard(cardNo) != flag) { if (flag) { - addFreePunch(0, oPunch::HiredCard, cardNo, false); + addFreePunch(0, oPunch::HiredCard, 0, cardNo, false); hiredCardHash.insert(cardNo); tHiredCardHashDataRevision = dataRevision; } diff --git a/code/oFreePunch.h b/code/oFreePunch.h index f93cc07..fe4c39a 100644 --- a/code/oFreePunch.h +++ b/code/oFreePunch.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -32,8 +32,7 @@ class oRunner; typedef oRunner *pRunner; class Table; -class oFreePunch : public oPunch -{ +class oFreePunch final : public oPunch { protected: int CardNo; int iHashType; //Index type used for lookup @@ -43,7 +42,7 @@ protected: class FreePunchComp { public: bool operator()(pFreePunch a, pFreePunch b) { - return a->Time < b->Time; + return a->getTimeInt() < b->getTimeInt(); } }; @@ -81,14 +80,14 @@ public: int getCardNo() const {return CardNo;} bool setCardNo(int cardNo, bool databaseUpdate = false); bool setType(const wstring &t, bool databaseUpdate = false); - void setTimeInt(int newTime, bool databaseUpdate); + void setTimeInt(int newTime, bool databaseUpdate) final; static void rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch); static bool disableHashing; void merge(const oBase &input, const oBase *base) final; - oFreePunch(oEvent *poe, int card, int time, int type); + oFreePunch(oEvent *poe, int card, int time, int type, int unit); oFreePunch(oEvent *poe, int id); virtual ~oFreePunch(void); diff --git a/code/oImportExport.cpp b/code/oImportExport.cpp index 7dde59f..1f695af 100644 --- a/code/oImportExport.cpp +++ b/code/oImportExport.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -211,14 +211,14 @@ bool oEvent::exportOECSV(const wchar_t *file, int languageTypeIndex, bool includ // Excel format HH:MM:SS if (it->getFinishTime() > 0) - row[OEfinish] = gdibase.recodeToNarrow(it->getFinishTimeS()); + row[OEfinish] = gdibase.recodeToNarrow(it->getFinishTimeS(false, SubSecond::Auto)); // Excel format HH:MM:SS if (it->getRunningTime(true) > 0) row[OEtime] = gdibase.recodeToNarrow(formatTimeHMS(it->getRunningTime(true))); - row[OEstatus] = conv_is(ConvertStatusToOE(it->getStatusComputed())); + row[OEstatus] = conv_is(ConvertStatusToOE(it->getStatusComputed(true))); row[OEclubno] = conv_is(it->getClubId()); if (it->getClubRef()) { @@ -268,8 +268,8 @@ bool oEvent::exportOECSV(const wchar_t *file, int languageTypeIndex, bool includ if (pc->getControl(k)->isRogaining(hasRogaining)) continue; row.push_back(gdibase.recodeToNarrow(pc->getControl(k)->getIdS())); - if (unsigned(k) < sp.size() && sp[k].time > 0) - row.push_back(gdibase.recodeToNarrow(formatTimeHMS(sp[k].time - it->tStartTime))); + if (unsigned(k) < sp.size() && sp[k].getTime(false) > 0) + row.push_back(gdibase.recodeToNarrow(formatTimeHMS(sp[k].getTime(false) - it->tStartTime))); else row.push_back("-----"); } @@ -326,7 +326,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, int ent = 0, fail = 0, removed = 0; if (xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.setPreferredIdType(preferredIdType); reader.readEntryList(gdi, xo, removeNonexisting, filter, ent, fail, removed); @@ -391,7 +391,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, int ent = 0, fail = 0; if (xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.readStartList(gdi, xo, ent, fail); } else { @@ -440,7 +440,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, if (xo.getAttrib("iofVersion")) { gdi.addString("", 0, "Importerar resultat (IOF, xml)"); gdi.refreshFast(); - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.readResultList(gdi, xo, ent, fail); } gdi.addString("", 0, "Klart. Antal importerade: X#" + itos(ent)); @@ -462,7 +462,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, int imp = 0, fail = 0; if (xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.readClassList(gdi, xo, imp, fail); } else { @@ -568,7 +568,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, int imp = 0, fail = 0; if (xo && xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.readCourseData(gdi, xo, updateClass, imp, fail); } else { @@ -611,7 +611,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, gdi.refreshFast(); if (xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.readEventList(gdi, xo); gdi.addString("", 0, L"Tävlingens namn: X#" + getName()); gdi.dropLine(); @@ -643,7 +643,7 @@ void oEvent::importXML_EntryData(gdioutput &gdi, const wstring &file, gdi.refreshFast(); if (xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); int imp = 0, fail = 0; reader.readServiceRequestList(gdi, xo, imp, fail); @@ -715,7 +715,9 @@ bool oEvent::addXMLCompetitorDB(const xmlobject &xentry, int clubId) person.getObjectString("sex", sex, 2); xmlobject bd = person.getObject("BirthDate"); - int birth = bd ? bd.getObjectInt("Date") : 0; + wstring birth; + if (bd) + bd.getObjectString("Date", birth); xmlobject nat = person.getObject("Nationality"); @@ -742,7 +744,7 @@ bool oEvent::addXMLCompetitorDB(const xmlobject &xentry, int clubId) rde->setExtId(extId); rde->setName(name.c_str()); rde->dbe().clubNo = clubId; - rde->dbe().birthYear = extendYear(birth); + rde->dbe().setBirthDate(birth); rde->dbe().sex = sex[0]; memcpy(rde->dbe().national, national, 3); } @@ -776,7 +778,7 @@ bool oEvent::addOECSVCompetitorDB(const vector &row) else strcpy_s(sex, ""); - int birth = _wtoi(row[OEbirth].c_str()); + const wstring &birth = row[OEbirth]; // Hack to take care of inconsistency between FFCO licensees archive (France) and event registrations from FFCO (FR) char national[4] = { 0,0,0,0 }; @@ -826,7 +828,7 @@ bool oEvent::addOECSVCompetitorDB(const vector &row) rde->setExtId(pid); rde->setName(name.c_str()); rde->dbe().clubNo = clubId; - rde->dbe().birthYear = extendYear(birth); + rde->dbe().setBirthDate(birth); rde->dbe().sex = sex[0]; memcpy(rde->dbe().national, national, 3); } @@ -888,7 +890,7 @@ bool oEvent::addXMLTeamEntry(const xmlobject &xentry, int clubId) } if (pc->getNumStages() < unsigned(maxleg)) { - setupRelay(*pc, PRelay, maxleg, getAbsTime(3600)); + setupRelay(*pc, PRelay, maxleg, getAbsTime(timeConstHour)); } for (size_t k = 0; k < teamCmp.size(); k++) { @@ -972,8 +974,10 @@ pRunner oEvent::addXMLPerson(const xmlobject &person) { r->setSex(interpretSex(person.getObjectString("sex", tmp))); xmlobject bd=person.getObject("BirthDate"); - if (bd) r->setBirthYear(extendYear(bd.getObjectInt("Date"))); - + if (bd) { + bd.getObjectString("Date", tmp); + r->setBirthDate(tmp); + } xmlobject nat=person.getObject("Nationality"); if (nat) { @@ -1072,7 +1076,7 @@ pRunner oEvent::addXMLEntry(const xmlobject &xentry, int clubId, bool setClass) bool createTeam = false; pClass pc = r->Class; if (pc->getNumStages() <= 1) { - setupRelay(*pc, PPatrolOptional, 2, getAbsTime(3600)); + setupRelay(*pc, PPatrolOptional, 2, getAbsTime(timeConstHour)); createTeam = true; } @@ -1239,7 +1243,8 @@ void oEvent::importOECSV_Data(const wstring &oecsvfile, bool clear) { } void oEvent::importXML_IOF_Data(const wstring &clubfile, - const wstring &competitorfile, bool clear) + const wstring &competitorfile, + bool onlyWithClub, bool clear) { if (!clubfile.empty()) { xmlparser xml_club; @@ -1262,7 +1267,7 @@ void oEvent::importXML_IOF_Data(const wstring &clubfile, if (!xo) { xo = xml_club.getObject("OrganisationList"); if (xo) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); reader.readClubList(gdibase, xo, clubCount); } } @@ -1298,10 +1303,11 @@ void oEvent::importXML_IOF_Data(const wstring &clubfile, gdibase.refresh(); int personCount = 0; + int duplicateCount = 0; xmlobject xo=xml_cmp.getObject("CompetitorList"); if (xo && xo.getAttrib("iofVersion")) { - IOF30Interface reader(this, false); + IOF30Interface reader(this, false, false); vector idProviders; reader.prescanCompetitorList(xo); @@ -1318,7 +1324,7 @@ void oEvent::importXML_IOF_Data(const wstring &clubfile, reader.setPreferredIdType(preferredIdProvider); } - reader.readCompetitorList(gdibase, xo, personCount); + reader.readCompetitorList(gdibase, xo, onlyWithClub, personCount, duplicateCount); } else { xmlList xl; @@ -1337,6 +1343,10 @@ void oEvent::importXML_IOF_Data(const wstring &clubfile, } gdibase.addStringUT(0, lang.tl("Antal importerade: ") + itow(personCount)); + + if (duplicateCount > 0) + gdibase.addString("", 0, "Ignorerade X duplikat.#" + itos(duplicateCount)); + gdibase.refresh(); setProperty("DatabaseUpdate", getRelativeDay()); @@ -1499,7 +1509,7 @@ bool oEvent::addXMLControl(const xmlobject &xcontrol, int type) if (type == 0) { int code = xcontrol.getObjectInt("ControlCode"); if (code>=30 && code<1024) { - pControl pc = getControl(code, true); + pControl pc = getControl(code, true, false); pc->getDI().setInt("xpos", xp); pc->getDI().setInt("ypos", yp); pc->synchronize(); @@ -1512,10 +1522,10 @@ bool oEvent::addXMLControl(const xmlobject &xcontrol, int type) int num = getNumberSuffix(start); if (num == 0 && start.length()>0) num = int(start[start.length()-1])-'0'; - pControl pc = getControl(getStartIndex(num), true); + pControl pc = getControl(getStartIndex(num), true, false); pc->setNumbers(L""); pc->setName(start); - pc->setStatus(oControl::StatusStart); + pc->setStatus(oControl::ControlStatus::StatusStart); pc->getDI().setInt("xpos", xp); pc->getDI().setInt("ypos", yp); } @@ -1528,10 +1538,10 @@ bool oEvent::addXMLControl(const xmlobject &xcontrol, int type) num = int(finish[finish.length()-1])-'0'; if (num > 0) finish = lang.tl("MÃ¥l ") + itow(num); - pControl pc = getControl(getFinishIndex(num), true); + pControl pc = getControl(getFinishIndex(num), true, false); pc->setNumbers(L""); pc->setName(finish); - pc->setStatus(oControl::StatusFinish); + pc->setStatus(oControl::ControlStatus::StatusFinish); pc->getDI().setInt("xpos", xp); pc->getDI().setInt("ypos", yp); } @@ -1697,8 +1707,9 @@ bool oEvent::addXMLClass(const xmlobject &xclass) for (size_t k = 0; k0) { fee.push_back(oe->interpretCurrency(f, cur)); } @@ -2378,8 +2389,8 @@ void oEvent::exportIOFResults(xmlparser &xml, bool selfContained, const set continue; xml.startTag("SplitTime", "sequence", itos(no++)); xml.write("ControlCode", pcourse->Controls[k]->getFirstNumber()); - if (unsigned(k)0) - xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(sp[k].time-it->tStartTime, 0)); + if (unsigned(k)0) + xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(sp[k].getTime(false) -it->tStartTime, 0)); else xml.write("Time", L"--:--:--"); @@ -2459,7 +2470,7 @@ void oEvent::exportIOFResults(xmlparser &xml, bool selfContained, const set xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(it->getStartTime(), ZeroTime)); xml.endTag(); xml.startTag("FinishTime"); - xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(it->getFinishTimeAdjusted(), ZeroTime)); + xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(it->getFinishTimeAdjusted(false), ZeroTime)); xml.endTag(); xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(it->getRunningTime(true),0)); @@ -2483,8 +2494,8 @@ void oEvent::exportIOFResults(xmlparser &xml, bool selfContained, const set continue; xml.startTag("SplitTime", "sequence", itos(no++)); xml.write("ControlCode", pcourse->Controls[k]->getFirstNumber()); - if (unsigned(k)0) - xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(sp[k].time - it->tStartTime, 0)); + if (unsigned(k)0) + xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(sp[k].getTime(false) - it->tStartTime, 0)); else xml.write("Time", L"--:--:--"); @@ -2581,7 +2592,7 @@ void oEvent::exportTeamSplits(xmlparser &xml, const set &classes, bool oldS xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(it->getStartTime(), ZeroTime)); xml.endTag(); xml.startTag("FinishTime"); - xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(it->getFinishTimeAdjusted(), ZeroTime)); + xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(it->getFinishTimeAdjusted(false), ZeroTime)); xml.endTag(); xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(it->getRunningTime(true), 0)); @@ -2606,7 +2617,7 @@ void oEvent::exportTeamSplits(xmlparser &xml, const set &classes, bool oldS xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(r->getStartTime(), ZeroTime)); xml.endTag(); xml.startTag("FinishTime"); - xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(r->getFinishTimeAdjusted(), ZeroTime)); + xml.write("Clock", "clockFormat", hhmmss, formatTimeIOF(r->getFinishTimeAdjusted(false), ZeroTime)); xml.endTag(); xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(r->getRunningTime(true), 0)); @@ -2636,8 +2647,8 @@ void oEvent::exportTeamSplits(xmlparser &xml, const set &classes, bool oldS continue; xml.startTag("SplitTime", "sequence", itos(no++)); xml.write("ControlCode", pcourse->Controls[k]->getFirstNumber()); - if (unsigned(k)0) - xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(sp[k].time - it->tStartTime, 0)); + if (unsigned(k)0) + xml.write("Time", "timeFormat", hhmmss, formatTimeIOF(sp[k].getTime(false) - it->tStartTime, 0)); else xml.write("Time", L"--:--:--"); @@ -2662,7 +2673,8 @@ void oEvent::exportIOFSplits(IOFVersion version, const wchar_t *file, bool oldStylePatrolExport, bool useUTC, const set &classes, int leg, bool teamsAsIndividual, bool unrollLoops, - bool includeStageInfo, bool forceSplitFee) { + bool includeStageInfo, bool forceSplitFee, + bool useEventorQuirks) { xmlparser xml; xml.openOutput(file, false); @@ -2681,7 +2693,7 @@ void oEvent::exportIOFSplits(IOFVersion version, const wchar_t *file, if (version == IOF20) exportIOFResults(xml, true, classes, leg, oldStylePatrolExport); else { - IOF30Interface writer(this, forceSplitFee); + IOF30Interface writer(this, forceSplitFee, useEventorQuirks); writer.writeResultList(xml, classes, leg, useUTC, teamsAsIndividual, unrollLoops, includeStageInfo); } @@ -2691,7 +2703,9 @@ void oEvent::exportIOFSplits(IOFVersion version, const wchar_t *file, void oEvent::exportIOFStartlist(IOFVersion version, const wchar_t *file, bool useUTC, const set &classes, bool teamsAsIndividual, - bool includeStageInfo, bool forceSplitFee) { + bool includeStageInfo, + bool forceSplitFee, + bool useEventorQuirks) { xmlparser xml; oClass::initClassId(*this); @@ -2700,7 +2714,7 @@ void oEvent::exportIOFStartlist(IOFVersion version, const wchar_t *file, bool us if (version == IOF20) exportIOFStartlist(xml); else { - IOF30Interface writer(this, forceSplitFee); + IOF30Interface writer(this, forceSplitFee, useEventorQuirks); writer.writeStartList(xml, classes, useUTC, teamsAsIndividual, includeStageInfo); } xml.closeOut(); diff --git a/code/oListInfo.cpp b/code/oListInfo.cpp index 4f83296..f42720c 100644 --- a/code/oListInfo.cpp +++ b/code/oListInfo.cpp @@ -1,6 +1,6 @@ /********************i**************************************************** MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -33,6 +33,8 @@ #include "gdifonts.h" #include "generalresult.h" #include "meosexception.h" +#include "gdiimpl.h" +#include "image.h" struct PrintPostInfo { PrintPostInfo(gdioutput &gdi, const oListParam &par) : @@ -82,7 +84,7 @@ oListInfo::~oListInfo(void) { } void oListInfo::replaceType(EPostType find, EPostType replace, bool onlyFirst) { - for (auto blp : { &Head, &subHead, &listPost, &subListPost }) { + for (auto blp : { &head, &subHead, &listPost, &subListPost }) { for (auto &pp : *blp) { if (pp.type == replace && onlyFirst) return; @@ -116,37 +118,29 @@ wstring oListParam::getContentsDescriptor(const oEvent &oe) const { return cls; } -oPrintPost::oPrintPost() -{ - dx=0; - dy=0; - format=0; - type=lString; - legIndex= - 1; +oPrintPost::oPrintPost() : type(lString), dx(0), dy(0), format(0) { + legIndex = -1; linearLegIndex = true; - fixedWidth = 0; - mergeWithTmp = 0; - doMergeNext = false; - color = colorDefault; - resultModuleIndex = -1; } -oPrintPost::oPrintPost(EPostType type_, const wstring &text_, - int format_, int dx_, int dy_, pair index) { - dx=dx_; - dy=dy_; - text=text_; - format=format_; - type=type_; - legIndex=index.first; +oPrintPost::oPrintPost(EPostType type, const wstring& text, + int format, int dx, int dy, + pair index) : type(type), text(text), + format(format), dx(dx), dy(dy) { + legIndex = index.first; linearLegIndex = index.second; - fixedWidth = 0; - mergeWithTmp = 0; - doMergeNext = false; - color = colorDefault; - resultModuleIndex = -1; } +oPrintPost::oPrintPost(const wstring& image, + int style, int dx, int dy, + int width, int height) : type(lImage), text(image), + format(style), dx(dx), dy(dy), + fixedWidth(width), fixedHeight(height) { + legIndex = -1; + linearLegIndex = true; +} + + bool oListInfo::needRegenerate(const oEvent &oe) const { for(oClassList::const_iterator it = oe.Classes.begin(); it != oe.Classes.end(); ++it) { if (it->isRemoved()) @@ -679,7 +673,7 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar else { pControl ctrl = getControl(punch->getControlId()); if (!ctrl) - ctrl = getControlByType(punch->Type); + ctrl = getControlByType(punch->type); if (ctrl && ctrl->hasName()) { swprintf_s(bfw, L"%s", ctrl->getName().c_str()); @@ -689,7 +683,7 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar break; case lPunchTimeSinceLast: if (punch && punch->previousPunchTime && r && !invalidClass) { - int time = punch->Time; + int time = punch->getTimeInt(); int pTime = punch->previousPunchTime; if (pTime > 0 && time > pTime) { int t = time - pTime; @@ -716,8 +710,8 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar } switch (pp.type) { case lPunchTime: { - if (punch->Time > 0) { - swprintf_s(bfw, L"\u2013 (%s)", formatTime(punch->Time - r->getStartTime()).c_str()); + if (punch->hasTime()) { + swprintf_s(bfw, L"\u2013 (%s)", formatTime(punch->getTimeInt() - r->getStartTime()).c_str()); } else { wsptr = &makeDash(L"- (-)"); @@ -737,13 +731,13 @@ const wstring &oEvent::formatPunchStringAux(const oPrintPost &pp, const oListPar break; } case lPunchAbsTime: { - if (punch->Time > 0) - wsptr = &getAbsTime(punch->Time); + if (punch->hasTime()) + wsptr = &getAbsTime(punch->getTimeInt()); break; } case lPunchTotalTime: { - if (punch->Time > 0) - wsptr = &formatTime(punch->Time - r->getStartTime()); + if (punch->hasTime()) + wsptr = &formatTime(punch->getTimeInt() - r->getStartTime()); break; } } @@ -821,6 +815,7 @@ const wstring &oEvent::formatSpecialStringAux(const oPrintPost &pp, const oListP } break; + case lTeamCourseName: case lCourseName: case lRunnerCourse: if (pc) { @@ -828,6 +823,13 @@ const wstring &oEvent::formatSpecialStringAux(const oPrintPost &pp, const oListP } break; + case lCourseNumber: + case lTeamCourseNumber: + if (pc) { + wsptr = &itow(pc->getId()); + } + break; + case lRunnerLegNumberAlpha: if (t && t->getClassRef(false) && legIndex >= 0) { wstring legStr = t->getClassRef(false)->getLegNumber(legIndex); @@ -1029,17 +1031,36 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wchar_t wbf[512]; const wstring *wsptr=0; wbf[0]=0; + SubSecond mode = useSubSecond() ? SubSecond::On : SubSecond::Auto; auto noTimingRunner = [&]() { - return (pc ? pc->getNoTiming() : false) || (r ? (r->getStatusComputed() == StatusNoTiming || r->noTiming()) : false); + return (pc ? pc->getNoTiming() : false) || (r ? (r->getStatusComputed(true) == StatusNoTiming || r->noTiming()) : false); }; auto noTimingTeam = [&]() { - return (pc ? pc->getNoTiming() : false) || (t ? (t->getStatusComputed() == StatusNoTiming || t->noTiming()): false); + return (pc ? pc->getNoTiming() : false) || (t ? (t->getStatusComputed(true) == StatusNoTiming || t->noTiming()): false); }; bool invalidClass = pc && pc->getClassStatus() != oClass::ClassStatus::Normal; int legIndex = pp.legIndex; - if(pc && !MetaList::isResultModuleOutput(pp.type) && !MetaList::isAllStageType(pp.type)) - legIndex = pc->getLinearIndex(pp.legIndex, pp.linearLegIndex); + if(pc && MetaList::isAllLegType(pp.type)) { + if (legIndex == -1) { + if (r) + legIndex = r->getLegNumber(); + else + legIndex = pc->getNumStages() - 1; + } + else if (legIndex < max(1, pc->getNumStages())) + legIndex = pc->getLinearIndex(pp.legIndex, pp.linearLegIndex); + } + else if (pc && !MetaList::isResultModuleOutput(pp.type) && !MetaList::isAllStageType(pp.type)) { + if (legIndex == -1) { + if (r) + legIndex = r->getLegNumber(); + else + legIndex = pc->getNumStages() - 1; + } + else + legIndex = pc->getLinearIndex(pp.legIndex, pp.linearLegIndex); + } switch ( pp.type ) { case lClassName: @@ -1101,19 +1122,34 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } break; case lCourseLength: - if (r) { - pCourse crs = r->getCourse(false); - return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); - } - break; case lCourseName: case lRunnerCourse: + case lCourseNumber: if (r) { pCourse crs = r->getCourse(false); return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); } break; + case lTeamCourseNumber: + case lTeamCourseName: + if (r && pc && legIndex < pc->getNumStages()) { + pCourse crs = r->getCourse(false); + return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); + } + else if (t && pc && legIndex < pc->getNumStages()) { + pCourse crs = pc->getCourse(legIndex, t->getStartNo()); + return formatSpecialStringAux(pp, par, t, 0, crs, 0, counter); + } + break; + + case lTeamLegName: + if (pc) { + if (legIndex < pc->getNumStages()) + wcscpy_s(wbf, pc->getLegNumber(legIndex).c_str()); + } + break; + case lClassAvailableMaps: if (pc) { int n = pc->getNumRemainingMaps(false); @@ -1165,9 +1201,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lRunnerFinish: if (r && !invalidClass) { if (pp.resultModuleIndex == -1) - wsptr = &r->getFinishTimeS(); + wsptr = &r->getFinishTimeS(false, mode); else - wsptr = &r->getTempResult(pp.resultModuleIndex).getFinishTimeS(this); + wsptr = &r->getTempResult(pp.resultModuleIndex).getFinishTimeS(this, mode); } break; case lRunnerStart: @@ -1189,7 +1225,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wsptr = &r->getStartTimeCompact(); else { int st = r->getStartTime(); - wsptr = &getTimeMS(st-oe->getFirstStart()); + wsptr = &formatTimeMS(st-oe->getFirstStart(), true); } } else @@ -1199,8 +1235,8 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lRunnerCheck: if (r && !invalidClass && r->Card) { oPunch *punch = r->Card->getPunchByType(oPunch::PunchCheck); - if (punch && punch->Time > 0) - wsptr = &getAbsTime(punch->Time); + if (punch && punch->hasTime()) + wsptr = &getAbsTime(punch->getTimeInt()); else wsptr = &makeDash(L"-"); } @@ -1261,9 +1297,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lRunnerTime: if (r && !invalidClass) { if (pp.resultModuleIndex == -1) - wsptr = &r->getRunningTimeS(true); + wsptr = &r->getRunningTimeS(true, mode); else - wsptr = &r->getTempResult(pp.resultModuleIndex).getRunningTimeS(0); + wsptr = &r->getTempResult(pp.resultModuleIndex).getRunningTimeS(0, mode); if (r->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); @@ -1275,7 +1311,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (r && !invalidClass) { int tm = r->getRunningTime(true); if (tm > 0) - tm -= r->getTimeAdjustment(); + tm -= r->getTimeAdjustment(true); wsptr = &formatTime(tm); } @@ -1286,14 +1322,14 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (invalidClass) wsptr = &lang.tl("Struken"); else if (pp.resultModuleIndex == -1) { - bool ok = r->prelStatusOK(true, true); + bool ok = r->prelStatusOK(true, true, true); if (ok && !noTimingRunner()) { - wsptr = &r->getRunningTimeS(true); + wsptr = &r->getRunningTimeS(true, mode); if (r->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; } - else if (r->getStatusComputed() == StatusOutOfCompetition) { + else if (r->getStatusComputed(true) == StatusOutOfCompetition) { swprintf_s(wbf, L"(%s)", wsptr->c_str()); wsptr = 0; } @@ -1308,7 +1344,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else { const oAbstractRunner::TempResult &res = r->getTempResult(pp.resultModuleIndex); if (res.isStatusOK() && !noTimingRunner()) { - wsptr = &res.getRunningTimeS(0); + wsptr = &res.getRunningTimeS(0, mode); if (r->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; @@ -1329,8 +1365,8 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (invalidClass) wsptr = &lang.tl("Struken"); else if (pp.resultModuleIndex == -1) { - if (r->prelStatusOK(true, true) && !noTimingRunner()) { - wstring timeStatus = r->getRunningTimeS(true); + if (r->prelStatusOK(true, true, true) && !noTimingRunner()) { + wstring timeStatus = r->getRunningTimeS(true, mode); if (r->hasInputData() || (r->getLegNumber() > 0 && !r->isPatrolMember())) { RunnerStatus ts = r->getTotalStatus(); @@ -1358,7 +1394,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else { const oAbstractRunner::TempResult &res = r->getTempResult(pp.resultModuleIndex); if (res.isStatusOK() && !noTimingRunner()) { - wsptr = &res.getRunningTimeS(0); + wsptr = &res.getRunningTimeS(0, mode); if (r->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; @@ -1386,7 +1422,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if ( (t && t->getNumShortening(tleg) == 0) || (!t && r->getNumShortening() == 0)) { int after = r->getTotalRunningTime() - pc->getTotalLegLeaderTime(oClass::AllowRecompute::Yes, tleg, true, true); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } else { wsptr = &makeDash(L"-"); @@ -1399,7 +1435,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (r->getNumShortening() == 0) { int after = r->getRunningTime(true) - pc->getBestLegTime(oClass::AllowRecompute::Yes, tleg, true); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } else { wsptr = &makeDash(L"-"); @@ -1410,14 +1446,13 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else { int after = r->getTempResult(pp.resultModuleIndex).getTimeAfter(); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } } break; - case lRunnerTimePerKM: - if (r && !invalidClass && r->prelStatusOK(true, true)) { + if (r && !invalidClass && r->prelStatusOK(true, true, true)) { const pCourse pc = r->getCourse(false); if (pc) { int t = r->getRunningTime(false); @@ -1432,9 +1467,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lRunnerTotalTime: if (r && !invalidClass) { if (pp.resultModuleIndex == -1) - wsptr = &r->getTotalRunningTimeS(); + wsptr = &r->getTotalRunningTimeS(mode); else - wsptr = &r->getTempResult(pp.resultModuleIndex).getRunningTimeS(r->getTotalTimeInput()); + wsptr = &r->getTempResult(pp.resultModuleIndex).getRunningTimeS(r->getTotalTimeInput(), mode); } break; case lRunnerTotalTimeStatus: @@ -1443,8 +1478,8 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else if (r) { if (pp.resultModuleIndex == -1) { if ((r->getTotalStatus()==StatusOK || (r->getTotalStatus()==StatusUnknown - && r->prelStatusOK(true, true) && r->getInputStatus() == StatusOK) ) && !noTimingRunner()) { - wsptr = &r->getTotalRunningTimeS(); + && r->prelStatusOK(true, true, true) && r->getInputStatus() == StatusOK) ) && !noTimingRunner()) { + wsptr = &r->getTotalRunningTimeS(mode); if (r->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; @@ -1457,7 +1492,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara const oAbstractRunner::TempResult &res = r->getTempResult(pp.resultModuleIndex); RunnerStatus input = r->getTotalStatusInput(); if (input == StatusOK && res.getStatus() == StatusOK && !noTimingRunner()) { - wsptr = &res.getRunningTimeS(r->getTotalTimeInput()); + wsptr = &res.getRunningTimeS(r->getTotalTimeInput(), mode); if (r->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; @@ -1622,9 +1657,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara int afterOld = r->inputTime - pc->getBestInputTime(oClass::AllowRecompute::Yes, tleg); int ad = after - afterOld; if (ad > 0) - swprintf_s(wbf, L"+%d:%02d", ad / 60, ad % 60); + swprintf_s(wbf, L"+%s", formatTimeMS(ad, false, mode).c_str()); if (ad < 0) - swprintf_s(wbf, L"-%d:%02d", (-ad) / 60, (-ad) % 60); + wsptr = &formatTimeMS(ad, false, mode); } } break; @@ -1672,9 +1707,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; case lRunnerTimeAdjustment: if (r && !invalidClass) { - int a = r->getTimeAdjustment(); + int a = r->getTimeAdjustment(true); if (a != 0) - wsptr = &getTimeMS(a); + wsptr = &formatTimeMS(a, false); } break; case lRunnerRogainingPointOvertime: @@ -1691,7 +1726,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (pp.resultModuleIndex == -1) { int tleg=r->tLeg>=0 ? r->tLeg:0; int brt = pc->getBestLegTime(oClass::AllowRecompute::Yes, tleg, true); - if (r->prelStatusOK(true, true) && brt > 0) { + if (r->prelStatusOK(true, true, true) && brt > 0) { after=r->getRunningTime(true) - brt; } } @@ -1701,7 +1736,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (r->getNumShortening() == 0) { if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } else { wsptr = &makeDash(L"-"); @@ -1715,7 +1750,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if ( (t && t->getNumShortening(tleg) == 0) || (!t && r->getNumShortening() == 0)) { int after = r->getTotalRunningTime() - pc->getTotalLegLeaderTime(oClass::AllowRecompute::Yes, tleg, true, true); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } else { wsptr = &makeDash(L"-"); @@ -1729,7 +1764,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (crs && r->tStatus==StatusOK && !noTimingRunner()) { int after = r->getRunningTime(true) - pc->getBestTimeCourse(oClass::AllowRecompute::Yes, crs->getId()); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } } break; @@ -1753,16 +1788,16 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wcscpy_s(wbf, par.getLegName().c_str()); break; case lRunnerLostTime: - if (r && r->prelStatusOK(true, true) && !noTimingRunner() && !invalidClass) { + if (r && r->prelStatusOK(true, true, true) && !noTimingRunner() && !invalidClass) { wcscpy_s(wbf, r->getMissedTimeS().c_str()); } break; case lRunnerTempTimeAfter: if (r && pc) { - if (r->tempStatus==StatusOK && pc && !noTimingRunner() - && r->tempRT>pc->tLegLeaderTime) { - int after=r->tempRT-pc->tLegLeaderTime; - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + if (r->tempStatus == StatusOK && pc && !noTimingRunner() + && r->tempRT > pc->tLegLeaderTime) { + int after = r->tempRT - pc->tLegLeaderTime; + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } } break; @@ -1815,6 +1850,11 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara swprintf_s(wbf, L"%d", y); } break; + case lRunnerBirthDate: + if (r) { + wsptr = &r->getBirthDate(); + } + break; case lRunnerSex: if (r) { PersonSex s = r->getSex(); @@ -1903,7 +1943,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara wsptr = &t->Runners[legIndex]->getStartTimeCompact(); else { int st = t->Runners[legIndex]->getStartTime(); - wsptr = &getTimeMS(st-3600); + wsptr = &formatTimeMS(st-timeConstHour, true); } } } @@ -1919,9 +1959,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lTeamTime: if (t && !invalidClass) { if (pp.resultModuleIndex == -1) - wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, false).c_str() ); + wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, false, mode).c_str() ); else - wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0); + wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0, mode); } break; case lTeamGrossTime: @@ -1938,9 +1978,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara RunnerStatus st = t->getLegStatus(legIndex, true, false); if (st == StatusOK || ((st == StatusUnknown || st == StatusOutOfCompetition) && t->getLegRunningTime(legIndex, true, false) > 0)) { if (st != StatusOutOfCompetition) - wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, false).c_str()); + wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, false, mode).c_str()); else - swprintf_s(wbf, L"(%s)", t->getLegRunningTimeS(legIndex, true, false).c_str()); + swprintf_s(wbf, L"(%s)", t->getLegRunningTimeS(legIndex, true, false, mode).c_str()); } else wsptr = &t->getLegStatusS(legIndex, true, false); @@ -1948,7 +1988,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else { RunnerStatus st = t->getTempResult(pp.resultModuleIndex).getStatus(); if (st == StatusOK || st == StatusUnknown) { - wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0); + wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0, mode); if (t->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; @@ -2004,9 +2044,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lTeamTimeAdjustment: if (t && !invalidClass) { - int a = t->getTimeAdjustment(); + int a = t->getTimeAdjustment(true); if (a != 0) - wsptr = &getTimeMS(a); + wsptr = &formatTimeMS(a, false); } break; @@ -2015,9 +2055,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (pp.resultModuleIndex == -1) { if (t->getLegStatus(legIndex, true, false)==StatusOK) { if (t->getNumShortening(legIndex) == 0) { - int ta=t->getTimeAfter(legIndex); + int ta=t->getTimeAfter(legIndex, true); if (ta>0) - swprintf_s(wbf, L"+%d:%02d", ta/60, ta%60); + swprintf_s(wbf, L"+%s", formatTimeMS(ta, false, mode).c_str()); } else { wsptr = &makeDash(L"-"); @@ -2028,13 +2068,13 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (t->getTempResult().getStatus() == StatusOK) { int after = t->getTempResult(pp.resultModuleIndex).getTimeAfter(); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } } } break; case lTeamPlace: - if (t && !invalidClass && !noTimingTeam()) { + if (t && !invalidClass && !noTimingTeam() && pc && pc->isValidLeg(legIndex)) { if (pp.resultModuleIndex == -1) { wcscpy_s(wbf, t->getLegPrintPlaceS(legIndex, false, pp.text.empty()).c_str()); } @@ -2047,24 +2087,41 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (invalidClass) wsptr = &lang.tl("Struken"); else if (t) { - int ix = r ? r->getLegNumber() : counter.level3; + /*int ix = r ? r->getLegNumber() : counter.level3; + if (pc) + ix = pc->getResultDefining(ix);*/ + int ix = legIndex; + if (pc) { + if (!pc->isValidLeg(legIndex)) + break; + ix = pc->getResultDefining(ix); + } RunnerStatus st = t->getLegStatus(ix, true, false); if (st == StatusOK) - wcscpy_s(wbf, t->getLegRunningTimeS(ix, true, false).c_str() ); + wcscpy_s(wbf, t->getLegRunningTimeS(ix, true, false, mode).c_str() ); else if (st == StatusOutOfCompetition && t->getLegRunningTime(ix, true, false) > 0) - swprintf_s(wbf, L"(%s)", t->getLegRunningTimeS(ix, true, false).c_str()); + swprintf_s(wbf, L"(%s)", t->getLegRunningTimeS(ix, true, false, mode).c_str()); else wcscpy_s(wbf, t->getLegStatusS(ix, true, false).c_str() ); } break; case lTeamLegTimeAfter: if (t) { - int ix = r ? r->getLegNumber() : counter.level3; + /*int ix = r ? r->getLegNumber() : counter.level3; xxx + if (pc) + ix = pc->getResultDefining(ix); + */ + int ix = legIndex; + if (pc) { + if (!pc->isValidLeg(legIndex)) + break; + ix = pc->getResultDefining(ix); + } if (t->getLegStatus(ix, true, false)==StatusOK && !invalidClass) { if (t->getNumShortening(ix) == 0) { - int ta=t->getTimeAfter(ix); + int ta=t->getTimeAfter(ix, true); if (ta>0) - swprintf_s(wbf, L"+%d:%02d", ta/60, ta%60); + swprintf_s(wbf, L"+%s", formatTimeMS(ta, false, mode).c_str()); } else { wsptr = &makeDash(L"-"); @@ -2092,7 +2149,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } break; case lTeamTotalTime: - if (t && !invalidClass) wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, true).c_str() ); + if (t && !invalidClass) wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, true, mode).c_str() ); break; case lTeamTotalTimeStatus: if (invalidClass) @@ -2100,7 +2157,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else if (t) { if (pp.resultModuleIndex == -1) { if (t->getLegStatus(legIndex, true, true)==StatusOK) - wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, true).c_str() ); + wcscpy_s(wbf, t->getLegRunningTimeS(legIndex, true, true, mode).c_str() ); else wcscpy_s(wbf, t->getLegStatusS(legIndex, true, true).c_str() ); } @@ -2108,7 +2165,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara RunnerStatus st = t->getTempResult(pp.resultModuleIndex).getStatus(); RunnerStatus inp = t->getInputStatus(); if (st == StatusOK && inp == StatusOK) { - wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0); + wsptr = &t->getTempResult(pp.resultModuleIndex).getRunningTimeS(0, mode); if (t->getNumShortening() > 0) { swprintf_s(wbf, L"*%s", wsptr->c_str()); wsptr = 0; @@ -2142,7 +2199,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara if (t->getTotalStatus()==StatusOK && pc && !noTimingTeam()) { int after = t->getTotalRunningTime() - pc->getTotalLegLeaderTime(oClass::AllowRecompute::Yes, tleg, true, true); if (after > 0) - swprintf_s(wbf, L"+%d:%02d", after/60, after%60); + swprintf_s(wbf, L"+%s", formatTimeMS(after, false, mode).c_str()); } } break; @@ -2154,9 +2211,9 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara int afterOld = t->inputTime - pc->getBestInputTime(oClass::AllowRecompute::Yes, tleg); int ad = after - afterOld; if (ad > 0) - swprintf_s(wbf, L"+%d:%02d", ad/60, ad%60); + swprintf_s(wbf, L"+%s", formatTimeMS(ad, false, mode).c_str()); if (ad < 0) - swprintf_s(wbf, L"-%d:%02d", (-ad)/60, (-ad)%60); + wsptr = &formatTimeMS(ad, false, mode); } } break; @@ -2212,22 +2269,22 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara } switch (pp.type) { case lPunchNamedSplit: - if (ctrl && ctrl->hasName() && r->getPunchTime(counter.level3, false) > 0) { + if (ctrl && ctrl->hasName() && r->getPunchTime(counter.level3, false, true) > 0) { swprintf_s(wbf, L"%s", r->getNamedSplitS(counter.level3).c_str()); } break; case lPunchNamedTime: - if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false) > 0)) { + if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false, true) > 0)) { swprintf_s(wbf, L"%s: %s (%s)", ctrl->getName().c_str(), r->getNamedSplitS(counter.level3).c_str(), - r->getPunchTimeS(counter.level3, false).c_str()); + r->getPunchTimeS(counter.level3, false, true, SubSecond::Off).c_str()); } break; case lControlName: case lPunchName: - if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false) > 0)) { + if (ctrl && ctrl->hasName() && (!par.lineBreakControlList || r->getPunchTime(counter.level3, false, true) > 0)) { swprintf_s(wbf, L"%s", ctrl->getName().c_str()); } else if (counter.level3 == nCtrl) { @@ -2238,7 +2295,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lPunchTime: { swprintf_s(wbf, L"%s (%s)", r->getSplitTimeS(counter.level3, false).c_str(), - r->getPunchTimeS(counter.level3, false).c_str()); + r->getPunchTimeS(counter.level3, false, true, SubSecond::Off).c_str()); break; } case lPunchSplitTime: { @@ -2246,13 +2303,13 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; } case lPunchTotalTime: { - if (r->getPunchTime(counter.level3, false) > 0) { - wcscpy_s(wbf, r->getPunchTimeS(counter.level3, false).c_str()); + if (r->getPunchTime(counter.level3, false, true) > 0) { + wcscpy_s(wbf, r->getPunchTimeS(counter.level3, false, true, SubSecond::Off).c_str()); } break; } case lPunchTotalTimeAfter: { - if (r->getPunchTime(counter.level3, false) > 0) { + if (r->getPunchTime(counter.level3, false, true) > 0) { int rt = r->getLegTimeAfterAcc(counter.level3); if (rt > 0) wcscpy_s(wbf, (L"+" + formatTime(rt)).c_str()); @@ -2266,7 +2323,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara case lPunchControlCode: { const oControl *ctrl = crs->getControl(counter.level3); if (ctrl) { - if (ctrl->getStatus() == oControl::StatusMultiple) { + if (ctrl->getStatus() == oControl::ControlStatus::StatusMultiple) { wstring str = ctrl->getStatusS(); swprintf_s(wbf, L"%s.", str.substr(0, 1).c_str()); } @@ -2292,7 +2349,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara break; } case lPunchAbsTime: { - int t = r->getPunchTime(counter.level3, false); + int t = r->getPunchTime(counter.level3, false, true); if (t > 0) wsptr = &getAbsTime(r->tStartTime + t); break; @@ -2318,6 +2375,43 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara else swprintf_s(wbf, L"%d", counter.level1+1); break; + + case lTotalRunLength: { + int sum = 0; + for (auto& r : Runners) { + if (!r.isRemoved() && r.getCourse(false) && r.isStatusOK(false, false)) + sum += r.getCourse(false)->getLength(); + } + wsptr = &itow(sum/1000); + break; + } + case lTotalRunTime: { + int sum = 0; + for (auto& r : Runners) { + if (!r.isRemoved()) + sum += r.getRunningTime(false); + } + wsptr = &formatTimeHMS(sum); + break; + } + case lNumEntries: { + int sum = 0; + for (auto& r : Runners) { + if (!r.isRemoved() && r.tParentRunner == nullptr) + sum++; + } + wsptr = &itow(sum); + break; + } + case lNumStarts: { + int sum = 0; + for (auto& r : Runners) { + if (!r.isRemoved() && r.tParentRunner == nullptr && (oAbstractRunner::isResultStatus(r.getStatus()) || r.isStatusOK(false, false))) + sum++; + } + wsptr = &itow(sum); + break; + } case lClubName: wsptr = c != 0 ? &c->getDisplayName() : 0; break; @@ -2360,7 +2454,7 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara const pControl ctrl = crs->getControl(punch->tRogainingIndex); if (ctrl) { swprintf_s(wbf, L"%d, %dp, %s (%s)", - punch->Type, ctrl->getRogainingPoints(), + punch->type, ctrl->getRogainingPoints(), r->Card->getRogainingSplit(counter.level3, r->tStartTime).c_str(), punch->getRunningTime(r->tStartTime).c_str()); } @@ -2403,15 +2497,14 @@ const wstring &oEvent::formatListStringAux(const oPrintPost &pp, const oListPara bool oEvent::formatPrintPost(const list &ppli, PrintPostInfo &ppi, const pTeam t, const pRunner r, const pClub c, const pClass pc, const pCourse crs, const pControl ctrl, - const oPunch *punch, int legIndex) -{ - list::const_iterator ppit; - int y=ppi.gdi.getCY(); - int x=ppi.gdi.getCX(); - bool updated=false; + const oPunch *punch, int legIndex) { + int y = ppi.gdi.getCY(); + int x = ppi.gdi.getCX(); + bool updated = false; int lineHeight = 0; - int pdx = 0, pdy = 0; - for (ppit = ppli.begin(); ppit != ppli.end();) { + int pdx = 0, pdy = 0; + auto ppit = ppli.begin(); + while (ppit != ppli.end()) { const oPrintPost &pp = *ppit; if (pp.type == lLineBreak) { @@ -2421,6 +2514,17 @@ bool oEvent::formatPrintPost(const list &ppli, PrintPostInfo &ppi, ++ppit; continue; } + else if (pp.type == lImage) { + pdy = ppi.gdi.scaleLength(pp.dy); + pdx = ppi.gdi.scaleLength(pp.dx); + int format = 0; + if (pp.imageNoUpdatePos) + format |= imageNoUpdatePos; + ppi.gdi.addImage("", y + pdy, x + pdx, format, pp.text, + ppi.gdi.scaleLength(pp.fixedWidth), ppi.gdi.scaleLength(pp.fixedHeight)); + ++ppit; + continue; + } int limit = ppit->xlimit; @@ -2444,8 +2548,13 @@ bool oEvent::formatPrintPost(const list &ppli, PrintPostInfo &ppi, pRunner rr = r; if (!rr && t) { if (pp.legIndex >= 0) { - int lg = pc ? pc->getLinearIndex(pp.legIndex, pp.linearLegIndex) : pp.legIndex; - rr = t->getRunner(lg); + if (pc) { + int lg = pp.legIndex; + if (pp.legIndex < max(1, pc->getNumStages())) + lg = pc->getLinearIndex(pp.legIndex, pp.linearLegIndex); + + rr = t->getRunner(lg); + } } else if (legIndex >= 0) rr = t->getRunner(legIndex); @@ -2536,7 +2645,7 @@ void oEvent::listGeneratePunches(const oListInfo &listInfo, gdioutput &gdi, if (cls && cls->getNoTiming()) return; - if (r && (r->getStatusComputed() == StatusNoTiming || r->noTiming())) + if (r && (r->getStatusComputed(true) == StatusNoTiming || r->noTiming())) return; int h = gdi.getLineHeight(); @@ -2648,7 +2757,7 @@ void oEvent::listGeneratePunches(const oListInfo &listInfo, gdioutput &gdi, continue; if (filterFinish && punch.isFinish(finishType)) continue; - prevPunchTime = punch.Time; + prevPunchTime = punch.getTimeInt(); if (w > 0 && updated) { updated = false; if (gdi.getCX() + w > xlimit || newLine) { @@ -2684,9 +2793,9 @@ void oEvent::generateList(gdioutput &gdi, bool reEvaluate, const oListInfo &li, oe->setGeneralResultContext(&li.lp); wstring listname; - if (!li.Head.empty()) { + if (!li.head.empty()) { oCounter counter; - const wstring &name = formatListString(li.Head.front(), li.lp, 0, 0, 0, 0, counter); + const wstring &name = formatListString(li.head.front(), li.lp, 0, 0, 0, 0, counter); listname = name; li.lp.updateDefaultName(name); } @@ -2840,7 +2949,7 @@ bool oListInfo::filterRunnerResult(GeneralResult *gResult, const oRunner &r) con } else if (filter(EFilterHasPrelResult)) { if (gResult == 0) { - if (lp.useControlIdResultTo <= 0 && (r.tStatus == StatusUnknown || isPossibleResultStatus(r.getStatusComputed())) && r.getRunningTime(false) <= 0) + if (lp.useControlIdResultTo <= 0 && (r.tStatus == StatusUnknown || isPossibleResultStatus(r.getStatusComputed(true))) && r.getRunningTime(false) <= 0) return true; else if ((lp.useControlIdResultTo > 0 || lp.useControlIdResultFrom > 0) && r.tempStatus != StatusOK) return true; @@ -2872,6 +2981,43 @@ GeneralResult *oListInfo::applyResultModule(oEvent &oe, vector &rlist) return gResult; } +void oEvent::formatHeader(gdioutput& gdi, const oListInfo& li, const pRunner rInput) { + vector< pair > v; + for (auto& lp : li.head) { + bool strUpdate = lp.type == lCmpName || lp.type == lString; + if (lp.xlimit == 0 && !strUpdate) { + v.clear(); + v.emplace_back(lp.type, lp.text); + gdiFonts font = lp.getFont(); + lp.xlimit = li.getMaxCharWidth(this, gdi, li.getParam().selection, v, font, lp.fontFace.c_str()); + } + else if (strUpdate) { + v.clear(); + const_cast(lp.text) = li.lp.getCustomTitle(lp.text); + v.emplace_back(lp.type, lp.text); + gdiFonts font = lp.getFont(); + lp.xlimit = li.getMaxCharWidth(this, gdi, li.getParam().selection, v, font, lp.fontFace.c_str()); + } + } + + pTeam team = nullptr; + pClass cls = nullptr; + pClub club = nullptr; + pCourse crs = nullptr; + int legIndex = -1; + if (rInput) { + team = rInput->getTeam(); + club = rInput->getClubRef(); + crs = rInput->getCourse(true); + cls = rInput->getClassRef(true); + } + + PrintPostInfo printPostInfo(gdi, li.lp); + formatPrintPost(li.head, printPostInfo, team, rInput, + club, cls, crs, + nullptr, nullptr, -1); +} + void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool formatHead) { li.setupLinks(); pClass sampleClass = 0; @@ -2907,7 +3053,7 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form } } } - else if (li.listType == li.EBaseTypeTeam) { + else if (li.listType == li.EBaseTypeTeam || li.listType == li.EBaseTypeClubTeam) { if (li.calcResults) calculateTeamResults(li.lp.selection, ResultType::ClassResult); if (li.calcTotalResults) @@ -2918,7 +3064,7 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form if (li.calcCourseClassResults) calculateResults(li.lp.selection, ResultType::ClassCourseResult); } - else if (li.listType == li.EBaseTypeClub) { + else if (li.listType == li.EBaseTypeClubRunner) { if (li.calcResults) { calculateTeamResults(li.lp.selection, ResultType::TotalResult); calculateTeamResults(li.lp.selection, ResultType::ClassResult); @@ -2943,7 +3089,7 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form //oCounter counter; //Render header vector< pair > v; - for (auto &listPostList : { &li.Head, &li.subHead, &li.listPost, &li.subListPost }) { + for (auto &listPostList : { &li.subHead, &li.listPost, &li.subListPost }) { for (auto &lp : *listPostList) { if (lp.xlimit == 0) { v.clear(); @@ -2954,20 +3100,9 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form } } - if (formatHead && li.getParam().showHeader) { - for (auto &h : li.Head) { - if (h.type == lCmpName || h.type == lString) { - v.clear(); - const_cast(h.text) = li.lp.getCustomTitle(h.text); - v.emplace_back(h.type, h.text); - gdiFonts font = h.getFont(); - h.xlimit = li.getMaxCharWidth(this, gdi, li.getParam().selection, v, font, h.fontFace.c_str()); - break; - } - } - formatPrintPost(li.Head, printPostInfo, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, -1); - } + if (formatHead && li.getParam().showHeader) + formatHeader(gdi, li, nullptr); + if (li.fixedType) { generateFixedList(gdi, li); return; @@ -2987,9 +3122,11 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form tlist.push_back(&*it); } - wstring oldKey; - if (li.listType == li.EBaseTypeRunner) { - vector rlist, rlistInput; + vector rlist; + GeneralResult *gResult = nullptr; + + if (li.listType == li.EBaseTypeRunner || li.listType == li.EBaseTypeClubRunner) { + vector rlistInput; getRunners(li.lp.selection, rlistInput, false); rlist.reserve(rlistInput.size()); for (auto r : rlistInput) { @@ -3000,8 +3137,227 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form if (li.sortOrder != Custom && !calculatedSplitResults) sortRunners(li.sortOrder, rlist); - GeneralResult *gResult = li.applyResultModule(*this, rlist); + gResult = li.applyResultModule(*this, rlist); + } + + wstring oldKey; + + auto formatTeam = [this, &gdi, &li, &gResult, &printPostInfo, &oldKey](pTeam it, + bool includeSubHead, int &parLegRangeMin, int &parLegRangeMax, pClass &parLegRangeClass) { + int linearLegSpec = li.lp.getLegNumber(it->getClassRef(false)); + + if (gResult && it->getTempResult(0).getStatus() == StatusNotCompetiting) + return false; + + if (li.filter(EFilterExcludeDNS)) { + if (it->tStatus == StatusDNS) + return false; + } + + if (li.filter(EFilterVacant)) { + if (it->isVacant()) + return false; + } + + if (li.filter(EFilterOnlyVacant)) { + if (!it->isVacant()) + return false; + } + + if (li.filter(EFilterHasResult)) { + if (gResult) { + RunnerStatus st = it->getTempResult(0).getStatus(); + if (st == StatusUnknown || (isPossibleResultStatus(st) && it->getTempResult(0).getRunningTime() <= 0)) + return false; + } + else { + RunnerStatus st = it->getLegStatus(linearLegSpec, true, false); + if (st == StatusUnknown || (isPossibleResultStatus(st) && it->getLegRunningTime(linearLegSpec, true, false) <= 0)) + return false; + else if (li.calcTotalResults) { + st = it->getLegStatus(linearLegSpec, true, true); + if (st == StatusUnknown || (isPossibleResultStatus(st) && it->getLegRunningTime(linearLegSpec, true, true) <= 0)) + return false; + } + } + } + else if (li.filter(EFilterHasPrelResult)) { + if (gResult) { + RunnerStatus st = it->getTempResult(0).getStatus(); + if ((st == StatusUnknown || isPossibleResultStatus(st)) && it->getTempResult(0).getRunningTime() <= 0) + return false; + } + else { + RunnerStatus st = it->getLegStatus(linearLegSpec, true, false); + if ((st == StatusUnknown || isPossibleResultStatus(st)) && it->getLegRunningTime(linearLegSpec, true, false) <= 0) + return false; + else if (li.calcTotalResults) { + RunnerStatus st = it->getLegStatus(linearLegSpec, true, true); + + if ((st == StatusUnknown || isPossibleResultStatus(st)) && it->getLegRunningTime(linearLegSpec, true, true) <= 0) + return false; + } + } + } + + const bool needParRange = li.subFilter(ESubFilterSameParallel) + || li.subFilter(ESubFilterSameParallelNotFirst); + + if (needParRange && it->Class != parLegRangeClass && it->Class) { + parLegRangeClass = it->Class; + parLegRangeClass->getParallelRange(linearLegSpec < 0 ? 0 : linearLegSpec, parLegRangeMin, parLegRangeMax); + if (li.subFilter(ESubFilterSameParallelNotFirst)) + parLegRangeMin++; + } + + if (includeSubHead) { + wstring newKey; + printPostInfo.par.relayLegIndex = linearLegSpec; + calculatePrintPostKey(li.subHead, gdi, li.lp, &*it, 0, it->Club, it->Class, printPostInfo.counter, newKey); + if (newKey != oldKey) { + if (!oldKey.empty()) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + + wstring legInfo; + if (linearLegSpec >= 0 && it->getClassRef(false)) { + // Specified leg + legInfo = lang.tl(L", Str. X#" + li.lp.getLegName()); + } + + gdi.addStringUT(pagePageInfo, it->getClass(true) + legInfo); // Teamlist + + oldKey.swap(newKey); + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; + printPostInfo.reset(); + printPostInfo.par.relayLegIndex = linearLegSpec; + formatPrintPost(li.subHead, printPostInfo, &*it, 0, it->Club, it->Class, + nullptr, nullptr, nullptr, -1); + } + } + ++printPostInfo.counter; + if (li.lp.filterInclude(printPostInfo.counter.level2, it)) { + printPostInfo.counter.level3 = 0; + printPostInfo.reset(); + printPostInfo.par.relayLegIndex = linearLegSpec; + formatPrintPost(li.listPost, printPostInfo, it, 0, it->Club, it->Class, + nullptr, nullptr, nullptr, -1); + + if (li.subListPost.empty()) + return true; + + if (li.listSubType == li.EBaseTypeRunner) { + int nr = int(it->Runners.size()); + vector tr; + tr.reserve(nr); + vector usedIx(nr, -1); + + for (int k = 0; k < nr; k++) { + if (!it->Runners[k]) { + if (li.filter(EFilterHasResult) || li.subFilter(ESubFilterHasResult) || + li.filter(EFilterHasPrelResult) || li.subFilter(ESubFilterHasPrelResult) || + li.filter(EFilterExcludeDNS) || li.filter(EFilterExcludeCANCEL) || li.subFilter(ESubFilterExcludeDNS) || + li.subFilter(ESubFilterVacant)) { + usedIx[k] = -2; // Skip totally + } + continue; + } + else + usedIx[k] = -2; // Skip unless allowed after filters + bool noResult = false; + bool noPrelResult = false; + bool noStart = false; + bool cancelled = false; + if (gResult == 0) { + noResult = it->Runners[k]->tStatus == StatusUnknown; + noPrelResult = it->Runners[k]->tStatus == StatusUnknown && it->Runners[k]->getRunningTime(false) <= 0; + noStart = it->Runners[k]->tStatus == StatusDNS || it->Runners[k]->tStatus == StatusCANCEL; + if (it->Runners[k]->Class && it->Runners[k]->Class->isQualificationFinalBaseClass()) { + if (k > 0 && it->Runners[k]->getClassRef(true) == it->Runners[k]->Class) + noStart = true; + } + + cancelled = it->Runners[k]->tStatus == StatusCANCEL; + //XXX TODO Multiday + } + else { + noResult = it->Runners[k]->tmpResult.status == StatusUnknown; + noPrelResult = it->Runners[k]->tmpResult.status == StatusUnknown && it->Runners[k]->tmpResult.runningTime <= 0; + noStart = it->Runners[k]->tmpResult.status == StatusDNS || it->Runners[k]->tmpResult.status == StatusCANCEL; + cancelled = it->Runners[k]->tmpResult.status == StatusCANCEL; + } + + if (noResult && (li.filter(EFilterHasResult) || li.subFilter(ESubFilterHasResult))) + continue; + + if (noPrelResult && (li.filter(EFilterHasPrelResult) || li.subFilter(ESubFilterHasPrelResult))) + continue; + + if (noStart && (li.filter(EFilterExcludeDNS) || li.subFilter(ESubFilterExcludeDNS))) + continue; + + if (cancelled && li.filter(EFilterExcludeCANCEL)) + continue; + + if (it->Runners[k]->isVacant() && li.subFilter(ESubFilterVacant)) + continue; + + if ((it->Runners[k]->tLeg < parLegRangeMin || it->Runners[k]->tLeg > parLegRangeMax) + && needParRange) + continue; + + usedIx[k] = tr.size(); + tr.push_back(it->Runners[k]); + } + + if (gResult) { + gResult->sortTeamMembers(tr); + + for (size_t k = 0; k < tr.size(); k++) { + bool suitableBreak = k < 2 || (k + 2) >= tr.size(); + printPostInfo.keepToghether = suitableBreak; + printPostInfo.par.relayLegIndex = tr[k] ? tr[k]->tLeg : -1; + formatPrintPost(li.subListPost, printPostInfo, &*it, tr[k], + it->Club, tr[k]->getClassRef(true), + nullptr, nullptr, nullptr, -1); + printPostInfo.counter.level3++; + } + } + else { + for (size_t k = 0; k < usedIx.size(); k++) { + if (usedIx[k] == -2) + continue; // Skip + bool suitableBreak = k < 2 || (k + 2) >= usedIx.size(); + printPostInfo.keepToghether = suitableBreak; + printPostInfo.par.relayLegIndex = k; + if (usedIx[k] == -1) { + pCourse crs = it->Class ? it->Class->getCourse(k, it->StartNo) : 0; + formatPrintPost(li.subListPost, printPostInfo, &*it, 0, + it->Club, it->Class, crs, nullptr, nullptr, k); + } + else { + formatPrintPost(li.subListPost, printPostInfo, &*it, tr[usedIx[k]], + it->Club, tr[usedIx[k]]->getClassRef(true), + nullptr, nullptr, nullptr, -1); + } + printPostInfo.counter.level3++; + } + } + } + else if (li.listSubType == li.EBaseTypeCoursePunches || + li.listSubType == li.EBaseTypeAllPunches) { + pRunner r = it->Runners.empty() ? 0 : it->Runners[0]; + if (!r) + return true; + + listGeneratePunches(li, gdi, &*it, r, it->Club, it->Class); + } + } + return true; + }; + + if (li.listType == li.EBaseTypeRunner) { for (size_t k = 0; k < rlist.size(); k++) { pRunner it = rlist[k]; if (li.filterRunnerResult(gResult, *it)) @@ -3045,7 +3401,6 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form sortTeams(li.sortOrder, legInfo.first, legInfo.second, tlist); } - GeneralResult *gResult = 0; if (!li.resultModule.empty()) { wstring src; gResult = getGeneralResult(li.resultModule, src).get(); @@ -3054,242 +3409,25 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form } // Range of runners to include int parLegRangeMin = 0, parLegRangeMax = 1000; - pClass parLegRangeClass = 0; - const bool needParRange = li.subFilter(ESubFilterSameParallel) - || li.subFilter(ESubFilterSameParallelNotFirst); - - for (size_t k = 0; k < tlist.size(); k++) { - pTeam it = tlist[k]; - int linearLegSpec = li.lp.getLegNumber(it->getClassRef(false)); - - if (gResult && it->getTempResult(0).getStatus() == StatusNotCompetiting) - continue; - - if (li.filter(EFilterExcludeDNS)) - if (it->tStatus == StatusDNS) - continue; - - if (li.filter(EFilterVacant)) - if (it->isVacant()) - continue; - - if (li.filter(EFilterOnlyVacant)) { - if (!it->isVacant()) - continue; - } - - if (li.filter(EFilterHasResult)) { - if (gResult) { - RunnerStatus st = it->getTempResult(0).getStatus(); - if (st == StatusUnknown || (isPossibleResultStatus(st) && it->getTempResult(0).getRunningTime()<=0)) - continue; - } - else { - RunnerStatus st = it->getLegStatus(linearLegSpec, true, false); - if (st == StatusUnknown || (isPossibleResultStatus(st) && it->getLegRunningTime(linearLegSpec, true, false) <=0)) - continue; - else if (li.calcTotalResults) { - st = it->getLegStatus(linearLegSpec, true, true); - if (st == StatusUnknown || (isPossibleResultStatus(st) && it->getLegRunningTime(linearLegSpec, true, true) <= 0)) - continue; - } - } - } - else if (li.filter(EFilterHasPrelResult)) { - if (gResult) { - RunnerStatus st = it->getTempResult(0).getStatus(); - if ((st == StatusUnknown || isPossibleResultStatus(st)) && it->getTempResult(0).getRunningTime() <= 0) - continue; - } - else { - RunnerStatus st = it->getLegStatus(linearLegSpec, true, false); - if ((st == StatusUnknown || isPossibleResultStatus(st)) && it->getLegRunningTime(linearLegSpec, true, false) <= 0) - continue; - else if (li.calcTotalResults) { - RunnerStatus st = it->getLegStatus(linearLegSpec, true, true); - - if ((st == StatusUnknown || isPossibleResultStatus(st)) && it->getLegRunningTime(linearLegSpec, true, true) <= 0) - continue; - } - } - } - - if (needParRange && it->Class != parLegRangeClass && it->Class) { - parLegRangeClass = it->Class; - parLegRangeClass->getParallelRange(linearLegSpec < 0 ? 0 : linearLegSpec, parLegRangeMin, parLegRangeMax); - if (li.subFilter(ESubFilterSameParallelNotFirst)) - parLegRangeMin++; - } - - wstring newKey; - printPostInfo.par.relayLegIndex = linearLegSpec; - calculatePrintPostKey(li.subHead, gdi, li.lp, &*it, 0, it->Club, it->Class, printPostInfo.counter, newKey); - if (newKey != oldKey) { - if (!oldKey.empty()) - gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); - - wstring legInfo; - if (linearLegSpec >= 0 && it->getClassRef(false)) { - // Specified leg - legInfo = lang.tl(L", Str. X#" + li.lp.getLegName()); - } - - gdi.addStringUT(pagePageInfo, it->getClass(true) + legInfo); // Teamlist - - oldKey.swap(newKey); - printPostInfo.counter.level2 = 0; - printPostInfo.counter.level3 = 0; - printPostInfo.reset(); - printPostInfo.par.relayLegIndex = linearLegSpec; - formatPrintPost(li.subHead, printPostInfo, &*it, 0, it->Club, it->Class, - nullptr, nullptr, nullptr, -1); - } - ++printPostInfo.counter; - if (li.lp.filterInclude(printPostInfo.counter.level2, it)) { - printPostInfo.counter.level3 = 0; - printPostInfo.reset(); - printPostInfo.par.relayLegIndex = linearLegSpec; - formatPrintPost(li.listPost, printPostInfo, it, 0, it->Club, it->Class, - nullptr, nullptr, nullptr, -1); - - if (li.subListPost.empty()) - continue; - - if (li.listSubType == li.EBaseTypeRunner) { - int nr = int(it->Runners.size()); - vector tr; - tr.reserve(nr); - vector usedIx(nr, -1); - - for (int k = 0; k < nr; k++) { - if (!it->Runners[k]) { - if (li.filter(EFilterHasResult) || li.subFilter(ESubFilterHasResult) || - li.filter(EFilterHasPrelResult) || li.subFilter(ESubFilterHasPrelResult) || - li.filter(EFilterExcludeDNS) || li.filter(EFilterExcludeCANCEL) || li.subFilter(ESubFilterExcludeDNS) || - li.subFilter(ESubFilterVacant)) { - usedIx[k] = -2; // Skip totally - } - continue; - } - else - usedIx[k] = -2; // Skip unless allowed after filters - bool noResult = false; - bool noPrelResult = false; - bool noStart = false; - bool cancelled = false; - if (gResult == 0) { - noResult = it->Runners[k]->tStatus == StatusUnknown; - noPrelResult = it->Runners[k]->tStatus == StatusUnknown && it->Runners[k]->getRunningTime(false) <= 0; - noStart = it->Runners[k]->tStatus == StatusDNS || it->Runners[k]->tStatus == StatusCANCEL; - if (it->Runners[k]->Class && it->Runners[k]->Class->isQualificationFinalBaseClass()) { - if (k > 0 && it->Runners[k]->getClassRef(true) == it->Runners[k]->Class) - noStart = true; - } - - cancelled = it->Runners[k]->tStatus == StatusCANCEL; - //XXX TODO Multiday - } - else { - noResult = it->Runners[k]->tmpResult.status == StatusUnknown; - noPrelResult = it->Runners[k]->tmpResult.status == StatusUnknown && it->Runners[k]->tmpResult.runningTime <= 0; - noStart = it->Runners[k]->tmpResult.status == StatusDNS || it->Runners[k]->tmpResult.status == StatusCANCEL; - cancelled = it->Runners[k]->tmpResult.status == StatusCANCEL; - } - - if (noResult && (li.filter(EFilterHasResult) || li.subFilter(ESubFilterHasResult))) - continue; - - if (noPrelResult && (li.filter(EFilterHasPrelResult) || li.subFilter(ESubFilterHasPrelResult))) - continue; - - if (noStart && (li.filter(EFilterExcludeDNS) || li.subFilter(ESubFilterExcludeDNS))) - continue; - - if (cancelled && li.filter(EFilterExcludeCANCEL)) - continue; - - if (it->Runners[k]->isVacant() && li.subFilter(ESubFilterVacant)) - continue; - - if ((it->Runners[k]->tLeg < parLegRangeMin || it->Runners[k]->tLeg > parLegRangeMax) - && needParRange) - continue; - - usedIx[k] = tr.size(); - tr.push_back(it->Runners[k]); - } - - if (gResult) { - gResult->sortTeamMembers(tr); - - for (size_t k = 0; k < tr.size(); k++) { - bool suitableBreak = k < 2 || (k + 2) >= tr.size(); - printPostInfo.keepToghether = suitableBreak; - printPostInfo.par.relayLegIndex = tr[k] ? tr[k]->tLeg : -1; - formatPrintPost(li.subListPost, printPostInfo, &*it, tr[k], - it->Club, tr[k]->getClassRef(true), - nullptr, nullptr, nullptr, -1); - printPostInfo.counter.level3++; - } - } - else { - for (size_t k = 0; k < usedIx.size(); k++) { - if (usedIx[k] == -2) - continue; // Skip - bool suitableBreak = k < 2 || (k + 2) >= usedIx.size(); - printPostInfo.keepToghether = suitableBreak; - printPostInfo.par.relayLegIndex = k; - if (usedIx[k] == -1) { - pCourse crs = it->Class ? it->Class->getCourse(k, it->StartNo) : 0; - formatPrintPost(li.subListPost, printPostInfo, &*it, 0, - it->Club, it->Class, crs, nullptr, nullptr, k); - } - else { - formatPrintPost(li.subListPost, printPostInfo, &*it, tr[usedIx[k]], - it->Club, tr[usedIx[k]]->getClassRef(true), - nullptr, nullptr, nullptr, -1); - } - printPostInfo.counter.level3++; - } - } - } - else if (li.listSubType == li.EBaseTypeCoursePunches || - li.listSubType == li.EBaseTypeAllPunches) { - pRunner r = it->Runners.empty() ? 0 : it->Runners[0]; - if (!r) continue; - - listGeneratePunches(li, gdi, &*it, r, it->Club, it->Class); - } - } + pClass parLegRangeClass = nullptr; + + for (pTeam t : tlist) { + formatTeam(t, true, parLegRangeMin, parLegRangeMax, parLegRangeClass); } } - else if (li.listType == li.EBaseTypeClub) { + else if (li.listType == li.EBaseTypeClubRunner) { Clubs.sort(); - oClubList::iterator it; - oRunnerList::iterator rit; - + map> clubToRunner; - - vector rlist; - rlist.reserve(Runners.size()); - for (auto &r : Runners) { - if (!li.filterRunner(r)) - rlist.push_back(&r); - } - if (li.sortOrder != Custom) - sortRunners(li.sortOrder, rlist); - GeneralResult *gResult = li.applyResultModule(*this, rlist); - for (pRunner r : rlist) { int club = r->getClubId(); if (club == 0) continue; - clubToRunner[club].push_back(r); } bool first = true; - for (it = Clubs.begin(); it != Clubs.end(); ++it) { + for (auto it = Clubs.begin(); it != Clubs.end(); ++it) { if (it->isRemoved()) continue; @@ -3340,6 +3478,56 @@ void oEvent::generateListInternal(gdioutput &gdi, const oListInfo &li, bool form }//Runners }//Clubs } + else if (li.listType == li.EBaseTypeClubTeam) { + Clubs.sort(); + map> clubToTeam; + + for (pTeam t : tlist) { + int club = t->getClubId(); + if (club == 0) + continue; + clubToTeam[club].push_back(t); + } + + bool first = true; + int parLegRangeMin = 0, parLegRangeMax = 1000; + pClass parLegRangeClass = nullptr; + + for (auto it = Clubs.begin(); it != Clubs.end(); ++it) { + if (it->isRemoved()) + continue; + + if (li.filter(EFilterVacant)) { + if (it->getId() == getVacantClub(false)) + continue; + } + + if (li.filter(EFilterOnlyVacant)) { + if (it->getId() != getVacantClub(false)) + continue; + } + + bool startClub = false; + for (auto rit : clubToTeam[it->getId()]) { + if (!startClub) { + if (!first) + gdi.addStringUT(gdi.getCY() - 1, 0, pageNewPage, ""); + else + first = false; + + gdi.addStringUT(pagePageInfo, it->getName()); + printPostInfo.counter.level2 = 0; + printPostInfo.counter.level3 = 0; + printPostInfo.reset(); + printPostInfo.par.relayLegIndex = -1; + formatPrintPost(li.subHead, printPostInfo, nullptr, nullptr, &*it, + nullptr, nullptr, nullptr, nullptr, -1); + startClub = true; + } + formatTeam(rit, false, parLegRangeMin, parLegRangeMax, parLegRangeClass); + }//Teams + }//Clubs + } else if (li.listType == oListInfo::EBaseTypeCourse) { Courses.sort(); oCourseList::iterator it; @@ -3658,7 +3846,7 @@ void oEvent::getListTypes(map &listMap, int filterResul if (!filterResults) { oListInfo li; li.Name = lang.tl(L"Klubbstartlista"); - li.listType = li.EBaseTypeClub; + li.listType = li.EBaseTypeClubRunner; li.supportClasses = true; li.supportLegs = false; listMap[EStdClubStartList]=li; @@ -3667,7 +3855,7 @@ void oEvent::getListTypes(map &listMap, int filterResul { oListInfo li; li.Name = lang.tl(L"Klubbresultatlista"); - li.listType = li.EBaseTypeClub; + li.listType = li.EBaseTypeClubRunner; li.supportClasses = true; li.supportLegs = false; listMap[EStdClubResultList]=li; @@ -3848,7 +4036,7 @@ void oEvent::generateFixedList(gdioutput &gdi, const oListInfo &li) cName[it->getId()] = it->getName(); } - oe->getTimeLineEvents(li.lp.selection, events, stored, 3600*24*7); + oe->getTimeLineEvents(li.lp.selection, events, stored, timeConstHour*24*7); gdi.fillDown(); int yp = gdi.getCY(); int xp = gdi.getCX(); @@ -3942,6 +4130,8 @@ void oEvent::generateListInfo(vector &par, oListInfo &li) { } } +extern Image image; + void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring &name) { const int lh=14; const int vspace=lh/2; @@ -3974,7 +4164,7 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & case EStdStartList: { li.addHead(oPrintPost(lCmpName, makeDash(lang.tl(L"Startlista - %s", true)), boldLarge, 0,0)); li.addHead(oPrintPost(lCmpDate, L"", normalText, 0, 25)); - + int bib = 0; int rank = 0; if (hasBib(true, false)) { @@ -4032,7 +4222,7 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & li.addSubHead(oPrintPost(lClubName, L"", boldText, 0, 12)); li.addSubHead(oPrintPost(lString, L"", boldText, 100, 16)); - li.listType=li.EBaseTypeClub; + li.listType=li.EBaseTypeClubRunner; li.sortOrder=ClassTeamLeg; li.setFilter(EFilterExcludeDNS); li.setFilter(EFilterVacant); @@ -4061,7 +4251,7 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & li.addSubHead(oPrintPost(lClubName, L"", boldText, 0, 12)); li.addSubHead(oPrintPost(lString, L"", boldText, 100, 16)); - li.listType=li.EBaseTypeClub; + li.listType=li.EBaseTypeClubRunner; li.sortOrder=ClassResult; li.calcResults = true; li.setFilter(EFilterVacant); @@ -4098,8 +4288,8 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & pos.add("place", 25); pos.add("name", li.getMaxCharWidth(this, par.selection, lPatrolNameNames, L"", normalText, 0, false, 25)); pos.add("club", li.getMaxCharWidth(this, par.selection, lPatrolClubNameNames, L"", normalText, 0, false, 25)); - pos.add("status", 50); - pos.add("after", 50); + pos.add("status", li.getMaxCharWidth(this, par.selection, lRunnerTimeStatus, L"", normalText, 0, false, 25)); + pos.add("after", li.getMaxCharWidth(this, par.selection, lRunnerTimeAfter, L"", normalText, 0, false, 25)); pos.add("missed", 50); li.addSubHead(oPrintPost(lClassName, L"", boldText, 0, 10)); @@ -4284,11 +4474,11 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & li.addListPost(oPrintPost(lTeamTimeStatus, L"", normalText, 400, 5, make_pair(-1, true))); li.addListPost(oPrintPost(lTeamTimeAfter, L"", normalText, 460, 5, make_pair(-1, true))); - li.addSubListPost(oPrintPost(lRunnerLegNumberAlpha, L"%s.", normalText, 25, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lRunnerName, L"", normalText, 45, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lRunnerTimeStatus, L"", normalText, 280, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lTeamLegTimeStatus, L"", normalText, 400, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lTeamLegTimeAfter, L"", normalText, 460, 0, make_pair(0, true))); + li.addSubListPost(oPrintPost(lRunnerLegNumberAlpha, L"%s.", normalText, 25, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lRunnerName, L"", normalText, 45, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lRunnerTimeStatus, L"", normalText, 280, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lTeamLegTimeStatus, L"", normalText, 400, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lTeamLegTimeAfter, L"", normalText, 460, 0, make_pair(-1, true))); if (li.lp.splitAnalysis) { li.addSubListPost(oPrintPost(lRunnerLostTime, L"", normalText, 510, 0)); @@ -4430,10 +4620,10 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & li.addListPost(oPrintPost(lTeamClub, L"", normalText, 300+bib, 4)); li.listSubType=li.EBaseTypeRunner; - li.addSubListPost(oPrintPost(lRunnerLegNumberAlpha, L"%s.", normalText, 25+bib, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lRunnerName, L"", normalText, 50+bib, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lRunnerCard, L"", normalText, 300+bib, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lRunnerCourse, L"", normalText, 400+bib, 0, make_pair(0, true))); + li.addSubListPost(oPrintPost(lRunnerLegNumberAlpha, L"%s.", normalText, 25+bib, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lRunnerName, L"", normalText, 50+bib, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lRunnerCard, L"", normalText, 300+bib, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lRunnerCourse, L"", normalText, 400+bib, 0, make_pair(-1, true))); li.addSubHead(oPrintPost(lClassName, L"", boldText, 0, 10)); li.addSubHead(oPrintPost(lString, lang.tl(L"Bricka"), boldText, 300+bib, 10)); @@ -4617,10 +4807,10 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & li.addListPost(oPrintPost(lTeamTimeStatus, L"", normalText, 480, 5, make_pair(-1, true))); li.addListPost(oPrintPost(lTeamTimeAfter, L"", normalText, 540, 5, make_pair(-1, true))); - li.addSubListPost(oPrintPost(lSubSubCounter, lang.tl(L"Lopp %s"), normalText, 25, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lRunnerTimeStatus, L"", normalText, 90, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lTeamLegTimeStatus, L"", normalText, 150, 0, make_pair(0, true))); - li.addSubListPost(oPrintPost(lTeamLegTimeAfter, L"", normalText, 210, 0, make_pair(0, true))); + li.addSubListPost(oPrintPost(lSubSubCounter, lang.tl(L"Lopp %s"), normalText, 25, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lRunnerTimeStatus, L"", normalText, 90, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lTeamLegTimeStatus, L"", normalText, 150, 0, make_pair(-1, true))); + li.addSubListPost(oPrintPost(lTeamLegTimeAfter, L"", normalText, 210, 0, make_pair(-1, true))); li.lp.setLegNumberCoded(-1); li.calcResults=true; @@ -4891,7 +5081,7 @@ void oEvent::generateListInfoAux(oListParam &par, oListInfo &li, const wstring & default: if (!getListContainer().interpret(this, gdibase, par, li)) - throw std::exception("Not implemented"); + throw meosException("Could not load list 'X'#L" + itos(par.listCode)); } } @@ -4923,12 +5113,163 @@ void oListInfo::setupLinks(const list &lst) const { } void oListInfo::setupLinks() const { - setupLinks(Head); + setupLinks(head); setupLinks(subHead); setupLinks(listPost); setupLinks(subListPost); } +void oListInfo::shrinkSize() { + + auto& scale = [](int& format) -> double { + + int highFormat = format & ~0xFF; + format &= 0xFF; + + double s = 1.0; + double fs = GDIImplFontSet::baseSize(format, 1.0); + if (format == boldLarge) + format = boldText; + else if (format == boldHuge) + format = boldLarge; + else if (format == boldText) + format = boldSmall; + else if (format == normalText || format == fontMedium) + format = fontSmall; + else if (format = fontMediumPlus || format == fontLarge) + format = fontMedium; + else if (format == italicText) + format = italicSmall; + else if (format == italicMediumPlus) + format = italicText; + double fsNew = GDIImplFontSet::baseSize(format, 1.0); + format |= highFormat; + return fsNew/fs; + }; + + auto scaleForward = [](oPrintPost& pp, double scale, + list &pList, map &yOffset) { + if (pp.xlimit > 0) + pp.xlimit = int(pp.xlimit*scale); + + int minDX = -1; + for (auto& h : pList) { + if (h.dy == pp.dy && h.dx > pp.dx) { + int df = h.dx - pp.dx; + //h.dx = pp.dx + int(df * scale); + int dx = h.dx - (pp.dx + int(df * scale)); + assert(dx >= 0); + if (minDX == -1 || dx < minDX) + minDX = dx; + } + } + + for (auto& h : pList) { + if (h.dy == pp.dy && h.dx > pp.dx) { + h.dx -= minDX; + } + } + + int dy = 0; + for (auto& h : pList) { + if (h.dy > pp.dy) { + dy = h.dy - int(pp.dy + (h.dy - pp.dy) * scale); + auto res = yOffset.emplace(pp.dy, dy); + if (!res.second) { + if (dy > res.first->second) { + // Already moved. Only do the extra offset and store the highest offset. + int newDY = dy - res.first->second; + res.first->second = dy; + dy = newDY; + } + } + break; + } + } + if (dy > 0) { + for (auto& h : pList) { + if (h.dy > pp.dy) { + h.dy -= dy; + } + } + } + }; + + list* allList[4] = { &head, &subHead, &listPost, &subListPost }; + + // Store original position in a map + map>> xOffsetToListPost; + for (int j = 0; j < 4; j++) { + list& lp = *allList[j]; + int ix = 0; + for (auto& h : lp) { + int off = h.dx; + xOffsetToListPost[off].emplace_back(j, ix); + ix++; + } + } + + for (int j = 0; j < 4; j++) { + list& lp = *allList[j]; + map yOffsets; + for (auto& h : lp) { + double s = scale(h.format); + if (s < 1.0) + scaleForward(h, s, lp, yOffsets); + } + } + + map, oPrintPost*> listPostToOffsetAfter; + + for (int j = 0; j < 4; j++) { + list& lp = *allList[j]; + int ix = 0; + for (auto& h : lp) { + listPostToOffsetAfter[make_pair(j, ix)] = &h; + ix++; + } + } + + for (auto& it : xOffsetToListPost) { + int minNewPos = -1; + for (pair& lpIx : it.second) { + oPrintPost &pp = *listPostToOffsetAfter[lpIx]; + if (minNewPos == -1 || minNewPos > pp.dx) + minNewPos = pp.dx; + } + + for (pair& lpIx : it.second) { + oPrintPost& pp = *listPostToOffsetAfter[lpIx]; + int off = pp.dx - minNewPos; + + if (off > 0) { + pp.dx = minNewPos; + // TODO: Offset forward + } + } + } + + + /*for (auto& h : Head) { + scale(h.format); + } + + for (auto& h : subHead) { + scale(h.format); + } + + for (auto& h : listPost) { + double s = scale(h.format); + if (s < 1.0) + scaleForward(h, s, listPost); + } + + for (auto& h : subListPost) { + scale(h.format); + }*/ +} + + oListInfo::ResultType oListInfo::getResultType() const { return resType; } @@ -4990,3 +5331,33 @@ wstring oListParam::getLegName() const { swprintf_s(bf, L"%d%c", maj, symb); return bf; } + + +void SplitPrintListInfo::serialize(xmlparser& xml) const { + xml.writeBool("Split", includeSplitTimes); + xml.writeBool("Speed", withSpeed); + xml.writeBool("Result", withResult); + xml.writeBool("Analysis", withAnalysis); + xml.writeBool("Heading", withStandardHeading); + xml.write("NumClsResult", numClassResults); +} + +void SplitPrintListInfo::deserialize(const xmlobject& xml) { + includeSplitTimes = xml.getObjectBool("Split"); + withSpeed = xml.getObjectBool("Speed"); + withResult = xml.getObjectBool("Result"); + withAnalysis = xml.getObjectBool("Analysis"); + withStandardHeading = xml.getObjectBool("Heading"); + if (xml.got("NumClsResult")) + numClassResults = xml.getObjectInt("NumClsResult"); +} + +int64_t SplitPrintListInfo::checkSum() const { + int64_t ch = 0; + ch = 2 * ch + includeSplitTimes; + ch = 2 * ch + withSpeed; + ch = 2 * ch + withResult; + ch = 2 * ch + withAnalysis; + + return ch; +} diff --git a/code/oListInfo.h b/code/oListInfo.h index 7231148..7732e36 100644 --- a/code/oListInfo.h +++ b/code/oListInfo.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -33,8 +33,7 @@ class oClass; typedef oEvent *pEvent; typedef oClass *pClass; -enum EPostType -{ +enum EPostType { lAlignNext, lNone, lString, @@ -56,6 +55,7 @@ enum EPostType lClassNumEntries, lCourseLength, lCourseName, + lCourseNumber, lCourseClimb, lCourseShortening, lCourseUsage, @@ -121,6 +121,7 @@ enum EPostType lRunnerLegNumber, lRunnerBirthYear, + lRunnerBirthDate, lRunnerAge, lRunnerSex, lRunnerNationality, @@ -140,6 +141,9 @@ enum EPostType lTeamTimeStatus, lTeamTimeAfter, lTeamPlace, + lTeamCourseName, + lTeamCourseNumber, + lTeamLegName, lTeamLegTimeStatus, lTeamLegTimeAfter, lTeamRogainingPoint, @@ -202,17 +206,22 @@ enum EPostType lControlRunnersLeft, lControlCodes, + lNumEntries, + lNumStarts, + lTotalRunLength, + lTotalRunTime, + lRogainingPunch, lTotalCounter, lSubCounter, lSubSubCounter, + lImage, lLineBreak, lLastItem }; -enum EStdListType -{ +enum EStdListType { EStdNone=-1, EStdStartList=1, EStdResultList, @@ -293,19 +302,25 @@ enum gdiFonts; struct oPrintPost { oPrintPost(); - oPrintPost(EPostType type_, const wstring &format_, - int style_, int dx_, int dy_, - pair legIndex_=make_pair(0, true)); + oPrintPost(EPostType type, const wstring &format, + int style, int dx, int dy, + pair legIndex = make_pair(0, true)); + oPrintPost(const wstring& image, + int style, int dx, int dy, + int width, int height); static string encodeFont(const string &face, int factor); static wstring encodeFont(const wstring &face, int factor); EPostType type; + int format; + wstring text; wstring fontFace; - int resultModuleIndex; - int format; - GDICOLOR color; + + int resultModuleIndex = -1; + GDICOLOR color = colorDefault; + int dx; int dy; mutable int xlimit = 0; @@ -316,10 +331,12 @@ struct oPrintPost { fontFace = encodeFont(font, factor); return *this; } - int fixedWidth; + int fixedWidth = 0; + int fixedHeight = 0; bool useStrictWidth = false; // Crop text - bool doMergeNext; - mutable const oPrintPost *mergeWithTmp; // Merge text with this output + bool doMergeNext = false; + bool imageNoUpdatePos = false; + mutable const oPrintPost *mergeWithTmp = nullptr; // Merge text with this output }; class gdioutput; @@ -329,6 +346,19 @@ class xmlparser; class xmlobject; class MetaListContainer; +struct SplitPrintListInfo { + bool includeSplitTimes = true; + bool withSpeed = true; + bool withResult = true; + bool withAnalysis = true; + bool withStandardHeading = true; + int numClassResults = 3; + + void serialize(xmlparser& xml) const; + void deserialize(const xmlobject& xml); + int64_t checkSum() const; +}; + struct oListParam { oListParam(); @@ -462,7 +492,8 @@ class oListInfo { public: enum EBaseType {EBaseTypeRunner, EBaseTypeTeam, - EBaseTypeClub, + EBaseTypeClubRunner, + EBaseTypeClubTeam, EBaseTypeCoursePunches, EBaseTypeAllPunches, EBaseTypeNone, @@ -474,7 +505,8 @@ public: EBasedTypeLast_}; bool isTeamList() const {return listType == EBaseTypeTeam;} - + bool isSplitPrintList() const { return splitPrintInfo != nullptr; } + enum ResultType { Global, Classwise, @@ -487,17 +519,19 @@ public: SpecificPunch, AnyPunch }; - static bool addRunners(EBaseType t) {return t == EBaseTypeRunner || t == EBaseTypeClub;} - static bool addTeams(EBaseType t) {return t == EBaseTypeTeam || t == EBaseTypeClub;} - static bool addPatrols(EBaseType t) {return t == EBaseTypeTeam || t == EBaseTypeClub;} + static bool addRunners(EBaseType t) {return t == EBaseTypeRunner || t == EBaseTypeClubRunner;} + static bool addTeams(EBaseType t) {return t == EBaseTypeTeam || t == EBaseTypeClubRunner || t == EBaseType::EBaseTypeClubTeam;} + static bool addPatrols(EBaseType t) {return t == EBaseTypeTeam || t == EBaseTypeClubRunner || t == EBaseType::EBaseTypeClubTeam;} // Return true if the runner should be skipped bool filterRunner(const oRunner &r) const; bool filterRunnerResult(GeneralResult *gResult, const oRunner &r) const; GeneralResult *applyResultModule(oEvent &oe, vector &rlist) const; - const wstring &getName() const {return Name;} + + void shrinkSize(); + protected: wstring Name; EBaseType listType; @@ -514,7 +548,7 @@ protected: oListParam lp; - list Head; + list head; list subHead; list listPost; vector listPostFilter; @@ -530,6 +564,9 @@ protected: void setupLinks() const; list next; + + shared_ptr splitPrintInfo; + public: ResultType getResultType() const; @@ -564,10 +601,20 @@ public: return supportClasses && next.empty(); } + bool hasHead() const { return head.size() > 0; } + + const shared_ptr& getSplitPrintInfo() const { + return splitPrintInfo; + } + + void setSplitPrintInfo(const shared_ptr& info) { + splitPrintInfo = info; + } + EStdListType getListCode() const {return lp.listCode;} oPrintPost &addHead(const oPrintPost &pp) { - Head.push_back(pp); - return Head.back(); + head.push_back(pp); + return head.back(); } oPrintPost &addSubHead(const oPrintPost &pp) { subHead.push_back(pp); diff --git a/code/oPunch.cpp b/code/oPunch.cpp index 7246d3a..f2860d1 100644 --- a/code/oPunch.cpp +++ b/code/oPunch.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -34,10 +34,9 @@ // Construction/Destruction ////////////////////////////////////////////////////////////////////// -oPunch::oPunch(oEvent *poe): oBase(poe) -{ - Type=0; - Time=0; +oPunch::oPunch(oEvent *poe): oBase(poe) { + type=0; + punchTime=0; tTimeAdjust=0; isUsed=false; hasBeenPlayed=false; @@ -47,9 +46,7 @@ oPunch::oPunch(oEvent *poe): oBase(poe) tIndex = -1; } -oPunch::~oPunch() -{ -} +oPunch::~oPunch() = default; wstring oPunch::getInfo() const { @@ -59,44 +56,101 @@ wstring oPunch::getInfo() const string oPunch::codeString() const { char bf[32]; - sprintf_s(bf, 32, "%d-%d;", Type, Time); + sprintf_s(bf, 32, "%d-%d;", type, punchTime); return bf; } void oPunch::appendCodeString(string &dst) const { - char bf[32]; - sprintf_s(bf, 32, "%d-%d;", Type, Time); + char ubf[16]; + if (punchUnit > 0) + sprintf_s(ubf, "@%d", punchUnit); + else + ubf[0] = 0; + + char bf[48]; + if (timeConstSecond > 1 && punchTime != -1) { + if (punchTime >= 0) + sprintf_s(bf, 32, "%d-%d.%d%s;", type, punchTime / timeConstSecond, + punchTime % timeConstSecond, ubf); + else { + sprintf_s(bf, 32, "%d--%d.%d%s;", type, (-punchTime) / timeConstSecond, + (-punchTime) % timeConstSecond, ubf); + } + } + else + sprintf_s(bf, 32, "%d-%d%s;", type, punchTime, ubf); + dst.append(bf); } -void oPunch::decodeString(const string &s) -{ - Type=atoi(s.c_str()); - Time=atoi(s.substr(s.find_first_of('-')+1).c_str()); +void oPunch::decodeString(const char *s) { + const char *typeS = s; + while (*s >= '0' && *s <= '9') + ++s; + + type = atoi(typeS); + + if (*s == '-') { + ++s; + const char *timeS = s; + while ((*s >= '0' && *s <= '9') || *s == '-') + ++s; + + int t = atoi(timeS); + if (timeConstSecond > 1 && *s == '.') { + ++s; + int tenth = *s - '0'; + while ((*s >= '0' && *s <= '9')) // Eat more decimal digits (unused) + ++s; + + if (tenth > 0 && tenth < 10) { + if (t >= 0 && *timeS != '-') + punchTime = timeConstSecond * t + tenth; + else + punchTime = timeConstSecond * t - tenth; + } + else + punchTime = timeConstSecond * t; + } + else if (t == -1) + punchTime = t; + else + punchTime = timeConstSecond * t; + } + else + punchTime = 0; + + if (*s == '@') { + ++s; + punchUnit = atoi(s); + } + else { + punchUnit = 0; + } } wstring oPunch::getString() const { wchar_t bf[32]; const wchar_t *ct; - wstring time(getTime()); + wstring time(getTime(false, SubSecond::Auto)); ct=time.c_str(); wstring typeS = getType(); const wchar_t *tp = typeS.c_str(); - if (Type==oPunch::PunchStart) + if (type==oPunch::PunchStart) swprintf_s(bf, L"%s\t%s", tp, ct); - else if (Type==oPunch::PunchFinish) + else if (type==oPunch::PunchFinish) swprintf_s(bf, L"%s\t%s", tp, ct); - else if (Type==oPunch::PunchCheck) + else if (type==oPunch::PunchCheck) swprintf_s(bf, L"%s\t%s", tp, ct); else { if (isUsed) - swprintf_s(bf, L"%d\t%s", Type, ct); + swprintf_s(bf, L"%d\t%s", type, ct); else - swprintf_s(bf, L" %d*\t%s", Type, ct); + swprintf_s(bf, L" %d*\t%s", type, ct); } return bf; @@ -104,36 +158,42 @@ wstring oPunch::getString() const { wstring oPunch::getSimpleString() const { - wstring time(getTime()); + wstring time(getTime(false, SubSecond::Auto)); - if (Type==oPunch::PunchStart) + if (type==oPunch::PunchStart) return lang.tl(L"starten (X)#" + time); - else if (Type==oPunch::PunchFinish) + else if (type==oPunch::PunchFinish) return lang.tl(L"mÃ¥let (X)#" + time); - else if (Type==oPunch::PunchCheck) + else if (type==oPunch::PunchCheck) return lang.tl(L"check (X)#" + time); else - return lang.tl(L"kontroll X (Y)#" + itow(Type) + L"#" + time); + return lang.tl(L"kontroll X (Y)#" + itow(type) + L"#" + time); } -wstring oPunch::getTime() const +wstring oPunch::getTime(bool adjust, SubSecond mode) const { - if (Time>=0) - return oe->getAbsTime(Time+tTimeAdjust); + if (punchTime >= 0) { + if (adjust) + return oe->getAbsTime(getTimeInt() + tTimeAdjust, mode); + else + return oe->getAbsTime(getTimeInt(), mode); + } else return makeDash(L"-"); } int oPunch::getTimeInt() const { - return Time; + if (punchUnit > 0) + return punchTime + oe->getUnitAdjustment(oPunch::SpecialPunch(type), punchUnit); + return punchTime; } - int oPunch::getAdjustedTime() const { - if (Time>=0) - return Time+tTimeAdjust; + if (punchTime>=0) + return getTimeInt() +tTimeAdjust; else return -1; } + void oPunch::setTime(const wstring &t) { if (convertAbsoluteTimeHMS(t, -1) <= 0) { @@ -141,20 +201,31 @@ void oPunch::setTime(const wstring &t) return; } - int tt = oe->getRelativeTime(t)-tTimeAdjust; + int tt = oe->getRelativeTime(t); if (tt < 0) tt = 0; + else if (punchUnit > 0) { + tt -= oe->getUnitAdjustment(oPunch::SpecialPunch(type), punchUnit); + + } setTimeInt(tt, false); } void oPunch::setTimeInt(int tt, bool databaseUpdate) { - if (tt != Time) { - Time = tt; + if (tt != punchTime) { + punchTime = tt; if (!databaseUpdate) updateChanged(); } } +void oPunch::setPunchUnit(int unit) { + if (unit != punchUnit) { + punchUnit = unit; + updateChanged(); + } +} + oDataContainer &oPunch::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const { throw std::exception("Unsupported"); } @@ -179,7 +250,7 @@ bool oPunch::canRemove() const } const wstring &oPunch::getType() const { - return getType(Type); + return getType(type); } const wstring &oPunch::getType(int t) { diff --git a/code/oPunch.h b/code/oPunch.h index cdfac98..8925618 100644 --- a/code/oPunch.h +++ b/code/oPunch.h @@ -7,7 +7,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -27,15 +27,18 @@ ************************************************************************/ +#include "meos_util.h" class oEvent; class oPunch : public oBase { protected: - int Type; - int Time; - bool isUsed; //Is used in the course... + int type = 0; + int punchTime = 0; + int punchUnit = 0; + + bool isUsed = false; //Is used in the course... // Index into course (-1 if unused) int tRogainingIndex; @@ -54,54 +57,64 @@ protected: bool hasBeenPlayed; /** Get internal data buffers for DI */ - oDataContainer &getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const; - int getDISize() const {return -1;} + oDataContainer& getDataBuffers(pvoid& data, pvoid& olddata, pvectorstr& strData) const; + int getDISize() const { return -1; } void changedObject(); mutable int previousPunchTime; /// Note that this is not valid in general public: - virtual int getControlId() const {return tMatchControlId;} - bool isUsedInCourse() const {return isUsed;} + int getPunchUnit() const { return punchUnit; } + void setPunchUnit(int unit); + + virtual int getControlId() const { return tMatchControlId; } + + bool isUsedInCourse() const { return isUsed; } void remove(); bool canRemove() const; 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;} - bool isFinish(int finishType) const {return Type==PunchFinish || Type == finishType;} - bool isCheck() const {return Type==PunchCheck;} - int getControlNumber() const {return Type>=30 ? Type : 0;} - const wstring &getType() const; - static const wstring &getType(int t); - int getTypeCode() const {return Type;} - wstring getString() 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; } + bool isFinish(int finishType) const { return type == PunchFinish || type == finishType; } + bool isCheck() const { return type == PunchCheck; } + int getControlNumber() const { return type >= 30 ? type : 0; } + const wstring& getType() const; + static const wstring& getType(int t); + int getTypeCode() const { return type; } + wstring getString() const; wstring getSimpleString() const; - wstring getTime() const; + wstring getTime(bool adjusted, SubSecond mode) const; + + // Return time adjusted after punch unit adjustment (but not including course/control adjustments) int getTimeInt() const; + + /** Return true if time is set */ + bool hasTime() const { return punchTime > 0; } + + /** Return time after unit adjustment AND control/course adjustments. */ int getAdjustedTime() const; - void setTime(const wstring &t); + void setTime(const wstring& t); virtual void setTimeInt(int newTime, bool databaseUpdate); - void setTimeAdjust(int t) {tTimeAdjust=t;} - void adjustTimeAdjust(int t) {tTimeAdjust+=t;} + void setTimeAdjust(int t) { tTimeAdjust = t; } + void adjustTimeAdjust(int t) { tTimeAdjust += t; } wstring getRunningTime(int startTime) const; - enum SpecialPunch {PunchStart=1, PunchFinish=2, PunchCheck=3, HiredCard=11111}; - void decodeString(const string &s); + enum SpecialPunch { PunchStart = 1, PunchFinish = 2, PunchCheck = 3, HiredCard = 11111 }; + void decodeString(const char* s); string codeString() const; - void appendCodeString(string &dst) const; + void appendCodeString(string& dst) const; + void merge(const oBase& input, const oBase* base) override; - void merge(const oBase &input, const oBase *base) override; - - oPunch(oEvent *poe); + oPunch(oEvent* poe); virtual ~oPunch(); friend class oCard; diff --git a/code/oReport.cpp b/code/oReport.cpp index 4393bef..c9a1e06 100644 --- a/code/oReport.cpp +++ b/code/oReport.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/oRunner.cpp b/code/oRunner.cpp index 14e342c..8cc9c3a 100644 --- a/code/oRunner.cpp +++ b/code/oRunner.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -370,8 +370,8 @@ bool oRunner::Write(xmlparser &xml) xml.write("Id", Id); xml.write("Updated", getStamp()); xml.write("Name", sName); - xml.write("Start", startTime); - xml.write("Finish", FinishTime); + xml.writeTime("Start", startTime); + xml.writeTime("Finish", FinishTime); xml.write("Status", status); xml.write("CardNo", cardNumber); xml.write("StartNo", StartNo); @@ -379,7 +379,7 @@ bool oRunner::Write(xmlparser &xml) xml.write("InputPoint", inputPoints); if (inputStatus != StatusOK) xml.write("InputStatus", itos(inputStatus)); //Force write of 0 - xml.write("InputTime", inputTime); + xml.writeTime("InputTime", inputTime); xml.write("InputPlace", inputPlace); if (Club) xml.write("Club", Club->Id); @@ -415,14 +415,14 @@ void oRunner::Set(const xmlobject &xo) Id = it->getInt(); } else if (it->is("Name")) { - sName = it->getw(); + sName = it->getWStr(); getRealName(sName, tRealName); } else if (it->is("Start")) { - tStartTime = startTime = it->getInt(); + tStartTime = startTime = it->getRelativeTime(); } else if (it->is("Finish")) { - FinishTime = it->getInt(); + FinishTime = it->getRelativeTime(); } else if (it->is("Status")) { unsigned rawStat = it->getInt(); @@ -447,11 +447,11 @@ void oRunner::Set(const xmlobject &xo) else if (it->is("oData")) getDI().set(*it); else if (it->is("Updated")) - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); else if (it->is("MultiR")) - decodeMultiR(it->getRaw()); + decodeMultiR(it->getRawStr()); else if (it->is("InputTime")) { - inputTime = it->getInt(); + inputTime = it->getRelativeTime(); } else if (it->is("InputStatus")) { unsigned rawStat = it->getInt(); @@ -754,7 +754,7 @@ void oAbstractRunner::setFinishTime(int t) void oRunner::setFinishTime(int t) { bool update=false; - if (Class && (getTimeAfter(tDuplicateLeg)==0 || getTimeAfter()==0)) + if (Class && (getTimeAfter(tDuplicateLeg, false)==0 || getTimeAfter()==0)) update=true; oAbstractRunner::setFinishTime(t); @@ -787,10 +787,14 @@ const wstring &oAbstractRunner::getStartTimeCompact() const { return makeDash(L"-"); } -const wstring &oAbstractRunner::getFinishTimeS() const +const wstring &oAbstractRunner::getFinishTimeS(bool adjusted, SubSecond mode) const { - if (FinishTime>0) - return oe->getAbsTime(FinishTime); + if (FinishTime > 0) { + if (adjusted) + return oe->getAbsTime(FinishTime, mode); + else + return oe->getAbsTime(FinishTime - getBuiltinAdjustment(), mode); + } else return makeDash(L"-"); } @@ -798,7 +802,7 @@ int oAbstractRunner::getRunningTime(bool computedTime) const { if (!computedTime || tComputedTime == 0) { int rt = FinishTime - tStartTime; if (rt > 0) - return getTimeAdjustment() + rt; + return getTimeAdjustment(false) + rt; else return 0; } @@ -806,14 +810,14 @@ int oAbstractRunner::getRunningTime(bool computedTime) const { return tComputedTime; } -const wstring &oAbstractRunner::getRunningTimeS(bool computedTime) const +const wstring &oAbstractRunner::getRunningTimeS(bool computedTime, SubSecond mode) const { - return formatTime(getRunningTime(computedTime)); + return formatTime(getRunningTime(computedTime), mode); } -const wstring &oAbstractRunner::getTotalRunningTimeS() const +const wstring &oAbstractRunner::getTotalRunningTimeS(SubSecond mode) const { - return formatTime(getTotalRunningTime()); + return formatTime(getTotalRunningTime(), mode); } int oAbstractRunner::getTotalRunningTime() const { @@ -831,7 +835,7 @@ int oRunner::getTotalRunningTime() const { const wstring &oAbstractRunner::getStatusS(bool formatForPrint, bool computedStatus) const { if (computedStatus) - return oEvent::formatStatus(getStatusComputed(), formatForPrint); + return oEvent::formatStatus(getStatusComputed(true), formatForPrint); else return oEvent::formatStatus(tStatus, formatForPrint); } @@ -889,7 +893,7 @@ const wchar_t *formatIOFStatus(RunnerStatus s, bool hasTime) { wstring oAbstractRunner::getIOFStatusS() const { - return formatIOFStatus(getStatusComputed(), getFinishTime()> 0); + return formatIOFStatus(getStatusComputed(true), getFinishTime()> 0); } wstring oAbstractRunner::getIOFTotalStatusS() const @@ -962,6 +966,13 @@ void oRunner::addPunches(pCard card, vector &missingPunches) { synchronizeAll(true); + if (Card != card) { + Card = card; + updateChanged(); + evaluateCard(true, missingPunches, 0, ChangeType::Update); + synchronizeAll(true); + } + if (oe->isClient() && oe->getPropertyInt("UseDirectSocket", true)!=0) { if (oldStatus != getStatus() || oldFinishTime != getFinishTime()) { SocketPunchInfo pi; @@ -1121,12 +1132,12 @@ bool oAbstractRunner::setTmpStore() { return res; }*/ -bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, +bool oRunner::evaluateCard(bool doApply, vector &missingPunches, int addpunch, ChangeType changeType) { if (unsigned(status) >= 100u) status = StatusUnknown; //Reset bad input pClass clz = getClassRef(true); - MissingPunches.clear(); + missingPunches.clear(); const int oldFT = FinishTime; int oldStartTime; RunnerStatus oldStatus; @@ -1201,9 +1212,10 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, // Reset rogaining. Store start/finish for (p_it = Card->punches.begin(); p_it != Card->punches.end(); ++p_it) { if (p_it->isStart() && tUseStartPunch) - *refStartTime = p_it->Time; - else if (p_it->isFinish()) - setFinishTime(p_it->Time); + *refStartTime = p_it->getTimeInt(); + else if (p_it->isFinish()) { + setFinishTime(p_it->getTimeInt()); + } } if ((inTeam || !tUseStartPunch) && doApply) apply(changeType, nullptr); //Post apply. Update start times. @@ -1245,25 +1257,28 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, bool hasRogaining = course->hasRogaining(); // Pairs: - intkeymap< pair > rogaining(course->getNumControls()); + intkeymap> rogaining(course->getNumControls()); + unordered_set requiredRG; for (int k = 0; k< course->nControls; k++) { if (course->Controls[k] && course->Controls[k]->isRogaining(hasRogaining)) { int pt = course->Controls[k]->getRogainingPoints(); for (int j = 0; jControls[k]->nNumbers; j++) { rogaining.insert(course->Controls[k]->Numbers[j], make_pair(k, pt)); } + if (course->Controls[k]->getStatus() == oControl::ControlStatus::StatusRogainingRequired) + requiredRG.insert(k); } } if (addpunch && Card->punches.empty()) { - Card->addPunch(addpunch, -1, course->Controls[0] ? course->Controls[0]->getId():0); + Card->addPunch(addpunch, -1, course->Controls[0] ? course->Controls[0]->getId():0, 0); } if (Card->punches.empty()) { for(int k=0;knControls;k++) { if (course->Controls[k]) { course->Controls[k]->startCheckControl(); - course->Controls[k]->addUncheckedPunches(MissingPunches, hasRogaining); + course->Controls[k]->addUncheckedPunches(missingPunches, hasRogaining); } } if ((inTeam || !tUseStartPunch) && doApply) @@ -1293,7 +1308,7 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, //Search for start and update start time. p_it=Card->punches.begin(); while ( p_it!=Card->punches.end()) { - if (p_it->Type == startPunchCode) { + if (p_it->type == startPunchCode) { if (tUseStartPunch && p_it->getAdjustedTime() != *refStartTime) { p_it->setTimeAdjust(0); *refStartTime = p_it->getAdjustedTime(); @@ -1317,12 +1332,12 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, } for (p_it = Card->punches.begin(); p_it != Card->punches.end(); ++p_it) { - if (p_it->Type>=10 && p_it->Type<=1024) - ++punchCount[p_it->Type]; + if (p_it->type>=10 && p_it->type<=1024) + ++punchCount[p_it->type]; } p_it = Card->punches.begin(); - splitTimes.resize(course->nControls, SplitData(NOTATIME, SplitData::Missing)); + splitTimes.resize(course->nControls, SplitData(NOTATIME, SplitData::SplitStatus::Missing)); int k=0; @@ -1349,8 +1364,8 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if (addpunch && ctrl->isRogaining(hasRogaining) && ctrl->getFirstNumber() == addpunch) { if ( Card->getPunchByType(addpunch) == 0) { oPunch op(oe); - op.Type=addpunch; - op.Time=-1; + op.type=addpunch; + op.punchTime=-1; op.isUsed=true; op.tIndex = k; op.tMatchControlId=ctrl->getId(); @@ -1359,11 +1374,11 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, } } - if (ctrl->getStatus() == oControl::StatusBad || - ctrl->getStatus() == oControl::StatusOptional || - ctrl->getStatus() == oControl::StatusBadNoTiming) { + if (ctrl->getStatus() == oControl::ControlStatus::StatusBad || + ctrl->getStatus() == oControl::ControlStatus::StatusOptional || + ctrl->getStatus() == oControl::ControlStatus::StatusBadNoTiming) { // The control is marked "bad" but we found it anyway in the card. Mark it as used. - if (tp_it!=Card->punches.end() && ctrl->hasNumberUnchecked(tp_it->Type)) { + if (tp_it!=Card->punches.end() && ctrl->hasNumberUnchecked(tp_it->type)) { tp_it->isUsed=true; //Show that this is used when splittimes are calculated. // Adjust if the time of this control was incorrectly set. tp_it->setTimeAdjust(timeAdjust); @@ -1376,11 +1391,11 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, } else { while(!ctrl->controlCompleted(hasRogaining) && tp_it!=Card->punches.end()) { - if (ctrl->hasNumberUnchecked(tp_it->Type)) { + if (ctrl->hasNumberUnchecked(tp_it->type)) { if (skippedPunches>0) { - if (ctrl->Status == oControl::StatusOK) { - int code = tp_it->Type; + if (ctrl->Status == oControl::ControlStatus::StatusOK) { + int code = tp_it->type; if (expectedPunchCount[code]>1 && punchCount[code] < expectedPunchCount[code]) { tp_it=Card->punches.end(); ctrl->uncheckNumber(code); @@ -1402,8 +1417,8 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if (ctrl->hasNumberUnchecked(addpunch)){ //Add this punch. oPunch op(oe); - op.Type=addpunch; - op.Time=-1; + op.type=addpunch; + op.punchTime=-1; op.isUsed=true; op.tMatchControlId=ctrl->getId(); @@ -1424,7 +1439,7 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if (tp_it==Card->punches.end() && !ctrl->controlCompleted(hasRogaining) && ctrl->hasNumberUnchecked(addpunch) ) { - Card->addPunch(addpunch, -1, ctrl->getId()); + Card->addPunch(addpunch, -1, ctrl->getId(), 0); if (ctrl->controlCompleted(hasRogaining)) splitTimes[k].setPunched(); Card->punches.back().isUsed=true; @@ -1432,7 +1447,7 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, Card->punches.back().tIndex = k; } - if (ctrl->controlCompleted(hasRogaining) && splitTimes[k].time == NOTATIME) + if (ctrl->controlCompleted(hasRogaining) && splitTimes[k].getTime(false) == NOTATIME) splitTimes[k].setPunched(); } else //if (ctrl && ctrl->Status==oControl::StatusBad){ @@ -1440,7 +1455,7 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, //Add missing punches if (ctrl && !ctrl->controlCompleted(hasRogaining)) - ctrl->addUncheckedPunches(MissingPunches, hasRogaining); + ctrl->addUncheckedPunches(missingPunches, hasRogaining); } //Add missing punches for remaining controls @@ -1450,12 +1465,12 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, ctrl->startCheckControl(); if (ctrl->hasNumberUnchecked(addpunch)) { - Card->addPunch(addpunch, -1, ctrl->getId()); + Card->addPunch(addpunch, -1, ctrl->getId(), 0); Card->updateChanged(); if (ctrl->controlCompleted(hasRogaining)) splitTimes[k].setNotPunched(); } - ctrl->addUncheckedPunches(MissingPunches, hasRogaining); + ctrl->addUncheckedPunches(missingPunches, hasRogaining); } k++; } @@ -1468,7 +1483,7 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, ++p_it; } - int OK = MissingPunches.empty(); + int OK = missingPunches.empty(); tRogaining.clear(); tRogainingPoints = 0; @@ -1476,13 +1491,14 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, // Rogaining logic if (rogaining.size() > 0) { - set visitedControls; + unordered_set visitedControls; for (p_it=Card->punches.begin(); p_it != Card->punches.end(); ++p_it) { pair pt; - if (rogaining.lookup(p_it->Type, pt)) { + if (rogaining.lookup(p_it->type, pt)) { p_it->anyRogainingMatchControlId = course->Controls[pt.first]->getId(); - if (visitedControls.count(pt.first) == 0) { - visitedControls.insert(pt.first); // May noy be revisited + if (visitedControls.insert(pt.first).second) { + requiredRG.erase(pt.first); + // May noy be revisited p_it->isUsed = true; p_it->tRogainingIndex = pt.first; p_it->tMatchControlId = p_it->anyRogainingMatchControlId; @@ -1494,6 +1510,12 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, } } + for (int mp : requiredRG) { + missingPunches.push_back(course->Controls[mp]->getFirstNumber()); + } + + OK = missingPunches.empty(); + // Manual point adjustment tRogainingPoints = max(0, tRogainingPoints + getPointAdjustment()); @@ -1540,14 +1562,14 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if (finishPunchCode != oPunch::PunchFinish) { while (backIter != Card->punches.rend()) { - if (backIter->Type == finishPunchCode) + if (backIter->type == finishPunchCode) break; ++backIter; } } - if (backIter != Card->punches.rend() && backIter->Type == finishPunchCode) { - FinishTime = backIter->Time; + if (backIter != Card->punches.rend() && backIter->type == finishPunchCode) { + FinishTime = backIter->getTimeInt(); if (finishPunchCode == oPunch::PunchFinish) backIter->tMatchControlId=oPunch::PunchFinish; } @@ -1560,13 +1582,13 @@ bool oRunner::evaluateCard(bool doApply, vector & MissingPunches, if (*refStatus == StatusOK && maxTimeStatus == 1) *refStatus = StatusMAX; //Maxtime - if (!MissingPunches.empty()) { - tProblemDescription = L"Stämplingar saknas: X#" + itow(MissingPunches[0]); - for (unsigned j = 1; j<3; j++) { - if (MissingPunches.size()>j) - tProblemDescription += L", " + itow(MissingPunches[j]); + if (!missingPunches.empty()) { + tProblemDescription = L"Stämplingar saknas: X#" + itow(missingPunches[0]); + for (unsigned j = 1; j < 3; j++) { + if (missingPunches.size() > j) + tProblemDescription += L", " + itow(missingPunches[j]); } - if (MissingPunches.size()>3) + if (missingPunches.size() > 3) tProblemDescription += L"..."; else tProblemDescription += L"."; @@ -1693,7 +1715,7 @@ void oRunner::doAdjustTimes(pCourse course) { pN--; // Skip bad controls } - if (ctrl->getStatus() == oControl::StatusNoTiming || (ctrlPrev && ctrlPrev->getStatus() == oControl::StatusBadNoTiming)) { + if (ctrl->getStatus() == oControl::ControlStatus::StatusNoTiming || (ctrlPrev && ctrlPrev->getStatus() == oControl::ControlStatus::StatusBadNoTiming)) { int t = 0; if (n>0 && pN>=0 && splitTimes[n].time>0 && splitTimes[pN].time>0) { t = splitTimes[n].time + adjustment - splitTimes[pN].time; @@ -1721,8 +1743,7 @@ void oRunner::doAdjustTimes(pCourse course) { } adjustTimes[n] = adjustment; - if (splitTimes[n].time>0) - splitTimes[n].time += adjustment; + splitTimes[n].setAdjustment(adjustment); } // Adjust remaining @@ -1780,7 +1801,7 @@ bool oRunner::storeTimesAux(pClass targetClass) { }*/ } - if (getStatusComputed() == StatusOK) { + if (getStatusComputed(false) == StatusOK) { int rt = getRunningTime(true); if (targetClass->tLeaderTime[leg].updateComputed(rt, oClass::LeaderInfo::Type::Leg)) updated = true; @@ -1877,21 +1898,21 @@ bool oRunner::storeTimesAux(pClass targetClass) { updated = true; } - if (getStatusComputed() == StatusOK) { + if (getStatusComputed(false) == StatusOK) { int rt = getRunningTime(true); if (targetClass->tLeaderTime[dupLeg].updateComputed(rt, oClass::LeaderInfo::Type::Leg)) updated = true; } - int rt = getRaceRunningTime(false, dupLeg); + int rt = getRaceRunningTime(false, dupLeg, false); if (targetClass->tLeaderTime[dupLeg].update(rt, oClass::LeaderInfo::Type::Total)) updated = true; - rt = getRaceRunningTime(true, dupLeg); + rt = getRaceRunningTime(true, dupLeg, false); if (targetClass->tLeaderTime[dupLeg].updateComputed(rt, oClass::LeaderInfo::Type::Total)) updated = true; - if (getTotalStatus() == StatusOK) { + if (getTotalStatus(false) == StatusOK) { rt = getTotalRunningTime(getFinishTime(), false, true); if (targetClass->tLeaderTime[dupLeg].update(rt, oClass::LeaderInfo::Type::TotalInput)) updated = true; @@ -1938,15 +1959,15 @@ bool oRunner::storeTimesAux(pClass targetClass) { return updated; } -int oRunner::getRaceRunningTime(bool computedTime, int leg) const { +int oRunner::getRaceRunningTime(bool computedTime, int leg, bool allowUpdate) const { if (tParentRunner) - return tParentRunner->getRaceRunningTime(computedTime, leg); + return tParentRunner->getRaceRunningTime(computedTime, leg, allowUpdate); if (leg == -1) leg = multiRunner.size() - 1; if (leg == 0) { /// XXX This code is buggy - if (getTotalStatus() == StatusOK) + if (getTotalStatus(allowUpdate) == StatusOK) return getRunningTime(computedTime) + inputTime; else return 0; } @@ -1960,16 +1981,16 @@ int oRunner::getRaceRunningTime(bool computedTime, int leg) const { switch(lt) { case LTNormal: - if (r->statusOK(computedTime)) { - int dt=leg>0 ? r->getRaceRunningTime(computedTime, leg)+r->getRunningTime(computedTime):0; + if (r->statusOK(computedTime, allowUpdate)) { + int dt=leg>0 ? r->getRaceRunningTime(computedTime, leg, allowUpdate)+r->getRunningTime(computedTime):0; return max(r->getFinishTime()-tStartTime, dt); // ### Luckor, jaktstart??? } else return 0; break; case LTSum: - if (r->statusOK(computedTime)) - return r->getRunningTime(computedTime)+getRaceRunningTime(computedTime, leg); + if (r->statusOK(computedTime, allowUpdate)) + return r->getRunningTime(computedTime)+getRaceRunningTime(computedTime, leg, allowUpdate); else return 0; default: @@ -2002,6 +2023,26 @@ bool oRunner::sortSplit(const oRunner &a, const oRunner &b) } bool oRunner::operator<(const oRunner &c) const { + if (oe->CurrentSortOrder == ClubClassStartTime) { + pClub cl = getClubRef(); + pClub ocl = c.getClubRef(); + if (cl != ocl) { + if (cl == nullptr && ocl) + return true; + else if (ocl == nullptr) + return false; + + const wstring a = cl->getName(); + const wstring b = ocl->getName(); + int res = CompareString(LOCALE_USER_DEFAULT, 0, + a.c_str(), a.length(), + b.c_str(), b.length()); + + if (res != CSTR_EQUAL) + return res == CSTR_LESS_THAN; + } + } + const oClass * myClass = getClassRef(true); const oClass * cClass = c.getClassRef(true); if (!myClass || !cClass) @@ -2011,7 +2052,7 @@ bool oRunner::operator<(const oRunner &c) const { tRealName.c_str(), tRealName.length(), c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN; - if (oe->CurrentSortOrder == ClassStartTime) { + if (oe->CurrentSortOrder == ClassStartTime || oe->CurrentSortOrder == ClubClassStartTime) { if (myClass->Id != cClass->Id) { if (myClass->tSortIndex != cClass->tSortIndex) return myClass->tSortIndex < cClass->tSortIndex; @@ -2035,6 +2076,30 @@ bool oRunner::operator<(const oRunner &c) const { } } } + if (oe->CurrentSortOrder == ClassStartTime) { + if (myClass->Id != cClass->Id) { + if (myClass->tSortIndex != cClass->tSortIndex) + return myClass->tSortIndex < cClass->tSortIndex; + else + return myClass->Id < cClass->Id; + } + else if (tStartTime != c.tStartTime) { + if (tStartTime <= 0 && c.tStartTime > 0) + return false; + else if (c.tStartTime <= 0 && tStartTime > 0) + return true; + else return tStartTime < c.tStartTime; + } + else { + //if (StartNo != c.StartNo && !(getBib().empty() && c.getBib().empty())) + // return StartNo < c.StartNo; + const wstring& b1 = getBib(); + const wstring& b2 = c.getBib(); + if (b1 != b2) { + return compareBib(b1, b2); + } + } + } else if (oe->CurrentSortOrder == ClassDefaultResult) { RunnerStatus stat = tStatus == StatusUnknown ? StatusOK : tStatus; RunnerStatus cstat = c.tStatus == StatusUnknown ? StatusOK : c.tStatus; @@ -2061,10 +2126,10 @@ bool oRunner::operator<(const oRunner &c) const { int t = getRunningTime(false); if (t <= 0) - t = 3600 * 1000; + t = timeConstHour * 1000; int ct = c.getRunningTime(false); if (ct <= 0) - ct = 3600 * 1000; + ct = timeConstHour * 1000; if (t != ct) return t < ct; @@ -2073,8 +2138,8 @@ bool oRunner::operator<(const oRunner &c) const { } else if (oe->CurrentSortOrder == ClassResult) { - RunnerStatus stat = getStatusComputed(); - RunnerStatus cstat = c.getStatusComputed(); + RunnerStatus stat = getStatusComputed(false); + RunnerStatus cstat = c.getStatusComputed(false); stat = stat == StatusUnknown ? StatusOK : stat; cstat = cstat == StatusUnknown ? StatusOK : cstat; @@ -2101,10 +2166,10 @@ bool oRunner::operator<(const oRunner &c) const { int t = getRunningTime(true); if (t <= 0) - t = 3600 * 1000; + t = timeConstHour * 1000; int ct = c.getRunningTime(true); if (ct <= 0) - ct = 3600 * 1000; + ct = timeConstHour * 1000; if (t != ct) return t < ct; @@ -2117,8 +2182,8 @@ bool oRunner::operator<(const oRunner &c) const { const pCourse crs1 = getCourse(false); const pCourse crs2 = c.getCourse(false); - RunnerStatus stat = getStatusComputed(); - RunnerStatus cstat = c.getStatusComputed(); + RunnerStatus stat = getStatusComputed(false); + RunnerStatus cstat = c.getStatusComputed(false); if (crs1 != crs2) { int id1 = crs1 ? crs1->getId() : 0; @@ -2174,21 +2239,21 @@ bool oRunner::operator<(const oRunner &c) const { } } else if (oe->CurrentSortOrder == SortByFinishTime) { - RunnerStatus stat = getStatusComputed(); - RunnerStatus cstat = c.getStatusComputed(); + RunnerStatus stat = getStatusComputed(false); + RunnerStatus cstat = c.getStatusComputed(false); if (stat != cstat) return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat]; else { - int ft = getFinishTimeAdjusted(); - int cft = c.getFinishTimeAdjusted(); + int ft = getFinishTimeAdjusted(true); + int cft = c.getFinishTimeAdjusted(true); if (stat == StatusOK && ft != cft) return ft < cft; } } else if (oe->CurrentSortOrder == SortByFinishTimeReverse) { - int ft = getFinishTimeAdjusted(); - int cft = c.getFinishTimeAdjusted(); + int ft = getFinishTimeAdjusted(true); + int cft = c.getFinishTimeAdjusted(true); if (ft != cft) return ft > cft; } @@ -2196,14 +2261,14 @@ bool oRunner::operator<(const oRunner &c) const { if (myClass != cClass) return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id); - RunnerStatus stat = getStatusComputed(); - RunnerStatus cstat = c.getStatusComputed(); + RunnerStatus stat = getStatusComputed(false); + RunnerStatus cstat = c.getStatusComputed(false); if (stat != cstat) return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat]; else { - int ft = getFinishTimeAdjusted(); - int cft = c.getFinishTimeAdjusted(); + int ft = getFinishTimeAdjusted(true); + int cft = c.getFinishTimeAdjusted(true); if (stat == StatusOK && ft != cft) return ft < cft; } @@ -2288,8 +2353,8 @@ bool oRunner::operator<(const oRunner &c) const { else if (oe->CurrentSortOrder == CourseResult) { const pCourse crs1 = getCourse(false); const pCourse crs2 = c.getCourse(false); - RunnerStatus stat = getStatusComputed(); - RunnerStatus cstat = c.getStatusComputed(); + RunnerStatus stat = getStatusComputed(false); + RunnerStatus cstat = c.getStatusComputed(false); if (crs1 != crs2) { int id1 = crs1 ? crs1->getId() : 0; @@ -2517,6 +2582,16 @@ int oRunner::getPlace(bool allowUpdate) const { return tPlace.get(!allowUpdate); } +RunnerStatus oRunner::getStatusComputed(bool allowUpdate) const { + if (allowUpdate && tPlace.isOld(*oe)) { + if (Class) { + oEvent::ResultType rt = oEvent::ResultType::ClassResult; + oe->calculateResults({ getClassId(true) }, rt, false); + } + } + return tComputedStatus != StatusUnknown ? tComputedStatus : tStatus; +} + int oRunner::getCoursePlace(bool perClass) const { if (perClass) { if (tCourseClassPlace.isOld(*oe) && Class) { @@ -2830,14 +2905,16 @@ bool oAbstractRunner::setStatus(RunnerStatus st, bool updateSource, ChangeType c assert(!(updateSource && changeType == ChangeType::Quiet)); bool ch = false; - if (tStatus!=st) { + if (tStatus != st) { ch = true; bool someOK = (st == StatusOK) || (tStatus == StatusOK); - tStatus=st; + tStatus = st; if (Class && someOK) { Class->clearCache(recalculate); } + if (st == StatusUnknown) + tComputedStatus = StatusUnknown; } if (st != status) { @@ -3021,7 +3098,7 @@ pRunner oRunner::nextNeedReadout() const { // For a runner in a team, first the team for the card for (size_t k = 0; k < tInTeam->Runners.size(); k++) { pRunner tr = tInTeam->Runners[k]; - if (tr && tr->getCardNo() == getCardNo() && !tr->Card && !tr->statusOK(false)) + if (tr && tr->getCardNo() == getCardNo() && !tr->Card && !tr->statusOK(false, false)) return tr; } } @@ -3124,7 +3201,7 @@ pRunner oEvent::getRunnerByCardNo(int cardNo, int time, CardLookupProperty prop) } else { pRunner bestR = 0; - const int K = 3600 * 24; + const int K = timeConstHour * 24; int dist = 10 * K; for (size_t k = 0; k < cand.size(); k++) { pRunner r = cand[k]; @@ -3141,7 +3218,7 @@ pRunner oEvent::getRunnerByCardNo(int cardNo, int time, CardLookupProperty prop) if (cc.second > 0) finish = max(finish, cc.second); } - start = max(0, start - 3 * 60); // Allow some extra time before start + start = max(0, start - 3 * timeConstMinute); // Allow some extra time before start if (start > 0 && finish > 0 && time >= start && time <= finish) return r; @@ -3559,7 +3636,7 @@ void oRunner::createMultiRunner(bool createMaster, bool sync) if (!multiRunner[k - 1] && allowCreate) { update = true; multiRunner[k - 1] = oe->addRunner(sName, getClubId(), - getClassId(false), 0, 0, false); + getClassId(false), 0, getBirthDate(), false); multiRunner[k - 1]->tDuplicateLeg = k; multiRunner[k - 1]->tParentRunner = this; multiRunner[k - 1]->cardNumber = 0; @@ -3636,12 +3713,12 @@ void oRunner::apply(ChangeType changeType, pRunner src) { setStartTime(lastStart, false, changeType); tUseStartPunch = false; } - else if (st == STHunting) { + else if (st == STPursuit) { pRunner r = getPredecessor(); int lastStart = 0; - if (r && r->FinishTime > 0 && r->statusOK(false)) { - int rt = r->getRaceRunningTime(false, tDuplicateLeg - 1); + if (r && r->FinishTime > 0 && r->statusOK(false, false)) { + int rt = r->getRaceRunningTime(false, tDuplicateLeg - 1, false); int timeAfter = rt - pc->getTotalLegLeaderTime(oClass::AllowRecompute::NoUseOld, r->tDuplicateLeg, false, true); if (rt > 0 && timeAfter >= 0) lastStart = pc->getStartData(tDuplicateLeg) + timeAfter; @@ -3816,9 +3893,9 @@ void oRunner::addTableRow(Table &table) const table.set(row++, it, TID_CARD, cno>0 ? itow(cno) : L"", true); table.set(row++, it, TID_START, getStartTimeS(), true); - table.set(row++, it, TID_FINISH, getFinishTimeS(), true); + table.set(row++, it, TID_FINISH, getFinishTimeS(false, SubSecond::Auto), true); table.set(row++, it, TID_STATUS, getStatusS(false, true), true, cellSelection); - table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(true), false); + table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(true, SubSecond::Auto), false); int rp = getRogainingPoints(true, false); table.set(row++, it, TID_POINTS, rp ? itow(rp) : L"", false); @@ -3841,7 +3918,7 @@ void oRunner::addTableRow(Table &table) const if (k + 3 < spvec.size()) { rawStat = _wtoi(spvec[k].c_str()); - rawTime = _wtoi(spvec[k + 1].c_str()); + rawTime = parseRelativeTime(spvec[k + 1].c_str()); rawPoints = _wtoi(spvec[k + 2].c_str()); place = _wtoi(spvec[k + 3].c_str()); } @@ -3884,7 +3961,7 @@ pair oRunner::inputData(int id, const wstring &input, case 2: { int time = ::convertAbsoluteTimeHMS(input, -1); - spvec[4 * stage + 1] = itow(time); + spvec[4 * stage + 1] = codeRelativeTimeW(time); output = formatTimeHMS(time); } break; @@ -4053,7 +4130,7 @@ void oRunner::fillInput(int id, vector< pair > &out, size_t &se } if (id==TID_COURSE) { - oe->fillCourses(out, true); + oe->getCourses(out, L"", true); out.push_back(make_pair(lang.tl(L"Klassens bana"), 0)); selected = getCourseId(); } @@ -4092,11 +4169,11 @@ int oRunner::getSplitTime(int controlNumber, bool normalized) const { if (!Card) { if (controlNumber == 0) - return getPunchTime(0, false); + return getPunchTime(0, false, true); else { - int ct = getPunchTime(controlNumber, false); + int ct = getPunchTime(controlNumber, false, true); if (ct > 0) { - int dt = getPunchTime(controlNumber - 1, false); + int dt = getPunchTime(controlNumber - 1, false, true); if (dt > 0 && ct > dt) return ct - dt; } @@ -4138,7 +4215,7 @@ int oRunner::getNamedSplit(int controlNumber) const { return -1; int k=controlNumber-1; - int ct = getPunchTime(controlNumber, false); + int ct = getPunchTime(controlNumber, false, true); if (ct <= 0) return -1; @@ -4147,7 +4224,7 @@ int oRunner::getNamedSplit(int controlNumber) const { pControl c = crs->Controls[k]; if (c && c->hasName()) { - int dt = getPunchTime(k, false); + int dt = getPunchTime(k, false, true); if (dt > 0 && ct > dt) return max(ct - dt, -1); else return -1; @@ -4169,38 +4246,38 @@ wstring oRunner::getNamedSplitS(int controlNumber) const return formatTime(getNamedSplit(controlNumber)); } -int oRunner::getPunchTime(int controlNumber, bool normalized) const +int oRunner::getPunchTime(int controlNumber, bool normalized, bool adjusted) const { if (!Card) { pCourse pc = getCourse(false); if (!pc || controlNumber > pc->getNumControls()) return -1; - + if (controlNumber == pc->getNumControls()) return getFinishTime() - tStartTime; int ccId = pc->getCourseControlId(controlNumber); pFreePunch fp = oe->getPunch(Id, ccId, getCardNo()); - if (fp) - return fp->Time - tStartTime; + if (fp) + return fp->getTimeInt() - tStartTime; return -1; } const vector &st = getSplitTimes(normalized); - if ( unsigned(controlNumber)0) - return st[controlNumber].time-tStartTime; + if (unsigned(controlNumber) < st.size()) { + if (st[controlNumber].hasTime()) + return st[controlNumber].getTime(adjusted) - tStartTime; else return -1; } - else if ( unsigned(controlNumber)==st.size() ) - return FinishTime-tStartTime; + else if (unsigned(controlNumber) == st.size()) + return FinishTime - tStartTime; return -1; } -wstring oRunner::getPunchTimeS(int controlNumber, bool normalized) const +wstring oRunner::getPunchTimeS(int controlNumber, bool normalized, bool adjusted, SubSecond mode) const { - return formatTime(getPunchTime(controlNumber, normalized)); + return formatTime(getPunchTime(controlNumber, normalized, adjusted), mode); } bool oAbstractRunner::isVacant() const @@ -4230,16 +4307,16 @@ void oRunner::getSplitTime(int courseControlId, RunnerStatus &stat, int &rt) con if (courseControlId==oPunch::PunchFinish && FinishTime>0) { stat = tStatus; - rt = getFinishTimeAdjusted(); + rt = getFinishTimeAdjusted(true); } else if (Card) { oPunch *p=Card->getPunchById(courseControlId); - if (p && p->Time>0) { + if (p && p->hasTime()) { rt=p->getAdjustedTime(); stat = StatusOK; } - else if (p && p->Time == -1 && statusOK(true)) { - rt = getFinishTimeAdjusted(); + else if (p && p->punchTime == -1 && statusOK(true, false)) { + rt = getFinishTimeAdjusted(true); if (rt > 0) stat = StatusOK; else @@ -4285,7 +4362,7 @@ void oRunner::fillSpeakerObject(int leg, int courseControlId, int previousContro spk.names.push_back(getName()); spk.club = getClub(); - spk.finishStatus=totalResult ? getTotalStatus() : getStatusComputed(); + spk.finishStatus=totalResult ? getTotalStatus() : getStatusComputed(true); spk.startTimeS=getStartTimeCompact(); spk.missingStartTime = tStartTime<=0; @@ -4331,7 +4408,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_settRealName, s_lc)) { + if (filterMatchString(r->tRealName, s_lc, score)) { matchFilter.insert(id); if (res == 0) res = r; @@ -4384,7 +4461,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_settRealName, s_lc)) { + if (filterMatchString(r->tRealName, s_lc, score)) { matchFilter.insert(r->Id); if (res == 0) res = r; @@ -4404,7 +4481,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_settRealName, s_lc)) { + if (filterMatchString(r->tRealName, s_lc, score)) { matchFilter.insert(r->Id); if (res == 0) res = r; @@ -4415,7 +4492,7 @@ pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_settLeaderTime.size()<=unsigned(leg)) return -1; - int t=getRaceRunningTime(true, leg); + int t=getRaceRunningTime(true, leg, allowUpdate); if (t<=0) return -1; @@ -4656,11 +4733,11 @@ static void addMissingControl(bool wideFormat, gdioutput &gdi, int xx = cx; wstring str = makeDash(L"-"); int posy = wideFormat ? cy : cy-int(gdi.getLineHeight()*0.4); - const int endx = cx + colDeltaX - 27; + const int endx = cx + colDeltaX - gdi.scaleLength(27/2); while (xx < endx) { gdi.addStringUT(posy, xx, fontSmall, str); - xx += 20; + xx += gdi.scaleLength(8); } // Make a thin line for list format, otherwise, take a full place @@ -4671,12 +4748,97 @@ static void addMissingControl(bool wideFormat, gdioutput &gdi, cy+=int(gdi.getLineHeight()*0.3); } + void oRunner::printSplits(gdioutput& gdi) const { + + wstring wListId; + pClass cls1 = getClassRef(true); + if (cls1) + wListId = cls1->getDCI().getString("SplitPrint"); + + if (wListId.empty()) { + // Make it possibe to define the list in the base class + pClass cls2 = getClassRef(false); + if (cls2 != cls1) + wListId = cls2->getDCI().getString("SplitPrint"); + } + + if (wListId.empty()) { + wListId = oe->getDCI().getString("SplitPrint"); + } + + string listId; + if (wListId.empty()) { + if (cls1) { + if (cls1->getClassType() == ClassType::oClassIndividual) + listId = "split_indivudual"; + } + } + else { + listId = gdioutput::narrow(wListId); + } + + const bool wideFormat = oe->getPropertyInt("WideSplitFormat", 0) == 1; + if (!wideFormat) + gdi.setCX(10); + + if (listId.empty()) { + printSplits(gdi, nullptr); + } + else { + oListParam par; + par.selection.insert(getClassId(true)); + oListInfo currentList; + + par.listCode = oe->getListContainer().getCodeFromUnqiueId(listId); + //auto &metaList = oe->getListContainer().getList(par.listCode); + par.showInterTimes = false; + par.setLegNumberCoded(getLegNumber()); + par.filterMaxPer = 3; + par.alwaysInclude = this; + par.showHeader = false; + + try { + oe->generateListInfo(par, currentList); + } + catch (const meosException&) { + oe->gdiBase().addInfoBox("load_id_list", L"info:nosplitprint", 10000); + printSplits(gdi, nullptr); + return; + } + + if (currentList.isSplitPrintList()) { + auto& sp = *currentList.getSplitPrintInfo(); + currentList.getParam().filterMaxPer = sp.numClassResults; + } + + if (!wideFormat) + currentList.shrinkSize(); + + printSplits(gdi, ¤tList); + } +} + +void oRunner::printSplits(gdioutput& gdi, const oListInfo* li) const { bool withAnalysis = (oe->getDI().getInt("Analysis") & 1) == 0; bool withSpeed = (oe->getDI().getInt("Analysis") & 2) == 0; bool withResult = (oe->getDI().getInt("Analysis") & 4) == 0; + bool includeStandardHeading = true; + bool includeDefaultTitle = true; + bool includeSplitTimes = true; + + if (li && li->isSplitPrintList()) { + auto& sp = *li->getSplitPrintInfo(); + includeDefaultTitle = !li->hasHead(); + includeStandardHeading = sp.withStandardHeading; + withSpeed = sp.withSpeed; + withResult = sp.withResult; + withAnalysis = sp.withAnalysis; + } + const bool wideFormat = oe->getPropertyInt("WideSplitFormat", 0) == 1; const int numCol = 4; + pClass cls = getClassRef(true); if (cls && cls->getNoTiming()) { withResult = false; @@ -4691,37 +4853,47 @@ void oRunner::printSplits(gdioutput& gdi) const { normal = normalText; bnormal = boldText; } - else { - gdi.setCX(10); - } + gdi.fillDown(); - gdi.addStringUT(head, oe->getName()); - gdi.addStringUT(normal, oe->getDate()); - gdi.dropLine(0.5); + gdi.pushX(); + if (includeDefaultTitle) { + gdi.addStringUT(head, oe->getName()); + gdi.addStringUT(normal, oe->getDate()); + gdi.dropLine(0.5); + } + else { + oe->formatHeader(gdi, *li, pRunner(this)); + gdi.popX(); + } + pCourse pc = getCourse(true); + SubSecond mode = oe->useSubSecond() ? SubSecond::On : SubSecond::Auto; - gdi.addStringUT(bnormal, getName() + L", " + getClass(true)); - gdi.addStringUT(normal, getClub()); - gdi.dropLine(0.5); - gdi.addStringUT(normal, lang.tl("Start: ") + getStartTimeS() + lang.tl(", MÃ¥l: ") + getFinishTimeS()); - if (cls && cls->isRogaining()) { - gdi.addStringUT(normal, lang.tl("Poäng: ") + - itow(getRogainingPoints(true, false)) + - +L" (" + lang.tl("Avdrag: ") + itow(getRogainingReduction(true)) + L")"); + if (includeStandardHeading) { + gdi.addStringUT(bnormal, getName() + L", " + getClass(true)); + gdi.addStringUT(normal, getClub()); + gdi.dropLine(0.5); + gdi.addStringUT(normal, lang.tl("Start: ") + getStartTimeS() + lang.tl(", MÃ¥l: ") + getFinishTimeS(false, mode)); + if (cls && cls->isRogaining()) { + gdi.addStringUT(normal, lang.tl("Poäng: ") + + itow(getRogainingPoints(true, false)) + + +L" (" + lang.tl("Avdrag: ") + itow(getRogainingReduction(true)) + L")"); + } + + wstring statInfo = lang.tl("Status: ") + getStatusS(true, true) + lang.tl(", Tid: ") + getRunningTimeS(true, mode); + if (withSpeed && pc && pc->getLength() > 0) { + int kmt = (getRunningTime(false) * 1000) / pc->getLength(); + statInfo += L" (" + formatTime(kmt, SubSecond::Off) + lang.tl(" min/km") + L")"; + } + gdi.addStringUT(normal, statInfo); } - wstring statInfo = lang.tl("Status: ") + getStatusS(true, true) + lang.tl(", Tid: ") + getRunningTimeS(true); - if (withSpeed && pc && pc->getLength() > 0) { - int kmt = (getRunningTime(false) * 1000) / pc->getLength(); - statInfo += L" (" + formatTime(kmt) + lang.tl(" min/km") + L")"; - } if (pc && withSpeed) { if (pc->legLengths.empty() || *max_element(pc->legLengths.begin(), pc->legLengths.end()) <= 0) withSpeed = false; // No leg lenghts available } - gdi.addStringUT(normal, statInfo); - - int cy = gdi.getCY() + 4; + + int cy = gdi.getCY() + gdi.scaleLength(4/2); int cx = gdi.getCX(); int spMax = 0; @@ -4729,19 +4901,20 @@ void oRunner::printSplits(gdioutput& gdi) const { if (pc) { for (int n = 0; n < pc->nControls; n++) { spMax = max(spMax, getSplitTime(n, false)); - totMax = max(totMax, getPunchTime(n, false)); + totMax = max(totMax, getPunchTime(n, false, false)); } } - bool moreThanHour = max(totMax, getRunningTime(true)) >= 3600; - bool moreThanHourSplit = spMax >= 3600; + bool moreThanHour = max(totMax, getRunningTime(true)) >= timeConstHour; + bool moreThanHourSplit = spMax >= timeConstHour; - const int c1 = 35; - const int c2 = 95 + (moreThanHourSplit ? 65 : 55); - const int c3 = c2 + 10; - const int c4 = moreThanHour ? c3 + 153 : c3 + 133; - const int c5 = withSpeed ? c4 + 80 : c4; + const int c1 = gdi.scaleLength(35/2); + const int spW = moreThanHourSplit ? 65 : 55; + const int c2 = gdi.scaleLength((95 + spW)/2); + const int c3 = c2 + gdi.scaleLength(10/2); + const int c4 = moreThanHour ? c3 + gdi.scaleLength(153/2) : c3 + gdi.scaleLength(133/2); + const int c5 = withSpeed ? c4 + gdi.scaleLength(80/2) : c4; const int baseCX = cx; - const int colDeltaX = c5 + 32; + const int colDeltaX = c5 + gdi.scaleLength(32/2); char bf[256]; int lastIndex = -1; @@ -4765,7 +4938,7 @@ void oRunner::printSplits(gdioutput& gdi) const { set headerPos; set checkedIndex; - if (Card) { + if (Card && includeSplitTimes) { bool hasRogaining = pc ? pc->hasRogaining() : false; const int cyHead = cy; @@ -4779,7 +4952,7 @@ void oRunner::printSplits(gdioutput& gdi) const { if (headerPos.count(cx) == 0) { headerPos.insert(cx); gdi.addString("", cyHead, cx, italicSmall, "Kontroll"); - gdi.addString("", cyHead, cx + c2 - 55, italicSmall, "Tid"); + gdi.addString("", cyHead, cx + c2 - gdi.scaleLength(spW/2), italicSmall, "Tid"); if (withSpeed) gdi.addString("", cyHead, cx + c5, italicSmall | textRight, "min/km"); } @@ -4789,22 +4962,22 @@ void oRunner::printSplits(gdioutput& gdi) const { const pControl c = pc->getControl(it->tRogainingIndex); string point = c ? itos(c->getRogainingPoints()) + "p." : ""; - gdi.addStringUT(cy, cx + c1 + 10, fontSmall, point); + gdi.addStringUT(cy, cx + c1 + gdi.scaleLength(10/2), fontSmall, point); any = true; - sprintf_s(bf, "%d", it->Type); + sprintf_s(bf, "%d", it->type); gdi.addStringUT(cy, cx, fontSmall, bf); int st = Card->getSplitTime(getStartTime(), &*it); if (st > 0) - gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(st)); + gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(st, SubSecond::Off)); - gdi.addStringUT(cy, cx + c3, fontSmall, it->getTime()); + gdi.addStringUT(cy, cx + c3, fontSmall, it->getTime(false, SubSecond::Off)); int pt = it->getAdjustedTime(); st = getStartTime(); if (st > 0 && pt > 0 && pt > st) { - wstring punchTime = formatTime(pt - st); + wstring punchTime = formatTime(pt - st, SubSecond::Off); gdi.addStringUT(cy, cx + c4, fontSmall | textRight, punchTime); } @@ -4831,17 +5004,17 @@ void oRunner::printSplits(gdioutput& gdi) const { gdi.addString("", cy, cx, fontSmall, "MÃ¥l"); sp = getSplitTime(splitTimes.size(), false); if (sp > 0) { - gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(sp)); - punchTime = formatTime(getRunningTime(true)); + gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(sp, SubSecond::Off)); + punchTime = formatTime(getRunningTime(true), SubSecond::Off); } - gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it->Time + adjust)); + gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it->getTimeInt() + adjust, SubSecond::Off)); any = true; if (!punchTime.empty()) { gdi.addStringUT(cy, cx + c4, fontSmall | textRight, punchTime); } controlLegIndex = pc->getNumControls(); } - else if (it->Type > 10) { //Filter away check and start + else if (it->type > 10) { //Filter away check and start int index = -1; if (cid > 0) index = findNextControl(ctrl, lastIndex + 1, cid, offset, hasRogaining); @@ -4868,12 +5041,12 @@ void oRunner::printSplits(gdioutput& gdi) const { } lastIndex = index; - if (it->Type == startType && (index + offset) == 1) + if (it->type == startType && (index + offset) == 1) continue; // Skip start control sprintf_s(bf, "%d.", index + offset + startOffset); gdi.addStringUT(cy, cx, fontSmall, bf); - sprintf_s(bf, "(%d)", it->Type); + sprintf_s(bf, "(%d)", it->type); gdi.addStringUT(cy, cx + c1, fontSmall, bf); controlLegIndex = it->tIndex; @@ -4881,19 +5054,19 @@ void oRunner::printSplits(gdioutput& gdi) const { adjust = getTimeAdjust(controlLegIndex); sp = getSplitTime(controlLegIndex, false); if (sp > 0) { - punchTime = getPunchTimeS(controlLegIndex, false); - gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(sp)); + punchTime = getPunchTimeS(controlLegIndex, false, false, SubSecond::Off); + gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(sp, SubSecond::Off)); } } else { if (!it->isUsed) { gdi.addStringUT(cy, cx, fontSmall, makeDash(L"-")); } - sprintf_s(bf, "(%d)", it->Type); + sprintf_s(bf, "(%d)", it->type); gdi.addStringUT(cy, cx + c1, fontSmall, bf); } - if (it->Time > 0) - gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it->Time + adjust)); + if (it->punchTime > 0) + gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it->getTimeInt() + adjust, SubSecond::Off)); else { wstring str = makeDash(L"-"); gdi.addStringUT(cy, cx + c3, fontSmall, str); @@ -4907,9 +5080,9 @@ void oRunner::printSplits(gdioutput& gdi) const { if (withSpeed && controlLegIndex >= 0 && size_t(controlLegIndex) < pc->legLengths.size()) { int length = pc->legLengths[controlLegIndex]; - if (length > 0) { + if (length > 0 && sp > 0) { int tempo = (sp * 1000) / length; - gdi.addStringUT(cy, cx + c5, fontSmall | textRight, formatTime(tempo)); + gdi.addStringUT(cy, cx + c5, fontSmall | textRight, formatTime(tempo, SubSecond::Off)); } } @@ -4943,7 +5116,7 @@ void oRunner::printSplits(gdioutput& gdi) const { for (int k = pc->useFirstAsStart() ? 1 : 0; k < last; k++) { int missed = getMissedTime(k); if (missed > 0) { - misses.push_back(pc->getControlOrdinal(k) + L"/" + formatTime(missed)); + misses.push_back(pc->getControlOrdinal(k) + L"/" + formatTime(missed, SubSecond::Off)); } } if (misses.size() == 0) { @@ -4985,39 +5158,39 @@ void oRunner::printSplits(gdioutput& gdi) const { if (headerPos.count(cx) == 0) { headerPos.insert(cx); gdi.addString("", cyHead, cx, italicSmall, "Kontroll"); - gdi.addString("", cyHead, cx + c2 - 55, italicSmall, "Tid"); + gdi.addString("", cyHead, cx + c2 - gdi.scaleLength(55/2), italicSmall, "Tid"); } bool any = false; wstring punchTime; if (it.isFinish(finishType)) { gdi.addString("", cy, cx, fontSmall, "MÃ¥l"); - int rt = it.Time - tStartTime; + int rt = it.getTimeInt() - tStartTime; if (rt > 0) { - gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(rt - lastTime)); - punchTime = formatTime(getRunningTime(true)); + gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(rt - lastTime, SubSecond::Off)); + punchTime = formatTime(getRunningTime(true), SubSecond::Off); } - gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it.Time)); + gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it.getTimeInt(), SubSecond::Off)); any = true; if (!punchTime.empty()) { gdi.addStringUT(cy, cx + c4, fontSmall | textRight, punchTime); } } - else if (it.Type > 10 && it.Type != startType) { //Filter away check and start + else if (it.type > 10 && it.type != startType) { //Filter away check and start sprintf_s(bf, "%d.", ++index); gdi.addStringUT(cy, cx, fontSmall, bf); - sprintf_s(bf, "(%d)", it.Type); + sprintf_s(bf, "(%d)", it.type); gdi.addStringUT(cy, cx + c1, fontSmall, bf); - if (it.Time > 0) { - int rt = it.Time - tStartTime; - punchTime = formatTime(rt); - gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(rt - lastTime)); + if (it.hasTime()) { + int rt = it.getTimeInt() - tStartTime; + punchTime = formatTime(rt, SubSecond::Off); + gdi.addStringUT(cy, cx + c2, fontSmall | textRight, formatTime(rt - lastTime, SubSecond::Off)); lastTime = rt; } - if (it.Time > 0) - gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it.Time)); + if (it.hasTime()) + gdi.addStringUT(cy, cx + c3, fontSmall, oe->getAbsTime(it.getTimeInt(), SubSecond::Off)); else { wstring str = makeDash(L"-"); gdi.addStringUT(cy, cx + c3, fontSmall, str); @@ -5046,7 +5219,7 @@ void oRunner::printSplits(gdioutput& gdi) const { if (tInTeam) oe->calculateTeamResults(std::set({ getClassId(true) }), oEvent::ResultType::ClassResult); - if (withResult && statusOK(true)) { + if (withResult && statusOK(true, true)) { gdi.dropLine(0.5); wstring place = oe->formatListString(lRunnerGeneralPlace, pRunner(this), L"%s"); wstring timestatus; @@ -5070,32 +5243,36 @@ void oRunner::printSplits(gdioutput& gdi) const { gdi.addString("", normal, place + timestatus + after); gdi.popX(); } + + if (Card->miliVolt > 0) { + gdi.dropLine(0.7); + auto stat = Card->isCriticalCardVoltage(); + wstring warning; + if (stat == oCard::BatteryStatus::Bad) + warning = lang.tl("Replace[battery]"); + else if (stat == oCard::BatteryStatus::Warning) + warning = lang.tl("Low"); + else + warning = lang.tl("OK"); + gdi.fillRight(); + gdi.pushX(); + gdi.addString("", fontSmall, L"Batteristatus:"); + gdi.addStringUT(boldSmall, getCard()->getCardVoltage()); + gdi.fillDown(); + gdi.addStringUT(fontSmall, L"(" + warning + L")"); + gdi.popX(); + } } - gdi.dropLine(0.7); - if (getCard() && getCard()->miliVolt > 0) { - auto stat = getCard()->isCriticalCardVoltage(); - wstring warning; - if (stat == oCard::BatteryStatus::Bad) - warning = lang.tl("Replace[battery]"); - else if (stat == oCard::BatteryStatus::Warning) - warning = lang.tl("Low"); - else - warning = lang.tl("OK"); - gdi.fillRight(); - gdi.pushX(); - gdi.addString("", fontSmall, L"Batteristatus:"); - gdi.addStringUT(boldSmall, getCard()->getCardVoltage()); - gdi.fillDown(); - gdi.addStringUT(fontSmall, L"(" + warning + L")"); - gdi.dropLine(0.7); - gdi.popX(); + + if (li) { + oe->generateList(gdi, false, *li, false); + gdi.dropLine(); } vector< pair > lines; oe->getExtraLines("SPExtra", lines); - for (size_t k = 0; k < lines.size(); k++) { gdi.addStringUT(lines[k].second, formatExtraLine(pRunner(this), lines[k].first)); } @@ -5236,7 +5413,7 @@ bool oRunner::updateFromDB(const wstring &name, int clubId, int classId, //setName(db_r->getName()); //setClub(db_r->getClub()); Don't... setExtIdentifier(db_r->getExtIdentifier()); - setBirthYear(db_r->getBirthYear()); + setBirthDate(db_r->getBirthDate()); setSex(db_r->getSex()); setNationality(db_r->getNationality()); return true; @@ -5247,7 +5424,7 @@ bool oRunner::updateFromDB(const wstring &name, int clubId, int classId, if (db_r) { setExtIdentifier(db_r->getExtIdentifier()); - setBirthYear(db_r->getBirthYear()); + setBirthDate(db_r->getBirthDate()); setSex(db_r->getSex()); setNationality(db_r->getNationality()); return true; @@ -5255,7 +5432,7 @@ bool oRunner::updateFromDB(const wstring &name, int clubId, int classId, else if (getExtIdentifier()>0) { db_r = oe->dbLookUpById(getExtIdentifier()); if (db_r && db_r->matchName(name)) { - setBirthYear(db_r->getBirthYear()); + setBirthDate(db_r->getBirthDate()); setSex(db_r->getSex()); setNationality(db_r->getNationality()); return true; @@ -5290,6 +5467,14 @@ int oRunner::getBirthYear() const return getDCI().getInt("BirthYear"); } +void oRunner::setBirthDate(const wstring& date) { + getDI().setDate("BirthYear", date); +} + +const wstring &oRunner::getBirthDate() const { + return getDCI().getDate("BirthYear"); +} + void oAbstractRunner::setSpeakerPriority(int year) { if (Class) { @@ -5621,7 +5806,7 @@ void oRunner::getLegTimeAfterAcc(vector ×) const for (unsigned k = 0; k<=nc; k++) { int s = 0; if (k < sp.size()) - s = sp[k].time; + s = sp[k].getTime(true); else if (k==nc) s = FinishTime; @@ -5666,7 +5851,7 @@ void oRunner::getLegPlacesAcc(vector &places) const for (unsigned k = 0; k<=nc; k++) { int s = 0; if (k < sp.size()) - s = sp[k].time; + s = sp[k].getTime(true); else if (k==nc) s = FinishTime; @@ -5729,14 +5914,14 @@ int oRunner::getMissedTime() const { } wstring oRunner::getMissedTimeS() const { - return getTimeMS(getMissedTime()); + return formatTimeMS(getMissedTime(), false, SubSecond::Off); } wstring oRunner::getMissedTimeS(int ctrlNo) const { int t = getMissedTime(ctrlNo); if (t>0) - return getTimeMS(t); + return formatTimeMS(t, false, SubSecond::Off); else return L""; } @@ -5787,16 +5972,13 @@ int oRunner::getLegTimeAfterAcc(int ctrlNo) const { } int oRunner::getTimeWhenPlaceFixed() const { - if (!Class || !statusOK(true)) + if (!Class || !statusOK(true, true)) return -1; - -#ifndef MEOSDB if (unsigned(tLeg) >= Class->tResultInfo.size()) { oe->analyzeClassResultStatus(); if (unsigned(tLeg) >= Class->tResultInfo.size()) return -1; } -#endif int lst = Class->tResultInfo[tLeg].lastStartTime; return lst > 0 ? lst + getRunningTime(false) : lst; @@ -5825,7 +6007,7 @@ pRunner oRunner::getMatchedRunner(const SICard &sic) const { LegTypes lt = Class->getLegType(multiOrdered[k]->tLeg); StartTypes st = Class->getStartType(multiOrdered[k]->tLeg); - if (lt == LTNormal || lt == LTParallel || st==STChange || st == STHunting) + if (lt == LTNormal || lt == LTParallel || st==STChange || st == STPursuit) return pRunner(this); vector crs; @@ -5941,8 +6123,8 @@ wstring oRunner::getCompleteIdentification(bool includeExtra) const { } } -RunnerStatus oAbstractRunner::getTotalStatus() const { - RunnerStatus st = getStatusComputed(); +RunnerStatus oAbstractRunner::getTotalStatus(bool allowUpdate) const { + RunnerStatus st = getStatusComputed(allowUpdate); if (st == StatusUnknown && inputStatus != StatusNotCompetiting) return StatusUnknown; else if (inputStatus == StatusUnknown) @@ -5951,8 +6133,8 @@ RunnerStatus oAbstractRunner::getTotalStatus() const { return max(st, inputStatus); } -RunnerStatus oRunner::getTotalStatus() const { - RunnerStatus stm = getStatusComputed(); +RunnerStatus oRunner::getTotalStatus(bool allowUpdate) const { + RunnerStatus stm = getStatusComputed(allowUpdate); if (stm == StatusUnknown && inputStatus != StatusNotCompetiting) return StatusUnknown; else if (inputStatus == StatusUnknown) @@ -5965,7 +6147,7 @@ RunnerStatus oRunner::getTotalStatus() const { RunnerStatus st = tInTeam->getLegStatus(leg-1, true, true); if (leg + 1 == tInTeam->getNumRunners()) - st = max(st, tInTeam->getStatusComputed()); + st = max(st, tInTeam->getStatusComputed(allowUpdate)); if (st == StatusOK || st == StatusUnknown) return stm; @@ -6100,7 +6282,7 @@ void oRunner::init(const RunnerWDBEntry &dbr, bool updateOnlyExt) { dbr.getName(sName); getRealName(sName, tRealName); getDI().setString("Nationality", dbr.getNationality()); - getDI().setInt("BirthYear", dbr.getBirthYear()); + getDI().setInt("BirthYear", dbr.dbe().getBirthDateInt()); getDI().setString("Sex", dbr.getSex()); setExtIdentifier(dbr.getExtId()); } @@ -6111,7 +6293,7 @@ void oRunner::init(const RunnerWDBEntry &dbr, bool updateOnlyExt) { cardNumber = dbr.dbe().cardNo; Club = oe->getRunnerDatabase().getClub(dbr.dbe().clubNo); getDI().setString("Nationality", dbr.getNationality()); - getDI().setInt("BirthYear", dbr.getBirthYear()); + getDI().setInt("BirthYear", dbr.dbe().getBirthDateInt()); getDI().setString("Sex", dbr.getSex()); setExtIdentifier(dbr.getExtId()); } @@ -6225,10 +6407,10 @@ const vector &oRunner::getSplitTimes(bool normalized) const { if (j == -1) t = getStartTime(); else if (splitTimes[j].hasTime()) - t = splitTimes[j].time; + t = splitTimes[j].getTime(true); j--; } - orderedSplits[mapToOriginal[k]] = splitTimes[k].time - t; + orderedSplits[mapToOriginal[k]] = splitTimes[k].getTime(true) - t; } } @@ -6240,7 +6422,7 @@ const vector &oRunner::getSplitTimes(bool normalized) const { if (j == -1) t = getStartTime(); else if (splitTimes[j].hasTime()) - t = splitTimes[j].time; + t = splitTimes[j].getTime(true); j--; } orderedSplits[mapToOriginal[pc->nControls]] = FinishTime - t; @@ -6284,19 +6466,31 @@ void oRunner::changedObject() { oe->sqlRunners.changed = true; } -int oAbstractRunner::getTimeAdjustment() const { +int oRunner::getBuiltinAdjustment() const { + if (adjustTimes.empty()) + return 0; + + return adjustTimes.back(); +} + +int oAbstractRunner::getTimeAdjustment(bool includeBuiltinAdjustment) const { if (oe->dataRevision != tAdjustDataRevision) { oDataConstInterface dci = getDCI(); tTimeAdjustment = dci.getInt("TimeAdjust"); + tPointAdjustment = dci.getInt("PointAdjust"); tAdjustDataRevision = oe->dataRevision; } - return tTimeAdjustment; + if (!includeBuiltinAdjustment) + return tTimeAdjustment; + else + return tTimeAdjustment + getBuiltinAdjustment(); + } int oAbstractRunner::getPointAdjustment() const { if (oe->dataRevision != tAdjustDataRevision) { - getTimeAdjustment(); //Setup cache + getTimeAdjustment(false); //Setup cache } return tPointAdjustment; } @@ -6323,13 +6517,16 @@ int oRunner::getRogainingPoints(bool computed, bool multidayTotal) const { } int oRunner::getRogainingReduction(bool computed) const { - if (computed && tComputedPoints >= 0 && tRogainingPointsGross >= tComputedPoints) - return tRogainingPointsGross - tComputedPoints; + // if (computed && tComputedPoints >= 0 && tRogainingPointsGross >= tComputedPoints) + // return tRogainingPointsGross - tComputedPoints; return tReduction; } int oRunner::getRogainingPointsGross(bool computed) const { - return tRogainingPointsGross; + if (computed) + return getRogainingPoints(computed, false) + tReduction; + else + return tRogainingPointsGross; } int oRunner::getRogainingOvertime(bool computed) const { @@ -6412,24 +6609,24 @@ const wstring &oAbstractRunner::TempResult::getPrintPlaceS(bool withDot) const { return _EmptyWString; } -const wstring &oAbstractRunner::TempResult::getRunningTimeS(int inputTime) const { - return formatTime(getRunningTime() + inputTime); +const wstring &oAbstractRunner::TempResult::getRunningTimeS(int inputTime, SubSecond mode) const { + return formatTime(getRunningTime() + inputTime, mode); } -const wstring &oAbstractRunner::TempResult::getFinishTimeS(const oEvent *oe) const { - return oe->getAbsTime(getFinishTime()); +const wstring &oAbstractRunner::TempResult::getFinishTimeS(const oEvent *oe, SubSecond mode) const { + return oe->getAbsTime(getFinishTime(), mode); } -const wstring &oAbstractRunner::TempResult::getStartTimeS(const oEvent *oe) const { +const wstring &oAbstractRunner::TempResult::getStartTimeS(const oEvent *oe, SubSecond mode) const { int st = getStartTime(); if (st > 0) - return oe->getAbsTime(st); + return oe->getAbsTime(st, mode); else return makeDash(L"-"); } const wstring &oAbstractRunner::TempResult::getOutputTime(int ix) const { int t = size_t(ix) < outputTimes.size() ? outputTimes[ix] : 0; - return formatTime(t); + return formatTime(t * timeConstSecond); } int oAbstractRunner::TempResult::getOutputNumber(int ix) const { @@ -6513,13 +6710,12 @@ void oAbstractRunner::getInputResults(vector &st, places.resize(nStageNow); for (int j = 0; j < nStageNow; j++) { st[j] = RunnerStatus(_wtoi(spvec[j * 4 + 0].c_str())); - times[j] = _wtoi(spvec[j * 4 + 1].c_str()); + times[j] = parseRelativeTime(spvec[j * 4 + 1].c_str()); points[j] = _wtoi(spvec[j * 4 + 2].c_str()); places[j] = _wtoi(spvec[j * 4 + 3].c_str()); } } - RunnerStatus oAbstractRunner::getStageResult(int stage, int &time, int &point, int &place) const { vector st; vector times; @@ -6544,7 +6740,7 @@ void oAbstractRunner::addToInputResult(int thisStageNo, const oAbstractRunner *s thisStageNo = max(thisStageNo, 0); int p = src->getPlace(); int rt = src->getRunningTime(true); - RunnerStatus st = src->getStatusComputed(); + RunnerStatus st = src->getStatusComputed(true); int pt = src->getRogainingPoints(true, false); const wstring &raw = src->getDCI().getString("InputResult"); @@ -6555,7 +6751,7 @@ void oAbstractRunner::addToInputResult(int thisStageNo, const oAbstractRunner *s int numStage = max(nStageNow, thisStageNo + 1); spvec.resize(numStage * 4); spvec[4*thisStageNo] = itow(st); - spvec[4*thisStageNo+1] = itow(rt); + spvec[4*thisStageNo+1] = codeRelativeTimeW(rt); spvec[4*thisStageNo+2] = itow(pt); spvec[4*thisStageNo+3] = itow(p); @@ -6789,8 +6985,8 @@ int oRunner::getCheckTime() const { else { p = oe->getPunch(Id, oPunch::PunchCheck, getCardNo()); } - if (p && p->Time > 0) - return p->Time; + if (p && p->hasTime()) + return p->getTimeInt(); return 0; } @@ -6804,7 +7000,7 @@ const pair oRunner::getRaceInfo() { int rtActual = getRunningTime(false); int pointsActual = getRogainingPoints(false, false); int pointsComp = getRogainingPoints(true, false); - RunnerStatus compStatus = getStatusComputed(); + RunnerStatus compStatus = getStatusComputed(true); bool ok = compStatus == StatusOK || compStatus == StatusOutOfCompetition || compStatus == StatusNoTiming; res.second = ok ? 1 : -1; @@ -6851,7 +7047,7 @@ const pair oRunner::getRaceInfo() { oe->getPunchesForRunner(Id, true, pl); if (!pl.empty()) { res.first = lang.tl(L"Senast sedd: X vid Y.#" + - oe->getAbsTime(pl.back()->Time) + + oe->getAbsTime(pl.back()->getTimeInt()) + L"#" + pl.back()->getType()); } } @@ -6893,8 +7089,8 @@ void oRunner::setStartGroup(int sg) { getDI().setInt("StartGroup", sg); } -bool oAbstractRunner::isStatusOK(bool computed) const { - RunnerStatus st = computed ? getStatusComputed() : getStatus(); +bool oAbstractRunner::isStatusOK(bool computed, bool allowUpdate) const { + RunnerStatus st = computed ? getStatusComputed(allowUpdate) : getStatus(); if (st == StatusOK) return true; else if (st == StatusOutOfCompetition || st == StatusNoTiming) { @@ -6904,8 +7100,8 @@ bool oAbstractRunner::isStatusOK(bool computed) const { return false; } -bool oAbstractRunner::isStatusUnknown(bool computed) const { - RunnerStatus st = computed ? getStatusComputed() : getStatus(); +bool oAbstractRunner::isStatusUnknown(bool computed, bool allowUpdate) const { + RunnerStatus st = computed ? getStatusComputed(allowUpdate) : getStatus(); if (st == StatusUnknown) return true; else if (st == StatusOutOfCompetition || st == StatusNoTiming) { diff --git a/code/oRunner.h b/code/oRunner.h index 0259d9f..67f3e00 100644 --- a/code/oRunner.h +++ b/code/oRunner.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -82,6 +82,7 @@ enum SortOrder { CourseResult, CourseStartTime, SortByEntryTime, + ClubClassStartTime, Custom, SortEnumLastItem }; @@ -156,9 +157,9 @@ public: } const wstring &getStatusS(RunnerStatus inputStatus) const; const wstring &getPrintPlaceS(bool withDot) const; - const wstring &getRunningTimeS(int inputTime) const; - const wstring &getFinishTimeS(const oEvent *oe) const; - const wstring &getStartTimeS(const oEvent *oe) const; + const wstring &getRunningTimeS(int inputTime, SubSecond mode) const; + const wstring &getFinishTimeS(const oEvent *oe, SubSecond mode) const; + const wstring &getStartTimeS(const oEvent *oe, SubSecond mode) const; const wstring &getOutputTime(int ix) const; int getOutputNumber(int ix) const; @@ -190,6 +191,8 @@ protected: bool sqlChanged; bool tEntryTouched; + virtual int getBuiltinAdjustment() const { return 0; } + mutable pair tPreventRestartCache = { false, -1 }; public: @@ -235,6 +238,7 @@ public: FlagAddedViaAPI = 128, // Added by the REST api entry. FlagOutsideCompetition = 256, FlagNoTiming = 512, // No timing requested + FlagNoDatabase = 1024, // Do not store in databse }; bool hasFlag(TransferFlags flag) const; @@ -306,7 +310,7 @@ public: virtual void apply(ChangeType ct, pRunner src) = 0; //Get time after on leg/for race - virtual int getTimeAfter(int leg) const = 0; + virtual int getTimeAfter(int leg, bool allowUpdate) const = 0; virtual void fillSpeakerObject(int leg, int controlCourseId, int previousControlCourseId, bool totalResult, @@ -361,8 +365,13 @@ public: virtual int getStartTime() const {return tStartTime;} virtual int getFinishTime() const {return FinishTime;} - - int getFinishTimeAdjusted() const {return getFinishTime() + getTimeAdjustment();} + + int getFinishTimeAdjusted(bool adjusted) const { + if (adjusted) + return getFinishTime() + getTimeAdjustment(false); + else + return FinishTime - getBuiltinAdjustment(); + } virtual int getRogainingPoints(bool computed, bool multidayTotal) const = 0; virtual int getRogainingReduction(bool computed) const = 0; @@ -371,12 +380,19 @@ public: virtual const wstring &getStartTimeS() const; virtual const wstring &getStartTimeCompact() const; - virtual const wstring &getFinishTimeS() const; + const wstring &getFinishTimeS(bool adjusted, SubSecond mode) const; - const wstring &getTotalRunningTimeS() const; - const wstring &getRunningTimeS(bool computedTime) const; + const wstring &getTotalRunningTimeS(SubSecond mode) const; + const wstring &getRunningTimeS(bool computedTime, SubSecond mode) const; virtual int getRunningTime(bool computedTime) const; + int getSubSeconds() const { + if (timeConstSecond > 1) + return getRunningTime(false) % timeConstSecond; + else + return 0; + } + /// Get total running time (including earlier stages / races) virtual int getTotalRunningTime() const; @@ -394,27 +410,27 @@ public: virtual int getPlace(bool allowUpdate = true) const = 0; virtual int getTotalPlace(bool allowUpdate = true) const = 0; - RunnerStatus getStatusComputed() const { return tComputedStatus != StatusUnknown ? tComputedStatus : tStatus; } - virtual RunnerStatus getStatus() const { return tStatus;} + virtual RunnerStatus getStatusComputed(bool allowUpdate) const = 0; + RunnerStatus getStatus() const { return tStatus;} /** Status OK, including NoTiming/OutOfCompetition*/ - bool isStatusOK(bool computed) const; + bool isStatusOK(bool computed, bool allowUpdate) const; /** Status unknown, including NoTiming/OutOfCompetition*/ - bool isStatusUnknown(bool computed) const; + bool isStatusUnknown(bool computed, bool allowUpdate) const; - inline bool statusOK(bool computed) const {return (computed ? getStatusComputed() : tStatus) == StatusOK;} - inline bool prelStatusOK(bool computed, bool includeOutsideCompetition) const { - bool ok = statusOK(computed) || (tStatus == StatusUnknown && getRunningTime(false) > 0); + inline bool statusOK(bool computed, bool allowUpdate) const {return (computed ? getStatusComputed(allowUpdate) : tStatus) == StatusOK;} + inline bool prelStatusOK(bool computed, bool includeOutsideCompetition, bool allowUpdate) const { + bool ok = statusOK(computed, allowUpdate) || (tStatus == StatusUnknown && getRunningTime(false) > 0); if (!ok && includeOutsideCompetition) { - RunnerStatus st = (computed ? getStatusComputed() : tStatus); + RunnerStatus st = (computed ? getStatusComputed(true) : tStatus); ok = (st == StatusOutOfCompetition || st == StatusNoTiming) && getRunningTime(false) > 0; } return ok; } // Returns true if the competitor has a definite result bool hasResult() const { - RunnerStatus st = this->getStatusComputed(); + RunnerStatus st = getStatusComputed(true); if (st == StatusUnknown || st == StatusNotCompetiting) return false; if (isPossibleResultStatus(st)) @@ -432,7 +448,7 @@ public: virtual int getRanking() const = 0; /// Get total status for this running (including team/earlier races) - virtual RunnerStatus getTotalStatus() const; + virtual RunnerStatus getTotalStatus(bool allowUpdate = true) const; RunnerStatus getStageResult(int stage, int &time, int &point, int &place) const; // Get results from all previous stages @@ -449,9 +465,9 @@ public: void setSpeakerPriority(int pri); virtual int getSpeakerPriority() const; - int getTimeAdjustment() const; + int getTimeAdjustment(bool includeBuiltinAdjustment) const; int getPointAdjustment() const; - + void setTimeAdjustment(int adjust); void setPointAdjustment(int adjust); @@ -477,39 +493,61 @@ public: struct RunnerWDBEntry; -struct SplitData { - enum SplitStatus {OK, Missing, NoTime}; - int time; +class SplitData { +public: + enum class SplitStatus { OK, Missing, NoTime }; +private: + int time; // Is the adjusted time + int adjustment = 0; // Is the applied adjustment SplitStatus status; +public: SplitData() {}; SplitData(int t, SplitStatus s) : time(t), status(s) {}; + void setAdjustment(int a) { + if (time > 0) + time += a - adjustment; + adjustment = a; + } + + SplitStatus getStatus() const { + return status; + } + + int getTime(bool adjusted) const { + if (adjusted) + return time; + else + return time - adjustment; + } + void setPunchTime(int t) { time = t; - status = OK; + status = SplitStatus::OK; } void setPunched() { time = -1; - status = NoTime; + status = SplitStatus::NoTime; } void setNotPunched() { time = -1; - status = Missing; + status = SplitStatus::Missing; } bool hasTime() const { - return time > 0 && status == OK; + return time > 0 && status == SplitStatus::OK; } bool isMissing() const { - return status == Missing; + return status == SplitStatus::Missing; } + + friend class oRunner; }; -class oRunner : public oAbstractRunner -{ +class oRunner final: public oAbstractRunner { protected: pCourse Course; @@ -670,6 +708,9 @@ protected: bool isHiredCard(int card) const; int tmpStartGroup = 0; + + int getBuiltinAdjustment() const override; + public: static const shared_ptr
&getTable(oEvent *oe); @@ -771,13 +812,13 @@ public: int getTotalRunningTime() const override; //Get total running time after leg - int getRaceRunningTime(bool computedTime, int leg) const; + int getRaceRunningTime(bool computedTime, int leg, bool allowUpdate) const; // Get the complete name, including team and club. wstring getCompleteIdentification(bool includeExtra = true) const; /// Get total status for this running (including team/earlier races) - RunnerStatus getTotalStatus() const override; + RunnerStatus getTotalStatus(bool allowUpdate = true) const override; // Return the runner in a multi-runner set matching the card, if course type is extra pRunner getMatchedRunner(const SICard &sic) const; @@ -824,7 +865,8 @@ public: bool updateFromDB(const wstring &name, int clubId, int classId, int cardNo, int birthYear, bool forceUpdate); - void printSplits(gdioutput &gdi) const; + void printSplits(gdioutput& gdi) const; + void printSplits(gdioutput &gdi, const oListInfo *li) const; void printStartInfo(gdioutput &gdi) const; @@ -857,7 +899,7 @@ public: bool synchronizeAll(bool writeOnly = false); void setFinishTime(int t) override; - int getTimeAfter(int leg) const; + int getTimeAfter(int leg, bool allowUpdate) const override; int getTimeAfter() const; int getTimeAfterCourse() const; @@ -873,6 +915,8 @@ public: oSpeakerObject &spk) const; bool needNoCard() const; + + RunnerStatus getStatusComputed(bool allowUpdate) const final; int getPlace(bool allowUpdate = true) const override; int getCoursePlace(bool perClass) const; int getTotalPlace(bool allowUpdate = true) const override; @@ -892,13 +936,14 @@ public: int getTimeAdjust(int controlNumber) const; int getNamedSplit(int controlNumber) const; + wstring getNamedSplitS(int controlNumber) const; + // Normalized = true means permuted to the unlooped version of the course - int getPunchTime(int controlNumber, bool normalized) const; + int getPunchTime(int controlNumber, bool normalized, bool adjusted) const; + wstring getPunchTimeS(int controlNumber, bool normalized, bool adjusted, SubSecond mode) const; + // Normalized = true means permuted to the unlooped version of the course wstring getSplitTimeS(int controlNumber, bool normalized) const; - // Normalized = true means permuted to the unlooped version of the course - wstring getPunchTimeS(int controlNumber, bool normalized) const; - wstring getNamedSplitS(int controlNumber) const; void addTableRow(Table &table) const; pair inputData(int id, const wstring &input, @@ -954,7 +999,10 @@ public: PersonSex getSex() const; void setBirthYear(int year); + void setBirthDate(const wstring& date); int getBirthYear() const; + const wstring &getBirthDate() const; + void setNationality(const wstring &nat); wstring getNationality() const; diff --git a/code/oTeam.cpp b/code/oTeam.cpp index 7404e7c..7229fae 100644 --- a/code/oTeam.cpp +++ b/code/oTeam.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -70,8 +70,8 @@ bool oTeam::write(xmlparser &xml) xml.write("StartNo", StartNo); xml.write("Updated", getStamp()); xml.write("Name", sName); - xml.write("Start", startTime); - xml.write("Finish", FinishTime); + xml.writeTime("Start", startTime); + xml.writeTime("Finish", FinishTime); xml.write("Status", status); xml.write("Runners", getRunners()); @@ -81,10 +81,9 @@ bool oTeam::write(xmlparser &xml) xml.write("InputPoint", inputPoints); if (inputStatus != StatusOK) xml.write("InputStatus", itos(inputStatus)); //Force write of 0 - xml.write("InputTime", inputTime); + xml.writeTime("InputTime", inputTime); xml.write("InputPlace", inputPlace); - getDI().write(xml); xml.endTag(); @@ -102,16 +101,16 @@ void oTeam::set(const xmlobject &xo) Id=it->getInt(); } else if (it->is("Name")){ - sName=it->getw(); + sName=it->getWStr(); } else if (it->is("StartNo")){ StartNo=it->getInt(); } else if (it->is("Start")){ - tStartTime = startTime = it->getInt(); + tStartTime = startTime = it->getRelativeTime(); } else if (it->is("Finish")){ - FinishTime=it->getInt(); + FinishTime=it->getRelativeTime(); } else if (it->is("Status")) { unsigned rawStatus = it->getInt(); @@ -125,11 +124,11 @@ void oTeam::set(const xmlobject &xo) } else if (it->is("Runners")){ vector r; - decodeRunners(it->getRaw(), r); + decodeRunners(it->getRawStr(), r); importRunners(r); } else if (it->is("InputTime")) { - inputTime = it->getInt(); + inputTime = it->getRelativeTime(); } else if (it->is("InputStatus")) { unsigned rawStatus = it->getInt(); @@ -142,7 +141,7 @@ void oTeam::set(const xmlobject &xo) inputPlace = it->getInt(); } else if (it->is("Updated")){ - Modified.setStamp(it->getRaw()); + Modified.setStamp(it->getRawStr()); } else if (it->is("oData")) { getDI().set(*it); @@ -328,13 +327,13 @@ void oTeam::setRunnerInternal(int k, pRunner r) updateChanged(); } -wstring oTeam::getLegFinishTimeS(int leg) const +wstring oTeam::getLegFinishTimeS(int leg, SubSecond mode) const { if (leg==-1) leg=Runners.size()-1; if (unsigned(leg)getFinishTimeS(); + return Runners[leg]->getFinishTimeS(true, mode); else return L"-"; } @@ -391,7 +390,7 @@ int oTeam::getLegFinishTime(int leg) const while (size_t(ileg) < Class->getNumStages()) { int ift = 0; if (Runners[ileg]) { - ift = Runners[ileg]->getFinishTimeAdjusted(); + ift = Runners[ileg]->getFinishTimeAdjusted(true); } if (ift > 0) { @@ -416,7 +415,7 @@ int oTeam::getLegFinishTime(int leg) const while (size_t(ileg) < Class->getNumStages()) { int ift = 0; if (Runners[ileg]) { - ift = Runners[ileg]->getFinishTimeAdjusted(); + ift = Runners[ileg]->getFinishTimeAdjusted(true); } if (ift > 0) { @@ -452,13 +451,13 @@ int oTeam::getLegRunningTime(int leg, bool computedTime, bool multidayTotal) con if (cr.version == oe->dataRevision) { if (cr.time > 0) - return cr.time + addon + getTimeAdjustment(); + return cr.time + addon + getTimeAdjustment(false); else return 0; } } - return getLegRunningTimeUnadjusted(leg, multidayTotal, false) + getTimeAdjustment(); + return getLegRunningTimeUnadjusted(leg, multidayTotal, false) + getTimeAdjustment(false); } int oTeam::getLegRestingTime(int leg, bool useComputedRunnerTime) const { @@ -468,7 +467,7 @@ int oTeam::getLegRestingTime(int leg, bool useComputedRunnerTime) const { int rest = 0; int R = min(Runners.size(), leg+1); for (int k = 1; k < R; k++) { - if (Class->getStartType(k) == STHunting && !Class->isParallel(k) && + if (Class->getStartType(k) == STPursuit && !Class->isParallel(k) && Runners[k] && Runners[k-1]) { int ft = getLegRunningTimeUnadjusted(k-1, false, useComputedRunnerTime) + tStartTime; @@ -500,9 +499,9 @@ int oTeam::getLegRunningTimeUnadjusted(int leg, bool multidayTotal, bool useComp switch(lt) { case LTNormal: - if (Runners[leg]->prelStatusOK(useComputedRunnerTime, true)) { + if (Runners[leg]->prelStatusOK(useComputedRunnerTime, true, false)) { int dt = leg>0 ? getLegRunningTimeUnadjusted(leg-1, false, useComputedRunnerTime)+Runners[leg]->getRunningTime(useComputedRunnerTime):0; - return addon + max(Runners[leg]->getFinishTimeAdjusted() - + return addon + max(Runners[leg]->getFinishTimeAdjusted(true) - (tStartTime + getLegRestingTime(leg, useComputedRunnerTime)), dt); } else return 0; @@ -510,10 +509,10 @@ int oTeam::getLegRunningTimeUnadjusted(int leg, bool multidayTotal, bool useComp case LTParallelOptional: case LTParallel: //Take the longest time of this runner and the previous - if (Runners[leg]->prelStatusOK(useComputedRunnerTime, false)) { + if (Runners[leg]->prelStatusOK(useComputedRunnerTime, false, false)) { int pt=leg>0 ? getLegRunningTimeUnadjusted(leg-1, false, useComputedRunnerTime) : 0; int rest = getLegRestingTime(leg, useComputedRunnerTime); - int finishT = Runners[leg]->getFinishTimeAdjusted(); + int finishT = Runners[leg]->getFinishTimeAdjusted(true); return addon + max(finishT-(tStartTime + rest), pt); } else return 0; @@ -539,7 +538,7 @@ int oTeam::getLegRunningTimeUnadjusted(int leg, bool multidayTotal, bool useComp if (Runners[cLeg] && Runners[cLeg]->getFinishTime() > 0) { int rt = Runners[cLeg]->getRunningTime(useComputedRunnerTime); if (legTime == 0 || rt < legTime) { - bad = !Runners[cLeg]->prelStatusOK(useComputedRunnerTime, false); + bad = !Runners[cLeg]->prelStatusOK(useComputedRunnerTime, false, false); legTime = rt; } } @@ -555,7 +554,7 @@ int oTeam::getLegRunningTimeUnadjusted(int leg, bool multidayTotal, bool useComp break; case LTSum: - if (Runners[leg]->prelStatusOK(useComputedRunnerTime, false)) { + if (Runners[leg]->prelStatusOK(useComputedRunnerTime, false, false)) { if (leg==0) return addon + Runners[leg]->getRunningTime(useComputedRunnerTime); else { @@ -601,13 +600,13 @@ int oTeam::getLegRunningTimeUnadjusted(int leg, bool multidayTotal, bool useComp return 0; } -wstring oTeam::getLegRunningTimeS(int leg, bool computedTime, bool multidayTotal) const +wstring oTeam::getLegRunningTimeS(int leg, bool computedTime, bool multidayTotal, SubSecond mode) const { if (leg==-1) leg = Runners.size()-1; int rt=getLegRunningTime(leg, computedTime, multidayTotal); - const wstring &bf = formatTime(rt); + const wstring &bf = formatTime(rt, mode); if (rt>0) { if ((unsigned(leg)getStartTime()==Class->getRestartTime(leg)) || getNumShortening(leg)>0) @@ -705,7 +704,7 @@ RunnerStatus oTeam::deduceComputedStatus() const { while (igetLegType(i) == LTIgnore) i++; - int st = Runners[i] ? Runners[i]->getStatusComputed() : StatusDNS; + int st = Runners[i] ? Runners[i]->getStatusComputed(false) : StatusDNS; int bestTime = Runners[i] ? Runners[i]->getFinishTime() : 0; //When Type Extra is used, the runner with the best time @@ -717,7 +716,7 @@ RunnerStatus oTeam::deduceComputedStatus() const { if (Runners[i]) { if (bestTime == 0 || (Runners[i]->getFinishTime()>0 && Runners[i]->getFinishTime()getStatusComputed(); + st = Runners[i]->getStatusComputed(false); bestTime = Runners[i]->getFinishTime(); } } @@ -736,7 +735,7 @@ RunnerStatus oTeam::deduceComputedStatus() const { } int oTeam::deduceComputedRunningTime() const { - return getLegRunningTimeUnadjusted(Runners.size() - 1, false, true) + getTimeAdjustment(); + return getLegRunningTimeUnadjusted(Runners.size() - 1, false, true) + getTimeAdjustment(false); } int oTeam::deduceComputedPoints() const { @@ -755,6 +754,15 @@ const wstring &oTeam::getLegStatusS(int leg, bool computed, bool multidayTotal) return oe->formatStatus(getLegStatus(leg, computed, multidayTotal), true); } +RunnerStatus oTeam::getStatusComputed(bool allowUpdate) const { + auto& p = getTeamPlace(Runners.size()-1); + + if (Class && allowUpdate && p.p.isOld(*oe)) { + oe->calculateTeamResults(std::set({ getClassId(true) }), oEvent::ResultType::ClassResult); + } + return tComputedStatus != StatusUnknown ? tComputedStatus : tStatus; +} + int oTeam::getLegPlace(int leg, bool multidayTotal, bool allowUpdate) const { if (leg == -1) leg = Runners.size() - 1; @@ -804,6 +812,27 @@ wstring oTeam::getLegPrintPlaceS(int leg, bool multidayTotal, bool withDot) cons return _EmptyWString; } +bool oTeam::compareResultClub(const oTeam& a, const oTeam& b) { + pClub ca = a.getClubRef(); + pClub cb = b.getClubRef(); + if (ca != cb) { + if (ca == nullptr && cb) + return true; + else if (cb == nullptr) + return false; + + const wstring an = ca->getName(); + const wstring bn = cb->getName(); + int res = CompareString(LOCALE_USER_DEFAULT, 0, + an.c_str(), an.length(), + bn.c_str(), bn.length()); + + if (res != CSTR_EQUAL) + return res == CSTR_LESS_THAN; + } + return compareResult(a, b); +} + bool oTeam::compareResult(const oTeam &a, const oTeam &b) { if (a.Class != b.Class) { @@ -888,9 +917,7 @@ void oTeam::setTeamMemberStatus(RunnerStatus dnsStatus) assert(!isResultStatus(dnsStatus) || dnsStatus == StatusOK); setStatus(dnsStatus, true, ChangeType::Update); for (unsigned i = 0; i < Runners.size(); i++) { - if (Runners[i] && !isResultStatus(Runners[i]->getStatus()) || - ((dnsStatus == StatusOutOfCompetition || dnsStatus == StatusNoTiming) && - Runners[i]->statusOK(false))) { + if (Runners[i] && (!isResultStatus(Runners[i]->getStatus()) || ((dnsStatus == StatusOutOfCompetition || dnsStatus == StatusNoTiming) && Runners[i]->statusOK(false, false)))) { Runners[i]->setStatus(dnsStatus, true, ChangeType::Update); } } @@ -1169,7 +1196,7 @@ void oTeam::apply(ChangeType changeType, pRunner source) { // Take the best time for extra runners while (z>0 && (tlt==LTExtra || tlt==LTIgnore)) { tlt = pc->getLegType(--z); - if (Runners[z] && Runners[z]->getStatus() == StatusOK) { + if (Runners[z]) { int tft = Runners[z]->getFinishTime(); if (tft>0 && tlt != LTIgnore) ft = ft>0 ? min(tft, ft) : tft; @@ -1192,7 +1219,7 @@ void oTeam::apply(ChangeType changeType, pRunner source) { tNumRestarts++; } - if (ft > 0) + if (ft >= 0) tr->setStartTime(ft, false, changeType); tr->tUseStartPunch=false; lastStartTime=ft; @@ -1204,7 +1231,7 @@ void oTeam::apply(ChangeType changeType, pRunner source) { } break; - case STHunting: { + case STPursuit: { bool setStart = false; if (i>0 && Runners[i-1]) { if (lt == LTNormal || lt == LTSum || availableStartTimes.empty()) { @@ -1275,6 +1302,9 @@ void oTeam::apply(ChangeType changeType, pRunner source) { // Extra finish time is used to split extra legs to parallel legs if (lt == LTExtra || pc->getLegType(i+1) == LTExtra) { + if (lt != LTExtra) + extraFinishTime = -1; + if (tr->getFinishTime()>0) { if (extraFinishTime <= 0) extraFinishTime = tr->getFinishTime(); @@ -1648,7 +1678,7 @@ void oTeam::fillSpeakerObject(int leg, int courseControlId, int previousControlC spk.status = spk.finishStatus; } -int oTeam::getTimeAfter(int leg) const { +int oTeam::getTimeAfter(int leg, bool allowUpdate) const { if (leg == -1) leg = Runners.size() - 1; @@ -2018,9 +2048,9 @@ void oTeam::addTableRow(Table &table) const { table.set(row++, it, TID_CLUB, getClub(), true, cellCombo); table.set(row++, it, TID_START, getStartTimeS(), true); - table.set(row++, it, TID_FINISH, getFinishTimeS(), true); + table.set(row++, it, TID_FINISH, getFinishTimeS(false, SubSecond::Auto), true); table.set(row++, it, TID_STATUS, getStatusS(false, true), true, cellSelection); - table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(true), false); + table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(true, SubSecond::Auto), false); int rp = getRogainingPoints(true, false); table.set(row++, it, TID_POINTS, rp ? itow(rp) : L"", false); @@ -2095,7 +2125,7 @@ pair oTeam::inputData(int id, const wstring &input, } else { if (isName && !input.empty() && Class) { - pRunner r = oe->addRunner(input, getClubId(), getClassId(false), 0, 0, false); + pRunner r = oe->addRunner(input, getClubId(), getClassId(false), 0, L"", false); setRunner(ix, r, true); output = r->getName(); } @@ -2501,7 +2531,7 @@ const pair oTeam::getRaceInfo() { int rtComp = getRunningTime(true); int pointsActual = getRogainingPoints(false, false); int pointsComp = getRogainingPoints(true, false); - RunnerStatus compStatus = getStatusComputed(); + RunnerStatus compStatus = getStatusComputed(true); bool ok = compStatus == StatusOK || compStatus == StatusOutOfCompetition || compStatus == StatusNoTiming; res.second = ok ? 1 : -1; @@ -2547,7 +2577,7 @@ const pair oTeam::getRaceInfo() { oe->getPunchesForRunner(Id, true, pl); if (!pl.empty()) { res.first = lang.tl(L"Senast sedd: X vid Y.#" + - oe->getAbsTime(pl.back()->Time) + + oe->getAbsTime(pl.back()->getTimeInt()) + L"#" + pl.back()->getType()); } } diff --git a/code/oTeam.h b/code/oTeam.h index 5eafd77..34de881 100644 --- a/code/oTeam.h +++ b/code/oTeam.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -31,8 +31,7 @@ typedef const oTeam* cTeam; const unsigned int maxRunnersTeam=32; -class oTeam : public oAbstractRunner -{ +class oTeam final : public oAbstractRunner { public: enum ResultCalcCacheSymbol { RCCCourse, @@ -235,6 +234,9 @@ public: void importRunners(const vector &rns); void importRunners(const vector &rns); + + RunnerStatus getStatusComputed(bool allowUpdate) const final; + int getPlace(bool allowUpdate = true) const override {return getLegPlace(-1, false, allowUpdate);} int getTotalPlace(bool allowUpdate = true) const override {return getLegPlace(-1, true, allowUpdate);} @@ -251,13 +253,13 @@ public: wstring getLegStartTimeS(int leg) const; wstring getLegStartTimeCompact(int leg) const; - wstring getLegFinishTimeS(int leg) const; + wstring getLegFinishTimeS(int leg, SubSecond mode) const; int getLegFinishTime(int leg) const; - int getTimeAfter(int leg) const; + int getTimeAfter(int leg, bool allowUpdate) const override; //Get total running time after leg - wstring getLegRunningTimeS(int leg, bool computed, bool multidayTotal) const; + wstring getLegRunningTimeS(int leg, bool computed, bool multidayTotal, SubSecond mode) const; int getLegRunningTime(int leg, bool computed, bool multidayTotal) const; @@ -274,7 +276,8 @@ public: static bool compareName(const oTeam &a, const oTeam &b) {return a.sName &clsWithRef); static void convertClassWithReferenceToPatrol(oEvent &oe, const set &clsWithRef); diff --git a/code/oTeamEvent.cpp b/code/oTeamEvent.cpp index be16b99..1e51b18 100644 --- a/code/oTeamEvent.cpp +++ b/code/oTeamEvent.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -296,6 +296,7 @@ pTeam oEvent::findTeam(const wstring &s, int lastId, unordered_set &filter) bool oTeam::matchTeam(int number, const wchar_t *s_lc) const { + int score; if (number) { if (matchNumber(StartNo, s_lc )) return true; @@ -306,11 +307,11 @@ bool oTeam::matchTeam(int number, const wchar_t *s_lc) const } } - if (filterMatchString(sName, s_lc)) + if (filterMatchString(sName, s_lc, score)) return true; for(size_t k=0;ktRealName, s_lc)) + if (Runners[k] && filterMatchString(Runners[k]->tRealName, s_lc, score)) return true; return false; @@ -642,11 +643,11 @@ void oEvent::setupRelay(oClass &cls, PredefinedTypes type, int nleg, const wstri cls.setRopeTime(0, L"-"); cls.setLegType(1, LTSum); - cls.setStartType(1, STHunting, false); - int t = convertAbsoluteTimeHMS(start, ZeroTime)+3600; + cls.setStartType(1, STPursuit, false); + int t = convertAbsoluteTimeHMS(start, ZeroTime)+timeConstHour; cls.setStartData(1, formatTimeHMS(t)); - cls.setRestartTime(1, formatTimeHMS(t+1800)); - cls.setRopeTime(1, formatTimeHMS(t+1800)); + cls.setRestartTime(1, formatTimeHMS(t+timeConstHour/2)); + cls.setRopeTime(1, formatTimeHMS(t+ timeConstHour / 2)); cls.setLegRunner(1, 0); cls.setCoursePool(false); getMeOSFeatures().useFeature(MeOSFeatures::Relay, true, *this); @@ -850,7 +851,7 @@ void oTeam::convertClassWithReferenceToPatrol(oEvent &oe, const std::set &c } void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map &classId2Linear, bool &hasRunner) const { - if (so == ClassStartTime || so == ClassStartTimeClub) { + if (so == ClassStartTime || so == ClassStartTimeClub || so == ClubClassStartTime) { if (leg == -1) leg = 0; if (unsigned(leg) < Runners.size()) @@ -865,7 +866,7 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map else if (so == ClassPoints) { bool totalResult = so == ClassTotalResult; setTmpTime(getRunningTime(true)); - tmpSortTime -= 7 * 24 * 3600 * getRogainingPoints(true, totalResult); + tmpSortTime -= 7 * 24 * timeConstHour * getRogainingPoints(true, totalResult); tmpCachedStatus = getLegStatus(-1, true, totalResult); } else if (so == ClassKnockoutTotalResult) { @@ -878,7 +879,7 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map int numResult = 0; int lastClassHeat = 0; for (auto &r : Runners) { - if (r && (r->prelStatusOK(true, true) || + if (r && (r->prelStatusOK(true, true, false) || (r->tStatus != StatusUnknown && r->tStatus != StatusDNS && r->tStatus != StatusCANCEL))) { if (r->Class && r->tLeg > 0 && r->Class->isQualificationFinalBaseClass() && r->getClassRef(true) == r->Class) @@ -937,7 +938,7 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map } else { setTmpTime(r->getRunningTime(true)); - tmpCachedStatus = r->getStatusComputed(); + tmpCachedStatus = r->getStatusComputed(false); } } else { @@ -948,18 +949,18 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map else { if (so == ClassDefaultResult) { setTmpTime(getLegRunningTime(lg, false, totalResult)); - tmpSortTime += getNumShortening(lg) * 3600 * 24 * 10; + tmpSortTime += getNumShortening(lg) * timeConstHour * 24 * 10; tmpCachedStatus = getLegStatus(lg, false, totalResult); } else { setTmpTime(getLegRunningTime(lg, true, totalResult)); - tmpSortTime += getNumShortening(lg) * 3600 * 24 * 10; + tmpSortTime += getNumShortening(lg) * timeConstHour * 24 * 10; tmpCachedStatus = getLegStatus(lg, true, totalResult); } // Ensure number of restarts has effect on final result if (lg == lastIndex) - tmpSortTime += tNumRestarts * 24 * 3600; + tmpSortTime += tNumRestarts * 24 * timeConstHour; } } unsigned rawStatus = tmpCachedStatus; @@ -981,6 +982,8 @@ bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) { if (so == ClassStartTimeClub) Teams.sort(oTeam::compareResultNoSno); + else if (so == ClubClassStartTime) + Teams.sort(oTeam::compareResultClub); else Teams.sort(oTeam::compareResult); @@ -998,7 +1001,9 @@ bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg, vectorbool {return oTeam::compareResultClub(*a, *b); }); + else if (so != ClassStartTimeClub) 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); }); @@ -1017,7 +1022,9 @@ bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg, vector &t if (!hasRunner) return false; - if (so != ClassStartTimeClub) + if (so == ClubClassStartTime) + sort(teams.begin(), teams.end(), [](oTeam * &a, oTeam * &b)->bool {return oTeam::compareResultClub(*a, *b); }); + else if (so != ClassStartTimeClub) sort(teams.begin(), teams.end(), [](oTeam * &a, oTeam * &b)->bool {return oTeam::compareResult(*a, *b); }); else sort(teams.begin(), teams.end(), [](oTeam * &a, oTeam * &b)->bool {return oTeam::compareResultNoSno(*a, *b); }); diff --git a/code/oevent_transfer.cpp b/code/oevent_transfer.cpp index ca63ad0..a8e2e4c 100644 --- a/code/oevent_transfer.cpp +++ b/code/oevent_transfer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 @@ -602,9 +602,9 @@ wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes, if (addToDate) { SYSTEMTIME st; convertDateYMS(Date, st, false); - __int64 absD = SystemTimeToInt64Second(st); - absD += 3600 * 24; - ce.Date = convertSystemDate(Int64SecondToSystemTime(absD)); + __int64 absD = SystemTimeToInt64TenthSecond(st); + absD += timeConstHour * 24; + ce.Date = convertSystemDate(Int64TenthSecondToSystemTime(absD)); } int len = Name.length(); if (len > 2 && isdigit(Name[len - 1]) && !isdigit(Name[len - 2])) { @@ -614,6 +614,7 @@ wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes, ce.Name += L" E2"; memcpy(ce.oData, oData, sizeof(oData)); + ce.dynamicData = dynamicData; for (oClubList::iterator it = Clubs.begin(); it != Clubs.end(); ++it) { if (it->isRemoved()) @@ -1159,7 +1160,7 @@ void oEvent::transferResult(oEvent &ce, continue; pRunner dst = ce.addRunner(src->sName, src->getClub(), classMap[src->getClassId(false)], - src->getCardNo(), src->getBirthYear(), true); + src->getCardNo(), src->getBirthDate(), true); dst->cloneData(src); dst->setInputData(*src); newEntries.push_back(dst); @@ -1172,7 +1173,7 @@ void oEvent::transferResult(oEvent &ce, continue; pRunner dst = ce.addRunner(src->sName, src->getClub(), classMap[src->getClassId(false)], - 0, src->getBirthYear(), true); + 0, src->getBirthDate(), true); dst->cloneData(src); dst->setInputData(*src); dst->setStatus(StatusNotCompetiting, true, ChangeType::Update); diff --git a/code/onlineinput.cpp b/code/onlineinput.cpp index ec94ad8..2c48c06 100644 --- a/code/onlineinput.cpp +++ b/code/onlineinput.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -112,11 +112,10 @@ int OnlineInput::processButton(gdioutput &gdi, ButtonInfo &bi) { void OnlineInput::fillMappings(gdioutput &gdi) const{ gdi.clearList("Mappings"); for (map::const_iterator it = specialPunches.begin(); it != specialPunches.end(); ++it) { - gdi.addItem("Mappings", itow(it->first) + L" -> " + oPunch::getType(it->second), it->first); + gdi.addItem("Mappings", itow(it->first) + L" \u21A6 " + oPunch::getType(it->second), it->first); } } - void OnlineInput::settings(gdioutput &gdi, oEvent &oe, State state) { int iv = interval; if (state == State::Create) { @@ -133,14 +132,24 @@ void OnlineInput::settings(gdioutput &gdi, oEvent &oe, State state) { gdi.addInput("URL", url, 40, 0, L"URL:", L"Till exempel X#http://www.input.org/online.php"); gdi.addCheckbox("UseROC", "Använd ROC-protokoll", OnlineCB, useROCProtocol).setExtra(getId()); - gdi.addCheckbox("UseUnitId", "Använd enhets-id istället för tävlings-id", OnlineCB, useROCProtocol & useUnitId).setExtra(getId()); + gdi.addCheckbox("UseUnitId", "Använd enhets-id istället för tävlings-id", OnlineCB, useROCProtocol && useUnitId).setExtra(getId()); gdi.setInputStatus("UseUnitId", useROCProtocol); gdi.addInput("CmpID", itow(cmpId), 10, 0, L"Tävlingens ID-nummer:"); gdi.dropLine(1); - gdi.addString("", boldText, "Kontrollmappning"); gdi.dropLine(0.5); + + controlMappingView(gdi, OnlineCB, getId()); + fillMappings(gdi); + + gdi.setCY(gdi.getHeight()); + gdi.popX(); + gdi.addString("", 10, "help:onlineinput"); +} + +void OnlineInput::controlMappingView(gdioutput& gdi, GUICALLBACK cb, int widgetId) +{ gdi.fillRight(); gdi.addInput("Code", L"", 4, 0, L"Kod:"); gdi.addSelection("Function", 80, 200, 0, L"Funktion:"); @@ -148,17 +157,12 @@ void OnlineInput::settings(gdioutput &gdi, oEvent &oe, State state) { gdi.addItem("Function", lang.tl("Start"), oPunch::PunchStart); gdi.addItem("Function", lang.tl("Check"), oPunch::PunchCheck); gdi.dropLine(); - gdi.addButton("SaveMapping", "Lägg till", OnlineCB).setExtra(getId()); + gdi.addButton("SaveMapping", "Lägg till", cb).setExtra(widgetId); gdi.popX(); gdi.dropLine(2); - gdi.addListBox("Mappings", 150, 100, 0, L"Definierade mappningar:", L"", true); + gdi.addListBox("Mappings", 150, 150, 0, L"Definierade mappningar:", L"", true); gdi.dropLine(); - gdi.addButton("RemoveMapping", "Ta bort", OnlineCB).setExtra(getId()); - fillMappings(gdi); - - gdi.setCY(gdi.getHeight()); - gdi.popX(); - gdi.addString("", 10, "help:onlineinput"); + gdi.addButton("RemoveMapping", "Ta bort", cb).setExtra(widgetId); } void OnlineInput::save(oEvent &oe, gdioutput &gdi, bool doProcess) { @@ -338,13 +342,14 @@ void OnlineInput::processPunches(oEvent &oe, const xmlList &punches) { wstring startno; punches[k].getObjectString("sno", startno); + int originalCode = code; if (specialPunches.count(code)) code = specialPunches[code]; pRunner r = 0; int card = punches[k].getObjectInt("card"); - int time = punches[k].getObjectInt("time") / 10; + int time = punches[k].getObjectInt("time") / (10 / timeConstSecond); time = oe.getRelativeTime(formatTimeHMS(time)); if (startno.length() > 0) @@ -364,7 +369,7 @@ void OnlineInput::processPunches(oEvent &oe, const xmlList &punches) { time = 0; addInfo(L"Ogiltig tid"); } - oe.addFreePunch(time, code, card, true); + oe.addFreePunch(time, code, originalCode, card, true); addInfo(L"Löpare: X, kontroll: Y, kl Z#" + rname + L"#" + oPunch::getType(code) + L"#" + oe.getAbsTime(time)); } @@ -380,6 +385,7 @@ void OnlineInput::processPunches(oEvent &oe, list< vector > &rocData) { wstring timeS = line[3].substr(11); int time = oe.getRelativeTime(timeS); + int originalCode = code; if (specialPunches.count(code)) code = specialPunches[code]; @@ -398,7 +404,7 @@ void OnlineInput::processPunches(oEvent &oe, list< vector > &rocData) { time = 0; addInfo(L"Ogiltig tid"); } - oe.addFreePunch(time, code, card, true); + oe.addFreePunch(time, code, originalCode, card, true); lastImportedId = max(lastImportedId, punchId); @@ -414,14 +420,14 @@ void OnlineInput::processCards(gdioutput &gdi, oEvent &oe, const xmlList &cards) SICard sic(ConvertedTimeStatus::Hour24); sic.CardNumber = cards[k].getObjectInt("number"); if (cards[k].getObject("finish")) - sic.FinishPunch.Time = cards[k].getObject("finish").getObjectInt("time") / 10; + sic.FinishPunch.Time = cards[k].getObject("finish").getObjectInt("time") / (10 / timeConstSecond); if (cards[k].getObject("start")) - sic.StartPunch.Time = cards[k].getObject("start").getObjectInt("time") / 10; + sic.StartPunch.Time = cards[k].getObject("start").getObjectInt("time") / (10 / timeConstSecond); xmlList punches; cards[k].getObjects("p", punches); for (size_t j = 0; j < punches.size(); j++) { sic.Punch[j].Code = punches[j].getObjectInt("code"); - sic.Punch[j].Time = punches[j].getObjectInt("time") / 10; + sic.Punch[j].Time = punches[j].getObjectInt("time") / (10 / timeConstSecond); } sic.nPunch = punches.size(); TabSI::getSI(gdi).addCard(sic); @@ -463,9 +469,9 @@ void OnlineInput::processEntries(oEvent &oe, const xmlList &entries) { bool paid = entry.getObjectBool("paid"); xmlobject xname = entry.getObject("name"); - int birthyear = 0; + wstring birthyear = 0; if (xname) { - birthyear = xname.getObjectInt("birthyear"); + xname.getObjectString("birthyear", birthyear); } wstring name; @@ -513,7 +519,7 @@ void OnlineInput::processEntries(oEvent &oe, const xmlList &entries) { else { r->setName(name, false); r->setClub(club); - r->setBirthYear(birthyear); + r->setBirthDate(birthyear); r->setCardNo(cardNo, false, false); r->setClassId(cls->getId(), true); } diff --git a/code/onlineinput.h b/code/onlineinput.h index 642b6b7..5e40015 100644 --- a/code/onlineinput.h +++ b/code/onlineinput.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -73,6 +73,7 @@ public: void save(oEvent &oe, gdioutput &gdi, bool doProcess) final; void settings(gdioutput &gdi, oEvent &oe, State state) final; + static void controlMappingView(gdioutput& gdi, GUICALLBACK cb, int widgetId); OnlineInput *clone() const {return new OnlineInput(*this);} void status(gdioutput &gdi) final; void process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) final; diff --git a/code/onlineresults.cpp b/code/onlineresults.cpp index d3db4ed..eb149cf 100644 --- a/code/onlineresults.cpp +++ b/code/onlineresults.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -103,6 +103,11 @@ void OnlineResults::settings(gdioutput &gdi, oEvent &oe, State state) { url = oe.getPropertyString("MOPURL", L""); file = oe.getPropertyString("MOPFolderName", L""); oe.getAllClasses(classes); + allClasses = true; + } + else if (allClasses && state == State::Edit) { + if (allClasses) + oe.getAllClasses(classes); } wstring time; @@ -283,6 +288,8 @@ void OnlineResults::save(oEvent &oe, gdioutput &gdi, bool doProcess) { getInfoServer().includeCourse(includeCourse); } gdi.getSelection("Classes", classes); + allClasses = classes.size() == oe.getNumClasses(); + if (sendToFile) { if (folder.empty()) { throw meosException("Mappnamnet fÃ¥r inte vara tomt."); @@ -394,6 +401,9 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { ProgressWindow pwMain((sendToURL && ast == SyncNone) ? gdi.getHWNDTarget() : 0); pwMain.init(); + if (allClasses) + oe->getAllClasses(classes); + wstring t; int xmlSize = 0; InfoCompetition &ic = getInfoServer(); @@ -408,10 +418,10 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { t = getTempFile(); if (dataType == DataType::IOF2) oe->exportIOFSplits(oEvent::IOF20, t.c_str(), false, false, - classes, -1, false, true, true, false); + classes, -1, false, true, true, false, false); else if (dataType == DataType::IOF3) oe->exportIOFSplits(oEvent::IOF30, t.c_str(), false, false, - classes, -1, false, true, true, false); + classes, -1, false, true, true, false, false); else throw meosException("Internal error"); } @@ -460,14 +470,10 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { key.push_back(mk1); pair mk2(L"pwd", passwd); key.push_back(mk2); - if (zipFile) { - pair mk3(L"Content-Type", L"application/zip"); - key.push_back(mk3); - } - else { - pair mk3(L"Content-Type", L"application/xml"); - key.push_back(mk3); - } + + bool addedHeader = false; + bool forceZIP = false; + bool forceNoZip = false; bool moreToWrite = true; string tmp; @@ -483,7 +489,7 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { xmlSize = xmlOut.closeOut(); wstring result = getTempFile(); - if (zipFile && xmlSize > 1024) { + if (!forceNoZip && ((zipFile && xmlSize > 1024) || forceZIP)) { wstring zipped = getTempFile(); zip(zipped.c_str(), 0, vector(1, t)); removeTempFile(t); @@ -496,6 +502,21 @@ void OnlineResults::process(gdioutput &gdi, oEvent *oe, AutoSyncType ast) { else bytesExported +=xmlSize; + + if (!addedHeader) { + if (zipFile) { + forceZIP = true; + pair mk3(L"Content-Type", L"application/zip"); + key.push_back(mk3); + } + else { + forceNoZip = true; + pair mk3(L"Content-Type", L"application/xml"); + key.push_back(mk3); + } + addedHeader = true; + } + dwl.postFile(url, t, result, key, pw); removeTempFile(t); diff --git a/code/onlineresults.h b/code/onlineresults.h index 8676d31..7180b3f 100644 --- a/code/onlineresults.h +++ b/code/onlineresults.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -35,6 +35,8 @@ protected: wstring prefix; int cmpId; set classes; + bool allClasses = false; + set controls; enum class DataType { diff --git a/code/ospeaker.h b/code/ospeaker.h index 8c237a1..155c41c 100644 --- a/code/ospeaker.h +++ b/code/ospeaker.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/parser.cpp b/code/parser.cpp index 6fa1cd9..ae06441 100644 --- a/code/parser.cpp +++ b/code/parser.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/parser.h b/code/parser.h index d3a3964..1d06ce8 100644 --- a/code/parser.h +++ b/code/parser.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/pdfwriter.cpp b/code/pdfwriter.cpp index 76e7770..eb36d64 100644 --- a/code/pdfwriter.cpp +++ b/code/pdfwriter.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -33,12 +33,15 @@ #include "gdifonts.h" #include "gdiimpl.h" #include "Printer.h" +#include "image.h" #define HPDF_DLL #include "hpdf.h" #include "pdfwriter.h" +extern Image image; + wstring getMeosCompectVersion(); void __stdcall pdfErrorhandler(HPDF_STATUS errorNo, @@ -248,6 +251,22 @@ void pdfwriter::generatePDF(const gdioutput &gdi, vector &info = pages[j].text; for (size_t k = 0; k < info.size(); k++) { + if ((info[k].ti.format & 0xFF) == textImage) { + if (info[k].ti.text.empty() || info[k].ti.text[0] != 'L') + throw meosException("Unsupported image"); + + uint64_t imgId = _wcstoui64(info[k].ti.text.c_str() + 1, nullptr, 10); + auto &data = image.getRawData(imgId); + auto image = HPDF_LoadPngImageFromMem(pdf, data.data(), data.size()); + int imgW = info[k].ti.textRect.right - info[k].ti.textRect.left; + int imgH = info[k].ti.textRect.bottom - info[k].ti.textRect.top; + + HPDF_Page_DrawImage(page, image, info[k].xp, h - info[k].yp, (imgW*scale), (imgH*scale)); + + continue; + } + + if (fonts.count(info[k].ti.font) == 0) { FontInfo fi; gdi.getFontInfo(info[k].ti, fi); diff --git a/code/pdfwriter.h b/code/pdfwriter.h index 3f5f858..b43156a 100644 --- a/code/pdfwriter.h +++ b/code/pdfwriter.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/prefseditor.cpp b/code/prefseditor.cpp index 398f7ea..869384e 100644 --- a/code/prefseditor.cpp +++ b/code/prefseditor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/prefseditor.h b/code/prefseditor.h index 24ee5e4..029d92b 100644 --- a/code/prefseditor.h +++ b/code/prefseditor.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/printer.cpp b/code/printer.cpp index 555527f..22e1fa6 100644 --- a/code/printer.cpp +++ b/code/printer.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/progress.cpp b/code/progress.cpp index 6be67f7..40d3805 100644 --- a/code/progress.cpp +++ b/code/progress.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/progress.h b/code/progress.h index 5d73ad8..5adadab 100644 --- a/code/progress.h +++ b/code/progress.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/qualification_final.cpp b/code/qualification_final.cpp index f5ac8ac..ab80c12 100644 --- a/code/qualification_final.cpp +++ b/code/qualification_final.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/qualification_final.h b/code/qualification_final.h index 7542d75..7054353 100644 --- a/code/qualification_final.h +++ b/code/qualification_final.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 diff --git a/code/random.cpp b/code/random.cpp index d201171..789ee13 100644 --- a/code/random.cpp +++ b/code/random.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/random.h b/code/random.h index 524e17c..646cd26 100644 --- a/code/random.h +++ b/code/random.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/recorder.cpp b/code/recorder.cpp index 8588bac..d7a04e0 100644 --- a/code/recorder.cpp +++ b/code/recorder.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/recorder.h b/code/recorder.h index afe8c45..810376c 100644 --- a/code/recorder.h +++ b/code/recorder.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/resource.h b/code/resource.h index 223c4e9..2bb1870 100644 --- a/code/resource.h +++ b/code/resource.h @@ -16,6 +16,8 @@ #define IDC_STATIC -1 #define IDI_SPLASHIMAGE 512 #define IDI_MEOSIMAGE 513 +#define IDI_MEOSINFO 514 + // Next default values for new objects // #ifdef APSTUDIO_INVOKED diff --git a/code/restserver.cpp b/code/restserver.cpp index 8de8a67..9df0727 100644 --- a/code/restserver.cpp +++ b/code/restserver.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 @@ -41,6 +41,7 @@ Eksoppsvägen 16, SE-75646 UPPSALA, Sweden #include "RunnerDB.h" #include "image.h" +extern Image image; using namespace restbed; vector< shared_ptr > RestServer::startedServers; @@ -399,19 +400,24 @@ void RestServer::computeInternal(oEvent &ref, shared_ptranswer); } else if (rq->parameters.count("image") > 0) { - ifstream fin; - string image = rq->parameters.find("image")->second; - if (imageCache.count(image)) { - rq->image = imageCache[image]; + string imageId = rq->parameters.find("image")->second; + if (imageCache.count(imageId)) { + rq->image = imageCache[imageId]; } - if (image == "meos") { - imageCache[image] = rq->image = Image::loadResourceToMemory(MAKEINTRESOURCE(513), _T("PNG")); + else if (imageId == "meos") { + imageCache[imageId] = rq->image = Image::loadResourceToMemory(MAKEINTRESOURCE(513), _T("PNG")); + } + else if (imageId.length() > 2 && imageId[0] == 'I' && imageId[1] == 'D') { + uint64_t imgId = _strtoui64(imageId.c_str() + 2, nullptr, 10); + ref.loadImage(imgId); + rq->image = image.getRawData(imgId); } else { wchar_t fn[260]; - if (image.find_first_of("\\/.?*") == string::npos) { - wstring par = wideParam(image) + L".png"; + if (imageId.find_first_of("\\/.?*") == string::npos) { + wstring par = wideParam(imageId) + L".png"; getUserFile(fn, par.c_str()); + ifstream fin; fin.open(fn, ios::binary); if (fin.good()) { fin.seekg(0, ios::end); @@ -421,7 +427,7 @@ void RestServer::computeInternal(oEvent &ref, shared_ptrimage[0], rq->image.size()); fin.close(); - imageCache[image] = rq->image; + imageCache[imageId] = rq->image; } } } @@ -440,15 +446,16 @@ void RestServer::computeInternal(oEvent &ref, shared_ptrsecond.first, *res->second.second); } ref.generateList(gdiPrint, true, *res->second.second, false); - wstring exportFile = getTempFile(); - HTMLWriter::write(gdiPrint, exportFile, ref.getName(), 30, res->second.first, ref); - - ifstream fin(exportFile.c_str()); - string rbf; + //wstring exportFile = getTempFile(); + ostringstream fout; + HTMLWriter::write(gdiPrint, fout, ref.getName(), 30, res->second.first, ref); + rq->answer = fout.str(); + //ifstream fin(exportFile.c_str()); + /*string rbf; while (std::getline(fin, rbf)) { rq->answer += rbf; } - removeTempFile(exportFile); + removeTempFile(exportFile);*/ } else { rq->answer = "Error (MeOS): Unknown list"; @@ -521,7 +528,7 @@ void RestServer::getData(oEvent &oe, const string &what, const multimap 0) getSelection(param.find("class")->second, cls); - oe.exportIOFSplits(oEvent::IOF30, exportFile.c_str(), false, useUTC, cls, -1, false, false, true, false); + oe.exportIOFSplits(oEvent::IOF30, exportFile.c_str(), false, useUTC, cls, -1, false, false, true, false, false); ifstream fin(exportFile.c_str()); string rbf; while (std::getline(fin, rbf)) { @@ -537,7 +544,7 @@ void RestServer::getData(oEvent &oe, const string &what, const multimap 0) getSelection(param.find("class")->second, cls); - oe.exportIOFStartlist(oEvent::IOF30, exportFile.c_str(), useUTC, cls, false, true, false); + oe.exportIOFStartlist(oEvent::IOF30, exportFile.c_str(), useUTC, cls, false, true, false, false); ifstream fin(exportFile.c_str()); string rbf; while (std::getline(fin, rbf)) { @@ -1122,11 +1129,11 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetClubId())) }, r->getClub()); xml.write("Class", { make_pair("id", itow(r->getClassId(true))) }, r->getClass(true)); xml.write("Card", r->getCardNo()); - xml.write("Status", {make_pair("code", itow(r->getStatusComputed()))}, r->getStatusS(true, true)); + xml.write("Status", {make_pair("code", itow(r->getStatusComputed(true)))}, r->getStatusS(true, true)); xml.write("Start", r->getStartTimeS()); - if (r->getFinishTime() > 0 && r->getStatusComputed() != StatusNoTiming && !r->noTiming()) { - xml.write("Finish", r->getFinishTimeS()); - xml.write("RunningTime", r->getRunningTimeS(true)); + if (r->getFinishTime() > 0 && r->getStatusComputed(true) != StatusNoTiming && !r->noTiming()) { + xml.write("Finish", r->getFinishTimeS(true, SubSecond::Auto)); + xml.write("RunningTime", r->getRunningTimeS(true, SubSecond::Auto)); xml.write("Place", r->getPlaceS()); xml.write("TimeAfter", formatTime(r->getTimeAfter())); } @@ -1138,7 +1145,7 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetFinishTime() > 0 || r->getCard() != nullptr) && r->getCourse(false) && - r->getStatusComputed() != StatusNoTiming && + r->getStatusComputed(true) != StatusNoTiming && !r->noTiming()) { auto &sd = r->getSplitTimes(false); vector after; @@ -1162,7 +1169,7 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetName()); if (s.hasTime()) { - xml.write("Time", formatTime(s.time - r->getStartTime())); + xml.write("Time", formatTime(s.getTime(true) - r->getStartTime())); if (size_t(ix) < delta.size() && size_t(ix) < after.size() && size_t(ix) < afterAcc.size()) { if (after[ix] > 0) @@ -1252,8 +1259,8 @@ void RestServer::lookup(oEvent &oe, const string &what, const multimapgetSex(); if (!sex.empty()) xml.write("Sex", sex); - if (r->dbe().birthYear > 0) - xml.write("BirthYear", itow(r->dbe().birthYear)); + if (r->dbe().getBirthYear() > 0) + xml.write("BirthYear", itow(r->dbe().getBirthYear())); xml.endTag(); } @@ -1392,7 +1399,7 @@ void RestServer::newEntry(oEvent &oe, const multimap ¶m, str } if (!permissionDenied && error.empty()) { - pRunner r = oe.addRunner(name, club, classId, cardNo, 0, true); + pRunner r = oe.addRunner(name, club, classId, cardNo, L"", true); if (r && dbr) { r->init(*dbr, true); } diff --git a/code/restserver.h b/code/restserver.h index 7993356..978d031 100644 --- a/code/restserver.h +++ b/code/restserver.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 diff --git a/code/socket.cpp b/code/socket.cpp index 30f8dd8..c26ec7d 100644 --- a/code/socket.cpp +++ b/code/socket.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/socket.h b/code/socket.h index 7724efb..bc5a533 100644 --- a/code/socket.h +++ b/code/socket.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/speakermonitor.cpp b/code/speakermonitor.cpp index 43c75c3..68197d2 100644 --- a/code/speakermonitor.cpp +++ b/code/speakermonitor.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -227,7 +227,7 @@ void SpeakerMonitor::renderResult(gdioutput &gdi, GDICOLOR color = colorLightRed; if (res.control == oPunch::PunchFinish) { - if (r && r->statusOK(true)) + if (r && r->statusOK(true, true)) color = colorLightGreen; else color = colorLightRed; @@ -298,7 +298,7 @@ void SpeakerMonitor::calculateResults() { if (results[k].status == StatusOK) results[k].resultScore = results[k].runTime; else - results[k].resultScore = RunnerStatusOrderMap[results[k].status] + 3600*24*7; + results[k].resultScore = RunnerStatusOrderMap[results[k].status] + timeConstHour*24*7; } totalLeaderTimes.clear(); @@ -440,7 +440,7 @@ void SpeakerMonitor::getMessage(const oEvent::ResultEvent &res, while (leg > 0) { pRunner pr = res.r->getTeam()->getRunner(--leg); // TODO: Pursuit - if (pr && pr->prelStatusOK(false, false) && pr->getFinishTime() == res.r->getStartTime()) { + if (pr && pr->prelStatusOK(false, false, true) && pr->getFinishTime() == res.r->getStartTime()) { vector &rResultsP = runnerToTimeKey[pr->getId()]; if (!rResultsP.empty()) { preRes = &rResultsP.back(); diff --git a/code/speakermonitor.h b/code/speakermonitor.h index 7c6a735..8559875 100644 --- a/code/speakermonitor.h +++ b/code/speakermonitor.h @@ -1,7 +1,7 @@ #pragma once /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/subcommand.h b/code/subcommand.h index 08ff787..c2b318d 100644 --- a/code/subcommand.h +++ b/code/subcommand.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software -Copyright (C) 2009-2022 Melin Software HB +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 diff --git a/code/swedish.lng b/code/swedish.lng index 6dc471e..2ab8f29 100644 --- a/code/swedish.lng +++ b/code/swedish.lng @@ -161,6 +161,7 @@ ClassResultFraction = Andel klar av klass ClassStartName = Startnamn ClassStartTime = Klass, starttid, namn ClassStartTimeClub = Klass, starttid, klubb +ClubClassStartTime = Klubb, klass, starttid ClassTotalResult = Klass, totalresultat Club = Klubb ClubName = Klubb @@ -1036,7 +1037,7 @@ TeamClub = Lags klubb TeamLegTimeAfter = Lags tid efter pÃ¥ sträcka TeamLegTimeStatus = Lags tid/status pÃ¥ sträcka TeamName = Lagnamn -TeamPlace = Lagplacering +TeamPlace = Lags placering efter sträcka TeamRogainingPoint = Lags rogainingpoäng TeamRunner = Lags deltagares namn TeamRunnerCard = Lags deltagares bricknummer @@ -1314,7 +1315,7 @@ help:41072 = Markera en stämpling i stämplingslistan för att ta bort den elle 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 SportIdent 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. info:readoutbase = 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 SportIdent 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 deltagare med hjälp av löpardatabasen. Deltagarens stämplingar används för att välja rätt klass. -info:readoutmore = Ljudval lÃ¥ter dig ställa in om ljudsignaler spelas upp vid avläsning.\n\nEget fönster öppnar ett separat fönster är avsedd att visas pÃ¥ en skärm vänd mot deltagarna.\n\nOm du kopplat in en stämplingsenhet (t.ex. mÃ¥lstämpling) kan du välja om du vill ta emot startstämplingar, radio, eller mÃ¥lstämplingar. Det är en spärr för att förhindra att t.ex. felaktiga startstämplingar kommer in av misstag och skriver över den lottade starttiden. +info:readoutmore = LÃ¥s funktionen för att förhindra oavsiktliga ändringar.\n\nLjudval lÃ¥ter dig ställa in om ljudsignaler spelas upp vid avläsning.\n\nÖppna avläsningsfönster visar ett separat fönster avsett att visas pÃ¥ en skärm vänd mot deltagarna.\n\nFlera lopp per löpare är användbart när en löpare springer valfritt antal varv; en ny anmälan skapas automatiskt för varje brickavläsning. 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. help:52726 = Anslut mot en server nedan. \n\nInstallation\nLadda ner och installera MySQL 5 (Community Edition) frÃ¥n www.mysql.com, förslagsvis med standardinställningar. MySQL behöver bara installeras pÃ¥ den dator som ska vara server. Starta sedan MySQL Command Line Client och skapa ett användarkonto för MeOS. Man skriver sÃ¥ här:\n\n> CREATE USER meos;\n> GRANT ALL ON *.* TO meos;\n\nDu har nu skapat en användare meos (med blankt lösenord). Ange serverns namn nedan (om du inte kan ansluta mÃ¥ste du kontrollera eventuella brandväggar).\n\nEtt alternativ är att använda MySQL:s inbyggda rotkonto, det vill säga, användarnamn 'root' och det lösenord du angav vid installationen av MySQL. help:5422 = Hittade ingen SI-enhet. Är de inkopplade och startade? @@ -2563,7 +2564,7 @@ Filen (X) är en resultatmodul = Filen (X) är en resultatmodul Filen (X) är inte en MeOS-tävling = Filen (X) är inte en MeOS-tävling MeOS-data = MeOS-data List definition = Listdefinition -ask:importcopy = En tävling (X) med samma ursprung finns redan. Du kan importera tävlingen som en version av denna eller som en ny oberoende tävling.\n\nVill du importera den som en ny tävling? +ask:importcopy = En tävling (X) med samma ursprung finns redan. Du kan importera tävlingen som en version av denna eller som en ny oberoende tävling.\n\nVill du importera den som en ny version av tävlingen? RunnerCardVoltage = Brickans batterispänning Applicera för specifik etapp = Tillämpa för specifik etapp Ljud = Ljud @@ -2592,7 +2593,7 @@ Sortera stigande = Sortera stigande Sök symbol = Sök symbol Testa = Testa Fel: Använd X i texten där värdet (Y) ska sättas in = Fel: Använd X i texten där värdet (Y) ska sättas in -info:teamcourseassignment = Den importerade filen innehÃ¥ller gafflingsdata för lag. För att importera gafflingarna mÃ¥ste tävlingen förberedas sÃ¥ den matchar laginformationen i filen: \n\n1. Se till att alla klasser är uppsatta med korrekt antal sträckor.\n2. Ställ in nummerlappsnummer för varje klass. Använd snabbinställningar pÃ¥ sidan klasser för att mata in första nummerlappsnummer i varje klass (automatisk inställning). Alternativt kan du importera lagen och tilldela nummerlappar som vanligt först.\n3. Importera banorna. Du kan importera den här filen flera gÃ¥nger för att uppdatera lottningen. +info:teamcourseassignment = Den importerade filen innehÃ¥ller gafflingsdata för lag. För att importera gafflingarna mÃ¥ste tävlingen förberedas sÃ¥ den matchar laginformationen i filen: \n\n1. Se till att alla klasser är uppsatta med korrekt antal sträckor.\n2. Ställ in nummerlappsnummer för varje klass. Använd snabbinställningar pÃ¥ sidan klasser för att mata in första nummerlappsnummer i varje klass (automatisk inställning). Alternativt kan du importera lagen och tilldela nummerlappar som vanligt först.\n3. Importera banorna. Du kan importera den här filen flera gÃ¥nger för att uppdatera gafflingen. Försvunnen = Försvunnen Automatnamn = Automatnamn Använd = Använd @@ -2604,3 +2605,64 @@ Duplicerad nummerlapp: X, Y = Duplicerad nummerlapp: X, Y Saknat lag mellan X och Y = Saknat lag mellan X och Y Lag utan nummerlapp: X = Lag utan nummerlapp: X Brickavläsning = Brickavläsning +Ett lÃ¥ngt tävlingsnamn kan ge oväntad nerskalning av utskrifter = Ett lÃ¥ngt tävlingsnamn kan ge oväntad nerskalning av utskrifter +Ingen reducerad avgift = Ingen reducerad avgift +Reducerad avgift = Reducerad avgift +Reducerad avgift för = Reducerad avgift för +Unga, till och med X Ã¥r = Unga, till och med X Ã¥r +Ungdomar och äldre kan fÃ¥ reducerad avgift = Ungdomar och äldre kan fÃ¥ reducerad avgift +Äldre, frÃ¥n och med X Ã¥r = Äldre, frÃ¥n och med X Ã¥r +Programmera stationen utan AUTOSEND = Programmera stationen utan AUTOSEND +Aktivera stöd för tiondels sekunder = Aktivera stöd för tiondels sekunder +Automatisk hyrbrickshantering genom registrerade hyrbrickor = Automatisk hyrbrickshantering genom registrerade hyrbrickor +prefsAutoTieRent = Automatisk hyrbrickshantering +prefsExpResFilename = Förvalt filnamn +prefsExpTypeIOF = Förvald exporttyp +prefsExpWithRaceNo = Inkludera lopp vid export +Ignorerade X duplikat = Ignorerade X duplikat +info:customsplitprint = Du kan använda en egen lista för sträcktidstskrifter. Designa listan och använd knappen 'Sträcktidslista' i listredigeraren för att göra inställningar för sträcktidsutskrift.\n\nDu kan ändra styra lista per klass genom att välja lista i tabelläget för klasser. +Sträcktidslista = Sträcktidslista +info:nosplitprint = Kan inte ladda den angivna utskriftslistan.\n\nAnvänder standardutskrift. +Välj deltagare för förhandsgranskning = Välj deltagare för förhandsgranskning +Inkludera bomanalys = Inkludera bomanalys +Inkludera individuellt resultat = Inkludera individuellt resultat +Inkludera sträcktider = Inkludera sträcktider +Inkludera tempo = Inkludera tempo +Skapa en klass för varje bana = Skapa en klass för varje bana +Ingen[competition] = Ingen +Basera pÃ¥ en tidigare tävling = Basera pÃ¥ en tidigare tävling +Baserad pÃ¥ X = Baserad pÃ¥ X +info:multiple_start = En deltagare kan göra valfritt antal starter med samma bricka. Automatisk nyanmälan för varje varv. +Flera starter per deltagare = Flera starter per deltagare +Image = Bild +NumEntries = Antal anmälda +NumStarts = Antal starter +TotalRunLength = Sammanlagd löpsträcka +TotalRunTime = Sammanlagd löptid +X stämplingar = X stämplingar +RunnerBirthDate = Födelsedatum +Avbryt inläsning = Avbryt inläsning +Spara oparad bricka = Spara oparad bricka +warn:changeid = Fältet Externt Id används vanligen till att matcha poster mot andra databaser (t.ex. för anmälnings-, resultat-, eller ekonomisystem). Om du gör manuella ändringar som inte passar ihop med dessa databaser kan olika svÃ¥rbegripliga fel uppstÃ¥. +Cannot represent ID X = Kan inte representera ID 'X' +Batteridatum = Batteridatum +Enhet = Enhet +Öppna avläsningsfönster = Öppna avläsningsfönster +info:readoutwindow = Avläsningsfönstret visar resultat och information om den senaste brickavläsningen, riktat till den tävlande. +info:mapcontrol = MeOS kan inte automatiskt avgöra vilken funktion en enhet har som inte är direkt ansluten till datorn. För dessa använder MeOS den programmerade stämplingskoden för att bestämma vilken typ av enhet det är; du kan själv ställa in hur olika enheter ska tolkas. Kodsiffror högre än 30 tolkas alltid som kontroller.\n\nVar noggrann om du använder startstämplingar för att inte av misstag ändra den lottade starttiden. +Checkenhet = Checkenhet +MÃ¥lenhet = MÃ¥lenhet +Startenhet = Startenhet +Du kan justera tiden för en viss enhet = Du kan justera tiden för en viss enhet +ClubTeam = Klubb (lag) +CourseNumber = Banans nummer +Enhetskod = Enhetskod +Tolkning av radiostämplingar med okänd typ = Tolkning av radiostämplingar med okänd typ +TeamCourseName = Banans namn för lag/sträcka +TeamCourseNumber = Banans nummer för lag/sträcka +TeamLegName = Namn pÃ¥ sträcka +Du mÃ¥ste ange minst tvÃ¥ gafflingsvarienater = Du mÃ¥ste ange minst tvÃ¥ gafflingsvarienater +Max antal gaffllingsvarianter att skapa = Max antal gaffllingsvarianter att skapa +Det uppskattade antalet startade lag i klassen är ett lämpligt värde = Det uppskattade antalet startade lag i klassen är ett lämpligt värde +Lägg till bild = Lägg till bild +Använd listan för sträcktidsutskrift = Använd listan för sträcktidsutskrift diff --git a/code/testmeos.cpp b/code/testmeos.cpp index 4d5c4d3..1241556 100644 --- a/code/testmeos.cpp +++ b/code/testmeos.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -139,9 +139,10 @@ void TestMeOS::runProtected(bool protect) const { oe_main->setProperty("Interactive", 0); oe_main->backupRunnerDatabase(); TabSI *tsi = dynamic_cast(gdi_main->getTabs().get(TabType::TSITab)); - tsi->setMode(TabSI::ModeReadOut); + tsi->setMode(TabSI::SIMode::ModeReadOut); tsi->clearQueue(); - + tsi->getSI(*gdi_main).resetPunchMap(); + OutputDebugString((L"Running test" + gdi_main->widen(test) + L"\n").c_str()); try { status = RUNNING; @@ -421,7 +422,7 @@ int TestMeOS::getResultModuleIndex(const char *tag) const { int TestMeOS::getListIndex(const char *name) const { vector< pair > lst; - oe_main->getListContainer().getLists(lst, false, false, false); + oe_main->getListContainer().getLists(lst, false, false, false, false); for (size_t k = 0; k < lst.size(); k++) { if (lst[k].first == lang.tl(name)) return lst[k].second; diff --git a/code/testmeos.h b/code/testmeos.h index 5f94c91..d58e6f2 100644 --- a/code/testmeos.h +++ b/code/testmeos.h @@ -2,7 +2,7 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/tests.cpp b/code/tests.cpp index 7207b4b..cfc9d74 100644 --- a/code/tests.cpp +++ b/code/tests.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2017 Melin Software HB + Copyright (C) 2009-2023 Melin Software HB Melin Software HB - software@melin.nu - www.melin.nu Eksoppsvägen 16, SE-75646 UPPSALA, Sweden diff --git a/code/timeconstants.hpp b/code/timeconstants.hpp new file mode 100644 index 0000000..9369166 --- /dev/null +++ b/code/timeconstants.hpp @@ -0,0 +1,11 @@ +#pragma once +constexpr int timeUnitsPerSecond = 10; + +constexpr int timeConstHour = 3600 * timeUnitsPerSecond; +constexpr int timeConstMinute = 60 * timeUnitsPerSecond; +constexpr int timeConstSecond = timeUnitsPerSecond; + +// For second based times +constexpr int timeConstSecPerMin = 60; +constexpr int timeConstMinPerHour = 60; +constexpr int timeConstSecPerHour = timeConstSecPerMin * timeConstMinPerHour; \ No newline at end of file diff --git a/code/toolbar.cpp b/code/toolbar.cpp index 74e7da7..1b47fea 100644 --- a/code/toolbar.cpp +++ b/code/toolbar.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/toolbar.h b/code/toolbar.h index 3f103ad..f05041a 100644 --- a/code/toolbar.h +++ b/code/toolbar.h @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 diff --git a/code/xmlparser.cpp b/code/xmlparser.cpp index 6a42d92..5cf29a9 100644 --- a/code/xmlparser.cpp +++ b/code/xmlparser.cpp @@ -1,6 +1,6 @@ /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -199,18 +199,19 @@ void xmlparser::writeBool(const char *tag, const char *prop, bool value) write(tag, prop, value ? L"true" : L"false"); } -/* -void xmlparser::write(const char *tag, const char *Property, const string &PropValue, const string &Value) -{ - if (!cutMode || Value != "" || PropValue != "") { - fOut() << "<" << tag << " " << Property << "=\"" - << encodeXML(PropValue) << "\">" << encodeXML(Value) +void xmlparser::writeAscii(const char *tag, const vector>& propValue, + const string &valueAscii) { + if (!cutMode || valueAscii != "") { + fOut() << "<" << tag; + for (size_t k = 0; k < propValue.size(); k++) { + fOut() << " " << propValue[k].first << "=\"" << encodeXML(propValue[k].second) << "\""; + } + fOut() << ">" << ::encodeXML(valueAscii) << "" << endl; } if (!fOut().good()) throw meosException("Writing to XML file failed."); } -*/ void xmlparser::write(const char *tag, const char *Property, const wstring &PropValue, const wstring &Value) { @@ -313,6 +314,29 @@ void xmlparser::write(const char *tag, int Value) throw meosException("Writing to XML file failed."); } +void xmlparser::writeTime(const char *tag, int relativeTime) { + if (!cutMode || relativeTime != 0) { + char bf[256]; + int subSec = timeConstSecond == 1 ? 0 : relativeTime % timeConstSecond; + + if (timeConstSecond == 1 || relativeTime == -1) + sprintf_s(bf, "<%s>%d\n", tag, relativeTime, tag); + else if (subSec == 0 && relativeTime != -10) + sprintf_s(bf, "<%s>%d\n", tag, relativeTime / timeConstSecond, tag); + else if (relativeTime >= 0) + sprintf_s(bf, "<%s>%d.%d\n", tag, (relativeTime / timeConstSecond), + (relativeTime % timeConstSecond), tag); + else { + int at = std::abs(relativeTime); + sprintf_s(bf, "<%s>-%d.%d\n", tag, (at / timeConstSecond), + (at % timeConstSecond), tag); + } + fOut() << bf; + } + if (!fOut().good()) + throw meosException("Writing to XML file failed."); +} + void xmlparser::writeBool(const char *tag, bool value) { if (!cutMode || value) { @@ -867,14 +891,11 @@ xmlattrib xmlobject::getAttrib(const char *pname) const static int unconverted = 0; - - - -const wchar_t *xmlobject::getw() const +const wchar_t *xmlobject::getWPtr() const { - const char *ptr = getRaw(); - if (ptr == 0) - return 0; + const char *ptr = getRawPtr(); + if (ptr == nullptr) + return nullptr; static wchar_t buff[buff_pre_alloc]; int len = strlen(ptr); len = min(len+1, buff_pre_alloc-10); @@ -901,12 +922,10 @@ const wchar_t *xmlobject::getw() const return buff; } - -const char *xmlobject::get() const -{ - const char *ptr = getRaw(); - if (ptr == 0) - return 0; +const char *xmlobject::getPtr() const { + const char *ptr = getRawPtr(); + if (ptr == nullptr) + return nullptr; static char buff[buff_pre_alloc]; if (parser->isUTF) { int len = strlen(ptr); @@ -923,10 +942,14 @@ const char *xmlobject::get() const return buff; } +int xmlobject::getRelativeTime() const { + const char *d = parser->xmlinfo[index].data; + return parseRelativeTime(d); +} void xmlparser::convertString(const char *in, char *out, int maxlen) const { - if (in==0) + if (in == nullptr) throw std::exception("Null pointer exception"); if (!isUTF) { @@ -995,9 +1018,9 @@ string &xmlobject::getObjectString(const char *pname, string &out) const { xmlobject x=getObject(pname); if (x) { - const char *bf = x.getRaw(); + const char *bf = x.getRawPtr(); if (bf) { - parser->convertString(x.getRaw(), parser->strbuff, buff_pre_alloc); + parser->convertString(bf, parser->strbuff, buff_pre_alloc); out = parser->strbuff; return out; } @@ -1005,7 +1028,7 @@ string &xmlobject::getObjectString(const char *pname, string &out) const xmlattrib xa(getAttrib(pname)); if (xa && xa.data) { - parser->convertString(xa.get(), parser->strbuff, buff_pre_alloc); + parser->convertString(xa.getPtr(), parser->strbuff, buff_pre_alloc); out = parser->strbuff; } else @@ -1018,7 +1041,7 @@ wstring &xmlobject::getObjectString(const char *pname, wstring &out) const { xmlobject x=getObject(pname); if (x) { - const wchar_t *bf = x.getw(); + const wchar_t *bf = x.getWPtr(); if (bf) { out = bf; return out; @@ -1027,7 +1050,7 @@ wstring &xmlobject::getObjectString(const char *pname, wstring &out) const xmlattrib xa(getAttrib(pname)); if (xa && xa.data) { - parser->convertString(xa.get(), parser->strbuffw, buff_pre_alloc); + parser->convertString(xa.getPtr(), parser->strbuffw, buff_pre_alloc); out = parser->strbuffw; } else @@ -1041,7 +1064,7 @@ char *xmlobject::getObjectString(const char *pname, char *out, int maxlen) const { xmlobject x=getObject(pname); if (x) { - const char *bf = x.getRaw(); + const char *bf = x.getRawPtr(); if (bf) { parser->convertString(bf, out, maxlen); return out; @@ -1064,7 +1087,7 @@ wchar_t *xmlobject::getObjectString(const char *pname, wchar_t *out, int maxlen) { xmlobject x=getObject(pname); if (x) { - const char *bf = x.getRaw(); + const char *bf = x.getRawPtr(); if (bf) { parser->convertString(bf, out, maxlen); return out; @@ -1084,15 +1107,15 @@ wchar_t *xmlobject::getObjectString(const char *pname, wchar_t *out, int maxlen) } -const char *xmlattrib::get() const +const char *xmlattrib::getPtr() const { if (data) return decodeXML(data); else - return 0; + return nullptr; } -const wchar_t *xmlattrib::wget() const +const wchar_t *xmlattrib::getWPtr() const { if (data) { const char *dec = decodeXML(data); @@ -1101,5 +1124,5 @@ const wchar_t *xmlattrib::wget() const return xbf; } else - return 0; + return nullptr; } diff --git a/code/xmlparser.h b/code/xmlparser.h index cdd7c34..21d0e51 100644 --- a/code/xmlparser.h +++ b/code/xmlparser.h @@ -10,7 +10,7 @@ #endif // _MSC_VER > 1000 /************************************************************************ MeOS - Orienteering Software - Copyright (C) 2009-2022 Melin Software HB + 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 @@ -55,11 +55,26 @@ struct xmlattrib xmlattrib(const char *t, char *d, const xmlparser *parser); const char *tag; char *data; - operator bool() const {return data!=0;} + operator bool() const { return data != nullptr; } int getInt() const {if (data) return atoi(data); else return 0;} - const char *get() const; - const wchar_t *wget() const; + const char *getPtr() const; + const wchar_t *getWPtr() const; + + string getStr() const { + const char* ptr = getPtr(); + if (ptr == nullptr) + ptr = ""; + return ptr; + } + + wstring getWStr() const { + const wchar_t* ptr = getWPtr(); + if (ptr == nullptr) + ptr = L""; + return ptr; + } + private: const xmlparser *parser; }; @@ -139,8 +154,7 @@ public: void writeBool(const char *tag, const char *prop, const bool value); - //void write(const char *tag, const char *prop, - // const string &propValue, const string &value); + void writeAscii(const char *tag, const vector> &propValue, const string &valueAscii); void write(const char *tag, const char *prop, const wstring &propValue, const wstring &value); @@ -164,6 +178,7 @@ public: void write(const char *tag, const wstring &value); void write(const char *tag, int value); + void writeTime(const char *tag, int relativeTime); void writeBool(const char *tag, bool value); void write64(const char *tag, __int64); @@ -252,14 +267,37 @@ public: return n[0] == pname[0] && strcmp(n, pname)==0; } - const char *getRaw() const {return parser->xmlinfo[index].data;} + const char *getRawPtr() const {return parser->xmlinfo[index].data;} - const char *get() const; - const wchar_t *getw() const; + string getRawStr() const { + const char* ptr = getRawPtr(); + if (ptr == nullptr) + ptr = ""; + return ptr; + } + + const char *getPtr() const; + const wchar_t *getWPtr() const; + + string getStr() const { + const char* ptr = getPtr(); + if (ptr == nullptr) + ptr = ""; + return ptr; + } + + wstring getWStr() const { + const wchar_t* ptr = getWPtr(); + if (ptr == nullptr) + ptr = L""; + return ptr; + } int getInt() const {const char *d = parser->xmlinfo[index].data; return d ? atoi(d) : 0;} + int getRelativeTime() const; + __int64 getInt64() const {const char *d = parser->xmlinfo[index].data; return d ? _atoi64(d) : 0;}