+#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"));
+ }
+}