]> git.tdb.fi Git - r2c2.git/commitdiff
New timetable system, which works like an actual timetable
authorMikko Rasa <tdb@tdb.fi>
Thu, 20 Mar 2014 22:33:13 +0000 (00:33 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sat, 22 Mar 2014 14:20:50 +0000 (16:20 +0200)
data/timetablepanel.ui [new file with mode: 0644]
data/traindialog.ui
source/engineer/timetablepanel.cpp [new file with mode: 0644]
source/engineer/timetablepanel.h [new file with mode: 0644]
source/engineer/traindialog.cpp
source/libr2c2/timetable.cpp [new file with mode: 0644]
source/libr2c2/timetable.h [new file with mode: 0644]
source/libr2c2/train.cpp
source/libr2c2/train.h

diff --git a/data/timetablepanel.ui b/data/timetablepanel.ui
new file mode 100644 (file)
index 0000000..3434315
--- /dev/null
@@ -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";
+               };
+       };
+};
index 0b9e70c2d634fc81dfa8cda54a867bbf29068705..b57dc9ce3dca966b6205fb1f2414de97ab112b01 100644 (file)
@@ -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 (file)
index 0000000..6104873
--- /dev/null
@@ -0,0 +1,222 @@
+#include <msp/core/maputils.h>
+#include <msp/gltk/button.h>
+#include <msp/gltk/root.h>
+#include <msp/strings/format.h>
+#include <msp/strings/regex.h>
+#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<GLtk::List *>(get_item(widgets, "lst_timetable"));
+       lst_timetable->set_data(rows);
+       lst_timetable->set_item_type<TimetableRowItem>();
+       lst_timetable->signal_item_selected.connect(sigc::mem_fun(this, &TimetablePanel::row_selected));
+
+       drp_type = dynamic_cast<GLtk::Dropdown *>(get_item(widgets, "drp_type"));
+       lbl_zone = dynamic_cast<GLtk::Label *>(get_item(widgets, "lbl_zone"));
+       ent_time = dynamic_cast<GLtk::Entry *>(get_item(widgets, "ent_time"));
+
+       dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_pick"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::pick_clicked));
+       dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_insert"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::insert_clicked));
+       dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_delete"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::delete_clicked));
+       dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_apply"))->signal_clicked.connect(sigc::mem_fun(this, &TimetablePanel::apply_clicked));
+
+       timetable = train.get_ai_of_type<Timetable>();
+       if(!timetable)
+               timetable = new Timetable(train);
+
+       unsigned length = timetable->get_length();
+       for(unsigned i=0; i<length; ++i)
+               rows.append(&timetable->get_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<Timetable::RowType>(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<unsigned>(m[1].str)*Time::hour;
+               row.time += lexical_cast<unsigned>(m[2].str)*Time::min;
+               if(m[3])
+                       row.time += lexical_cast<unsigned>(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<GLtk::Root>());
+               Ray ray = engineer.get_main_view().create_ray(rx, ry);
+               Track *track = engineer.get_layout().pick<Track>(ray);
+               if(track)
+               {
+                       const set<Zone *> &zones = engineer.get_layout().get_all<Zone>();
+                       Zone *track_zone = 0;
+                       for(set<Zone *>::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 (file)
index 0000000..a0f5be8
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef TIMETABLEPANEL_H_
+#define TIMETABLEPANEL_H_
+
+#include <msp/gltk/dropdown.h>
+#include <msp/gltk/entry.h>
+#include <msp/gltk/label.h>
+#include <msp/gltk/list.h>
+#include <msp/gltk/panel.h>
+#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<const R2C2::Timetable::Row *> 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
index 54dac6a84af53c2e69b9ab5896351d32d73eafe0..f202377f0cddea3dba3cb0cdfb31fa576eba6462 100644 (file)
@@ -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<GLtk::Toggle *>(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<GLtk::Toggle *>(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<AIControl>();
        if(!control)
                control = new AIControl(train);
diff --git a/source/libr2c2/timetable.cpp b/source/libr2c2/timetable.cpp
new file mode 100644 (file)
index 0000000..f0e49ff
--- /dev/null
@@ -0,0 +1,252 @@
+#include <msp/strings/format.h>
+#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<Row>::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<Row>::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<Row>::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<Row>::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(t<current_row->time)
+                       t += Time::day;
+
+               Time::TimeDelta b = t-dt*clock.get_rate();
+               if(b<current_row->time)
+               {
+                       train.ai_message(Message("set-target-speed", train.get_maximum_speed()));
+                       ++current_row;
+               }
+       }
+}
+
+void Timetable::save(list<DataFile::Statement> &st) const
+{
+       for(list<Row>::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<Row>::const_iterator i)
+{
+       for(list<Row>::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<Row>::iterator depart = rows.end();
+       for(list<Row>::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<Row>::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(dt<Time::zero)
+                               dt += Time::day;
+                       dt /= clock.get_rate();
+                       train.ai_message(Message("set-departure-delay", dt));
+               }
+       }
+}
+
+void Timetable::event(TrainAI &, const Message &msg)
+{
+       if(msg.type=="arrived")
+       {
+               if(current_row->type==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<DataFile::Statement> &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<Timetable>(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<Timetable::Row>(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 (file)
index 0000000..b67e5fb
--- /dev/null
@@ -0,0 +1,89 @@
+#ifndef LIBR2C2_TIMETABLE_H_
+#define LIBR2C2_TIMETABLE_H_
+
+#include <msp/datafile/objectloader.h>
+#include "trainai.h"
+
+namespace R2C2 {
+
+class Layout;
+class Zone;
+
+class Timetable: public TrainAI
+{
+public:
+       class Loader: public Msp::DataFile::ObjectLoader<Timetable>
+       {
+       private:
+               Layout &layout;
+
+       public:
+               Loader(Timetable &, Layout &);
+
+       private:
+               void row();
+       };
+
+       enum RowType
+       {
+               ARRIVE = 1,
+               DEPART
+       };
+
+       struct Row
+       {
+               class Loader: public Msp::DataFile::ObjectLoader<Row>
+               {
+               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<Msp::DataFile::Statement> &) const;
+       };
+
+       sigc::signal<void, unsigned, const Row &> signal_row_added;
+       sigc::signal<void, unsigned, const Row &> signal_row_modified;
+       sigc::signal<void, unsigned> signal_row_removed;
+
+private:
+       std::list<Row> rows;
+       std::list<Row>::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<Msp::DataFile::Statement> &) const;
+
+private:
+       void check_update(std::list<Row>::const_iterator);
+       void update_route();
+       void event(TrainAI &, const Message &);
+};
+
+} // namespace R2C2
+
+#endif
index 845232fa4d9cf442fc5dddc692c5873291c35302..34b80ddcc39d213635431e50613ecdce29776d51 100644 (file)
@@ -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<DataFile::Statement> &st) const
                        router->save(ss.sub);
                        st.push_back(ss);
                }
+               else if(Timetable *timetable = dynamic_cast<Timetable *>(*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<VehicleType>(art_nr);
index 9603891e8f6e4d0672df77c21546c0b033e9bf90..25392bd768db8f06493fe414fb6f9e038068cec1 100644 (file)
@@ -35,6 +35,7 @@ public:
                void name(const std::string &);
                void quantized_speed();
                void router();
+               void timetable();
                void vehicle(ArticleNumber);
        };