From 9ad36841021cdd5c7f14d52e946d8ecdb602cf78 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 21 Mar 2014 00:33:13 +0200 Subject: [PATCH] New timetable system, which works like an actual timetable --- data/timetablepanel.ui | 59 +++++++ data/traindialog.ui | 7 + source/engineer/timetablepanel.cpp | 222 +++++++++++++++++++++++++ source/engineer/timetablepanel.h | 51 ++++++ source/engineer/traindialog.cpp | 6 + source/libr2c2/timetable.cpp | 252 +++++++++++++++++++++++++++++ source/libr2c2/timetable.h | 89 ++++++++++ source/libr2c2/train.cpp | 14 ++ source/libr2c2/train.h | 1 + 9 files changed, 701 insertions(+) create mode 100644 data/timetablepanel.ui create mode 100644 source/engineer/timetablepanel.cpp create mode 100644 source/engineer/timetablepanel.h create mode 100644 source/libr2c2/timetable.cpp create mode 100644 source/libr2c2/timetable.h diff --git a/data/timetablepanel.ui b/data/timetablepanel.ui new file mode 100644 index 0000000..3434315 --- /dev/null +++ b/data/timetablepanel.ui @@ -0,0 +1,59 @@ +style "group"; +layout +{ + margin + { + horizontal 0; + vertical 0; + }; +}; + +column +{ + list "lst_timetable"; + expand true false; + + row + { + entry "ent_time" + { + edit_size 8 1; + }; + + dropdown "drp_type" + { + item "Arrive"; + item "Depart"; + }; + + label "lbl_zone" + { + text "No selection"; + }; + }; + + row + { + button "btn_insert" + { + text "Ins"; + }; + + button "btn_delete" + { + text "Del"; + }; + + button "btn_apply" + { + text "Apply"; + }; + + split; + + button "btn_pick" + { + text "Pick"; + }; + }; +}; diff --git a/data/traindialog.ui b/data/traindialog.ui index 0b9e70c..b57dc9c 100644 --- a/data/traindialog.ui +++ b/data/traindialog.ui @@ -89,6 +89,13 @@ column style "pointer_left"; exclusive true; }; + + toggle "tgl_timetable" + { + text "Ttbl"; + style "pointer_left"; + exclusive true; + }; }; label "lbl_detail_placeholder" diff --git a/source/engineer/timetablepanel.cpp b/source/engineer/timetablepanel.cpp new file mode 100644 index 0000000..6104873 --- /dev/null +++ b/source/engineer/timetablepanel.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include "libr2c2/zone.h" +#include "engineer.h" +#include "timetablepanel.h" + +using namespace std; +using namespace Msp; +using namespace R2C2; + +class TimetableRowItem: public GLtk::List::MultiColumnItem +{ +public: + typedef const Timetable::Row *ValueType; + + TimetableRowItem(ValueType); +}; + + +string format_time(const Time::TimeDelta &time) +{ + unsigned second = time/Time::sec; + unsigned hour = second/3600; + unsigned minute = second/60%60; + second %= 60; + return format("%02d:%02d:%02d", hour, minute, second); +} + + +TimetablePanel::TimetablePanel(Engineer &e, R2C2::Train &t): + engineer(e), + train(t), + zone(0), + zone_pick(false), + picked_zone(0), + pick_highlight(0) +{ + Loader::WidgetMap widgets; + DataFile::load(*this, "data/timetablepanel.ui", widgets); + + lst_timetable = dynamic_cast(get_item(widgets, "lst_timetable")); + lst_timetable->set_data(rows); + lst_timetable->set_item_type(); + lst_timetable->signal_item_selected.connect(sigc::mem_fun(this, &TimetablePanel::row_selected)); + + drp_type = dynamic_cast(get_item(widgets, "drp_type")); + lbl_zone = dynamic_cast(get_item(widgets, "lbl_zone")); + ent_time = dynamic_cast(get_item(widgets, "ent_time")); + + dynamic_cast(get_item(widgets, "btn_pick"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::pick_clicked)); + dynamic_cast(get_item(widgets, "btn_insert"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::insert_clicked)); + dynamic_cast(get_item(widgets, "btn_delete"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::delete_clicked)); + dynamic_cast(get_item(widgets, "btn_apply"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::apply_clicked)); + + timetable = train.get_ai_of_type(); + if(!timetable) + timetable = new Timetable(train); + + unsigned length = timetable->get_length(); + for(unsigned i=0; iget_row(i)); + rows.append(0); + timetable->signal_row_added.connect(sigc::mem_fun(this, &TimetablePanel::row_added)); + timetable->signal_row_modified.connect(sigc::mem_fun(this, &TimetablePanel::row_modified)); + timetable->signal_row_removed.connect(sigc::mem_fun(this, &TimetablePanel::row_removed)); +} + +void TimetablePanel::row_added(unsigned i, const Timetable::Row &row) +{ + rows.insert(i, &row); +} + +void TimetablePanel::row_modified(unsigned i, const Timetable::Row &) +{ + rows.refresh(i); +} + +void TimetablePanel::row_removed(unsigned i) +{ + rows.remove(i); +} + +Timetable::Row TimetablePanel::create_row() +{ + Timetable::Row row; + + row.type = static_cast(drp_type->get_selected_index()+1); + row.zone = zone; + + Regex r_time("([01]?[0-9]|2[0-3]):([0-5][0-9])(:([0-5][0-9]))?"); + RegMatch m = r_time.match(ent_time->get_text()); + if(m) + { + row.time = lexical_cast(m[1].str)*Time::hour; + row.time += lexical_cast(m[2].str)*Time::min; + if(m[3]) + row.time += lexical_cast(m[4].str)*Time::sec; + } + + return row; +} + +void TimetablePanel::row_selected(unsigned i) +{ + const Timetable::Row *row = rows.get(i); + if(row) + { + drp_type->set_selected_index(row->type-1); + if(row->zone) + lbl_zone->set_text(row->zone->get_name()); + ent_time->set_text(format_time(row->time)); + } +} + +void TimetablePanel::pick_clicked() +{ + zone_pick = true; + picked_zone = 0; + signal_grab_pointer.emit(); +} + +void TimetablePanel::insert_clicked() +{ + int index = lst_timetable->get_selected_index(); + if(index<0) + index = timetable->get_length(); + timetable->insert_row(index, create_row()); +} + +void TimetablePanel::delete_clicked() +{ + int index = lst_timetable->get_selected_index(); + if(index>=0) + timetable->remove_row(index); +} + +void TimetablePanel::apply_clicked() +{ + int index = lst_timetable->get_selected_index(); + if(index>=0) + timetable->modify_row(index, create_row()); +} + +void TimetablePanel::button_press(int x, int y, unsigned btn) +{ + Panel::button_press(x, y, btn); + + if(zone_pick) + { + signal_ungrab_pointer.emit(); + zone_pick = false; + + delete pick_highlight; + pick_highlight = 0; + + if(picked_zone && btn==1) + { + zone = picked_zone; + lbl_zone->set_text(zone->get_name()); + } + } +} + +void TimetablePanel::pointer_motion(int x, int y) +{ + Panel::pointer_motion(x, y); + + if(zone_pick) + { + int rx = x; + int ry = y; + map_coords_to_ancestor(rx, ry, *find_ancestor()); + Ray ray = engineer.get_main_view().create_ray(rx, ry); + Track *track = engineer.get_layout().pick(ray); + if(track) + { + const set &zones = engineer.get_layout().get_all(); + Zone *track_zone = 0; + for(set::const_iterator i=zones.begin(); (!track_zone && i!=zones.end()); ++i) + if((*i)->has_track(*track)) + track_zone = *i; + if(track_zone!=picked_zone) + { + picked_zone = track_zone; + delete pick_highlight; + if(picked_zone) + pick_highlight = new TrackChain3D(engineer.get_layout_3d(), *track_zone); + else + pick_highlight = 0; + } + } + } +} + + +TimetableRowItem::TimetableRowItem(ValueType row) +{ + if(row) + { + add(*new GLtk::Label(format_time(row->time))); + if(row->zone) + { + string type; + switch(row->type) + { + case Timetable::ARRIVE: type = "Arrive at "; break; + case Timetable::DEPART: type = "Depart from "; break; + } + add(*new GLtk::Label(type+row->zone->get_name())); + } + else + add(*new GLtk::Label); + } + else + { + add(*new GLtk::Label("--:--:--")); + add(*new GLtk::Label("End of timetable")); + } +} diff --git a/source/engineer/timetablepanel.h b/source/engineer/timetablepanel.h new file mode 100644 index 0000000..a0f5be8 --- /dev/null +++ b/source/engineer/timetablepanel.h @@ -0,0 +1,51 @@ +#ifndef TIMETABLEPANEL_H_ +#define TIMETABLEPANEL_H_ + +#include +#include +#include +#include +#include +#include "libr2c2/timetable.h" +#include "libr2c2/train.h" +#include "3d/trackchain.h" + +class Engineer; + +class TimetablePanel: public Msp::GLtk::Panel, public sigc::trackable +{ +private: + Engineer &engineer; + R2C2::Train &train; + R2C2::Timetable *timetable; + Msp::GLtk::List *lst_timetable; + Msp::GLtk::Dropdown *drp_type; + R2C2::Zone *zone; + Msp::GLtk::Label *lbl_zone; + Msp::GLtk::Entry *ent_time; + Msp::GLtk::BasicListData rows; + + bool zone_pick; + R2C2::Zone *picked_zone; + R2C2::TrackChain3D *pick_highlight; + +public: + TimetablePanel(Engineer &, R2C2::Train &); + +private: + void row_added(unsigned, const R2C2::Timetable::Row &); + void row_modified(unsigned, const R2C2::Timetable::Row &); + void row_removed(unsigned); + R2C2::Timetable::Row create_row(); + + void row_selected(unsigned); + void pick_clicked(); + void insert_clicked(); + void delete_clicked(); + void apply_clicked(); + + virtual void button_press(int, int, unsigned); + virtual void pointer_motion(int, int); +}; + +#endif diff --git a/source/engineer/traindialog.cpp b/source/engineer/traindialog.cpp index 54dac6a..f202377 100644 --- a/source/engineer/traindialog.cpp +++ b/source/engineer/traindialog.cpp @@ -10,6 +10,7 @@ #include "libr2c2/trainstatus.h" #include "controlpanel.h" #include "routerpanel.h" +#include "timetablepanel.h" #include "traindialog.h" #include "vehiclespanel.h" @@ -57,6 +58,11 @@ TrainDialog::TrainDialog(Engineer &e, R2C2::Train &t): panels.push_back(pnl); dynamic_cast(get_item(widgets, "tgl_router"))->signal_toggled.connect(sigc::bind(sigc::mem_fun(this, &TrainDialog::toggle_panel), pnl)); + pnl_expander->add(*(pnl = new TimetablePanel(engineer, train))); + pnl->set_visible(false); + panels.push_back(pnl); + dynamic_cast(get_item(widgets, "tgl_timetable"))->signal_toggled.connect(sigc::bind(sigc::mem_fun(this, &TrainDialog::toggle_panel), pnl)); + AIControl *control = train.get_ai_of_type(); if(!control) control = new AIControl(train); diff --git a/source/libr2c2/timetable.cpp b/source/libr2c2/timetable.cpp new file mode 100644 index 0000000..f0e49ff --- /dev/null +++ b/source/libr2c2/timetable.cpp @@ -0,0 +1,252 @@ +#include +#include "clock.h" +#include "layout.h" +#include "timetable.h" +#include "train.h" +#include "zone.h" + +using namespace std; +using namespace Msp; + +namespace R2C2 { + +Timetable::Timetable(Train &t): + TrainAI(t), + current_row(rows.end()), + update_pending(false) +{ + train.signal_ai_event.connect(sigc::mem_fun(this, &Timetable::event)); +} + +void Timetable::append_row(const Row &r) +{ + insert_row(rows.size(), r); +} + +void Timetable::insert_row(unsigned i, const Row &r) +{ + if(i>rows.size()) + throw out_of_range("Timetable::insert_row"); + + list::iterator j = rows.begin(); + advance(j, i); + j = rows.insert(j, r); + signal_row_added.emit(i, *j); + + check_update(j); +} + +void Timetable::modify_row(unsigned i, const Row &r) +{ + if(i>=rows.size()) + throw out_of_range("Timetable::remove_row"); + + list::iterator j = rows.begin(); + advance(j, i); + *j = r; + signal_row_modified.emit(i, r); + + check_update(j); +} + +void Timetable::remove_row(unsigned i) +{ + if(i>=rows.size()) + throw out_of_range("Timetable::remove_row"); + + list::iterator j = rows.begin(); + advance(j, i); + + check_update(j); + if(j==current_row) + --current_row; + + rows.erase(j); + signal_row_removed.emit(i); +} + +const Timetable::Row &Timetable::get_row(unsigned i) const +{ + if(i>=rows.size()) + throw out_of_range("Timetable::get_row"); + + list::const_iterator j = rows.begin(); + advance(j, i); + return *j; +} + +void Timetable::tick(const Time::TimeDelta &dt) +{ + if(update_pending && !train.get_block_allocator().is_active()) + update_route(); + + if(current_row->type==DEPART) + { + const Clock &clock = train.get_layout().get_clock(); + + Time::TimeDelta t = clock.get_current_time(); + if(ttime) + t += Time::day; + + Time::TimeDelta b = t-dt*clock.get_rate(); + if(btime) + { + train.ai_message(Message("set-target-speed", train.get_maximum_speed())); + ++current_row; + } + } +} + +void Timetable::save(list &st) const +{ + for(list::const_iterator i=rows.begin(); i!=rows.end(); ++i) + { + DataFile::Statement ss("row"); + i->save(ss.sub); + st.push_back(ss); + } +} + +void Timetable::check_update(list::const_iterator i) +{ + for(list::const_iterator j=current_row; (j!=rows.end() && j!=i); ++j) + if(j->type==ARRIVE) + return; + update_pending = true; +} + +void Timetable::update_route() +{ + update_pending = false; + if(rows.empty()) + return; + + list::iterator depart = rows.end(); + for(list::iterator i=current_row;; ) + { + if(i==rows.end()) + { + i = rows.begin(); + depart = rows.end(); + } + + if(i->type==DEPART) + depart = i; + else if(depart!=rows.end() && i->type==ARRIVE) + break; + + ++i; + if(i==current_row) + { + current_row = rows.end(); + return; + } + } + + train.ai_message(Message("clear-route")); + + current_row = depart; + for(list::const_iterator i=depart; i!=rows.end(); ++i) + { + if(i->type==ARRIVE) + { + train.ai_message(Message("set-destination-zone", i->zone)); + break; + } + else if(i->type==DEPART) + { + const Clock &clock = train.get_layout().get_clock(); + Time::TimeDelta dt = i->time-clock.get_current_time(); + while(dttype==ARRIVE) + { + current_row->time = train.get_layout().get_clock().get_current_time(); + unsigned i = distance(rows.begin(), current_row); + signal_row_modified.emit(i, *current_row); + } + update_pending = true; + } +} + + +Timetable::Row::Row(): + type(ARRIVE), + zone(0) +{ } + +void Timetable::Row::save(list &st) const +{ + st.push_back((DataFile::Statement("type"), type)); + st.push_back((DataFile::Statement("time"), time.raw())); + st.push_back((DataFile::Statement("zone"), zone->get_group(), zone->get_number())); +} + + +Timetable::Loader::Loader(Timetable &t, Layout &l): + DataFile::ObjectLoader(t), + layout(l) +{ + add("row", &Loader::row); +} + +void Timetable::Loader::row() +{ + Row r; + load_sub(r, layout); + obj.rows.push_back(r); + obj.update_pending = true; +} + + +Timetable::Row::Loader::Loader(Row &r, Layout &l): + DataFile::ObjectLoader(r), + layout(l) +{ + add("zone", &Loader::zone); + add("time", &Loader::time); + add("type", &Row::type); +} + +void Timetable::Row::Loader::zone(const string &name, unsigned number) +{ + obj.zone = &layout.get_zone(name, number); +} + +void Timetable::Row::Loader::time(Time::RawTime t) +{ + obj.time = Time::TimeDelta(t); +} + + +void operator<<(LexicalConverter &conv, Timetable::RowType rt) +{ + switch(rt) + { + case Timetable::ARRIVE: conv.result("ARRIVE"); return; + case Timetable::DEPART: conv.result("DEPART"); return; + default: throw lexical_error(format("conversion of RowType(%d) to string", rt)); + } +} + +void operator>>(const LexicalConverter &conv, Timetable::RowType &rt) +{ + if(conv.get()=="ARRIVE") + rt = Timetable::ARRIVE; + else if(conv.get()=="DEPART") + rt = Timetable::DEPART; + else + throw lexical_error(format("conversion of '%s' to RowType", conv.get())); +} + +} // namespace R2C2 diff --git a/source/libr2c2/timetable.h b/source/libr2c2/timetable.h new file mode 100644 index 0000000..b67e5fb --- /dev/null +++ b/source/libr2c2/timetable.h @@ -0,0 +1,89 @@ +#ifndef LIBR2C2_TIMETABLE_H_ +#define LIBR2C2_TIMETABLE_H_ + +#include +#include "trainai.h" + +namespace R2C2 { + +class Layout; +class Zone; + +class Timetable: public TrainAI +{ +public: + class Loader: public Msp::DataFile::ObjectLoader + { + private: + Layout &layout; + + public: + Loader(Timetable &, Layout &); + + private: + void row(); + }; + + enum RowType + { + ARRIVE = 1, + DEPART + }; + + struct Row + { + class Loader: public Msp::DataFile::ObjectLoader + { + private: + Layout &layout; + + public: + Loader(Row &, Layout &); + + private: + void zone(const std::string &, unsigned); + void time(Msp::Time::RawTime); + }; + + RowType type; + Zone *zone; + Msp::Time::TimeDelta time; + + Row(); + + void save(std::list &) const; + }; + + sigc::signal signal_row_added; + sigc::signal signal_row_modified; + sigc::signal signal_row_removed; + +private: + std::list rows; + std::list::iterator current_row; + bool update_pending; + +public: + Timetable(Train &); + + void append_row(const Row &); + void insert_row(unsigned, const Row &); + void modify_row(unsigned, const Row &); + void remove_row(unsigned); + + unsigned get_length() const { return rows.size(); } + const Row &get_row(unsigned) const; + + virtual void tick(const Msp::Time::TimeDelta &); + + void save(std::list &) const; + +private: + void check_update(std::list::const_iterator); + void update_route(); + void event(TrainAI &, const Message &); +}; + +} // namespace R2C2 + +#endif diff --git a/source/libr2c2/train.cpp b/source/libr2c2/train.cpp index 845232f..34b80dd 100644 --- a/source/libr2c2/train.cpp +++ b/source/libr2c2/train.cpp @@ -14,6 +14,7 @@ #include "route.h" #include "simplecontroller.h" #include "speedquantizer.h" +#include "timetable.h" #include "trackcircuit.h" #include "trackiter.h" #include "tracktype.h" @@ -408,6 +409,12 @@ void Train::save(list &st) const router->save(ss.sub); st.push_back(ss); } + else if(Timetable *timetable = dynamic_cast(*i)) + { + DataFile::Statement ss("timetable"); + timetable->save(ss.sub); + st.push_back(ss); + } } } @@ -577,6 +584,7 @@ Train::Loader::Loader(Train &t): add("name", &Loader::name); add("quantized_speed", &Loader::quantized_speed); add("router", &Loader::router); + add("timetable", &Loader::timetable); add("vehicle", &Loader::vehicle); } @@ -612,6 +620,12 @@ void Train::Loader::router() load_sub(*rtr); } +void Train::Loader::timetable() +{ + Timetable *ttbl = new Timetable(obj); + load_sub(*ttbl, obj.layout); +} + void Train::Loader::vehicle(ArticleNumber art_nr) { const VehicleType &vtype = obj.layout.get_catalogue().get(art_nr); diff --git a/source/libr2c2/train.h b/source/libr2c2/train.h index 9603891..25392bd 100644 --- a/source/libr2c2/train.h +++ b/source/libr2c2/train.h @@ -35,6 +35,7 @@ public: void name(const std::string &); void quantized_speed(); void router(); + void timetable(); void vehicle(ArticleNumber); }; -- 2.45.2