diff --git a/application/editormodesrk.cpp b/application/editormodesrk.cpp index db2a8598..3c128de2 100644 --- a/application/editormodesrk.cpp +++ b/application/editormodesrk.cpp @@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/. #include "editor/editorFormat.hpp" #include "editor/editorGeometry.hpp" #include "editor/editorSwitchStore.hpp" +#include "editor/editorTrack.hpp" #include "simulation/simulation.h" #include "utilities/Globals.h" @@ -27,6 +28,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include #include namespace @@ -60,6 +62,11 @@ std::string shown(std::string const &Word) return Word == "-" ? std::string{} : Word; } +template std::string anchor_of(Record_ const &Entry) +{ + return Entry.track != nullptr && false == Entry.track->m_editorremoved ? Entry.track->name() : Entry.anchor; +} + } // namespace void srk_tool::load() @@ -95,6 +102,7 @@ void srk_tool::load() } entry.code = shown(entry.code); entry.line = shown(entry.line); + entry.track = simulation::Paths.find(entry.anchor); if (false == entry.anchor.empty()) m_records.push_back(std::move(entry)); } @@ -118,7 +126,7 @@ void srk_tool::store() continue; if (lines.empty()) lines.emplace_back("# stations described in the track editor, the simulation skips these lines"); - lines.push_back(format("station %s line %s anchor %s name %s", word_of(entry.code).c_str(), word_of(entry.line).c_str(), entry.anchor.c_str(), entry.name.c_str())); + lines.push_back(format("station %s line %s anchor %s name %s", word_of(entry.code).c_str(), word_of(entry.line).c_str(), anchor_of(entry).c_str(), entry.name.c_str())); } scene::Layers.mark(layer, kMark, std::move(lines)); } @@ -140,24 +148,32 @@ void srk_tool::open(TTrack &Track) // the record of the station, by any of its switches m_record = -1; for (int index = 0; index < static_cast(m_records.size()) && m_record < 0; ++index) - if (std::any_of(m_layout->switches.begin(), m_layout->switches.end(), [&](TTrack const *Switch) { return Switch->name() == m_records[index].anchor; })) + { + auto &entry{m_records[index]}; + if (entry.track == nullptr) + entry.track = simulation::Paths.find(entry.anchor); + if (std::find(m_layout->switches.begin(), m_layout->switches.end(), entry.track) != m_layout->switches.end()) m_record = index; - m_anchor = m_record >= 0 ? m_records[m_record].anchor : m_layout->switches.front()->name(); + } + m_anchor = m_record >= 0 ? m_records[m_record].track : m_layout->switches.front(); } void srk_tool::refresh() { m_history_size = m_history.history_size(); - auto *anchor{m_anchor.empty() ? nullptr : simulation::Paths.find(m_anchor)}; - if (anchor == nullptr || anchor->m_editorremoved) + if (m_anchor == nullptr || m_anchor->m_editorremoved) { m_layout.reset(); return; } auto const view{m_view_state}; - open(*anchor); + auto const status{m_status}; + open(*m_anchor); if (m_layout.has_value()) + { m_view_state = view; + m_status = status; + } } srk_tool::record &srk_tool::record_of_open() @@ -165,8 +181,9 @@ srk_tool::record &srk_tool::record_of_open() if (m_record < 0) { record entry; - auto const *first{m_layout->switches.front()}; + auto *first{m_layout->switches.front()}; entry.anchor = first->name(); + entry.track = first; entry.layer = scene::Layers.resolve(first->layer()); if (false == scene::Layers.valid(entry.layer) || false == scene::Layers.accepts(entry.layer)) entry.layer = scene::Layers.active(); @@ -174,7 +191,7 @@ srk_tool::record &srk_tool::record_of_open() entry.layer = 1; m_records.push_back(std::move(entry)); m_record = static_cast(m_records.size()) - 1; - m_anchor = m_records.back().anchor; + m_anchor = first; } return m_records[m_record]; } @@ -189,7 +206,7 @@ std::string srk_tool::title() const if (false == entry.code.empty()) return entry.code; } - return format(STR_C("Station of %s"), m_anchor.c_str()); + return format(STR_C("Station of %s"), m_anchor != nullptr ? m_anchor->name().c_str() : ""); } void srk_tool::find_all() @@ -205,9 +222,14 @@ void srk_tool::update() if (false == m_open) return; load(); - if (m_layout.has_value() && m_history.history_size() != m_history_size) - refresh(); - else if (false == m_layout.has_value() && false == m_anchor.empty() && m_history.history_size() != m_history_size) + // the stations follow their anchors renamed + for (auto &entry : m_records) + if (auto const name{anchor_of(entry)}; name != entry.anchor) + { + entry.anchor = name; + m_changed = true; + } + if ((m_layout.has_value() || m_anchor != nullptr) && m_history.history_size() != m_history_size) refresh(); // a track of another station selected opens it auto *track{m_selection.selected_track()}; @@ -223,6 +245,20 @@ void srk_tool::forget(scene::basic_node const *Node) { if (Node == m_picked) m_picked = nullptr; + for (auto &entry : m_records) + if (entry.track == Node) + entry.track = nullptr; + if (Node == m_anchor) + { + m_anchor = nullptr; + if (m_layout.has_value()) + for (auto *track : m_layout->switches) + if (track != Node) + { + m_anchor = track; + break; + } + } if (m_layout.has_value() && std::any_of(m_layout->paths.begin(), m_layout->paths.end(), [&](station::path const &Path) { return Path.track == Node; })) { // laid out again from its anchor at the next update @@ -274,7 +310,7 @@ void srk_tool::render_found() for (std::size_t index = 0; index < m_found.size(); ++index) { auto const &[name, count]{m_found[index]}; - auto const described{std::find_if(m_records.begin(), m_records.end(), [&](record const &Entry) { return Entry.anchor == name; })}; + auto const described{std::find_if(m_records.begin(), m_records.end(), [&](record const &Entry) { return anchor_of(Entry) == name; })}; auto label{format(STR_P("%s: %zu switch", "%s: %zu switches", count), name.c_str(), count)}; if (described != m_records.end() && false == described->name.empty()) label = described->name + " - " + label; @@ -332,6 +368,18 @@ void srk_tool::render_options() item_tooltip("The number of the railway line the station lies on, as the block sections between the stations are named after it"); ImGui::EndTable(); } + step_heading(1, STR_C("Switch numbers")); + int from{m_reversed ? 1 : 0}; + if (segmented("##srkfrom", from, + {{0, STR_C("From the left"), layout.kilometrage ? "From the lower km, as PKP PLK numbers them" : "From the west end of the station"}, + {1, STR_C("From the right"), layout.kilometrage ? "From the higher km" : "From the east end of the station"}})) + m_reversed = from == 1; + if (main_button(STR_C("Number the switches"), ImVec2(-1.0f, tall_button_height()))) + number_switches(); + item_tooltip("Numbers the switches 1, 2, 3... along the station and names them after its code, as on the lines of PKP PLK: bpd_1, " + "the parts of a slip bpd_15a to bpd_15d. The drives follow their switches, events written by hand keep the old names. One step of undo"); + if (false == m_status.empty()) + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", m_status.c_str()); ImGui::Spacing(); if (ImGui::CollapsingHeader(format("%s (%zu)###srkswitches", STR_C("Switches"), layout.switches.size()).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { @@ -369,18 +417,86 @@ void srk_tool::render_options() } } ImGui::Spacing(); - if (false == m_status.empty()) - ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", m_status.c_str()); if (ImGui::Button(STR_C("Another station"), ImVec2(-1.0f, 0.0f))) { m_layout.reset(); - m_anchor.clear(); + m_anchor = nullptr; m_record = -1; m_hover = -1; } item_tooltip("Leaves this station; LMB on a switch opens another one"); } +void srk_tool::number_switches() +{ + m_status.clear(); + auto const code{m_record >= 0 ? m_records[m_record].code : std::string{}}; + if (code.empty()) + { + m_status = STR_C("Give the station its code first: the switches are named after it"); + return; + } + auto const units{station::numbering(*m_layout, m_reversed)}; + std::vector> names; + std::unordered_set own; + for (std::size_t index = 0; index < units.size(); ++index) + for (std::size_t part = 0; part < units[index].parts.size(); ++part) + { + names.emplace_back(units[index].parts[part], station::switch_name(code, static_cast(index) + 1, part, units[index].parts.size())); + own.insert(units[index].parts[part]); + } + for (auto &[track, name] : names) + { + std::string reason; + TTrack *owner{nullptr}; + if (false == scene::Layers.editable(track, &reason)) + { + m_status = format(STR_C("%s can't be renamed: %s"), track->name().c_str(), reason.c_str()); + return; + } + if (false == editor_track::name_valid(*track, name, reason, &owner) && (owner == nullptr || own.count(owner) == 0)) + { + m_status = format(STR_C("%s can't be named %s: %s"), track->name().c_str(), name.c_str(), reason.c_str()); + return; + } + } + std::vector> before; + std::map parts; + std::map bases; + for (auto const &[track, name] : names) + if (track->name() != name) + { + before.emplace_back(track, editor_track::capture(*track)); + parts[track->name()] = name; + } + for (std::size_t index = 0; index < units.size(); ++index) + if (false == units[index].record.empty()) + bases[units[index].record] = station::switch_name(code, static_cast(index) + 1, 0, 1); + switch_store::rename(parts, bases); + // the names go round among the switches: a switch whose name another one gets gives it up first + for (auto const &entry : names) + if (entry.first->name() != entry.second && std::any_of(names.begin(), names.end(), [&](auto const &Other) { return Other.first != entry.first && Other.second == entry.first->name(); })) + editor_track::rename(*entry.first, entry.first->name() + "~"); + for (auto const &[track, name] : names) + editor_track::rename(*track, name); + std::size_t numbered{0}; + for (std::size_t index = 0; index < units.size(); ++index) + if (auto const record{switch_store::named(*units[index].parts.front())}; record && record->number != std::to_string(index + 1)) + { + switch_store::number(record->name, std::to_string(index + 1)); + ++numbered; + } + if (before.empty() && numbered == 0) + { + m_status = STR_C("The switches have these numbers already"); + return; + } + if (before.empty()) + before.emplace_back(units.front().parts.front(), editor_track::capture(*units.front().parts.front())); + m_history.push_track_snapshot(std::move(before)); + m_status = format(STR_P("%zu switch numbered", "%zu switches numbered", units.size()), units.size()); +} + void srk_tool::fit_view(float const Width, float const Height) { auto const &layout{*m_layout}; @@ -508,13 +624,17 @@ void srk_tool::render_canvas(float const Height) draw_path(path, overlay_color::selected, 4.0f); if (m_hover >= 0) draw_path(layout.paths[m_hover], kHovered, 4.0f); - // the switches: a dot at the start, the name over or under it in turn + // the switches: a dot at the start, the name over or under it in turn; the number alone, once named after the station + auto const prefix{m_record >= 0 && false == m_records[m_record].code.empty() ? station::switch_name(m_records[m_record].code, 0, 0, 1) : std::string{}}; + auto const stem{prefix.empty() ? std::string{} : prefix.substr(0, prefix.size() - 1)}; for (std::size_t index = 0; index < layout.switches.size(); ++index) { auto const *track{layout.switches[index]}; auto const at{screen_of(station::project(layout, track->m_paths.front().points[segment_data::point::start]))}; draw->AddCircleFilled(at, 3.5f, kSwitch); - auto const text{track->name()}; + auto text{track->name()}; + if (false == stem.empty() && text.size() > stem.size() && text.compare(0, stem.size(), stem) == 0) + text.erase(0, stem.size()); auto const extent{ImGui::CalcTextSize(text.c_str())}; ImVec2 const place{at.x - extent.x * 0.5f, index % 2 == 0 ? at.y - extent.y - 6.0f : at.y + 6.0f}; draw->AddRectFilled(ImVec2(place.x - 2.0f, place.y - 1.0f), ImVec2(place.x + extent.x + 2.0f, place.y + extent.y + 1.0f), IM_COL32(22, 24, 28, 200), 2.0f); @@ -597,7 +717,7 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri ++approach; else tracks.insert(path.track); - WriteLog(format("SELFTEST srkopen %s: %zu switches, %zu tracks, %zu paths out, axis %.0f m, switches over %.0f..%.0f, offsets %.1f..%.1f, km %s, record %d", m_anchor.c_str(), layout.switches.size(), tracks.size(), approach, + WriteLog(format("SELFTEST srkopen %s: %zu switches, %zu tracks, %zu paths out, axis %.0f m, switches over %.0f..%.0f, offsets %.1f..%.1f, km %s, record %d", m_anchor->name().c_str(), layout.switches.size(), tracks.size(), approach, layout.chainage.back(), layout.from, layout.to, layout.low, layout.high, layout.kilometrage ? format("%.3f", layout.km).c_str() : "-", m_record)); for (auto const *track : layout.switches) { @@ -606,6 +726,29 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri } return editor_selftest_result::handled; } + if (Command == "srknumber") + { + // srknumber [from=right]: the switches of the open station numbered + if (false == m_layout.has_value()) + { + WriteLog("SELFTEST srknumber: no station open"); + return editor_selftest_result::handled; + } + auto const values{selftest_values(Words)}; + auto const from{values.find("from")}; + m_reversed = from != values.end() && from->second == "right"; + number_switches(); + std::string list; + for (auto const &unit : station::numbering(*m_layout, m_reversed)) + for (auto const *part : unit.parts) + { + auto const record{switch_store::named(*part)}; + list += ' ' + part->name() + '/' + (record ? record->number : std::string{"?"}); + } + WriteLog("SELFTEST srknumber: " + m_status); + WriteLog("SELFTEST srknumber names:" + list); + return editor_selftest_result::handled; + } if (Command == "srkstations") { load(); diff --git a/application/editormodesrk.h b/application/editormodesrk.h index 2460bda5..557c4711 100644 --- a/application/editormodesrk.h +++ b/application/editormodesrk.h @@ -42,6 +42,7 @@ class srk_tool final : public editor_tool { scene::layer_handle layer{null_handle}; std::string anchor; // the name of a switch of the station + TTrack *track{nullptr}; // the switch, once found: it stays the anchor when it gets renamed std::string code; // the abbreviation of the station, as MMz std::string name; std::string line; // the number of the railway line @@ -60,7 +61,8 @@ class srk_tool final : public editor_tool std::vector m_records; std::optional m_layout; int m_record{-1}; // of the open station, -1 while it has none - std::string m_anchor; // the switch the open station was found from + TTrack *m_anchor{nullptr}; // the switch the open station was found from + bool m_reversed{false}; // the switches numbered from the right end of the schematic std::size_t m_history_size{0}; TTrack const *m_picked{nullptr}; // the track selected when the station was looked for view m_view_state; @@ -78,4 +80,6 @@ class srk_tool final : public editor_tool void render_canvas(float const Height); void fit_view(float const Width, float const Height); void find_all(); + // the switches numbered along the station and named after its code; one step of the history + void number_switches(); }; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 7da24b60..919a1b93 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -374,6 +374,17 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve for (auto *track : Snapshot.created) editor_track::revive(*track); } + // names passed round among the tracks: a track whose name another one gets back gives it up first. The drives the step + // put back have the names of their switches already + std::vector restored; + for (auto const &entry : Snapshot.directives) + restored.push_back(entry.first); + for (auto const &entry : Snapshot.tracks) + if (entry.second.name != entry.first->name() && std::any_of(Snapshot.tracks.begin(), Snapshot.tracks.end(), [&](auto const &Other) { return Other.first != entry.first && Other.second.name == entry.first->name(); })) + editor_track::rename(*entry.first, entry.first->name() + "~", restored); + for (auto const &entry : Snapshot.tracks) + if (entry.second.name != entry.first->name()) + editor_track::rename(*entry.first, entry.second.name, restored); for (auto const &entry : Snapshot.tracks) editor_track::apply(*entry.first, entry.second); editor_track::commit(tracks); diff --git a/editor/editorStation.cpp b/editor/editorStation.cpp index c60aa4a0..31e3d245 100644 --- a/editor/editorStation.cpp +++ b/editor/editorStation.cpp @@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/. #include "world/Track.h" #include +#include #include #include #include @@ -291,6 +292,12 @@ std::optional find(TTrack &From, options const &Options) result.km = best.origin / 1000.0; } } + // without the km the station runs from the west to the east, as the map shows it + if (false == result.kilometrage && map_of(result.axis.back()).x < map_of(result.axis.front()).x) + { + std::reverse(result.axis.begin(), result.axis.end()); + measure(); + } // the paths, laid out along the axis auto const add = [&](TTrack &Track, bool const Approach) { for (int index = 0; index < static_cast(Track.m_paths.size()); ++index) @@ -391,6 +398,49 @@ std::vector stations(options const &Options) return result; } +std::vector numbering(layout const &Layout, bool const Reversed) +{ + std::unordered_set members; + for (auto const &entry : Layout.paths) + if (false == entry.approach) + members.insert(entry.track); + std::unordered_set taken; + std::vector result; + for (auto *track : Layout.switches) + { + if (track->eType == tt_Table || taken.count(track) > 0) + continue; + unit entry; + if (auto const record{switch_store::named(*track)}) + { + entry.record = record->name; + if (record->kind != "switch") + for (auto const &name : record->parts) + if (auto *part{simulation::Paths.find(name)}; part != nullptr && members.count(part) > 0 && taken.insert(part).second) + entry.parts.push_back(part); + } + if (taken.insert(track).second) + entry.parts.push_back(track); + entry.chainage = std::numeric_limits::max(); + for (auto const *part : entry.parts) + entry.chainage = std::min(entry.chainage, project(Layout, end_of(*part, 0, false)).x); + result.push_back(std::move(entry)); + } + std::stable_sort(result.begin(), result.end(), [&](unit const &A, unit const &B) { return Reversed ? A.chainage > B.chainage : A.chainage < B.chainage; }); + return result; +} + +std::string switch_name(std::string const &Code, int const Number, std::size_t const Part, std::size_t const Parts) +{ + std::string result; + for (auto const c : Code) + result += static_cast(std::tolower(static_cast(c))); + result += '_' + std::to_string(Number); + if (Parts > 1) + result += static_cast('a' + std::min(Part, 25)); + return result; +} + std::vector switches_of(TTrack &Switch, options const &Options) { return collect(Switch, Options).switches; diff --git a/editor/editorStation.hpp b/editor/editorStation.hpp index 96c283a0..a6a85ec1 100644 --- a/editor/editorStation.hpp +++ b/editor/editorStation.hpp @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include +#include #include #include @@ -51,6 +52,14 @@ struct layout double km{0.0}; // at chainage 0, when it does }; +// what shares a number of the station: a switch, or the switches of a slip, the two of a double switch, the paths of a crossing +struct unit +{ + std::vector parts; + std::string record; // the name of its record of the switches, empty without one + double chainage{0.0}; // of the start of its first part along the station +}; + // the station of the switch, or of the plain track near one std::optional find(TTrack &From, options const &Options = {}); // the chainage along the axis and the offset to the left of it, as the map shows it @@ -59,6 +68,10 @@ glm::dvec2 project(layout const &Layout, glm::dvec3 const &Point); double km_of(layout const &Layout, double const Chainage); // one switch of each station of the scenery std::vector stations(options const &Options = {}); +// the switches of the station by their numbers: along the chainage, or against it when Reversed; turntables have none +std::vector numbering(layout const &Layout, bool const Reversed); +// _, each part of a unit of several with a letter of its own: bpd_15a +std::string switch_name(std::string const &Code, int const Number, std::size_t const Part, std::size_t const Parts); // the switches of the station of the switch, without laying it out std::vector switches_of(TTrack &Switch, options const &Options = {}); diff --git a/editor/editorSwitchStore.cpp b/editor/editorSwitchStore.cpp index 02a04a3b..f8a2cedb 100644 --- a/editor/editorSwitchStore.cpp +++ b/editor/editorSwitchStore.cpp @@ -330,6 +330,42 @@ void forget(std::string const &Name) mark(layer); } +void rename(std::map const &Parts, std::map const &Bases) +{ + load(); + auto const renamed = [](std::map const &Names, std::string &Name) { + auto const found{Names.find(Name)}; + if (found == Names.end() || found->second == Name) + return false; + Name = found->second; + return true; + }; + std::set layers; + for (auto &entry : g_records) + { + auto changed{renamed(entry.kind == "slip" || entry.kind == "crossing" ? Bases : Parts, entry.name)}; + for (auto &part : entry.parts) + changed = renamed(Parts, part) || changed; + if (changed) + layers.insert(entry.layer); + } + for (auto const layer : layers) + mark(layer); + g_changed = g_changed || false == layers.empty(); +} + +void number(std::string const &Name, std::string const &Number) +{ + load(); + for (auto &entry : g_records) + if (entry.kind != "connection" && entry.name == Name && entry.number != Number) + { + entry.number = Number; + mark(entry.layer); + g_changed = true; + } +} + void tidy() { load(); diff --git a/editor/editorSwitchStore.hpp b/editor/editorSwitchStore.hpp index 7b5f29f4..69e90c14 100644 --- a/editor/editorSwitchStore.hpp +++ b/editor/editorSwitchStore.hpp @@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include #include #include #include @@ -72,6 +73,11 @@ void put(record Entry, scene::layer_handle const Layer); void place(record &Entry, TTrack const &First); void classify(record &Entry, TTrack const &Switch); void forget(std::string const &Name); +// the records follow renamed switches: their parts and the records named after them; Bases renames the records of slips and +// crossings, whose names aren't the names of their parts +void rename(std::map const &Parts, std::map const &Bases); +// the number of the switch, slip or crossing of the record of the name, as the lists of the switches give it +void number(std::string const &Name, std::string const &Number); // on save: the records follow the renamed switches, the ones whose paths are gone are dropped void tidy(); // "Rz 60E1-500-1:12 L", as the type is written on the lists of the switches diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 01b33559..c2aa39ea 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -367,7 +367,7 @@ editor_track::name_uses editor_track::uses_of_name(TTrack const &Track) return result; } -void editor_track::rename(TTrack &Track, std::string const &Name) +void editor_track::rename(TTrack &Track, std::string const &Name, std::vector const &Kept) { auto const old{Track.name()}; if (old == Name) @@ -388,7 +388,7 @@ void editor_track::rename(TTrack &Track, std::string const &Name) std::string file; std::vector values; auto const index{track_parameter(handle, file, values)}; - if (index < 0 || lower_name(values[index]) != old) + if (index < 0 || lower_name(values[index]) != old || std::find(Kept.begin(), Kept.end(), handle) != Kept.end()) continue; values[index] = Name; scene::Layers.modify(handle, editor_includes::compose_directive(file, values)); diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index b32de786..7e69cc1c 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -86,7 +86,8 @@ class editor_track std::vector loose; }; static name_uses uses_of_name(TTrack const &Track); - static void rename(TTrack &Track, std::string const &Name); + // the includes of Kept keep their directives: an undo step puts them back on its own + static void rename(TTrack &Track, std::string const &Name, std::vector const &Kept = {}); // lists of the events of the path, in the order of event_keyword(): event0, event1, event2, eventall0, eventall1, eventall2 static std::vector> &events(TTrack &Track, int const Index); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 58b5fc97..a9d74cd5 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -10365,3 +10365,48 @@ msgstr[0] "%s: %zu rozjazd" msgstr[1] "%s: %zu rozjazdy" msgstr[2] "%s: %zu rozjazdów" +msgid "Switch numbers" +msgstr "Numery rozjazdów" + +msgid "From the left" +msgstr "Od lewej" + +msgid "From the right" +msgstr "Od prawej" + +msgid "From the lower km, as PKP PLK numbers them" +msgstr "Od niższego km, jak numeruje PKP PLK" + +msgid "From the west end of the station" +msgstr "Od zachodniego końca stacji" + +msgid "From the higher km" +msgstr "Od wyższego km" + +msgid "From the east end of the station" +msgstr "Od wschodniego końca stacji" + +msgid "Number the switches" +msgstr "Ponumeruj rozjazdy" + +msgid "Numbers the switches 1, 2, 3... along the station and names them after its code, as on the lines of PKP PLK: bpd_1, the parts of a slip bpd_15a to bpd_15d. The drives follow their switches, events written by hand keep the old names. One step of undo" +msgstr "Numeruje rozjazdy 1, 2, 3... wzdłuż stacji i nazywa je od jej skrótu, jak na liniach PKP PLK: bpd_1, części rozjazdu krzyżowego bpd_15a do bpd_15d. Napędy idą za swoimi rozjazdami, eventy pisane ręcznie zostają przy starych nazwach. Jeden krok cofania" + +msgid "Give the station its code first: the switches are named after it" +msgstr "Najpierw wpisz skrót stacji: od niego biorą się nazwy rozjazdów" + +msgid "%s can't be renamed: %s" +msgstr "Nie można zmienić nazwy %s: %s" + +msgid "%s can't be named %s: %s" +msgstr "%s nie może dostać nazwy %s: %s" + +msgid "The switches have these numbers already" +msgstr "Rozjazdy mają już te numery" + +msgid "%zu switch numbered" +msgid_plural "%zu switches numbered" +msgstr[0] "Ponumerowano %zu rozjazd" +msgstr[1] "Ponumerowano %zu rozjazdy" +msgstr[2] "Ponumerowano %zu rozjazdów" +