-- Smart Pickup — ESC-fane -- Viser og redigerer runs og starter/stopper dem. Al logik ligger i ADRunsController; -- her oversættes kun mellem GUI-elementer og run-felter. Alt kører i pcall så en GUI-fejl -- aldrig tager spillet med sig. SmartPickupFrame = {} SmartPickupFrame._mt = Class(SmartPickupFrame, TabbedMenuFrameElement) SmartPickupFrame.REFRESH_INTERVAL_MS = 1000 SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: " SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2 function SmartPickupFrame.new(i18n, getAdEnv) local self = SmartPickupFrame:superClass().new(nil, SmartPickupFrame._mt) self.name = "SmartPickupFrame" self.i18n = i18n self.getAdEnv = getAdEnv self.selectedRunId = nil self.runIds, self.targetIds, self.poolGroups = {}, {}, {} self.rowData = {} -- element -> {kind = "ingredient"|"vehicle", ...} self.clonedRows = {} self.refreshTimer = 0 self.statusOverride = nil self.statusRow = nil -- Text-elementet i den klonede statusrække self.needsRow = nil self.pendingRebuild = false self.frameOpen = false self.focusLost = false self.backButtonInfo = {inputAction = InputAction.MENU_BACK} self.startButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_button_start"), callback = function() self:onClickStartStop() end} self.newButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_button_new"), callback = function() self:onClickNew() end} self.renameButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_button_rename"), callback = function() self:onClickRename() end} self.deleteButtonInfo = {inputAction = InputAction.MENU_CANCEL, text = i18n:getText("spu_button_delete"), callback = function() self:onClickDelete() end} -- Egen reference til listen: fallback'en i refreshButtons skal kunne sende den igen. self.menuButtonInfo = {self.backButtonInfo, self.startButtonInfo, self.newButtonInfo, self.renameButtonInfo, self.deleteButtonInfo} self:setMenuButtonInfo(self.menuButtonInfo) return self end function SmartPickupFrame:delete() SmartPickupFrame:superClass().delete(self) end function SmartPickupFrame:copyAttributes(src) SmartPickupFrame:superClass().copyAttributes(self, src) self.i18n, self.getAdEnv = src.i18n, src.getAdEnv end -- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker. function SmartPickupFrame:initialize() for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab"}) do local prefab = self[name] if prefab ~= nil then prefab:unlinkElement() FocusManager:removeElement(prefab) end end self.ingredientModeOption:setTexts({self.i18n:getText("spu_mode_auto"), self.i18n:getText("spu_mode_manual")}) local loopTexts = {self.i18n:getText("spu_loops_infinite")} for loops = 1, ADRuns.MAX_LOOPS do table.insert(loopTexts, tostring(loops)) end self.loopsOption:setTexts(loopTexts) end -- FocusManager har skiftet API mellem FS-versioner; her hentes det fokuserede element -- så blødt som muligt, fordi det kun bruges til at opdage at fokus lige er slettet. function SmartPickupFrame.getFocusedElement() if FocusManager == nil then return nil end if FocusManager.getFocusedElement ~= nil then local ok, element = pcall(FocusManager.getFocusedElement, FocusManager) if ok then return element end end local data = FocusManager.currentFocusData if type(data) == "table" then return data.focusElement end return nil end function SmartPickupFrame.isDescendantOf(element, ancestor) while element ~= nil do if element == ancestor then return true end element = element.parent end return false end -- rebuild sletter de rækker der kan have fokus (fx vare-rækkerne når Varer skifter til -- Auto); uden dette står fokus på et slettet element og tastatur/controller er død. function SmartPickupFrame:restoreFocusIfLost() if self.focusLost and self.frameOpen and self.boxLayout ~= nil then FocusManager:setFocus(self.boxLayout) end self.focusLost = false end function SmartPickupFrame:onFrameOpen() SmartPickupFrame:superClass().onFrameOpen(self) self.statusOverride = nil self.frameOpen = true self.pendingRebuild = false self:safe(self.rebuild) -- Som AutoDrive's egne sider: fokus på selve listen, ikke på en enkelt række. self:safe(function(frame) FocusManager:setFocus(frame.boxLayout) end) end function SmartPickupFrame:onFrameClose() self.frameOpen = false SmartPickupFrame:superClass().onFrameClose(self) end -- Genopbygning sker her og aldrig inde i et klik-callback: sletter man elementet midt i -- dets egen klik-håndtering, hopper værdien frem og tilbage og fokus ryger. function SmartPickupFrame:update(dt) SmartPickupFrame:superClass().update(self, dt) if self.pendingRebuild then self.pendingRebuild = false self:safe(self.rebuild) end self.refreshTimer = self.refreshTimer + dt if self.refreshTimer >= SmartPickupFrame.REFRESH_INTERVAL_MS then self.refreshTimer = 0 self:safe(self.refreshLive) end end function SmartPickupFrame:safe(fn, ...) local ok, err = pcall(fn, self, ...) if not ok then Logging.warning(SmartPickupFrame.LOG_PREFIX .. "GUI-fejl: %s", tostring(err)) self.statusOverride = string.format(self.i18n:getText("spu_status_error"), tostring(err)) self:setStatusText(self.statusOverride) end end function SmartPickupFrame:setStatusText(text) if self.statusRow ~= nil then self.statusRow:setText(text) end end -- ESC-menuen kalder ikke altid update() på siden, så genopbygningen sker straks. De -- elementer der udløser den (Run/Mål/Varer-tilstand, knapperne) er statiske og slettes -- ikke af rebuild, så det er sikkert; pendingRebuild er kun bagstopper. function SmartPickupFrame:requestRebuild() self.pendingRebuild = false self:safe(self.rebuild) end function SmartPickupFrame:getRun() return self.selectedRunId ~= nil and ADRuns.getRun(ADRunsController.store, self.selectedRunId) or nil end -- ---- opbygning ---- -- addRow melder hver klonet række ind i FocusManager; uden afmelding her ville -- FocusManager beholde slettede rækker og fokus-navigationen gå i stå. function SmartPickupFrame:clearRows() local focused = SmartPickupFrame.getFocusedElement() for _, row in ipairs(self.clonedRows) do if focused ~= nil and SmartPickupFrame.isDescendantOf(focused, row) then self.focusLost = true end FocusManager:removeElement(row) row:delete() end self.clonedRows, self.rowData = {}, {} self.statusRow = nil self.needsRow = nil end function SmartPickupFrame:addRow(prefab, data) local row = prefab:clone(self.boxLayout) FocusManager:loadElementFromCustomValues(row) table.insert(self.clonedRows, row) local option = row:getDescendantByName("option") if option ~= nil then option:setTexts({self.i18n:getText("ui_no"), self.i18n:getText("ui_yes")}) self.rowData[option] = data end return row end -- Tekstrække uden valgmuligheder: sektionstitler, Wait-pulje-linjer og statuslinjen. function SmartPickupFrame:addTextRow(text) local title = self:addRow(self.textPrefab, {kind = "text"}):getDescendantByName("title") title:setText(text) return title end -- AutoDrive farver hver række i onCreate; her sker det efter hver genopbygning, -- fordi rækkerne først findes på det tidspunkt. function SmartPickupFrame:colorRows() local colors = InGameMenuSettingsFrame ~= nil and InGameMenuSettingsFrame.COLOR_ALTERNATING or nil if colors == nil or self.boxLayout == nil then return end local unpackFn = table.unpack or unpack local isEvenRow = false for _, element in ipairs(self.boxLayout.elements) do if element.setImageColor ~= nil and colors[isEvenRow] ~= nil then element:setImageColor(nil, unpackFn(colors[isEvenRow])) end isEvenRow = not isEvenRow end end function SmartPickupFrame:rebuild() local adEnv = self.getAdEnv() local store = ADRunsController.store if self.selectedRunId == nil or ADRuns.getRun(store, self.selectedRunId) == nil then self.selectedRunId = store.runs[1] ~= nil and store.runs[1].id or nil end self:clearRows() self:fillRunOption(adEnv) local run = self:getRun() local hasRun = run ~= nil and adEnv ~= nil for _, element in ipairs({self.targetOption, self.ingredientModeOption, self.waitPoolOption, self.loopsOption}) do element:setDisabled(not hasRun) end if hasRun then self:fillTargetOption(adEnv, run) self.ingredientModeOption:setState(run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) self:fillIngredientRows(adEnv, run) self.needsRow = self:addTextRow(self:describeNeeds(adEnv, run)) self:fillWaitPoolOption(adEnv, run) self.loopsOption:setState(run.loops + 1, false) self:addTextRow(self.i18n:getText("spu_vehicles")) self:fillVehicleRows(adEnv, run) end if adEnv ~= nil then self:addTextRow(self.i18n:getText("spu_waitPools")) self:fillPoolRows(adEnv) end self.statusRow = self:addTextRow("") self:colorRows() self.boxLayout:invalidateLayout() self:refreshLive() self:restoreFocusIfLost() end function SmartPickupFrame:fillRunOption(adEnv) local texts, index = {}, 1 self.runIds = {} for position, run in ipairs(ADRunsController.store.runs) do local status = adEnv ~= nil and ADRunsController.getRunStatus(adEnv, run) or {running = 0, total = #run.vehicleIds} table.insert(texts, string.format("%s (%d/%d)", run.name, status.running, status.total)) table.insert(self.runIds, run.id) if run.id == self.selectedRunId then index = position end end if #texts == 0 then texts = {self.i18n:getText("spu_noRuns")} end self.runOption:setTexts(texts) self.runOption:setState(index, false) self.runOption:setDisabled(#self.runIds == 0) end function SmartPickupFrame:fillTargetOption(adEnv, run) local texts, index = {self.i18n:getText("spu_noTarget")}, 1 self.targetIds = {false} for _, marker in ipairs(ADRunsController.getTargetMarkers(adEnv, g_currentMission:getFarmId())) do table.insert(texts, tostring(marker.name)) table.insert(self.targetIds, marker.id) if marker.id == run.targetWayPointId then index = #texts end end self.targetOption:setTexts(texts) self.targetOption:setState(index, false) end function SmartPickupFrame:fillIngredientRows(adEnv, run) if run.ingredientMode ~= "manual" then return end local chosen = {} for _, name in ipairs(run.ingredients) do chosen[name] = true end for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do local row = self:addRow(self.ingredientPrefab, {kind = "ingredient", name = fillType.name}) row:getDescendantByName("title"):setText(tostring(fillType.title)) row:getDescendantByName("option"):setState(chosen[fillType.name] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) end end function SmartPickupFrame:fillWaitPoolOption(adEnv, run) local texts, index = {self.i18n:getText("spu_waitPool_all")}, 1 self.poolGroups = {""} for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do table.insert(texts, string.format("%s (%d)", pool.group, pool.count)) table.insert(self.poolGroups, pool.group) if pool.group == run.waitPoolGroup then index = #texts end end self.waitPoolOption:setTexts(texts) self.waitPoolOption:setState(index, false) end function SmartPickupFrame:fillVehicleRows(adEnv, run) local inRun = {} for _, id in ipairs(run.vehicleIds) do inRun[id] = true end for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do local id = ADRunsController.getVehicleId(vehicle) local row = self:addRow(self.vehiclePrefab, {kind = "vehicle", vehicle = vehicle, row = nil}) local option, title = row:getDescendantByName("option"), row:getDescendantByName("title") self.rowData[option].row = row option:setState(inRun[id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) -- Traktorer i et ANDET run kan ikke vælges her (én traktor = ét run); rækken viser hvor den er. local otherRun = ADRunsController.getRunForVehicle(vehicle) if otherRun ~= nil and otherRun.id ~= run.id then option:setDisabled(true) self.rowData[option].locked = true title:setText(string.format("%s — %s", ADRunsController.getVehicleLabel(adEnv, vehicle), string.format(self.i18n:getText("spu_inRun"), tostring(otherRun.name)))) else title:setText(self:describeVehicle(adEnv, run, vehicle, inRun[id])) end end end -- "Fastrac 2135 4WS (#1) — 1.000.000 l — ✔" / "… — ✖ ingen vogn"; reservationsnote fra Start bevares. -- "Fastrac 2135 4WS (#1) — 1.000.000 l — kan bære: Halm, TMR — ✔" / "… — ✖ ingen vogn". -- Vises for ALLE traktorer, så man kan se om vognen passer, før man siger Ja. function SmartPickupFrame:describeVehicle(adEnv, run, vehicle, isInRun) local label = ADRunsController.getVehicleLabel(adEnv, vehicle) local validation = ADRunsController.validateVehicle(adEnv, run, vehicle) local parts = {label} if validation.capacity ~= nil then table.insert(parts, string.format(self.i18n:getText("spu_capacity"), g_i18n:formatNumber(validation.capacity, 0))) local carriable = self:describeCarriable(adEnv, run, vehicle, validation.carriable) if carriable ~= nil then table.insert(parts, carriable) end end if validation.ok then table.insert(parts, "✔") else table.insert(parts, "✖ " .. self.i18n:getText("spu_reason_" .. tostring(validation.reason))) end return table.concat(parts, " — ") end -- Hvilke af run'ets varer vognen kan bære (titler); nil når ingen. function SmartPickupFrame:describeCarriable(adEnv, run, vehicle, carriable) local canCarry = {} for _, fillType in ipairs(carriable or {}) do canCarry[fillType] = true end local titles = {} for _, fillType in ipairs(ADRunsController.getRunIngredients(adEnv, run, vehicle)) do if canCarry[fillType] then local title = g_fillTypeManager.getFillTypeTitleByIndex ~= nil and g_fillTypeManager:getFillTypeTitleByIndex(fillType) or nil table.insert(titles, tostring(title or g_fillTypeManager:getFillTypeNameByIndex(fillType))) end end if #titles == 0 then return nil end return string.format(self.i18n:getText("spu_canCarry"), table.concat(titles, ", ")) end -- Felterne følger modellen: når et klik ikke er nået ind i run'et, springer feltet tilbage, -- og loggen ("run '…': waitPoolGroup=…") viser om klikket blev registreret. function SmartPickupFrame:syncOptionsFromModel(adEnv, run) local function sync(option, wantedIndex) if option ~= nil and wantedIndex ~= nil and SmartPickupFrame.getOptionState(option) ~= wantedIndex then option:setState(wantedIndex, false) end end local targetIndex = 1 for index, id in ipairs(self.targetIds) do if id == run.targetWayPointId then targetIndex = index end end sync(self.targetOption, targetIndex) sync(self.ingredientModeOption, run.ingredientMode == "manual" and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF) local poolIndex = 1 for index, group in ipairs(self.poolGroups) do if group == run.waitPoolGroup then poolIndex = index end end sync(self.waitPoolOption, poolIndex) sync(self.loopsOption, run.loops + 1) end -- "Hentes (mest trængende først): Forage 0/33.750 l · Silage 0/33.750 l · Straw 21.375/21.375 l" function SmartPickupFrame:describeNeeds(adEnv, run) local farmId = g_currentMission:getFarmId() local target = run.targetWayPointId ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, run.targetWayPointId, farmId) or nil if target == nil then return self.i18n:getText("spu_needs_noTarget") end local fillTypes = {} if run.ingredientMode == "manual" then for _, name in ipairs(run.ingredients) do local index = g_fillTypeManager:getFillTypeIndexByName(name) if index ~= nil then table.insert(fillTypes, index) end end else for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do table.insert(fillTypes, fillType.index) end end local needs = {} for _, fillType in ipairs(fillTypes) do local level, capacity = ADSmartPickup.getStationLevelAndCapacity(target, fillType, farmId) if level ~= nil then table.insert(needs, {fillType = fillType, level = level, capacity = capacity, free = capacity - level}) end end table.sort(needs, function(a, b) if a.free ~= b.free then return a.free > b.free end return a.fillType < b.fillType end) local parts = {} for _, need in ipairs(needs) do local title = g_fillTypeManager.getFillTypeTitleByIndex ~= nil and g_fillTypeManager:getFillTypeTitleByIndex(need.fillType) or g_fillTypeManager:getFillTypeNameByIndex(need.fillType) table.insert(parts, string.format("%s %s/%s l", tostring(title), g_i18n:formatNumber(need.level, 0), g_i18n:formatNumber(need.capacity, 0))) end if #parts == 0 then return self.i18n:getText("spu_needs_none") end return string.format(self.i18n:getText("spu_needs"), table.concat(parts, " · ")) end function SmartPickupFrame:fillPoolRows(adEnv) if adEnv == nil then return end local occupied = {} for _, claim in ipairs(ADUnloadWait.getClaims()) do occupied[claim.marker.group] = occupied[claim.marker.group] or {} -- kun løbenummeret ("#31") — fulde navne klippes af i rækken local label = ADRunsController.getVehicleLabel(adEnv, claim.vehicle) table.insert(occupied[claim.marker.group], string.match(label, "%(#(%d+)%)") and ("#" .. string.match(label, "%(#(%d+)%)")) or label) end for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do local names = occupied[pool.group] or {} local suffix = #names > 0 and (" (" .. table.concat(names, ", ") .. ")") or "" self:addTextRow(string.format(self.i18n:getText("spu_waitPoolRow"), pool.group, pool.count, #names, suffix)) end end -- BinaryOption har :getState() i FS25, men feltet .state i ældre GUI-versioner. function SmartPickupFrame.getOptionState(option) return option.getState ~= nil and option:getState() or option.state end -- setMenuButtonInfoDirty findes i FS22/25's TabbedMenuFrameElement; mangler den, -- sendes listen igen. Kaldes hvert sekund, så den må aldrig kaste. function SmartPickupFrame:refreshButtons() if self.setMenuButtonInfoDirty ~= nil then self:setMenuButtonInfoDirty() else self:setMenuButtonInfo(self.menuButtonInfo) end end -- Status + Start/Stop-knappens tekst, kaldes hvert sekund. function SmartPickupFrame:refreshLive() local adEnv = self.getAdEnv() local run = self:getRun() if adEnv == nil then self:setStatusText(self.i18n:getText("spu_status_noAd")) return end local isRunning = false if run ~= nil then self:syncOptionsFromModel(adEnv, run) if self.needsRow ~= nil then self.needsRow:setText(self:describeNeeds(adEnv, run)) end local status = ADRunsController.getRunStatus(adEnv, run) isRunning = status.running > 0 local text = isRunning and string.format(self.i18n:getText("spu_status_running"), status.running, status.total) or string.format(self.i18n:getText("spu_status_stopped"), status.total) self:setStatusText(self.statusOverride or text) for option, data in pairs(self.rowData) do -- kun run'ets egne traktorer opdateres live (alle valideres ved rebuild) local isInRun = SmartPickupFrame.getOptionState(option) == SmartPickupFrame.STATE_ON if data.kind == "vehicle" and data.row ~= nil and isInRun then data.row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, data.vehicle, true)) end end else self:setStatusText(self.statusOverride or self.i18n:getText("spu_noRuns")) end -- Engangsbesked ("Startet 2, sprunget over 1", "flyttet fra ..."): vist nu, ryddet her, -- så næste opdatering om et sekund viser den levende status igen. self.statusOverride = nil self.startButtonInfo.text = self.i18n:getText(isRunning and "spu_button_stop" or "spu_button_start") self.startButtonInfo.disabled = run == nil self.renameButtonInfo.disabled = run == nil self.deleteButtonInfo.disabled = run == nil self:refreshButtons() end -- ---- callbacks fra XML (state = 1-baseret indeks; element = det klikkede element) ---- function SmartPickupFrame:onClickRun(state) self.selectedRunId = self.runIds[state] self.statusOverride = nil self:requestRebuild() end function SmartPickupFrame:onClickTarget(state) self:safe(function(frame) local run = frame:getRun() if run == nil then return end -- targetIds[1] er false = "(vælg mål)"; modellen tolker false som "ryd målet" ADRunsController.updateRun(run.id, {targetWayPointId = frame.targetIds[state] or false}) frame:requestRebuild() end) end function SmartPickupFrame:onClickIngredientMode(state) self:safe(function(frame) local run = frame:getRun() if run == nil then return end ADRunsController.updateRun(run.id, {ingredientMode = state == SmartPickupFrame.STATE_ON and "manual" or "auto"}) frame:requestRebuild() end) end function SmartPickupFrame:onClickIngredient(state, element) self:safe(function(frame) local run, data = frame:getRun(), frame.rowData[element] if run == nil or data == nil then return end local names = {} for _, name in ipairs(run.ingredients) do if name ~= data.name then table.insert(names, name) end end if state == SmartPickupFrame.STATE_ON then table.insert(names, data.name) end ADRunsController.updateRun(run.id, {ingredients = names}) end) end function SmartPickupFrame:onClickWaitPool(state) self:safe(function(frame) local run = frame:getRun() if run == nil then return end ADRunsController.updateRun(run.id, {waitPoolGroup = frame.poolGroups[state] or ""}) end) end function SmartPickupFrame:onClickLoops(state) self:safe(function(frame) local run = frame:getRun() if run == nil then return end ADRunsController.updateRun(run.id, {loops = state - 1}) end) end function SmartPickupFrame:onClickVehicle(state, element) self:safe(function(frame) local run, data = frame:getRun(), frame.rowData[element] if run == nil or data == nil or data.locked then return end if state == SmartPickupFrame.STATE_ON then local previousRunId = ADRunsController.assignVehicle(run.id, data.vehicle) if previousRunId ~= nil then local previous = ADRuns.getRun(ADRunsController.store, previousRunId) frame.statusOverride = string.format(frame.i18n:getText("spu_movedFrom"), previous ~= nil and previous.name or tostring(previousRunId)) end else ADRunsController.unassignVehicle(data.vehicle) end frame:refreshLive() end) end -- ---- knapper i bundlinjen ---- function SmartPickupFrame:onClickStartStop() self:safe(function(frame) local adEnv, run = frame.getAdEnv(), frame:getRun() if adEnv == nil or run == nil then return end if ADRunsController.getRunStatus(adEnv, run).running > 0 then ADRunsController.stopRun(adEnv, run.id) frame.statusOverride = nil else local started, skipped = 0, 0 for _, result in ipairs(ADRunsController.startRun(adEnv, run.id)) do if result.ok then started = started + 1 else skipped = skipped + 1 end end frame.statusOverride = string.format(frame.i18n:getText("spu_status_started"), started, skipped) end ADRunsController.save() frame:requestRebuild() end) end function SmartPickupFrame:onClickNew() self:safe(function(frame) -- nextId er id'et det NYE run får; navnet skal matche det, ikke det forrige. local run = ADRunsController.createRun(string.format(frame.i18n:getText("spu_newRunName"), ADRunsController.store.nextId)) frame.selectedRunId = run.id frame.statusOverride = nil frame:requestRebuild() end) end function SmartPickupFrame:onClickRename() -- Dialogen kan kaste (fx manglende i18n-nøgle) — fang det som resten af GUI'et, i stedet -- for at lade fejlen undslippe klik-callbacken uden statuslinje-besked. self:safe(function(frame) local run = frame:getRun() if run == nil then return end TextInputDialog.show(SmartPickupFrame.onRenameResult, frame, run.name, frame.i18n:getText("spu_renameTitle"), nil, 30, g_i18n:getText("button_ok")) end) end function SmartPickupFrame:onRenameResult(value, clickOk) self:safe(function(frame) local run = frame:getRun() if clickOk and run ~= nil and value ~= nil and value:match("%S") then ADRunsController.updateRun(run.id, {name = value}) frame:requestRebuild() end end) end function SmartPickupFrame:onClickDelete() -- Samme begrundelse som onClickRename: dialog-opkaldet skal ikke kunne undslippe uden om safe(). self:safe(function(frame) local run = frame:getRun() if run == nil then return end YesNoDialog.show(SmartPickupFrame.onDeleteResult, frame, string.format(frame.i18n:getText("spu_deleteConfirm"), run.name), frame.i18n:getText("spu_button_delete")) end) end function SmartPickupFrame:onDeleteResult(yes) self:safe(function(frame) local run = frame:getRun() if yes and run ~= nil then ADRunsController.deleteRun(run.id) frame.selectedRunId = nil frame:requestRebuild() end end) end