meos-2024/code/TabTeam.cpp
2024-04-07 08:37:06 +02:00

2111 lines
63 KiB
C++

/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2024 Melin Software HB
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
#include "stdafx.h"
#include "resource.h"
#include <commctrl.h>
#include <commdlg.h>
#include "oEvent.h"
#include "xmlparser.h"
#include "gdioutput.h"
#include "gdiconstants.h"
#include "csvparser.h"
#include "SportIdent.h"
#include "meos_util.h"
#include <cassert>
#include "meosexception.h"
#include "TabTeam.h"
#include "TabRunner.h"
#include "MeOSFeatures.h"
#include "RunnerDB.h"
#include "autocomplete.h"
#include "TabSI.h"
TabTeam::TabTeam(oEvent *poe):TabBase(poe) {
clearCompetitionData();
}
TabTeam::~TabTeam(void) = default;
int TeamCB(gdioutput *gdi, GuiEventType type, BaseInfo* data) {
TabTeam &tt = dynamic_cast<TabTeam &>(*gdi->getTabs().get(TTeamTab));
return tt.teamCB(*gdi, type, data);
}
int teamSearchCB(gdioutput *gdi, GuiEventType type, BaseInfo* data) {
TabTeam &tc = dynamic_cast<TabTeam &>(*gdi->getTabs().get(TTeamTab));
return tc.searchCB(*gdi, type, data);
}
int TabTeam::searchCB(gdioutput &gdi, int type, void *data) {
static DWORD editTick = 0;
wstring expr;
bool showNow = false;
bool filterMore = false;
if (type == GUI_INPUTCHANGE) {
inputId++;
InputInfo &ii = *(InputInfo *)(data);
expr = trim(ii.text);
filterMore = expr.length() > lastSearchExpr.length() &&
expr.substr(0, lastSearchExpr.length()) == lastSearchExpr;
editTick = GetTickCount();
if (expr != lastSearchExpr) {
int nr = oe->getNumRunners();
if (timeToFill < 50 || (filterMore && (timeToFill * lastFilter.size())/nr < 50))
showNow = true;
else {// Delay filter
gdi.addTimeoutMilli(500, "Search", teamSearchCB).setData(inputId, expr);
}
}
}
else if (type == GUI_TIMER) {
TimerInfo &ti = *(TimerInfo *)(data);
if (inputId != ti.getData())
return 0;
expr = ti.getDataString();
filterMore = expr.length() > lastSearchExpr.length() &&
expr.substr(0, lastSearchExpr.length()) == lastSearchExpr;
showNow = true;
}
else if (type == GUI_EVENT) {
EventInfo &ev = *(EventInfo *)(data);
if (ev.getKeyCommand() == KC_FIND) {
gdi.setInputFocus("SearchText", true);
}
else if (ev.getKeyCommand() == KC_FINDBACK) {
gdi.setInputFocus("SearchText", false);
}
}
else if (type == GUI_FOCUS) {
InputInfo &ii = *(InputInfo *)(data);
if (ii.text == getSearchString()) {
((InputInfo *)gdi.setText("SearchText", L""))->setFgColor(colorDefault);
}
}
if (showNow) {
unordered_set<int> filter;
if (type == GUI_TIMER)
gdi.setWaitCursor(true);
if (filterMore) {
oe->findTeam(expr, 0, filter);
lastSearchExpr = expr;
// Filter more
if (filter.empty()) {
vector< pair<wstring, size_t> > runners;
runners.push_back(make_pair(lang.tl(L"Ingen matchar 'X'#" + expr), -1));
gdi.setItems("Teams", runners);
}
else
gdi.filterOnData("Teams", filter);
}
else {
oe->findTeam(expr, 0, filter);
lastSearchExpr = expr;
vector< pair<wstring, size_t> > runners;
oe->fillTeams(runners);
if (filter.size() == runners.size()){
}
else if (filter.empty()) {
runners.clear();
runners.push_back(make_pair(lang.tl(L"Ingen matchar 'X'#" + expr), -1));
}
else {
vector< pair<wstring, size_t> > runners2;
for (size_t k = 0; k<runners.size(); k++) {
if (filter.count(runners[k].second) == 1) {
runners2.push_back(make_pair(wstring(), runners[k].second));
runners2.back().first.swap(runners[k].first);
}
}
runners.swap(runners2);
}
gdi.setItems("Teams", runners);
}
if (type == GUI_TIMER)
gdi.setWaitCursor(false);
}
return 0;
}
void TabTeam::selectTeam(gdioutput &gdi, pTeam t)
{
if (gdioutput* gdi_comments = getExtraWindow("comments", false); gdi_comments)
gdi_comments->closeWindow();
if (t){
t->synchronize();
t->evaluate(oBase::ChangeType::Quiet);
teamId=t->getId();
gdi.enableEditControls(true);
gdi.enableInput("Save");
gdi.enableInput("Undo");
gdi.enableInput("Remove");
gdi.enableInput("EditAnnotation");
oe->fillClasses(gdi, "RClass", {}, oEvent::extraNone, oEvent::filterOnlyMulti);
gdi.selectItemByData("RClass", t->getClassId(false));
gdi.selectItemByData("Teams", t->getId());
if (gdi.hasWidget("StatusIn")) {
gdi.selectItemByData("StatusIn", t->getInputStatus());
int ip = t->getInputPlace();
if (ip > 0)
gdi.setText("PlaceIn", ip);
else
gdi.setText("PlaceIn", makeDash(L"-"));
gdi.setText("TimeIn", t->getInputTimeS());
if (gdi.hasWidget("PointIn"))
gdi.setText("PointIn", t->getInputPoints());
}
if (gdi.hasWidget("NoRestart")) {
gdi.check("NoRestart", t->preventRestart());
}
TabRunner::loadExtraFields(gdi, t);
loadTeamMembers(gdi, 0, 0, t);
}
else {
teamId=0;
gdi.enableEditControls(false);
gdi.disableInput("Save");
gdi.disableInput("Undo");
gdi.disableInput("Remove");
gdi.disableInput("EditAnnotation");
ListBoxInfo lbi;
gdi.getSelectedItem("RClass", lbi);
gdi.selectItemByData("Teams", -1);
if (gdi.hasWidget("StatusIn")) {
gdi.selectFirstItem("StatusIn");
gdi.setText("PlaceIn", L"");
gdi.setText("TimeIn", makeDash(L"-"));
if (gdi.hasWidget("PointIn"))
gdi.setText("PointIn", L"");
}
loadTeamMembers(gdi, lbi.data, 0, 0);
}
updateTeamStatus(gdi, t);
gdi.refresh();
}
void TabTeam::updateTeamStatus(gdioutput &gdi, pTeam t)
{
if (!t) {
gdi.setText("Name", L"");
if (gdi.hasWidget("StartNo"))
gdi.setText("StartNo", L"");
if (gdi.hasWidget("Club"))
gdi.setText("Club", L"");
bool hasFee = gdi.hasWidget("Fee");
if (hasFee) {
gdi.setText("Fee", L"");
}
gdi.setText("Start", makeDash(L"-"));
gdi.setText("Finish", makeDash(L"-"));
gdi.setText("Time", makeDash(L"-"));
gdi.selectItemByData("Status", 0);
gdi.setText("TimeAdjust", makeDash(L"-"));
gdi.setText("PointAdjust", makeDash(L"-"));
gdi.setText("TeamInfo", L"", true);
return;
}
gdi.setText("Name", t->getName());
if (gdi.hasWidget("StartNo"))
gdi.setText("StartNo", t->getBib());
if (gdi.hasWidget("Club"))
gdi.setText("Club", t->getClub());
bool hasFee = gdi.hasWidget("Fee");
if (hasFee) {
gdi.setText("Fee", oe->formatCurrency(t->getDI().getInt("Fee")));
}
gdi.setText("Start", t->getStartTimeS());
gdi.setText("Finish",t->getFinishTimeS(false, SubSecond::Auto));
gdi.setText("Time", t->getRunningTimeS(true, SubSecond::Auto));
gdi.setText("TimeAdjust", formatTimeMS(t->getTimeAdjustment(false), false, SubSecond::Auto));
gdi.setText("PointAdjust", -t->getPointAdjustment());
gdi.selectItemByData("Status", t->getStatus());
auto ri = t->getRaceInfo();
BaseInfo *bi = gdi.setText("TeamInfo", ri.first, true);
TextInfo *ti = dynamic_cast<TextInfo*>(bi);
assert(ti);
if (ti) {
if (ri.second > 0)
ti->setColor(GDICOLOR::colorGreen);
else if (ri.second < 0)
ti->setColor(GDICOLOR::colorRed);
else
ti->setColor(GDICOLOR::colorDefault);
}
}
bool TabTeam::save(gdioutput &gdi, bool dontReloadTeams) {
if (teamId==0)
return 0;
DWORD tid=teamId;
wstring name=gdi.getText("Name");
if (name.empty()) {
gdi.alert("Alla lag måste ha ett namn.");
return 0;
}
bool create=false;
pTeam t;
if (tid==0) {
t=oe->addTeam(name);
create=true;
}
else t=oe->getTeam(tid);
teamId=t->getId();
bool bibModified = false;
if (t) {
t->setName(name, true);
if (gdi.hasWidget("StartNo")) {
const wstring &bib = gdi.getText("StartNo");
if (bib != t->getBib()) {
bibModified = true;
wchar_t pat[32];
int no = oClass::extractBibPattern(bib, pat);
t->setBib(bib, no, no > 0);
}
}
wstring start = gdi.getText("Start");
t->setStartTimeS(start);
if (t->getRunner(0))
t->getRunner(0)->setStartTimeS(start);
t->setFinishTimeS(gdi.getText("Finish"));
if (gdi.hasWidget("Fee"))
t->getDI().setInt("Fee", oe->interpretCurrency(gdi.getText("Fee")));
if (gdi.hasWidget("NoRestart"))
t->preventRestart(gdi.isChecked("NoRestart"));
TabRunner::saveExtraFields(gdi, *t);
t->apply(oBase::ChangeType::Quiet, nullptr);
if (gdi.hasWidget("Club")) {
ListBoxInfo lbi;
gdi.getSelectedItem("Club", lbi);
if (!lbi.text.empty()) {
pClub pc=oe->getClub(lbi.text);
if (!pc)
pc = oe->addClub(lbi.text);
pc->synchronize();
}
t->setClub(lbi.text);
if (!dontReloadTeams)
oe->fillClubs(gdi, "Club");
gdi.setText("Club", lbi.text);
}
ListBoxInfo lbi;
gdi.getSelectedItem("Status", lbi);
RunnerStatus sIn = (RunnerStatus)lbi.data;
// Must be done AFTER all runners are set. But setting runner can modify status, so decide here.
bool setTeamStatus = (!oAbstractRunner::isResultStatus(sIn) || sIn == StatusOK) && (t->getStatus() != sIn);
bool checkStatus = (sIn != t->getStatus());
if (sIn == StatusUnknown && !oAbstractRunner::isResultStatus(t->getStatus()))
t->setTeamMemberStatus(StatusUnknown);
else if ((RunnerStatus)lbi.data != t->getStatus())
t->setStatus((RunnerStatus)lbi.data, true, oBase::ChangeType::Update);
gdi.getSelectedItem("RClass", lbi);
int classId = lbi.data;
bool newClass = t->getClassId(false) != classId;
set<int> classes;
bool globalDep = false;
if (t->getClassRef(false))
globalDep = t->getClassRef(false)->hasClassGlobalDependence();
classes.insert(classId);
classes.insert(t->getClassId(false));
bool readStatusIn = true;
if (newClass && t->getInputStatus() != StatusNotCompetiting && t->hasInputData()) {
if (gdi.ask(L"Vill du sätta resultatet från tidigare etapper till <Deltar ej>?")) {
t->resetInputData();
readStatusIn = false;
}
}
if (newClass && !bibModified) {
pClass pc = oe->getClass(classId);
if (pc) {
pair<int, wstring> snoBib = pc->getNextBib();
if (snoBib.first > 0) {
t->setBib(snoBib.second, snoBib.first, true);
}
}
}
t->setClassId(classId, true);
t->adjustMultiRunners();
if (gdi.hasWidget("TimeAdjust")) {
int time = convertAbsoluteTimeMS(gdi.getText("TimeAdjust"));
if (time != NOTIME)
t->setTimeAdjustment(time);
}
if (gdi.hasWidget("PointAdjust")) {
t->setPointAdjustment(-gdi.getTextNo("PointAdjust"));
}
if (gdi.hasWidget("StatusIn") && readStatusIn) {
t->setInputStatus(RunnerStatus(gdi.getSelectedItem("StatusIn").first));
t->setInputPlace(gdi.getTextNo("PlaceIn"));
t->setInputTime(gdi.getText("TimeIn"));
if (gdi.hasWidget("PointIn"))
t->setInputPoints(gdi.getTextNo("PointIn"));
}
pClass pc=oe->getClass(classId);
if (pc) {
globalDep |= pc->hasClassGlobalDependence();
for (unsigned i=0;i<pc->getNumStages(); i++) {
char bf[16];
sprintf_s(bf, "R%d", i);
if (!gdi.hasWidget("SI" + itos(i))) // Skip if field not loaded in page
continue;
if (pc->getLegRunner(i)==i) {
const wstring name=gdi.getText(bf);
if (name.empty()) { //Remove
t->removeRunner(gdi, true, i);
}
else {
pRunner r=t->getRunner(i);
char bf2[16];
sprintf_s(bf2, "SI%d", i);
int cardNo = gdi.getTextNo(bf2);
if (r) {
bool newName = !r->matchName(name);
int oldId = gdi.getExtraInt(bf);
// Same runner set
if (oldId == r->getId()) {
if (newName) {
r->updateFromDB(name, r->getClubId(), r->getClassId(false),
cardNo, 0, false);
r->setName(name, true);
}
r->setCardNo(cardNo, true);
if (gdi.isChecked("RENT" + itos(i)))
r->getDI().setInt("CardFee", oe->getBaseCardFee());
else
r->getDI().setInt("CardFee", 0);
r->synchronize(true);
continue;
}
if (newName) {
if (!t->getClub().empty())
r->setClub(t->getClub());
r->resetPersonalData();
r->updateFromDB(name, r->getClubId(), r->getClassId(false),
cardNo, 0, false);
}
}
else
r = oe->addRunner(name, t->getClubId(), t->getClassId(false), cardNo, L"", false);
r->setName(name, true);
r->setCardNo(cardNo, true);
if (gdi.isChecked("RENT" + itos(i)))
r->getDI().setInt("CardFee", oe->getBaseCardFee());
else
r->getDI().setInt("CardFee", 0);
r->synchronize();
t->setRunner(i, r, true);
}
}
}
}
if (setTeamStatus)
t->setTeamMemberStatus(sIn);
if (t->checkValdParSetup()) {
gdi.alert("Laguppställningen hade fel, som har rättats");
}
if (t->getRunner(0))
t->getRunner(0)->setStartTimeS(start);
t->applyBibs();
t->evaluate(oBase::ChangeType::Update);
if (globalDep)
oe->reEvaluateAll(classes, false);
if (!dontReloadTeams) {
fillTeamList(gdi);
//updateTeamStatus(gdi, t);
}
if (checkStatus && sIn != t->getStatus()) {
gdi.alert("Status matchar inte deltagarnas status.");
}
}
if (create) {
selectTeam(gdi, 0);
gdi.setInputFocus("Name", true);
}
else if (!dontReloadTeams) {
selectTeam(gdi, t);
}
return true;
}
wstring getForking(pClass pc, int key, int maxLen) {
wstring crsList;
bool hasCrs = false;
for (size_t k = 0; k < pc->getNumStages(); k++) {
pCourse crs = pc->getCourse(k, key);
wstring cS;
if (crs != 0) {
cS = crs->getName();
hasCrs = true;
}
else
cS = makeDash(L"-");
if (!crsList.empty())
crsList += L", ";
crsList += cS;
if (hasCrs && crsList.length() > maxLen) {
return crsList;
}
}
if (!hasCrs)
crsList.clear();
return crsList;
}
int TabTeam::teamCB(gdioutput &gdi, GuiEventType type, BaseInfo* data) {
if (type==GUI_BUTTON) {
ButtonInfo bi=*(ButtonInfo *)data;
if (bi.id=="Save") {
return save(gdi, false);
}
else if (bi.id == "Cancel") {
loadPage(gdi);
return 0;
}
else if (bi.id=="TableMode") {
if (currentMode == 0 && teamId>0)
save(gdi, true);
currentMode = 1;
loadPage(gdi);
}
else if (bi.id=="FormMode") {
if (currentMode != 0) {
currentMode = 0;
gdi.enableTables();
loadPage(gdi);
}
}
else if (bi.id=="Undo") {
pTeam t = oe->getTeam(teamId);
selectTeam(gdi, t);
return 0;
}
else if (bi.id == "EditAnnotation") {
if (!teamId)
return 0;
pTeam t = oe->getTeam(teamId);
if (t && getExtraWindow("comments", true) == nullptr) {
gdioutput* settings = createExtraWindow("comments", L"Kommentarer", gdi.scaleLength(550), gdi.scaleLength(350), true);
class TeamComments : public TabRunner::CommentHandler {
public:
TeamComments(oTeam& t) : CommentHandler(t) {}
void save(gdioutput& gdi) override {
doSave(gdi);
auto t = &getRunner();
oe->gdiBase().restore("SelectR");
TabTeam::forkingKey(oe->gdiBase(), pTeam(t));
oe->gdiBase().refresh();
}
};
TabRunner::loadComments(*settings, *t, make_shared<TeamComments>(*t));
}
}
else if (bi.id=="Search") {
ListBoxInfo lbi;
gdi.getSelectedItem("Teams", lbi);
oe->fillTeams(gdi, "Teams");
unordered_set<int> foo;
pTeam t=oe->findTeam(gdi.getText("SearchText"), lbi.data, foo);
if (t) {
selectTeam(gdi, t);
gdi.selectItemByData("Teams", t->getId());
}
else
gdi.alert("Laget hittades inte");
}
else if (bi.id == "ImportTeams") {
if (teamId>0)
save(gdi, true);
showTeamImport(gdi);
}
else if (bi.id == "DoImportTeams") {
doTeamImport(gdi);
}
else if (bi.id == "AddTeamMembers") {
if (teamId>0)
save(gdi, true);
showAddTeamMembers(gdi);
}
else if (bi.id == "DoAddTeamMembers") {
doAddTeamMembers(gdi);
}
else if (bi.id == "SaveTeams") {
saveTeamImport(gdi, bi.getExtraInt() != 0);
}
else if (bi.id == "ShowAll") {
fillTeamList(gdi);
}
else if (bi.id == "DirectEntry") {
gdi.restore("DirectEntry", false);
gdi.fillDown();
gdi.dropLine();
gdi.addString("", boldText, "Direktanmälan");
gdi.addString("", 0, "Du kan använda en SI-enhet för att läsa in bricknummer.");
gdi.dropLine(0.2);
int leg = bi.getExtraInt();
gdi.fillRight();
gdi.addInput("DirName", L"", 16, TeamCB, L"Namn:");
gdi.addInput("DirCard", L"", 8, TeamCB, L"Bricka:");
TabSI &tsi = dynamic_cast<TabSI &>(*gdi.getTabs().get(TSITab));
tsi.setCardNumberField("DirCard");
gdi.setPostClearCb(TeamCB);
bool rent = false;
gdi.dropLine(1.1);
gdi.addCheckbox("DirRent", "Hyrd", 0, rent);
gdi.dropLine(-0.2);
gdi.addButton("DirOK", "OK", TeamCB).setDefault().setExtra(leg);
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
gdi.disableInput("DirOK");
gdi.refreshFast();
}
else if (bi.id == "DirOK") {
pTeam t = oe->getTeam(teamId);
if (!t || !t->getClassRef(false))
return 0;
int leg = bi.getExtraInt();
wstring name = gdi.getText("DirName");
int storedId = gdi.getBaseInfo("DirName").getExtraInt();
int card = gdi.getTextNo("DirCard");
if (card <= 0 || name.empty())
throw meosException("Internal error"); //Cannot happen
pRunner r = 0;
if (storedId > 0) {
r = oe->getRunner(storedId, 0);
if (r != 0 && (r->getName() != name || r->getCardNo() != card))
r = 0; // Ignore match
}
bool rExists = r != 0;
pRunner old = oe->getRunnerByCardNo(card, 0, oEvent::CardLookupProperty::CardInUse);
if (old && r != old) {
throw meosException(L"Brickan används av X.#" + old->getName() );
}
pClub clb = 0;
if (!rExists) {
pRunner rOrig = oe->getRunnerByCardNo(card, 0, oEvent::CardLookupProperty::Any);
if (rOrig)
clb = rOrig->getClubRef();
}
bool rent = gdi.isChecked("DirRent");
if (r == 0) {
r = oe->addRunner(name, clb ? clb->getId() : t->getClubId(), t->getClassId(false), card, L"", false);
}
if (rent)
r->getDI().setInt("CardFee", oe->getBaseCardFee());
t->synchronize();
pRunner oldR = t->getRunner(leg);
t->setRunner(leg, 0, false);
t->synchronize(true);
if (oldR) {
if (rExists) {
switchRunners(t, leg, r, oldR);
}
else {
oldR->setClassId(0, true);
vector<int> mp;
oldR->evaluateCard(true, mp, 0, oBase::ChangeType::Update);
oldR->synchronize(true);
t->setRunner(leg, r, true);
t->checkValdParSetup();
if (oldR->isAnnonumousTeamMember())
oe->removeRunner({ oldR->getId() });
t->synchronize(true);
}
}
else {
t->setRunner(leg, r, true);
t->checkValdParSetup();
t->synchronize(true);
}
selectTeam(gdi, t);
}
else if (bi.id == "Browse") {
const wchar_t *target = bi.getExtra();
vector< pair<wstring, wstring> > ext;
ext.push_back(make_pair(L"Laguppställning", L"*.csv;*.txt"));
wstring fileName = gdi.browseForOpen(ext, L"csv");
if (!fileName.empty())
gdi.setText(target, fileName);
}
else if (bi.id == "ChangeKey") {
pTeam t = oe->getTeam(teamId);
if (!t || !t->getClassRef(false))
return 0;
pClass pc = t->getClassRef(false);
gdi.restore("ChangeKey", false);
gdi.fillRight();
gdi.pushX();
gdi.dropLine();
gdi.addSelection("ForkKey", 100, 400, 0, L"Gafflingsnyckel:");
int nf = pc->getNumForks();
vector<pair<wstring, size_t>> keys;
keys.reserve(nf);
for (int f = 0; f < nf; f++) {
keys.push_back(make_pair(itow(f+1) + L": " + getForking(pc, f+1, 30), f));
}
int currentKey = max(t->getStartNo()-1, 0) % nf;
gdi.setItems("ForkKey", keys);
gdi.autoGrow("ForkKey");
gdi.selectItemByData("ForkKey", currentKey);
gdi.dropLine(0.9);
gdi.addButton("SaveKey", "Ändra", TeamCB).setDefault();
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
gdi.popX();
gdi.dropLine(1);
renderComments(t, gdi);
gdi.refresh();
}
else if (bi.id == "SaveKey") {
pTeam t = oe->getTeam(teamId);
if (!t || !t->getClassRef(false))
return 0;
t->synchronize();
pClass pc = t->getClassRef(false);
int nf = pc->getNumForks();
ListBoxInfo lbi;
gdi.getSelectedItem("ForkKey", lbi);
for (int k = 0; k < nf; k++) {
for (int j = 0; j < 2; j++) {
int newSno = t->getStartNo();
if (j == 0)
newSno += k;
else
newSno -=k;
if (newSno <= 0)
continue;
int currentKey = max(newSno-1, 0) % nf;
if (currentKey == lbi.data) {
for (int j = 0; j < t->getNumRunners(); j++) {
if (t->getRunner(j)) {
t->getRunner(j)->setCourseId(0);
t->synchronize(true);
}
}
t->setStartNo(newSno, oBase::ChangeType::Update);
t->synchronize(true);
t->evaluate(oBase::ChangeType::Update);
selectTeam(gdi, t);
return 0;
}
}
}
}
else if (bi.id.substr(0,2)=="MR") {
int leg = atoi(bi.id.substr(2, string::npos).c_str());
if (teamId != 0) {
save(gdi, false);
pTeam t = oe->getTeam(teamId);
if (t != 0) {
pRunner r = t->getRunner(leg);
if (r) {
TabRunner *tr = (TabRunner *)gdi.getTabs().get(TRunnerTab);
tr->loadPage(gdi, r->getId());
}
}
}
}
else if (bi.id.substr(0,2)=="DR") {
int leg=atoi(bi.id.substr(2, string::npos).c_str());
pTeam t = oe->getTeam(teamId);
if (t == 0)
return 0;
pClass pc = t->getClassRef(false);
if (pc == 0)
return 0;
gdi.restore("SelectR", false);
gdi.setRestorePoint("SelectR");
gdi.dropLine();
gdi.fillDown();
gdi.addString("", fontMediumPlus, L"Välj löpare för sträcka X#" + pc->getLegNumber(leg));
gdi.setData("Leg", leg);
gdi.setRestorePoint("DirectEntry");
gdi.addString("", 0, "help:teamwork");
gdi.dropLine(0.5);
gdi.addButton("DirectEntry", "Direktanmälan...", TeamCB).setExtra(leg);
set<int> presented;
gdi.pushX();
gdi.fillRight();
int w,h;
gdi.getTargetDimension(w, h);
w = max(w, gdi.getWidth());
int limit = max(w - gdi.scaleLength(150), gdi.getCX() + gdi.scaleLength(200));
set< pair<wstring, int> > rToList;
set<int> usedR;
wstring anon = lang.tl("N.N.");
set<int> clubs;
for (int i = 0; i < t->getNumRunners(); i++) {
if (pc->getLegRunnerIndex(i) == 0) {
pRunner r = t->getRunner(i);
if (!r)
continue;
if (r->getClubId() > 0)
clubs.insert(r->getClubId()); // Combination teams
if (r && r->getName() != anon) {
rToList.insert( make_pair(r->getName(), r->getId()));
}
}
}
showRunners(gdi, "Från laget", rToList, limit, usedR);
vector<pRunner> clsR;
oe->getRunners(0, 0, clsR, true);
rToList.clear();
if (t->getClubId() > 0)
clubs.insert(t->getClubId());
if (!clubs.empty()) {
for (size_t i = 0; i < clsR.size(); i++) {
if (clsR[i]->getRaceNo() > 0)
continue;
if (clubs.count(clsR[i]->getClubId()) == 0)
continue;
if (clsR[i]->getClassId(false) != t->getClassId(false))
continue;
if (clsR[i]->getName() == anon)
continue;
rToList.insert( make_pair(clsR[i]->getName(), clsR[i]->getId()));
}
showRunners(gdi, "Från klassen", rToList, limit, usedR);
rToList.clear();
for (size_t i = 0; i < clsR.size(); i++) {
if (clsR[i]->getRaceNo() > 0)
continue;
if (clubs.count(clsR[i]->getClubId()) == 0)
continue;
if (clsR[i]->getName() == anon)
continue;
rToList.insert( make_pair(clsR[i]->getName(), clsR[i]->getId()));
}
showRunners(gdi, "Från klubben", rToList, limit, usedR);
}
vector< pair<wstring, size_t> > otherR;
for (size_t i = 0; i < clsR.size(); i++) {
if (clsR[i]->getRaceNo() > 0)
continue;
if (clsR[i]->getName() == anon)
continue;
if (usedR.count(clsR[i]->getId()))
continue;
const wstring &club = clsR[i]->getClub();
wstring id = clsR[i]->getName() + L", " + clsR[i]->getClass(false);
if (!club.empty())
id += L" (" + club + L")";
otherR.push_back(make_pair(id, clsR[i]->getId()));
}
gdi.fillDown();
if (!otherR.empty()) {
gdi.addString("", 1, "Övriga");
gdi.fillRight();
gdi.addSelection("SelectR", 250, 400, TeamCB);
gdi.addButton("SelectRunner", "OK", TeamCB).setExtra(leg);
gdi.fillDown();
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
gdi.setItems("SelectR", otherR);
}
else {
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
}
gdi.popX();
renderComments(t, gdi);
gdi.refresh();
}
else if (bi.id=="SelectRunner") {
ListBoxInfo lbi;
gdi.getSelectedItem("SelectR", lbi);
pRunner r = oe->getRunner(lbi.data, 0);
if (r == 0) {
throw meosException("Ingen deltagare vald.");
}
int leg = gdi.getDataInt("Leg");
pTeam t = oe->getTeam(teamId);
processChangeRunner(gdi, t, leg, r);
}
else if (bi.id=="Add") {
if (teamId>0) {
wstring name = gdi.getText("Name");
pTeam t = oe->getTeam(teamId);
if (!name.empty() && t && t->getName() != name) {
if (gdi.ask(L"Vill du lägga till laget 'X'?#" + name)) {
t = oe->addTeam(name);
teamId = t->getId();
}
save(gdi, false);
return true;
}
save(gdi, false);
}
pTeam t = oe->addTeam(oe->getAutoTeamName());
ListBoxInfo lbi;
gdi.getSelectedItem("RClass", lbi);
int clsId;
if (signed(lbi.data)>0)
clsId = lbi.data;
else
clsId = oe->getFirstClassId(true);
pClass pc = oe->getClass(clsId);
if (pc) {
pair<int, wstring> snoBib = pc->getNextBib();
if (snoBib.first > 0) {
t->setBib(snoBib.second, snoBib.first, true);
}
}
t->setClassId(clsId, true);
t->synchronize();
fillTeamList(gdi);
//oe->fillTeams(gdi, "Teams");
selectTeam(gdi, t);
//selectTeam(gdi, 0);
//gdi.selectItemByData("Teams", -1);
gdi.setInputFocus("Name", true);
}
else if (bi.id=="Remove") {
DWORD tid=teamId;
if (tid==0)
throw std::exception("Inget lag valt.");
pTeam t = oe->getTeam(tid);
if (!t || t->isRemoved()) {
selectTeam(gdi, 0);
}
else if (gdi.ask(L"Vill du verkligen ta bort laget?")) {
vector<int> runners;
vector<int> noRemove;
for (int k = 0; k < t->getNumRunners(); k++) {
pRunner r = t->getRunner(k);
if (r && r->getRaceNo() == 0) {
if (r->getCard() == 0)
runners.push_back(r->getId());
else
noRemove.push_back(r->getId());
}
}
oe->removeTeam(tid);
oe->removeRunner(runners);
for (size_t k = 0; k<noRemove.size(); k++) {
pRunner r = oe->getRunner(noRemove[k], 0);
if (r) {
r->setClassId(0, true);
r->synchronize();
}
}
fillTeamList(gdi);
//oe->fillTeams(gdi, "Teams");
selectTeam(gdi, 0);
gdi.selectItemByData("Teams", -1);
}
}
}
else if (type == GUI_LINK) {
TextInfo ti = dynamic_cast<TextInfo &>(*(BaseInfo *)data);
if (ti.id == "SelectR") {
int leg = gdi.getDataInt("Leg");
pTeam t = oe->getTeam(teamId);
int rid = ti.getExtraInt();
pRunner r = oe->getRunner(rid, 0);
processChangeRunner(gdi, t, leg, r);
}
}
else if (type==GUI_LISTBOX) {
ListBoxInfo bi=*(ListBoxInfo *)data;
if (bi.id=="Teams") {
if (gdi.isInputChanged("")) {
pTeam t = oe->getTeam(teamId);
bool newName = t && t->getName() != gdi.getText("Name");
bool newBib = gdi.hasWidget("StartNo") && t && t->getBib() != gdi.getText("StartNo");
save(gdi, true);
if (newName || newBib) {
fillTeamList(gdi);
}
}
pTeam t=oe->getTeam(bi.data);
selectTeam(gdi, t);
}
else if (bi.id=="RClass") { //New class selected.
DWORD tid=teamId;
//gdi.getData("TeamID", tid);
if (tid){
pTeam t=oe->getTeam(tid);
pClass pc=oe->getClass(bi.data);
if (pc && pc->getNumDistinctRunners() == shownDistinctRunners &&
pc->getNumStages() == shownRunners) {
// Keep team setup, i.e. do nothing
}
else if (t && pc && (t->getClassId(false)==bi.data
|| t->getNumRunners()==pc->getNumStages()) )
loadTeamMembers(gdi, 0,0,t);
else
loadTeamMembers(gdi, bi.data, 0, t);
}
else loadTeamMembers(gdi, bi.data, 0, 0);
}
else {
ListBoxInfo lbi;
gdi.getSelectedItem("RClass", lbi);
if (signed(lbi.data)>0){
pClass pc=oe->getClass(lbi.data);
if (pc) {
for(unsigned i=0;i<pc->getNumStages();i++){
char bf[16];
sprintf_s(bf, "R%d", i);
if (bi.id==bf){
pRunner r=oe->getRunner(bi.data, 0);
if (r) {
sprintf_s(bf, "SI%d", i);
int cno = r->getCardNo();
gdi.setText(bf, cno > 0 ? itow(cno) : L"");
warnDuplicateCard(gdi, bf, cno, r);
}
}
}
}
}
}
}
else if (type==GUI_INPUTCHANGE) {
InputInfo &ii=*(InputInfo *)data;
pClass pc=oe->getClass(classId);
if (pc){
for(unsigned i=0;i<pc->getNumStages();i++){
char bf[16];
sprintf_s(bf, "R%d", i);
if (ii.id == bf) {
for (unsigned k=i+1; k<pc->getNumStages(); k++) {
if (pc->getLegRunner(k)==i) {
sprintf_s(bf, "R%d", k);
gdi.setText(bf, ii.text);
}
}
break;
}
}
}
if (ii.id == "DirName" || ii.id == "DirCard") {
int cno = gdi.getTextNo("DirCard");
if (cno > 0 && oe->hasHiredCardData()) {
gdi.check("DirRent", oe->isHiredCard(cno));
}
gdi.setInputStatus("DirOK", !gdi.getText("DirName").empty() && cno > 0);
}
if (oe->useRunnerDb() && ii.id == "DirName") {
auto &db = oe->getRunnerDatabase();
bool show = false;
if (ii.text.length() > 1) {
auto dbClub = TabRunner::extractClub(oe, gdi);
auto rw = db.getRunnerSuggestions(ii.text, dbClub ? dbClub->getId() : 0, 10);
if (dbClub != nullptr && rw.empty()) // Default: Any club
rw = db.getRunnerSuggestions(ii.text, 0, 10);
if (!rw.empty()) {
auto &ac = gdi.addAutoComplete(ii.id);
ac.setAutoCompleteHandler(this);
ac.setData(TabSI::getRunnerAutoCompelete(db, rw, dbClub));
ac.show();
show = true;
}
}
if (!show) {
gdi.clearAutoComplete(ii.id);
}
}
}
else if (type == GUI_INPUT) {
InputInfo &ii=*(InputInfo *)data;
if (ii.id == "DirName" || ii.id == "DirCard") {
gdi.setInputStatus("DirOK", !gdi.getText("DirName").empty() &&
gdi.getTextNo("DirCard") > 0);
}
if (ii.id == "DirCard") {
int cno = gdi.getTextNo("DirCard");
if (cno > 0 && gdi.getText("DirName").empty()) {
bool matched = false;
pRunner r = oe->getRunnerByCardNo(cno, 0, oEvent::CardLookupProperty::ForReadout);
if (r && (r->getStatus() == StatusUnknown || r->getStatus() == StatusDNS) ) {
// Switch to exactly this runner. Has not run before
gdi.setText("DirName", r->getName())->setExtra(r->getId());
matched = true;
}
else {
r = oe->getRunnerByCardNo(cno, 0, oEvent::CardLookupProperty::Any);
if (r) {
// Copy only the name.
gdi.setText("DirName", r->getName())->setExtra(0);
matched = true;
}
else if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::RunnerDb)) {
const RunnerWDBEntry *rdb = oe->getRunnerDatabase().getRunnerByCard(cno);
if (rdb) {
wstring name;
rdb->getName(name);
gdi.setText("DirName", name)->setExtra(0);
matched = true;
}
}
}
if (r)
gdi.check("DirRent", r->getDCI().getInt("CardFee") != 0);
if (matched)
gdi.setInputStatus("DirOK", true);
}
}
pClass pc=oe->getClass(classId);
if (pc) {
for (unsigned i = 0; i < pc->getNumStages(); i++) {
if (ii.id == "SI" + itos(i)) {
int cardNo = _wtoi(ii.text.c_str());
pTeam t = oe->getTeam(teamId);
if (t) {
pRunner r = t->getRunner(i);
if (ii.changedInput() && oe->hasHiredCardData()) {
gdi.check("RENT" + itos(i), oe->isHiredCard(cardNo));
}
warnDuplicateCard(gdi, ii.id, cardNo, r);
}
break;
}
else if (ii.id == "R" + itos(i)) {
enableRunner(gdi, i, !ii.text.empty());
}
}
}
}
else if (type == GUI_COMBOCHANGE) {
ListBoxInfo &combo = *(ListBoxInfo *)(data);
bool show = false;
if (oe->useRunnerDb() && combo.id == "Club" && combo.text.length() > 1) {
auto clubs = oe->getRunnerDatabase().getClubSuggestions(combo.text, 20);
if (!clubs.empty()) {
auto &ac = gdi.addAutoComplete(combo.id);
ac.setAutoCompleteHandler(this);
vector<AutoCompleteRecord> items;
for (auto club : clubs)
items.emplace_back(club->getDisplayName(), -int(items.size()), club->getName(), club->getId());
ac.setData(items);
ac.show();
show = true;
}
}
if (!show) {
gdi.clearAutoComplete(combo.id);
}
}
else if (type==GUI_CLEAR) {
if (teamId>0)
save(gdi, true);
if (gdioutput* gdi_settings = getExtraWindow("comments", false); gdi_settings)
gdi_settings->closeWindow();
return true;
}
else if (type==GUI_POSTCLEAR) {
// Clear out SI-link
TabSI &tsi = dynamic_cast<TabSI &>(*gdi.getTabs().get(TSITab));
tsi.setCardNumberField("");
return true;
}
return 0;
}
void TabTeam::loadTeamMembers(gdioutput &gdi, int ClassId, int ClubId, pTeam t)
{
if (ClassId==0)
if (t) ClassId=t->getClassId(false);
classId=ClassId;
gdi.restore("",false);
pClass pc=oe->getClass(ClassId);
if (!pc) return;
shownRunners = pc->getNumStages();
shownDistinctRunners = pc->getNumDistinctRunners();
gdi.setRestorePoint();
gdi.newColumn();
gdi.fillDown();
char bf[16];
char bf_si[16];
int xp = gdi.getCX();
int yp = gdi.getCY();
int numberPos = xp;
xp += gdi.scaleLength(25);
const int dxIn[6] = {0, 184, 220, 300, 326, 364};
int dx[6];
dx[0] = 0;
for (int i = 1; i < 6; i++) {
if (i == 1)
dx[i] = gdi.getInputDimension(18).first + gdi.scaleLength(4);
else if (i == 3)
dx[i] = dx[i-1] + gdi.getInputDimension(7).first + gdi.scaleLength(4);
else
dx[i] = dx[i-1] + gdi.scaleLength(dxIn[i] - dxIn[i-1]);
}
gdi.addString("", yp, xp + dx[0], 0, "Namn:");
gdi.addString("", yp, xp + dx[2], 0, "Bricka:");
gdi.addString("", yp, xp + dx[3]- gdi.scaleLength(5), 0, "Hyrd:");
gdi.addString("", yp, xp + dx[5], 0, "Status:");
gdi.dropLine(0.5);
const int textOffY = gdi.scaleLength(4);
for (unsigned i = 0; i < pc->getNumStages(); i++) {
yp = gdi.getCY() - gdi.scaleLength(3);
sprintf_s(bf, "R%d", i);
gdi.pushX();
bool hasSI = false;
gdi.addStringUT(yp + textOffY, numberPos, 0, pc->getLegNumber(i) + L".");
if (pc->getLegRunner(i) == i) {
gdi.addInput(xp + dx[0], yp, bf, L"", 18, TeamCB);//Name
gdi.addButton(xp + dx[1], yp - 2, gdi.scaleLength(28), "DR" + itos(i), L"\u21C6", TeamCB, L"Knyt löpare till sträckan.", false, false); // Change
sprintf_s(bf_si, "SI%d", i);
hasSI = true;
gdi.addInput(xp + dx[2], yp, bf_si, L"", 7, TeamCB).setExtra(i); //Si
gdi.addCheckbox(xp + dx[3], yp + gdi.scaleLength(10), "RENT" + itos(i), "", 0, false); //Rentcard
}
else {
gdi.addInput(xp + dx[0], yp, bf, L"", 18, 0);//Name
gdi.disableInput(bf);
}
gdi.addButton(xp + dx[4], yp - 2, gdi.scaleLength(28), "MR" + itos(i), L"\u21BA", TeamCB, L"Redigera deltagaren.", false, false); // Change
gdi.addString(("STATUS" + itos(i)).c_str(), yp + textOffY, xp + dx[5], 0, "#MMMMMMMMMMMMMMMM");
gdi.setText("STATUS" + itos(i), L"", false);
gdi.dropLine(0.5);
gdi.popX();
if (t) {
pRunner r = t->getRunner(i);
enableRunner(gdi, i, r != nullptr);
if (r) {
gdi.setText(bf, r->getNameRaw())->setExtra(r->getId());
r->getPlace(true); // Ensure computed status is up-to-date
if (hasSI) {
int cno = r->getCardNo();
gdi.setText(bf_si, cno > 0 ? itow(cno) : L"");
warnDuplicateCard(gdi, bf_si, cno, r);
gdi.check("RENT" + itos(i), r->getDCI().getInt("CardFee") != 0);
}
string sid = "STATUS" + itos(i);
if (r->statusOK(true, true)) {
TextInfo * ti = (TextInfo *)gdi.setText(sid, L"OK, " + r->getRunningTimeS(true, SubSecond::Auto), false);
if (ti)
ti->setColor(colorGreen);
}
else if (r->getStatusComputed(true) != StatusUnknown) {
TextInfo * ti = (TextInfo *)gdi.setText(sid, r->getStatusS(false, true) + L", " + r->getRunningTimeS(true, SubSecond::Auto), false);
if (ti)
ti->setColor(colorRed);
}
}
}
}
if (t)
forkingKey(gdi, t);
gdi.refresh();
}
void TabTeam::forkingKey(gdioutput& gdi, pTeam t) {
pClass pc = t->getClassRef(false);
gdi.pushX();
gdi.setRestorePoint("SelectR");
gdi.addString("", 0, "help:7618");
gdi.dropLine();
int numF = pc->getNumForks();
if (numF > 1 && t) {
gdi.addString("", 1, "Gafflingsnyckel X#" + itos(1 + (max(t->getStartNo() - 1, 0) % numF))).setColor(colorGreen);
wstring crsList = getForking(pc, t->getStartNo(), 50);
if (!crsList.empty()) {
gdi.addStringUT(0, crsList);
gdi.dropLine(0.5);
gdi.setRestorePoint("ChangeKey");
gdi.addButton("ChangeKey", "Ändra lagets gaffling", TeamCB);
}
}
renderComments(t, gdi);
}
void TabTeam::renderComments(const pTeam& t, gdioutput& gdi) {
if (t->getNumRunners() > 5)
TabRunner::renderComments(gdi, *t, true, false);
else {
gdi.dropLine(1); // Leave room for the below
TabRunner::renderComments(gdi, *t, false, false);
}
}
void TabTeam::enableRunner(gdioutput& gdi, int index, bool enable) {
string ix = itos(index);
string si = ("SI" + ix);
bool hasSI = enable || !gdi.getText(si, true).empty();
gdi.setInputStatus(si.c_str(), hasSI, true);
gdi.setInputStatus(("RENT" + ix).c_str(), hasSI, true);
gdi.setInputStatus(("MR" + ix).c_str(), enable, true);
}
bool TabTeam::loadPage(gdioutput &gdi, int id) {
teamId = id;
return loadPage(gdi);
}
bool TabTeam::loadPage(gdioutput &gdi)
{
shownRunners = 0;
shownDistinctRunners = 0;
oe->checkDB();
oe->reEvaluateAll(set<int>(), true);
gdi.selectTab(tabId);
gdi.clearPage(false);
if (currentMode == 1) {
addToolbar(gdi);
gdi.dropLine(1);
gdi.addTable(oTeam::getTable(oe), gdi.getCX(), gdi.getCY());
return true;
}
gdi.fillDown();
gdi.addString("", boldLarge, "Lag(flera)");
gdi.pushX();
gdi.fillRight();
gdi.registerEvent("SearchRunner", teamSearchCB).setKeyCommand(KC_FIND);
gdi.registerEvent("SearchRunnerBack", teamSearchCB).setKeyCommand(KC_FINDBACK);
gdi.addInput("SearchText", L"", 17, teamSearchCB, L"", L"Sök på namn, bricka eller startnummer.").isEdit(false).setBgColor(colorLightCyan).ignore(true);
gdi.dropLine(-0.2);
gdi.addButton("ShowAll", "Visa alla", TeamCB).isEdit(false);
gdi.dropLine(2);
gdi.popX();
gdi.fillDown();
gdi.addListBox("Teams", 250, 440, TeamCB, L"", L"").isEdit(false).ignore(true);
gdi.setInputFocus("Teams");
fillTeamList(gdi);
int posXForButtons = gdi.getCX();
int posYForButtons = gdi.getCY();
gdi.newColumn();
gdi.fillDown();
gdi.pushX();
gdi.addInput("Name", L"", 24, 0, L"Lagnamn:");
gdi.fillRight();
bool drop = false;
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Bib)) {
gdi.addInput("StartNo", L"", 4, 0, L"Nr:", L"Nummerlapp");
drop = oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy);
}
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Clubs)) {
gdi.addCombo("Club", 180, 300, TeamCB, L"Klubb:");
oe->fillClubs(gdi, "Club");
drop = true;
}
if (drop) {
gdi.dropLine(3);
gdi.popX();
}
gdi.addSelection("RClass", 170, 300, TeamCB, L"Klass:");
oe->fillClasses(gdi, "RClass", {make_pair(lang.tl("Ny klass"), 0)}, oEvent::extraNone, oEvent::filterOnlyMulti);
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy))
gdi.addInput("Fee", L"", 5, 0, L"Avgift:");
gdi.popX();
gdi.fillDown();
gdi.dropLine(3);
gdi.pushX();
gdi.fillRight();
gdi.addInput("Start", L"", 8, 0, L"Starttid:");
gdi.addInput("Finish", L"", 8, 0, L"Måltid:");
const bool timeAdjust = oe->getMeOSFeatures().hasFeature(MeOSFeatures::TimeAdjust);
const bool pointAdjust = oe->getMeOSFeatures().hasFeature(MeOSFeatures::PointAdjust);
if (timeAdjust || pointAdjust) {
gdi.dropLine(3);
gdi.popX();
if (timeAdjust) {
gdi.addInput("TimeAdjust", L"", 8, 0, L"Tidstillägg:");
}
if (pointAdjust) {
gdi.addInput("PointAdjust", L"", 8, 0, L"Poängavdrag:");
}
}
/*if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::TimeAdjust)) {
gdi.addInput("TimeAdjust", "", 5, 0, "Tidstillägg:");
}
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::PointAdjust)) {
gdi.addInput("PointAdjust", "", 5, 0, "Poängavdrag:");
}*/
gdi.fillDown();
gdi.dropLine(3);
gdi.popX();
gdi.pushX();
gdi.fillRight();
gdi.addInput("Time", L"", 6, 0, L"Tid:").isEdit(false).ignore(true);
gdi.disableInput("Time");
gdi.fillDown();
gdi.addSelection("Status", 100, 160, 0, L"Status:", L"tooltip_explain_status");
oe->fillStatus(gdi, "Status");
gdi.popX();
gdi.selectItemByData("Status", 0);
gdi.dropLine(0.4);
if (oe->hasAnyRestartTime()) {
gdi.addCheckbox("NoRestart", "Förhindra omstart", 0, false, "Förhindra att laget deltar i någon omstart");
}
gdi.addString("TeamInfo", 0, " ").setColor(colorRed);
const bool multiDay = oe->hasPrevStage();
if (multiDay) {
gdi.dropLine(1.5);
int xx = gdi.getCX();
int yy = gdi.getCY();
gdi.dropLine(0.5);
gdi.fillDown();
int dx = int(gdi.getLineHeight()*0.7);
int ccx = xx + dx;
gdi.setCX(ccx);
gdi.addString("", 1, "Resultat från tidigare etapper");
gdi.dropLine(0.3);
gdi.fillRight();
gdi.addSelection("StatusIn", 100, 160, 0, L"Status:", L"tooltip_explain_status");
oe->fillStatus(gdi, "StatusIn");
gdi.selectItemByData("Status", 0);
gdi.addInput("PlaceIn", L"", 5, 0, L"Placering:");
int xmax = gdi.getCX() + dx;
gdi.setCX(ccx);
gdi.dropLine(3);
gdi.addInput("TimeIn", L"", 5, 0, L"Tid:");
if (oe->hasRogaining()) {
gdi.addInput("PointIn", L"", 5, 0, L"Poäng:");
}
gdi.dropLine(3);
RECT rc;
rc.right = xx;
rc.top = yy;
rc.left = max(xmax, gdi.getWidth()-dx);
rc.bottom = gdi.getCY();
gdi.addRectangle(rc, colorLightGreen, true, false);
gdi.popX();
}
TabRunner::addExtraFields(*oe, gdi, oEvent::ExtraFieldContext::Team);
gdi.dropLine(0.5);
gdi.popX();
gdi.fillRight();
gdi.addButton("Save", "Spara", TeamCB, "help:save");
gdi.disableInput("Save");
gdi.addButton("Undo", "Ångra", TeamCB);
gdi.disableInput("Undo");
gdi.popX();
gdi.dropLine(2.5);
gdi.addButton("Remove", "Radera", TeamCB);
gdi.disableInput("Remove");
gdi.addButton("Add", "Nytt lag", TeamCB);
gdi.popX();
gdi.dropLine(2.2);
gdi.addButton("EditAnnotation", L"Kommentar >>", TeamCB, L"Lägg till eller redigera kommentarer om laget.");
gdi.setOnClearCb(TeamCB);
addToolbar(gdi);
RECT rc;
rc.left = posXForButtons;
rc.top = posYForButtons;
gdi.setCY(posYForButtons + gdi.scaleLength(10));
gdi.setCX(posXForButtons + gdi.getLineHeight());
gdi.fillDown();
gdi.addString("", fontMediumPlus, "Verktyg");
gdi.dropLine(0.3);
gdi.fillRight();
gdi.addButton("ImportTeams", "Importera laguppställningar", TeamCB);
gdi.addButton("AddTeamMembers", "Skapa anonyma lagmedlemmar", TeamCB, "Fyll obesatta sträckor i alla lag med anonyma tillfälliga lagmedlemmar (N.N.)");
rc.right = gdi.getCX() + gdi.getLineHeight();
gdi.dropLine(2);
rc.bottom = gdi.getHeight();
gdi.addRectangle(rc, colorLightBlue);
gdi.setRestorePoint();
selectTeam(gdi, oe->getTeam(teamId));
gdi.refresh();
return true;
}
void TabTeam::fillTeamList(gdioutput &gdi) {
timeToFill = GetTickCount();
oe->fillTeams(gdi, "Teams");
timeToFill = GetTickCount() - timeToFill;
lastSearchExpr = L"";
((InputInfo *)gdi.setText("SearchText", getSearchString()))->setFgColor(colorGreyBlue);
lastFilter.clear();
}
const wstring &TabTeam::getSearchString() const {
return lang.tl(L"Sök (X)#Ctrl+F");
}
void TabTeam::addToolbar(gdioutput &gdi) const {
const int button_w=gdi.scaleLength(130);
gdi.addButton(2+0*button_w, 2, button_w, "FormMode",
"Formulärläge", TeamCB, "", false, true).fixedCorner();
gdi.check("FormMode", currentMode==0);
gdi.addButton(2+1*button_w, 2, button_w, "TableMode",
"Tabelläge", TeamCB, "", false, true).fixedCorner();
gdi.check("TableMode", currentMode==1);
}
void TabTeam::showTeamImport(gdioutput &gdi) {
gdi.clearPage(false);
gdi.addString("", boldLarge, "Importera laguppställningar");
gdi.addString("", 10, "help:teamlineup");
gdi.dropLine();
gdi.setRestorePoint("TeamLineup");
gdi.pushX();
gdi.fillRight();
gdi.addInput("FileName", L"", 40, 0, L"Filnamn:");
gdi.dropLine(0.9);
gdi.addButton("Browse", "Bläddra", TeamCB).setExtra(L"FileName");
gdi.dropLine(3);
gdi.popX();
gdi.fillDown();
gdi.addCheckbox("OnlyExisting", "Använd befintliga deltagare", 0, false,
"Knyt redan anmälda deltagare till laget (identifiera genom namn och/eller bricka)");
gdi.fillRight();
gdi.addButton("DoImportTeams", "Importera", TeamCB).setDefault();
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
gdi.refresh();
}
void TabTeam::doTeamImport(gdioutput &gdi) {
wstring file = gdi.getText("FileName");
bool useExisting = gdi.isChecked("OnlyExisting");
csvparser csv;
map<wstring, int> classNameToNumber;
vector<pClass> cls;
oe->getClasses(cls, true);
for (size_t k = 0; k < cls.size();k++) {
classNameToNumber[cls[k]->getName()] = cls[k]->getNumStages();
}
gdi.fillDown();
csv.importTeamLineup(file, classNameToNumber, teamLineup);
gdi.restore("TeamLineup", false);
gdi.dropLine();
for (size_t k = 0; k < teamLineup.size(); k++) {
wstring tdesc = teamLineup[k].teamClass + L", " + teamLineup[k].teamName;
if (!teamLineup[k].teamClub.empty())
tdesc += L", " + teamLineup[k].teamClub;
gdi.addStringUT(1, tdesc);
for (size_t j = 0; j < teamLineup[k].members.size(); j++) {
TeamLineup::TeamMember &member = teamLineup[k].members[j];
if (member.name.empty())
continue;
wstring mdesc = L" " + itow(j+1) + L". ";
bool warn = false;
if (useExisting) {
pRunner r = findRunner(member.name, member.cardNo);
if (r != 0)
mdesc += r->getCompleteIdentification();
else {
mdesc += member.name + lang.tl(L" (ej funnen)");
warn = true;
}
}
else {
mdesc += member.name + L" (" + itow(member.cardNo) + L") " + member.club;
}
if (!member.course.empty()) {
if (oe->getCourse(member.course))
mdesc += L" : " + member.course;
else {
mdesc += L" : " + lang.tl(L"Banan saknas");
warn = true;
}
}
if (!member.cls.empty()) {
if (oe->getClass(member.cls))
mdesc += L" [" + member.cls + L"]";
else {
mdesc += L" " + lang.tl(L"Klassen saknas");
warn = true;
}
}
TextInfo &ti = gdi.addStringUT(0, mdesc);
if (warn)
ti.setColor(colorRed);
}
gdi.dropLine();
}
gdi.fillRight();
gdi.addButton("ImportTeams", "<< Bakåt", TeamCB);
gdi.addButton("SaveTeams", "Spara laguppställningar", TeamCB).setDefault().setExtra(useExisting);
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
gdi.refresh();
}
void TabTeam::saveTeamImport(gdioutput &gdi, bool useExisting) {
for (size_t k = 0; k < teamLineup.size(); k++) {
pClub club = !teamLineup[k].teamClub.empty() ? oe->getClubCreate(0, teamLineup[k].teamClub) : 0;
pTeam t = oe->addTeam(teamLineup[k].teamName, club ? club->getId() : 0, oe->getClass(teamLineup[k].teamClass)->getId());
for (size_t j = 0; j < teamLineup[k].members.size(); j++) {
TeamLineup::TeamMember &member = teamLineup[k].members[j];
if (member.name.empty())
continue;
pRunner r = 0;
if (useExisting) {
r = findRunner(member.name, member.cardNo);
if (r && !member.course.empty()) {
pCourse pc = oe->getCourse(member.course);
r->setCourseId(pc ? pc->getId() : 0);
}
if (r && !member.cls.empty()) {
pClass rcls = oe->getClass(member.cls);
r->setClassId(rcls ? rcls->getId() : 0, true);
}
}
else {
r = oe->addRunner(member.name, member.club, 0, member.cardNo, L"", false);
if (r && !member.course.empty()) {
pCourse pc = oe->getCourse(member.course);
r->setCourseId(pc ? pc->getId() : 0);
}
if (r && !member.cls.empty()) {
pClass rcls = oe->getClass(member.cls);
r->setClassId(rcls ? rcls->getId() : 0, true);
}
}
t->setRunner(j, r, false);
if (r)
r->synchronize(true);
}
t->synchronize();
gdi.dropLine();
}
loadPage(gdi);
}
pRunner TabTeam::findRunner(const wstring &name, int cardNo) const {
wstring n = canonizeName(name.c_str());
if (cardNo != 0) {
vector<pRunner> pr;
oe->getRunnersByCardNo(cardNo, true, oEvent::CardLookupProperty::Any, pr);
for (size_t k = 0; k < pr.size(); k++) {
wstring a = canonizeName(pr[k]->getName().c_str());
if (a == n)
return pr[k];
}
}
else {
vector<pRunner> pr;
oe->getRunners(0, 0, pr, false);
for (size_t k = 0; k < pr.size(); k++) {
wstring a = canonizeName(pr[k]->getName().c_str());
if (a == n)
return pr[k];
}
}
return 0;
}
void TabTeam::showAddTeamMembers(gdioutput &gdi) {
gdi.clearPage(false);
gdi.addString("", boldLarge, "Tillsätt tillfälliga anonyma lagmedlemmar");
gdi.addString("", 10, "help:anonymous_team");
gdi.dropLine();
gdi.pushX();
gdi.fillDown();
gdi.addInput("Name", lang.tl("N.N."), 24, 0, L"Anonymt namn:");
gdi.fillDown();
gdi.addCheckbox("OnlyRequired", "Endast på obligatoriska sträckor", 0, true);
gdi.addCheckbox("WithFee", "Med anmälningsavgift (lagets klubb)", 0, true);
gdi.fillRight();
gdi.addButton("DoAddTeamMembers", "Tillsätt", TeamCB).setDefault();
gdi.addButton("Cancel", "Avbryt", TeamCB).setCancel();
gdi.refresh();
}
void TabTeam::doAddTeamMembers(gdioutput &gdi) {
vector<pTeam> t;
oe->getTeams(0, t, true);
bool onlyReq = gdi.isChecked("OnlyRequired");
bool withFee = gdi.isChecked("WithFee");
wstring nn = gdi.getText("Name");
for (size_t k = 0; k < t.size(); k++) {
pTeam mt = t[k];
pClass cls = mt->getClassRef(false);
if (cls == 0)
continue;
bool ch = false;
for (int j = 0; j < mt->getNumRunners(); j++) {
if (mt->getRunner(j) == 0) {
LegTypes lt = cls->getLegType(j);
if (onlyReq && lt == LTExtra || lt == LTIgnore || lt == LTParallelOptional)
continue;
pRunner r = 0;
if (withFee) {
r = oe->addRunner(nn, mt->getClubId(), 0, 0, L"", false);
r->synchronize();
mt->setRunner(j, r, false);
r->addClassDefaultFee(true);
}
else {
r = oe->addRunnerVacant(0);
r->setName(nn, false);
//oe->addRunner(nn, oe->getVacantClub(), 0, 0, 0, false);
r->synchronize();
mt->setRunner(j, r, false);
}
ch = true;
}
}
if (ch) {
mt->apply(oBase::ChangeType::Update, nullptr);
mt->synchronize();
for (int j = 0; j < mt->getNumRunners(); j++) {
if (mt->getRunner(j) != 0) {
mt->getRunner(j)->synchronize();
}
}
}
}
loadPage(gdi);
}
void TabTeam::showRunners(gdioutput &gdi, const char *title,
const set< pair<wstring, int> > &rToList,
int limitX, set<int> &usedR) {
if (rToList.empty())
return;
bool any = false;
for(set< pair<wstring, int> >::const_iterator it = rToList.begin(); it != rToList.end(); ++it) {
if (usedR.count(it->second))
continue;
usedR.insert(it->second);
if (!any) {
gdi.dropLine(0.2);
int YC = gdi.getCY() + gdi.getLineHeight() / 2;
int CX = gdi.getCX();
int hy = gdi.scaleLength(2);
RECT rc = { CX, YC-hy, CX + gdi.getLineHeight(), YC + hy };
gdi.addRectangle(rc, GDICOLOR::colorMediumDarkRed, false);
gdi.addString("", boldText|Capitalize, title);
gdi.dropLine(1.2);
gdi.popX();
any = true;
}
if (gdi.getCX() > limitX) {
gdi.dropLine(1.5);
gdi.popX();
}
gdi.addString("SelectR", 0, L"#" + it->first, TeamCB).setColor(colorRed).setExtra(it->second);
}
if (any) {
gdi.dropLine(2);
gdi.popX();
}
}
void TabTeam::processChangeRunner(gdioutput &gdi, pTeam t, int leg, pRunner r) {
if (r && t && leg < t->getNumRunners()) {
pRunner oldR = t->getRunner(leg);
gdioutput::AskAnswer ans = gdioutput::AskAnswer::AnswerNo;
if (r == oldR) {
gdi.restore("SelectR");
forkingKey(gdi, t);
return;
}
else if (oldR) {
if (r->getTeam()) {
ans = gdi.askCancel(L"Vill du att X och Y byter sträcka?#" +
r->getName() + L"#" + oldR->getName());
}
else {
ans = gdi.askCancel(L"Vill du att X tar sträckan istället för Y?#" +
r->getName() + L"#" + oldR->getName());
}
}
else {
ans = gdi.askCancel(L"Vill du att X går in i laget?#" + r->getName());
}
if (ans == gdioutput::AskAnswer::AnswerNo)
return;
else if (ans == gdioutput::AskAnswer::AnswerCancel) {
gdi.restore("SelectR");
return;
}
save(gdi, true);
vector<int> mp;
switchRunners(t, leg, r, oldR);
/*if (r->getTeam()) {
pTeam otherTeam = r->getTeam();
int otherLeg = r->getLegNumber();
otherTeam->setRunner(otherLeg, oldR, true);
if (oldR)
oldR->evaluateCard(true, mp, 0, true);
otherTeam->checkValdParSetup();
otherTeam->apply(true, 0, false);
otherTeam->synchronize(true);
}
else if (oldR) {
t->setRunner(leg, 0, false);
t->synchronize(true);
oldR->setClassId(r->getClassId(), true);
oldR->evaluateCard(true, mp, 0, true);
oldR->synchronize(true);
}
t->setRunner(leg, r, true);
r->evaluateCard(true, mp, 0, true);
t->checkValdParSetup();
t->apply(true, 0, false);
t->synchronize(true);*/
loadPage(gdi);
}
}
void TabTeam::switchRunners(pTeam t, int leg, pRunner r, pRunner oldR) {
vector<int> mp;
bool removeAnnonumousTeamMember = false;
int crsIdR = r->getCourseId();
int crsIdROld = oldR ? oldR->getCourseId() : 0;
if (r->getTeam()) {
pTeam otherTeam = r->getTeam();
int otherLeg = r->getLegNumber();
otherTeam->setRunner(otherLeg, oldR, true);
if (oldR) {
oldR->setCourseId(crsIdR);
oldR->evaluateCard(true, mp, 0, oBase::ChangeType::Update);
}
otherTeam->checkValdParSetup();
otherTeam->apply(oBase::ChangeType::Update, nullptr);
otherTeam->adjustMultiRunners();
otherTeam->synchronize(true);
}
else if (oldR) {
t->setRunner(leg, 0, false);
t->synchronize(true);
if (r->getClassRef(false) && r->getClassRef(false)->isTeamClass())
oldR->setClassId(0, false);
else
oldR->setClassId(r->getClassId(false), true);
removeAnnonumousTeamMember = oldR->isAnnonumousTeamMember();
oldR->setCourseId(crsIdR);
oldR->evaluateCard(true, mp, 0, oBase::ChangeType::Update);
oldR->synchronize(true);
}
t->setRunner(leg, r, true);
r->setCourseId(crsIdROld);
r->evaluateCard(true, mp, 0, oBase::ChangeType::Update);
t->checkValdParSetup();
t->apply(oBase::ChangeType::Update, nullptr);
t->adjustMultiRunners();
t->synchronize(true);
if (removeAnnonumousTeamMember)
oe->removeRunner({ oldR->getId() });
}
void TabTeam::clearCompetitionData() {
shownRunners = 0;
shownDistinctRunners = 0;
teamId = 0;
inputId = 0;
timeToFill = 0;
currentMode = 0;
}
bool TabTeam::warnDuplicateCard(gdioutput &gdi, string id, int cno, pRunner r) {
pRunner warnCardDupl = 0;
if (r && !r->getCard() && cno != 0) {
vector<pRunner> allR;
oe->getRunnersByCardNo(cno, true, oEvent::CardLookupProperty::Any, allR);
for (pRunner ar : allR) {
if (!r->canShareCard(ar, cno)) {
warnCardDupl = ar;
break;
}
}
}
InputInfo &cardNo = dynamic_cast<InputInfo &>(gdi.getBaseInfo(id.c_str()));
if (warnCardDupl) {
cardNo.setBgColor(colorLightRed);
gdi.updateToolTip(id, L"Brickan används av X.#" + warnCardDupl->getCompleteIdentification());
cardNo.refresh();
return warnCardDupl->getTeam() == r->getTeam();
}
else {
if (cardNo.getBgColor() != colorDefault) {
cardNo.setBgColor(colorDefault);
gdi.updateToolTip(id, L"");
cardNo.refresh();
}
return false;
}
}
void TabTeam::handleAutoComplete(gdioutput &gdi, AutoCompleteInfo &info) {
auto bi = gdi.setText(info.getTarget().c_str(), info.getCurrent(), false, -1, false);
if (bi) {
int ix = info.getCurrentInt();
bi->setExtra(ix);
if (info.getTarget() == "Name") {
auto &db = oe->getRunnerDatabase();
auto runner = db.getRunnerByIndex(ix);
if (runner && gdi.hasWidget("Club") && gdi.getText("Club").empty()) {
pClub club = db.getClub(runner->dbe().clubNo);
if (club)
gdi.setText("Club", club->getName(), false, -1, false);
}
if (runner && runner->dbe().cardNo > 0 && gdi.hasWidget("DirCard") && gdi.getText("DirCard").empty()) {
gdi.setText("DirCard", itow(runner->dbe().cardNo), false, -1, false);
}
}
}
gdi.clearAutoComplete("");
gdi.TabFocus(1);
}