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:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user