--- /dev/null
+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";
+ };
+ };
+};
style "pointer_left";
exclusive true;
};
+
+ toggle "tgl_timetable"
+ {
+ text "Ttbl";
+ style "pointer_left";
+ exclusive true;
+ };
};
label "lbl_detail_placeholder"
--- /dev/null
+#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"));
+ }
+}
--- /dev/null
+#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
#include "libr2c2/trainstatus.h"
#include "controlpanel.h"
#include "routerpanel.h"
+#include "timetablepanel.h"
#include "traindialog.h"
#include "vehiclespanel.h"
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);
--- /dev/null
+#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
--- /dev/null
+#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
#include "route.h"
#include "simplecontroller.h"
#include "speedquantizer.h"
+#include "timetable.h"
#include "trackcircuit.h"
#include "trackiter.h"
#include "tracktype.h"
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);
+ }
}
}
add("name", &Loader::name);
add("quantized_speed", &Loader::quantized_speed);
add("router", &Loader::router);
+ add("timetable", &Loader::timetable);
add("vehicle", &Loader::vehicle);
}
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);
void name(const std::string &);
void quantized_speed();
void router();
+ void timetable();
void vehicle(ArticleNumber);
};