17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 07:37:28 +01:00

feat(editor): the signalling mode numbers the switches of a station along it and names them after its code, the drives and the switch records follow, and an undo of names passed round among the tracks leaves the drives right

This commit is contained in:
maj00r
2026-10-22 07:50:59 +02:00
parent 8c5795348f
commit 5044beb380
10 changed files with 332 additions and 23 deletions

View File

@@ -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 <algorithm>
#include <array>
#include <cmath>
#include <map>
#include <unordered_set>
namespace
@@ -60,6 +62,11 @@ std::string shown(std::string const &Word)
return Word == "-" ? std::string{} : Word;
}
template <typename Record_> 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<int>(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<int>(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<std::pair<TTrack *, std::string>> names;
std::unordered_set<TTrack const *> 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<int>(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<std::pair<TTrack *, editor_track::state>> before;
std::map<std::string, std::string> parts;
std::map<std::string, std::string> 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<int>(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();

View File

@@ -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<record> m_records;
std::optional<station::layout> 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();
};

View File

@@ -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<scene::instance_handle> 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);

View File

@@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/.
#include "world/Track.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <limits>
#include <unordered_set>
@@ -291,6 +292,12 @@ std::optional<layout> 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<int>(Track.m_paths.size()); ++index)
@@ -391,6 +398,49 @@ std::vector<TTrack *> stations(options const &Options)
return result;
}
std::vector<unit> numbering(layout const &Layout, bool const Reversed)
{
std::unordered_set<TTrack const *> members;
for (auto const &entry : Layout.paths)
if (false == entry.approach)
members.insert(entry.track);
std::unordered_set<TTrack const *> taken;
std::vector<unit> 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<double>::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<char>(std::tolower(static_cast<unsigned char>(c)));
result += '_' + std::to_string(Number);
if (Parts > 1)
result += static_cast<char>('a' + std::min<std::size_t>(Part, 25));
return result;
}
std::vector<TTrack *> switches_of(TTrack &Switch, options const &Options)
{
return collect(Switch, Options).switches;

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <glm/glm.hpp>
@@ -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<TTrack *> 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<layout> 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<TTrack *> 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<unit> numbering(layout const &Layout, bool const Reversed);
// <code>_<number>, 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<TTrack *> switches_of(TTrack &Switch, options const &Options = {});

View File

@@ -330,6 +330,42 @@ void forget(std::string const &Name)
mark(layer);
}
void rename(std::map<std::string, std::string> const &Parts, std::map<std::string, std::string> const &Bases)
{
load();
auto const renamed = [](std::map<std::string, std::string> 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<scene::layer_handle> 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();

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <map>
#include <optional>
#include <string>
#include <vector>
@@ -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<std::string, std::string> const &Parts, std::map<std::string, std::string> 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

View File

@@ -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<scene::instance_handle> 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<std::string> 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));

View File

@@ -86,7 +86,8 @@ class editor_track
std::vector<std::string> 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<scene::instance_handle> const &Kept = {});
// lists of the events of the path, in the order of event_keyword(): event0, event1, event2, eventall0, eventall1, eventall2
static std::vector<std::pair<std::string, basic_event *>> &events(TTrack &Track, int const Index);

View File

@@ -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"