6443 lines
172 KiB
C++
6443 lines
172 KiB
C++
/************************************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2017 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 fro more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Melin Software HB - software@melin.nu - www.melin.nu
|
|
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
|
|
|
|
************************************************************************/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include <vector>
|
|
#include <set>
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#include "oEvent.h"
|
|
#include "gdioutput.h"
|
|
#include "gdifonts.h"
|
|
#include "oDataContainer.h"
|
|
#include "MetaList.h"
|
|
|
|
#include "random.h"
|
|
#include "SportIdent.h"
|
|
|
|
#include "meosException.h"
|
|
#include "oFreeImport.h"
|
|
#include "TabBase.h"
|
|
#include "meos.h"
|
|
#include "meos_util.h"
|
|
#include "RunnerDB.h"
|
|
#include "localizer.h"
|
|
#include "progress.h"
|
|
#include "intkeymapimpl.hpp"
|
|
#include "meosdb/sqltypes.h"
|
|
#include "socket.h"
|
|
#include <hash_set>
|
|
#include "MeOSFeatures.h"
|
|
#include "generalresult.h"
|
|
#include "oEventDraw.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include "Table.h"
|
|
|
|
//Version of database
|
|
int oEvent::dbVersion = 79;
|
|
|
|
class RelativeTimeFormatter : public oDataDefiner {
|
|
string name;
|
|
public:
|
|
RelativeTimeFormatter(const char *n) : name(n) {}
|
|
|
|
const string &formatData(oBase *obj) const {
|
|
int t = obj->getDCI().getInt(name);
|
|
if (t <= 0)
|
|
return MakeDash("-");
|
|
return obj->getEvent()->getAbsTime(t);
|
|
}
|
|
string setData(oBase *obj, const string &input) const {
|
|
int t = obj->getEvent()->getRelativeTime(input);
|
|
obj->getDI().setInt(name.c_str(), t);
|
|
return formatData(obj);
|
|
}
|
|
int addTableColumn(Table *table, const string &description, int minWidth) const {
|
|
return table->addColumn(description, max(minWidth, 90), false, true);
|
|
}
|
|
};
|
|
|
|
class AbsoluteTimeFormatter : public oDataDefiner {
|
|
string name;
|
|
public:
|
|
AbsoluteTimeFormatter(const char *n) : name(n) {}
|
|
|
|
const string &formatData(oBase *obj) const {
|
|
int t = obj->getDCI().getInt(name);
|
|
return formatTime(t);
|
|
}
|
|
string setData(oBase *obj, const string &input) const {
|
|
int t = convertAbsoluteTimeMS(input);
|
|
if (t == NOTIME)
|
|
t = 0;
|
|
obj->getDI().setInt(name.c_str(), t);
|
|
return formatData(obj);
|
|
}
|
|
int addTableColumn(Table *table, const string &description, int minWidth) const {
|
|
return table->addColumn(description, max(minWidth, 90), false, true);
|
|
}
|
|
};
|
|
|
|
oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi)
|
|
{
|
|
readOnly = false;
|
|
tLongTimesCached = -1;
|
|
directSocket = 0;
|
|
hMod=0;
|
|
ZeroTime=0;
|
|
vacantId = 0;
|
|
dataRevision = 0;
|
|
sqlCounterRunners=0;
|
|
sqlCounterClasses=0;
|
|
sqlCounterCourses=0;
|
|
sqlCounterControls=0;
|
|
sqlCounterClubs=0;
|
|
sqlCounterCards=0;
|
|
sqlCounterPunches=0;
|
|
sqlCounterTeams=0;
|
|
|
|
disableRecalculate = false;
|
|
|
|
initProperties();
|
|
|
|
#ifndef MEOSDB
|
|
listContainer = new MetaListContainer(this);
|
|
#else
|
|
throw std::exception();
|
|
#endif
|
|
|
|
|
|
tCurrencyFactor = 1;
|
|
tCurrencySymbol = "kr";
|
|
tCurrencySeparator = ",";
|
|
tCurrencyPreSymbol = false;
|
|
|
|
tClubDataRevision = -1;
|
|
|
|
nextFreeStartNo = 0;
|
|
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st);
|
|
|
|
char bf[64];
|
|
sprintf_s(bf, 64, "%d-%02d-%02d", st.wYear, st.wMonth, st.wDay);
|
|
|
|
Date=bf;
|
|
ZeroTime=st.wHour*3600;
|
|
oe=this;
|
|
|
|
runnerDB = new RunnerDB(this);
|
|
meosFeatures = new MeOSFeatures();
|
|
openFileLock = new MeOSFileLock();
|
|
|
|
char cp[64];
|
|
DWORD size=64;
|
|
GetComputerName(cp, &size);
|
|
clientName=cp;
|
|
|
|
HasDBConnection = false;
|
|
HasPendingDBConnection = false;
|
|
|
|
#ifdef BUILD_DB_DLL
|
|
msSynchronizeList=0;
|
|
msSynchronizeRead=0;
|
|
msSynchronizeUpdate=0;
|
|
msOpenDatabase=0;
|
|
msRemove=0;
|
|
msMonitor=0;
|
|
msUploadRunnerDB = 0;
|
|
|
|
msGetErrorState=0;
|
|
msResetConnection=0;
|
|
msReConnect=0;
|
|
#endif
|
|
|
|
currentNameMode = FirstLast;
|
|
|
|
nextTimeLineEvent = 0;
|
|
//These object must be initialized on creation of any oObject,
|
|
//but we need to create (dummy) objects to get the sizeof their
|
|
//oData[]-sets...
|
|
|
|
// --- REMEMBER TO UPDATE dvVersion when these are changed.
|
|
|
|
oEventData=new oDataContainer(dataSize);
|
|
oEventData->addVariableCurrency("CardFee", "Brickhyra");
|
|
oEventData->addVariableCurrency("EliteFee", "Elitavgift");
|
|
oEventData->addVariableCurrency("EntryFee", "Normalavgift");
|
|
oEventData->addVariableCurrency("YouthFee", "Ungdomsavgift");
|
|
oEventData->addVariableInt("YouthAge", oDataContainer::oIS8U, "Åldersgräns ungdom");
|
|
oEventData->addVariableInt("SeniorAge", oDataContainer::oIS8U, "Åldersgräns äldre");
|
|
|
|
oEventData->addVariableString("Account", 30, "Konto");
|
|
oEventData->addVariableDate("PaymentDue", "Sista betalningsdatum");
|
|
oEventData->addVariableDate("OrdinaryEntry", "Ordinarie anmälningsdatum");
|
|
oEventData->addVariableString("LateEntryFactor", 6, "Avgiftshöjning (procent)");
|
|
|
|
oEventData->addVariableString("Organizer", "Arrangör");
|
|
oEventData->addVariableString("CareOf", 31, "c/o");
|
|
|
|
oEventData->addVariableString("Street", 32, "Adress");
|
|
oEventData->addVariableString("Address", 32, "Postadress");
|
|
oEventData->addVariableString("EMail", "E-post");
|
|
oEventData->addVariableString("Homepage", "Hemsida");
|
|
oEventData->addVariableString("Phone", 32, "Telefon");
|
|
|
|
oEventData->addVariableInt("UseEconomy", oDataContainer::oIS8U, "Ekonomi");
|
|
oEventData->addVariableInt("UseSpeaker", oDataContainer::oIS8U, "Speaker");
|
|
oEventData->addVariableInt("SkipRunnerDb", oDataContainer::oIS8U, "Databas");
|
|
oEventData->addVariableInt("ExtId", oDataContainer::oIS64, "Externt Id");
|
|
|
|
oEventData->addVariableInt("MaxTime", oDataContainer::oISTime, "Gräns för maxtid");
|
|
|
|
oEventData->addVariableString("PreEvent", sizeof(CurrentNameId), "");
|
|
oEventData->addVariableString("PostEvent", sizeof(CurrentNameId), "");
|
|
|
|
// Positive number -> stage number, negative number -> no stage number. Zero = unknown
|
|
oEventData->addVariableInt("EventNumber", oDataContainer::oIS8, "");
|
|
|
|
oEventData->addVariableInt("CurrencyFactor", oDataContainer::oIS16, "Valutafaktor");
|
|
oEventData->addVariableString("CurrencySymbol", 5, "Valutasymbol");
|
|
oEventData->addVariableString("CurrencySeparator", 2, "Decimalseparator");
|
|
oEventData->addVariableInt("CurrencyPreSymbol", oDataContainer::oIS8, "Symbolläge");
|
|
oEventData->addVariableString("CurrencyCode", 5, "Valutakod");
|
|
oEventData->addVariableInt("UTC", oDataContainer::oIS8, "UTC");
|
|
|
|
oEventData->addVariableInt("Analysis", oDataContainer::oIS8, "Utan analys");
|
|
// With split time analysis (0 = default, with analysis, with min/km)
|
|
// bit 1: without analysis
|
|
// bit 2: without min/km
|
|
// bit 4: without result
|
|
|
|
oEventData->addVariableString("SPExtra", "Extra rader");
|
|
oEventData->addVariableString("IVExtra", "Fakturainfo");
|
|
oEventData->addVariableString("Features", "Funktioner");
|
|
oEventData->addVariableString("EntryExtra", "Extra rader");
|
|
oEventData->addVariableInt("NumStages", oDataContainer::oIS8, "Antal etapper");
|
|
oEventData->addVariableInt("BibGap", oDataContainer::oIS8U, "Nummerlappshopp");
|
|
oEventData->addVariableInt("LongTimes", oDataContainer::oIS8U, "Långa tider");
|
|
oEventData->addVariableString("PayModes", "Betalsätt");
|
|
|
|
oEventData->initData(this, dataSize);
|
|
|
|
oClubData=new oDataContainer(oClub::dataSize);
|
|
oClubData->addVariableInt("District", oDataContainer::oIS32, "Organisation");
|
|
|
|
oClubData->addVariableString("ShortName", 8, "Kortnamn");
|
|
oClubData->addVariableString("CareOf", 31, "c/o");
|
|
oClubData->addVariableString("Street", 41, "Gata");
|
|
oClubData->addVariableString("City", 23, "Stad");
|
|
oClubData->addVariableString("State", 23, "Region");
|
|
oClubData->addVariableString("ZIP", 11, "Postkod");
|
|
oClubData->addVariableString("EMail", 64, "E-post");
|
|
oClubData->addVariableString("Phone", 32, "Telefon");
|
|
oClubData->addVariableString("Nationality", 3, "Nationalitet");
|
|
oClubData->addVariableString("Country", 23, "Land");
|
|
oClubData->addVariableString("Type", 20, "Typ");
|
|
oClubData->addVariableInt("ExtId", oDataContainer::oIS64, "Externt Id");
|
|
|
|
vector< pair<string,string> > eInvoice;
|
|
eInvoice.push_back(make_pair("E", "Elektronisk"));
|
|
eInvoice.push_back(make_pair("A", "Elektronisk godkänd"));
|
|
eInvoice.push_back(make_pair("P", "Ej elektronisk"));
|
|
eInvoice.push_back(make_pair("", MakeDash("-")));
|
|
oClubData->addVariableEnum("Invoice", 1, "Faktura", eInvoice);
|
|
oClubData->addVariableInt("InvoiceNo", oDataContainer::oIS16U, "Fakturanummer");
|
|
|
|
oRunnerData=new oDataContainer(oRunner::dataSize);
|
|
oRunnerData->addVariableCurrency("Fee", "Anm. avgift");
|
|
oRunnerData->addVariableCurrency("CardFee", "Brickhyra");
|
|
oRunnerData->addVariableCurrency("Paid", "Betalat");
|
|
oRunnerData->addVariableInt("PayMode", oDataContainer::oIS8U, "Betalsätt");
|
|
oRunnerData->addVariableCurrency("Taxable", "Skattad avgift");
|
|
oRunnerData->addVariableInt("BirthYear", oDataContainer::oIS32, "Födelseår");
|
|
oRunnerData->addVariableString("Bib", 8, "Nummerlapp").zeroSortPadding = 5;
|
|
oRunnerData->addVariableInt("Rank", oDataContainer::oIS16U, "Ranking");
|
|
//oRunnerData->addVariableInt("VacRank", oDataContainer::oIS16U, "Vak. ranking");
|
|
|
|
oRunnerData->addVariableDate("EntryDate", "Anm. datum");
|
|
|
|
vector< pair<string,string> > sex;
|
|
sex.push_back(make_pair("M", "Man"));
|
|
sex.push_back(make_pair("F", "Kvinna"));
|
|
sex.push_back(make_pair("", MakeDash("-")));
|
|
|
|
oRunnerData->addVariableEnum("Sex", 1, "Kön", sex);
|
|
oRunnerData->addVariableString("Nationality", 3, "Nationalitet");
|
|
oRunnerData->addVariableString("Country", 23, "Land");
|
|
oRunnerData->addVariableInt("ExtId", oDataContainer::oIS64, "Externt Id");
|
|
oRunnerData->addVariableInt("Priority", oDataContainer::oIS8U, "Prioritering");
|
|
oRunnerData->addVariableString("Phone", 20, "Telefon");
|
|
|
|
oRunnerData->addVariableInt("RaceId", oDataContainer::oIS32, "Lopp-id", &oRunner::raceIdFormatter);
|
|
|
|
oRunnerData->addVariableInt("TimeAdjust", oDataContainer::oIS16, "Tidsjustering");
|
|
oRunnerData->addVariableInt("PointAdjust", oDataContainer::oIS32, "Poängjustering");
|
|
oRunnerData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring");
|
|
oRunnerData->addVariableInt("Shorten", oDataContainer::oIS8U, "Avkortning");
|
|
oRunnerData->addVariableInt("EntrySource", oDataContainer::oIS32, "Källa");
|
|
oRunnerData->addVariableInt("Heat", oDataContainer::oIS8U, "Heat");
|
|
|
|
oControlData=new oDataContainer(oControl::dataSize);
|
|
oControlData->addVariableInt("TimeAdjust", oDataContainer::oIS32, "Tidsjustering");
|
|
oControlData->addVariableInt("MinTime", oDataContainer::oIS32, "Minitid");
|
|
oControlData->addVariableDecimal("xpos", "x", 1);
|
|
oControlData->addVariableDecimal("ypos", "y", 1);
|
|
oControlData->addVariableDecimal("latcrd", "Latitud", 6);
|
|
oControlData->addVariableDecimal("longcrd", "Longitud", 6);
|
|
|
|
oControlData->addVariableInt("Rogaining", oDataContainer::oIS32, "Poäng");
|
|
oControlData->addVariableInt("Radio", oDataContainer::oIS8U, "Radio");
|
|
|
|
oCourseData=new oDataContainer(oCourse::dataSize);
|
|
oCourseData->addVariableInt("NumberMaps", oDataContainer::oIS16, "Kartor");
|
|
oCourseData->addVariableString("StartName", 16, "Start");
|
|
oCourseData->addVariableInt("Climb", oDataContainer::oIS16, "Stigning");
|
|
oCourseData->addVariableInt("StartIndex", oDataContainer::oIS32, "Startindex");
|
|
oCourseData->addVariableInt("FinishIndex", oDataContainer::oIS32, "Målindex");
|
|
oCourseData->addVariableInt("RPointLimit", oDataContainer::oIS32, "Poänggräns");
|
|
oCourseData->addVariableInt("RTimeLimit", oDataContainer::oIS32, "Tidsgräns");
|
|
oCourseData->addVariableInt("RReduction", oDataContainer::oIS32, "Poängreduktion");
|
|
oCourseData->addVariableInt("RReductionMethod", oDataContainer::oIS8U, "Reduktionsmetod");
|
|
|
|
oCourseData->addVariableInt("FirstAsStart", oDataContainer::oIS8U, "Från första");
|
|
oCourseData->addVariableInt("LastAsFinish", oDataContainer::oIS8U, "Till sista");
|
|
|
|
oCourseData->addVariableInt("CControl", oDataContainer::oIS16U, "Varvningskontroll"); //Common control index
|
|
oCourseData->addVariableInt("Shorten", oDataContainer::oIS32, "Avkortning");
|
|
|
|
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");
|
|
oClassData->addVariableInt("HighAge", oDataContainer::oIS8U, "Övre ålder");
|
|
oClassData->addVariableInt("HasPool", oDataContainer::oIS8U, "Banpool");
|
|
oClassData->addVariableInt("AllowQuickEntry", oDataContainer::oIS8U, "Direktanmälan");
|
|
|
|
oClassData->addVariableString("ClassType", 40, "Klasstyp");
|
|
|
|
vector< pair<string,string> > sexClass;
|
|
sexClass.push_back(make_pair("M", "Män"));
|
|
sexClass.push_back(make_pair("F", "Kvinnor"));
|
|
sexClass.push_back(make_pair("B", "Alla"));
|
|
sexClass.push_back(make_pair("", MakeDash("-")));
|
|
|
|
oClassData->addVariableEnum("Sex", 1, "Kön", sexClass);
|
|
oClassData->addVariableString("StartName", 16, "Start");
|
|
oClassData->addVariableInt("StartBlock", oDataContainer::oIS8U, "Block");
|
|
oClassData->addVariableInt("NoTiming", oDataContainer::oIS8U, "Ej tidtagning");
|
|
oClassData->addVariableInt("FreeStart", oDataContainer::oIS8U, "Fri starttid");
|
|
oClassData->addVariableInt("IgnoreStart", oDataContainer::oIS8U, "Ej startstämpling");
|
|
|
|
firstStartDefiner = new RelativeTimeFormatter("FirstStart");
|
|
oClassData->addVariableInt("FirstStart", oDataContainer::oIS32, "Första start", firstStartDefiner);
|
|
intervalDefiner = new AbsoluteTimeFormatter("StartInterval");
|
|
oClassData->addVariableInt("StartInterval", oDataContainer::oIS16, "Intervall", intervalDefiner);
|
|
oClassData->addVariableInt("Vacant", oDataContainer::oIS8U, "Vakanser");
|
|
oClassData->addVariableInt("Reserved", oDataContainer::oIS16U, "Extraplatser");
|
|
|
|
oClassData->addVariableCurrency("ClassFee", "Anm. avgift");
|
|
oClassData->addVariableCurrency("HighClassFee", "Efteranm. avg.");
|
|
oClassData->addVariableCurrency("ClassFeeRed", "Reducerad avg.");
|
|
oClassData->addVariableCurrency("HighClassFeeRed", "Red. avg. efteranm.");
|
|
|
|
oClassData->addVariableInt("SortIndex", oDataContainer::oIS32, "Sortering");
|
|
oClassData->addVariableInt("MaxTime", oDataContainer::oISTime, "Maxtid");
|
|
|
|
vector< pair<string,string> > statusClass;
|
|
statusClass.push_back(make_pair("", "OK"));
|
|
statusClass.push_back(make_pair("IR", "Struken med återbetalning"));
|
|
statusClass.push_back(make_pair("I", "Struken utan återbetalning"));
|
|
|
|
oClassData->addVariableEnum("Status", 2, "Status", statusClass);
|
|
oClassData->addVariableInt("DirectResult", oDataContainer::oIS8, "Resultat vid målstämpling");
|
|
oClassData->addVariableString("Bib", 8, "Nummerlapp");
|
|
|
|
vector< pair<string,string> > bibMode;
|
|
bibMode.push_back(make_pair("", "Från lag"));
|
|
bibMode.push_back(make_pair("A", "Lag + sträcka"));
|
|
bibMode.push_back(make_pair("F", "Fritt"));
|
|
|
|
oClassData->addVariableEnum("BibMode", 1, "Nummerlappshantering", bibMode);
|
|
oClassData->addVariableInt("Unordered", oDataContainer::oIS8U, "Oordnade parallella");
|
|
oClassData->addVariableInt("Heat", oDataContainer::oIS8U, "Heat");
|
|
|
|
oTeamData = new oDataContainer(oTeam::dataSize);
|
|
oTeamData->addVariableCurrency("Fee", "Anm. avgift");
|
|
oTeamData->addVariableCurrency("Paid", "Betalat");
|
|
oTeamData->addVariableInt("PayMode", oDataContainer::oIS8U, "Betalsätt");
|
|
oTeamData->addVariableCurrency("Taxable", "Skattad avgift");
|
|
oTeamData->addVariableDate("EntryDate", "Anm. datum");
|
|
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("PointAdjust", oDataContainer::oIS32, "Poängjustering");
|
|
oTeamData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring");
|
|
oTeamData->addVariableInt("EntrySource", oDataContainer::oIS32, "Källa");
|
|
oTeamData->addVariableInt("Heat", oDataContainer::oIS8U, "Heat");
|
|
|
|
generalResults.push_back(GeneralResultCtr("atcontrol", "Result at a control", new ResultAtControl()));
|
|
generalResults.push_back(GeneralResultCtr("totatcontrol", "Total/team result at a control", new TotalResultAtControl()));
|
|
|
|
currentClientCS = 0;
|
|
memset(CurrentFile, 0, sizeof(CurrentFile));
|
|
}
|
|
|
|
oEvent::~oEvent()
|
|
{
|
|
//Clean up things in the right order.
|
|
clear();
|
|
delete runnerDB;
|
|
delete meosFeatures;
|
|
runnerDB = 0;
|
|
meosFeatures = 0;
|
|
|
|
if (hMod) {
|
|
HasDBConnection=false;
|
|
FreeLibrary(hMod);
|
|
hMod=0;
|
|
}
|
|
|
|
delete oEventData;
|
|
delete oRunnerData;
|
|
delete oClubData;
|
|
delete oControlData;
|
|
delete oCourseData;
|
|
delete oClassData;
|
|
delete oTeamData;
|
|
|
|
delete openFileLock;
|
|
delete listContainer;
|
|
|
|
return;
|
|
}
|
|
|
|
void oEvent::initProperties() {
|
|
setProperty("TextSize", getPropertyString("TextSize", "0"));
|
|
setProperty("Language", getPropertyString("Language", "103"));
|
|
|
|
setProperty("Interactive", getPropertyString("Interactive", "1"));
|
|
setProperty("Database", getPropertyString("Database", "1"));
|
|
|
|
// Setup some defaults
|
|
getPropertyInt("SplitLateFees", false);
|
|
getPropertyInt("DirectPort", 21338);
|
|
getPropertyInt("UseHourFormat", 1);
|
|
getPropertyInt("UseDirectSocket", true);
|
|
getPropertyInt("UseEventorUTC", 0);
|
|
}
|
|
|
|
void oEvent::listProperties(bool userProps, vector< pair<string, PropertyType> > &propNames) const {
|
|
|
|
|
|
set<string> filter;
|
|
if (userProps) {
|
|
filter.insert("Language");
|
|
filter.insert("apikey");
|
|
filter.insert("Colors");
|
|
filter.insert("xpos");
|
|
filter.insert("ypos");
|
|
filter.insert("xsize");
|
|
filter.insert("ysize");
|
|
filter.insert("ListType");
|
|
filter.insert("LastCompetition");
|
|
filter.insert("DrawTypeDefault");
|
|
filter.insert("Email");
|
|
filter.insert("TextSize");
|
|
filter.insert("PayModes");
|
|
}
|
|
|
|
// Boolean and integer properties
|
|
set<string> b, i;
|
|
|
|
// Booleans
|
|
b.insert("AdvancedClassSettings");
|
|
b.insert("AutoTie");
|
|
b.insert("CurrencyPreSymbol");
|
|
b.insert("Database");
|
|
b.insert("Interactive");
|
|
b.insert("intertime");
|
|
b.insert("ManualInput");
|
|
b.insert("PageBreak");
|
|
b.insert("RentCard");
|
|
b.insert("SpeakerShortNames");
|
|
b.insert("splitanalysis");
|
|
b.insert("UseDirectSocket");
|
|
b.insert("UseEventor");
|
|
b.insert("UseEventorUTC");
|
|
b.insert("UseHourFormat");
|
|
b.insert("SplitLateFees");
|
|
b.insert("WideSplitFormat");
|
|
b.insert("pagebreak");
|
|
b.insert("FirstTime");
|
|
b.insert("ExportCSVSplits");
|
|
|
|
// Integers
|
|
i.insert("YouthFee");
|
|
i.insert("YouthAge");
|
|
i.insert("TextSize");
|
|
i.insert("SynchronizationTimeOut");
|
|
i.insert("SeniorAge");
|
|
i.insert("Port");
|
|
i.insert("MaximumSpeakerDelay");
|
|
i.insert("FirstInvoice");
|
|
i.insert("EntryFee");
|
|
i.insert("EliteFee");
|
|
i.insert("DirectPort");
|
|
i.insert("DatabaseUpdate");
|
|
i.insert("ControlTo");
|
|
i.insert("ControlFrom");
|
|
i.insert("CardFee");
|
|
i.insert("addressypos");
|
|
i.insert("addressxpos");
|
|
i.insert("AutoSaveTimeOut");
|
|
|
|
propNames.clear();
|
|
for(map<string, string>::const_iterator it = eventProperties.begin();
|
|
it != eventProperties.end(); ++it) {
|
|
if (!filter.count(it->first)) {
|
|
if (b.count(it->first)) {
|
|
assert(!i.count(it->first));
|
|
propNames.push_back(make_pair(it->first, Boolean));
|
|
}
|
|
else if (i.count(it->first)) {
|
|
propNames.push_back(make_pair(it->first, Integer));
|
|
}
|
|
else
|
|
propNames.push_back(make_pair(it->first, String));
|
|
}
|
|
}
|
|
}
|
|
|
|
pControl oEvent::addControl(int Id, int Number, const string &Name)
|
|
{
|
|
if (Id<=0)
|
|
Id=getFreeControlId();
|
|
else
|
|
qFreeControlId = max (qFreeControlId, Id);
|
|
|
|
oControl c(this);
|
|
c.set(Id, Number, Name);
|
|
Controls.push_back(c);
|
|
|
|
oe->updateTabs();
|
|
return &Controls.back();
|
|
}
|
|
|
|
int oEvent::getNextControlNumber() const
|
|
{
|
|
int c = 31;
|
|
for (oControlList::const_iterator it = Controls.begin(); it!=Controls.end(); ++it)
|
|
c = max(c, it->maxNumber()+1);
|
|
|
|
return c;
|
|
}
|
|
|
|
pControl oEvent::addControl(const oControl &oc)
|
|
{
|
|
if (oc.Id<=0)
|
|
return 0;
|
|
|
|
if (getControl(oc.Id, false))
|
|
return 0;
|
|
|
|
qFreeControlId = max (qFreeControlId, Id);
|
|
|
|
Controls.push_back(oc);
|
|
return &Controls.back();
|
|
}
|
|
|
|
DirectSocket &oEvent::getDirectSocket() {
|
|
if (directSocket == 0)
|
|
directSocket = new DirectSocket(getId(), getPropertyInt("DirectPort", 21338));
|
|
|
|
return *directSocket;
|
|
}
|
|
|
|
pControl oEvent::getControl(int Id) const {
|
|
return const_cast<oEvent *>(this)->getControl(Id, false);
|
|
}
|
|
|
|
pControl oEvent::getControl(int Id, bool create) {
|
|
oControlList::const_iterator it;
|
|
|
|
for (it=Controls.begin(); it != Controls.end(); ++it) {
|
|
if (it->Id==Id)
|
|
return pControl(&*it);
|
|
}
|
|
|
|
if (!create || Id<=0)
|
|
return 0;
|
|
|
|
//Not found. Auto add...
|
|
return addControl(Id, Id, "");
|
|
}
|
|
|
|
bool oEvent::writeControls(xmlparser &xml)
|
|
{
|
|
oControlList::iterator it;
|
|
|
|
xml.startTag("ControlList");
|
|
|
|
for (it=Controls.begin(); it != Controls.end(); ++it)
|
|
it->write(xml);
|
|
|
|
xml.endTag();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::writeCourses(xmlparser &xml)
|
|
{
|
|
oCourseList::iterator it;
|
|
|
|
xml.startTag("CourseList");
|
|
|
|
for (it=Courses.begin(); it != Courses.end(); ++it)
|
|
it->Write(xml);
|
|
|
|
xml.endTag();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::writeClasses(xmlparser &xml)
|
|
{
|
|
oClassList::iterator it;
|
|
|
|
xml.startTag("ClassList");
|
|
|
|
for (it=Classes.begin(); it != Classes.end(); ++it)
|
|
it->Write(xml);
|
|
|
|
xml.endTag();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::writeClubs(xmlparser &xml)
|
|
{
|
|
oClubList::iterator it;
|
|
|
|
xml.startTag("ClubList");
|
|
|
|
for (it=Clubs.begin(); it != Clubs.end(); ++it)
|
|
it->write(xml);
|
|
|
|
xml.endTag();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::writeRunners(xmlparser &xml, ProgressWindow &pw)
|
|
{
|
|
oRunnerList::iterator it;
|
|
|
|
xml.startTag("RunnerList");
|
|
int k=0;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->tDuplicateLeg) //Duplicates is written by the ruling runner.
|
|
it->Write(xml);
|
|
if (++k%300 == 200)
|
|
pw.setSubProgress( (1000*k)/ Runners.size());
|
|
}
|
|
xml.endTag();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool oEvent::writePunches(xmlparser &xml, ProgressWindow &pw)
|
|
{
|
|
oFreePunchList::iterator it;
|
|
|
|
xml.startTag("PunchList");
|
|
int k = 0;
|
|
for (it=punches.begin(); it != punches.end(); ++it) {
|
|
it->Write(xml);
|
|
if (++k%300 == 200)
|
|
pw.setSubProgress( (1000*k)/ Runners.size());
|
|
}
|
|
xml.endTag();
|
|
|
|
return true;
|
|
}
|
|
|
|
//Write free cards not owned by a runner.
|
|
bool oEvent::writeCards(xmlparser &xml)
|
|
{
|
|
oCardList::iterator it;
|
|
|
|
xml.startTag("CardList");
|
|
|
|
for (it=Cards.begin(); it != Cards.end(); ++it) {
|
|
if (it->getOwner() == 0)
|
|
it->Write(xml);
|
|
}
|
|
|
|
xml.endTag();
|
|
return true;
|
|
}
|
|
|
|
void oEvent::duplicate() {
|
|
char file[260];
|
|
char filename[64];
|
|
char nameid[64];
|
|
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st);
|
|
|
|
sprintf_s(filename, 64, "meos_%d%02d%02d_%02d%02d%02d_%X.meos",
|
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
|
|
|
|
getUserFile(file, filename);
|
|
|
|
_splitpath_s(filename, NULL, 0, NULL,0, nameid, 64, NULL, 0);
|
|
int i=0;
|
|
while (nameid[i]) {
|
|
if (nameid[i]=='.') {
|
|
nameid[i]=0;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
char oldFile[260];
|
|
char oldId[64];
|
|
strcpy_s(oldFile, CurrentFile);
|
|
strcpy_s(oldId, CurrentNameId);
|
|
string oldAnno = getAnnotation();
|
|
|
|
strcpy_s(CurrentFile, file);
|
|
strcpy_s(CurrentNameId, nameid);
|
|
|
|
sprintf_s(filename, "%d/%d %d:%02d",
|
|
st.wDay, st.wMonth, st.wHour, st.wMinute);
|
|
|
|
string anno = lang.tl("Kopia (X)#" + string(filename));
|
|
anno = oldAnno.empty() ? anno : oldAnno + " " + anno;
|
|
setAnnotation(anno);
|
|
|
|
try {
|
|
save();
|
|
}
|
|
catch(...) {
|
|
// Restore in case of error
|
|
strcpy_s(CurrentFile, oldFile);
|
|
strcpy_s(CurrentNameId, oldId);
|
|
setAnnotation(oldAnno);
|
|
throw;
|
|
}
|
|
|
|
// Restore
|
|
strcpy_s(CurrentFile, oldFile);
|
|
strcpy_s(CurrentNameId, oldId);
|
|
setAnnotation(oldAnno);
|
|
}
|
|
|
|
bool oEvent::save()
|
|
{
|
|
if (empty() || gdibase.isTest())
|
|
return true;
|
|
|
|
autoSynchronizeLists(true);
|
|
|
|
if (!CurrentFile[0])
|
|
throw std::exception("Felaktigt filnamn");
|
|
|
|
int f=0;
|
|
_sopen_s(&f, CurrentFile, _O_RDONLY, _SH_DENYNO, _S_IWRITE);
|
|
|
|
char fn1[260];
|
|
char fn2[260];
|
|
string finalRenameTarget;
|
|
|
|
if (f!=-1) {
|
|
_close(f);
|
|
time_t currentTime = time(0);
|
|
const int baseAge = 3; // Three minutes
|
|
time_t allowedAge = baseAge*60;
|
|
time_t oldAge = allowedAge + 60;
|
|
const int maxBackup = 8;
|
|
int toDelete = maxBackup;
|
|
|
|
for(int k = 0; k <= maxBackup; k++) {
|
|
sprintf_s(fn1, MAX_PATH, "%s.bu%d", CurrentFile, k);
|
|
struct stat st;
|
|
int ret = stat(fn1, &st);
|
|
if (ret==0) {
|
|
time_t age = currentTime - st.st_mtime;
|
|
// If file is too young or to old at its
|
|
// position, it is possible to delete.
|
|
// The oldest old file (or youngest young file if none is old)
|
|
// possible to delete is deleted.
|
|
// If no file is possible to delete, the oldest
|
|
// file is deleted.
|
|
if ( (age<allowedAge && toDelete==maxBackup) || age>oldAge)
|
|
toDelete = k;
|
|
allowedAge *= 2;
|
|
oldAge*=2;
|
|
|
|
if (k==maxBackup-3)
|
|
oldAge = 24*3600; // Allow a few old copies
|
|
}
|
|
else {
|
|
toDelete = k; // File does not exist. No file need be deleted
|
|
break;
|
|
}
|
|
}
|
|
|
|
sprintf_s(fn1, MAX_PATH, "%s.bu%d", CurrentFile, toDelete);
|
|
::remove(fn1);
|
|
|
|
for(int k=toDelete;k>0;k--) {
|
|
sprintf_s(fn1, MAX_PATH, "%s.bu%d", CurrentFile, k-1);
|
|
sprintf_s(fn2, MAX_PATH, "%s.bu%d", CurrentFile, k);
|
|
rename(fn1, fn2);
|
|
}
|
|
|
|
finalRenameTarget = fn1;
|
|
//rename(CurrentFile, fn1);
|
|
}
|
|
bool res;
|
|
if (finalRenameTarget.empty()) {
|
|
res = save(CurrentFile);
|
|
if (!(HasDBConnection || HasPendingDBConnection))
|
|
openFileLock->lockFile(CurrentFile);
|
|
}
|
|
else {
|
|
string tmpName = string(CurrentFile) + ".~tmp";
|
|
res = save(tmpName);
|
|
if (res) {
|
|
openFileLock->unlockFile();
|
|
rename(CurrentFile, finalRenameTarget.c_str());
|
|
rename(tmpName.c_str(), CurrentFile);
|
|
|
|
if (!(HasDBConnection || HasPendingDBConnection))
|
|
openFileLock->lockFile(CurrentFile);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool oEvent::save(const string &fileIn) {
|
|
if (gdibase.isTest())
|
|
return true;
|
|
|
|
const char *file = fileIn.c_str();
|
|
xmlparser xml(0);
|
|
ProgressWindow pw(gdibase.getHWND());
|
|
|
|
if (Runners.size()>200)
|
|
pw.init();
|
|
|
|
xml.openOutput(file, true);
|
|
xml.startTag("meosdata", "version", getMajorVersion());
|
|
xml.write("Name", Name);
|
|
xml.write("Date", Date);
|
|
xml.write("ZeroTime", ZeroTime);
|
|
xml.write("NameId", CurrentNameId);
|
|
xml.write("Annotation", Annotation);
|
|
xml.write("Id", Id);
|
|
xml.write("Updated", Modified.getStamp());
|
|
|
|
oEventData->write(this, xml);
|
|
|
|
int i = 0;
|
|
vector<int> p;
|
|
p.resize(10);
|
|
p[0] = 2; //= {2, 20, 50, 80, 180, 400,500,700,800,1000};
|
|
p[1] = Controls.size();
|
|
p[2] = Courses.size();
|
|
p[3] = Classes.size();
|
|
p[4] = Clubs.size();
|
|
p[5] = Runners.size() + Cards.size();
|
|
p[6] = Teams.size();
|
|
p[7] = punches.size();
|
|
p[8] = Cards.size();
|
|
p[9] = Runners.size()/2;
|
|
|
|
int sum = 0;
|
|
for (int k = 0; k<10; k++)
|
|
sum += p[k];
|
|
|
|
for (int k = 1; k<10; k++)
|
|
p[k] = p[k-1] + (1000 * p[k]) / sum;
|
|
|
|
p[9] = 1000;
|
|
|
|
pw.setProgress(p[i++]);
|
|
writeControls(xml);
|
|
pw.setProgress(p[i++]);
|
|
writeCourses(xml);
|
|
pw.setProgress(p[i++]);
|
|
writeClasses(xml);
|
|
pw.setProgress(p[i++]);
|
|
writeClubs(xml);
|
|
pw.initSubProgress(p[i], p[i+1]);
|
|
pw.setProgress(p[i++]);
|
|
writeRunners(xml, pw);
|
|
pw.setProgress(p[i++]);
|
|
writeTeams(xml);
|
|
pw.initSubProgress(p[i], p[i+1]);
|
|
pw.setProgress(p[i++]);
|
|
writePunches(xml, pw);
|
|
pw.setProgress(p[i++]);
|
|
writeCards(xml);
|
|
|
|
xml.startTag("Lists");
|
|
listContainer->save(MetaListContainer::ExternalList, xml, this);
|
|
xml.endTag();
|
|
|
|
xml.closeOut();
|
|
pw.setProgress(p[i++]);
|
|
updateRunnerDatabase();
|
|
pw.setProgress(p[i++]);
|
|
|
|
return true;
|
|
}
|
|
|
|
string oEvent::getNameId(int id) const {
|
|
if (id == 0)
|
|
return CurrentNameId;
|
|
|
|
list<CompetitionInfo>::const_iterator it;
|
|
for (it=cinfo.begin(); it!=cinfo.end(); ++it) {
|
|
if (it->Server.empty()) {
|
|
if (id == it->Id)
|
|
return it->NameId;
|
|
}
|
|
else if (!it->Server.empty()) {
|
|
if (id == (10000000+it->Id)) {
|
|
return it->NameId;
|
|
}
|
|
}
|
|
}
|
|
return _EmptyString;
|
|
}
|
|
|
|
const string &oEvent::getFileNameFromId(int id) const {
|
|
|
|
list<CompetitionInfo>::const_iterator it;
|
|
for (it=cinfo.begin(); it!=cinfo.end(); ++it) {
|
|
if (it->Server.empty()) {
|
|
if (id == it->Id)
|
|
return it->FullPath;
|
|
}
|
|
else if (!it->Server.empty()) {
|
|
if (id == (10000000+it->Id)) {
|
|
return _EmptyString;
|
|
}
|
|
}
|
|
}
|
|
return _EmptyString;
|
|
}
|
|
|
|
|
|
bool oEvent::open(int id)
|
|
{
|
|
list<CompetitionInfo>::iterator it;
|
|
|
|
for (it=cinfo.begin(); it!=cinfo.end(); ++it) {
|
|
if (it->Server.empty()) {
|
|
if (id == it->Id) {
|
|
CompetitionInfo ci=*it; //Take copy
|
|
return open(ci.FullPath.c_str());
|
|
}
|
|
}
|
|
else if (!it->Server.empty()) {
|
|
if (id == (10000000+it->Id)) {
|
|
CompetitionInfo ci=*it; //Take copy
|
|
return readSynchronize(ci);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static DWORD timer;
|
|
static string mlog;
|
|
|
|
static void tic() {
|
|
timer = GetTickCount();
|
|
mlog.clear();
|
|
}
|
|
|
|
static void toc(const string &str) {
|
|
DWORD t = GetTickCount();
|
|
if (!mlog.empty())
|
|
mlog += ",\n";
|
|
else
|
|
mlog = "Tid (hundradels sekunder):\n";
|
|
|
|
mlog += str + "=" + itos( (t-timer)/10 );
|
|
timer = t;
|
|
}
|
|
|
|
|
|
bool oEvent::open(const string &file, bool Import)
|
|
{
|
|
if (!Import)
|
|
openFileLock->lockFile(file);
|
|
|
|
xmlparser xml(0);
|
|
xml.setProgress(gdibase.getHWND());
|
|
tic();
|
|
string log;
|
|
xml.read(file);
|
|
|
|
xmlattrib ver = xml.getObject(0).getAttrib("version");
|
|
if (ver) {
|
|
string vs = ver.get();
|
|
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("warn:opennewversion#" + vs);
|
|
if (!cont)
|
|
return false;
|
|
}
|
|
}
|
|
toc("parse");
|
|
//This generates a new file name
|
|
newCompetition("-");
|
|
|
|
if (!Import) {
|
|
strcpy_s(CurrentFile, MAX_PATH, file.c_str()); //Keep new file name, if imported
|
|
|
|
_splitpath_s(CurrentFile, NULL, 0, NULL,0, CurrentNameId, 64, NULL, 0);
|
|
int i=0;
|
|
while (CurrentNameId[i]) {
|
|
if (CurrentNameId[i]=='.') {
|
|
CurrentNameId[i]=0;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
bool res = open(xml);
|
|
if (res && !Import)
|
|
openFileLock->lockFile(file);
|
|
return res;
|
|
}
|
|
|
|
void oEvent::restoreBackup()
|
|
{
|
|
string cfile = string(CurrentFile) + ".meos";
|
|
strcpy_s(CurrentFile, cfile.c_str());
|
|
}
|
|
|
|
bool oEvent::open(const xmlparser &xml) {
|
|
xmlobject xo;
|
|
|
|
xo = xml.getObject("Date");
|
|
if (xo) Date=xo.get();
|
|
|
|
xo = xml.getObject("Name");
|
|
if (xo) Name=xo.get();
|
|
|
|
xo = xml.getObject("Annotation");
|
|
if (xo) Annotation = xo.get();
|
|
|
|
xo=xml.getObject("ZeroTime");
|
|
if (xo) ZeroTime=xo.getInt();
|
|
|
|
xo=xml.getObject("Id");
|
|
if (xo) Id=xo.getInt();
|
|
|
|
xo=xml.getObject("oData");
|
|
|
|
if (xo)
|
|
oEventData->set(this, xo);
|
|
|
|
setCurrency(-1, "", ",", false);
|
|
|
|
xo = xml.getObject("NameId");
|
|
if (xo)
|
|
strncpy_s(CurrentNameId, xo.get(), sizeof(CurrentNameId));
|
|
|
|
toc("event");
|
|
//Get controls
|
|
xo = xml.getObject("ControlList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
set<int> knownControls;
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Control")){
|
|
oControl c(this);
|
|
c.set(&*it);
|
|
|
|
if (c.Id>0 && knownControls.count(c.Id) == 0) {
|
|
Controls.push_back(c);
|
|
knownControls.insert(c.Id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("controls");
|
|
|
|
//Get courses
|
|
xo=xml.getObject("CourseList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
set<int> knownCourse;
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Course")){
|
|
oCourse c(this);
|
|
c.Set(&*it);
|
|
if (c.Id>0 && knownCourse.count(c.Id) == 0) {
|
|
addCourse(c);
|
|
knownCourse.insert(c.Id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("course");
|
|
|
|
//Get classes
|
|
xo=xml.getObject("ClassList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
set<int> knownClass;
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Class")){
|
|
oClass c(this);
|
|
c.Set(&*it);
|
|
if (c.Id>0 && knownClass.count(c.Id) == 0) {
|
|
Classes.push_back(c);
|
|
knownClass.insert(c.Id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("class");
|
|
|
|
//Get clubs
|
|
xo=xml.getObject("ClubList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Club")){
|
|
oClub c(this);
|
|
c.set(*it);
|
|
if (c.Id>0)
|
|
addClub(c);//Clubs.push_back(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("club");
|
|
|
|
//Get runners
|
|
xo=xml.getObject("RunnerList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Runner")){
|
|
oRunner r(this, 0);
|
|
r.Set(*it);
|
|
if (r.Id>0)
|
|
addRunner(r, false);
|
|
else if (r.Card)
|
|
r.Card->tOwner=0;
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("runner");
|
|
|
|
//Get teams
|
|
xo=xml.getObject("TeamList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Team")){
|
|
oTeam t(this, 0);
|
|
t.set(*it);
|
|
if (t.Id>0){
|
|
Teams.push_back(t);
|
|
teamById[t.Id] = &Teams.back();
|
|
Teams.back().apply(false, 0, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("team");
|
|
|
|
xo=xml.getObject("PunchList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
|
|
xmlList::const_iterator it;
|
|
if (xl.size() > 10)
|
|
setupCardHash(false); // This improves performance when there are many cards.
|
|
oFreePunch::disableHashing = true;
|
|
try {
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Punch")){
|
|
oFreePunch p(this, 0, 0, 0);
|
|
p.Set(&*it);
|
|
addFreePunch(p);
|
|
}
|
|
}
|
|
}
|
|
catch(...) {
|
|
oFreePunch::disableHashing = false;
|
|
throw;
|
|
}
|
|
oFreePunch::disableHashing = false;
|
|
oFreePunch::rehashPunches(*this, 0, 0);
|
|
setupCardHash(true); // Clear
|
|
}
|
|
|
|
toc("punch");
|
|
|
|
xo=xml.getObject("CardList");
|
|
if (xo){
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
xmlList::const_iterator it;
|
|
|
|
for(it=xl.begin(); it != xl.end(); ++it){
|
|
if (it->is("Card")){
|
|
oCard c(this);
|
|
c.Set(*it);
|
|
assert(c.Id>=0);
|
|
Cards.push_back(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
toc("card");
|
|
|
|
xo=xml.getObject("Updated");
|
|
if (xo) Modified.setStamp(xo.get());
|
|
|
|
adjustTeamMultiRunners(0);
|
|
updateFreeId();
|
|
reEvaluateAll(set<int>(), true); //True needed to update data for sure
|
|
|
|
toc("update");
|
|
string err;
|
|
|
|
try {
|
|
xmlobject xList = xml.getObject("Lists");
|
|
if (xList) {
|
|
if (!listContainer->load(MetaListContainer::ExternalList, xList, true)) {
|
|
err = "Visa listor är gjorda i en senare version av MeOS och kunde inte laddas.";
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &ex) {
|
|
if (err.empty())
|
|
err = ex.what();
|
|
}
|
|
getMeOSFeatures().deserialize(getDCI().getString("Features"), *this);
|
|
|
|
if (!err.empty())
|
|
throw meosException(err);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::openRunnerDatabase(char* filename)
|
|
{
|
|
char file[260];
|
|
getUserFile(file, filename);
|
|
|
|
char fclub[260];
|
|
char frunner[260];
|
|
|
|
strcpy_s(fclub, file);
|
|
strcat_s(fclub, ".clubs");
|
|
|
|
strcpy_s(frunner, file);
|
|
strcat_s(frunner, ".persons");
|
|
|
|
try {
|
|
if (fileExist(fclub) && fileExist(frunner)) {
|
|
runnerDB->loadClubs(fclub);
|
|
runnerDB->loadRunners(frunner);
|
|
}
|
|
}
|
|
catch(std::exception &ex) {
|
|
MessageBox(0, ex.what(), "Error", MB_OK);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
pRunner oEvent::dbLookUpById(__int64 extId) const
|
|
{
|
|
if (!useRunnerDb())
|
|
return 0;
|
|
oEvent *toe = const_cast<oEvent *>(this);
|
|
static oRunner sRunner = oRunner(toe, 0);
|
|
sRunner = oRunner(toe, 0);
|
|
RunnerDBEntry *dbr = runnerDB->getRunnerById(int(extId));
|
|
if (dbr != 0) {
|
|
sRunner.init(*dbr);
|
|
/*dbr->getName(sRunner.Name);
|
|
sRunner.CardNo = dbr->cardNo;
|
|
sRunner.Club = runnerDB->getClub(dbr->clubNo);
|
|
sRunner.getDI().setString("Nationality", dbr->getNationality());
|
|
sRunner.getDI().setInt("BirthYear", dbr->getBirthYear());
|
|
sRunner.getDI().setString("Sex", dbr->getSex());
|
|
sRunner.setExtIdentifier(dbr->getExtId());*/
|
|
return &sRunner;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
pRunner oEvent::dbLookUpByCard(int cardNo) const
|
|
{
|
|
if (!useRunnerDb())
|
|
return 0;
|
|
|
|
oEvent *toe = const_cast<oEvent *>(this);
|
|
static oRunner sRunner = oRunner(toe, 0);
|
|
sRunner = oRunner(toe, 0);
|
|
RunnerDBEntry *dbr = runnerDB->getRunnerByCard(cardNo);
|
|
if (dbr != 0) {
|
|
dbr->getName(sRunner.sName);
|
|
oRunner::getRealName(sRunner.sName, sRunner.tRealName);
|
|
sRunner.init(*dbr);
|
|
sRunner.CardNo = cardNo;
|
|
return &sRunner;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
pRunner oEvent::dbLookUpByName(const string &name, int clubId, int classId, int birthYear) const
|
|
{
|
|
if (!useRunnerDb())
|
|
return 0;
|
|
|
|
oEvent *toe = const_cast<oEvent *>(this);
|
|
|
|
static oRunner sRunner = oRunner(toe, 0);
|
|
sRunner = oRunner(toe, 0);
|
|
|
|
if (birthYear == 0) {
|
|
pClass pc = getClass(classId);
|
|
|
|
int expectedAge = pc ? pc->getExpectedAge() : 0;
|
|
|
|
if (expectedAge>0)
|
|
birthYear = getThisYear() - expectedAge;
|
|
}
|
|
|
|
pClub pc = getClub(clubId);
|
|
|
|
if (pc && pc->getExtIdentifier()>0)
|
|
clubId = (int)pc->getExtIdentifier();
|
|
|
|
RunnerDBEntry *dbr = runnerDB->getRunnerByName(name, clubId, birthYear);
|
|
|
|
if (dbr) {
|
|
sRunner.init(*dbr);
|
|
/*
|
|
dbr->getName(sRunner.Name);
|
|
sRunner.CardNo = dbr->cardNo;
|
|
sRunner.Club = runnerDB->getClub(dbr->clubNo);
|
|
sRunner.getDI().setString("Nationality", dbr->getNationality());
|
|
sRunner.getDI().setInt("BirthYear", dbr->getBirthYear());
|
|
sRunner.getDI().setString("Sex", dbr->getSex());*/
|
|
sRunner.setExtIdentifier(int(dbr->getExtId()));
|
|
return &sRunner;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool oEvent::saveRunnerDatabase(char *filename, bool onlyLocal)
|
|
{
|
|
char file[260];
|
|
getUserFile(file, filename);
|
|
|
|
char fclub[260];
|
|
char frunner[260];
|
|
strcpy_s(fclub, file);
|
|
strcat_s(fclub, ".clubs");
|
|
|
|
strcpy_s(frunner, file);
|
|
strcat_s(frunner, ".persons");
|
|
|
|
if (!onlyLocal || !runnerDB->isFromServer()) {
|
|
runnerDB->saveClubs(fclub);
|
|
runnerDB->saveRunners(frunner);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void oEvent::updateRunnerDatabase()
|
|
{
|
|
if (Name=="!TESTTÄVLING")
|
|
return;
|
|
|
|
if (useRunnerDb()) {
|
|
oRunnerList::iterator it;
|
|
map<int, int> clubIdMap;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->Card && it->Card->cardNo == it->CardNo &&
|
|
it->getDI().getInt("CardFee")==0 && it->Card->getNumPunches()>7)
|
|
updateRunnerDatabase(&*it, clubIdMap);
|
|
}
|
|
runnerDB->refreshTables();
|
|
}
|
|
if (listContainer) {
|
|
for (int k = 0; k < listContainer->getNumLists(); k++) {
|
|
if (listContainer->isExternal(k)) {
|
|
MetaList &ml = listContainer->getList(k);
|
|
string uid = ml.getUniqueId() + ".meoslist";
|
|
char file[260];
|
|
getUserFile(file, uid.c_str());
|
|
if (!fileExist(file)) {
|
|
ml.save(file, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::updateRunnerDatabase(pRunner r, map<int, int> &clubIdMap)
|
|
{
|
|
if (!r->CardNo)
|
|
return;
|
|
runnerDB->updateAdd(*r, clubIdMap);
|
|
}
|
|
|
|
pCourse oEvent::addCourse(const string &pname, int plengh, int id)
|
|
{
|
|
oCourse c(this, id);
|
|
c.Length = plengh;
|
|
c.Name = pname;
|
|
return addCourse(c);
|
|
}
|
|
|
|
pCourse oEvent::addCourse(const oCourse &oc)
|
|
{
|
|
if (oc.Id==0)
|
|
return 0;
|
|
else {
|
|
pCourse pOld=getCourse(oc.getId());
|
|
if (pOld)
|
|
return 0;
|
|
}
|
|
Courses.push_back(oc);
|
|
qFreeCourseId=max(qFreeCourseId, oc.getId());
|
|
|
|
pCourse pc = &Courses.back();
|
|
|
|
if (!pc->existInDB()) {
|
|
pc->updateChanged();
|
|
pc->synchronize();
|
|
}
|
|
courseIdIndex[oc.Id] = pc;
|
|
return pc;
|
|
}
|
|
|
|
void oEvent::autoAddTeam(pRunner pr)
|
|
{
|
|
//Warning: make sure there is no team already in DB that has not yet been applied yet...
|
|
if (pr && pr->Class) {
|
|
pClass pc = pr->Class;
|
|
if (pc->isSingleRunnerMultiStage()) {
|
|
//Auto create corresponding team
|
|
pTeam t = addTeam(pr->getName(), pr->getClubId(), pc->getId());
|
|
if (pr->StartNo == 0)
|
|
pr->StartNo = Teams.size();
|
|
t->setStartNo(pr->StartNo, false);
|
|
t->setRunner(0, pr, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::autoRemoveTeam(pRunner pr)
|
|
{
|
|
if (pr && pr->Class) {
|
|
pClass pc = pr->Class;
|
|
if (pc->isSingleRunnerMultiStage()) {
|
|
if (pr->tInTeam) {
|
|
// A team may have more than this runner -> do not remove
|
|
bool canRemove = true;
|
|
const vector<pRunner> &runners = pr->tInTeam->Runners;
|
|
for (size_t k = 0; k<runners.size(); k++) {
|
|
if (runners[k] && runners[k]->sName != pr->sName)
|
|
canRemove = false;
|
|
}
|
|
if (canRemove)
|
|
removeTeam(pr->tInTeam->getId());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pRunner oEvent::addRunner(const string &name, int clubId, int classId,
|
|
int cardNo, int birthYear, bool autoAdd)
|
|
{
|
|
if (birthYear != 0)
|
|
birthYear = extendYear(birthYear);
|
|
|
|
pRunner db_r = oe->dbLookUpByCard(cardNo);
|
|
|
|
if (db_r && !db_r->matchName(name))
|
|
db_r = 0; // "Existing" card, but different runner
|
|
|
|
|
|
if (db_r == 0 && getNumberSuffix(name) == 0)
|
|
db_r = oe->dbLookUpByName(name, clubId, classId, birthYear);
|
|
|
|
if (db_r) {
|
|
// We got name from DB. Other parameters might have changed from DB.
|
|
if (clubId>0)
|
|
db_r->Club = getClub(clubId);
|
|
db_r->Class = getClass(classId);
|
|
if (cardNo>0)
|
|
db_r->CardNo = cardNo;
|
|
if (birthYear>0)
|
|
db_r->setBirthYear(birthYear);
|
|
return addRunnerFromDB(db_r, classId, autoAdd);
|
|
}
|
|
oRunner r(this);
|
|
r.sName = name;
|
|
oRunner::getRealName(r.sName, r.tRealName);
|
|
r.Club = getClub(clubId);
|
|
r.Class = getClass(classId);
|
|
if (cardNo>0)
|
|
r.CardNo = cardNo;
|
|
if (birthYear>0)
|
|
r.setBirthYear(birthYear);
|
|
pRunner pr = addRunner(r, true);
|
|
|
|
if (pr->getDI().getInt("EntryDate") == 0)
|
|
pr->getDI().setDate("EntryDate", getLocalDate());
|
|
if (pr->Class) {
|
|
int heat = pr->Class->getDCI().getInt("Heat");
|
|
if (heat != 0)
|
|
pr->getDI().setInt("Heat", heat);
|
|
}
|
|
|
|
pr->updateChanged();
|
|
|
|
if (autoAdd)
|
|
autoAddTeam(pr);
|
|
return pr;
|
|
}
|
|
|
|
pRunner oEvent::addRunner(const string &pname, const string &pclub, int classId,
|
|
int cardNo, int birthYear, bool autoAdd)
|
|
{
|
|
if (!pclub.empty() || getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) {
|
|
pClub club = getClubCreate(0, pclub);
|
|
return addRunner(pname, club->getId(), classId, cardNo, birthYear, autoAdd);
|
|
}
|
|
else
|
|
return addRunner(pname, 0, classId, cardNo, birthYear, autoAdd);
|
|
}
|
|
|
|
pRunner oEvent::addRunnerFromDB(const pRunner db_r,
|
|
int classId, bool autoAdd)
|
|
{
|
|
oRunner r(this);
|
|
r.sName = db_r->sName;
|
|
oRunner::getRealName(r.sName, r.tRealName);
|
|
r.CardNo = db_r->CardNo;
|
|
|
|
if (db_r->Club) {
|
|
r.Club = getClub(db_r->getClubId());
|
|
if (!r.Club)
|
|
r.Club = addClub(*db_r->Club);
|
|
}
|
|
|
|
r.Class=classId ? getClass(classId) : 0;
|
|
memcpy(r.oData, db_r->oData, sizeof(r.oData));
|
|
|
|
pRunner pr = addRunner(r, true);
|
|
if (pr->getDI().getInt("EntryDate") == 0)
|
|
pr->getDI().setDate("EntryDate", getLocalDate());
|
|
|
|
if (r.Class) {
|
|
int heat = r.Class->getDCI().getInt("Heat");
|
|
if (heat != 0)
|
|
pr->getDI().setInt("Heat", heat);
|
|
}
|
|
|
|
pr->updateChanged();
|
|
|
|
if (autoAdd)
|
|
autoAddTeam(pr);
|
|
return pr;
|
|
}
|
|
|
|
pRunner oEvent::addRunner(const oRunner &r, bool updateStartNo) {
|
|
bool needUpdate = Runners.empty();
|
|
|
|
Runners.push_back(r);
|
|
pRunner pr=&Runners.back();
|
|
if (pr->StartNo == 0 && updateStartNo) {
|
|
pr->StartNo = ++nextFreeStartNo; // Need not be unique
|
|
}
|
|
else {
|
|
nextFreeStartNo = max(nextFreeStartNo, pr->StartNo);
|
|
}
|
|
|
|
if (pr->Card)
|
|
pr->Card->tOwner = pr;
|
|
|
|
if (HasDBConnection) {
|
|
if (!pr->existInDB())
|
|
pr->synchronize();
|
|
}
|
|
if (needUpdate)
|
|
oe->updateTabs();
|
|
|
|
if (pr->Class)
|
|
pr->Class->tResultInfo.clear();
|
|
|
|
bibStartNoToRunnerTeam.clear();
|
|
runnerById[pr->Id] = pr;
|
|
|
|
// Notify runner database that runner has entered
|
|
getRunnerDatabase().hasEnteredCompetition(r.getExtIdentifier());
|
|
return pr;
|
|
}
|
|
|
|
pRunner oEvent::addRunnerVacant(int classId) {
|
|
pRunner r=addRunner(lang.tl("Vakant"), getVacantClub(), classId, 0,0, true);
|
|
if (r) {
|
|
r->apply(false, 0, false);
|
|
r->synchronize(true);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int oEvent::getFreeCourseId()
|
|
{
|
|
qFreeCourseId++;
|
|
return qFreeCourseId;
|
|
}
|
|
|
|
int oEvent::getFreeControlId()
|
|
{
|
|
qFreeControlId++;
|
|
return qFreeControlId;
|
|
}
|
|
|
|
string oEvent::getAutoCourseName() const
|
|
{
|
|
char bf[32];
|
|
sprintf_s(bf, lang.tl("Bana %d").c_str(), Courses.size()+1);
|
|
return bf;
|
|
}
|
|
|
|
int oEvent::getFreeClassId()
|
|
{
|
|
qFreeClassId++;
|
|
return qFreeClassId;
|
|
}
|
|
|
|
int oEvent::getFirstClassId(bool teamClass) const {
|
|
for (oClassList::const_iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
int ns = it->getNumStages();
|
|
if (teamClass && ns > 0)
|
|
return it->Id;
|
|
else if (!teamClass && ns == 0)
|
|
return it->Id;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int oEvent::getFreeCardId()
|
|
{
|
|
qFreeCardId++;
|
|
return qFreeCardId;
|
|
}
|
|
|
|
int oEvent::getFreePunchId()
|
|
{
|
|
qFreePunchId++;
|
|
return qFreePunchId;
|
|
}
|
|
|
|
string oEvent::getAutoClassName() const
|
|
{
|
|
char bf[32];
|
|
sprintf_s(bf, 32, lang.tl("Klass %d").c_str(), Classes.size()+1);
|
|
return bf;
|
|
}
|
|
|
|
string oEvent::getAutoTeamName() const
|
|
{
|
|
char bf[32];
|
|
sprintf_s(bf, 32, lang.tl("Lag %d").c_str(), Teams.size()+1);
|
|
return bf;
|
|
}
|
|
|
|
string oEvent::getAutoRunnerName() const
|
|
{
|
|
char bf[32];
|
|
sprintf_s(bf, 32, lang.tl("Deltagare %d").c_str(), Runners.size()+1);
|
|
return bf;
|
|
}
|
|
|
|
int oEvent::getFreeClubId()
|
|
{
|
|
qFreeClubId++;
|
|
return qFreeClubId;
|
|
}
|
|
|
|
int oEvent::getFreeRunnerId()
|
|
{
|
|
qFreeRunnerId++;
|
|
return qFreeRunnerId;
|
|
}
|
|
|
|
void oEvent::updateFreeId(oBase *obj)
|
|
{
|
|
if (typeid(*obj)==typeid(oRunner)){
|
|
qFreeRunnerId=max(obj->Id, qFreeRunnerId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oClass)){
|
|
qFreeClassId=max(obj->Id, qFreeClassId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oCourse)){
|
|
qFreeCourseId=max(obj->Id, qFreeCourseId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oControl)){
|
|
qFreeControlId=max(obj->Id, qFreeControlId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oClub)){
|
|
if (obj->Id != cVacantId)
|
|
qFreeClubId=max(obj->Id, qFreeClubId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oCard)){
|
|
qFreeCardId=max(obj->Id, qFreeCardId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oFreePunch)){
|
|
qFreePunchId=max(obj->Id, qFreePunchId);
|
|
}
|
|
else if (typeid(*obj)==typeid(oTeam)){
|
|
qFreeTeamId=max(obj->Id, qFreeTeamId);
|
|
}
|
|
/*else if (typeid(*obj)==typeid(oEvent)){
|
|
qFree
|
|
}*/
|
|
}
|
|
|
|
void oEvent::updateFreeId()
|
|
{
|
|
{
|
|
oRunnerList::iterator it;
|
|
qFreeRunnerId=0;
|
|
nextFreeStartNo = 0;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
qFreeRunnerId = max(qFreeRunnerId, it->Id);
|
|
nextFreeStartNo = max(nextFreeStartNo, it->StartNo);
|
|
}
|
|
}
|
|
{
|
|
oClassList::iterator it;
|
|
qFreeClassId=0;
|
|
for (it=Classes.begin(); it != Classes.end(); ++it)
|
|
qFreeClassId=max(qFreeClassId, it->Id);
|
|
}
|
|
{
|
|
oCourseList::iterator it;
|
|
qFreeCourseId=0;
|
|
for (it=Courses.begin(); it != Courses.end(); ++it)
|
|
qFreeCourseId=max(qFreeCourseId, it->Id);
|
|
}
|
|
{
|
|
oControlList::iterator it;
|
|
qFreeControlId=0;
|
|
for (it=Controls.begin(); it != Controls.end(); ++it)
|
|
qFreeControlId=max(qFreeControlId, it->Id);
|
|
}
|
|
{
|
|
oClubList::iterator it;
|
|
qFreeClubId=0;
|
|
for (it=Clubs.begin(); it != Clubs.end(); ++it) {
|
|
if (it->Id != cVacantId)
|
|
qFreeClubId=max(qFreeClubId, it->Id);
|
|
}
|
|
}
|
|
{
|
|
oCardList::iterator it;
|
|
qFreeCardId=0;
|
|
for (it=Cards.begin(); it != Cards.end(); ++it)
|
|
qFreeCardId=max(qFreeCardId, it->Id);
|
|
}
|
|
{
|
|
oFreePunchList::iterator it;
|
|
qFreePunchId=0;
|
|
for (it=punches.begin(); it != punches.end(); ++it)
|
|
qFreePunchId=max(qFreePunchId, it->Id);
|
|
}
|
|
|
|
{
|
|
oTeamList::iterator it;
|
|
qFreeTeamId=0;
|
|
for (it=Teams.begin(); it != Teams.end(); ++it)
|
|
qFreeTeamId=max(qFreeTeamId, it->Id);
|
|
}
|
|
}
|
|
|
|
int oEvent::getVacantClub()
|
|
{
|
|
if (vacantId > 0)
|
|
return vacantId;
|
|
|
|
pClub pc = getClub("Vakant");
|
|
if (pc == 0)
|
|
pc = getClub("Vacant"); //eng
|
|
if (pc == 0)
|
|
pc = getClub(lang.tl("Vakant")); //other lang?
|
|
|
|
if (pc == 0)
|
|
pc=getClubCreate(cVacantId, lang.tl("Vakant"));
|
|
|
|
vacantId = pc->getId();
|
|
return vacantId;
|
|
}
|
|
|
|
int oEvent::getVacantClubIfExist() const
|
|
{
|
|
if (vacantId > 0)
|
|
return vacantId;
|
|
if (vacantId == -1)
|
|
return 0;
|
|
pClub pc=getClub("Vakant");
|
|
if (pc == 0)
|
|
pc = getClub("Vacant");
|
|
if (pc == 0)
|
|
pc = getClub(lang.tl("Vakant")); //other lang?
|
|
|
|
if (!pc) {
|
|
vacantId = -1;
|
|
return 0;
|
|
}
|
|
vacantId = pc->getId();
|
|
return vacantId;
|
|
}
|
|
|
|
pCard oEvent::allocateCard(pRunner owner)
|
|
{
|
|
oCard c(this);
|
|
c.tOwner = owner;
|
|
Cards.push_back(c);
|
|
pCard newCard = &Cards.back();
|
|
return newCard;
|
|
}
|
|
|
|
bool oEvent::sortRunners(SortOrder so) {
|
|
reinitializeClasses();
|
|
if (so == Custom)
|
|
return false;
|
|
CurrentSortOrder=so;
|
|
Runners.sort();
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) {
|
|
reinitializeClasses();
|
|
oTeamList::iterator it;
|
|
map<int, int> classId2Linear;
|
|
if (so==ClassResult || so==ClassTotalResult || so == ClassTeamLegResult) {
|
|
bool totalResult = so == ClassTotalResult;
|
|
bool legResult = so == ClassTeamLegResult;
|
|
bool hasRunner = (leg == -1);
|
|
for (it=Teams.begin(); it != Teams.end(); ++it) {
|
|
int lg = leg;
|
|
int clsId = it->Class->getId();
|
|
|
|
if (leg>=0 && !linearLeg && it->Class) {
|
|
map<int,int>::iterator res = classId2Linear.find(it->Class->getId());
|
|
if (res == classId2Linear.end()) {
|
|
unsigned linLegBase = it->Class->getLegNumberLinear(leg, 0);
|
|
while (linLegBase + 1 < it->Class->getNumStages()) {
|
|
if (it->Class->isParallel(linLegBase+1) || it->Class->isOptional(linLegBase+1))
|
|
linLegBase++;
|
|
else
|
|
break;
|
|
}
|
|
lg = linLegBase;
|
|
//lg = linLegBase + it->Class->getNumParallel(linLegBase) - 1;
|
|
classId2Linear[clsId] = lg;
|
|
}
|
|
else {
|
|
lg = res->second;
|
|
}
|
|
}
|
|
|
|
const int lastIndex = it->Class ? it->Class->getLastStageIndex() : 0;
|
|
lg = min<unsigned>(lg, lastIndex);
|
|
|
|
|
|
if (lg >= leg)
|
|
hasRunner = true;
|
|
if (legResult) {
|
|
pRunner r = it->getRunner(lg);
|
|
if (r) {
|
|
it->_sortTime = r->getRunningTime();
|
|
it->_cachedStatus = r->getStatus();
|
|
}
|
|
else {
|
|
it->_sortTime = 0;
|
|
it->_cachedStatus = StatusUnknown;
|
|
}
|
|
}
|
|
else {
|
|
it->_sortTime=it->getLegRunningTime(lg, totalResult) + it->getNumShortening(lg)*3600*24*10;
|
|
it->_cachedStatus = it->getLegStatus(lg, totalResult);
|
|
|
|
// Ensure number of restarts has effect on final result
|
|
if (lg == lastIndex)
|
|
it->_sortTime += it->tNumRestarts*24*3600;
|
|
}
|
|
unsigned rawStatus = it->_cachedStatus;
|
|
it->_sortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0];
|
|
|
|
}
|
|
|
|
if (!hasRunner)
|
|
return false;
|
|
|
|
Teams.sort(oTeam::compareResult);
|
|
}
|
|
else if (so==ClassStartTime) {
|
|
for (it=Teams.begin(); it != Teams.end(); ++it) {
|
|
it->_cachedStatus = StatusUnknown;
|
|
it->_sortStatus=0;
|
|
it->_sortTime=it->getLegStartTime(leg);
|
|
if (it->_sortTime<=0)
|
|
it->_sortStatus=1;
|
|
}
|
|
Teams.sort(oTeam::compareResult);
|
|
}
|
|
return true;
|
|
}
|
|
string oEvent::getZeroTime() const
|
|
{
|
|
return getAbsTime(0);
|
|
}
|
|
|
|
void oEvent::setZeroTime(string m)
|
|
{
|
|
unsigned nZeroTime = convertAbsoluteTime(m);
|
|
if (nZeroTime!=ZeroTime && nZeroTime != -1) {
|
|
updateChanged();
|
|
ZeroTime=nZeroTime;
|
|
}
|
|
}
|
|
|
|
void oEvent::setName(const string &m)
|
|
{
|
|
if (trim(m).empty())
|
|
throw meosException("Tomt namn är inte tillåtet.");
|
|
|
|
if (m != getName()) {
|
|
Name = m;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
void oEvent::setAnnotation(const string &m)
|
|
{
|
|
if (m!=Annotation) {
|
|
Annotation=m;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
string oEvent::getTitleName() const {
|
|
if (empty())
|
|
return "";
|
|
if (HasPendingDBConnection)
|
|
return getName() + lang.tl(" (på server)") + lang.tl(" DATABASE ERROR");
|
|
else if (isClient())
|
|
return getName() + lang.tl(" (på server)");
|
|
else
|
|
return getName() + lang.tl(" (lokalt)");
|
|
}
|
|
|
|
void oEvent::setDate(const string &m)
|
|
{
|
|
if (m!=Date) {
|
|
int d = convertDateYMS(m, true);
|
|
if (d <= 0)
|
|
throw meosException("Felaktigt datumformat 'X' (Använd ÅÅÅÅ-MM-DD).#" + m);
|
|
Date = formatDate(d, true);
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
const string &oEvent::getAbsTime(DWORD time) const {
|
|
DWORD t = ZeroTime + time;
|
|
if (int(t)<0)
|
|
t = 0;
|
|
int days = time/(3600*24);
|
|
if (days <= 0)
|
|
return formatTimeHMS(t % (24*3600));
|
|
else {
|
|
string &res = StringCache::getInstance().get();
|
|
res = itos(days) + "D " + formatTimeHMS(t % (24*3600));
|
|
return res;
|
|
}
|
|
}
|
|
|
|
const string &oEvent::getTimeZoneString() const {
|
|
if (!date2LocalTZ.count(Date))
|
|
date2LocalTZ[Date] = ::getTimeZoneString(Date);
|
|
return date2LocalTZ[Date];
|
|
}
|
|
|
|
string oEvent::getAbsDateTimeISO(DWORD time, bool includeDate, bool useGMT) const
|
|
{
|
|
DWORD t = ZeroTime + time;
|
|
string dateS, timeS;
|
|
if (int(t)<0) {
|
|
dateS = "2000-01-01";
|
|
if (useGMT)
|
|
timeS = "00:00:00Z";
|
|
else
|
|
timeS = "00:00:00" + getTimeZoneString();
|
|
}
|
|
else {
|
|
int extraDay;
|
|
|
|
if (useGMT) {
|
|
int offset = ::getTimeZoneInfo(Date);
|
|
t += offset;
|
|
if (t < 0) {
|
|
extraDay = -1;
|
|
t += 3600 * 24;
|
|
}
|
|
else {
|
|
extraDay = t / (3600*24);
|
|
}
|
|
char bf[64];
|
|
sprintf_s(bf, "%02d:%02d:%02dZ", (t/3600)%24, (t/60)%60, t%60);
|
|
timeS = bf;
|
|
}
|
|
else {
|
|
char bf[64];
|
|
extraDay = t / (3600*24);
|
|
sprintf_s(bf, "%02d:%02d:%02d", (t/3600)%24, (t/60)%60, t%60);
|
|
timeS = bf + getTimeZoneString();
|
|
}
|
|
|
|
if (extraDay == 0 ) {
|
|
dateS = Date;
|
|
}
|
|
else {
|
|
SYSTEMTIME st;
|
|
convertDateYMS(Date, st, false);
|
|
__int64 sec = SystemTimeToInt64Second(st);
|
|
sec = sec + (extraDay * 3600 * 24);
|
|
st = Int64SecondToSystemTime(sec);
|
|
dateS = convertSystemDate(st);
|
|
}
|
|
}
|
|
|
|
if (includeDate)
|
|
return dateS + "T" + timeS;
|
|
else
|
|
return timeS;
|
|
}
|
|
|
|
const string &oEvent::getAbsTimeHM(DWORD time) const
|
|
{
|
|
DWORD t=ZeroTime+time;
|
|
|
|
if (int(t)<0)
|
|
return MakeDash("-");
|
|
|
|
char bf[32];
|
|
sprintf_s(bf, "%02d:%02d", (t/3600)%24, (t/60)%60);
|
|
|
|
string &res = StringCache::getInstance().get();
|
|
res = bf;
|
|
return res;
|
|
}
|
|
|
|
//Absolute time string to absolute time int (used by cvs-parser)
|
|
int oEvent::convertAbsoluteTime(const string &m)
|
|
{
|
|
if (m.empty() || m[0]=='-')
|
|
return -1;
|
|
|
|
int len=m.length();
|
|
bool firstComma = false;
|
|
for (int k=0;k<len;k++) {
|
|
BYTE b=m[k];
|
|
if ( !(b==' ' || (b>='0' && b<='9')) ) {
|
|
if (b==':' && firstComma == false)
|
|
continue;
|
|
else if ((b==',' || b=='.') && firstComma == false) {
|
|
firstComma = true;
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int hour=atoi(m.c_str());
|
|
|
|
if (hour<0 || hour>23)
|
|
return -1;
|
|
|
|
int minute=0;
|
|
int second=0;
|
|
|
|
int kp=m.find_first_of(':');
|
|
|
|
if (kp>0)
|
|
{
|
|
string mtext=m.substr(kp+1);
|
|
minute=atoi(mtext.c_str());
|
|
|
|
if (minute<0 || minute>60)
|
|
minute=0;
|
|
|
|
kp=mtext.find_last_of(':');
|
|
|
|
if (kp>0) {
|
|
second=atoi(mtext.substr(kp+1).c_str());
|
|
|
|
if (second<0 || second>60)
|
|
second=0;
|
|
}
|
|
}
|
|
int t=hour*3600+minute*60+second;
|
|
|
|
if (t<0) return 0;
|
|
|
|
return t;
|
|
}
|
|
|
|
int oEvent::getRelativeTime(const string &date, const string &absoluteTime, const string &timeZone) const {
|
|
|
|
int atime=convertAbsoluteTime(absoluteTime);
|
|
|
|
if (timeZone == "Z" || timeZone == "z") {
|
|
SYSTEMTIME st;
|
|
convertDateYMS(date, st, false);
|
|
|
|
st.wHour = atime / 3600;
|
|
st.wMinute = (atime / 60) % 60;
|
|
st.wSecond = atime % 60;
|
|
|
|
SYSTEMTIME localTime;
|
|
memset(&localTime, 0, sizeof(SYSTEMTIME));
|
|
SystemTimeToTzSpecificLocalTime(0, &st, &localTime);
|
|
|
|
atime = localTime.wHour*3600 + localTime.wMinute * 60 + localTime.wSecond;
|
|
}
|
|
|
|
if (atime>=0 && atime<3600*24){
|
|
int rtime=atime-ZeroTime;
|
|
|
|
if (rtime<=0)
|
|
rtime+=3600*24;
|
|
|
|
//Don't allow times just before zero time.
|
|
if (rtime>3600*23)
|
|
return -1;
|
|
|
|
return rtime;
|
|
}
|
|
else return -1;
|
|
}
|
|
|
|
|
|
int oEvent::getRelativeTime(const string &m) const
|
|
{
|
|
int dayIndex = 0;
|
|
for (size_t k = 0; k + 1 < m.length(); k++) {
|
|
int c = m[k];
|
|
if (c == 'D' || c == 'd' || c == 'T' || c == 't') {
|
|
dayIndex = k + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int atime;
|
|
int days = 0;
|
|
if (dayIndex == 0)
|
|
atime = convertAbsoluteTime(m);
|
|
else {
|
|
atime = convertAbsoluteTime(m.substr(dayIndex));
|
|
days = atoi(m.c_str());
|
|
}
|
|
if (atime>=0 && atime <= 3600*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;
|
|
|
|
return rtime;
|
|
}
|
|
else return -1;
|
|
}
|
|
|
|
int oEvent::getRelativeTimeFrom12Hour(const string &m) const
|
|
{
|
|
int atime=convertAbsoluteTime(m);
|
|
|
|
if (atime>=0 && atime<3600*24) {
|
|
int lowBound = ZeroTime;
|
|
int highBound = ZeroTime + 3600 * 12;
|
|
|
|
bool ok = ( atime >= lowBound && atime <= highBound) ||
|
|
( (atime+3600*24) >= lowBound && (atime+3600*24) <= highBound);
|
|
|
|
int rtime = atime - ZeroTime;
|
|
if (!ok)
|
|
rtime += 12 * 3600;
|
|
|
|
rtime = (rtime+24*3600)%(24*3600);
|
|
|
|
//int rtime=atime-(ZeroTime % (3600*12));
|
|
|
|
/* if (rtime<=0)
|
|
rtime+=3600*12;
|
|
*/
|
|
//Don't allow times just before zero time.
|
|
if (rtime>3600*22)
|
|
return -1;
|
|
|
|
return rtime;
|
|
}
|
|
else return -1;
|
|
}
|
|
|
|
void oEvent::removeRunner(const vector<int> &ids)
|
|
{
|
|
oRunnerList::iterator it;
|
|
|
|
set<int> toRemove;
|
|
for (size_t k = 0; k < ids.size(); k++) {
|
|
int Id = ids[k];
|
|
pRunner r=getRunner(Id, 0);
|
|
|
|
if (r==0)
|
|
continue;
|
|
|
|
r = r->tParentRunner ? r->tParentRunner : r;
|
|
|
|
if (toRemove.count(r->getId()))
|
|
continue; //Already found.
|
|
|
|
//Remove a singe runner team
|
|
autoRemoveTeam(r);
|
|
|
|
for (size_t k=0;k<r->multiRunner.size();k++)
|
|
if (r->multiRunner[k])
|
|
toRemove.insert(r->multiRunner[k]->getId());
|
|
|
|
toRemove.insert(Id);
|
|
}
|
|
|
|
if (toRemove.empty())
|
|
return;
|
|
|
|
dataRevision++;
|
|
set<pClass> affectedCls;
|
|
for (it=Runners.begin(); it != Runners.end();){
|
|
oRunner &cr = *it;
|
|
if (toRemove.count(cr.getId())> 0) {
|
|
if (cr.Class)
|
|
affectedCls.insert(cr.Class);
|
|
if (HasDBConnection)
|
|
msRemove(&cr);
|
|
toRemove.erase(cr.getId());
|
|
runnerById.erase(cr.getId());
|
|
if (cr.Card) {
|
|
assert( cr.Card->tOwner == &cr );
|
|
cr.Card->tOwner = 0;
|
|
}
|
|
// Reset team runner (this should not happen)
|
|
if (it->tInTeam) {
|
|
if (it->tInTeam->Runners[it->tLeg]==&*it)
|
|
it->tInTeam->Runners[it->tLeg] = 0;
|
|
}
|
|
|
|
oRunnerList::iterator next = it;
|
|
++next;
|
|
|
|
Runners.erase(it);
|
|
if (toRemove.empty()) {
|
|
break;
|
|
}
|
|
else
|
|
it = next;
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
|
|
for (set<pClass>::iterator it = affectedCls.begin(); it != affectedCls.end(); ++it) {
|
|
(*it)->clearCache(true);
|
|
(*it)->markSQLChanged(-1,-1);
|
|
}
|
|
|
|
oe->updateTabs();
|
|
}
|
|
|
|
void oEvent::removeCourse(int Id)
|
|
{
|
|
oCourseList::iterator it;
|
|
|
|
for (it=Courses.begin(); it != Courses.end(); ++it){
|
|
if (it->Id==Id){
|
|
if (HasDBConnection)
|
|
msRemove(&*it);
|
|
dataRevision++;
|
|
Courses.erase(it);
|
|
courseIdIndex.erase(Id);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::removeClass(int Id)
|
|
{
|
|
oClassList::iterator it;
|
|
|
|
for (it=Classes.begin(); it != Classes.end(); ++it){
|
|
if (it->Id==Id){
|
|
if (HasDBConnection)
|
|
msRemove(&*it);
|
|
Classes.erase(it);
|
|
dataRevision++;
|
|
updateTabs();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::removeControl(int Id)
|
|
{
|
|
oControlList::iterator it;
|
|
|
|
for (it=Controls.begin(); it != Controls.end(); ++it){
|
|
if (it->Id==Id){
|
|
if (HasDBConnection)
|
|
msRemove(&*it);
|
|
Controls.erase(it);
|
|
dataRevision++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::removeClub(int Id)
|
|
{
|
|
oClubList::iterator it;
|
|
|
|
for (it=Clubs.begin(); it != Clubs.end(); ++it){
|
|
if (it->Id==Id) {
|
|
if (HasDBConnection)
|
|
msRemove(&*it);
|
|
Clubs.erase(it);
|
|
clubIdIndex.erase(Id);
|
|
dataRevision++;
|
|
return;
|
|
}
|
|
}
|
|
if (vacantId == Id)
|
|
vacantId = 0; // Clear vacant id
|
|
}
|
|
|
|
void oEvent::removeCard(int Id)
|
|
{
|
|
oCardList::iterator it;
|
|
|
|
for (it=Cards.begin(); it != Cards.end(); ++it) {
|
|
if (it->getOwner() == 0 && it->Id == Id) {
|
|
if (it->tOwner) {
|
|
if (it->tOwner->Card == &*it)
|
|
it->tOwner->Card = 0;
|
|
}
|
|
if (HasDBConnection)
|
|
msRemove(&*it);
|
|
Cards.erase(it);
|
|
dataRevision++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool oEvent::isCourseUsed(int Id) const
|
|
{
|
|
oClassList::const_iterator it;
|
|
|
|
for (it=Classes.begin(); it != Classes.end(); ++it){
|
|
if (it->isCourseUsed(Id))
|
|
return true;
|
|
}
|
|
|
|
oRunnerList::const_iterator rit;
|
|
|
|
for (rit=Runners.begin(); rit != Runners.end(); ++rit){
|
|
pCourse pc=rit->getCourse(false);
|
|
if (pc && pc->Id==Id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::isClassUsed(int Id) const
|
|
{
|
|
//Search runners
|
|
oRunnerList::const_iterator it;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->getClassId()==Id)
|
|
return true;
|
|
}
|
|
|
|
//Search teams
|
|
oTeamList::const_iterator tit;
|
|
for (tit=Teams.begin(); tit != Teams.end(); ++tit){
|
|
if (tit->getClassId()==Id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::isClubUsed(int Id) const
|
|
{
|
|
//Search runners
|
|
oRunnerList::const_iterator it;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->getClubId()==Id)
|
|
return true;
|
|
}
|
|
|
|
//Search teams
|
|
oTeamList::const_iterator tit;
|
|
for (tit=Teams.begin(); tit != Teams.end(); ++tit){
|
|
if (tit->getClubId()==Id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::isRunnerUsed(int Id) const
|
|
{
|
|
//Search teams
|
|
oTeamList::const_iterator tit;
|
|
for (tit=Teams.begin(); tit != Teams.end(); ++tit){
|
|
if (tit->isRunnerUsed(Id)) {
|
|
if (tit->Class && tit->Class->isSingleRunnerMultiStage())
|
|
//Don't report single-runner-teams as blocking
|
|
continue;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::isControlUsed(int Id) const
|
|
{
|
|
oCourseList::const_iterator it;
|
|
|
|
for (it=Courses.begin(); it != Courses.end(); ++it){
|
|
|
|
for(int i=0;i<it->nControls;i++)
|
|
if (it->Controls[i] && it->Controls[i]->Id==Id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::classHasResults(int Id) const
|
|
{
|
|
oRunnerList::const_iterator it;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
if ( (Id == 0 || it->getClassId() == Id) && (it->getCard() || it->FinishTime))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::classHasTeams(int Id) const
|
|
{
|
|
oTeamList::const_iterator it;
|
|
|
|
for (it=Teams.begin(); it != Teams.end(); ++it)
|
|
if (it->getClassId()==Id)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void oEvent::generateVacancyList(gdioutput &gdi, GUICALLBACK cb)
|
|
{
|
|
sortRunners(ClassStartTime);
|
|
oRunnerList::iterator it;
|
|
|
|
// BIB, START, NAME, CLUB, SI
|
|
int dx[5]={0, 0, 70, 150};
|
|
|
|
bool withbib=hasBib(true, false);
|
|
int i;
|
|
|
|
if (withbib) for (i=1;i<4;i++) dx[i]+=40;
|
|
|
|
int y=gdi.getCY();
|
|
int x=gdi.getCX();
|
|
int lh=gdi.getLineHeight();
|
|
|
|
const int yStart = y;
|
|
int nVac = 0;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip() || !it->isVacant())
|
|
continue;
|
|
nVac++;
|
|
}
|
|
|
|
int nCol = 1 + min(3, nVac/10);
|
|
int RunnersPerCol = nVac / nCol;
|
|
|
|
char bf[256];
|
|
int nRunner = 0;
|
|
y+=lh;
|
|
|
|
int Id=0;
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->skip() || !it->isVacant())
|
|
continue;
|
|
|
|
if (it->getClassId() != Id) {
|
|
Id=it->getClassId();
|
|
y+=lh/2;
|
|
|
|
if (nRunner>=RunnersPerCol) {
|
|
y = yStart;
|
|
x += dx[3]+5;
|
|
nRunner = 0;
|
|
}
|
|
|
|
|
|
gdi.addStringUT(y, x+dx[0], 1, it->getClass());
|
|
y+=lh+lh/3;
|
|
}
|
|
|
|
oDataInterface DI=it->getDI();
|
|
|
|
if (withbib) {
|
|
string bib=it->getBib();
|
|
|
|
if (!bib.empty()) {
|
|
gdi.addStringUT(y, x+dx[0], 0, bib);
|
|
}
|
|
}
|
|
gdi.addStringUT(y, x+dx[1], 0, it->getStartTimeS(), 0, cb).setExtra(it->getId());
|
|
|
|
_itoa_s(it->Id, bf, 256, 10);
|
|
gdi.addStringUT(y, x+dx[2], 0, it->getName(), dx[3]-dx[2]-4, cb).setExtra(it->getId());
|
|
//gdi.addStringUT(y, x+dx[3], 0, it->getClub());
|
|
|
|
y+=lh;
|
|
nRunner++;
|
|
}
|
|
if (nVac==0)
|
|
gdi.addString("", y, x, 0, "Inga vakanser tillgängliga. Vakanser skapas vanligen vid lottning.");
|
|
gdi.updateScrollbars();
|
|
}
|
|
|
|
void oEvent::generateInForestList(gdioutput &gdi, GUICALLBACK cb, GUICALLBACK cb_nostart)
|
|
{
|
|
//Lazy setup: tie runners and persons
|
|
oFreePunch::rehashPunches(*oe, 0, 0);
|
|
|
|
// Map cardNo -> punch
|
|
multimap<int, pFreePunch> punchHash;
|
|
map<int, int> cardCount;
|
|
|
|
for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->isRemoved() && it->getCardNo() > 0)
|
|
++cardCount[it->getCardNo()];
|
|
}
|
|
|
|
typedef multimap<int, pFreePunch>::const_iterator TPunchIter;
|
|
|
|
for (oFreePunchList::iterator it = punches.begin(); it != punches.end(); ++it) {
|
|
punchHash.insert(make_pair(it->getCardNo(), &*it));
|
|
}
|
|
|
|
sortTeams(ClassStartTime, 0, true);
|
|
int y=gdi.getCY();
|
|
int x=gdi.getCX();
|
|
int lh=gdi.getLineHeight();
|
|
|
|
oTeamList::iterator it;
|
|
gdi.addStringUT(2, lang.tl("Kvar-i-skogen") + MakeDash(" - ") + getName());
|
|
y+=lh/2;
|
|
|
|
gdi.addStringUT(1, getDate());
|
|
|
|
gdi.dropLine();
|
|
|
|
y+=3*lh;
|
|
int id=0;
|
|
int nr=0;
|
|
|
|
for(it=Teams.begin(); it!=Teams.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
if (it->tStatus==StatusUnknown) {
|
|
|
|
if (id != it->getClassId()) {
|
|
if (nr>0) {
|
|
gdi.addString("", y, x, 0, "Antal: X#"+itos(nr));
|
|
y+=lh;
|
|
nr=0;
|
|
}
|
|
y += lh;
|
|
id = it->getClassId();
|
|
gdi.addStringUT(y, x, 1, it->getClass());
|
|
y += lh;
|
|
}
|
|
gdi.addStringUT(y, x, 0, it->getClass());
|
|
nr++;
|
|
gdi.addStringUT(y, x+100, 0, it->getName(), 0, cb).setExtra(it->getId()).id = "T";
|
|
y+=lh;
|
|
}
|
|
}
|
|
|
|
if (nr>0) {
|
|
gdi.addString("", y, x, 0, "Antal: X#"+itos(nr));
|
|
y+=lh;
|
|
}
|
|
|
|
{
|
|
int tnr = 0;
|
|
id=0;
|
|
nr=0;
|
|
sortRunners(ClassStartTime);
|
|
|
|
oRunnerList::iterator it;
|
|
|
|
int dx[4]={0, 70, 350, 470};
|
|
int y=gdi.getCY();
|
|
int x=gdi.getCX();
|
|
int lh=gdi.getLineHeight();
|
|
|
|
y+=lh;
|
|
char bf[256];
|
|
|
|
y=gdi.getCY();
|
|
vector<pRunner> rr;
|
|
setupCardHash(false);
|
|
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->skip() || it->needNoCard())
|
|
continue;
|
|
|
|
if (it->tStatus == StatusUnknown) {
|
|
|
|
if (id != it->getClassId()) {
|
|
if (nr>0) {
|
|
gdi.addString("", y, x, 0, "Antal: X#"+itos(nr));
|
|
y+=lh;
|
|
nr=0;
|
|
}
|
|
y += lh;
|
|
id = it->getClassId();
|
|
gdi.addStringUT(y, x, 1, it->getClass());
|
|
y += lh;
|
|
}
|
|
|
|
bool hasPunch = false;
|
|
string punches;
|
|
string otherRunners;
|
|
pair<TPunchIter, TPunchIter> range = punchHash.equal_range(it->getCardNo());
|
|
for (TPunchIter pit = range.first; pit != range.second; ++pit) {
|
|
if (pit->second->tRunnerId == it->getId()) {
|
|
if (hasPunch)
|
|
punches.append(", ");
|
|
else
|
|
hasPunch = true;
|
|
|
|
punches.append(pit->second->getSimpleString());
|
|
}
|
|
}
|
|
|
|
getRunnersByCardNo(it->getCardNo(), true, true, rr);
|
|
for (size_t k = 0; k < rr.size(); k++) {
|
|
if (rr[k]->getId() != it->getId()) {
|
|
if (otherRunners.empty()) {
|
|
otherRunners = lang.tl("Bricka X används också av: #" + itos(it->getCardNo()));
|
|
}
|
|
else {
|
|
otherRunners += ", ";
|
|
}
|
|
otherRunners += rr[k]->getName();
|
|
}
|
|
}
|
|
gdi.addStringUT(y, x+dx[0], 0, it->getStartTimeS());
|
|
string club = it->getClub();
|
|
if (!club.empty())
|
|
club = " (" + club + ")";
|
|
|
|
gdi.addStringUT(y, x+dx[1], 0, it->getName()+club, dx[2]-dx[1]-4, cb).setExtra(it->getId()).id = "R";
|
|
_itoa_s(it->Id, bf, 256, 10);
|
|
nr++;
|
|
tnr++;
|
|
|
|
if (hasPunch) {
|
|
if (otherRunners.empty()) {
|
|
RECT rc = gdi.addString("", y, x+dx[2], 0, "(har stämplat)", dx[3]-dx[2]-4).textRect;
|
|
capitalize(punches);
|
|
gdi.addToolTip("", "#" + punches, 0, &rc);
|
|
}
|
|
else {
|
|
// Återanvänd bricka
|
|
RECT rc = gdi.addString("", y, x+dx[2], 0, "#(" + lang.tl("reused card") + ")", dx[3]-dx[2]-4).textRect;
|
|
capitalize(punches);
|
|
gdi.addToolTip("", "#" + punches + ". " + otherRunners, 0, &rc);
|
|
}
|
|
}
|
|
gdi.addStringUT(y, x+dx[3], 0, it->getClass());
|
|
y+=lh;
|
|
}
|
|
}
|
|
if (nr>0) {
|
|
gdi.addString("", y, x, 0, "Antal: X#"+itos(nr));
|
|
y+=lh;
|
|
}
|
|
|
|
if (tnr == 0 && Runners.size()>0) {
|
|
gdi.addString("", 10, "inforestwarning");
|
|
}
|
|
}
|
|
setupCardHash(true);
|
|
|
|
gdi.updateScrollbars();
|
|
}
|
|
|
|
void oEvent::generateMinuteStartlist(gdioutput &gdi) {
|
|
sortRunners(SortByStartTime);
|
|
|
|
int dx[4]={0, gdi.scaleLength(70), gdi.scaleLength(340), gdi.scaleLength(510)};
|
|
int y=gdi.getCY();
|
|
int x=gdi.getCX();
|
|
int lh=gdi.getLineHeight();
|
|
|
|
vector<int> blocks;
|
|
vector<string> starts;
|
|
getStartBlocks(blocks, starts);
|
|
|
|
char bf[256];
|
|
for (size_t k=0;k<blocks.size();k++) {
|
|
gdi.dropLine();
|
|
if (k>0)
|
|
gdi.addStringUT(gdi.getCY()-1, 0, pageNewPage, "");
|
|
|
|
gdi.addStringUT(boldLarge, lang.tl("Minutstartlista") + MakeDash(" - ") + getName());
|
|
if (!starts[k].empty()) {
|
|
sprintf_s(bf, lang.tl("%s, block: %d").c_str(), starts[k].c_str(), blocks[k]);
|
|
gdi.addStringUT(fontMedium, bf);
|
|
}
|
|
else if (blocks[k]!=0) {
|
|
sprintf_s(bf, lang.tl("Startblock: %d").c_str(), blocks[k]);
|
|
gdi.addStringUT(fontMedium, bf);
|
|
}
|
|
|
|
vector< vector< vector<pRunner> > > sb;
|
|
sb.reserve(Runners.size());
|
|
int LastStartTime=-1;
|
|
for (oRunnerList::iterator it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->Class && it->Class->getBlock()!=blocks[k])
|
|
continue;
|
|
if (it->Class && it->Class->getStart() != starts[k] )
|
|
continue;
|
|
if (!it->Class && blocks[k]!=0)
|
|
continue;
|
|
if (it->getStatus() == StatusNotCompetiting)
|
|
continue;
|
|
|
|
if (LastStartTime!=it->tStartTime) {
|
|
sb.resize(sb.size() + 1);
|
|
LastStartTime = it->tStartTime;
|
|
}
|
|
|
|
if (sb.empty())
|
|
sb.resize(1);
|
|
|
|
if (it->tInTeam == 0)
|
|
sb.back().push_back(vector<pRunner>(1, &*it));
|
|
else {
|
|
if (it->legToRun() > 0 && it->getStartTime() == 0)
|
|
continue;
|
|
int minIx = 10000;
|
|
for (int j = 0; j < it->tInTeam->getNumRunners(); j++) {
|
|
if (j != it->tLeg &&
|
|
it->tInTeam->Runners[j] &&
|
|
it->tInTeam->Runners[j]->tStartTime == it->tStartTime)
|
|
minIx = min(minIx, j);
|
|
}
|
|
if (minIx == 10000)
|
|
sb.back().push_back(vector<pRunner>(1, &*it)); // Single runner on this start time
|
|
else if (minIx > it->tLeg) {
|
|
sb.back().push_back(vector<pRunner>());
|
|
for (int j = 0; j < it->tInTeam->getNumRunners(); j++) {
|
|
if (it->tInTeam->Runners[j] &&
|
|
it->tInTeam->Runners[j]->tStartTime == it->tStartTime)
|
|
sb.back().back().push_back(it->tInTeam->Runners[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
y = gdi.getCY();
|
|
for (size_t k = 0; k < sb.size(); k++) {
|
|
if (sb[k].empty())
|
|
continue;
|
|
y+=lh/2;
|
|
gdi.addStringUT(y, x+dx[0], boldText, sb[k][0][0]->getStartTimeS());
|
|
y+=lh;
|
|
|
|
for (size_t j = 0; j < sb[k].size(); j++) {
|
|
const int src_y = y;
|
|
int indent = 0;
|
|
const vector<pRunner> &r = sb[k][j];
|
|
if (r.size() == 1) {
|
|
if (r[0]->getCardNo()>0)
|
|
gdi.addStringUT(y, x+dx[0], fontMedium, itos(r[0]->getCardNo()));
|
|
|
|
string name;
|
|
if (r[0]->getBib().empty())
|
|
name = r[0]->getName();
|
|
else
|
|
name = r[0]->getName() + " (" + r[0]->getBib() + ")";
|
|
gdi.addStringUT(y, x+dx[1], fontMedium, name, dx[2]-dx[1]-4);
|
|
}
|
|
else {
|
|
string name;
|
|
if (!r[0]->tInTeam->getBib().empty())
|
|
name = r[0]->tInTeam->getBib() + ": ";
|
|
|
|
int nnames = 0;
|
|
for (size_t i = 0; i < r.size(); i++) {
|
|
if (nnames>0)
|
|
name += ", ";
|
|
nnames++;
|
|
|
|
if (nnames > 2) {
|
|
gdi.addStringUT(y, x+dx[0]+indent, fontMedium, name, dx[2]-dx[0]-4-indent);
|
|
name.clear();
|
|
nnames = 1;
|
|
y+=lh;
|
|
indent = gdi.scaleLength(20);
|
|
}
|
|
|
|
name += r[i]->getName();
|
|
if (r[i]->getCardNo()>0) {
|
|
name += " (" + itos(r[i]->getCardNo()) + ")";
|
|
}
|
|
|
|
}
|
|
gdi.addStringUT(y, x+dx[0]+indent, fontMedium, name, dx[2]-dx[0]-4-indent);
|
|
}
|
|
|
|
gdi.addStringUT(src_y, x+dx[2], fontMedium, r[0]->getClub(), dx[3]-dx[2]-4);
|
|
gdi.addStringUT(src_y, x+dx[3], fontMedium, r[0]->getClass());
|
|
y+=lh;
|
|
}
|
|
}
|
|
}
|
|
gdi.refresh();
|
|
}
|
|
|
|
const string &oEvent::getName() const {
|
|
if (Name.size() > 1 && Name.at(0) == '%') {
|
|
return lang.tl(Name.substr(1));
|
|
}
|
|
else
|
|
return Name;
|
|
}
|
|
|
|
bool oEvent::empty() const
|
|
{
|
|
return Name.empty();
|
|
}
|
|
|
|
void oEvent::clearListedCmp()
|
|
{
|
|
cinfo.clear();
|
|
}
|
|
|
|
bool oEvent::enumerateCompetitions(const char *file, const char *filetype)
|
|
{
|
|
WIN32_FIND_DATA fd;
|
|
|
|
char dir[MAX_PATH];
|
|
char FullPath[MAX_PATH];
|
|
|
|
strcpy_s(dir, MAX_PATH, file);
|
|
|
|
if (dir[strlen(file)-1]!='\\')
|
|
strcat_s(dir, MAX_PATH, "\\");
|
|
|
|
strcpy_s(FullPath, MAX_PATH, dir);
|
|
|
|
strcat_s(dir, MAX_PATH, filetype);
|
|
|
|
HANDLE h=FindFirstFile(dir, &fd);
|
|
|
|
if (h==INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
bool more=true;
|
|
int id=1;
|
|
cinfo.clear();
|
|
|
|
while (more) {
|
|
if (fd.cFileName[0]!='.') //Avoid .. and .
|
|
{
|
|
char FullPathFile[MAX_PATH];
|
|
strcpy_s(FullPathFile, MAX_PATH, FullPath);
|
|
strcat_s(FullPathFile, MAX_PATH, fd.cFileName);
|
|
|
|
CompetitionInfo ci;
|
|
|
|
ci.FullPath=FullPathFile;
|
|
ci.Name="";
|
|
ci.Date="2007-01-01";
|
|
ci.Id=id++;
|
|
|
|
SYSTEMTIME st;
|
|
FileTimeToSystemTime(&fd.ftLastWriteTime, &st);
|
|
ci.Modified=convertSystemTime(st);
|
|
xmlparser xp(0);
|
|
|
|
try {
|
|
xp.read(FullPathFile, 8);
|
|
|
|
const xmlobject date=xp.getObject("Date");
|
|
|
|
if (date) ci.Date=date.get();
|
|
|
|
const xmlobject name=xp.getObject("Name");
|
|
|
|
if (name) {
|
|
ci.Name=name.get();
|
|
if (ci.Name.size() > 1 && ci.Name.at(0) == '%') {
|
|
ci.Name = lang.tl(ci.Name.substr(1));
|
|
}
|
|
}
|
|
const xmlobject annotation=xp.getObject("Annotation");
|
|
|
|
if (annotation)
|
|
ci.Annotation=annotation.get();
|
|
|
|
const xmlobject nameid = xp.getObject("NameId");
|
|
if (nameid)
|
|
ci.NameId = nameid.get();
|
|
|
|
cinfo.push_front(ci);
|
|
}
|
|
catch (std::exception &) {
|
|
// XXX Do what??
|
|
}
|
|
}
|
|
more=FindNextFile(h, &fd)!=0;
|
|
}
|
|
|
|
FindClose(h);
|
|
|
|
if (!getServerName().empty())
|
|
msListCompetitions(this);
|
|
|
|
for (list<CompetitionInfo>::iterator it=cinfo.begin(); it!=cinfo.end(); ++it) {
|
|
if (it->Name.size() > 1 && it->Name[0] == '%')
|
|
it->Name = lang.tl(it->Name.substr(1));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::enumerateBackups(const char *file)
|
|
{
|
|
backupInfo.clear();
|
|
|
|
enumerateBackups(file, "*.meos.bu?", 1);
|
|
enumerateBackups(file, "*.removed", 1);
|
|
enumerateBackups(file, "*.dbmeos*", 2);
|
|
backupInfo.sort();
|
|
|
|
int id = 1;
|
|
for (list<BackupInfo>::iterator it = backupInfo.begin(); it != backupInfo.end(); ++it) {
|
|
it->backupId = id++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const BackupInfo &oEvent::getBackup(int bid) const {
|
|
for (list<BackupInfo>::const_iterator it = backupInfo.begin(); it != backupInfo.end(); ++it) {
|
|
if (it->backupId == bid) {
|
|
return *it;
|
|
}
|
|
}
|
|
throw meosException("Internal error");
|
|
}
|
|
|
|
void oEvent::deleteBackups(const BackupInfo &bu) {
|
|
string file = bu.fileName + bu.Name;
|
|
list<string> toRemove;
|
|
|
|
for (list<BackupInfo>::iterator it = backupInfo.begin(); it != backupInfo.end(); ++it) {
|
|
if (file == it->fileName + it->Name)
|
|
toRemove.push_back(it->FullPath);
|
|
}
|
|
if (!toRemove.empty()) {
|
|
char path[260];
|
|
char drive[48];
|
|
char filename[260];
|
|
char ext[64];
|
|
//_splitpath_s(toRemove.back().c_str(), drive, ds, path, dirs, filename, fns, ext, exts);
|
|
_splitpath_s(toRemove.back().c_str(), drive, path, filename, ext);
|
|
|
|
string dest = string(drive) + path;
|
|
toRemove.push_back(dest + bu.fileName + ".persons");
|
|
toRemove.push_back(dest + bu.fileName + ".clubs");
|
|
|
|
for (list<string>::iterator it = toRemove.begin(); it != toRemove.end(); ++it) {
|
|
DeleteFile(it->c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool oEvent::listBackups(gdioutput &gdi, GUICALLBACK cb)
|
|
{
|
|
int y = gdi.getCY();
|
|
int x = gdi.getCX();
|
|
|
|
list<BackupInfo>::iterator it = backupInfo.begin();
|
|
while (it != backupInfo.end()) {
|
|
list<BackupInfo>::iterator sum_size = it;
|
|
size_t s = 0;
|
|
//string date = it->Modified;
|
|
string file = it->fileName + it->Name;
|
|
|
|
while(sum_size != backupInfo.end() && file == sum_size->fileName + sum_size->Name) {
|
|
s += sum_size->fileSize;
|
|
++sum_size;
|
|
}
|
|
string type = lang.tl(it->type==1 ? "backup" : "serverbackup");
|
|
string size;
|
|
if (s < 1024) {
|
|
size = itos(s) + " bytes";
|
|
}
|
|
else if (s < 1024*512) {
|
|
size = itos(s/1024) + " kB";
|
|
}
|
|
else {
|
|
size = itos(s/(1024*1024)) + "." + itos( ((10*(s/1024))/1024)%10) + " MB";
|
|
}
|
|
gdi.dropLine();
|
|
gdi.addStringUT(gdi.getCY(), gdi.getCX(), boldText, it->Name + " (" + it->Date + ") " + type, 400);
|
|
|
|
gdi.pushX();
|
|
gdi.fillRight();
|
|
gdi.addString("", 0, "Utrymme: X#" + size);
|
|
gdi.addString("EraseBackup", 0, "[Radera]", cb).setExtra(it->backupId);
|
|
gdi.fillDown();
|
|
gdi.popX();
|
|
gdi.dropLine(1.5);
|
|
y = gdi.getCY();
|
|
while(it != backupInfo.end() && file == it->fileName + it->Name) {
|
|
gdi.addStringUT(y, x+30, 0, it->Modified, 400, cb).setExtra(it->backupId);
|
|
++it;
|
|
y += gdi.getLineHeight();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BackupInfo::operator<(const BackupInfo &ci)
|
|
{
|
|
if (Date!=ci.Date)
|
|
return Date>ci.Date;
|
|
|
|
if (fileName!=ci.fileName)
|
|
return fileName<ci.fileName;
|
|
|
|
return Modified>ci.Modified;
|
|
}
|
|
|
|
|
|
bool oEvent::enumerateBackups(const char *file, const char *filetype, int type)
|
|
{
|
|
WIN32_FIND_DATA fd;
|
|
char dir[MAX_PATH];
|
|
char FullPath[MAX_PATH];
|
|
|
|
strcpy_s(dir, MAX_PATH, file);
|
|
|
|
if (dir[strlen(file)-1]!='\\')
|
|
strcat_s(dir, MAX_PATH, "\\");
|
|
|
|
strcpy_s(FullPath, MAX_PATH, dir);
|
|
strcat_s(dir, MAX_PATH, filetype);
|
|
HANDLE h=FindFirstFile(dir, &fd);
|
|
|
|
if (h==INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
bool more=true;
|
|
while (more) {
|
|
if (fd.cFileName[0]!='.') {//Avoid .. and .
|
|
char FullPathFile[MAX_PATH];
|
|
strcpy_s(FullPathFile, MAX_PATH, FullPath);
|
|
strcat_s(FullPathFile, MAX_PATH, fd.cFileName);
|
|
|
|
BackupInfo ci;
|
|
|
|
ci.type = type;
|
|
ci.FullPath=FullPathFile;
|
|
ci.Name="";
|
|
ci.Date="2007-01-01";
|
|
ci.fileName = fd.cFileName;
|
|
ci.fileSize = fd.nFileSizeLow;
|
|
size_t pIndex = ci.fileName.find_first_of(".");
|
|
if (pIndex>0 && pIndex<ci.fileName.size())
|
|
ci.fileName = ci.fileName.substr(0, pIndex);
|
|
|
|
SYSTEMTIME st;
|
|
FILETIME localTime;
|
|
FileTimeToLocalFileTime(&fd.ftLastWriteTime, &localTime);
|
|
FileTimeToSystemTime(&localTime, &st);
|
|
|
|
ci.Modified=convertSystemTime(st);
|
|
xmlparser xp(0);
|
|
|
|
try {
|
|
xp.read(FullPathFile, 5);
|
|
//xmlobject *xo=xp.getObject("meosdata");
|
|
const xmlobject date=xp.getObject("Date");
|
|
|
|
if (date) ci.Date=date.get();
|
|
|
|
const xmlobject name=xp.getObject("Name");
|
|
|
|
if (name) {
|
|
ci.Name=name.get();
|
|
if (ci.Name.size() > 1 && ci.Name.at(0) == '%') {
|
|
ci.Name = lang.tl(ci.Name.substr(1));
|
|
}
|
|
}
|
|
|
|
backupInfo.push_front(ci);
|
|
}
|
|
catch (std::exception &) {
|
|
//XXX Do what?
|
|
}
|
|
}
|
|
more=FindNextFile(h, &fd)!=0;
|
|
}
|
|
|
|
FindClose(h);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::fillCompetitions(gdioutput &gdi,
|
|
const string &name, int type,
|
|
const string &select) {
|
|
cinfo.sort();
|
|
cinfo.reverse();
|
|
list<CompetitionInfo>::iterator it;
|
|
|
|
gdi.clearList(name);
|
|
string b;
|
|
int idSel = -1;
|
|
//char bf[128];
|
|
for (it=cinfo.begin(); it!=cinfo.end(); ++it) {
|
|
string annotation;
|
|
if (!it->Annotation.empty())
|
|
annotation = " (" + it->Annotation + ")";
|
|
if (it->Server.length()==0) {
|
|
if (type==0 || type==1) {
|
|
if (it->NameId == select && !select.empty())
|
|
idSel = it->Id;
|
|
string bf = "[" + it->Date + "] " + it->Name;
|
|
gdi.addItem(name, bf + annotation, it->Id);
|
|
}
|
|
}
|
|
else if (type==0 || type==2) {
|
|
if (it->NameId == select && !select.empty())
|
|
idSel = it->Id;
|
|
string bf;
|
|
if (type==0)
|
|
bf = lang.tl("Server: [X] Y#" + it->Date + "#" + it->Name);
|
|
else
|
|
bf = "[" + it->Date + "] " + it->Name;
|
|
|
|
gdi.addItem(name, bf + annotation, 10000000+it->Id);
|
|
}
|
|
}
|
|
|
|
if (idSel != -1)
|
|
gdi.selectItemByData(name.c_str(), idSel);
|
|
return true;
|
|
}
|
|
|
|
void tabAutoKillMachines();
|
|
|
|
void oEvent::checkDB()
|
|
{
|
|
if (HasDBConnection) {
|
|
vector<string> err;
|
|
int k=checkChanged(err);
|
|
|
|
#ifdef _DEBUG
|
|
if (k>0) {
|
|
char bf[256];
|
|
sprintf_s(bf, "Databasen innehåller %d osynkroniserade ändringar.", k);
|
|
string msg(bf);
|
|
for(int i=0;i < min<int>(err.size(), 10);i++)
|
|
msg+=string("\n")+err[i];
|
|
|
|
MessageBox(0, msg.c_str(), "Varning/Fel", MB_OK);
|
|
}
|
|
#endif
|
|
}
|
|
updateTabs();
|
|
gdibase.setWindowTitle(getTitleName());
|
|
}
|
|
|
|
void destroyExtraWindows();
|
|
|
|
void oEvent::clear()
|
|
{
|
|
checkDB();
|
|
|
|
if (HasDBConnection)
|
|
msMonitor(0);
|
|
|
|
HasDBConnection=false;
|
|
HasPendingDBConnection = false;
|
|
|
|
destroyExtraWindows();
|
|
|
|
while (!tables.empty()) {
|
|
tables.begin()->second->releaseOwnership();
|
|
tables.erase(tables.begin());
|
|
}
|
|
Table::resetTableIds();
|
|
|
|
getRunnerDatabase().releaseTables();
|
|
getMeOSFeatures().clear(*this);
|
|
Id=0;
|
|
dataRevision = 0;
|
|
tClubDataRevision = -1;
|
|
|
|
ZeroTime=0;
|
|
Name.clear();
|
|
Annotation.clear();
|
|
|
|
//Make sure no daemon is hunting us.
|
|
tabAutoKillMachines();
|
|
|
|
delete directSocket;
|
|
directSocket = 0;
|
|
|
|
tLongTimesCached = -1;
|
|
|
|
//Order of destruction is extreamly important...
|
|
runnerById.clear();
|
|
bibStartNoToRunnerTeam.clear();
|
|
Runners.clear();
|
|
Teams.clear();
|
|
teamById.clear();
|
|
|
|
Classes.clear();
|
|
Courses.clear();
|
|
courseIdIndex.clear();
|
|
|
|
Controls.clear();
|
|
|
|
Cards.clear();
|
|
Clubs.clear();
|
|
clubIdIndex.clear();
|
|
|
|
punchIndex.clear();
|
|
punches.clear();
|
|
|
|
updateFreeId();
|
|
|
|
strcpy_s(CurrentNameId, "");
|
|
strcpy_s(CurrentFile, "");
|
|
|
|
sqlCounterRunners=0;
|
|
sqlCounterClasses=0;
|
|
sqlCounterCourses=0;
|
|
sqlCounterControls=0;
|
|
sqlCounterClubs=0;
|
|
sqlCounterCards=0;
|
|
sqlCounterPunches=0;
|
|
sqlCounterTeams=0;
|
|
|
|
sqlUpdateControls.clear();
|
|
sqlUpdateCards.clear();
|
|
sqlUpdateClubs.clear();
|
|
sqlUpdateClasses.clear();
|
|
sqlUpdateCourses.clear();
|
|
sqlUpdateRunners.clear();
|
|
sqlUpdatePunches.clear();
|
|
sqlUpdateTeams.clear();
|
|
|
|
vacantId = 0;
|
|
oEventData->initData(this, sizeof(oData));
|
|
timelineClasses.clear();
|
|
timeLineEvents.clear();
|
|
nextTimeLineEvent = 0;
|
|
|
|
tCurrencyFactor = 1;
|
|
tCurrencySymbol = "kr";
|
|
tCurrencySeparator = ",";
|
|
tCurrencyPreSymbol = false;
|
|
|
|
readPunchHash.clear();
|
|
|
|
//Reset speaker data structures.
|
|
listContainer->clearExternal();
|
|
while(!generalResults.empty() && generalResults.back().isDynamic())
|
|
generalResults.pop_back();
|
|
|
|
// Cleanup user interface
|
|
gdibase.getTabs().clearCompetitionData();
|
|
|
|
MeOSUtil::useHourFormat = getPropertyInt("UseHourFormat", 1) != 0;
|
|
|
|
currentNameMode = (NameMode) getPropertyInt("NameMode", FirstLast);
|
|
}
|
|
|
|
bool oEvent::deleteCompetition()
|
|
{
|
|
if (!empty() && !HasDBConnection) {
|
|
string removed = string(CurrentFile)+".removed";
|
|
::remove(removed.c_str()); //Delete old removed file
|
|
openFileLock->unlockFile();
|
|
::rename(CurrentFile, removed.c_str());
|
|
return true;
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
void oEvent::newCompetition(const string &name)
|
|
{
|
|
openFileLock->unlockFile();
|
|
clear();
|
|
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st);
|
|
|
|
Date = convertSystemDate(st);
|
|
ZeroTime = st.wHour*3600;
|
|
|
|
Name = name;
|
|
oEventData->initData(this, sizeof(oData));
|
|
|
|
getDI().setString("Organizer", getPropertyString("Organizer", ""));
|
|
getDI().setString("Street", getPropertyString("Street", ""));
|
|
getDI().setString("Address", getPropertyString("Address", ""));
|
|
getDI().setString("EMail", getPropertyString("EMail", ""));
|
|
getDI().setString("Homepage", getPropertyString("Homepage", ""));
|
|
|
|
getDI().setInt("CardFee", getPropertyInt("CardFee", 25));
|
|
getDI().setInt("EliteFee", getPropertyInt("EliteFee", 130));
|
|
getDI().setInt("EntryFee", getPropertyInt("EntryFee", 90));
|
|
getDI().setInt("YouthFee", getPropertyInt("YouthFee", 50));
|
|
|
|
getDI().setInt("SeniorAge", getPropertyInt("SeniorAge", 0));
|
|
getDI().setInt("YouthAge", getPropertyInt("YouthAge", 16));
|
|
|
|
getDI().setString("Account", getPropertyString("Account", ""));
|
|
getDI().setString("LateEntryFactor", getPropertyString("LateEntryFactor", "50 %"));
|
|
|
|
getDI().setString("CurrencySymbol", getPropertyString("CurrencySymbol", "kr"));
|
|
getDI().setString("CurrencySeparator", getPropertyString("CurrencySeparator", "."));
|
|
getDI().setInt("CurrencyFactor", getPropertyInt("CurrencyFactor", 1));
|
|
getDI().setInt("CurrencyPreSymbol", getPropertyInt("CurrencyPreSymbol", 0));
|
|
getDI().setString("PayModes", getPropertyString("PayModes", ""));
|
|
|
|
setCurrency(-1, "", "", 0);
|
|
|
|
char file[260];
|
|
char filename[64];
|
|
sprintf_s(filename, 64, "meos_%d%02d%02d_%02d%02d%02d_%X.meos",
|
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
|
|
|
|
//strcpy_s(CurrentNameId, filename);
|
|
getUserFile(file, filename);
|
|
|
|
strcpy_s(CurrentFile, MAX_PATH, file);
|
|
_splitpath_s(CurrentFile, NULL, 0, NULL,0, CurrentNameId, 64, NULL, 0);
|
|
int i=0;
|
|
while (CurrentNameId[i]) {
|
|
if (CurrentNameId[i]=='.') {
|
|
CurrentNameId[i]=0;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
oe->updateTabs();
|
|
}
|
|
|
|
void oEvent::reEvaluateCourse(int CourseId, bool DoSync)
|
|
{
|
|
oRunnerList::iterator it;
|
|
|
|
if (DoSync)
|
|
autoSynchronizeLists(false);
|
|
|
|
vector<int> mp;
|
|
set<int> classes;
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->getCourse(false) && it->getCourse(false)->getId()==CourseId){
|
|
classes.insert(it->getClassId());
|
|
}
|
|
}
|
|
|
|
reEvaluateAll(classes, DoSync);
|
|
}
|
|
|
|
void oEvent::reEvaluateAll(const set<int> &cls, bool doSync)
|
|
{
|
|
if (doSync)
|
|
autoSynchronizeLists(false);
|
|
|
|
for(oClassList::iterator it=Classes.begin();it!=Classes.end();++it) {
|
|
if (cls.empty() || cls.count(it->Id)) {
|
|
it->clearSplitAnalysis();
|
|
it->resetLeaderTime();
|
|
it->reinitialize();
|
|
}
|
|
}
|
|
|
|
for(oTeamList::iterator tit=Teams.begin();tit!=Teams.end();++tit) {
|
|
if (!cls.empty() && cls.count(tit->getClassId()) == 0)
|
|
continue;
|
|
|
|
tit->resetTmpStore();
|
|
int nr = tit->getNumRunners();
|
|
for (int k = 0; k < nr; k++) {
|
|
if (tit->Runners[k])
|
|
tit->Runners[k]->resetTmpStore();
|
|
}
|
|
|
|
if (!tit->isRemoved()) {
|
|
tit->apply(false, 0, true);
|
|
}
|
|
}
|
|
oRunnerList::iterator it;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!cls.empty() && cls.count(it->getClassId()) == 0)
|
|
continue;
|
|
|
|
if (!it->tInTeam) {
|
|
it->resetTmpStore();
|
|
it->apply(false, 0, true);
|
|
}
|
|
}
|
|
|
|
vector<int> mp;
|
|
bool needupdate = true;
|
|
int leg = 0;
|
|
while (needupdate) {
|
|
needupdate = false;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!cls.empty() && cls.count(it->getClassId()) == 0)
|
|
continue;
|
|
|
|
if (!it->isRemoved()) {
|
|
if (it->tLeg == leg) {
|
|
it->evaluateCard(false, mp); // Must not sync!
|
|
it->storeTimes();
|
|
}
|
|
else if (it->tLeg>leg)
|
|
needupdate = true;
|
|
}
|
|
}
|
|
leg++;
|
|
}
|
|
|
|
// Update team start times etc.
|
|
for(oTeamList::iterator tit=Teams.begin();tit!=Teams.end();++tit) {
|
|
if (!tit->isRemoved()) {
|
|
if (!cls.empty() && cls.count(tit->getClassId()) == 0)
|
|
continue;
|
|
|
|
tit->apply(false, 0, true);
|
|
tit->setTmpStore();
|
|
if (doSync)
|
|
tit->synchronize(true);
|
|
}
|
|
}
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->isRemoved()) {
|
|
if (!cls.empty() && cls.count(it->getClassId()) == 0)
|
|
continue;
|
|
|
|
if (!it->tInTeam)
|
|
it->apply(false, 0, true);
|
|
it->setTmpStore();
|
|
it->storeTimes();
|
|
it->clearOnChangedRunningTime();
|
|
}
|
|
}
|
|
//reCalculateLeaderTimes(0);
|
|
}
|
|
|
|
void oEvent::reEvaluateChanged()
|
|
{
|
|
if (sqlChangedClasses || sqlChangedCourses || sqlChangedControls) {
|
|
reEvaluateAll(set<int>(), false);
|
|
globalModification = true;
|
|
return;
|
|
}
|
|
|
|
if (sqlChangedClubs)
|
|
globalModification = true;
|
|
|
|
|
|
if (!sqlChangedCards && !sqlChangedRunners && !sqlChangedTeams)
|
|
return; // Nothing to do
|
|
|
|
map<int, bool> resetClasses;
|
|
for(oClassList::iterator it=Classes.begin();it!=Classes.end();++it) {
|
|
if (it->wasSQLChanged(-1, oPunch::PunchFinish)) {
|
|
it->clearSplitAnalysis();
|
|
it->resetLeaderTime();
|
|
it->reinitialize();
|
|
resetClasses[it->getId()] = it->hasClassGlobalDependance();
|
|
}
|
|
}
|
|
|
|
stdext::hash_set<int> addedTeams;
|
|
|
|
for(oTeamList::iterator tit=Teams.begin();tit!=Teams.end();++tit) {
|
|
if (tit->isRemoved() || !tit->wasSQLChanged())
|
|
continue;
|
|
|
|
addedTeams.insert(tit->getId());
|
|
tit->resetTmpStore();
|
|
|
|
int nr = tit->getNumRunners();
|
|
for (int k = 0; k < nr; k++) {
|
|
if (tit->Runners[k])
|
|
tit->Runners[k]->resetTmpStore();
|
|
}
|
|
tit->apply(false, 0, true);
|
|
}
|
|
|
|
oRunnerList::iterator it;
|
|
vector< vector<pRunner> > legRunners(maxRunnersTeam);
|
|
|
|
if (Teams.size() > 0) {
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
if (resetClasses.count(it->getClassId()))
|
|
it->storeTimes();
|
|
|
|
if (!it->wasSQLChanged() && !resetClasses[it->getClassId()])
|
|
continue;
|
|
|
|
pTeam t = it->tInTeam;
|
|
if (t && !addedTeams.count(t->getId())) {
|
|
addedTeams.insert(t->getId());
|
|
t->resetTmpStore();
|
|
int nr = t->getNumRunners();
|
|
for (int k = 0; k < nr; k++) {
|
|
if (t->Runners[k])
|
|
t->Runners[k]->resetTmpStore();
|
|
}
|
|
t->apply(false, 0, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
pRunner r = &*it;
|
|
if (r->isRemoved())
|
|
continue;
|
|
|
|
if (r->wasSQLChanged() || (r->tInTeam && addedTeams.count(r->tInTeam->getId()))) {
|
|
unsigned leg = r->tLeg;
|
|
if (leg <0 || leg >= maxRunnersTeam)
|
|
leg = 0;
|
|
|
|
if (legRunners[leg].empty())
|
|
legRunners[leg].reserve(Runners.size() / (leg+1));
|
|
|
|
legRunners[leg].push_back(r);
|
|
if (!r->tInTeam) {
|
|
r->resetTmpStore();
|
|
r->apply(false, 0, true);
|
|
}
|
|
}
|
|
else {
|
|
if (r->Class && r->Class->wasSQLChanged(-1, oPunch::PunchFinish)) {
|
|
it->storeTimes();
|
|
}
|
|
}
|
|
}
|
|
|
|
vector<int> mp;
|
|
|
|
// Reevaluate
|
|
for (size_t leg = 0; leg < legRunners.size(); leg++) {
|
|
const vector<pRunner> &lr = legRunners[leg];
|
|
for (size_t k = 0; k < lr.size(); k++) {
|
|
lr[k]->evaluateCard(false, mp); // Must not sync!
|
|
}
|
|
}
|
|
|
|
for(oTeamList::iterator tit=Teams.begin();tit!=Teams.end();++tit) {
|
|
if (addedTeams.count(tit->getId())) {
|
|
tit->apply(false, 0, true);
|
|
tit->setTmpStore();
|
|
}
|
|
}
|
|
|
|
for (size_t leg = 0; leg < legRunners.size(); leg++) {
|
|
const vector<pRunner> &lr = legRunners[leg];
|
|
for (size_t k = 0; k < lr.size(); k++) {
|
|
if (!lr[k]->tInTeam)
|
|
lr[k]->apply(false, 0, true);
|
|
lr[k]->setTmpStore();
|
|
lr[k]->clearOnChangedRunningTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::reCalculateLeaderTimes(int classId)
|
|
{
|
|
if (disableRecalculate)
|
|
return;
|
|
#ifdef _DEBUG
|
|
char bf[128];
|
|
sprintf_s(bf, "Calculate leader times %d\n", classId);
|
|
OutputDebugString(bf);
|
|
#endif
|
|
for (oClassList::iterator it=Classes.begin(); it != Classes.end(); ++it) {
|
|
if (!it->isRemoved() && (classId==it->getId() || classId==0))
|
|
it->resetLeaderTime();
|
|
}
|
|
bool needupdate = true;
|
|
int leg = 0;
|
|
while (needupdate) {
|
|
needupdate = false;
|
|
for (oRunnerList::iterator it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->isRemoved() && (classId==0 || classId==it->getClassId())) {
|
|
if (it->tLeg == leg)
|
|
it->storeTimes();
|
|
else if (it->tLeg>leg)
|
|
needupdate = true;
|
|
}
|
|
}
|
|
leg++;
|
|
}
|
|
}
|
|
|
|
|
|
string oEvent::getCurrentTimeS() const
|
|
{
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st);
|
|
|
|
char bf[64];
|
|
sprintf_s(bf, 64, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
|
|
return bf;
|
|
}
|
|
|
|
int oEvent::findBestClass(const SICard &card, vector<pClass> &classes) const
|
|
{
|
|
classes.clear();
|
|
int Distance=-1000;
|
|
oClassList::const_iterator it;
|
|
|
|
for (it=Classes.begin(); it != Classes.end(); ++it) {
|
|
vector<pCourse> courses;
|
|
it->getCourses(0, courses);
|
|
bool insertClass = false; // Make sure a class is only included once
|
|
|
|
for (size_t k = 0; k<courses.size(); k++) {
|
|
pCourse pc = courses[k];
|
|
if (pc) {
|
|
int d=pc->distance(card);
|
|
|
|
if (d>=0) {
|
|
if (Distance<0) Distance=1000;
|
|
|
|
if (d<Distance) {
|
|
Distance=d;
|
|
classes.clear();
|
|
insertClass = true;
|
|
classes.push_back(pClass(&*it));
|
|
}
|
|
else if (d == Distance) {
|
|
if (!insertClass) {
|
|
insertClass = true;
|
|
classes.push_back(pClass(&*it));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (Distance<0 && d>Distance) {
|
|
Distance = d;
|
|
classes.clear();
|
|
insertClass = true;
|
|
classes.push_back(pClass(&*it));
|
|
}
|
|
else if (Distance == d) {
|
|
if (!insertClass) {
|
|
insertClass = true;
|
|
classes.push_back(pClass(&*it));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Distance;
|
|
}
|
|
|
|
void oEvent::convertTimes(SICard &sic) const
|
|
{
|
|
if (sic.convertedTime)
|
|
return;
|
|
|
|
sic.convertedTime = true;
|
|
|
|
if (sic.CheckPunch.Code!=-1){
|
|
if (sic.CheckPunch.Time<ZeroTime)
|
|
sic.CheckPunch.Time+=(24*3600);
|
|
|
|
sic.CheckPunch.Time-=ZeroTime;
|
|
}
|
|
|
|
// Support times longer than 24 hours
|
|
int maxLegTime = useLongTimes() ? 22 * 3600 : 0;
|
|
|
|
if (maxLegTime > 0) {
|
|
|
|
const int START = 1000;
|
|
const int FINISH = 1001;
|
|
vector<pair<int, int> > times;
|
|
|
|
if (sic.StartPunch.Code!=-1) {
|
|
if (sic.StartPunch.Time != -1)
|
|
times.push_back(make_pair(sic.StartPunch.Time, START));
|
|
}
|
|
|
|
for (unsigned k=0; k <sic.nPunch; k++){
|
|
if (sic.Punch[k].Code!=-1 && sic.Punch[k].Time != -1) {
|
|
times.push_back(make_pair(sic.Punch[k].Time, k));
|
|
}
|
|
}
|
|
|
|
if (sic.FinishPunch.Code!=-1 && sic.FinishPunch.Time != 1) {
|
|
times.push_back(make_pair(sic.FinishPunch.Time, FINISH));
|
|
|
|
if (!times.empty()) {
|
|
int dayOffset = 0;
|
|
if (times.front().first < int(ZeroTime)) {
|
|
dayOffset = 3600 * 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;
|
|
}
|
|
times[k].first += dayOffset;
|
|
}
|
|
|
|
// Update card times
|
|
for (size_t k = 0; k < times.size(); k++) {
|
|
if (times[k].second == START)
|
|
sic.StartPunch.Time = times[k].first;
|
|
else if (times[k].second == FINISH)
|
|
sic.FinishPunch.Time = times[k].first;
|
|
else
|
|
sic.Punch[times[k].second].Time = times[k].first;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sic.StartPunch.Code != -1) {
|
|
if (sic.StartPunch.Time<ZeroTime)
|
|
sic.StartPunch.Time+=(24*3600);
|
|
|
|
sic.StartPunch.Time-=ZeroTime;
|
|
}
|
|
|
|
for (unsigned k = 0; k < sic.nPunch; k++){
|
|
if (sic.Punch[k].Code!=-1){
|
|
if (sic.Punch[k].Time<ZeroTime)
|
|
sic.Punch[k].Time+=(24*3600);
|
|
|
|
sic.Punch[k].Time-=ZeroTime;
|
|
}
|
|
}
|
|
|
|
if (sic.FinishPunch.Code!=-1){
|
|
if (sic.FinishPunch.Time<ZeroTime)
|
|
sic.FinishPunch.Time+=(24*3600);
|
|
|
|
sic.FinishPunch.Time-=ZeroTime;
|
|
}
|
|
}
|
|
|
|
int oEvent::getFirstStart(int ClassId)
|
|
{
|
|
oRunnerList::iterator it=Runners.begin();
|
|
int MinTime=3600*24;
|
|
|
|
while(it!=Runners.end()){
|
|
if (ClassId==0 || it->getClassId()==ClassId)
|
|
if (it->tStartTime<MinTime && it->tStatus==StatusOK && it->tStartTime!=0)
|
|
MinTime=it->tStartTime;
|
|
|
|
++it;
|
|
}
|
|
|
|
if (MinTime==3600*24)
|
|
MinTime=0;
|
|
|
|
return MinTime;
|
|
}
|
|
|
|
bool oEvent::exportONattResults(gdioutput &gdi, const string &file)
|
|
{
|
|
ofstream fout(file.c_str());
|
|
|
|
calculateResults(RTClassResult);
|
|
|
|
sortRunners(ClassFinishTime);
|
|
|
|
oRunnerList::iterator it;
|
|
|
|
int Id=0;
|
|
|
|
int ClassNr=1;
|
|
bool warn=false;
|
|
char bf[256];
|
|
|
|
int FirstStart=0;
|
|
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->tStatus!=0 && it->tStatus!=StatusDNS){
|
|
if (it->getClassId()!=Id){
|
|
//Next class
|
|
Id=it->getClassId();
|
|
FirstStart=getFirstStart(Id);
|
|
|
|
if (it->getClass()=="Lång")
|
|
ClassNr=1;
|
|
else if (it->getClass()=="Mellan")
|
|
ClassNr=2;
|
|
else if (it->getClass()=="Kort")
|
|
ClassNr=3;
|
|
else{
|
|
if (!warn)
|
|
gdi.alert("Varning: Funktionen anpassad för klassnamn Lång, Mellan och Kort");
|
|
warn=true;
|
|
ClassNr=Id;
|
|
}
|
|
}
|
|
|
|
fout << ClassNr << ";";
|
|
|
|
int st=it->tStartTime-FirstStart;
|
|
sprintf_s(bf, 64, "%d.%02d;", st/60, st%60);
|
|
fout << bf;
|
|
|
|
if (it->getStatus()==StatusOK){
|
|
int ft=it->FinishTime-FirstStart;
|
|
sprintf_s(bf, 64, "%d.%02d;", ft/60, ft%60);
|
|
fout << bf;
|
|
}
|
|
else fout << ";";
|
|
|
|
fout << it->getName() << ";";
|
|
fout << it->getClub() << endl;
|
|
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool oEvent::hasRank() const
|
|
{
|
|
oRunnerList::const_iterator it;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->getDCI().getInt("Rank")>0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void oEvent::setMaximalTime(const string &t)
|
|
{
|
|
getDI().setInt("MaxTime", convertAbsoluteTime(t));
|
|
}
|
|
|
|
int oEvent::getMaximalTime() const
|
|
{
|
|
return getDCI().getInt("MaxTime");
|
|
}
|
|
|
|
string oEvent::getMaximalTimeS() const
|
|
{
|
|
return formatTime(getMaximalTime());
|
|
}
|
|
|
|
|
|
bool oEvent::hasBib(bool runnerBib, bool teamBib) const
|
|
{
|
|
if (runnerBib) {
|
|
oRunnerList::const_iterator it;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (!it->getBib().empty())
|
|
return true;
|
|
}
|
|
}
|
|
if (teamBib) {
|
|
oTeamList::const_iterator it;
|
|
for (it=Teams.begin(); it != Teams.end(); ++it){
|
|
if (!it->getBib().empty())
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool oEvent::hasTeam() const
|
|
{
|
|
return Teams.size() > 0;
|
|
}
|
|
|
|
void oEvent::addBib(int ClassId, int leg, const string &firstNumber) {
|
|
if ( !classHasTeams(ClassId) ) {
|
|
sortRunners(ClassStartTimeClub);
|
|
oRunnerList::iterator it;
|
|
|
|
if (!firstNumber.empty()) {
|
|
char pattern[32];
|
|
int num = oClass::extractBibPattern(firstNumber, pattern);
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
if ( (ClassId==0 || it->getClassId()==ClassId) && it->legToRun()==leg) {
|
|
char bib[32];
|
|
sprintf_s(bib, pattern, num);
|
|
it->setBib(bib, num, true, false);
|
|
num++;
|
|
it->synchronize();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (it->isRemoved())
|
|
continue;
|
|
if (ClassId==0 || it->getClassId()==ClassId) {
|
|
it->getDI().setString("Bib", "");//Update only bib
|
|
it->synchronize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
oTeamList::iterator it;
|
|
for (it=Teams.begin(); it != Teams.end(); ++it)
|
|
it->apply(false, 0, false);
|
|
|
|
if (!firstNumber.empty()) {
|
|
// Clear out start number temporarily, to not use it for sorting
|
|
for (it=Teams.begin(); it != Teams.end(); ++it) {
|
|
if (ClassId==0 || it->getClassId()==ClassId) {
|
|
if (it->getClassRef() && it->getClassRef()->getBibMode() != BibFree) {
|
|
for (size_t i = 0; i < it->Runners.size(); i++) {
|
|
if (it->Runners[i]) {
|
|
it->Runners[i]->setStartNo(0, false);
|
|
it->Runners[i]->setBib("", 0, false, false);
|
|
}
|
|
}
|
|
}
|
|
it->setStartNo(0, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
sortTeams(ClassStartTime, 0, true); // Sort on first leg starttime and sortindex
|
|
|
|
if (!firstNumber.empty()) {
|
|
char pattern[32];
|
|
int num = oClass::extractBibPattern(firstNumber, pattern);
|
|
|
|
for (it=Teams.begin(); it != Teams.end(); ++it) {
|
|
if (ClassId==0 || it->getClassId()==ClassId) {
|
|
char bib[32];
|
|
sprintf_s(bib, pattern, num);
|
|
it->setBib(bib, num, true, false);
|
|
num++;
|
|
it->apply(true, 0, false);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for(it=Teams.begin(); it != Teams.end(); ++it){
|
|
if (ClassId==0 || it->getClassId()==ClassId) {
|
|
it->getDI().setString("Bib", ""); //Update only bib
|
|
it->apply(true, 0, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::addAutoBib() {
|
|
sortRunners(ClassStartTimeClub);
|
|
oRunnerList::iterator it;
|
|
int clsId = -1;
|
|
int bibGap = oe->getBibClassGap();
|
|
int interval = 1;
|
|
|
|
char pattern[32] = {0};
|
|
char storedPattern[32];
|
|
strcpy_s(storedPattern, "%d");
|
|
|
|
int number = 0;
|
|
|
|
|
|
for (oTeamList::iterator tit=Teams.begin(); tit != Teams.end(); ++tit) {
|
|
if (!tit->skip())
|
|
tit->apply(false, 0, false);
|
|
}
|
|
|
|
// Clear out start number temporarily, to not use it for sorting
|
|
for (oTeamList::iterator tit = Teams.begin(); tit != Teams.end(); ++tit) {
|
|
if (tit->skip())
|
|
continue;
|
|
pClass cls = tit->getClassRef();
|
|
if (cls == 0)
|
|
continue;
|
|
string bibInfo = cls->getDCI().getString("Bib");
|
|
|
|
bool teamAssign = !bibInfo.empty();
|
|
bool freeMode = cls->getBibMode()==BibFree;
|
|
if (!teamAssign && freeMode)
|
|
continue; // Manul or none
|
|
|
|
bool addBib = bibInfo != "-";
|
|
|
|
if (addBib && teamAssign)
|
|
tit->setStartNo(0, false);
|
|
|
|
if (tit->getClassRef() && tit->getClassRef()->getBibMode() != BibFree) {
|
|
for (size_t i = 0; i < tit->Runners.size(); i++) {
|
|
if (tit->Runners[i]) {
|
|
if (addBib && teamAssign)
|
|
tit->Runners[i]->setStartNo(0, false);
|
|
if (!freeMode)
|
|
tit->Runners[i]->setBib("", 0, false, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sortTeams(ClassStartTime, 0, true); // Sort on first leg starttime and sortindex
|
|
map<int, vector<pTeam> > cls2TeamList;
|
|
|
|
for (oTeamList::iterator tit = Teams.begin(); tit != Teams.end(); ++tit) {
|
|
if (tit->skip())
|
|
continue;
|
|
int clsId = tit->getClassId();
|
|
cls2TeamList[clsId].push_back(&*tit);
|
|
}
|
|
|
|
map<int, vector<pRunner> > cls2RunnerList;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip() || !it->getClassId())
|
|
continue;
|
|
int clsId = it->getClassId();
|
|
cls2RunnerList[clsId].push_back(&*it);
|
|
}
|
|
|
|
Classes.sort();
|
|
for (oClassList::iterator clsIt = Classes.begin(); clsIt != Classes.end(); ++clsIt) {
|
|
|
|
const pClass cls = &*clsIt;
|
|
clsId = cls->getId();
|
|
|
|
string bibInfo = cls->getDCI().getString("Bib");
|
|
if (bibInfo.empty()) {
|
|
// Skip class
|
|
continue;
|
|
}
|
|
else if (bibInfo == "*") {
|
|
if (number == 0)
|
|
number = 1;
|
|
else
|
|
number += bibGap;
|
|
|
|
if (pattern[0] == 0) {
|
|
strcpy_s(pattern, storedPattern);
|
|
}
|
|
}
|
|
else if (bibInfo == "-") {
|
|
if (pattern[0]) {
|
|
strcpy_s(storedPattern, pattern);
|
|
}
|
|
pattern[0] = 0; // Clear bibs in class
|
|
|
|
}
|
|
else {
|
|
number = oClass::extractBibPattern(bibInfo, pattern);
|
|
}
|
|
|
|
if (cls->getNumStages() > 1) {
|
|
vector<pTeam> &tl = cls2TeamList[clsId];
|
|
|
|
if (cls->getBibMode() == BibAdd) {
|
|
int ns = cls->getNumStages();
|
|
if (ns <= 10)
|
|
interval = 10;
|
|
else
|
|
interval = 100;
|
|
|
|
if (bibInfo == "*") {
|
|
int add = interval - number % interval;
|
|
number += add;
|
|
}
|
|
}
|
|
else {
|
|
interval = 1;
|
|
}
|
|
|
|
if (pattern[0] == 0) {
|
|
// Remove bib
|
|
for (size_t k = 0; k < tl.size(); k++) {
|
|
tl[k]->getDI().setString("Bib", ""); //Update only bib
|
|
tl[k]->apply(true, 0, false);
|
|
}
|
|
}
|
|
else {
|
|
for (size_t k = 0; k < tl.size(); k++) {
|
|
char buff[32];
|
|
sprintf_s(buff, pattern, number);
|
|
tl[k]->setBib(buff, number, true, false);
|
|
number += interval;
|
|
tl[k]->apply(true, 0, false);
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else {
|
|
interval = 1;
|
|
|
|
vector<pRunner> &rl = cls2RunnerList[clsId];
|
|
for (size_t k = 0; k < rl.size(); k++) {
|
|
if (pattern[0]) {
|
|
char buff[32];
|
|
sprintf_s(buff, pattern, number);
|
|
rl[k]->setBib(buff, number, true, false);
|
|
number += interval;
|
|
}
|
|
else {
|
|
rl[k]->getDI().setString("Bib", ""); //Update only bib
|
|
}
|
|
rl[k]->synchronize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::checkOrderIdMultipleCourses(int ClassId)
|
|
{
|
|
sortRunners(ClassStartTime);
|
|
int order=1;
|
|
oRunnerList::iterator it;
|
|
|
|
//Find first free order
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (ClassId==0 || it->getClassId()==ClassId){
|
|
it->synchronize();//Ensure we are up-to-date
|
|
order=max(order, it->StartNo);
|
|
}
|
|
}
|
|
|
|
//Assign orders
|
|
for(it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (ClassId==0 || it->getClassId()==ClassId)
|
|
if (it->StartNo==0){
|
|
it->StartNo=++order;
|
|
it->updateChanged(); //Mark as changed.
|
|
it->synchronize(); //Sync!
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::fillStatus(gdioutput &gdi, const string& id)
|
|
{
|
|
vector< pair<string, size_t> > d;
|
|
oe->fillStatus(d);
|
|
gdi.addItem(id, d);
|
|
}
|
|
|
|
const vector< pair<string, size_t> > &oEvent::fillStatus(vector< pair<string, size_t> > &out)
|
|
{
|
|
out.clear();
|
|
out.push_back(make_pair(lang.tl("-"), StatusUnknown));
|
|
out.push_back(make_pair(lang.tl("Godkänd"), StatusOK));
|
|
out.push_back(make_pair(lang.tl("Ej start"), StatusDNS));
|
|
out.push_back(make_pair(lang.tl("Felst."), StatusMP));
|
|
out.push_back(make_pair(lang.tl("Utg."), StatusDNF));
|
|
out.push_back(make_pair(lang.tl("Disk."), StatusDQ));
|
|
out.push_back(make_pair(lang.tl("Maxtid"), StatusMAX));
|
|
out.push_back(make_pair(lang.tl("Deltar ej"), StatusNotCompetiting));
|
|
return out;
|
|
}
|
|
|
|
int oEvent::getPropertyInt(const char *name, int def)
|
|
{
|
|
if (eventProperties.count(name)==1)
|
|
return atoi(eventProperties[name].c_str());
|
|
else {
|
|
setProperty(name, def);
|
|
return def;
|
|
}
|
|
}
|
|
|
|
const string &oEvent::getPropertyString(const char *name, const string &def)
|
|
{
|
|
if (eventProperties.count(name)==1)
|
|
return eventProperties[name];
|
|
else {
|
|
eventProperties[name] = def;
|
|
return eventProperties[name];
|
|
}
|
|
}
|
|
|
|
string oEvent::getPropertyStringDecrypt(const char *name, const string &def)
|
|
{
|
|
char bf[MAX_COMPUTERNAME_LENGTH + 1];
|
|
DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
|
|
GetComputerName(bf, &len);
|
|
string prop = getPropertyString(name, def);
|
|
string prop2;
|
|
int code = 0;
|
|
const int s = 337;
|
|
|
|
for (size_t j = 0; j<prop.length(); j+=2) {
|
|
for (size_t k = 0; k<len; k++)
|
|
code = code * 31 + bf[k];
|
|
unsigned int b1 = ((unsigned char *)prop.c_str())[j] - 33;
|
|
unsigned int b2 = ((unsigned char *)prop.c_str())[j+1] - 33;
|
|
unsigned int b = b1 | (b2<<4);
|
|
unsigned kk = abs(code) % s;
|
|
b = (b + s - kk) % s;
|
|
code += b%5;
|
|
prop2.push_back((unsigned char)b);
|
|
}
|
|
return prop2;
|
|
}
|
|
|
|
void oEvent::setPropertyEncrypt(const char *name, const string &prop)
|
|
{
|
|
char bf[MAX_COMPUTERNAME_LENGTH + 1];
|
|
DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
|
|
GetComputerName(bf, &len);
|
|
string prop2;
|
|
int code = 0;
|
|
const int s = 337;
|
|
|
|
for (size_t j = 0; j<prop.length(); j++) {
|
|
for (size_t k = 0; k<len; k++)
|
|
code = code * 31 + bf[k];
|
|
unsigned int b = ((unsigned char *)prop.c_str())[j];
|
|
unsigned kk = abs(code) % s;
|
|
code += b%5;
|
|
b = (b + kk) % s;
|
|
unsigned b1 = (b & 0x0F) + 33;
|
|
unsigned b2 = (b>>4) + 33;
|
|
prop2.push_back((unsigned char)b1);
|
|
prop2.push_back((unsigned char)b2);
|
|
}
|
|
|
|
setProperty(name, prop2);
|
|
}
|
|
|
|
void oEvent::setProperty(const char *name, int prop)
|
|
{
|
|
eventProperties[name]=itos(prop);
|
|
}
|
|
|
|
void oEvent::setProperty(const char *name, const string &prop)
|
|
{
|
|
eventProperties[name]=prop;
|
|
}
|
|
|
|
void oEvent::saveProperties(const char *file) {
|
|
map<string, string>::const_iterator it;
|
|
xmlparser xml(0);
|
|
xml.openOutputT(file, false, "MeOSPreference");
|
|
|
|
for (it = eventProperties.begin(); it != eventProperties.end(); ++it) {
|
|
xml.write(it->first.c_str(), it->second);
|
|
}
|
|
|
|
xml.closeOut();
|
|
}
|
|
|
|
void oEvent::loadProperties(const char *file) {
|
|
eventProperties.clear();
|
|
initProperties();
|
|
try {
|
|
xmlparser xml(0);
|
|
xml.read(file);
|
|
xmlobject xo = xml.getObject("MeOSPreference");
|
|
if (xo) {
|
|
xmlList list;
|
|
xo.getObjects(list);
|
|
for (size_t k = 0; k<list.size(); k++) {
|
|
eventProperties[list[k].getName()] = list[k].get();
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception &) {
|
|
// Failed to read. Continue.
|
|
}
|
|
}
|
|
|
|
bool compareClubClassTeamName(const oRunner &a, const oRunner &b)
|
|
{
|
|
if (a.Club==b.Club) {
|
|
if (a.Class==b.Class) {
|
|
if (a.tInTeam==b.tInTeam)
|
|
return a.tRealName<b.tRealName;
|
|
else if (a.tInTeam) {
|
|
if (b.tInTeam)
|
|
return a.tInTeam->getStartNo() < b.tInTeam->getStartNo();
|
|
else return false;
|
|
}
|
|
return b.tInTeam!=0;
|
|
}
|
|
else
|
|
return a.getClass()<b.getClass();
|
|
}
|
|
else
|
|
return a.getClub()<b.getClub();
|
|
|
|
}
|
|
|
|
void oEvent::assignCardInteractive(gdioutput &gdi, GUICALLBACK cb)
|
|
{
|
|
gdi.fillDown();
|
|
gdi.dropLine(1);
|
|
gdi.addString("", 2, "Tilldelning av hyrbrickor");
|
|
|
|
Runners.sort(compareClubClassTeamName);
|
|
|
|
oRunnerList::iterator it;
|
|
pClub lastClub=0;
|
|
|
|
int k=0;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
|
|
if (it->skip() || it->getCardNo() || it->isVacant() || it->needNoCard())
|
|
continue;
|
|
|
|
if (it->getStatus() == StatusDNS || it->getStatus() == StatusNotCompetiting)
|
|
continue;
|
|
|
|
if (it->Club!=lastClub) {
|
|
lastClub=it->Club;
|
|
gdi.dropLine(0.5);
|
|
gdi.addString("", 1, it->getClub());
|
|
}
|
|
|
|
string r;
|
|
if (it->Class)
|
|
r+=it->getClass()+", ";
|
|
|
|
if (it->tInTeam) {
|
|
char bf[1024];
|
|
sprintf_s(bf, "%d %s, ", it->tInTeam->getStartNo(),
|
|
it->tInTeam->getName().c_str());
|
|
r+=bf;
|
|
}
|
|
|
|
r+=it->getName()+":";
|
|
gdi.fillRight();
|
|
gdi.pushX();
|
|
gdi.addStringUT(0, r);
|
|
char id[24];
|
|
sprintf_s(id, "*%d", k++);
|
|
|
|
gdi.addInput(max(gdi.getCX(), 450), gdi.getCY()-4,
|
|
id, "", 10, cb).setExtra(it->getId());
|
|
|
|
gdi.popX();
|
|
gdi.dropLine(1.6);
|
|
gdi.fillDown();
|
|
}
|
|
|
|
if (k==0)
|
|
gdi.addString("", 0, "Ingen löpare saknar bricka");
|
|
}
|
|
|
|
void oEvent::calcUseStartSeconds()
|
|
{
|
|
tUseStartSeconds=false;
|
|
oRunnerList::iterator it;
|
|
for (it=Runners.begin(); it != Runners.end(); ++it)
|
|
if ( it->getStartTime()>0 &&
|
|
(it->getStartTime()+ZeroTime)%60!=0 ) {
|
|
tUseStartSeconds=true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
const string &oEvent::formatStatus(RunnerStatus status)
|
|
{
|
|
const static string stats[8]={"?", "Godkänd", "Ej start", "Felst.", "Utg.", "Disk.", "Maxtid", "Deltar ej"};
|
|
switch(status) {
|
|
case StatusOK:
|
|
return lang.tl(stats[1]);
|
|
case StatusDNS:
|
|
return lang.tl(stats[2]);
|
|
case StatusMP:
|
|
return lang.tl(stats[3]);
|
|
case StatusDNF:
|
|
return lang.tl(stats[4]);
|
|
case StatusDQ:
|
|
return lang.tl(stats[5]);
|
|
case StatusMAX:
|
|
return lang.tl(stats[6]);
|
|
case StatusNotCompetiting:
|
|
return lang.tl(stats[7]);
|
|
default:
|
|
return stats[0];
|
|
}
|
|
}
|
|
|
|
#ifndef MEOSDB
|
|
|
|
void oEvent::analyzeClassResultStatus() const
|
|
{
|
|
map<int, ClassResultInfo> res;
|
|
for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved() || !it->Class)
|
|
continue;
|
|
|
|
int id = it->Class->Id * 31 + it->tLeg;
|
|
ClassResultInfo &cri = res[id];
|
|
|
|
if (it->getStatus() == StatusUnknown) {
|
|
cri.nUnknown++;
|
|
if (it->tStartTime > 0) {
|
|
if (!it->isVacant()) {
|
|
if (cri.lastStartTime>=0)
|
|
cri.lastStartTime = max(cri.lastStartTime, it->tStartTime);
|
|
}
|
|
}
|
|
else
|
|
cri.lastStartTime = -1; // Cannot determine
|
|
}
|
|
else
|
|
cri.nFinished++;
|
|
|
|
}
|
|
|
|
for (oClassList::const_iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
if (!it->legInfo.empty()) {
|
|
it->tResultInfo.resize(it->legInfo.size());
|
|
for (size_t k = 0; k<it->legInfo.size(); k++) {
|
|
int id = it->Id * 31 + k;
|
|
it->tResultInfo[k] = res[id];
|
|
}
|
|
}
|
|
else {
|
|
it->tResultInfo.resize(1);
|
|
it->tResultInfo[0] = res[it->Id * 31];
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::generateTestCard(SICard &sic) const
|
|
{
|
|
sic.clear(0);
|
|
|
|
if (Runners.empty())
|
|
return;
|
|
|
|
analyzeClassResultStatus();
|
|
|
|
oRunnerList::const_iterator it;
|
|
|
|
int rNo = rand()%Runners.size();
|
|
|
|
it=Runners.begin();
|
|
|
|
while(rNo-->0)
|
|
++it;
|
|
|
|
oRunner *r = 0;
|
|
int cardNo = 0;
|
|
while(r==0 && it!=Runners.end()) {
|
|
cardNo = it->CardNo;
|
|
if (cardNo == 0 && it->tParentRunner)
|
|
cardNo = it->tParentRunner->CardNo;
|
|
|
|
if (it->Class && it->tLeg>0) {
|
|
StartTypes st = it->Class->getStartType(it->tLeg);
|
|
if (st == STHunting) {
|
|
if (it->Class->tResultInfo[it->tLeg-1].nUnknown > 0)
|
|
cardNo = 0; // Wait with this leg
|
|
}
|
|
}
|
|
|
|
// Make sure teams start in right order
|
|
if (it->tInTeam && it->tLeg>0) {
|
|
if (it->Class) {
|
|
StartTypes st = it->Class->getStartType(it->tLeg);
|
|
if (st != STDrawn && st != STTime) {
|
|
pRunner prev = it->tInTeam->Runners[it->tLeg - 1];
|
|
if (prev && prev->getStatus() == StatusUnknown)
|
|
cardNo = 0; // Wait with this runner
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cardNo && !it->Card) {
|
|
// For team runners, we require start time to get right order
|
|
if (!it->tInTeam || it->tStartTime>0)
|
|
r=pRunner(&*it);
|
|
}
|
|
++it;
|
|
}
|
|
--it;
|
|
while(r==0 && it!=Runners.begin()) {
|
|
cardNo = it->CardNo;
|
|
if (cardNo == 0 && it->tParentRunner)
|
|
cardNo = it->tParentRunner->CardNo;
|
|
|
|
if (it->Class && it->tLeg>0) {
|
|
StartTypes st = it->Class->getStartType(it->tLeg);
|
|
if (st == STHunting) {
|
|
if (it->Class->tResultInfo[it->tLeg-1].nUnknown > 0)
|
|
cardNo = 0; // Wait with this leg
|
|
}
|
|
}
|
|
|
|
// Make sure teams start in right order
|
|
if (it->tInTeam && it->tLeg>0) {
|
|
if (it->Class) {
|
|
StartTypes st = it->Class->getStartType(it->tLeg);
|
|
if (st != STDrawn && st != STTime) {
|
|
pRunner prev = it->tInTeam->Runners[it->tLeg - 1];
|
|
if (prev && prev->getStatus() == StatusUnknown)
|
|
cardNo = 0; // Wait with this runner
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cardNo && !it->Card) {
|
|
// For team runners, we require start time to get right order
|
|
if (!it->tInTeam || it->tStartTime>0) {
|
|
r=pRunner(&*it);
|
|
}
|
|
}
|
|
--it;
|
|
}
|
|
|
|
if (r) {
|
|
r->synchronize();
|
|
pCourse pc=r->getCourse(false);
|
|
|
|
if (!pc) {
|
|
pClass cls = r->Class;
|
|
if (cls) {
|
|
pc = const_cast<oEvent *>(this)->generateTestCourse(rand()%15+7);
|
|
pc->synchronize();
|
|
cls->setCourse(pc);
|
|
cls->synchronize();
|
|
}
|
|
}
|
|
|
|
if (pc) {
|
|
sic.CardNumber = cardNo;
|
|
|
|
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;
|
|
else if (rand()%20 == 3)
|
|
tomiss *= rand()%3;
|
|
|
|
int f = sic.FinishPunch.Time = s+(30+pc->getLength()/200)*60+ rand()%(60*10) + tomiss;
|
|
|
|
if (rand()%40==0 || r->tStartTime>0)
|
|
sic.StartPunch.Code=-1;
|
|
|
|
if (rand()%50==31)
|
|
sic.FinishPunch.Code=-1;
|
|
|
|
if (rand()%70==31)
|
|
sic.CardNumber++;
|
|
|
|
sic.nPunch=0;
|
|
double dt=1./double(pc->nControls+1);
|
|
|
|
int missed = 0;
|
|
|
|
for(int k=0;k<pc->nControls;k++) {
|
|
if (rand()%130!=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)
|
|
missed += ( (tomiss - missed) * (rand()%4 + 1))/6;
|
|
else if (left == 1)
|
|
missed = tomiss;
|
|
}
|
|
|
|
sic.Punch[sic.nPunch].Time=int((f-tomiss)*cc+s*(1.-cc)) + missed;
|
|
sic.nPunch++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pCourse oEvent::generateTestCourse(int nCtrl)
|
|
{
|
|
char bf[64];
|
|
static int sk=0;
|
|
sprintf_s(bf, lang.tl("Bana %d").c_str(), ++sk);
|
|
pCourse pc=addCourse(bf, 4000+(rand()%1000)*10);
|
|
|
|
int i=0;
|
|
for (;i<nCtrl/3;i++)
|
|
pc->addControl(rand()%(99-32)+32);
|
|
|
|
i++;
|
|
pc->addControl(50)->setName("Radio 1");
|
|
|
|
for (;i<(2*nCtrl)/3;i++)
|
|
pc->addControl(rand()%(99-32)+32);
|
|
|
|
i++;
|
|
pc->addControl(150)->setName("Radio 2");
|
|
|
|
for (;i<nCtrl-1;i++)
|
|
pc->addControl(rand()%(99-32)+32);
|
|
pc->addControl(100)->setName("Förvarning");
|
|
|
|
return pc;
|
|
}
|
|
|
|
|
|
pClass oEvent::generateTestClass(int nlegs, int nrunners,
|
|
char *name, const string &start)
|
|
{
|
|
pClass cls=addClass(name);
|
|
|
|
if (nlegs==1 && nrunners==1) {
|
|
int nCtrl=rand()%15+5;
|
|
if (rand()%10==1)
|
|
nCtrl+=rand()%40;
|
|
cls->setCourse(generateTestCourse(nCtrl));
|
|
}
|
|
else if (nlegs==1 && nrunners==2) {
|
|
setupRelay(*cls, PPatrol, 2, start);
|
|
int nCtrl=rand()%15+10;
|
|
pCourse pc=generateTestCourse(nCtrl);
|
|
cls->addStageCourse(0, pc->getId());
|
|
cls->addStageCourse(1, pc->getId());
|
|
}
|
|
else if (nlegs>1 && nrunners==2) {
|
|
setupRelay(*cls, PTwinRelay, nlegs, start);
|
|
int nCtrl=rand()%8+10;
|
|
int cid[64];
|
|
for (int k=0;k<nlegs;k++)
|
|
cid[k]=generateTestCourse(nCtrl)->getId();
|
|
|
|
for (int k=0;k<nlegs;k++)
|
|
for (int j=0;j<nlegs;j++)
|
|
cls->addStageCourse(k, cid[(k+j)%nlegs]);
|
|
}
|
|
else if (nlegs>1 && nrunners==nlegs) {
|
|
setupRelay(*cls, PRelay, nlegs, start);
|
|
int nCtrl=rand()%8+10;
|
|
int cid[64];
|
|
for (int k=0;k<nlegs;k++)
|
|
cid[k]=generateTestCourse(nCtrl)->getId();
|
|
|
|
for (int k=0;k<nlegs;k++)
|
|
for (int j=0;j<nlegs;j++)
|
|
cls->addStageCourse(k, cid[(k+j)%nlegs]);
|
|
}
|
|
else if (nlegs>1 && nrunners==1) {
|
|
setupRelay(*cls, PHunting, 2, start);
|
|
cls->addStageCourse(0, generateTestCourse(rand()%8+10)->getId());
|
|
cls->addStageCourse(1, generateTestCourse(rand()%8+10)->getId());
|
|
}
|
|
return cls;
|
|
}
|
|
|
|
|
|
void oEvent::generateTestCompetition(int nClasses, int nRunners,
|
|
bool generateTeams) {
|
|
if (nClasses > 0) {
|
|
oe->newCompetition("!TESTTÄVLING");
|
|
oe->setZeroTime("05:00:00");
|
|
oe->getMeOSFeatures().useAll(*oe);
|
|
}
|
|
bool useSOFTMethod = true;
|
|
vector<string> gname;
|
|
//gname.reserve(RunnerDatabase.size());
|
|
vector<string> fname;
|
|
//fname.reserve(RunnerDatabase.size());
|
|
|
|
runnerDB->getAllNames(gname, fname);
|
|
|
|
if (fname.empty())
|
|
fname.push_back("Foo");
|
|
|
|
if (gname.empty())
|
|
gname.push_back("Bar");
|
|
|
|
/* oRunnerList::iterator it;
|
|
for(it=RunnerDatabase.begin(); it!=RunnerDatabase.end(); ++it){
|
|
if (!it->getGivenName().empty())
|
|
gname.push_back(it->getGivenName());
|
|
|
|
if (!it->getFamilyName().empty())
|
|
fname.push_back(it->getFamilyName());
|
|
}
|
|
*/
|
|
int nClubs=30;
|
|
char bf[128];
|
|
int startno=1;
|
|
const vector<oDBClubEntry> &oc = runnerDB->getClubDB();
|
|
for(int k=0;k<nClubs;k++) {
|
|
if (oc.empty()) {
|
|
sprintf_s(bf, "Klubb %d", k);
|
|
addClub(bf, k+1);
|
|
}
|
|
else {
|
|
addClub(oc[(k*13)%oc.size()].getName(), k+1);
|
|
}
|
|
}
|
|
|
|
int now=getRelativeTime(getCurrentTimeS());
|
|
string start=getAbsTime(now+60*3-(now%60));
|
|
|
|
for (int k=0;k<nClasses;k++) {
|
|
pClass cls=0;
|
|
|
|
if (!generateTeams) {
|
|
int age=0;
|
|
if (k<7)
|
|
age=k+10;
|
|
else if (k==7)
|
|
age=18;
|
|
else if (k==8)
|
|
age=20;
|
|
else if (k==9)
|
|
age=21;
|
|
else
|
|
age=30+(k-9)*5;
|
|
|
|
sprintf_s(bf, "HD %d", age);
|
|
cls=generateTestClass(1,1, bf, "");
|
|
}
|
|
else {
|
|
sprintf_s(bf, "Klass %d", k);
|
|
int nleg=k%5+1;
|
|
int nrunner=k%3+1;
|
|
nrunner = nrunner == 3 ? nleg:nrunner;
|
|
|
|
nleg=3;
|
|
nrunner=3;
|
|
cls=generateTestClass(nleg, nrunner, bf, start);
|
|
}
|
|
}
|
|
|
|
nClasses = Classes.size();
|
|
int k = 0;
|
|
|
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it, ++k) {
|
|
pClass cls = &*it;
|
|
int classesLeft=(nClasses-k);
|
|
int nRInClass=nRunners/classesLeft;
|
|
|
|
if (classesLeft>2 && nRInClass>3)
|
|
nRInClass+=int(nRInClass*0.7)-rand()%int(nRInClass*1.5);
|
|
|
|
if (cls->getNumDistinctRunners()==1) {
|
|
for (int i=0;i<nRInClass;i++) {
|
|
pRunner r=addRunner(gname[rand()%gname.size()]+" "+fname[rand()%fname.size()],
|
|
rand()%nClubs+1, cls->getId(), 0, 0, true);
|
|
|
|
r->setStartNo(startno++, false);
|
|
r->setCardNo(500001+Runners.size()*97+rand()%97, false);
|
|
r->apply(false, 0, false);
|
|
}
|
|
nRunners-=nRInClass;
|
|
if (k%5!=5) {
|
|
vector<ClassDrawSpecification> spec;
|
|
spec.push_back(ClassDrawSpecification(cls->getId(), 0, getRelativeTime(start), 10, 3));
|
|
drawList(spec, useSOFTMethod, 1, drawAll);
|
|
}
|
|
else
|
|
cls->Name += " Öppen";
|
|
}
|
|
else {
|
|
int dr=cls->getNumDistinctRunners();
|
|
for (int i=0;i<nRInClass;i++) {
|
|
pTeam t=addTeam("Lag " + fname[rand()%fname.size()], rand()%nClubs+1, cls->getId());
|
|
t->setStartNo(startno++, false);
|
|
|
|
for (int j=0;j<dr;j++) {
|
|
pRunner r=addRunner(gname[rand()%gname.size()]+" "+fname[rand()%fname.size()], 0, 0, 0, 0, true);
|
|
r->setCardNo(500001+Runners.size()*97+rand()%97, false);
|
|
t->setRunner(j, r, false);
|
|
}
|
|
}
|
|
nRunners-=nRInClass;
|
|
|
|
if ( cls->getStartType(0)==STDrawn ) {
|
|
vector<ClassDrawSpecification> spec;
|
|
spec.push_back(ClassDrawSpecification(cls->getId(), 0, getRelativeTime(start), 20, 3));
|
|
drawList(spec, useSOFTMethod, 1, drawAll);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void oEvent::getFreeImporter(oFreeImport &fi)
|
|
{
|
|
if (!fi.isLoaded())
|
|
fi.load();
|
|
|
|
fi.init(Runners, Clubs, Classes);
|
|
}
|
|
|
|
|
|
void oEvent::fillFees(gdioutput &gdi, const string &name, bool withAuto) const {
|
|
gdi.clearList(name);
|
|
|
|
set<int> fees;
|
|
|
|
int f;
|
|
for (oClassList::const_iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
f = it->getDCI().getInt("ClassFee");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
|
|
f = it->getDCI().getInt("ClassFeeRed");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
|
|
if (withAuto) {
|
|
f = it->getDCI().getInt("HighClassFee");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
|
|
f = it->getDCI().getInt("HighClassFeeRed");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
}
|
|
}
|
|
|
|
f = getDCI().getInt("EliteFee");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
|
|
f = getDCI().getInt("EntryFee");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
|
|
f = getDCI().getInt("YouthFee");
|
|
if (f > 0)
|
|
fees.insert(f);
|
|
|
|
vector< pair<string, size_t> > ff;
|
|
if (withAuto)
|
|
ff.push_back(make_pair(lang.tl("Från klassen"), -1));
|
|
for (set<int>::iterator it = fees.begin(); it != fees.end(); ++it)
|
|
ff.push_back(make_pair(formatCurrency(*it), *it));
|
|
|
|
gdi.addItem(name, ff);
|
|
}
|
|
|
|
void oEvent::fillLegNumbers(const set<int> &cls,
|
|
bool isTeamList,
|
|
bool includeSubLegs,
|
|
vector< pair<string, size_t> > &out) {
|
|
oClassList::iterator it;
|
|
synchronizeList(oLClassId);
|
|
|
|
out.clear();
|
|
set< pair<int, int> > legs;
|
|
|
|
for (it=Classes.begin(); it != Classes.end(); ++it) {
|
|
if (!it->Removed && (cls.empty() || cls.count(it->getId()))) {
|
|
if (it->getNumStages() == 0)
|
|
continue;
|
|
|
|
for (size_t j = 0; j < it->getNumStages(); j++) {
|
|
int number, order;
|
|
if (it->splitLegNumberParallel(j, number, order)) {
|
|
if (order == 0)
|
|
legs.insert( make_pair(number, 0) );
|
|
else {
|
|
if (it->isOptional(j))
|
|
continue;
|
|
|
|
if (order == 1)
|
|
legs.insert( make_pair(number, 1000));
|
|
legs.insert(make_pair(number, 1000+order));
|
|
}
|
|
}
|
|
else {
|
|
legs.insert( make_pair(number, 0) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out.reserve(legs.size() + 1);
|
|
for (set< pair<int, int> >::const_iterator it = legs.begin(); it != legs.end(); ++it) {
|
|
if (it->second == 0) {
|
|
out.push_back( make_pair(lang.tl("Sträcka X#" + itos(it->first + 1)), it->first));
|
|
}
|
|
}
|
|
if (includeSubLegs) {
|
|
for (set< pair<int, int> >::const_iterator it = legs.begin(); it != legs.end(); ++it) {
|
|
if (it->second >= 1000) {
|
|
int leg = it->first;
|
|
int sub = it->second - 1000;
|
|
char bf[64];
|
|
char symb = 'a' + sub;
|
|
sprintf_s(bf, "Sträcka X#%d%c", leg+1, symb);
|
|
out.push_back( make_pair(lang.tl(bf), (leg + 1) * 10000 + sub));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isTeamList)
|
|
out.push_back(make_pair(lang.tl("Sista sträckan"), 1000));
|
|
else
|
|
out.push_back(make_pair(lang.tl("Alla sträckor"), 1000));
|
|
}
|
|
|
|
void oEvent::generateTableData(const string &tname, Table &table, TableUpdateInfo &tui)
|
|
{
|
|
if (tname == "runners") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
pRunner r = tui.doAdd ? addRunner(getAutoRunnerName(),0,0,0,0,false) : pRunner(tui.object);
|
|
generateRunnerTableData(table, r);
|
|
return;
|
|
}
|
|
else if (tname == "classes") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
pClass c = tui.doAdd ? addClass(getAutoClassName()) : pClass(tui.object);
|
|
generateClassTableData(table, c);
|
|
return;
|
|
}
|
|
else if (tname == "clubs") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
pClub c = tui.doAdd ? addClub("Club", 0) : pClub(tui.object);
|
|
generateClubTableData(table, c);
|
|
return;
|
|
}
|
|
else if (tname == "teams") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
pTeam t = tui.doAdd ? addTeam(getAutoTeamName()) : pTeam(tui.object);
|
|
generateTeamTableData(table, t);
|
|
return;
|
|
}
|
|
else if (tname == "cards") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
generateCardTableData(table, pCard(tui.object));
|
|
return;
|
|
}
|
|
else if (tname == "controls") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
generateControlTableData(table, pControl(tui.object));
|
|
return;
|
|
}
|
|
else if (tname == "punches") {
|
|
if (tui.doRefresh && !tui.doAdd)
|
|
return;
|
|
|
|
pFreePunch c = tui.doAdd ? addFreePunch(0,0,0, false) : pFreePunch(tui.object);
|
|
generatePunchTableData(table, c);
|
|
return;
|
|
}
|
|
else if (tname == "runnerdb") {
|
|
if (tui.doAdd || !tui.doRefresh) {
|
|
oDBRunnerEntry *entry = tui.doAdd ? getRunnerDatabase().addRunner() : (oDBRunnerEntry *)(tui.object);
|
|
getRunnerDatabase().generateRunnerTableData(table, entry);
|
|
}
|
|
|
|
if (tui.doRefresh)
|
|
getRunnerDatabase().refreshRunnerTableData(table);
|
|
|
|
return;
|
|
}
|
|
else if (tname == "clubdb") {
|
|
if (tui.doAdd || !tui.doRefresh) {
|
|
pClub c = tui.doAdd ? getRunnerDatabase().addClub() : pClub(tui.object);
|
|
getRunnerDatabase().generateClubTableData(table, c);
|
|
}
|
|
|
|
if (tui.doRefresh) {
|
|
getRunnerDatabase().refreshClubTableData(table);
|
|
}
|
|
return;
|
|
}
|
|
throw std::exception("Wrong table name");
|
|
}
|
|
|
|
void oEvent::applyEventFees(bool updateClassFromEvent,
|
|
bool updateFees, bool updateCardFees,
|
|
const set<int> &classFilter) {
|
|
synchronizeList(oLClassId, true, false);
|
|
synchronizeList(oLRunnerId, false, true);
|
|
bool allClass = classFilter.empty();
|
|
|
|
if (updateClassFromEvent) {
|
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
if (allClass || classFilter.count(it->getId())) {
|
|
it->addClassDefaultFee(true);
|
|
it->synchronize(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateFees) {
|
|
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip())
|
|
continue;
|
|
|
|
if (allClass || classFilter.count(it->getClassId())) {
|
|
it->addClassDefaultFee(true);
|
|
it->synchronize(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateCardFees) {
|
|
int cf = getDCI().getInt("CardFee");
|
|
|
|
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip())
|
|
continue;
|
|
|
|
if (it->getDI().getInt("CardFee") != 0) {
|
|
it->getDI().setInt("CardFee", cf);
|
|
it->synchronize(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef MEOSDB
|
|
void hideTabs();
|
|
void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker,
|
|
bool skipEconomy, bool skipLists, bool skipRunners, bool skipControls);
|
|
|
|
void oEvent::updateTabs(bool force, bool hide) const
|
|
{
|
|
bool hasTeam = !Teams.empty();
|
|
|
|
for (oClassList::const_iterator it = Classes.begin();
|
|
!hasTeam && it!=Classes.end(); ++it) {
|
|
if (it->getNumStages()>1)
|
|
hasTeam = true;
|
|
}
|
|
|
|
bool hasRunner = !Runners.empty() || !Classes.empty();
|
|
bool hasLists = !empty();
|
|
|
|
if (hide || isReadOnly())
|
|
hideTabs();
|
|
else
|
|
createTabs(force, empty(), !hasTeam, !getMeOSFeatures().hasFeature(MeOSFeatures::Speaker),
|
|
!(getMeOSFeatures().hasFeature(MeOSFeatures::Economy)
|
|
|| getMeOSFeatures().hasFeature(MeOSFeatures::EditClub)),
|
|
!hasLists, !hasRunner, Controls.empty());
|
|
}
|
|
|
|
#else
|
|
void oEvent::updateTabs(bool force) const
|
|
{
|
|
}
|
|
#endif
|
|
|
|
bool oEvent::useRunnerDb() const
|
|
{
|
|
return getMeOSFeatures().hasFeature(MeOSFeatures::RunnerDb);
|
|
//return getDCI().getInt("SkipRunnerDb")==0;
|
|
}
|
|
/*
|
|
void oEvent::useRunnerDb(bool use) {
|
|
getDI().setInt("SkipRunnerDb", use ? 0 : 1);
|
|
}*/
|
|
|
|
bool oEvent::hasMultiRunner() const {
|
|
for (oClassList::const_iterator it = Classes.begin(); it!=Classes.end(); ++it) {
|
|
if (it->hasMultiCourse() && it->getNumDistinctRunners() != it->getNumStages())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Return false if card is not used */
|
|
bool oEvent::checkCardUsed(gdioutput &gdi, oRunner &runnerToAssignCard, int cardNo) {
|
|
synchronizeList(oLRunnerId);
|
|
//pRunner pold=oe->getRunnerByCardNo(cardNo, 0, true, true);
|
|
char bf[1024];
|
|
pRunner pold = 0;
|
|
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip())
|
|
continue;
|
|
if (!runnerToAssignCard.canShareCard(&*it, cardNo)) {
|
|
pold = &*it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pold) {
|
|
sprintf_s(bf, ("#" + lang.tl("Bricka %d används redan av %s och kan inte tilldelas.")).c_str(),
|
|
cardNo, pold->getCompleteIdentification().c_str());
|
|
gdi.alert(bf);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void oEvent::removeVacanies(int classId) {
|
|
oRunnerList::iterator it;
|
|
vector<int> toRemove;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip() || !it->isVacant())
|
|
continue;
|
|
|
|
if (classId!=0 && it->getClassId()!=classId)
|
|
continue;
|
|
|
|
if (!isRunnerUsed(it->Id))
|
|
toRemove.push_back(it->Id);
|
|
}
|
|
|
|
removeRunner(toRemove);
|
|
}
|
|
|
|
void oEvent::sanityCheck(gdioutput &gdi, bool expectResult, int onlyThisClass) {
|
|
bool hasResult = false;
|
|
bool warnNoName = false;
|
|
bool warnNoClass = false;
|
|
bool warnNoTeam = false;
|
|
bool warnNoPatrol = false;
|
|
bool warnIndividualTeam = false;
|
|
|
|
for (oRunnerList::iterator it = Runners.begin(); it!=Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
if (onlyThisClass > 0 && it->getClassId() != onlyThisClass)
|
|
continue;
|
|
if (it->sName.empty()) {
|
|
if (!warnNoName) {
|
|
warnNoName = true;
|
|
gdi.alert("Varning: deltagare med blankt namn påträffad. MeOS "
|
|
"kräver att alla deltagare har ett namn, och tilldelar namnet 'N.N.'");
|
|
}
|
|
it->setName("N.N.", false);
|
|
it->synchronize();
|
|
}
|
|
|
|
if (!it->Class) {
|
|
if (!warnNoClass) {
|
|
gdi.alert("Deltagaren 'X' saknar klass.#" + it->getName());
|
|
warnNoClass = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!it->tInTeam) {
|
|
ClassType type = it->Class->getClassType();
|
|
if (type == oClassIndividRelay) {
|
|
it->setClassId(it->Class->getId(), true);
|
|
it->synchronize();
|
|
}
|
|
else if (type == oClassRelay) {
|
|
if (!warnNoTeam) {
|
|
gdi.alert("Deltagaren 'X' deltar i stafettklassen 'Y' men saknar lag. Klassens start- "
|
|
"och resultatlistor kan därmed bli felaktiga.#" + it->getName() +
|
|
"#" + it->getClass());
|
|
warnNoTeam = true;
|
|
}
|
|
}
|
|
else if (type == oClassPatrol) {
|
|
if (!warnNoPatrol) {
|
|
gdi.alert("Deltagaren 'X' deltar i patrullklassen 'Y' men saknar patrull. Klassens start- "
|
|
"och resultatlistor kan därmed bli felaktiga.#" + it->getName() +
|
|
+ "#" + it->getClass());
|
|
warnNoPatrol = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (it->getFinishTime()>0)
|
|
hasResult = true;
|
|
}
|
|
|
|
for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
if (onlyThisClass > 0 && it->getClassId() != onlyThisClass)
|
|
continue;
|
|
|
|
if (it->sName.empty()) {
|
|
if (!warnNoName) {
|
|
warnNoName = true;
|
|
gdi.alert("Varning: lag utan namn påträffat. "
|
|
"MeOS kräver att alla lag har ett namn, och tilldelar namnet 'N.N.'");
|
|
}
|
|
it->setName("N.N.", false);
|
|
it->synchronize();
|
|
}
|
|
|
|
if (!it->Class) {
|
|
if (!warnNoClass) {
|
|
gdi.alert("Laget 'X' saknar klass.#" + it->getName());
|
|
warnNoClass = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
ClassType type = it->Class->getClassType();
|
|
if (type == oClassIndividual) {
|
|
if (!warnIndividualTeam) {
|
|
gdi.alert("Laget 'X' deltar i individuella klassen 'Y'. Klassens start- och resultatlistor "
|
|
"kan därmed bli felaktiga.#" + it->getName() + "#" + it->getClass());
|
|
warnIndividualTeam = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (expectResult && !hasResult)
|
|
gdi.alert("Tävlingen innehåller inga resultat.");
|
|
|
|
|
|
bool warnBadStart = false;
|
|
|
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
if (it->getClassStatus() != oClass::Normal)
|
|
continue;
|
|
|
|
if (onlyThisClass > 0 && it->getId() != onlyThisClass)
|
|
continue;
|
|
|
|
if (it->hasMultiCourse()) {
|
|
for (unsigned k=0;k<it->getNumStages(); k++) {
|
|
StartTypes st = it->getStartType(k);
|
|
LegTypes lt = it->getLegType(k);
|
|
if (k==0 && (st == STChange || st == STHunting) && !warnBadStart) {
|
|
warnBadStart = true;
|
|
gdi.alert("Klassen 'X' har jaktstart/växling på första sträckan.#" + it->getName());
|
|
}
|
|
if (st == STTime && it->getStartData(k)<=0 && !warnBadStart &&
|
|
(lt == LTNormal || lt == LTSum)) {
|
|
warnBadStart = true;
|
|
gdi.alert("Ogiltig starttid i 'X' på sträcka Y.#" + it->getName() + "#" + itos(k+1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HWND oEvent::hWnd() const
|
|
{
|
|
return gdibase.getHWND();
|
|
}
|
|
|
|
oTimeLine::~oTimeLine() {
|
|
|
|
}
|
|
|
|
void oEvent::remove() {
|
|
if (isClient())
|
|
dropDatabase();
|
|
else
|
|
deleteCompetition();
|
|
|
|
clearListedCmp();
|
|
newCompetition("");
|
|
}
|
|
|
|
bool oEvent::canRemove() const {
|
|
return true;
|
|
}
|
|
|
|
string oEvent::formatCurrency(int c, bool includeSymbol) const {
|
|
if (tCurrencyFactor == 1)
|
|
if (!includeSymbol)
|
|
return itos(c);
|
|
else if (tCurrencyPreSymbol)
|
|
return tCurrencySymbol + itos(c);
|
|
else
|
|
return itos(c) + tCurrencySymbol;
|
|
else {
|
|
char bf[32];
|
|
if (includeSymbol) {
|
|
sprintf_s(bf, 32, "%d%s%02d", c/tCurrencyFactor,
|
|
tCurrencySeparator.c_str(), c%tCurrencyFactor);
|
|
|
|
if (tCurrencyPreSymbol)
|
|
return tCurrencySymbol + bf;
|
|
else
|
|
return bf + tCurrencySymbol;
|
|
}
|
|
else {
|
|
sprintf_s(bf, 32, "%d.%02d", c/tCurrencyFactor, c%tCurrencyFactor);
|
|
return bf;
|
|
}
|
|
}
|
|
}
|
|
|
|
int oEvent::interpretCurrency(const string &c) const {
|
|
if (tCurrencyFactor == 1 && tCurrencyPreSymbol == false)
|
|
return atoi(c.c_str());
|
|
|
|
size_t s = 0;
|
|
while (s < c.length() && (c[s]<'0' || c[s]>'9'))
|
|
s++;
|
|
|
|
string cc = c.substr(s);
|
|
|
|
for (size_t k = 0; k<cc.length(); k++) {
|
|
if (cc[k] == ',' || cc[k] == tCurrencySeparator[0])
|
|
cc[k] = '.';
|
|
}
|
|
|
|
return int(atof(cc.c_str())*tCurrencyFactor);
|
|
}
|
|
|
|
int oEvent::interpretCurrency(double val, const string &cur) {
|
|
if (_stricmp("sek", cur.c_str()) == 0)
|
|
setCurrency(1, "kr", ",", false);
|
|
else if (_stricmp("eur", cur.c_str()) == 0)
|
|
setCurrency(100, "€", ".", false);
|
|
|
|
return int(floor(val * tCurrencyFactor+0.5));
|
|
}
|
|
|
|
void oEvent::setCurrency(int factor, const string &symbol, const string &separator, bool preSymbol) {
|
|
if (factor == -1) {
|
|
// Load from data
|
|
int cf = getDCI().getInt("CurrencyFactor");
|
|
if (cf != 0)
|
|
tCurrencyFactor = cf;
|
|
|
|
string cs = getDCI().getString("CurrencySymbol");
|
|
if (!cs.empty())
|
|
tCurrencySymbol = cs;
|
|
|
|
cs = getDCI().getString("CurrencySeparator");
|
|
if (!cs.empty())
|
|
tCurrencySeparator = cs;
|
|
|
|
int ps = getDCI().getInt("CurrencyPreSymbol");
|
|
tCurrencyPreSymbol = (ps != 0);
|
|
|
|
if (tCurrencySymbol.size() > 0) {
|
|
if (tCurrencyPreSymbol) {
|
|
char end = *tCurrencySymbol.rbegin();
|
|
if ((end>='a' && end <='z') || end>='A' && end <='Z')
|
|
tCurrencySymbol += " ";
|
|
}
|
|
else {
|
|
char end = *tCurrencySymbol.begin();
|
|
if ((end>='a' && end <='z') || end>='A' && end <='Z')
|
|
tCurrencySymbol = " " + tCurrencySymbol;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
tCurrencyFactor = factor;
|
|
tCurrencySymbol = symbol;
|
|
tCurrencySeparator = separator;
|
|
tCurrencyPreSymbol = preSymbol;
|
|
getDI().setString("CurrencySymbol", symbol);
|
|
getDI().setInt("CurrencyFactor", factor);
|
|
getDI().setString("CurrencySeparator", separator);
|
|
getDI().setInt("CurrencyPreSymbol", preSymbol ? 1 : 0);
|
|
}
|
|
}
|
|
string oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes,
|
|
bool cloneCourses, bool cloneResult, bool addToDate) {
|
|
|
|
if (cloneResult) {
|
|
cloneTimes = true;
|
|
cloneCourses = true;
|
|
}
|
|
if (cloneTimes)
|
|
cloneRunners = true;
|
|
|
|
oEvent ce(gdibase);
|
|
ce.newCompetition(Name);
|
|
ce.ZeroTime = ZeroTime;
|
|
ce.Date = Date;
|
|
|
|
if (addToDate) {
|
|
SYSTEMTIME st;
|
|
convertDateYMS(Date, st, false);
|
|
__int64 absD = SystemTimeToInt64Second(st);
|
|
absD += 3600*24;
|
|
ce.Date = convertSystemDate(Int64SecondToSystemTime(absD));
|
|
}
|
|
int len = Name.length();
|
|
if (len > 2 && isdigit(Name[len-1]) && !isdigit(Name[len-2])) {
|
|
++ce.Name[len-1]; // E1 -> E2
|
|
}
|
|
else
|
|
ce.Name += " E2";
|
|
|
|
memcpy(ce.oData, oData, sizeof(oData));
|
|
|
|
for (oClubList::iterator it = Clubs.begin(); it != Clubs.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
pClub pc = ce.addClub(it->name, it->Id);
|
|
memcpy(pc->oData, it->oData, sizeof(pc->oData));
|
|
}
|
|
|
|
if (cloneCourses) {
|
|
for (oControlList::iterator it = Controls.begin(); it != Controls.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
pControl pc = ce.addControl(it->Id, 100, it->Name);
|
|
pc->setNumbers(it->codeNumbers());
|
|
pc->Status = it->Status;
|
|
memcpy(pc->oData, it->oData, sizeof(pc->oData));
|
|
}
|
|
|
|
for (oCourseList::iterator it = Courses.begin(); it != Courses.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
pCourse pc = ce.addCourse(it->Name, it->Length, it->Id);
|
|
pc->importControls(it->getControls(), false);
|
|
pc->legLengths = it->legLengths;
|
|
memcpy(pc->oData, it->oData, sizeof(pc->oData));
|
|
}
|
|
}
|
|
|
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
pClass pc = ce.addClass(it->Name, 0, it->Id);
|
|
memcpy(pc->oData, it->oData, sizeof(pc->oData));
|
|
pc->setNumStages(it->getNumStages());
|
|
pc->legInfo = it->legInfo;
|
|
|
|
if (cloneCourses) {
|
|
pc->Course = ce.getCourse(it->getCourseId());
|
|
pc->MultiCourse = it->MultiCourse; // Points to wrong competition, but valid for now...
|
|
}
|
|
}
|
|
|
|
if (cloneRunners) {
|
|
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
oRunner r(&ce, it->Id);
|
|
r.sName = it->sName;
|
|
oRunner::getRealName(r.sName, r.tRealName);
|
|
r.StartNo = it->StartNo;
|
|
r.CardNo = it->CardNo;
|
|
r.Club = ce.getClub(it->getClubId());
|
|
r.Class = ce.getClass(it->getClassId());
|
|
|
|
if (cloneCourses)
|
|
r.Course = ce.getCourse(it->getCourseId());
|
|
|
|
pRunner pr = ce.addRunner(r, false);
|
|
|
|
pr->decodeMultiR(it->codeMultiR());
|
|
memcpy(pr->oData, it->oData, sizeof(pr->oData));
|
|
|
|
if (cloneTimes) {
|
|
pr->startTime = it->startTime;
|
|
}
|
|
|
|
if (cloneResult) {
|
|
if (it->Card) {
|
|
pr->Card = ce.addCard(*it->Card);
|
|
pr->Card->tOwner = pr;
|
|
}
|
|
pr->FinishTime = it->FinishTime;
|
|
pr->status = it->status;
|
|
}
|
|
}
|
|
|
|
for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) {
|
|
if (it->skip())
|
|
continue;
|
|
|
|
oTeam t(&ce, it->Id);
|
|
|
|
t.sName = it->sName;
|
|
t.StartNo = it->StartNo;
|
|
t.Club = ce.getClub(it->getClubId());
|
|
t.Class = ce.getClass(it->getClassId());
|
|
|
|
if (cloneTimes)
|
|
t.startTime = it->startTime;
|
|
|
|
pTeam pt = ce.addTeam(t, false);
|
|
memcpy(pt->oData, it->oData, sizeof(pt->oData));
|
|
|
|
pt->Runners.resize(it->Runners.size());
|
|
for (size_t k = 0; k<it->Runners.size(); k++) {
|
|
int id = it->Runners[k] ? it->Runners[k]->Id : 0;
|
|
if (id)
|
|
pt->Runners[k] = ce.getRunner(id, 0);
|
|
}
|
|
|
|
t.apply(false, 0, false);
|
|
}
|
|
|
|
for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) {
|
|
it->createMultiRunner(false, false);
|
|
}
|
|
}
|
|
|
|
vector<pRunner> changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign;
|
|
set<int> dummy;
|
|
transferResult(ce, dummy, TransferAnyway, false, changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign);
|
|
|
|
vector<pTeam> newEntriesT, notTransferedT, noAssignT;
|
|
transferResult(ce, TransferAnyway, newEntriesT, notTransferedT, noAssignT);
|
|
|
|
int eventNumberCurrent = getStageNumber();
|
|
if (eventNumberCurrent <= 0) {
|
|
eventNumberCurrent = 1;
|
|
setStageNumber(eventNumberCurrent);
|
|
}
|
|
|
|
ce.getDI().setString("PreEvent", CurrentNameId);
|
|
ce.setStageNumber(eventNumberCurrent + 1);
|
|
getDI().setString("PostEvent", ce.CurrentNameId);
|
|
|
|
int nf = getMeOSFeatures().getNumFeatures();
|
|
for (int k = 0; k < nf; k++) {
|
|
MeOSFeatures::Feature f = getMeOSFeatures().getFeature(k);
|
|
if (getMeOSFeatures().hasFeature(f))
|
|
ce.getMeOSFeatures().useFeature(f, true, ce);
|
|
}
|
|
|
|
ce.save();
|
|
|
|
return ce.CurrentFile;
|
|
}
|
|
|
|
bool checkTargetClass(pRunner target, pRunner source,
|
|
const oClassList &Classes,
|
|
const vector<pRunner> &targetVacant,
|
|
vector<pRunner> &changedClass,
|
|
oEvent::ChangedClassMethod changeClassMethod) {
|
|
if (changeClassMethod == oEvent::TransferAnyway)
|
|
return true;
|
|
|
|
if (!compareClassName(target->getClass(), source->getClass())) {
|
|
// Store all vacant positions in the right class
|
|
int targetClass = -1;
|
|
|
|
if (target->getStatus() == StatusOK) {
|
|
// There is already a result. Do not change class!
|
|
return false;
|
|
}
|
|
|
|
if (changeClassMethod == oEvent::TransferNoResult)
|
|
return false; // Do not allow change class, do not transfer result
|
|
|
|
for (oClassList::const_iterator cit = Classes.begin(); cit != Classes.end(); ++cit) {
|
|
if (cit->isRemoved())
|
|
continue;
|
|
if (compareClassName(cit->getName(), source->getClass())) {
|
|
targetClass = cit->getId();
|
|
|
|
if (targetClass == source->getClassId() || cit->getName() == source->getClass())
|
|
break; // Assume exact match
|
|
}
|
|
}
|
|
|
|
if (targetClass != -1) {
|
|
set<int> vacantIx;
|
|
for (size_t j = 0; j < targetVacant.size(); j++) {
|
|
if (!targetVacant[j])
|
|
continue;
|
|
if (targetVacant[j]->getClassId() == targetClass)
|
|
vacantIx.insert(j);
|
|
}
|
|
int posToUse = -1;
|
|
if (vacantIx.size() == 1)
|
|
posToUse = *vacantIx.begin();
|
|
else if (vacantIx.size() > 1) {
|
|
string srcBib = source->getBib();
|
|
if (srcBib.length() > 0) {
|
|
for (set<int>::iterator tit = vacantIx.begin(); tit != vacantIx.end(); ++tit) {
|
|
if (targetVacant[*tit]->getBib() == srcBib) {
|
|
posToUse = *tit;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (posToUse == -1)
|
|
posToUse = *vacantIx.begin();
|
|
}
|
|
|
|
if (posToUse != -1) {
|
|
// Change class or change class vacant
|
|
changedClass.push_back(target);
|
|
|
|
int oldStart = target->getStartTime();
|
|
string oldBib = target->getBib();
|
|
int oldSN = target->getStartNo();
|
|
int oldClass = target->getClassId();
|
|
pRunner tgt = targetVacant[posToUse];
|
|
target->cloneStartTime(tgt);
|
|
target->setBib(tgt->getBib(), 0, false, false);
|
|
target->setStartNo(tgt->getStartNo(), false);
|
|
target->setClassId(tgt->getClassId(), false);
|
|
|
|
tgt->setStartTime(oldStart, true, false);
|
|
tgt->setBib(oldBib, 0, false, false);
|
|
tgt->setStartNo(oldSN, false);
|
|
tgt->setClassId(oldClass, false);
|
|
return true; // Changed to correct class
|
|
}
|
|
else if (changeClassMethod == oEvent::ChangeClass) {
|
|
// Simpliy change class
|
|
target->setClassId(targetClass, false);
|
|
return true;
|
|
}
|
|
}
|
|
return false; // Wrong class, ChangeClass (but failed)
|
|
}
|
|
|
|
return true; // Same class, OK
|
|
}
|
|
|
|
void oEvent::transferResult(oEvent &ce,
|
|
const set<int> &allowNewEntries,
|
|
ChangedClassMethod changeClassMethod,
|
|
bool transferAllNoCompete,
|
|
vector<pRunner> &changedClass,
|
|
vector<pRunner> &changedClassNoResult,
|
|
vector<pRunner> &assignedVacant,
|
|
vector<pRunner> &newEntries,
|
|
vector<pRunner> ¬Transfered,
|
|
vector<pRunner> &noAssignmentTarget) {
|
|
|
|
inthashmap processed(ce.Runners.size());
|
|
inthashmap used(Runners.size());
|
|
|
|
changedClass.clear();
|
|
changedClassNoResult.clear();
|
|
assignedVacant.clear();
|
|
newEntries.clear();
|
|
notTransfered.clear();
|
|
noAssignmentTarget.clear();
|
|
|
|
vector<pRunner> targetRunners;
|
|
vector<pRunner> targetVacant;
|
|
|
|
targetRunners.reserve(ce.Runners.size());
|
|
for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) {
|
|
if (!it->skip()) {
|
|
if (!it->isVacant())
|
|
targetRunners.push_back(&*it);
|
|
else
|
|
targetVacant.push_back(&*it);
|
|
}
|
|
}
|
|
|
|
calculateResults(RTTotalResult);
|
|
// Lookup by id
|
|
for (size_t k = 0; k < targetRunners.size(); k++) {
|
|
pRunner it = targetRunners[k];
|
|
pRunner r = getRunner(it->Id, 0);
|
|
if (!r)
|
|
continue;
|
|
|
|
__int64 id1 = r->getExtIdentifier();
|
|
__int64 id2 = it->getExtIdentifier();
|
|
|
|
if (id1>0 && id2>0 && id1 != id2)
|
|
continue;
|
|
|
|
string cnA = canonizeName(it->sName.c_str());
|
|
string cnB = canonizeName(r->sName.c_str());
|
|
string ccnA = canonizeName(it->getClub().c_str());
|
|
string ccnB = canonizeName(r->getClub().c_str());
|
|
|
|
if ((id1>0 && id1==id2) ||
|
|
(r->CardNo>0 && r->CardNo == it->CardNo) ||
|
|
(it->sName == r->sName) || (cnA == cnB && ccnA == ccnB)) {
|
|
processed.insert(it->Id, 1);
|
|
used.insert(r->Id, 1);
|
|
if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod))
|
|
it->setInputData(*r);
|
|
else {
|
|
it->resetInputData();
|
|
changedClassNoResult.push_back(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (processed.size() < int(targetRunners.size())) {
|
|
// Lookup by card
|
|
int v;
|
|
setupCardHash(false);
|
|
for (size_t k = 0; k < targetRunners.size(); k++) {
|
|
pRunner it = targetRunners[k];
|
|
if (processed.lookup(it->Id, v))
|
|
continue;
|
|
if (it->CardNo > 0) {
|
|
pRunner r = getRunnerByCardNo(it->CardNo, 0);
|
|
|
|
if (!r || used.lookup(r->Id, v))
|
|
continue;
|
|
|
|
__int64 id1 = r->getExtIdentifier();
|
|
__int64 id2 = it->getExtIdentifier();
|
|
|
|
if (id1>0 && id2>0 && id1 != id2)
|
|
continue;
|
|
|
|
if ((id1>0 && id1==id2) || (it->sName == r->sName && it->getClub() == r->getClub())) {
|
|
processed.insert(it->Id, 1);
|
|
used.insert(r->Id, 1);
|
|
if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod))
|
|
it->setInputData(*r);
|
|
else {
|
|
it->resetInputData();
|
|
changedClassNoResult.push_back(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setupCardHash(true); // Clear cache
|
|
}
|
|
|
|
int v = -1;
|
|
|
|
// Store remaining runners
|
|
vector<pRunner> remainingRunners;
|
|
for (oRunnerList::iterator it2 = Runners.begin(); it2 != Runners.end(); ++it2) {
|
|
if (it2->skip() || used.lookup(it2->Id, v))
|
|
continue;
|
|
if (it2->isVacant())
|
|
continue; // Ignore vacancies on source side
|
|
|
|
remainingRunners.push_back(&*it2);
|
|
}
|
|
|
|
if (processed.size() < int(targetRunners.size()) && !remainingRunners.empty()) {
|
|
// Lookup by name / ext id
|
|
vector<int> cnd;
|
|
for (size_t k = 0; k < targetRunners.size(); k++) {
|
|
pRunner it = targetRunners[k];
|
|
if (processed.lookup(it->Id, v))
|
|
continue;
|
|
|
|
__int64 id1 = it->getExtIdentifier();
|
|
|
|
cnd.clear();
|
|
for (size_t j = 0; j < remainingRunners.size(); j++) {
|
|
pRunner src = remainingRunners[j];
|
|
if (!src)
|
|
continue;
|
|
|
|
if (id1 > 0) {
|
|
__int64 id2 = src->getExtIdentifier();
|
|
if (id2 == id1) {
|
|
cnd.clear();
|
|
cnd.push_back(j);
|
|
break; //This is the one, if they have the same Id there will be a unique match below
|
|
}
|
|
}
|
|
if (it->sName == src->sName && it->getClub() == src->getClub())
|
|
cnd.push_back(j);
|
|
}
|
|
|
|
if (cnd.size() == 1) {
|
|
pRunner &src = remainingRunners[cnd[0]];
|
|
processed.insert(it->Id, 1);
|
|
used.insert(src->Id, 1);
|
|
if (checkTargetClass(it, src, ce.Classes, targetVacant, changedClass, changeClassMethod)) {
|
|
it->setInputData(*src);
|
|
}
|
|
else {
|
|
it->resetInputData();
|
|
changedClassNoResult.push_back(it);
|
|
}
|
|
src = 0;
|
|
}
|
|
else if (cnd.size() > 0) { // More than one candidate
|
|
int winnerIx = -1;
|
|
int point = -1;
|
|
for (size_t j = 0; j < cnd.size(); j++) {
|
|
pRunner src = remainingRunners[cnd[j]];
|
|
int p = 0;
|
|
if (src->getClass() == it->getClass())
|
|
p += 1;
|
|
if (src->getBirthYear() == it->getBirthYear())
|
|
p += 2;
|
|
if (p > point) {
|
|
winnerIx = cnd[j];
|
|
point = p;
|
|
}
|
|
}
|
|
|
|
if (winnerIx != -1) {
|
|
processed.insert(it->Id, 1);
|
|
pRunner winner = remainingRunners[winnerIx];
|
|
remainingRunners[winnerIx] = 0;
|
|
|
|
used.insert(winner->Id, 1);
|
|
if (checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass, changeClassMethod)) {
|
|
it->setInputData(*winner);
|
|
}
|
|
else {
|
|
it->resetInputData();
|
|
changedClassNoResult.push_back(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Transfer vacancies
|
|
for (size_t k = 0; k < remainingRunners.size(); k++) {
|
|
pRunner src = remainingRunners[k];
|
|
if (!src || used.lookup(src->Id, v))
|
|
continue;
|
|
|
|
bool forceSkip = src->hasFlag(oAbstractRunner::FlagTransferSpecified) &&
|
|
!src->hasFlag(oAbstractRunner::FlagTransferNew);
|
|
|
|
if (forceSkip) {
|
|
notTransfered.push_back(src);
|
|
continue;
|
|
}
|
|
|
|
pRunner targetVacant = ce.getRunner(src->getId(), 0);
|
|
if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(), src->getClass()) ) {
|
|
targetVacant->setName(src->getName(), false);
|
|
targetVacant->setClub(src->getClub());
|
|
targetVacant->setCardNo(src->getCardNo(), false);
|
|
targetVacant->cloneData(src);
|
|
assignedVacant.push_back(targetVacant);
|
|
}
|
|
else {
|
|
pClass dstClass = ce.getClass(src->getClassId());
|
|
if (dstClass && compareClassName(dstClass->getName(), src->getClass())) {
|
|
if ( (!src->hasFlag(oAbstractRunner::FlagTransferSpecified) && allowNewEntries.count(src->getClassId()))
|
|
|| src->hasFlag(oAbstractRunner::FlagTransferNew)) {
|
|
if (src->getClubId() > 0)
|
|
ce.getClubCreate(src->getClubId(), src->getClub());
|
|
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
|
|
src->getCardNo(), src->getBirthYear(), true);
|
|
dst->cloneData(src);
|
|
dst->setInputData(*src);
|
|
newEntries.push_back(dst);
|
|
}
|
|
else if (transferAllNoCompete) {
|
|
if (src->getClubId() > 0)
|
|
ce.getClubCreate(src->getClubId(), src->getClub());
|
|
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
|
|
0, src->getBirthYear(), true);
|
|
dst->cloneData(src);
|
|
dst->setInputData(*src);
|
|
dst->setStatus(StatusNotCompetiting, true, false);
|
|
notTransfered.push_back(dst);
|
|
}
|
|
else
|
|
notTransfered.push_back(src);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Runners on target side not assigned a result
|
|
for (size_t k = 0; k < targetRunners.size(); k++) {
|
|
if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) {
|
|
noAssignmentTarget.push_back(targetRunners[k]);
|
|
if (targetRunners[k]->inputStatus == StatusUnknown ||
|
|
(targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) {
|
|
targetRunners[k]->inputStatus = StatusNotCompetiting;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::transferResult(oEvent &ce,
|
|
ChangedClassMethod changeClassMethod,
|
|
vector<pTeam> &newEntries,
|
|
vector<pTeam> ¬Transfered,
|
|
vector<pTeam> &noAssignmentTarget) {
|
|
|
|
inthashmap processed(ce.Teams.size());
|
|
inthashmap used(Teams.size());
|
|
|
|
newEntries.clear();
|
|
notTransfered.clear();
|
|
noAssignmentTarget.clear();
|
|
|
|
vector<pTeam> targetTeams;
|
|
|
|
targetTeams.reserve(ce.Teams.size());
|
|
for (oTeamList::iterator it = ce.Teams.begin(); it != ce.Teams.end(); ++it) {
|
|
if (!it->skip()) {
|
|
targetTeams.push_back(&*it);
|
|
}
|
|
}
|
|
|
|
calculateTeamResults(true);
|
|
// Lookup by id
|
|
for (size_t k = 0; k < targetTeams.size(); k++) {
|
|
pTeam it = targetTeams[k];
|
|
pTeam t = getTeam(it->Id);
|
|
if (!t)
|
|
continue;
|
|
|
|
__int64 id1 = t->getExtIdentifier();
|
|
__int64 id2 = it->getExtIdentifier();
|
|
|
|
if (id1>0 && id2>0 && id1 != id2)
|
|
continue;
|
|
|
|
if ((id1>0 && id1==id2) || (it->sName == t->sName && it->getClub() == t->getClub())) {
|
|
processed.insert(it->Id, 1);
|
|
used.insert(t->Id, 1);
|
|
it->setInputData(*t);
|
|
//checkTargetClass(it, r, ce.Classes, targetVacant, changedClass);
|
|
}
|
|
}
|
|
|
|
int v = -1;
|
|
|
|
// Store remaining runners
|
|
vector<pTeam> remainingTeams;
|
|
for (oTeamList::iterator it2 = Teams.begin(); it2 != Teams.end(); ++it2) {
|
|
if (it2->skip() || used.lookup(it2->Id, v))
|
|
continue;
|
|
if (it2->isVacant())
|
|
continue; // Ignore vacancies on source side
|
|
|
|
remainingTeams.push_back(&*it2);
|
|
}
|
|
|
|
if (processed.size() < int(targetTeams.size()) && !remainingTeams.empty()) {
|
|
// Lookup by name / ext id
|
|
vector<int> cnd;
|
|
for (size_t k = 0; k < targetTeams.size(); k++) {
|
|
pTeam it = targetTeams[k];
|
|
if (processed.lookup(it->Id, v))
|
|
continue;
|
|
|
|
__int64 id1 = it->getExtIdentifier();
|
|
|
|
cnd.clear();
|
|
for (size_t j = 0; j < remainingTeams.size(); j++) {
|
|
pTeam src = remainingTeams[j];
|
|
if (!src)
|
|
continue;
|
|
|
|
if (id1 > 0) {
|
|
__int64 id2 = src->getExtIdentifier();
|
|
if (id2 == id1) {
|
|
cnd.clear();
|
|
cnd.push_back(j);
|
|
break; //This is the one, if they have the same Id there will be a unique match below
|
|
}
|
|
}
|
|
|
|
if (it->sName == src->sName && it->getClub() == src->getClub())
|
|
cnd.push_back(j);
|
|
}
|
|
|
|
if (cnd.size() == 1) {
|
|
pTeam &src = remainingTeams[cnd[0]];
|
|
processed.insert(it->Id, 1);
|
|
used.insert(src->Id, 1);
|
|
it->setInputData(*src);
|
|
//checkTargetClass(it, src, ce.Classes, targetVacant, changedClass);
|
|
src = 0;
|
|
}
|
|
else if (cnd.size() > 0) { // More than one candidate
|
|
int winnerIx = -1;
|
|
int point = -1;
|
|
for (size_t j = 0; j < cnd.size(); j++) {
|
|
pTeam src = remainingTeams[cnd[j]];
|
|
int p = 0;
|
|
if (src->getClass() == it->getClass())
|
|
p += 1;
|
|
if (p > point) {
|
|
winnerIx = cnd[j];
|
|
point = p;
|
|
}
|
|
}
|
|
|
|
if (winnerIx != -1) {
|
|
processed.insert(it->Id, 1);
|
|
pTeam winner = remainingTeams[winnerIx];
|
|
remainingTeams[winnerIx] = 0;
|
|
|
|
used.insert(winner->Id, 1);
|
|
it->setInputData(*winner);
|
|
//checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
// Transfer vacancies
|
|
for (size_t k = 0; k < remainingRunners.size(); k++) {
|
|
pRunner src = remainingRunners[k];
|
|
if (!src || used.lookup(src->Id, v))
|
|
continue;
|
|
|
|
pRunner targetVacant = ce.getRunner(src->getId(), 0);
|
|
if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(), src->getClass()) ) {
|
|
targetVacant->setName(src->getName());
|
|
targetVacant->setClub(src->getClub());
|
|
targetVacant->setCardNo(src->getCardNo(), false);
|
|
targetVacant->cloneData(src);
|
|
assignedVacant.push_back(targetVacant);
|
|
}
|
|
else {
|
|
pClass dstClass = ce.getClass(src->getClassId());
|
|
if (dstClass && compareClassName(dstClass->getName(), src->getClass())) {
|
|
if (allowNewEntries.count(src->getClassId())) {
|
|
if (src->getClubId() > 0)
|
|
ce.getClubCreate(src->getClubId(), src->getClub());
|
|
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
|
|
src->getCardNo(), src->getBirthYear(), true);
|
|
dst->cloneData(src);
|
|
dst->setInputData(*src);
|
|
newEntries.push_back(dst);
|
|
}
|
|
else if (transferAllNoCompete) {
|
|
if (src->getClubId() > 0)
|
|
ce.getClubCreate(src->getClubId(), src->getClub());
|
|
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
|
|
0, src->getBirthYear(), true);
|
|
dst->cloneData(src);
|
|
dst->setInputData(*src);
|
|
dst->setStatus(StatusNotCompetiting);
|
|
notTransfered.push_back(dst);
|
|
}
|
|
else
|
|
notTransfered.push_back(src);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Runners on target side not assigned a result
|
|
for (size_t k = 0; k < targetRunners.size(); k++) {
|
|
if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) {
|
|
noAssignmentTarget.push_back(targetRunners[k]);
|
|
if (targetRunners[k]->inputStatus == StatusUnknown ||
|
|
(targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) {
|
|
targetRunners[k]->inputStatus = StatusNotCompetiting;
|
|
}
|
|
}
|
|
|
|
}*/
|
|
}
|
|
|
|
MetaListContainer &oEvent::getListContainer() const {
|
|
if (!listContainer)
|
|
throw std::exception("Nullpointer exception");
|
|
return *listContainer;
|
|
}
|
|
|
|
void oEvent::setExtraLines(const char *attrib, const vector< pair<string, int> > &lines) {
|
|
string str;
|
|
|
|
for(size_t k = 0; k < lines.size(); k++) {
|
|
if (k>0)
|
|
str.push_back('|');
|
|
|
|
string msg = lines[k].first;
|
|
for (size_t i = 0; i < msg.size(); i++) {
|
|
if (msg[i] == '|')
|
|
str.push_back(':'); // Encoding does not support |
|
|
else
|
|
str.push_back(msg[i]);
|
|
}
|
|
str.push_back('|');
|
|
str.append(itos(lines[k].second));
|
|
}
|
|
getDI().setString(attrib, str);
|
|
}
|
|
|
|
void oEvent::getExtraLines(const char *attrib, vector< pair<string, int> > &lines) const {
|
|
vector<string> splt;
|
|
const string &splitPrintExtra = getDCI().getString(attrib);
|
|
split(splitPrintExtra, "|", splt);
|
|
lines.clear();
|
|
lines.reserve(splt.size() / 2);
|
|
for (size_t k = 0; k + 1 < splt.size(); k+=2) {
|
|
lines.push_back(make_pair(splt[k], atoi(splt[k+1].c_str())));
|
|
}
|
|
|
|
while(!lines.empty()) {
|
|
if (lines.back().first.length() == 0)
|
|
lines.pop_back();
|
|
else break;
|
|
}
|
|
}
|
|
|
|
oEvent::MultiStageType oEvent::getMultiStageType() const {
|
|
if (getDCI().getString("PreEvent").empty())
|
|
return MultiStageNone;
|
|
else
|
|
return MultiStageSameEntry;
|
|
}
|
|
|
|
bool oEvent::hasNextStage() const {
|
|
return !getDCI().getString("PostEvent").empty();
|
|
}
|
|
|
|
bool oEvent::hasPrevStage() const {
|
|
return !getDCI().getString("PreEvent").empty();
|
|
}
|
|
|
|
int oEvent::getNumStages() const {
|
|
int ns = getDCI().getInt("NumStages");
|
|
if (ns>0)
|
|
return ns;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
void oEvent::setNumStages(int numStages) {
|
|
getDI().setInt("NumStages", numStages);
|
|
}
|
|
|
|
int oEvent::getStageNumber() const {
|
|
return getDCI().getInt("EventNumber");
|
|
}
|
|
|
|
void oEvent::setStageNumber(int num) {
|
|
getDI().setInt("EventNumber", num);
|
|
}
|
|
|
|
oDataContainer &oEvent::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
|
|
data = (pvoid)oData;
|
|
olddata = (pvoid)oDataOld;
|
|
strData = (pvectorstr)&dynamicData;
|
|
return *oEventData;
|
|
}
|
|
|
|
void oEvent::changedObject() {
|
|
globalModification = true;
|
|
}
|
|
|
|
void oEvent::pushDirectChange() {
|
|
PostMessage(gdibase.getMain(), WM_USER + 4, 0, 0);
|
|
}
|
|
|
|
int oEvent::getBibClassGap() const {
|
|
int ns = getDCI().getInt("BibGap");
|
|
return ns;
|
|
}
|
|
|
|
void oEvent::setBibClassGap(int numStages) {
|
|
getDI().setInt("BibGap", numStages);
|
|
}
|
|
|
|
void oEvent::checkNecessaryFeatures() {
|
|
bool hasMultiRace = false;
|
|
bool hasRelay = false;
|
|
bool hasPatrol = false;
|
|
bool hasForkedIndividual = false;
|
|
|
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
|
const oClass &c = *it;
|
|
bool multiRace = false;
|
|
bool relay = false;
|
|
bool patrol = false;
|
|
|
|
for (size_t j = 0; j < c.legInfo.size(); j++) {
|
|
|
|
if (c.legInfo[j].duplicateRunner != -1)
|
|
multiRace = true;
|
|
|
|
if (j > 0 && !c.legInfo[j].isParallel() && !c.legInfo[j].isOptional()) {
|
|
relay = true;
|
|
patrol = false;
|
|
}
|
|
|
|
if (j > 0 && (c.legInfo[j].isParallel() || c.legInfo[j].isOptional()) && !relay) {
|
|
patrol = true;
|
|
}
|
|
}
|
|
|
|
hasForkedIndividual |= c.legInfo.size() == 1;
|
|
hasMultiRace |= multiRace;
|
|
hasRelay |= relay;
|
|
hasPatrol |= patrol;
|
|
}
|
|
|
|
if (hasForkedIndividual)
|
|
oe->getMeOSFeatures().useFeature(MeOSFeatures::ForkedIndividual, true, *this);
|
|
|
|
if (hasRelay)
|
|
oe->getMeOSFeatures().useFeature(MeOSFeatures::Relay, true, *this);
|
|
|
|
if (hasPatrol)
|
|
oe->getMeOSFeatures().useFeature(MeOSFeatures::Patrol, true, *this);
|
|
|
|
if (hasMultiRace)
|
|
oe->getMeOSFeatures().useFeature(MeOSFeatures::MultipleRaces, true, *this);
|
|
|
|
oe->synchronize(true);
|
|
}
|
|
|
|
bool oEvent::useLongTimes() const {
|
|
if (tLongTimesCached != -1)
|
|
return tLongTimesCached != 0;
|
|
|
|
tLongTimesCached = getDCI().getInt("LongTimes");
|
|
return tLongTimesCached != 0;
|
|
}
|
|
|
|
void oEvent::useLongTimes(bool use) {
|
|
tLongTimesCached = use;
|
|
getDI().setInt("LongTimes", use ? 1 : 0);
|
|
}
|
|
|
|
int oEvent::convertToFullTime(int inTime) {
|
|
if (inTime < 0 || !useLongTimes() || inTime > 24*3600)
|
|
return inTime;
|
|
|
|
return inTime;
|
|
}
|
|
|
|
|
|
void oEvent::getPayModes(vector< pair<string, size_t> > &modes) {
|
|
modes.clear();
|
|
modes.reserve(10);
|
|
vector< pair<string, int> > lines;
|
|
getExtraLines("PayModes", lines);
|
|
|
|
modes.push_back(make_pair(lang.tl("Kontant betalning"), 0));
|
|
map<int,int> id2ix;
|
|
id2ix[0] = 0;
|
|
|
|
for (size_t k = 0; k < lines.size(); k++) {
|
|
int id = lines[k].second;
|
|
if (id2ix.count(id))
|
|
modes[id2ix[id]].first = lines[k].first;
|
|
else {
|
|
id2ix[id] = k;
|
|
modes.push_back(make_pair(lines[k].first, id));
|
|
}
|
|
}
|
|
}
|
|
|
|
void oEvent::setPayMode(int id, const string &mode) {
|
|
vector< pair<string, int> > lines;
|
|
getExtraLines("PayModes", lines);
|
|
|
|
if (mode.empty()) {
|
|
// Remove
|
|
for (size_t k = 0; k < lines.size(); k++) {
|
|
if (lines[k].second == id) {
|
|
bool valid = id != 0;
|
|
for (oRunnerList::const_iterator it = Runners.begin();
|
|
valid && it != Runners.end(); ++it) {
|
|
if (it->getPaymentMode() == id)
|
|
valid = false;
|
|
}
|
|
for (oTeamList::const_iterator it = Teams.begin();
|
|
valid && it != Teams.end(); ++it) {
|
|
if (it->getPaymentMode() == id)
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid)
|
|
throw meosException("Betalningsättet behövs och kan inte tas bort.");
|
|
|
|
lines.erase(lines.begin() + k);
|
|
k--;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Add / update
|
|
bool done = false;
|
|
for (size_t k = 0; k < lines.size(); k++) {
|
|
if (lines[k].second == id) {
|
|
lines[k].first = mode;
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!done) {
|
|
lines.push_back(make_pair(mode, id));
|
|
}
|
|
}
|
|
|
|
setExtraLines("PayModes", lines);
|
|
}
|
|
|
|
void oEvent::useDefaultProperties(bool useDefault) {
|
|
if (useDefault) {
|
|
if (savedProperties.empty())
|
|
savedProperties.swap(eventProperties);
|
|
}
|
|
else {
|
|
if (!savedProperties.empty()) {
|
|
savedProperties.swap(eventProperties);
|
|
savedProperties.clear();
|
|
}
|
|
}
|
|
} |