use "r2c2_net";
};
+ program "remote"
+ {
+ source "source/remote";
+ require "mspgui";
+ require "mspgl";
+ require "mspgltk";
+ require "mspnet";
+ use "r2c2";
+ use "r2c2_net";
+ };
+
program "serial"
{
source "source/serial";
--- /dev/null
+layout
+{
+ margin
+ {
+ top 2;
+ horizontal 6;
+ bottom 8;
+ };
+};
+
+column
+{
+ label ""
+ {
+ text "Connect to server";
+ };
+
+ grid 2
+ {
+ label ""
+ {
+ text "Host";
+ };
+
+ entry "ent_host";
+
+ label ""
+ {
+ text "Port";
+ };
+
+ entry "ent_port";
+ };
+
+ row
+ {
+ split;
+
+ action_button "" 0
+ {
+ text "Connect";
+ };
+ };
+};
--- /dev/null
+layout
+{
+ margin
+ {
+ horizontal 6;
+ vertical 8;
+ };
+};
+
+row
+{
+ grid 3
+ {
+ indicator "ind_power_on"
+ {
+ style "green";
+ };
+ indicator "ind_power_off"
+ {
+ style "red";
+ };
+ indicator "ind_halt";
+ button "btn_power_on"
+ {
+ text "On";
+ style "green";
+ };
+ button "btn_power_off"
+ {
+ text "Off";
+ style "red";
+ };
+ constraint COPY_WIDTH "btn_power_on";
+ button "btn_halt"
+ {
+ text "Halt";
+ };
+ constraint COPY_WIDTH "btn_power_on";
+ };
+ label "lbl_status"
+ {
+ style "digital";
+ };
+ expand;
+};
--- /dev/null
+layout
+{
+ margin
+ {
+ horizontal 6;
+ vertical 8;
+ };
+};
+
+column
+{
+ row
+ {
+ panel "pnl_functions"
+ {
+ style "group";
+ layout
+ {
+ margin
+ {
+ horizontal 0;
+ vertical 0;
+ };
+ };
+ };
+ column
+ {
+ label "lbl_speed"
+ {
+ style "digital";
+ };
+ expand true false;
+
+ vslider "sld_speed";
+ constraint COPY_WIDTH "pnl_functions";
+ expand;
+ };
+ column
+ {
+ indicator "ind_forward";
+ button "btn_forward"
+ {
+ style "arrow_up";
+ };
+ constraint COPY_WIDTH "sld_speed";
+ button "btn_reverse"
+ {
+ style "arrow_down";
+ };
+ constraint COPY_HEIGHT "btn_forward";
+ indicator "ind_reverse";
+ };
+ };
+ label "lbl_status"
+ {
+ style "digital";
+ };
+ expand true false;
+};
--- /dev/null
+layout
+{
+ margin
+ {
+ horizontal 6;
+ vertical 8;
+ };
+};
+
+dropdown "drp_trains";
+expand true false;
const std::list<std::string> &get_routes() const { return routes; }
void set_power(bool);
void set_halt(bool);
+ bool get_power() const { return power; }
+ bool get_halt() const { return halt; }
NetTrain &get_train(unsigned) const;
const std::map<unsigned, NetTrain *> &get_trains() const { return trains; }
public:
NetTrain(Client &, const TrainInfoPacket &);
+ Client &get_client() const { return client; }
const VehicleType &get_loco_type() const { return loco_type; }
unsigned get_address() const { return address; }
const std::string &get_name() const { return name; }
bool get_function(unsigned i) const { return (functions>>i)&1; }
void set_route(const std::string &);
const std::string &get_route() const { return route; }
+ const std::string &get_status() const { return status; }
void process_packet(const TrainControlPacket &);
void process_packet(const TrainFunctionPacket &);
--- /dev/null
+#include <msp/core/maputils.h>
+#include <msp/net/inet.h>
+#include <msp/net/resolve.h>
+#include "connectdialog.h"
+
+using namespace Msp;
+using namespace R2C2;
+
+ConnectDialog::ConnectDialog(Client &c):
+ client(c)
+{
+ Loader::WidgetMap widgets;
+ DataFile::load(*this, "data/remote/connectdialog.ui", widgets);
+
+ ent_host = dynamic_cast<GLtk::Entry *>(get_item(widgets, "ent_host"));
+ ent_port = dynamic_cast<GLtk::Entry *>(get_item(widgets, "ent_port"));
+ ent_port->set_text("8315");
+}
+
+void ConnectDialog::on_response(int)
+{
+ Net::SockAddr *addr = Net::resolve(ent_host->get_text(), ent_port->get_text(), Net::INET);
+ client.connect(*addr);
+ delete addr;
+}
--- /dev/null
+#ifndef CONNECTDIALOG_H_
+#define CONNECTDIALOG_H_
+
+#include <msp/gltk/dialog.h>
+#include <msp/gltk/entry.h>
+#include "network/client.h"
+
+class ConnectDialog: public Msp::GLtk::Dialog
+{
+private:
+ R2C2::Client &client;
+ Msp::GLtk::Entry *ent_host;
+ Msp::GLtk::Entry *ent_port;
+
+public:
+ ConnectDialog(R2C2::Client &);
+
+private:
+ virtual void on_response(int);
+};
+
+#endif
--- /dev/null
+#include <msp/gl/framebuffer.h>
+#include <msp/gltk/layout.h>
+#include <msp/time/utils.h>
+#include "connectdialog.h"
+#include "remote.h"
+
+using namespace std;
+using namespace Msp;
+using namespace R2C2;
+
+Remote::Remote(int, char **):
+ window(1024, 768),
+ client(catalogue),
+ ui_resources("data/r2c2.res"),
+ root(ui_resources, window),
+ root_layout(new GLtk::Layout),
+ status_bar(client)
+{
+ window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &Remote::exit), 0));
+
+ root.set_size(window.get_width()/2, window.get_height()/2);
+ root.set_layout(root_layout);
+ root_layout->set_margin(GLtk::Sides());
+ root_layout->set_spacing(0);
+
+ for(unsigned i=0; i<2; ++i)
+ {
+ selectors[i] = new TrainSelector(client);
+ root.add(*selectors[i]);
+ root_layout->set_gravity(*selectors[i], i*2-1, 1);
+ root_layout->set_expand(*selectors[i], true, false);
+ selectors[i]->signal_train_selected.connect(sigc::bind(sigc::mem_fun(this, &Remote::train_selected), i));
+
+ if(i>0)
+ {
+ root_layout->add_constraint(*selectors[i], GLtk::Layout::RIGHT_OF, *selectors[i-1]);
+ root_layout->add_constraint(*selectors[i], GLtk::Layout::COPY_WIDTH, *selectors[i-1]);
+ }
+
+ panels[i] = 0;
+ }
+
+ root.add(status_bar);
+ root_layout->set_gravity(status_bar, -1, -1);
+ root_layout->set_expand(status_bar, true, false);
+
+ catalogue.add_source("data/Märklin/H0");
+
+ client.use_event_dispatcher(ev_disp);
+}
+
+int Remote::main()
+{
+ ConnectDialog *dlg = new ConnectDialog(client);
+ root.add(*dlg);
+ root_layout->set_gravity(*dlg, 0, 0);
+
+ window.show();
+
+ return Application::main();
+}
+
+void Remote::tick()
+{
+ for(unsigned i=0;; ++i)
+ {
+ Time::TimeStamp t = Time::now();
+ if(i>0 && t>=next_frame)
+ {
+ next_frame = t+Time::sec/30;
+ break;
+ }
+ ev_disp.tick(max(next_frame-t, Time::zero));
+ }
+
+ window.tick();
+
+ GL::Framebuffer::system().clear(GL::COLOR_BUFFER_BIT);
+ root.render();
+ window.swap_buffers();
+}
+
+void Remote::train_selected(NetTrain *train, unsigned index)
+{
+ delete panels[index];
+ panels[index] = 0;
+
+ if(train)
+ {
+ panels[index] = new TrainPanel(*train);
+ root.add(*panels[index]);
+ root_layout->set_expand(*panels[index], true, true);
+ root_layout->add_constraint(*panels[index], GLtk::Layout::BELOW, *selectors[index]);
+ root_layout->add_constraint(*panels[index], GLtk::Layout::ALIGN_LEFT, *selectors[index]);
+ root_layout->add_constraint(*panels[index], GLtk::Layout::ALIGN_RIGHT, *selectors[index]);
+ root_layout->add_constraint(*panels[index], GLtk::Layout::ABOVE, status_bar);
+ }
+}
--- /dev/null
+#ifndef REMOTE_H_
+#define REMOTE_H_
+
+#include <msp/core/application.h>
+#include <msp/gltk/resources.h>
+#include <msp/gltk/root.h>
+#include <msp/graphics/simplewindow.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/time/timestamp.h>
+#include "network/client.h"
+#include "statusbar.h"
+#include "trainpanel.h"
+#include "trainselector.h"
+
+class Remote: public Msp::RegisteredApplication<Remote>
+{
+private:
+ Msp::Graphics::SimpleGLWindow window;
+ R2C2::Catalogue catalogue;
+ R2C2::Client client;
+ Msp::IO::EventDispatcher ev_disp;
+ Msp::GLtk::Resources ui_resources;
+ Msp::GLtk::Root root;
+ Msp::GLtk::Layout *root_layout;
+ TrainSelector *selectors[2];
+ TrainPanel *panels[2];
+ StatusBar status_bar;
+ Msp::Time::TimeStamp next_frame;
+
+public:
+ Remote(int, char **);
+
+ virtual int main();
+private:
+ virtual void tick();
+ void train_selected(R2C2::NetTrain *, unsigned);
+};
+
+#endif
--- /dev/null
+#include <msp/core/maputils.h>
+#include <msp/gltk/button.h>
+#include "statusbar.h"
+
+using namespace std;
+using namespace Msp;
+using namespace R2C2;
+
+StatusBar::StatusBar(Client &c):
+ client(c)
+{
+ Loader::WidgetMap widgets;
+ DataFile::load(*this, "data/remote/statusbar.ui", widgets);
+
+ Msp::GLtk::Button *btn = dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_power_on"));
+ btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&client, &Client::set_power), true));
+ btn = dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_power_off"));
+ btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&client, &Client::set_power), false));
+ btn = dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_halt"));
+ btn->signal_clicked.connect(sigc::mem_fun(this, &StatusBar::ui_halt_clicked));
+
+ ind_power_on = dynamic_cast<GLtk::Indicator *>(get_item(widgets, "ind_power_on"));
+ ind_power_off = dynamic_cast<GLtk::Indicator *>(get_item(widgets, "ind_power_off"));
+ ind_halt = dynamic_cast<GLtk::Indicator *>(get_item(widgets, "ind_halt"));
+
+ lbl_status = dynamic_cast<GLtk::Label *>(get_item(widgets, "lbl_status"));
+
+ client.signal_power_changed.connect(sigc::mem_fun(this, &StatusBar::power_changed));
+ client.signal_halt_changed.connect(sigc::mem_fun(this, &StatusBar::halt_changed));
+ client.signal_emergency.connect(sigc::mem_fun(this, &StatusBar::emergency));
+}
+
+void StatusBar::ui_halt_clicked()
+{
+ //client.set_halt(!client.get_halt());
+}
+
+void StatusBar::power_changed(bool p)
+{
+ ind_power_on->set_active(p);
+ ind_power_off->set_active(!p);
+}
+
+void StatusBar::halt_changed(bool h)
+{
+ ind_halt->set_active(h);
+}
+
+void StatusBar::emergency(const string &e)
+{
+ lbl_status->set_text(e);
+}
--- /dev/null
+#ifndef STATUSBAR_H_
+#define STATUSBAR_H_
+
+#include <msp/gltk/indicator.h>
+#include <msp/gltk/label.h>
+#include <msp/gltk/panel.h>
+#include "network/client.h"
+
+class StatusBar: public Msp::GLtk::Panel
+{
+private:
+ R2C2::Client &client;
+ Msp::GLtk::Indicator *ind_power_on;
+ Msp::GLtk::Indicator *ind_power_off;
+ Msp::GLtk::Indicator *ind_halt;
+ Msp::GLtk::Label *lbl_status;
+
+public:
+ StatusBar(R2C2::Client &);
+
+private:
+ void ui_on_clicked();
+ void ui_off_clicked();
+ void ui_halt_clicked();
+ void power_changed(bool);
+ void halt_changed(bool);
+ void emergency(const std::string &);
+};
+
+#endif
--- /dev/null
+#include <msp/core/maputils.h>
+#include <msp/core/raii.h>
+#include <msp/gltk/button.h>
+#include <msp/gltk/column.h>
+#include <msp/gltk/label.h>
+#include <msp/gltk/toggle.h>
+#include <msp/strings/format.h>
+#include "network/client.h"
+#include "trainpanel.h"
+
+using namespace std;
+using namespace Msp;
+using namespace R2C2;
+
+TrainPanel::TrainPanel(NetTrain &t):
+ train(t),
+ updating(false)
+{
+ Loader::WidgetMap widgets;
+ DataFile::load(*this, "data/remote/trainpanel.ui", widgets);
+
+ Msp::GLtk::Button *btn = dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_forward"));
+ btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&train, &NetTrain::set_reverse), false));
+ btn = dynamic_cast<GLtk::Button *>(get_item(widgets, "btn_reverse"));
+ btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&train, &NetTrain::set_reverse), true));
+
+ ind_forward = dynamic_cast<GLtk::Indicator *>(get_item(widgets, "ind_forward"));
+ ind_reverse = dynamic_cast<GLtk::Indicator *>(get_item(widgets, "ind_reverse"));
+
+ sld_speed = dynamic_cast<GLtk::Slider *>(get_item(widgets, "sld_speed"));
+ sld_speed->set_range(0, train.get_loco_type().get_maximum_speed()/train.get_client().get_catalogue().get_scale()*3.6);
+ sld_speed->signal_value_changed.connect(sigc::mem_fun(this, &TrainPanel::ui_speed_changed));
+
+ lbl_speed = dynamic_cast<GLtk::Label *>(get_item(widgets, "lbl_speed"));
+
+ lbl_status = dynamic_cast<GLtk::Label *>(get_item(widgets, "lbl_status"));
+ train.signal_status_changed.connect(sigc::mem_fun(this, &TrainPanel::status_changed));
+ lbl_status->set_text(train.get_status());
+
+ GLtk::Panel *pnl_functions = dynamic_cast<GLtk::Panel *>(get_item(widgets, "pnl_functions"));
+ const VehicleType::FunctionMap &functions = train.get_loco_type().get_functions();
+ GLtk::Column column(*pnl_functions->get_layout());
+ for(VehicleType::FunctionMap::const_iterator i=functions.begin(); i!=functions.end(); ++i)
+ {
+ GLtk::Toggle *tgl = new GLtk::Toggle(i->second);
+ pnl_functions->add(*tgl);
+ tgl->set_value(train.get_function(i->first));
+ tgl->signal_toggled.connect(sigc::bind(sigc::mem_fun(this, &TrainPanel::ui_function_toggled), i->first));
+ tgl_functions[i->first] = tgl;
+ }
+
+ train.signal_target_speed_changed.connect(sigc::mem_fun(this, &TrainPanel::update_speed));
+ update_speed(train.get_target_speed());
+ train.signal_reverse_changed.connect(sigc::mem_fun(this, &TrainPanel::update_reverse));
+ update_reverse(train.get_reverse());
+}
+
+void TrainPanel::update_reverse(bool r)
+{
+ ind_forward->set_active(!r);
+ ind_reverse->set_active(r);
+}
+
+void TrainPanel::update_speed(float s)
+{
+ SetFlag setf(updating);
+ float scale_speed = s/train.get_client().get_catalogue().get_scale()*3.6;
+ sld_speed->set_value(scale_speed);
+ lbl_speed->set_text(format("%3.0f", scale_speed));
+}
+
+void TrainPanel::ui_speed_changed(float s)
+{
+ if(!updating)
+ {
+ float real_speed = s*train.get_client().get_catalogue().get_scale()/3.6;
+ train.set_target_speed(real_speed);
+ }
+}
+
+void TrainPanel::function_changed(unsigned f, bool a)
+{
+ SetFlag setf(updating);
+ map<unsigned, GLtk::Toggle *>::iterator i = tgl_functions.find(f);
+ if(i!=tgl_functions.end())
+ get_item(tgl_functions, f)->set_value(a);
+}
+
+void TrainPanel::ui_function_toggled(bool a, unsigned f)
+{
+ if(!updating)
+ train.set_function(f, a);
+}
+
+void TrainPanel::status_changed(const string &s)
+{
+ lbl_status->set_text(s);
+}
--- /dev/null
+#ifndef TRAINPANEL_H_
+#define TRAINPANEL_H_
+
+#include <msp/gltk/indicator.h>
+#include <msp/gltk/panel.h>
+#include <msp/gltk/slider.h>
+#include <msp/gltk/toggle.h>
+#include "network/train.h"
+
+class TrainPanel: public Msp::GLtk::Panel, public sigc::trackable
+{
+private:
+ R2C2::NetTrain &train;
+ Msp::GLtk::Indicator *ind_forward;
+ Msp::GLtk::Indicator *ind_reverse;
+ Msp::GLtk::Slider *sld_speed;
+ Msp::GLtk::Label *lbl_speed;
+ Msp::GLtk::Label *lbl_status;
+ std::map<unsigned, Msp::GLtk::Toggle *> tgl_functions;
+ bool updating;
+
+public:
+ TrainPanel(R2C2::NetTrain &);
+
+private:
+ void update_reverse(bool);
+ void update_speed(float);
+ void ui_speed_changed(float);
+ void function_changed(unsigned, bool);
+ void ui_function_toggled(bool, unsigned);
+ void status_changed(const std::string &);
+};
+
+#endif
--- /dev/null
+#include <msp/core/maputils.h>
+#include "network/client.h"
+#include "trainselector.h"
+
+using namespace std;
+using namespace Msp;
+using namespace R2C2;
+
+string train_name(NetTrain *const &train)
+{
+ if(train)
+ return train->get_name();
+ else
+ return "(no train selected)";
+}
+
+TrainSelector::TrainSelector(Client &client):
+ trains(&train_name)
+{
+ Loader::WidgetMap widgets;
+ DataFile::load(*this, "data/remote/trainselector.ui", widgets);
+
+ drp_trains = dynamic_cast<GLtk::Dropdown *>(get_item(widgets, "drp_trains"));
+ drp_trains->set_data(trains);
+ drp_trains->signal_item_selected.connect(sigc::mem_fun(this, &TrainSelector::train_selected));
+
+ client.signal_train_added.connect(sigc::mem_fun(this, &TrainSelector::train_added));
+
+ trains.append(0);
+ drp_trains->set_selected_index(0);
+ const map<unsigned, NetTrain *> &ctrains = client.get_trains();
+ for(map<unsigned, NetTrain *>::const_iterator i=ctrains.begin(); i!=ctrains.end(); ++i)
+ trains.append(i->second);
+}
+
+void TrainSelector::train_added(NetTrain &train)
+{
+ trains.append(&train);
+}
+
+void TrainSelector::train_selected(unsigned index)
+{
+ signal_train_selected.emit(trains.get(index));
+}
--- /dev/null
+#ifndef TRAINSELECTOR_H_
+#define TRAINSELECTOR_H_
+
+#include <msp/gltk/dropdown.h>
+#include <msp/gltk/listdata.h>
+#include <msp/gltk/panel.h>
+#include "network/train.h"
+
+class TrainSelector: public Msp::GLtk::Panel
+{
+public:
+ sigc::signal<void, R2C2::NetTrain *> signal_train_selected;
+
+private:
+ Msp::GLtk::Dropdown *drp_trains;
+ Msp::GLtk::FunctionListData<R2C2::NetTrain *> trains;
+
+public:
+ TrainSelector(R2C2::Client &);
+
+private:
+ void train_added(R2C2::NetTrain &);
+ void train_selected(unsigned);
+};
+
+#endif