From: Mikko Rasa Date: Tue, 14 Apr 2015 23:37:43 +0000 (+0300) Subject: Add a new remote control program with GLtk-based UI X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=f8873062b146028c07f55ad625d2767e45133c27;p=r2c2.git Add a new remote control program with GLtk-based UI --- diff --git a/Build b/Build index 8f0fb18..60211ba 100644 --- a/Build +++ b/Build @@ -56,6 +56,17 @@ package "r2c2" 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"; diff --git a/data/remote/connectdialog.ui b/data/remote/connectdialog.ui new file mode 100644 index 0000000..3cf45fc --- /dev/null +++ b/data/remote/connectdialog.ui @@ -0,0 +1,44 @@ +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"; + }; + }; +}; diff --git a/data/remote/statusbar.ui b/data/remote/statusbar.ui new file mode 100644 index 0000000..8efe2d0 --- /dev/null +++ b/data/remote/statusbar.ui @@ -0,0 +1,45 @@ +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; +}; diff --git a/data/remote/trainpanel.ui b/data/remote/trainpanel.ui new file mode 100644 index 0000000..7b03c96 --- /dev/null +++ b/data/remote/trainpanel.ui @@ -0,0 +1,59 @@ +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; +}; diff --git a/data/remote/trainselector.ui b/data/remote/trainselector.ui new file mode 100644 index 0000000..9261de3 --- /dev/null +++ b/data/remote/trainselector.ui @@ -0,0 +1,11 @@ +layout +{ + margin + { + horizontal 6; + vertical 8; + }; +}; + +dropdown "drp_trains"; +expand true false; diff --git a/source/network/client.h b/source/network/client.h index 9f142df..94fa785 100644 --- a/source/network/client.h +++ b/source/network/client.h @@ -54,6 +54,8 @@ public: const std::list &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 &get_trains() const { return trains; } diff --git a/source/network/train.h b/source/network/train.h index 5c938dc..c908bee 100644 --- a/source/network/train.h +++ b/source/network/train.h @@ -35,6 +35,7 @@ private: 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; } @@ -48,6 +49,7 @@ public: 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 &); diff --git a/source/remote/connectdialog.cpp b/source/remote/connectdialog.cpp new file mode 100644 index 0000000..40a8bb3 --- /dev/null +++ b/source/remote/connectdialog.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#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(get_item(widgets, "ent_host")); + ent_port = dynamic_cast(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; +} diff --git a/source/remote/connectdialog.h b/source/remote/connectdialog.h new file mode 100644 index 0000000..68ae64b --- /dev/null +++ b/source/remote/connectdialog.h @@ -0,0 +1,22 @@ +#ifndef CONNECTDIALOG_H_ +#define CONNECTDIALOG_H_ + +#include +#include +#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 diff --git a/source/remote/remote.cpp b/source/remote/remote.cpp new file mode 100644 index 0000000..f6e8692 --- /dev/null +++ b/source/remote/remote.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#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); + } +} diff --git a/source/remote/remote.h b/source/remote/remote.h new file mode 100644 index 0000000..bff56ed --- /dev/null +++ b/source/remote/remote.h @@ -0,0 +1,39 @@ +#ifndef REMOTE_H_ +#define REMOTE_H_ + +#include +#include +#include +#include +#include +#include +#include "network/client.h" +#include "statusbar.h" +#include "trainpanel.h" +#include "trainselector.h" + +class Remote: public Msp::RegisteredApplication +{ +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 diff --git a/source/remote/statusbar.cpp b/source/remote/statusbar.cpp new file mode 100644 index 0000000..5feda64 --- /dev/null +++ b/source/remote/statusbar.cpp @@ -0,0 +1,52 @@ +#include +#include +#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(get_item(widgets, "btn_power_on")); + btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&client, &Client::set_power), true)); + btn = dynamic_cast(get_item(widgets, "btn_power_off")); + btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&client, &Client::set_power), false)); + btn = dynamic_cast(get_item(widgets, "btn_halt")); + btn->signal_clicked.connect(sigc::mem_fun(this, &StatusBar::ui_halt_clicked)); + + ind_power_on = dynamic_cast(get_item(widgets, "ind_power_on")); + ind_power_off = dynamic_cast(get_item(widgets, "ind_power_off")); + ind_halt = dynamic_cast(get_item(widgets, "ind_halt")); + + lbl_status = dynamic_cast(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); +} diff --git a/source/remote/statusbar.h b/source/remote/statusbar.h new file mode 100644 index 0000000..296b019 --- /dev/null +++ b/source/remote/statusbar.h @@ -0,0 +1,30 @@ +#ifndef STATUSBAR_H_ +#define STATUSBAR_H_ + +#include +#include +#include +#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 diff --git a/source/remote/trainpanel.cpp b/source/remote/trainpanel.cpp new file mode 100644 index 0000000..58dcd67 --- /dev/null +++ b/source/remote/trainpanel.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include +#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(get_item(widgets, "btn_forward")); + btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&train, &NetTrain::set_reverse), false)); + btn = dynamic_cast(get_item(widgets, "btn_reverse")); + btn->signal_clicked.connect(sigc::bind(sigc::mem_fun(&train, &NetTrain::set_reverse), true)); + + ind_forward = dynamic_cast(get_item(widgets, "ind_forward")); + ind_reverse = dynamic_cast(get_item(widgets, "ind_reverse")); + + sld_speed = dynamic_cast(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(get_item(widgets, "lbl_speed")); + + lbl_status = dynamic_cast(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(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::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); +} diff --git a/source/remote/trainpanel.h b/source/remote/trainpanel.h new file mode 100644 index 0000000..02b0b06 --- /dev/null +++ b/source/remote/trainpanel.h @@ -0,0 +1,34 @@ +#ifndef TRAINPANEL_H_ +#define TRAINPANEL_H_ + +#include +#include +#include +#include +#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 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 diff --git a/source/remote/trainselector.cpp b/source/remote/trainselector.cpp new file mode 100644 index 0000000..ca4ef0a --- /dev/null +++ b/source/remote/trainselector.cpp @@ -0,0 +1,44 @@ +#include +#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(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 &ctrains = client.get_trains(); + for(map::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)); +} diff --git a/source/remote/trainselector.h b/source/remote/trainselector.h new file mode 100644 index 0000000..00f46dd --- /dev/null +++ b/source/remote/trainselector.h @@ -0,0 +1,26 @@ +#ifndef TRAINSELECTOR_H_ +#define TRAINSELECTOR_H_ + +#include +#include +#include +#include "network/train.h" + +class TrainSelector: public Msp::GLtk::Panel +{ +public: + sigc::signal signal_train_selected; + +private: + Msp::GLtk::Dropdown *drp_trains; + Msp::GLtk::FunctionListData trains; + +public: + TrainSelector(R2C2::Client &); + +private: + void train_added(R2C2::NetTrain &); + void train_selected(unsigned); +}; + +#endif