--- /dev/null
+#include "libr2c2/block.h"
+#include "allocation.h"
+#include "layout.h"
+#include "path.h"
+#include "track.h"
+
+using namespace Msp;
+
+namespace R2C2 {
+
+Allocation3D::Allocation3D(Layout3D &l, Train &t):
+ Utility3D(l),
+ train(t)
+{
+ layout.get_layout().signal_block_reserved.connect(sigc::mem_fun(this, &Allocation3D::block_reserved));
+ train.signal_advanced.connect(sigc::mem_fun(this, &Allocation3D::train_advanced));
+}
+
+void Allocation3D::set_color(const GL::Color &c)
+{
+ color = c;
+ for(PathMap::const_iterator i=paths.begin(); i!=paths.end(); ++i)
+ {
+ float intensity = 0.5+train.get_block_allocator().is_block_current(*i->first)*0.5;
+ for(PathList::const_iterator j=i->second.begin(); j!=i->second.end(); ++j)
+ (*j)->set_color(color*intensity);
+ }
+}
+
+void Allocation3D::block_reserved(Block &block, Train *t)
+{
+ if(t && t!=&train)
+ return;
+
+ if(t)
+ {
+ if(paths.count(&block))
+ return;
+
+ PathList &bpaths = paths[&block];
+ const Block::TrackSet &tracks = block.get_tracks();
+ for(Block::TrackSet::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+ {
+ Path3D *path = new Path3D(layout.get<Track3D>(**i));
+ bpaths.push_back(path);
+ path->set_layer(1);
+ float intensity = 0.5+train.get_block_allocator().is_block_current(block)*0.5;
+ path->set_color(color*intensity);
+ path->set_automatic();
+ }
+ }
+ else
+ {
+ PathMap::iterator i = paths.find(&block);
+ if(i==paths.end())
+ return;
+
+ for(PathList::iterator j=i->second.begin(); j!=i->second.end(); ++j)
+ delete *j;
+ paths.erase(i);
+ }
+}
+
+void Allocation3D::train_advanced(Block &block)
+{
+ PathMap::iterator i = paths.find(&block);
+ if(i==paths.end())
+ return;
+
+ for(PathList::iterator j=i->second.begin(); j!=i->second.end(); ++j)
+ (*j)->set_color(color);
+}
+
+} // namespace R2C2
--- /dev/null
+#ifndef R2C2_3D_ALLOCATION_H_
+#define R2C2_3D_ALLOCATION_H_
+
+#include <msp/gl/color.h>
+#include "libr2c2/train.h"
+#include "utility.h"
+
+namespace R2C2 {
+
+class Layout3D;
+class Path3D;
+
+class Allocation3D: public Utility3D, public sigc::trackable
+{
+private:
+ typedef std::list<Path3D *> PathList;
+ typedef std::map<Block *, PathList> PathMap;
+
+ Train &train;
+ Msp::GL::Color color;
+ PathMap paths;
+
+public:
+ Allocation3D(Layout3D &, Train &);
+
+ void set_color(const Msp::GL::Color &);
+
+private:
+ void block_reserved(Block &, Train *);
+ void train_advanced(Block &);
+};
+
+} // namespace R2C2
+
+#endif
#include "layout.h"
#include "signal.h"
#include "track.h"
+#include "utility.h"
#include "vehicle.h"
using namespace std;
Layout3D::~Layout3D()
{
+ while(!utilities.empty())
+ delete *utilities.begin();
while(!objects.empty())
delete objects.begin()->second;
}
objects.erase(&o.get_object());
}
+void Layout3D::add(Utility3D &u)
+{
+ utilities.insert(&u);
+}
+
+void Layout3D::remove(Utility3D &u)
+{
+ utilities.erase(&u);
+}
+
void Layout3D::object_added(Object &o)
{
if(Track *t = dynamic_cast<Track *>(&o))
namespace R2C2 {
class Object3D;
+class Utility3D;
class Layout3D: public sigc::trackable
{
Layout &layout;
Catalogue3D catalogue;
ObjectMap objects;
+ std::set<Utility3D *> utilities;
Msp::GL::InstanceScene scene;
Msp::GL::SimpleScene ep_scene;
Msp::GL::InstanceScene path_scene;
T &get(Object &o) const
{ return dynamic_cast<T &>(get(o)); }
+ void add(Utility3D &);
+ void remove(Utility3D &);
+
Msp::GL::Scene &get_scene() { return scene; }
Msp::GL::Scene &get_endpoint_scene() { return ep_scene; }
Msp::GL::Scene &get_path_scene() { return path_scene; }
namespace R2C2 {
Path3D::Path3D(const Track3D &t):
+ Utility3D(t.get_layout()),
track(t),
- paths(0),
+ path(t.get_track().get_active_path()),
+ side(0),
automatic(true),
+ mesh(0),
z_offs(0)
{
- track.get_layout().get_path_scene().add(*this);
+ update_mesh();
+
+ layout.get_path_scene().add(*this);
+ if(track.get_track().get_type().is_turnout())
+ track.get_track().signal_path_changed.connect(sigc::mem_fun(this, &Path3D::path_changed));
}
Path3D::~Path3D()
{
- track.get_layout().get_path_scene().remove(*this);
+ layout.get_path_scene().remove(*this);
}
void Path3D::set_automatic()
automatic = true;
}
-void Path3D::set_path(unsigned p)
+void Path3D::set_path(int p)
{
- if(!(track.get_track().get_type().get_paths()&(1<<p)))
+ if(p>=0 && !(track.get_track().get_type().get_paths()&(1<<p)))
throw invalid_argument("Path3D::set_path");
automatic = false;
- paths = 1<<p;
+ path = p;
+ update_mesh();
}
-void Path3D::set_mask(unsigned p)
+void Path3D::set_side(int s)
{
- if(p&~track.get_track().get_type().get_paths())
- throw invalid_argument("Path3D::set_mask");
- automatic = false;
- paths = p;
+ side = (s<0 ? -1 : s>0 ? 1 : 0);
+ update_mesh();
}
void Path3D::set_color(const GL::Color &c)
void Path3D::set_layer(float l)
{
- z_offs = l*track.get_track().get_layout().get_catalogue().get_gauge()*0.01;
+ z_offs = l*layout.get_layout().get_catalogue().get_gauge()*0.01;
+}
+
+void Path3D::path_changed(unsigned p)
+{
+ if(automatic)
+ {
+ path = p;
+ update_mesh();
+ }
+}
+
+void Path3D::update_mesh()
+{
+ mesh = &track.get_type().get_path_mesh(path, side);
}
long Path3D::get_instance_key() const
{
if(tag=="unlit")
{
- unsigned mask = (automatic ? 1<<track.get_track().get_active_path() : paths);
- mask &= track.get_track().get_type().get_paths();
- if(!mask)
- return;
-
GL::MatrixStack::Push push_mtx(renderer.matrix_stack());
renderer.matrix_stack() *= track.Object3D::get_matrix();
renderer.matrix_stack() *= GL::Matrix::translation(0, 0, z_offs);
glColor4f(color.r, color.g, color.b, color.a);
- for(unsigned i=0; mask; ++i, mask>>=1)
- if(mask&1)
- track.get_type().get_path_mesh(i).draw(renderer);
+ mesh->draw(renderer);
}
}
#include <msp/gl/color.h>
#include <msp/gl/mesh.h>
#include <msp/gl/renderable.h>
+#include "utility.h"
namespace R2C2 {
class Track3D;
-class Path3D: public Msp::GL::Renderable
+class Path3D: public Utility3D, public Msp::GL::Renderable, public sigc::trackable
{
private:
const Track3D &track;
- unsigned paths;
+ int path;
+ int side;
bool automatic;
+ const Msp::GL::Mesh *mesh;
Msp::GL::Color color;
float z_offs;
~Path3D();
void set_automatic();
- void set_path(unsigned);
- void set_mask(unsigned);
+ void set_path(int);
+ void set_side(int);
void set_color(const Msp::GL::Color &);
void set_layer(float);
+private:
+ void path_changed(unsigned);
+ void update_mesh();
+public:
virtual long get_instance_key() const;
virtual void render(Msp::GL::Renderer &, const Msp::GL::Tag &) const;
Object3D(l, t),
GL::ObjectInstance(l.get_catalogue().get_track(t.get_type()).get_object()),
track(t),
- type(layout.get_catalogue().get_track(track.get_type())),
- path(new Path3D(*this))
+ type(layout.get_catalogue().get_track(track.get_type()))
{
layout.get_scene().add(*this);
Track3D::~Track3D()
{
- delete path;
-
layout.get_scene().remove(*this);
for(vector<Endpoint3D *>::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
Track &track;
const TrackType3D &type;
std::vector<Endpoint3D *> endpoints;
- Path3D *path;
public:
Track3D(Layout3D &, Track &);
Layout3D &get_layout() const { return layout; }
Track &get_track() const { return track; }
const TrackType3D &get_type() const { return type; }
- Path3D &get_path() { return *path; }
virtual Vector get_node() const;
virtual bool is_visible() const { return true; }
--- /dev/null
+#include "libr2c2/blockiter.h"
+#include "libr2c2/trackiter.h"
+#include "layout.h"
+#include "path.h"
+#include "track.h"
+#include "trackcircuit.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace R2C2 {
+
+TrackCircuit3D::TrackCircuit3D(Layout3D &l, TrackCircuit &tc):
+ Utility3D(l),
+ track_circuit(tc)
+{
+ Block *block = track_circuit.get_block();
+ TrackIter iter = BlockIter(block, 0).track_iter();
+ for(; (iter && &iter->get_block()==block); iter=iter.next())
+ {
+ Path3D *path = new Path3D(layout.get<Track3D>(*iter));
+ paths.push_back(path);
+ path->set_side(iter.entry()*2-1);
+ }
+
+ track_circuit.signal_state_changed.connect(sigc::mem_fun(this, &TrackCircuit3D::state_changed));
+ state_changed(track_circuit.get_state());
+}
+
+void TrackCircuit3D::state_changed(Sensor::State state)
+{
+ GL::Color color;
+ if(state>=Sensor::ACTIVE)
+ color = GL::Color(0.3f, 0.8f, 0.0f);
+ else if(state>Sensor::INACTIVE)
+ color = GL::Color(0.6f, 0.5f, 0.0f);
+ else
+ color = GL::Color(0.0f, 0.3f, 0.6f);
+
+ for(list<Path3D *>::const_iterator i=paths.begin(); i!=paths.end(); ++i)
+ (*i)->set_color(color);
+}
+
+} // namespace R2C2
--- /dev/null
+#ifndef R2C2_TRACKCIRCUIT_H_
+#define R2C2_TRACKCIRCUIT_H_
+
+#include <libr2c2/trackcircuit.h>
+#include "utility.h"
+
+namespace R2C2 {
+
+class Layout3D;
+class Path3D;
+
+class TrackCircuit3D: public Utility3D, public sigc::trackable
+{
+private:
+ TrackCircuit &track_circuit;
+ std::list<Path3D *> paths;
+
+public:
+ TrackCircuit3D(Layout3D &, TrackCircuit &);
+
+private:
+ void state_changed(Sensor::State);
+};
+
+} // namespace R2C2
+
+#endif
own_data = true;
}
-
+
unsigned paths = tt.get_paths();
- for(unsigned i=0; paths; ++i, paths>>=1)
+ for(int i=-1; i<=1; ++i)
{
- GL::Mesh *m = 0;
- if(paths&1)
+ // TODO Make profile width configurable
+ Profile profile;
+ if(i==0)
+ {
+ float rail_w = (rail_max.x-rail_min.x)*2;
+ profile.append_vertex(Vector(rail_w*-0.5, 0, 0), false);
+ profile.append_vertex(Vector(rail_w*0.5, 0, 0), false);
+ }
+ else
{
- m = new GL::Mesh(GL::VERTEX3);
+ profile.append_vertex(Vector(i*(gauge*0.5+rail_min.x*2), 0, 0), false);
+ profile.append_vertex(Vector(i*(gauge*0.5+rail_max.x*2), 0, 0), false);
+ }
+
+ // TODO Avoid generating redundant meshes for single-path tracks
+ for(int j=-1; j<=4; ++j)
+ {
+ if(j>=0 && !((paths>>j)&1))
+ continue;
+
+ GL::Mesh *m = new GL::Mesh(GL::VERTEX3);
GL::MeshBuilder bld(*m);
unsigned index = 0;
- for(vector<TrackPart>::const_iterator j=parts.begin(); j!=parts.end(); ++j)
- if(j->get_path()==i)
- build_part(*j, cat.get_path_profile(), Vector(0, 0, ballast_h+1.5*rail_h), false, bld, index);
+ for(vector<TrackPart>::const_iterator k=parts.begin(); k!=parts.end(); ++k)
+ if(j<0 || k->get_path()==static_cast<unsigned>(j))
+ build_part(*k, profile, Vector(0, 0, ballast_h+1.5*rail_h), false, bld, index);
+ path_meshes[(j&0xFF)|((i&3)<<8)] = m;
}
- path_meshes.push_back(m);
}
}
TrackType3D::~TrackType3D()
{
- for(vector<GL::Mesh *>::iterator i=path_meshes.begin(); i!=path_meshes.end(); ++i)
- delete *i;
+ for(map<unsigned, GL::Mesh *>::iterator i=path_meshes.begin(); i!=path_meshes.end(); ++i)
+ delete i->second;
if(own_data)
{
delete object;
}
}
-const GL::Mesh &TrackType3D::get_path_mesh(unsigned p) const
+const GL::Mesh &TrackType3D::get_path_mesh(int p, int s) const
{
- if(p>=path_meshes.size())
- throw out_of_range("TrackType3D::get_path_mesh");
- if(!path_meshes[p])
- throw invalid_argument("TrackType3D::get_path_mesh");
- return *path_meshes[p];
+ unsigned key = (p<0 ? 0xFF : p) | ((s&3)<<8);
+ return *get_item(path_meshes, key);
}
void TrackType3D::build_part(const TrackPart &part, const Profile &profile, const Vector &offset, bool mirror, GL::MeshBuilder &bld, unsigned &base_index)
#ifndef R2C2_3D_TRACKTYPE_H_
#define R2C2_3D_TRACKTYPE_H_
+#include <map>
#include <msp/gl/mesh.h>
#include <msp/gl/meshbuilder.h>
#include <msp/gl/object.h>
Msp::GL::Mesh *mesh;
Msp::GL::Object *object;
bool own_data;
- std::vector<Msp::GL::Mesh *> path_meshes;
+ std::map<unsigned, Msp::GL::Mesh *> path_meshes;
public:
TrackType3D(Catalogue3D &, const TrackType &);
~TrackType3D();
const Msp::GL::Object &get_object() const { return *object; }
- const Msp::GL::Mesh &get_path_mesh(unsigned) const;
+ const Msp::GL::Mesh &get_path_mesh(int, int) const;
private:
void build_part(const TrackPart &, const Profile &, const Vector &, bool, Msp::GL::MeshBuilder &, unsigned &);
--- /dev/null
+#include "layout.h"
+#include "utility.h"
+
+namespace R2C2 {
+
+Utility3D::Utility3D(Layout3D &l):
+ layout(l)
+{
+ layout.add(*this);
+}
+
+Utility3D::~Utility3D()
+{
+ layout.remove(*this);
+}
+
+} // namespace R2C2
--- /dev/null
+#ifndef R2C2_3D_UTILITY_H_
+#define R2C2_3D_UTILITY_H_
+
+namespace R2C2 {
+
+class Layout3D;
+
+class Utility3D
+{
+protected:
+ Layout3D &layout;
+
+ Utility3D(Layout3D &);
+public:
+ virtual ~Utility3D();
+};
+
+} // namespace R2C2
+
+#endif
layout = new Layout(catalogue);
layout_3d = new Layout3D(*layout);
- layout->signal_object_added.connect(sigc::mem_fun(this, &Designer::object_added));
- layout->signal_object_removed.connect(sigc::mem_fun(this, &Designer::object_removed));
-
if(argc>1)
{
filename = argv[1];
root.tick();
camera_ctl->tick(dt);
- for(list<Track *>::iterator i=new_tracks.begin(); i!=new_tracks.end(); ++i)
- layout_3d->get<Track3D>(**i).get_path().set_mask(0);
- new_tracks.clear();
-
render();
window.swap_buffers();
root.render();
}
-void Designer::object_added(Object &obj)
-{
- if(Track *trk = dynamic_cast<Track *>(&obj))
- new_tracks.push_back(trk);
-}
-
-void Designer::object_removed(Object &obj)
-{
- if(Track *trk = dynamic_cast<Track *>(&obj))
- {
- list<Track *>::iterator i = find(new_tracks.begin(), new_tracks.end(), trk);
- if(i!=new_tracks.end())
- new_tracks.erase(i);
- }
-}
-
Object *Designer::pick_object(const Vector &pointer)
{
View3D &view = *(mode==CATALOGUE ? cat_view : main_view);
void Designer::clear_paths()
{
- const set<Track *> <racks = layout->get_all<Track>();
- for(set<Track *>::iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
- {
- Track3D &t3d = layout_3d->get<Track3D>(**i);
- t3d.get_path().set_mask(0);
- }
+ for(list<Path3D *>::iterator i=highlight_paths.begin(); i!=highlight_paths.end(); ++i)
+ delete *i;
+ highlight_paths.clear();
}
void Designer::show_route(const Route &route)
for(set<Track *>::iterator i=rtracks.begin(); i!=rtracks.end(); ++i)
{
Track3D &t3d = layout_3d->get<Track3D>(**i);
- t3d.get_path().set_color(GL::Color(0.5, 0.8, 1.0));
- int path = -1;
+ Path3D *path = new Path3D(t3d);
+ path->set_color(GL::Color(0.5, 0.8, 1.0));
if(unsigned tid = (*i)->get_turnout_id())
- path = route.get_turnout(tid);
- if(path>=0)
- t3d.get_path().set_path(path);
- else
- t3d.get_path().set_mask((*i)->get_type().get_paths());
+ path->set_path(route.get_turnout(tid));
}
}
for(Zone::TrackSet::const_iterator i=ztracks.begin(); i!=ztracks.end(); ++i)
{
Track3D &t3d = layout_3d->get<Track3D>(**i);
- t3d.get_path().set_color(GL::Color(0.8, 1.0, 0.5));
- t3d.get_path().set_mask((*i)->get_type().get_paths());
+ Path3D *path = new Path3D(t3d);
+ path->set_color(GL::Color(0.8, 1.0, 0.5));
}
}
Msp::GL::Object *base_object;
R2C2::Route *cur_route;
R2C2::Zone *cur_zone;
- std::list<R2C2::Track *> new_tracks;
+ std::list<R2C2::Path3D *> highlight_paths;
Mode mode;
Selection selection;
void axis_motion(unsigned, float, float);
void apply_camera();
void render();
- void object_added(R2C2::Object &);
- void object_removed(R2C2::Object &);
R2C2::Object *pick_object(const R2C2::Vector &);
void update_track_icon(R2C2::Track3D &);
void selection_changed();
#include "libr2c2/driver.h"
#include "libr2c2/trackcircuit.h"
#include "libr2c2/tracktype.h"
+#include "3d/allocation.h"
#include "3d/path.h"
#include "3d/track.h"
+#include "3d/trackcircuit.h"
#include "3d/vehicle.h"
#include "engineer.h"
#include "mainwindow.h"
DataFile::load(layout, options.layout_fn);
layout.signal_train_added.connect(sigc::mem_fun(this, &Engineer::train_added));
- layout.signal_block_reserved.connect(sigc::hide<1>(sigc::mem_fun(this, &Engineer::reset_block_color)));
layout.signal_emergency.connect(sigc::mem_fun(this, &Engineer::set_status));
const set<Block *> &blocks = layout.get_all<Block>();
for(set<Block *>::const_iterator i=blocks.begin(); i!=blocks.end(); ++i)
- if(Sensor *sensor = (*i)->get_sensor())
- sensor->signal_state_changed.connect(sigc::hide(sigc::bind(sigc::mem_fun(this, &Engineer::reset_block_color), sigc::ref(**i))));
+ if(TrackCircuit *tc = (*i)->get_sensor())
+ new TrackCircuit3D(layout_3d, *tc);
+
+ const set<Track *> &tracks = layout.get_all<Track>();
+ for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+ if((*i)->get_type().is_turnout())
+ new Path3D(layout_3d.get<Track3D>(**i));
if(FS::exists(options.state_fn))
DataFile::load(layout, options.state_fn);
delete picking_path;
picking_path = new Path3D(layout_3d.get<Track3D>(*track));
- if(picking_entry>=0)
- picking_path->set_mask(picking_track->get_type().get_endpoint(picking_entry).paths);
- else
- picking_path->set_mask(picking_track->get_type().get_paths());
picking_path->set_color(GL::Color(0));
- picking_path->set_layer(1);
+ picking_path->set_layer(2);
}
}
}
else if(btn==3 && picking_entry>=0)
{
picking_entry = (picking_entry+1)%picking_track->get_type().get_endpoints().size();
- picking_path->set_mask(picking_track->get_type().get_endpoint(picking_entry).paths);
}
}
else
camera.set_depth_clip(pos.z*0.5, pos.z*1.5);
}
-void Engineer::set_block_color(const Block &block, const GL::Color &color)
-{
- const set<Track *> &tracks = block.get_tracks();
- for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
- layout_3d.get<Track3D>(**i).get_path().set_color(color);
-}
-
-void Engineer::reset_block_color(const Block &block)
-{
- bool active = false;
- if(Sensor *sensor = block.get_sensor())
- active = sensor->get_state()>Sensor::INACTIVE;
-
- if(block.get_train())
- {
- GL::Color color;
- map<Train *, GL::Color>::iterator i = train_colors.find(block.get_train());
- if(i!=train_colors.end())
- color = i->second;
-
- if(active)
- set_block_color(block, color*0.6);
- else
- set_block_color(block, color*0.5+0.5);
- }
- else if(active)
- set_block_color(block, GL::Color(0.6));
- else
- set_block_color(block, GL::Color(1));
-}
-
Object *Engineer::pick_object(const Vector &p)
{
const GL::Vector3 &start = camera.get_position();
}
}
train_colors[&train] = best_color;
+
+ Allocation3D *alloc = new Allocation3D(layout_3d, train);
+ alloc->set_color(best_color);
}
void Engineer::sighandler(int sig)
void button_press(unsigned);
void axis_motion(unsigned, float, float);
void view_all();
- void set_block_color(const R2C2::Block &, const Msp::GL::Color &);
- void reset_block_color(const R2C2::Block &);
void sensor_event(unsigned, bool);
void block_reserved(const R2C2::Block &, const R2C2::Train *);
R2C2::Object *pick_object(const R2C2::Vector &);
void Catalogue::Loader::gauge(float g)
{
obj.gauge = g/1000;
- obj.path_profile = Profile();
- obj.path_profile.append_vertex(Vector(0.1*obj.gauge, 0, 0), false);
- obj.path_profile.append_vertex(Vector(-0.1*obj.gauge, 0, 0), false);
}
void Catalogue::Loader::layout()
float gauge;
Profile rail_profile;
Profile ballast_profile;
- Profile path_profile;
std::string track_technique;
TrackMap tracks;
VehicleMap vehicles;
float get_rail_elevation() const;
const Profile &get_rail_profile() const { return rail_profile; }
const Profile &get_ballast_profile() const { return ballast_profile; }
- const Profile &get_path_profile() const { return path_profile; }
const std::string &get_track_technique() const { return track_technique; }
void add_track(TrackType &);
tracks.insert(&track);
update_ends(track);
on_track_added(track);
+ signal_track_added.emit(track);
}
void TrackChain::add_tracks(const TrackSet &trks)
for(TrackSet::iterator i=pending.begin(); i!=pending.end(); ++i)
if((valid=check_validity(**i))==VALID)
{
- tracks.insert(*i);
- update_ends(**i);
- on_track_added(**i);
+ Track *t = *i;
pending.erase(i);
+ tracks.insert(t);
+ update_ends(*t);
+ on_track_added(*t);
+ signal_track_added.emit(*t);
break;
}
void TrackChain::object_removed(Object &obj)
{
if(Track *track = dynamic_cast<Track *>(&obj))
+ {
tracks.erase(track);
/* TODO If the track was in the middle of the chain, keep only the
longest fragment */
+ signal_track_removed.emit(*track);
+ }
}
} // namespace R2C2
INCOMPATIBLE
};
+public:
+ sigc::signal<void, Track &> signal_track_added;
+ sigc::signal<void, Track &> signal_track_removed;
+
Layout &layout;
TrackSet tracks;
TrackIter ends[2];