/************************************************************************
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 .
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
#include "stdafx.h"
#include "resource.h"
#include
#include
#include
#include "oEvent.h"
#include "xmlparser.h"
#include "gdioutput.h"
#include "csvparser.h"
#include "SportIdent.h"
#include "gdifonts.h"
#include "IOF30Interface.h"
#include "meosexception.h"
#include "MeOSFeatures.h"
#include "oEventDraw.h"
#include "oListInfo.h"
#include "TabCourse.h"
#include "TabCompetition.h"
#include "meos_util.h"
#include "pdfwriter.h"
TabCourse::TabCourse(oEvent *poe):TabBase(poe)
{
clearCompetitionData();
}
TabCourse::~TabCourse(void)
{
}
void TabCourse::selectCourse(gdioutput &gdi, pCourse pc)
{
if (gdi.hasWidget("Rogaining")) {
gdi.setText("TimeLimit", L"");
gdi.disableInput("TimeLimit");
gdi.setText("PointLimit", L"");
gdi.disableInput("PointLimit");
gdi.setText("PointReduction", L"");
gdi.disableInput("PointReduction");
gdi.check("ReductionPerMinute", false);
gdi.disableInput("ReductionPerMinute");
gdi.selectItemByData("Rogaining", 0);
}
if (pc) {
pc->synchronize();
wstring uis = pc->getControlsUI();
gdi.setText("Controls", uis);
gdi.setText("CourseExpanded", encodeCourse(uis, pc->getMaximumRogainingTime() > 0,
pc->useFirstAsStart(), pc->useLastAsFinish()), true);
gdi.setText("Name", pc->getName());
gdi.setTextZeroBlank("Length", pc->getLength());
gdi.setTextZeroBlank("Climb", pc->getDI().getInt("Climb"));
gdi.setTextZeroBlank("NumberMaps", pc->getNumberMaps());
gdi.check("FirstAsStart", pc->useFirstAsStart());
gdi.check("LastAsFinish", pc->useLastAsFinish());
if (gdi.hasWidget("Rogaining")) {
int rt = pc->getMaximumRogainingTime();
int rp = pc->getMinimumRogainingPoints();
if ( rt > 0 ) {
gdi.selectItemByData("Rogaining", 1);
gdi.enableInput("TimeLimit");
gdi.setText("TimeLimit", formatTimeHMS(rt));
gdi.enableInput("PointReduction");
gdi.setText("PointReduction", itow(pc->getRogainingPointsPerMinute()));
gdi.enableInput("ReductionPerMinute");
gdi.check("ReductionPerMinute", pc->getDCI().getInt("RReductionMethod") != 0);
}
else if (rp > 0) {
gdi.selectItemByData("Rogaining", 2);
gdi.enableInput("PointLimit");
gdi.setText("PointLimit", itow(rp));
}
}
courseId = pc->getId();
gdi.enableInput("Remove");
gdi.enableInput("Save");
gdi.selectItemByData("Courses", pc->getId());
gdi.setText("CourseProblem", lang.tl(pc->getCourseProblems()), true);
vector cls;
vector crs;
oe->getClasses(cls, true);
wstring usedInClasses;
for (size_t k = 0; k < cls.size(); k++) {
int nleg = max(cls[k]->getNumStages(), 1);
int nlegwithcrs = 0;
vector usage;
set allClassCrs;
for (int j = 0; j < nleg; j++) {
cls[k]->getCourses(j, crs);
if (!crs.empty())
nlegwithcrs++;
bool done = false;
for (size_t i = 0; i < crs.size(); i++) {
if (!crs[i])
continue;
allClassCrs.insert(crs[i]->getId());
if (!done && crs[i] == pc) {
usage.push_back(cls[k]->getLegNumber(j));
done = true; // Cannot break, fill allClasssCrs
}
}
}
wstring add;
if (usage.size() == nleg ||
(usage.size() == nlegwithcrs && nlegwithcrs > 0) ||
(!usage.empty() && allClassCrs.size() == 1)) {
add = cls[k]->getName();
}
else if (!usage.empty()) {
add = cls[k]->getName();
add += L" (";
for (size_t i = 0; i < usage.size(); i++) {
if (i > 0)
add += L", ";
add += usage[i];
}
add += L")";
}
if (!add.empty()) {
if (!usedInClasses.empty())
usedInClasses += L", ";
usedInClasses += add;
}
}
gdi.setText("CourseUse", usedInClasses, true);
pCourse shortens = pc->getLongerVersion();
if (shortens)
gdi.setTextTranslate("Shortens", L"Avkortar: X#" + shortens->getName(), true);
else
gdi.setText("Shortens", L"", true);
gdi.enableEditControls(true);
fillCourseControls(gdi, pc->getControlsUI());
int cc = pc->getCommonControl();
gdi.check("WithLoops", cc != 0);
gdi.setInputStatus("CommonControl", cc != 0);
if (cc) {
gdi.selectItemByData("CommonControl", cc);
}
auto sh = pc->getShorterVersion();
if (sh.first)
fillOtherCourses(gdi, *pc, cc != 0);
else
gdi.selectItemByData("ShortCourse", -1);
gdi.check("Shorten", sh.first);
gdi.setInputStatus("ShortCourse", sh.first);
gdi.selectItemByData("ShortCourse", sh.second ? sh.second->getId() : 0);
}
else {
gdi.setText("Name", L"");
gdi.setText("Controls", L"");
gdi.setText("CourseExpanded", L"");
gdi.setText("Length", L"");
gdi.setText("Climb", L"");
gdi.setText("NumberMaps", L"");
gdi.check("FirstAsStart", false);
gdi.check("LastAsFinish", false);
courseId = 0;
gdi.disableInput("Remove");
gdi.disableInput("Save");
gdi.selectItemByData("Courses", -1);
gdi.setText("CourseProblem", L"", true);
gdi.setText("CourseUse", L"", true);
gdi.setText("Shortens", L"", true);
gdi.check("WithLoops", false);
gdi.clearList("CommonControl");
gdi.setInputStatus("CommonControl", false);
gdi.check("Shorten", false);
gdi.clearList("ShortCourse");
gdi.setInputStatus("ShortCourse", false);
gdi.enableEditControls(false);
}
gdi.refreshFast();
gdi.setInputStatus("DrawCourse", pc != 0, true);
}
int CourseCB(gdioutput *gdi, GuiEventType type, BaseInfo* data) {
TabCourse &tc = dynamic_cast(*gdi->getTabs().get(TCourseTab));
return tc.courseCB(*gdi, type, data);
}
void TabCourse::save(gdioutput &gdi, int canSwitchViewMode) {
DWORD cid = courseId;
pCourse pc;
wstring name=gdi.getText("Name");
if (cid == 0 && name.empty())
return;
if (name.empty()) {
gdi.alert("Banan måste ha ett namn.");
return;
}
bool create=false;
if (cid>0)
pc=oe->getCourse(cid);
else {
pc=oe->addCourse(name);
create=true;
}
bool firstAsStart = gdi.isChecked("FirstAsStart");
bool lastAsFinish = gdi.isChecked("LastAsFinish");
bool oldFirstAsStart = pc->useFirstAsStart();
if (!oldFirstAsStart && firstAsStart) {
vector cr;
oe->getRunners(0, pc->getId(), cr, false);
bool hasRes = false;
for (size_t k = 0; k < cr.size(); k++) {
if (cr[k]->getCard() != 0) {
hasRes = true;
break;
}
}
if (hasRes) {
firstAsStart = gdi.ask(L"ask:firstasstart");
}
}
pc->setName(name);
bool changedCourse = pc->importControls(gdi.narrow(gdi.getText("Controls")), true, true);
pc->setLength(gdi.getTextNo("Length"));
pc->getDI().setInt("Climb", gdi.getTextNo("Climb"));
pc->setNumberMaps(gdi.getTextNo("NumberMaps"));
pc->firstAsStart(firstAsStart);
pc->lastAsFinish(lastAsFinish);
if (gdi.isChecked("WithLoops")) {
int cc = gdi.getTextNo("CommonControl");
if (cc == 0)
throw meosException("Ange en varvningskontroll för banan");
pc->setCommonControl(cc);
}
else
pc->setCommonControl(0);
if (gdi.isChecked("Shorten")) {
ListBoxInfo ci;
if (gdi.getSelectedItem("ShortCourse", ci) && oe->getCourse(ci.data)) {
pc->setShorterVersion(true, oe->getCourse(ci.data));
}
else if (gdi.isChecked("WithLoops")) {
pc->setShorterVersion(true, nullptr);
}
else
throw meosException("Ange en avkortad banvariant");
}
else
pc->setShorterVersion(false, 0);
if (gdi.hasWidget("Rogaining")) {
string t;
pc->setMaximumRogainingTime(convertAbsoluteTimeMS(gdi.getText("TimeLimit")));
pc->setMinimumRogainingPoints(_wtoi(gdi.getText("PointLimit").c_str()));
int pr = _wtoi(gdi.getText("PointReduction").c_str());
pc->setRogainingPointsPerMinute(pr);
if (pr > 0) {
int rmethod = gdi.isChecked("ReductionPerMinute") ? 1 : 0;
pc->getDI().setInt("RReductionMethod", rmethod);
}
}
pc->synchronize();//Update SQL
oe->fillCourses(gdi, "Courses", {}, false);
oe->reEvaluateCourse(pc->getId(), true);
if (canSwitchViewMode != 2 && changedCourse && pc->getLegLengths().size() > 2) {
if (canSwitchViewMode == 1) {
if(gdi.ask(L"ask:updatelegs")) {
gdi.sendCtrlMessage("LegLengths");
return;
}
}
else {
gdi.alert("warn:updatelegs");
}
}
if (gdi.getData("FromClassPage", cid)) {
assert(false);
}
else if (addedCourse || create)
selectCourse(gdi, 0);
else
selectCourse(gdi, pc);
}
int TabCourse::courseCB(gdioutput &gdi, GuiEventType type, BaseInfo* data) {
if (type==GUI_BUTTON) {
ButtonInfo bi=*(ButtonInfo *)data;
if (bi.id=="Save") {
save(gdi, 1);
}
else if (bi.id == "SwitchMode") {
if (!tableMode)
save(gdi, true);
tableMode = !tableMode;
loadPage(gdi);
}
else if (bi.id == "LegLengths") {
save(gdi, 2);
pCourse pc = oe->getCourse(courseId);
if (!pc || pc->getNumControls() == 0) {
return 0;
}
gdi.clearPage(false);
gdi.addString("", boldLarge, L"Redigera sträcklängder för X#" + pc->getName());
gdi.dropLine();
int w = gdi.scaleLength(120);
int xp = gdi.getCX() + w;
int yp = gdi.getCY();
gdi.addString("", 1, "Sträcka:");
gdi.addString("", yp, xp, 1, "Längd:");
for (int i = 0; i <= pc->getNumControls(); i++) {
int len = pc->getLegLength(i);
pControl cbegin = pc->getControl(i-1);
wstring begin = i == 0 ? lang.tl("Start") : (cbegin ? cbegin->getName() : L"");
pControl cend = pc->getControl(i);
wstring end = i == pc->getNumControls() ? lang.tl("Mål") : (cend ? cend->getName() : L"");
gdi.pushX();
gdi.fillRight();
gdi.addStringUT(0, begin + makeDash(L" - ") + end + L":").xlimit = w-10;
gdi.setCX(xp);
gdi.fillDown();
gdi.addInput("c" + itos(i), len > 0 ? itow(len) : L"", 8);
gdi.popX();
if (i < pc->getNumControls()) {
RECT rc;
rc.left = gdi.getCX() + gdi.getLineHeight();
rc.right = rc.left + (3*w)/2;
rc.top = gdi.getCY() + 2;
rc.bottom = gdi.getCY() + 4;
gdi.addRectangle(rc, colorDarkBlue, false);
}
}
gdi.dropLine();
gdi.fillRight();
gdi.addButton("Cancel", "Avbryt", CourseCB).setCancel();
gdi.addButton("SaveLegLen", "Spara", CourseCB).setDefault();
gdi.setOnClearCb(CourseCB);
gdi.setData("EditLengths", 1);
gdi.refresh();
}
else if (bi.id == "SaveLegLen") {
saveLegLengths(gdi);
loadPage(gdi);
}
else if (bi.id=="BrowseCourse") {
vector< pair > ext;
ext.push_back(make_pair(L"Alla banfiler", L"*.xml;*.csv;*.txt"));
ext.push_back(make_pair(L"Banor, OCAD semikolonseparerat", L"*.csv;*.txt"));
ext.push_back(make_pair(L"Banor, IOF (xml)", L"*.xml"));
wstring file=gdi.browseForOpen(ext, L"csv");
if (file.length()>0)
gdi.setText("FileName", file);
}
else if (bi.id=="Print") {
gdi.print(oe);
}
else if (bi.id=="PDF") {
vector< pair > ext;
ext.push_back(make_pair(L"Portable Document Format (PDF)", L"*.pdf"));
int index;
wstring file=gdi.browseForSave(ext, L"pdf", index);
if (!file.empty()) {
pdfwriter pdf;
pdf.generatePDF(gdi, file, L"Report", L"MeOS", gdi.getTL(), true);
gdi.openDoc(file.c_str());
}
}
else if (bi.id == "WithLoops") {
bool w = gdi.isChecked(bi.id);
gdi.setInputStatus("CommonControl", w);
if (w && gdi.getTextNo("CommonControl") == 0)
gdi.selectFirstItem("CommonControl");
pCourse pc = oe->getCourse(courseId);
if (pc) {
pair sel = gdi.getSelectedItem("ShortCourse");
fillOtherCourses(gdi, *pc, w);
if (!w && sel.first == 0)
sel.second = false;
if (sel.second) {
gdi.selectItemByData("ShortCourse", sel.first);
}
else if (w) {
gdi.selectItemByData("ShortCourse", 0);
}
}
}
else if (bi.id == "Shorten") {
bool w = gdi.isChecked(bi.id);
gdi.setInputStatus("ShortCourse", w);
if (w) {
pCourse pc = oe->getCourse(courseId);
if (pc) {
fillOtherCourses(gdi, *pc, gdi.isChecked("WithLoops"));
}
ListBoxInfo clb;
if (!gdi.getSelectedItem("ShortCourse", clb) || clb.data <= 0)
gdi.selectFirstItem("ShortCourse");
}
}
else if (bi.id == "ExportCourses") {
int FilterIndex=0;
vector< pair > ext;
ext.push_back(make_pair(L"IOF CourseData, version 3.0 (xml)", L"*.xml"));
wstring save = gdi.browseForSave(ext, L"xml", FilterIndex);
if (save.length()>0) {
IOF30Interface iof30(oe, false, false);
xmlparser xml;
xml.openOutput(save.c_str(), false);
iof30.writeCourses(xml);
xml.closeOut();
}
}
else if (bi.id == "DeleteAll") {
if (!gdi.ask(L"Vill du ta bort alla banor från tävlingen?"))
return 0;
// Clear all course references
vector rr;
oe->getRunners(0, 0, rr);
for (pRunner r : rr) {
r->setCourseId(0);
}
vector cc;
oe->getClasses(cc, true);
for (pClass c : cc) {
c->setCourse(nullptr);
for (int i = 0; i < c->getNumStages(); i++)
c->clearStageCourses(i);
}
vector crs;
oe->getCourses(crs);
for (pCourse c : crs) {
oe->removeCourse(c->getId());
}
loadPage(gdi);
}
else if (bi.id=="ImportCourses") {
setupCourseImport(gdi, CourseCB);
}
else if (bi.id=="DoImportCourse") {
wstring filename = gdi.getText("FileName");
if (filename.empty())
return 0;
gdi.disableInput("DoImportCourse");
gdi.disableInput("Cancel");
gdi.disableInput("BrowseCourse");
gdi.disableInput("AddClasses");
gdi.disableInput("CreateClasses");
try {
TabCourse::runCourseImport(gdi, filename, oe,
gdi.isChecked("AddClasses"),
gdi.isChecked("CreateClasses"));
}
catch (const std::exception &) {
gdi.enableInput("DoImportCourse");
gdi.enableInput("Cancel");
gdi.enableInput("BrowseCourse");
gdi.enableInput("AddClasses");
gdi.enableInput("CreateClasses");
throw;
}
gdi.addButton("Cancel", "OK", CourseCB);
gdi.dropLine();
gdi.refresh();
}
else if (bi.id == "DrawCourse") {
save(gdi, true);
pCourse crs = oe->getCourse(courseId);
if (crs == 0)
throw meosException("Ingen bana vald.");
vector cls;
oe->getClasses(cls, true);
wstring clsNames;
bool hasAsked = false;
courseDrawClasses.clear();
for (size_t k = 0; k < cls.size(); k++) {
if (cls[k]->getCourseId() != courseId)
continue;
if (!hasAsked &&oe->classHasResults(cls[k]->getId())) {
hasAsked = true;
if (!gdi.ask(L"warning:drawresult"))
return 0;
}
courseDrawClasses.emplace_back(cls[k]->getId(), 0, -1, -1, 0, oEvent::VacantPosition::Mixed);
if (!clsNames.empty())
clsNames += L", ";
clsNames += cls[k]->getName();
}
if (courseDrawClasses.empty())
throw meosException("Ingen klass använder banan.");
gdi.clearPage(false);
gdi.addString("", boldLarge, L"Lotta klasser med banan X#" + crs->getName());
gdi.addStringUT(0, clsNames);
gdi.dropLine();
gdi.pushX();
gdi.fillRight();
int firstStart = timeConstHour;
int interval = 2*timeConstMinute;
int vac = 1;
gdi.addInput("FirstStart", oe->getAbsTime(firstStart), 10, 0, L"Första start:");
gdi.addInput("Interval", formatTime(interval), 10, 0, L"Startintervall (min):");
gdi.addInput("Vacances", itow(vac), 10, 0, L"Antal vakanser:");
gdi.fillDown();
gdi.popX();
gdi.dropLine(3);
gdi.addSelection("Method", 200, 200, 0, L"Metod:");
gdi.addItem("Method", lang.tl("Lottning") + L" (MeOS)", int(oEvent::DrawMethod::MeOS));
gdi.addItem("Method", lang.tl("Lottning"), int(oEvent::DrawMethod::Random));
gdi.addItem("Method", lang.tl("SOFT-lottning"), int(oEvent::DrawMethod::SOFT));
gdi.selectItemByData("Method", (int)getDefaultMethod());
gdi.dropLine(0.9);
gdi.fillRight();
gdi.addButton("DoDrawCourse", "Lotta", CourseCB).setDefault();
gdi.addButton("Cancel", "Avbryt", CourseCB).setCancel();
gdi.dropLine();
gdi.fillDown();
gdi.refresh();
}
else if (bi.id == "DoDrawCourse") {
wstring firstStart = gdi.getText("FirstStart");
wstring minInterval = gdi.getText("Interval");
int vacances = gdi.getTextNo("Vacances");
int fs = oe->getRelativeTime(firstStart);
int iv = convertAbsoluteTimeMS(minInterval);
oEvent::DrawMethod method = oEvent::DrawMethod(gdi.getSelectedItem("Method").first);
courseDrawClasses[0].firstStart = fs;
courseDrawClasses[0].vacances = vacances;
courseDrawClasses[0].interval = iv;
for (size_t k = 1; k < courseDrawClasses.size(); k++) {
vector r;
oe->getRunners(courseDrawClasses[k-1].classID, 0, r, false);
int vacDelta = vacances;
for (size_t i = 0; i < r.size(); i++) {
if (r[i]->isVacant())
vacDelta--;
}
courseDrawClasses[k].firstStart = courseDrawClasses[k-1].firstStart + (r.size() + vacDelta) * iv;
courseDrawClasses[k].vacances = vacances;
courseDrawClasses[k].interval = iv;
}
oe->drawList(courseDrawClasses, method, 1, oEvent::DrawType::DrawAll);
oe->addAutoBib();
gdi.clearPage(false);
gdi.addButton("Cancel", "Återgå", CourseCB);
oListParam par;
oListInfo info;
par.listCode = EStdStartList;
for (size_t k=0; kgenerateListInfo(gdi, par, info);
oe->generateList(gdi, false, info, true);
gdi.refresh();
}
else if (bi.id=="Add") {
if (courseId>0) {
wstring ctrl = gdi.getText("Controls");
wstring name = gdi.getText("Name");
pCourse pc = oe->getCourse(courseId);
if (pc && !name.empty() && !ctrl.empty() && pc->getControlsUI() != ctrl) {
if (name == pc->getName()) {
// Make name unique if same name
int len = name.length();
if (len > 2 && (isdigit(name[len-1]) || isdigit(name[len-2]))) {
++name[len-1]; // course 1 -> course 2, course 1a -> course 1b
}
else
name += L" 2";
}
if (gdi.ask(L"Vill du lägga till banan 'X' (Y)?#" + name + L"#" + ctrl)) {
pc = oe->addCourse(name);
courseId = pc->getId();
gdi.setText("Name", name);
save(gdi, 1);
return true;
}
}
save(gdi, 1);
}
pCourse pc = oe->addCourse(oe->getAutoCourseName());
pc->synchronize();
oe->fillCourses(gdi, "Courses", {}, false);
selectCourse(gdi, pc);
gdi.setInputFocus("Name", true);
addedCourse = true;
}
else if (bi.id=="Remove"){
DWORD cid = courseId;
if (cid==0)
throw meosException("Ingen bana vald.");
if (oe->isCourseUsed(cid))
gdi.alert("Banan används och kan inte tas bort.");
else
oe->removeCourse(cid);
oe->fillCourses(gdi, "Courses", {}, false);
selectCourse(gdi, 0);
}
else if (bi.id == "FirstAsStart" || bi.id == "LastAsFinish") {
refreshCourse(gdi.getText("Controls"), gdi);
}
else if (bi.id=="Cancel"){
loadPage(gdi);
}
}
else if (type==GUI_LISTBOX){
ListBoxInfo bi=*(ListBoxInfo *)data;
if (bi.id=="Courses") {
if (gdi.isInputChanged(""))
save(gdi, 0);
pCourse pc=oe->getCourse(bi.data);
selectCourse(gdi, pc);
addedCourse = false;
}
else if (bi.id=="Rogaining") {
wstring t;
t = gdi.getText("TimeLimit");
if (!t.empty() && _wtoi(t.c_str()) > 0)
time_limit = t;
t = gdi.getText("PointLimit");
if (!t.empty())
point_limit = t;
t = gdi.getText("PointReduction");
if (!t.empty())
point_reduction = t;
wstring tl, pl, pr;
if (bi.data == 1) {
tl = time_limit;
pr = point_reduction;
}
else if (bi.data == 2) {
pl = point_limit;
}
gdi.setInputStatus("TimeLimit", !tl.empty());
gdi.setText("TimeLimit", tl);
gdi.setInputStatus("PointLimit", !pl.empty());
gdi.setText("PointLimit", pl);
gdi.setInputStatus("PointReduction", !pr.empty());
gdi.setInputStatus("ReductionPerMinute", !pr.empty());
gdi.setText("PointReduction", pr);
}
}
else if (type == GUI_INPUT) {
InputInfo ii=*(InputInfo *)data;
if (ii.id == "Controls") {
int current = gdi.getTextNo("CommonControl");
fillCourseControls(gdi, ii.text);
if (gdi.isChecked("WithLoops") && current != 0)
gdi.selectItemByData("CommonControl", current);
}
}
else if (type == GUI_INPUTCHANGE) {
InputInfo &ii=*(InputInfo *)data;
if (ii.id == "Controls") {
refreshCourse(ii.text, gdi);
}
}
else if (type==GUI_CLEAR) {
if (gdi.hasData("EditLengths")) {
saveLegLengths(gdi);
return true;
}
if (courseId>0)
save(gdi, 0);
return true;
}
return 0;
}
bool TabCourse::loadPage(gdioutput &gdi) {
oe->checkDB();
gdi.selectTab(tabId);
DWORD ClassID=0, RunnerID=0;
time_limit = L"01:00:00";
point_limit = L"10";
point_reduction = L"1";
gdi.getData("ClassID", ClassID);
gdi.getData("RunnerID", RunnerID);
gdi.clearPage(false);
int xp = gdi.getCX();
gdi.setData("ClassID", ClassID);
gdi.setData("RunnerID", RunnerID);
string switchMode;
const int button_w = gdi.scaleLength(90);
switchMode = tableMode ? "Formulärläge" : "Tabelläge";
gdi.addButton(2, 2, button_w, "SwitchMode", switchMode,
CourseCB, "Välj vy", false, false).fixedCorner();
if (tableMode) {
gdi.addTable(oCourse::getTable(oe), xp, gdi.scaleLength(30));
return true;
}
gdi.addString("", boldLarge, "Banor");
gdi.pushY();
gdi.fillDown();
gdi.addListBox("Courses", 250, 360, CourseCB, L"Banor (antal kontroller)").isEdit(false).ignore(true);
gdi.setTabStops("Courses", 240);
oe->fillCourses(gdi, "Courses", {}, false);
gdi.dropLine(0.7);
gdi.pushX();
gdi.addString("", boldText, "Funktioner");
gdi.dropLine();
gdi.fillRight();
gdi.addButton("ImportCourses", "Importera från fil...", CourseCB);
gdi.addButton("ExportCourses", "Exportera...", CourseCB);
gdi.popX();
gdi.dropLine(2.5);
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::DrawStartList)) {
gdi.addButton("DrawCourse", "Lotta starttider..", CourseCB);
gdi.disableInput("DrawCourse");
}
gdi.addButton("DeleteAll", "Radera alla...", CourseCB);
gdi.newColumn();
gdi.fillDown();
gdi.popY();
gdi.addString("", 10, "help:25041");
gdi.dropLine(0.5);
gdi.pushX();
gdi.fillRight();
gdi.addInput("Name", L"", 20, 0, L"Namn:");
gdi.fillDown();
gdi.addInput("NumberMaps", L"", 6, 0, L"Antal kartor:");
gdi.popX();
vector allCrs;
oe->getCourses(allCrs);
size_t mlen = 0;
for (size_t k = 0; k < allCrs.size(); k++) {
mlen = max(allCrs[k]->getControlsUI().length()/2+5, mlen);
}
gdi.addInput("Controls", L"", max(48, mlen), CourseCB, L"Kontroller:");
gdi.dropLine(0.3);
gdi.addString("CourseExpanded", 0, "...").setColor(colorDarkGreen);
gdi.dropLine(0.5);
gdi.addString("", 10, "help:12662");
gdi.dropLine(1);
gdi.fillRight();
gdi.addInput("Climb", L"", 8, 0, L"Climb (m):");
gdi.addInput("Length", L"", 8, 0, L"Längd (m):");
gdi.dropLine(0.9);
gdi.fillDown();
gdi.addButton("LegLengths", "Redigera sträcklängder...", CourseCB).isEdit(true);
gdi.dropLine(0.5);
gdi.popX();
gdi.fillRight();
gdi.addCheckbox("FirstAsStart", "Använd första kontrollen som start", CourseCB);
gdi.fillDown();
gdi.addCheckbox("LastAsFinish", "Använd sista kontrollen som mål", CourseCB);
gdi.popX();
gdi.fillRight();
gdi.addCheckbox("WithLoops", "Bana med slingor", CourseCB);
gdi.setCX(gdi.getCX()+ gdi.scaleLength(20));
gdi.addString("", 0, "Varvningskontroll:");
gdi.fillDown();
gdi.dropLine(-0.2);
gdi.addSelection("CommonControl", 50, 200, 0, L"", L"En bana med slingor tillåter deltagaren att ta slingorna i valfri ordning");
gdi.dropLine(0.2);
gdi.popX();
gdi.fillRight();
gdi.addCheckbox("Shorten", "Med avkortning", CourseCB);
gdi.setCX(gdi.getCX()+ gdi.scaleLength(20));
gdi.addString("", 0, "Avkortad banvariant:");
gdi.dropLine(-0.2);
gdi.addSelection("ShortCourse", 150, 200, 0, L"", L"info_shortening");
gdi.addString("Shortens", 0, "");
gdi.fillDown();
gdi.dropLine(2.5);
gdi.popX();
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Rogaining)) {
RECT rc;
rc.top = gdi.getCY() -5;
rc.left = gdi.getCX();
gdi.setCX(gdi.getCX()+gdi.scaleLength(10));
gdi.addString("", 1, "Rogaining");
gdi.dropLine(0.5);
gdi.fillRight();
gdi.addSelection("Rogaining", 120, 80, CourseCB);
gdi.addItem("Rogaining", lang.tl("Ingen rogaining"), 0);
gdi.addItem("Rogaining", lang.tl("Tidsgräns"), 1);
gdi.addItem("Rogaining", lang.tl("Poänggräns"), 2);
gdi.setCX(gdi.getCX()+gdi.scaleLength(20));
gdi.dropLine(-0.8);
int cx = gdi.getCX();
gdi.addInput("PointLimit", L"", 8, 0, L"Poänggräns:").isEdit(false);
gdi.addInput("TimeLimit", L"", 8, 0, L"Tidsgräns:").isEdit(false);
gdi.addInput("PointReduction", L"", 8, 0, L"Poängavdrag (per minut):").isEdit(false);
gdi.dropLine(3.5);
rc.right = gdi.getCX() + 5;
gdi.setCX(cx);
gdi.fillDown();
gdi.addCheckbox("ReductionPerMinute", "Poängavdrag per påbörjad minut");
rc.bottom = gdi.getCY() + 5;
gdi.addRectangle(rc, colorLightBlue, true);
}
gdi.popX();
gdi.fillDown();
gdi.dropLine(1);
gdi.addString("CourseUse", 0, "").setColor(colorDarkBlue);
gdi.dropLine();
gdi.addString("CourseProblem", 1, "").setColor(colorRed);
gdi.dropLine(2);
gdi.fillRight();
gdi.addButton("Save", "Spara", CourseCB, "help:save").setDefault();
gdi.addButton("Remove", "Radera", CourseCB);
gdi.addButton("Add", "Ny bana", CourseCB);
gdi.disableInput("Remove");
gdi.disableInput("Save");
selectCourse(gdi, oe->getCourse(courseId));
gdi.setOnClearCb(CourseCB);
gdi.refresh();
return true;
}
void TabCourse::runCourseImport(gdioutput& gdi, const wstring &filename,
oEvent *oe, bool addToClasses, bool createClasses) {
if (csvparser::iscsv(filename) != csvparser::CSV::NoCSV) {
gdi.fillRight();
gdi.pushX();
gdi.addString("", 0, "Importerar OCAD csv-fil...");
gdi.refreshFast();
csvparser csv;
if (csv.importOCAD_CSV(*oe, filename, addToClasses)) {
gdi.addString("", 1, "Klart.").setColor(colorGreen);
}
else gdi.addString("", 0, "Operationen misslyckades.").setColor(colorRed);
gdi.popX();
gdi.dropLine(2.5);
gdi.fillDown();
}
else if (filename.find(L".txt") != wstring::npos || filename.find(L".TXT") != wstring::npos) {
std::ifstream fin(filename);
if (!fin.good())
throw meosException(L"Cannot read " + filename);
char bf[2048];
vector sw;
int importedC = oe->getNumCourses() + 1;
int line = 0;
while (fin.good()) {
fin.getline(bf, 2048);
if (strlen(bf) < 2)
continue;
if (0 == line && uint8_t(bf[0]) == 0xEF && uint8_t(bf[1]) == 0xBB && uint8_t(bf[2]) == 0xBF) {
split(bf + 3, "\t;,", sw);
if (sw.size() == 1)
split(bf + 3, " ", sw);
}
else {
split(bf, "\t;,", sw);
if (sw.size() == 1)
split(bf, " ", sw);
}
line++;
if (sw.size() <= 1)
continue;
wstring name;
int first = 0;
if (atoi(sw[0].c_str()) < 30 && trim(sw[0]).length() > 2) {
name = gdioutput::fromUTF8(trim(sw[0]));
first = 1;
}
if (name.empty())
name = lang.tl("Bana X#" + itos(importedC++));
string cs;
for (int i = first; i < sw.size(); i++) {
if (trim(sw[i]).empty())
continue;
int c = atoi(sw[i].c_str());
if (c >= 30 && c < 1000)
cs += itos(c) + " ";
else {
throw meosException("Kan inte tolka 'X' som en bana#" + string(bf));
}
}
pCourse pc = oe->addCourse(name);
pc->importControls(cs, true, false);
pc->synchronize();
}
fin.close();
}
else {
set noFilter;
pair noType;
int classIdOffset = 0;
int courseIdOffset = 0;
oe->importXML_EntryData(gdi, filename.c_str(), addToClasses, false,
noFilter, classIdOffset, courseIdOffset, noType);
}
if (addToClasses) {
// There is specific course-class matching inside the import of each format,
// that uses additional information. Here we try to match based on a generic approach.
vector cls;
vector crs;
oe->getClasses(cls, false);
oe->getCourses(crs);
map name2Course;
map > course2Class;
for (size_t k = 0; k < crs.size(); k++)
name2Course[crs[k]->getName()] = crs[k];
bool hasMissing = false;
for (size_t k = 0; k < cls.size(); k++) {
vector usedCrs;
cls[k]->getCourses(-1, usedCrs);
if (usedCrs.empty()) {
map::iterator res = name2Course.find(cls[k]->getName());
if (res != name2Course.end()) {
usedCrs.push_back(res->second);
if (cls[k]->getNumStages()==0) {
cls[k]->setCourse(res->second);
}
else {
for (size_t i = 0; igetNumStages(); i++)
cls[k]->addStageCourse(i, res->second->getId(), -1);
}
}
else {
hasMissing = true;
}
}
for (size_t j = 0; j < usedCrs.size(); j++) {
course2Class[usedCrs[j]->getId()].push_back(cls[k]);
}
}
for (size_t k = 0; k < crs.size(); k++) {
pClass bestClass;
if (hasMissing && (bestClass = oe->getBestClassMatch(crs[k]->getName())) != 0) {
vector usedCrs;
bestClass->getCourses(-1, usedCrs);
if (usedCrs.empty()) {
course2Class[crs[k]->getId()].push_back(bestClass);
if (bestClass->getNumStages()==0) {
bestClass->setCourse(crs[k]);
}
else {
for (size_t i = 0; igetNumStages(); i++)
bestClass->addStageCourse(i, crs[k]->getId(), -1);
}
}
}
}
gdi.addString("", 1, "Klasser");
int yp = gdi.getCY();
int xp = gdi.getCX();
int w = gdi.scaleLength(200);
for (size_t k = 0; k < cls.size(); k++) {
vector usedCrs;
cls[k]->getCourses(-1, usedCrs);
wstring c;
for (size_t j = 0; j < usedCrs.size(); j++) {
if (j>0)
c += L", ";
c += usedCrs[j]->getName();
}
TextInfo &ci = gdi.addStringUT(yp, xp, 0, cls[k]->getName(), w);
if (c.empty()) {
c = makeDash(L"-");
ci.setColor(colorRed);
}
gdi.addStringUT(yp, xp + w, 0, c);
yp += gdi.getLineHeight();
}
gdi.dropLine();
gdi.addString("", 1, "Banor");
yp = gdi.getCY();
for (size_t k = 0; k < crs.size(); k++) {
wstring c;
vector usedCls = course2Class[crs[k]->getId()];
for (size_t j = 0; j < usedCls.size(); j++) {
if (j>0)
c += L", ";
c += usedCls[j]->getName();
}
TextInfo &ci = gdi.addStringUT(yp, xp, 0, crs[k]->getName(), w);
if (c.empty()) {
c = makeDash(L"-");
ci.setColor(colorRed);
}
gdi.addStringUT(yp, xp + w, 0, c);
yp += gdi.getLineHeight();
}
gdi.dropLine();
}
if (createClasses) {
vector cls;
vector crs;
oe->getClasses(cls, false);
oe->getCourses(crs);
unordered_set usedCourseId;
vector usedCrs;
for (size_t k = 0; k < cls.size(); k++) {
cls[k]->getCourses(-1, usedCrs);
for (pCourse pc : usedCrs)
usedCourseId.insert(pc->getId());
}
set usedNames;
for (pCourse pc : crs) {
if (usedCourseId.count(pc->getId()))
continue;
pClass matchCls = oe->getClassCreate(-1, pc->getName(), usedNames);
if (!matchCls || matchCls->getCourse(false)) {
oe->addClass(pc->getName() + lang.tl(" Bana"), pc->getId());
}
else {
matchCls->setCourse(pc);
}
}
}
gdi.addButton(gdi.getWidth()+20, 45, gdi.scaleLength(baseButtonWidth),
"Print", "Skriv ut...", CourseCB,
"Skriv ut listan.", true, false);
gdi.addButton(gdi.getWidth()+20, 75, gdi.scaleLength(baseButtonWidth),
"PDF", "PDF...", CourseCB,
"Spara som PDF.", true, false);
gdi.setWindowTitle(oe->getTitleName());
oe->updateTabs();
gdi.refresh();
}
void TabCourse::setupCourseImport(gdioutput& gdi, GUICALLBACK cb) {
gdi.clearPage(true);
gdi.addString("", 2, "Importera banor/klasser");
gdi.addString("", 0, "help:importcourse");
gdi.dropLine();
gdi.fillRight();
gdi.pushX();
gdi.addInput("FileName", L"", 48, 0, L"Filnamn:");
gdi.dropLine();
gdi.fillDown();
gdi.addButton("BrowseCourse", "Bläddra...", CourseCB);
gdi.dropLine(0.5);
gdi.popX();
gdi.fillDown();
gdi.addCheckbox("AddClasses", "Lägg till klasser", 0, true);
gdi.addCheckbox("CreateClasses", "Skapa en klass för varje bana", 0, false);
gdi.dropLine();
gdi.fillRight();
gdi.addButton("DoImportCourse", "Importera", cb).setDefault();
gdi.fillDown();
gdi.addButton("Cancel", "Avbryt", cb).setCancel();
gdi.setInputFocus("FileName");
gdi.popX();
}
void TabCourse::fillCourseControls(gdioutput &gdi, const wstring &ctrl) {
vector nr;
oCourse::splitControls(gdi.narrow(ctrl), nr);
vector< pair > item;
map used;
for (size_t k = 0; k < nr.size(); k++) {
pControl pc = oe->getControl(nr[k], false, false);
if (pc) {
if (pc->getStatus() == oControl::ControlStatus::StatusOK)
++used[pc->getFirstNumber()];
}
else
++used[nr[k]];
}
set added;
for (int i = 10; i > 0; i--) {
for (map::iterator it = used.begin(); it != used.end(); ++it) {
if (it->second >= i && !added.count(it->first)) {
added.insert(it->first);
item.push_back(make_pair(itow(it->first), it->first));
}
}
}
gdi.clearList("CommonControl");
gdi.setItems("CommonControl", item);
}
void TabCourse::fillOtherCourses(gdioutput &gdi, oCourse &crs, bool withLoops) {
vector< pair > ac;
oe->getCourses(ac, L"", true);
set skipped;
skipped.insert(crs.getId());
pCourse longer = crs.getLongerVersion();
int iter = 20;
while (longer && --iter>0) {
skipped.insert(longer->getId());
longer = longer->getLongerVersion();
}
vector< pair > out;
if (withLoops)
out.emplace_back(lang.tl("Färre slingor"), 0);
for (size_t k = 0; k < ac.size(); k++) {
if (!skipped.count(ac[k].second))
out.push_back(ac[k]);
}
gdi.setItems("ShortCourse", out);
}
void TabCourse::saveLegLengths(gdioutput &gdi) {
pCourse pc = oe->getCourse(courseId);
if (!pc)
return;
pc->synchronize(false);
wstring lstr;
bool gotAny = false;
for (int i = 0; i <= pc->getNumControls(); i++) {
wstring t = trim(gdi.getText("c" + itos(i)));
if (t.empty())
t = L"0";
else
gotAny = true;
if (i == 0)
lstr = t;
else
lstr += L";" + t;
}
if (!gotAny)
lstr = L"";
pc->importLegLengths(gdi.narrow(lstr), true);
pc->synchronize(true);
}
oEvent::DrawMethod TabCourse::getDefaultMethod() const {
int dm = oe->getPropertyInt("DefaultDrawMethod", int(oEvent::DrawMethod::MeOS));
if (dm == (int)oEvent::DrawMethod::Random)
return oEvent::DrawMethod::Random;
if (dm == (int)oEvent::DrawMethod::MeOS)
return oEvent::DrawMethod::MeOS;
else
return oEvent::DrawMethod::SOFT;
}
void TabCourse::clearCompetitionData() {
courseId = 0;
addedCourse = false;
tableMode = false;
}
void TabCourse::refreshCourse(const wstring &text, gdioutput &gdi) {
bool firstAsStart = gdi.isChecked("FirstAsStart");
bool lastAsFinish = gdi.isChecked("LastAsFinish");
bool rogaining = gdi.hasWidget("Rogaining") && gdi.getSelectedItem("Rogaining").first == 1;
wstring controls = encodeCourse(text, rogaining, firstAsStart, lastAsFinish);
if (controls != gdi.getText("CourseExpanded"))
gdi.setText("CourseExpanded", controls, true);
}
wstring TabCourse::encodeCourse(const wstring &in, bool rogaining, bool firstStart, bool lastFinish) {
vector newC;
string ins;
wide2String(in, ins);
oCourse::splitControls(ins, newC);
wstring dash = makeDash(L"-");
wstring out;
out.reserve(in.length() * 2);
wstring bf;
if (!rogaining) {
for (size_t i = 0; i < newC.size(); ++i) {
if (i == 0 && (newC.size() > 1 || firstStart)) {
out += lang.tl("Start");
if (firstStart)
out += L"(" + itow(newC[i]) + L")";
else
out += dash + formatControl(newC[i], bf);
if (newC.size() == 1) {
out += dash + lang.tl("Mål");
break;
}
continue;
}
else
out += dash;
if (i + 1 == newC.size()) {
if (i == 0) {
out = lang.tl("Start") + dash;
}
if (lastFinish)
out += lang.tl("Mål") + L"(" + itow(newC[i]) + L")";
else
out += formatControl(newC[i], bf) + dash + lang.tl("Mål");
}
else {
out += formatControl(newC[i], bf);
}
}
}
else {
int pcnt = 0;
for (size_t i = 0; i < newC.size(); ++i) {
if (i > 0)
out += L"; ";
auto pc = oe->getControl(newC[i]);
if (pc)
pcnt += pc->getRogainingPoints();
out += formatControl(newC[i], bf);
}
if (pcnt > 0)
out += L" = " + itow(pcnt) + L"p";
}
return out;
}
const wstring &TabCourse::formatControl(int id, wstring &bf) const {
pControl ctrl = oe->getControl(id, false, true);
if (ctrl) {
bf = ctrl->getString();
return bf;
}
else
return itow(id);
}