mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-15 09:55:55 +01:00
snap now fills in a struct (MusicFrame) which contins a snapped frame along with a music divisor. this gives useful information wrt magnetic snap which may or may not have rounded to an exact musical position. region position may now be set musically (using quarter notes for now). this patch fixes several problems in the current code: - dragging a list of music-locked regions now maintains correct musical offsets within the list. - splitting regions using magnetic snap works correctly (#7192) - cut drag should now work correctly with magnetic snap. - musical length of split midi regions is no longer frame based.
669 lines
17 KiB
C++
669 lines
17 KiB
C++
/*
|
|
Copyright (C) 2000-2006 Paul Davis
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "pbd/basename.h"
|
|
#include "pbd/error.h"
|
|
|
|
#include "ardour/audioregion.h"
|
|
#include "ardour/audiosource.h"
|
|
#include "ardour/midi_region.h"
|
|
#include "ardour/midi_source.h"
|
|
#include "ardour/region.h"
|
|
#include "ardour/region_factory.h"
|
|
#include "ardour/session.h"
|
|
|
|
#include "pbd/i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
using namespace std;
|
|
|
|
PBD::Signal1<void,boost::shared_ptr<Region> > RegionFactory::CheckNewRegion;
|
|
Glib::Threads::Mutex RegionFactory::region_map_lock;
|
|
RegionFactory::RegionMap RegionFactory::region_map;
|
|
PBD::ScopedConnectionList* RegionFactory::region_list_connections = 0;
|
|
Glib::Threads::Mutex RegionFactory::region_name_maps_mutex;
|
|
std::map<std::string, uint32_t> RegionFactory::region_name_number_map;
|
|
std::map<std::string, PBD::ID> RegionFactory::region_name_map;
|
|
RegionFactory::CompoundAssociations RegionFactory::_compound_associations;
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (boost::shared_ptr<const Region> region, bool announce)
|
|
{
|
|
boost::shared_ptr<Region> ret;
|
|
boost::shared_ptr<const AudioRegion> ar;
|
|
boost::shared_ptr<const MidiRegion> mr;
|
|
|
|
if ((ar = boost::dynamic_pointer_cast<const AudioRegion>(region)) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new AudioRegion (ar, MusicFrame (0, 0)));
|
|
|
|
} else if ((mr = boost::dynamic_pointer_cast<const MidiRegion>(region)) != 0) {
|
|
|
|
if (mr->session().config.get_midi_copy_is_fork()) {
|
|
/* What we really want to do here is what Editor::fork_region()
|
|
does via Session::create_midi_source_by_stealing_name(), but we
|
|
don't have a Track. We'll just live with the skipped number,
|
|
and store the ancestral name of sources so multiple clones
|
|
generates reasonable names that don't have too many suffixes. */
|
|
const std::string ancestor_name = mr->sources().front()->ancestor_name();
|
|
const std::string base = PBD::basename_nosuffix(ancestor_name);
|
|
|
|
boost::shared_ptr<MidiSource> source = mr->session().create_midi_source_for_session(base);
|
|
source->set_ancestor_name(mr->sources().front()->name());
|
|
ret = mr->clone(source);
|
|
} else {
|
|
ret = boost::shared_ptr<Region> (new MidiRegion (mr, MusicFrame (0, 0)));
|
|
}
|
|
|
|
} else {
|
|
fatal << _("programming error: RegionFactory::create() called with unknown Region type")
|
|
<< endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
|
|
if (ret) {
|
|
ret->set_name (new_region_name(ret->name()));
|
|
|
|
if (ret->session().config.get_glue_new_regions_to_bars_and_beats() && ret->position_lock_style() != MusicTime) {
|
|
ret->set_position_lock_style (MusicTime);
|
|
}
|
|
|
|
/* pure copy constructor - no property list */
|
|
if (announce) {
|
|
map_add (ret);
|
|
CheckNewRegion (ret);
|
|
}
|
|
}
|
|
|
|
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
|
|
// boost_debug_shared_ptr_mark_interesting (ret.get(), "Region");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (boost::shared_ptr<Region> region, const PropertyList& plist, bool announce)
|
|
{
|
|
boost::shared_ptr<Region> ret;
|
|
boost::shared_ptr<const AudioRegion> other_a;
|
|
boost::shared_ptr<const MidiRegion> other_m;
|
|
|
|
if ((other_a = boost::dynamic_pointer_cast<AudioRegion>(region)) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new AudioRegion (other_a));
|
|
|
|
} else if ((other_m = boost::dynamic_pointer_cast<MidiRegion>(region)) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new MidiRegion (other_m));
|
|
|
|
} else {
|
|
fatal << _("programming error: RegionFactory::create() called with unknown Region type")
|
|
<< endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
return boost::shared_ptr<Region>();
|
|
}
|
|
|
|
if (ret) {
|
|
ret->apply_changes (plist);
|
|
|
|
if (ret->session().config.get_glue_new_regions_to_bars_and_beats() && ret->position_lock_style() != MusicTime) {
|
|
ret->set_position_lock_style (MusicTime);
|
|
}
|
|
|
|
if (announce) {
|
|
map_add (ret);
|
|
CheckNewRegion (ret);
|
|
}
|
|
}
|
|
|
|
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
|
|
// boost_debug_shared_ptr_mark_interesting (ret.get(), "Region");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (boost::shared_ptr<Region> region, MusicFrame offset, const PropertyList& plist, bool announce)
|
|
{
|
|
boost::shared_ptr<Region> ret;
|
|
boost::shared_ptr<const AudioRegion> other_a;
|
|
boost::shared_ptr<const MidiRegion> other_m;
|
|
|
|
if ((other_a = boost::dynamic_pointer_cast<AudioRegion>(region)) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new AudioRegion (other_a, offset));
|
|
|
|
} else if ((other_m = boost::dynamic_pointer_cast<MidiRegion>(region)) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new MidiRegion (other_m, offset));
|
|
|
|
} else {
|
|
fatal << _("programming error: RegionFactory::create() called with unknown Region type")
|
|
<< endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
return boost::shared_ptr<Region>();
|
|
}
|
|
|
|
if (ret) {
|
|
ret->apply_changes (plist);
|
|
|
|
if (ret->session().config.get_glue_new_regions_to_bars_and_beats() && ret->position_lock_style() != MusicTime) {
|
|
ret->set_position_lock_style (MusicTime);
|
|
}
|
|
|
|
if (announce) {
|
|
map_add (ret);
|
|
CheckNewRegion (ret);
|
|
}
|
|
}
|
|
|
|
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
|
|
// boost_debug_shared_ptr_mark_interesting (ret.get(), "Region");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (boost::shared_ptr<Region> region, const SourceList& srcs, const PropertyList& plist, bool announce)
|
|
{
|
|
boost::shared_ptr<Region> ret;
|
|
boost::shared_ptr<const AudioRegion> other;
|
|
|
|
/* used by AudioFilter when constructing a new region that is intended to have nearly
|
|
identical settings to an original, but using different sources.
|
|
*/
|
|
|
|
if ((other = boost::dynamic_pointer_cast<AudioRegion>(region)) != 0) {
|
|
|
|
// XXX use me in caller where plist is setup, this is start i think srcs.front()->length (srcs.front()->timeline_position())
|
|
|
|
ret = boost::shared_ptr<Region> (new AudioRegion (other, srcs));
|
|
|
|
} else {
|
|
fatal << _("programming error: RegionFactory::create() called with unknown Region type")
|
|
<< endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
|
|
if (ret) {
|
|
ret->apply_changes (plist);
|
|
|
|
if (ret->session().config.get_glue_new_regions_to_bars_and_beats() && ret->position_lock_style() != MusicTime) {
|
|
ret->set_position_lock_style (MusicTime);
|
|
}
|
|
|
|
if (announce) {
|
|
map_add (ret);
|
|
CheckNewRegion (ret);
|
|
}
|
|
}
|
|
|
|
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
|
|
// boost_debug_shared_ptr_mark_interesting (ret.get(), "Region");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (boost::shared_ptr<Source> src, const PropertyList& plist, bool announce)
|
|
{
|
|
SourceList srcs;
|
|
srcs.push_back (src);
|
|
return create (srcs, plist, announce);
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (const SourceList& srcs, const PropertyList& plist, bool announce)
|
|
{
|
|
boost::shared_ptr<Region> ret;
|
|
boost::shared_ptr<AudioSource> as;
|
|
boost::shared_ptr<MidiSource> ms;
|
|
|
|
if ((as = boost::dynamic_pointer_cast<AudioSource>(srcs[0])) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new AudioRegion (srcs));
|
|
|
|
} else if ((ms = boost::dynamic_pointer_cast<MidiSource>(srcs[0])) != 0) {
|
|
|
|
ret = boost::shared_ptr<Region> (new MidiRegion (srcs));
|
|
|
|
}
|
|
|
|
if (ret) {
|
|
ret->apply_changes (plist);
|
|
|
|
if (ret->session().config.get_glue_new_regions_to_bars_and_beats() && ret->position_lock_style() != MusicTime) {
|
|
ret->set_position_lock_style (MusicTime);
|
|
}
|
|
|
|
if (announce) {
|
|
map_add (ret);
|
|
CheckNewRegion (ret);
|
|
}
|
|
}
|
|
|
|
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
|
|
// boost_debug_shared_ptr_mark_interesting (ret.get(), "Region");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (Session& session, XMLNode& node, bool yn)
|
|
{
|
|
return session.XMLRegionFactory (node, yn);
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::create (SourceList& srcs, const XMLNode& node)
|
|
{
|
|
boost::shared_ptr<Region> ret;
|
|
|
|
if (srcs.empty()) {
|
|
return ret;
|
|
}
|
|
|
|
if (srcs[0]->type() == DataType::AUDIO) {
|
|
|
|
ret = boost::shared_ptr<Region> (new AudioRegion (srcs));
|
|
|
|
} else if (srcs[0]->type() == DataType::MIDI) {
|
|
|
|
ret = boost::shared_ptr<Region> (new MidiRegion (srcs));
|
|
|
|
}
|
|
|
|
if (ret) {
|
|
if (ret->set_state (node, Stateful::loading_state_version)) {
|
|
ret.reset ();
|
|
} else {
|
|
map_add (ret);
|
|
|
|
/* Don't fiddle with position_lock_style here as the region
|
|
description is coming from XML.
|
|
*/
|
|
|
|
CheckNewRegion (ret);
|
|
}
|
|
}
|
|
|
|
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
|
|
// boost_debug_shared_ptr_mark_interesting (ret.get(), "Region");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
RegionFactory::map_add (boost::shared_ptr<Region> r)
|
|
{
|
|
pair<ID,boost::shared_ptr<Region> > p;
|
|
p.first = r->id();
|
|
p.second = r;
|
|
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
region_map.insert (p);
|
|
}
|
|
|
|
if (!region_list_connections) {
|
|
region_list_connections = new ScopedConnectionList;
|
|
}
|
|
|
|
r->DropReferences.connect_same_thread (*region_list_connections, boost::bind (&RegionFactory::map_remove, boost::weak_ptr<Region> (r)));
|
|
r->PropertyChanged.connect_same_thread (*region_list_connections, boost::bind (&RegionFactory::region_changed, _1, boost::weak_ptr<Region> (r)));
|
|
|
|
add_to_region_name_maps (r);
|
|
}
|
|
|
|
void
|
|
RegionFactory::map_remove (boost::weak_ptr<Region> w)
|
|
{
|
|
boost::shared_ptr<Region> r = w.lock ();
|
|
if (!r) {
|
|
return;
|
|
}
|
|
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
RegionMap::iterator i = region_map.find (r->id());
|
|
|
|
if (i != region_map.end()) {
|
|
remove_from_region_name_map (i->second->name ());
|
|
region_map.erase (i);
|
|
}
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::region_by_id (const PBD::ID& id)
|
|
{
|
|
RegionMap::iterator i = region_map.find (id);
|
|
|
|
if (i == region_map.end()) {
|
|
return boost::shared_ptr<Region>();
|
|
}
|
|
|
|
return i->second;
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::wholefile_region_by_name (const std::string& name)
|
|
{
|
|
for (RegionMap::iterator i = region_map.begin(); i != region_map.end(); ++i) {
|
|
if (i->second->whole_file() && i->second->name() == name) {
|
|
return i->second;
|
|
}
|
|
}
|
|
return boost::shared_ptr<Region>();
|
|
}
|
|
|
|
boost::shared_ptr<Region>
|
|
RegionFactory::region_by_name (const std::string& name)
|
|
{
|
|
for (RegionMap::iterator i = region_map.begin(); i != region_map.end(); ++i) {
|
|
if (i->second->name() == name) {
|
|
return i->second;
|
|
}
|
|
}
|
|
return boost::shared_ptr<Region>();
|
|
}
|
|
|
|
void
|
|
RegionFactory::clear_map ()
|
|
{
|
|
if (region_list_connections) {
|
|
region_list_connections->drop_connections ();
|
|
}
|
|
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
region_map.clear ();
|
|
_compound_associations.clear ();
|
|
region_name_map.clear ();
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFactory::delete_all_regions ()
|
|
{
|
|
RegionMap copy;
|
|
|
|
/* copy region list */
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
copy = region_map;
|
|
}
|
|
|
|
/* clear existing map */
|
|
clear_map ();
|
|
|
|
/* tell everyone to drop references */
|
|
for (RegionMap::iterator i = copy.begin(); i != copy.end(); ++i) {
|
|
i->second->drop_references ();
|
|
}
|
|
|
|
/* the copy should now hold the only references, which will
|
|
vanish as we leave this scope, thus calling all destructors.
|
|
*/
|
|
}
|
|
|
|
uint32_t
|
|
RegionFactory::nregions ()
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
return region_map.size ();
|
|
}
|
|
|
|
/** Add a region to the two region name maps */
|
|
void
|
|
RegionFactory::add_to_region_name_maps (boost::shared_ptr<Region> region)
|
|
{
|
|
update_region_name_number_map (region);
|
|
|
|
Glib::Threads::Mutex::Lock lm (region_name_maps_mutex);
|
|
region_name_map[region->name()] = region->id ();
|
|
}
|
|
|
|
/** Account for a region rename in the two region name maps */
|
|
void
|
|
RegionFactory::rename_in_region_name_maps (boost::shared_ptr<Region> region)
|
|
{
|
|
update_region_name_number_map (region);
|
|
|
|
Glib::Threads::Mutex::Lock lm (region_name_maps_mutex);
|
|
|
|
map<string, PBD::ID>::iterator i = region_name_map.begin();
|
|
while (i != region_name_map.end() && i->second != region->id ()) {
|
|
++i;
|
|
}
|
|
|
|
/* Erase the entry for the old name and put in a new one */
|
|
if (i != region_name_map.end()) {
|
|
region_name_map.erase (i);
|
|
region_name_map[region->name()] = region->id ();
|
|
}
|
|
}
|
|
|
|
/** Remove a region's details from the region_name_map */
|
|
void
|
|
RegionFactory::remove_from_region_name_map (string n)
|
|
{
|
|
map<string, PBD::ID>::iterator i = region_name_map.find (n);
|
|
if (i != region_name_map.end ()) {
|
|
region_name_map.erase (i);
|
|
}
|
|
}
|
|
|
|
/** Update a region's entry in the region_name_number_map */
|
|
void
|
|
RegionFactory::update_region_name_number_map (boost::shared_ptr<Region> region)
|
|
{
|
|
string::size_type const last_period = region->name().find_last_of ('.');
|
|
|
|
if (last_period != string::npos && last_period < region->name().length() - 1) {
|
|
|
|
string const base = region->name().substr (0, last_period);
|
|
string const number = region->name().substr (last_period + 1);
|
|
|
|
/* note that if there is no number, we get zero from atoi,
|
|
which is just fine
|
|
*/
|
|
|
|
Glib::Threads::Mutex::Lock lm (region_name_maps_mutex);
|
|
region_name_number_map[base] = atoi (number.c_str ());
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFactory::region_changed (PropertyChange const & what_changed, boost::weak_ptr<Region> w)
|
|
{
|
|
boost::shared_ptr<Region> r = w.lock ();
|
|
if (!r) {
|
|
return;
|
|
}
|
|
|
|
if (what_changed.contains (Properties::name)) {
|
|
rename_in_region_name_maps (r);
|
|
}
|
|
}
|
|
|
|
int
|
|
RegionFactory::region_name (string& result, string base, bool newlevel)
|
|
{
|
|
char buf[16];
|
|
string subbase;
|
|
|
|
if (base.find("/") != string::npos) {
|
|
base = base.substr(base.find_last_of("/") + 1);
|
|
}
|
|
|
|
if (base == "") {
|
|
|
|
snprintf (buf, sizeof (buf), "%d", RegionFactory::nregions() + 1);
|
|
result = "region.";
|
|
result += buf;
|
|
|
|
} else {
|
|
|
|
if (newlevel) {
|
|
subbase = base;
|
|
} else {
|
|
string::size_type pos;
|
|
|
|
pos = base.find_last_of ('.');
|
|
|
|
/* pos may be npos, but then we just use entire base */
|
|
|
|
subbase = base.substr (0, pos);
|
|
|
|
}
|
|
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_name_maps_mutex);
|
|
|
|
map<string,uint32_t>::iterator x;
|
|
|
|
result = subbase;
|
|
|
|
if ((x = region_name_number_map.find (subbase)) == region_name_number_map.end()) {
|
|
result += ".1";
|
|
region_name_number_map[subbase] = 1;
|
|
} else {
|
|
x->second++;
|
|
snprintf (buf, sizeof (buf), ".%d", x->second);
|
|
|
|
result += buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
string
|
|
RegionFactory::compound_region_name (const string& playlist, uint32_t compound_ops, uint32_t depth, bool whole_source)
|
|
{
|
|
if (whole_source) {
|
|
return string_compose (_("%1 compound-%2 (%3)"), playlist, compound_ops+1, depth+1);
|
|
} else {
|
|
return string_compose (_("%1 compound-%2.1 (%3)"), playlist, compound_ops+1, depth+1);
|
|
}
|
|
}
|
|
|
|
string
|
|
RegionFactory::new_region_name (string old)
|
|
{
|
|
string::size_type last_period;
|
|
uint32_t number;
|
|
string::size_type len = old.length() + 64;
|
|
string remainder;
|
|
std::vector<char> buf(len);
|
|
|
|
if ((last_period = old.find_last_of ('.')) == string::npos) {
|
|
|
|
/* no period present - add one explicitly */
|
|
|
|
old += '.';
|
|
last_period = old.length() - 1;
|
|
number = 0;
|
|
|
|
} else {
|
|
|
|
if (last_period < old.length() - 1) {
|
|
|
|
string period_to_end = old.substr (last_period+1);
|
|
|
|
/* extra material after the period */
|
|
|
|
string::size_type numerals_end = period_to_end.find_first_not_of ("0123456789");
|
|
|
|
number = atoi (period_to_end);
|
|
|
|
if (numerals_end < period_to_end.length() - 1) {
|
|
/* extra material after the end of the digits */
|
|
remainder = period_to_end.substr (numerals_end);
|
|
}
|
|
|
|
} else {
|
|
last_period = old.length();
|
|
number = 0;
|
|
}
|
|
}
|
|
|
|
while (number < (UINT_MAX-1)) {
|
|
|
|
string sbuf;
|
|
|
|
number++;
|
|
|
|
snprintf (&buf[0], len, "%s%" PRIu32 "%s", old.substr (0, last_period + 1).c_str(), number, remainder.c_str());
|
|
sbuf = &buf[0];
|
|
|
|
if (region_name_map.find (sbuf) == region_name_map.end ()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (number != (UINT_MAX-1)) {
|
|
return &buf[0];
|
|
}
|
|
|
|
error << string_compose (_("cannot create new name for region \"%1\""), old) << endmsg;
|
|
return old;
|
|
}
|
|
|
|
void
|
|
RegionFactory::get_regions_using_source (boost::shared_ptr<Source> s, std::set<boost::shared_ptr<Region> >& r)
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
|
|
for (RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
|
|
if (i->second->uses_source (s)) {
|
|
r.insert (i->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFactory::remove_regions_using_source (boost::shared_ptr<Source> src)
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
|
|
RegionMap::iterator i = region_map.begin();
|
|
while (i != region_map.end()) {
|
|
|
|
RegionMap::iterator j = i;
|
|
++j;
|
|
|
|
if (i->second->uses_source (src)) {
|
|
remove_from_region_name_map (i->second->name ());
|
|
region_map.erase (i);
|
|
}
|
|
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
void
|
|
RegionFactory::add_compound_association (boost::shared_ptr<Region> orig, boost::shared_ptr<Region> copy)
|
|
{
|
|
Glib::Threads::Mutex::Lock lm (region_map_lock);
|
|
_compound_associations[copy] = orig;
|
|
}
|