/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2020 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 LoadCoursePage(gdioutput &gdi); void LoadClassPage(gdioutput &gdi); 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); } fillOtherCourses(gdi, *pc, cc != 0); auto sh = pc->getShorterVersion(); 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); } int CourseCB(gdioutput *gdi, int type, void *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); 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"); 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, int type, void *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) { ListBoxInfo clb; if (!gdi.getSelectedItem("ShortCoursse", clb) || clb.data <= 0) gdi.selectFirstItem("CommonControl"); } } 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); xmlparser xml; xml.openOutput(save.c_str(), false); iof30.writeCourses(xml); xml.closeOut(); } } 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"); try { TabCourse::runCourseImport(gdi, filename, oe, gdi.isChecked("AddClasses")); } catch (std::exception &) { gdi.enableInput("DoImportCourse"); gdi.enableInput("Cancel"); gdi.enableInput("BrowseCourse"); gdi.enableInput("AddClasses"); 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.push_back(ClassDrawSpecification(cls[k]->getId(), 0, -1, -1, 0)); 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 = 3600; int interval = 2*60; 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(par, gdi.getLineHeight(), 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"); 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"); selectCourse(gdi, 0); } else if (bi.id == "FirstAsStart" || bi.id == "LastAsFinish") { refreshCourse(gdi.getText("Controls"), gdi); } else if (bi.id=="Cancel"){ LoadPage("Banor"); } } 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"); 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); gdi.addButton("DrawCourse", "Lotta starttider..", CourseCB); gdi.disableInput("DrawCourse"); 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(48u, 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 addClasses) { if (csvparser::iscsv(filename) != csvparser::CSV::NoCSV) { gdi.fillRight(); gdi.pushX(); gdi.addString("", 0, "Importerar OCAD csv-fil..."); gdi.refreshFast(); csvparser csv; if (csv.importOCAD_CSV(*oe, filename, addClasses)) { gdi.addString("", 1, "Klart.").setColor(colorGreen); } else gdi.addString("", 0, "Operationen misslyckades.").setColor(colorRed); gdi.popX(); gdi.dropLine(2.5); gdi.fillDown(); } else { set noFilter; string noType; oe->importXML_EntryData(gdi, filename.c_str(), addClasses, false, noFilter, noType); } if (addClasses) { // 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(); } gdi.addButton(gdi.getWidth()+20, 45, gdi.scaleLength(120), "Print", "Skriv ut...", CourseCB, "Skriv ut listan.", true, false); gdi.addButton(gdi.getWidth()+20, 75, gdi.scaleLength(120), "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.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); if (pc) { if (pc->getStatus() == oControl::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.addItem("CommonControl", item); } void TabCourse::fillOtherCourses(gdioutput &gdi, oCourse &crs, bool withLoops) { vector< pair > ac; oe->fillCourses(ac, 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.clearList("ShortCourse"); gdi.addItem("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); if (ctrl) { bf = ctrl->getString(); return bf; } else return itow(id); }