Logo Search packages:      
Sourcecode: k3d version File versions

document.cpp

Go to the documentation of this file.
// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
            \brief Provides a concrete implementation of k3d::idocument, which encapsulates an open K-3D document
            \author Tim Shead (tshead@k-3d.com)
            \author Dan Erikson (derikson@montana.com)
*/

#include "document.h"

#include <k3dsdk/application.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/command_node.h>
#include <k3dsdk/data.h>
#include <k3dsdk/dependencies.h>
#include <k3dsdk/file_filter.h>
#include <k3dsdk/icommand_tree.h>
#include <k3dsdk/idag.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/idocument_plugin_factory.h>
#include <k3dsdk/igeometry_read_format.h>
#include <k3dsdk/igeometry_write_format.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imouse_event_observer.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/iplugin_factory.h>
#include <k3dsdk/iselectable.h>
#include <k3dsdk/iselection.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/path_data.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property_collection.h>
#include <k3dsdk/serialization.h>
#include <k3dsdk/string_cast.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/user_interface.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/viewport.h>
#include <k3dsdk/xml_utility.h>

#include <sdpxml/sdpxml.h>
#include <sigc++/bind.h>

#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/path.hpp>

#include <fstream>
#include <iterator>
#include <memory>

namespace
{

/////////////////////////////////////////////////////////////////////////////
// next_document_number

/// Returns unique document numbers for the duration of the current session
00077 unsigned long next_document_number()
{
      static unsigned long document_number = 0;
      return ++document_number;
}

/////////////////////////////////////////////////////////////////////////////
// save_object

/// Serializes K-3D objects
class save_object
{
public:
      save_object(sdpxml::Element& Element, k3d::idependencies& Dependencies) :
            m_Element(Element),
            m_Dependencies(Dependencies),
            m_Count(0)
      {
      }

      void operator()(k3d::iobject* const Object)
      {
            // Save the object if it supports persistence ...
            k3d::ipersistent* const persist = dynamic_cast<k3d::ipersistent*>(Object);
            if(!persist)
                  return;

            // Create an XML element for the object ...
            sdpxml::Element& objectelement = m_Element.Append(sdpxml::Element("object", "",
                  sdpxml::Attribute("name", Object->name()),
                  sdpxml::Attribute("class", sdpToString(Object->factory().class_id())),
                  sdpxml::Attribute("id", sdpToString(Object->id()))));
            persist->save(objectelement, m_Dependencies);

            m_Count++;
      }

private:
      sdpxml::Element& m_Element;
      k3d::idependencies& m_Dependencies;
      unsigned long m_Count;
};

/////////////////////////////////////////////////////////////////////////////
// state_recorder_implementation

/// Provides a concrete implementation of k3d::istate_recorder
class state_recorder_implementation :
      public k3d::istate_recorder
{
public:
      state_recorder_implementation() :
            m_redo_position(m_change_sets.end()),
            m_saved_position(m_change_sets.end())
      {
      }

      ~state_recorder_implementation()
      {
            // Delete all our change sets ...
            std::for_each(m_change_sets.begin(), m_change_sets.end(), k3d::delete_object());

            // Sanity checks ...
            return_if_fail(!m_current_recording.get());
      }

      void start_recording(std::auto_ptr<k3d::istate_change_set> ChangeSet)
      {
            // Sanity checks ...
            return_if_fail(ChangeSet.get());
            return_if_fail(!m_current_recording.get());
            m_current_recording = ChangeSet;
      }

      std::auto_ptr<k3d::istate_change_set> stop_recording()
      {
            // Sanity checks ...
            return_val_if_fail(m_current_recording.get(), m_current_recording);

            // Let the world know that recording is stopping ...
            m_current_recording->recording_done_signal().emit();

            return m_current_recording;
      }

      void commit_change_set(std::auto_ptr<k3d::istate_change_set> ChangeSet, const std::string& Label)
      {
            // Sanity checks ...
            return_if_fail(ChangeSet.get());
            return_if_fail(Label.size());

            // Recording a state change creates a "branch" in the state change tree, so trim any data in the old branch ...
            for(change_set_iterator victim = m_redo_position; victim != m_change_sets.end(); victim++)
                  {
                        if(m_saved_position == victim)
                              {
                                    m_saved_position = m_change_sets.end();
                                    break;
                              }
                  }

            std::for_each(m_redo_position, m_change_sets.end(), k3d::delete_object());
            m_change_sets.erase(m_redo_position, m_change_sets.end());

            // Store the new change set ...
            m_change_sets.push_back(new change_set_record(ChangeSet.release(), Label));
            m_redo_position = m_change_sets.end();

            // Notify observers that our contents have been modified ...
            m_stack_changed_signal.emit();
      }

      k3d::istate_change_set* current_change_set()
      {
            return m_current_recording.get();
      }

      void mark_saved()
      {
            if(m_redo_position != m_change_sets.begin())
                  m_saved_position = --change_set_iterator(m_redo_position);

            m_mark_saved_signal.emit();
      }

      bool unsaved_changes()
      {
            // Either we haven't changed anything or we undid all our changes
            if(m_redo_position == m_change_sets.begin())
                  return m_saved_position != m_change_sets.end();

            // See if the current state and the saved state match ...
            return m_saved_position != --change_set_iterator(m_redo_position);
      }

      unsigned long undo_count()
      {
            return std::distance(m_change_sets.begin(), m_redo_position);
      }

      unsigned long redo_count()
      {
            return std::distance(m_redo_position, m_change_sets.end());
      }

      const std::string next_undo_label()
      {
            return m_redo_position != m_change_sets.begin() ? (*(--change_set_iterator(m_redo_position)))->label : std::string();
      }

      const std::string next_redo_label()
      {
            return m_redo_position != m_change_sets.end() ? (*m_redo_position)->label : std::string();
      }

      void undo()
      {
            // Should never happen in the middle of recording a state change set ...
            return_if_fail(!m_current_recording.get());

            // Make sure we have actions to undo ...
            return_if_fail(m_redo_position != m_change_sets.begin());

            // Undo the most recent change set
            m_redo_position--;
            (*m_redo_position)->change_set->undo();

            // Let the gentry know ...
            m_stack_changed_signal.emit();
      }

      void redo()
      {
            // Should never happen in the middle of recording a state change set ...
            return_if_fail(!m_current_recording.get());

            // Make sure we have actions to redo ...
            return_if_fail(m_redo_position != m_change_sets.end());

            // Redo the most recently undone change set ...
            (*m_redo_position)->change_set->redo();
            m_redo_position++;

            // Let the gentry know ...
            m_stack_changed_signal.emit();
      }

      void visit_change_sets(ichange_set_visitor& Visitor)
      {
            for(change_set_iterator undoset = m_change_sets.begin(); undoset != m_redo_position; undoset++)
                  Visitor.visit_undo_change_set((*undoset)->label, undoset == m_saved_position);

            for(change_set_iterator redoset = m_redo_position; redoset != m_change_sets.end(); redoset++)
                  Visitor.visit_redo_change_set((*redoset)->label, redoset == m_saved_position);
      }

      k3d::istate_recorder::stack_changed_signal_t& stack_changed_signal()
      {
            return m_stack_changed_signal;
      }

      k3d::istate_recorder::mark_saved_signal_t& mark_saved_signal()
      {
            return m_mark_saved_signal;
      }

private:
      /// Associates a name with a change set
      class change_set_record
      {
      public:
            change_set_record(k3d::istate_change_set* const ChangeSet, const std::string& Label) :
                  change_set(ChangeSet),
                  label(Label)
            {
                  // Sanity checks ...
                  assert_warning(change_set);
                  assert_warning(label.size());
            }
            
            ~change_set_record()
            {
                  delete change_set;
            }

            k3d::istate_change_set* const change_set;
            const std::string label;
      };

      /// Stores a collection of change sets
      typedef std::list<change_set_record*> change_sets_t;
      typedef change_sets_t::iterator change_set_iterator;
      /// Stores change sets
00310       change_sets_t m_change_sets;

      /// Stores the current change set
00313       std::auto_ptr<k3d::istate_change_set> m_current_recording;
      /// Stores an iterator that partitions the m_change_sets collection into undo-able and redo-able change sets
00315       change_set_iterator m_redo_position;
      /// Stores an iterator that points to the last saved change set
00317       change_set_iterator m_saved_position;

      stack_changed_signal_t m_stack_changed_signal;
      mark_saved_signal_t m_mark_saved_signal;
};

/////////////////////////////////////////////////////////////////////////////
// object_collection_implementation

class object_collection_implementation :
      public k3d::iobject_collection
{
public:
      object_collection_implementation(k3d::istate_recorder& StateRecorder) :
            m_state_recorder(StateRecorder),
            m_largest_id(0)
      {
      }

      ~object_collection_implementation()
      {
      }

      void add_objects(const objects_t& Objects)
      {
            // Ensure no NULLs creep in ...
            objects_t objects(Objects);
            if(objects.erase(static_cast<k3d::iobject*>(0)))
                  std::cerr << warning << "NULL object cannot be inserted into object collection and will be ignored" << std::endl;

            // Keep track of the largest ID seen so far ...
            for(objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
                  m_largest_id = std::max(m_largest_id, (*object)->id());

            // We want to emit a signal whenever an object's name changes ...
            for(objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
                  (*object)->name_changed_signal().connect(SigC::bind(m_rename_object_signal.slot(), *object));

            // If we're recording undo/redo data, record the new state ...
            if(m_state_recorder.current_change_set())
                  {
                        m_state_recorder.current_change_set()->record_old_state(new remove_objects_container(*this, objects));
                        m_state_recorder.current_change_set()->record_new_state(new add_objects_container(*this, objects));
                  }

            // Make the change and notify observers ...
            m_objects.insert(objects.begin(), objects.end());
            m_add_objects_signal.emit(objects);
      }

      const iobject_collection::objects_t& collection()
      {
            return m_objects;
      }

      void remove_objects(const objects_t& Objects)
      {
            // Ensure no NULLs creep in ...
            objects_t objects(Objects);
            if(objects.erase(static_cast<k3d::iobject*>(0)))
                  std::cerr << warning << "NULL object will be ignored" << std::endl;

            // If we're recording undo/redo data, record the new state ...
            if(m_state_recorder.current_change_set())
                  {
                        m_state_recorder.current_change_set()->record_old_state(new add_objects_container(*this, objects));
                        m_state_recorder.current_change_set()->record_new_state(new remove_objects_container(*this, objects));
                  }

            // Make the change and notify observers ...
            for(objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
                  {
                        (*object)->deleted_signal().emit();
                        m_objects.erase(*object);
                  }
            m_remove_objects_signal.emit(objects);

      }

      k3d::iobject::id_type next_available_id()
      {
            return ++m_largest_id;
      }

      add_objects_signal_t& add_objects_signal()
      {
            return m_add_objects_signal;
      }

      remove_objects_signal_t& remove_objects_signal()
      {
            return m_remove_objects_signal;
      }

      rename_object_signal_t& rename_object_signal()
      {
            return m_rename_object_signal;
      }

      void on_close_document()
      {
            // Give objects a chance to shut down ...
            for(k3d::iobject_collection::objects_t::iterator object = m_objects.begin(); object != m_objects.end(); ++object)
                  {
                        (*object)->deleted_signal().emit();
                  }

            // Zap objects ...
            for(k3d::iobject_collection::objects_t::iterator object = m_objects.begin(); object != m_objects.end(); ++object)
                  delete dynamic_cast<k3d::ideletable*>(*object);
      }

private:
      class add_objects_container :
            public k3d::istate_container
      {
      public:
            add_objects_container(k3d::iobject_collection& Collection, const k3d::iobject_collection::objects_t& Objects) :
                  m_collection(Collection),
                  m_objects(Objects)
            {
            }

            ~add_objects_container()
            {
            }

            void restore_state()
            {
                  m_collection.add_objects(m_objects);
            }

      private:
            k3d::iobject_collection& m_collection;
00451             const k3d::iobject_collection::objects_t m_objects;
      };

      class remove_objects_container :
            public k3d::istate_container
      {
      public:
            remove_objects_container(k3d::iobject_collection& Collection, const k3d::iobject_collection::objects_t& Objects) :
                  m_collection(Collection),
                  m_objects(Objects)
            {
            }

            ~remove_objects_container()
            {
            }

            void restore_state()
            {
                  m_collection.remove_objects(m_objects);
            }

      private:
            k3d::iobject_collection& m_collection;
            const k3d::iobject_collection::objects_t m_objects;
      };

      /// Provides storage for undo/redo information
00479       k3d::istate_recorder& m_state_recorder;
      /// Provides undo-able storage for a collection of objects
      k3d::iobject_collection::objects_t m_objects;
      /// Signal for notifying observers when objects are added to the collection
00483       add_objects_signal_t m_add_objects_signal;
      /// Signal for notifying observers when objects are removed from the collection
00485       remove_objects_signal_t m_remove_objects_signal;
      /// Signal for notifying observers when an object's name is changed
00487       rename_object_signal_t m_rename_object_signal;
      /// Caches the largest object ID encountered so-far, so we can generate unique IDs quickly
00489       k3d::iobject::id_type m_largest_id;
};

/////////////////////////////////////////////////////////////////////////////
// dag_implementation

class dag_implementation :
      public k3d::idag,
      public SigC::Object
{
public:
      dag_implementation(k3d::istate_recorder& StateRecorder) :
            m_state_recorder(StateRecorder)
      {
      }

      ~dag_implementation()
      {
      }

      void set_dependencies(dependencies_t& Dependencies)
      {
            // Don't let any NULLs creep in ...
            if(Dependencies.erase(static_cast<k3d::iproperty*>(0)))
                  std::cerr << warning << "Cannot assign a dependency to a NULL property" << std::endl;

            // Ensure there aren't any circular referances
            /** \todo Make this more robust */
            for(dependencies_t::iterator dependency = Dependencies.begin(); dependency != Dependencies.end(); ++dependency)
                  {
                        if(dependency->first == dependency->second)
                              dependency->second = 0;
                  }

            // If we're recording undo/redo data, record the new state ...
            if(m_state_recorder.current_change_set())
                  m_state_recorder.current_change_set()->record_new_state(new set_dependencies_container(*this, Dependencies));

            // Update our internal graph, keep track of the original state as we go ...
            dependencies_t old_dependencies;
            for(dependencies_t::iterator dependency = Dependencies.begin(); dependency != Dependencies.end(); ++dependency)
                  {
                        dependencies_t::iterator old_dependency = get_dependency(dependency->first);
                        old_dependencies.insert(*old_dependency);
                              
                        old_dependency->second = dependency->second;
                        
                        m_change_connections[dependency->first].disconnect();
                        if(dependency->second)
                              m_change_connections[dependency->first] = dependency->second->changed_signal().connect(dependency->first->changed_signal().slot());
                  }

            // If we're recording undo/redo data, keep track of the original state ...
            if(m_state_recorder.current_change_set())
                  m_state_recorder.current_change_set()->record_old_state(new set_dependencies_container(*this, old_dependencies));

            // Notify observers that the DAG as a whole has changed ...
            m_dependency_signal.emit(Dependencies);

            // Synthesize change notifications for every property whose parent was set ...
            for(dependencies_t::iterator dependency = Dependencies.begin(); dependency != Dependencies.end(); ++dependency)
                  dependency->first->changed_signal().emit();
      }

      k3d::iproperty* dependency(k3d::iproperty& Target)
      {
            return get_dependency(&Target)->second;
      }

      const dependencies_t& dependencies()
      {
            return m_dependencies;
      }

      dependency_signal_t& dependency_signal()
      {
            return m_dependency_signal;
      }

      void on_close_document()
      {
            // Since the document is closing anyway, close all our connections to avoid pointless thrashing-around ...
            for(connections_t::iterator connection = m_change_connections.begin(); connection != m_change_connections.end(); ++connection)
                  connection->second.disconnect();
                  
            for(connections_t::iterator connection = m_delete_connections.begin(); connection != m_delete_connections.end(); ++connection)
                  connection->second.disconnect();
      }

      void on_property_deleted(k3d::iproperty* Property)
      {
            dependencies_t::iterator dependency = m_dependencies.find(Property);
            return_if_fail(dependency != m_dependencies.end());
            
            if(m_state_recorder.current_change_set())
                  {
                        dependencies_t old_dependencies;
                        old_dependencies.insert(*dependency);
                        m_state_recorder.current_change_set()->record_old_state(new set_dependencies_container(*this, old_dependencies));
                        m_state_recorder.current_change_set()->record_new_state(new delete_property_container(*this, Property));
                  }

            m_dependencies.erase(dependency);
            
            m_delete_connections[Property].disconnect();
            m_delete_connections.erase(Property);

            dependencies_t new_dependencies;          
            for(dependencies_t::iterator dependency = m_dependencies.begin(); dependency != m_dependencies.end(); ++dependency)
                  {
                        if(dependency->second == Property)
                              new_dependencies.insert(std::make_pair(dependency->first, static_cast<k3d::iproperty*>(0)));
                  }

            if(new_dependencies.size())
                  set_dependencies(new_dependencies);             
      }

private:
      dependencies_t::iterator get_dependency(k3d::iproperty* Property)
      {
            assert(Property);

            dependencies_t::iterator result = m_dependencies.find(Property);
            if(result == m_dependencies.end())
                  {
                        result = m_dependencies.insert(std::make_pair(Property, static_cast<k3d::iproperty*>(0))).first;
                        m_delete_connections[Property] = Property->deleted_signal().connect(SigC::bind(SigC::slot(*this, &dag_implementation::on_property_deleted), Property));
                  }

            return result;
      }

      class set_dependencies_container :
            public k3d::istate_container
      {
      public:
            set_dependencies_container(k3d::idag& Dag, const k3d::idag::dependencies_t& Dependencies) :
                  m_dag(Dag),
                  m_dependencies(Dependencies)
            {
            }
            
            ~set_dependencies_container()
            {
            }
            
            void restore_state()
            {
                  m_dag.set_dependencies(m_dependencies);
            }
      
      private:
00642             k3d::idag& m_dag;
00643             k3d::idag::dependencies_t m_dependencies;
      };
      
      class delete_property_container :
            public k3d::istate_container
      {
      public:
            delete_property_container(dag_implementation& Dag, k3d::iproperty* const Property) :
                  m_dag(Dag),
                  m_property(Property)
            {
            }
            
            ~delete_property_container()
            {
            }
            
            void restore_state()
            {
                  m_dag.on_property_deleted(m_property);
            }
      
      private:
            dag_implementation& m_dag;
            k3d::iproperty* const m_property;
      };

      k3d::istate_recorder& m_state_recorder;
      
      /// Stores inter-property dependencies
      dependencies_t m_dependencies;

      /// Defines storage for per-property signal connections     
      typedef std::map<k3d::iproperty*, SigC::Connection> connections_t;
      /// Stores connections between property change signals (so a change to a source property is automatically passed-along to its dependent properties)
00678       connections_t m_change_connections;
      /// Stores connections to property delete signals (so we can clean-up when a property goes away)
00680       connections_t m_delete_connections;
      /// Signal that is emitted anytime the dependency graph is modified
00682       dependency_signal_t m_dependency_signal;
};

/////////////////////////////////////////////////////////////////////////////
// selection_implementation

/// Provides an implementation of k3d::iselection
class selection_implementation :
      public k3d::iselection
{
public:
      selected_signal_t& selected_signal()
      {
            return m_selected_signal;
      }

      deselected_signal_t& deselected_signal()
      {
            return m_deselected_signal;
      }

      deselect_all_signal_t& deselect_all_signal()
      {
            return m_deselect_all_signal;
      }

private:
      selected_signal_t m_selected_signal;
      deselected_signal_t m_deselected_signal;
      deselect_all_signal_t m_deselect_all_signal;
};

/////////////////////////////////////////////////////////////////////////////
// public_document_implementation

/// Encapsulates an open K-3D document
class public_document_implementation :
      public k3d::idocument,
      public k3d::command_node,
      public k3d::property_collection,
      public SigC::Object
{
public:
      public_document_implementation(k3d::istate_recorder& StateRecorder, k3d::iobject_collection& Objects, k3d::idag& Dag) :
            k3d::command_node("document"),
            property_collection(Dag),
            m_state_recorder(StateRecorder),
            m_objects(Objects),
            m_dag(Dag),
            m_path(k3d::init_name("path") + k3d::init_description("Document path [string]") + k3d::init_value(boost::filesystem::path()) + k3d::init_document(*this)),
            m_title(k3d::init_name("title") + k3d::init_description("Document title [string]") + k3d::init_value<std::string>("") + k3d::init_document(*this)),
            m_mouse_focus(k3d::init_value<k3d::imouse_event_observer*>(0))
      {
            // We want to be asked before closing the application
            k3d::application().safe_to_close_signal().connect(SigC::slot(*this, &public_document_implementation::safe_to_close_application));

            // We want to be notified as other documents come-and-go ...
            k3d::application().pre_create_document_signal().connect(SigC::slot(*this, &public_document_implementation::on_pre_create_document));
            k3d::application().close_document_signal().connect(SigC::slot(*this, &public_document_implementation::on_close_document));

            // Add ourselves to the command tree ...
            k3d::icommand_node* const parent = dynamic_cast<k3d::icommand_node*>(&k3d::application());
            return_if_fail(parent);
            k3d::application().command_tree().add_node(*this, *parent);

            // Register some properties ...
            register_property(m_path);
            register_property(m_title);

            k3d::application().new_document_signal().emit(*this);
      }

      ~public_document_implementation()
      {
            // Note: our close signal gets called by our owner - yes, it's a hack

            // Remove ourselves from the command tree ...
            k3d::application().command_tree().remove_node(*this);
      }

      // Create a new, default document from scratch ...
      bool create()
      {
            // Set our default title ...
            m_title.set_value("Untitled Document " + sdpToString(next_document_number()));

            k3d::viewport::redraw_all(*this, k3d::iviewport::ASYNCHRONOUS);

            return true;
      }

      // Open a document from disk ...
      bool open(const boost::filesystem::path& DocumentFile)
      {
            // Record our path & title ...
            m_path.set_value(DocumentFile);
            m_title.set_value(DocumentFile.leaf());

            // Load the document ...
            return_val_if_fail(load(DocumentFile), false);

            // Force a display update ...
            k3d::viewport::redraw_all(*this, k3d::iviewport::ASYNCHRONOUS);

            return true;
      }

      /////////////////////////////////////////////////////////////////////////////
      // k3d::icommand_node implementation

      bool execute_command(const std::string& Command, const std::string& Arguments)
      {
            return k3d::command_node::execute_command(Command, Arguments);
      }

      bool safe_to_close_application()
      {
            return m_safe_to_close_signal.emit();
      }

      void on_pre_create_document()
      {
            // This little bit of trickery ensures that documents always have a unique command node name ...
            set_command_node_name(command_node_name() + "*");
      }

      void on_close_document(k3d::idocument& Document)
      {
            // Don't do anything if it's us ...
            if(this == &Document)
                  return;

            // This little bit of trickery ensures that documents always have a unique command node name ...
            std::string name = command_node_name();
            if(name[name.size()-1] == '*')
                  set_command_node_name(name.substr(0, name.size() - 1));
      }


      k3d::iobject_collection& objects()
      {
            return m_objects;
      }

      k3d::idag& dag()
      {
            return m_dag;
      }

      k3d::iselection& selection()
      {
            return m_selection;
      }

      k3d::istate_recorder& state_recorder()
      {
            return m_state_recorder;
      }

      const boost::filesystem::path path()
      {
            return m_path.value();
      }

      const std::string title()
      {
            return m_title.value();
      }

      struct sort_by_id
      {
            bool operator()(k3d::iobject* LHS, k3d::iobject* RHS)
            {
                  return LHS->id() < RHS->id();
            }
      };

      bool save(const boost::filesystem::path& File)
      {
            // Try to open the file ...
            boost::filesystem::ofstream filestream(File);
            return_val_if_fail(filestream.good(), false);

            // Record our new path & title ...
            m_path.set_value(File);
            m_title.set_value(File.leaf());

            // Create our output document and dependencies objects ...
            sdpxml::Document xml("k3dml");
            sdpxml::SetAttribute(xml, sdpxml::Attribute("version", VERSION));
            k3d::dependencies dependencies;

            // The document itself ...
            sdpxml::Element& xml_document = xml.Append(sdpxml::Element("document"));

            // Save objects ...
            sdpxml::Element& xml_objects = xml_document.Append(sdpxml::Element("objects"));

            // Sort objects by ID before we save ...
            std::vector<k3d::iobject*> objects(m_objects.collection().begin(), m_objects.collection().end());
            std::sort(objects.begin(), objects.end(), sort_by_id());
            
            for(std::vector<k3d::iobject*>::iterator object = objects.begin(); object != objects.end(); ++object)
                  {
                        // Save the object if it supports persistence ...
                        k3d::ipersistent* const persist = dynamic_cast<k3d::ipersistent*>(*object);
                        if(!persist)
                              continue;

                        // Create an XML element for the object ...
                        sdpxml::Element& xml_object = xml_objects.Append(sdpxml::Element("object", "",
                              sdpxml::Attribute("name", (*object)->name()),
                              sdpxml::Attribute("class", sdpToString((*object)->factory().class_id())),
                              sdpxml::Attribute("id", sdpToString((*object)->id()))));
                        persist->save(xml_object, dependencies);
                  }

            // Save the DAG ...
            k3d::save_dag(*this, xml_document);

            // Save the XML ...
            filestream << xml << std::endl;
            return_val_if_fail(filestream.good(), false);

            // Record save position in the undo/redo stack
            state_recorder().mark_saved();

            return true;
      }

      void set_mouse_focus(k3d::imouse_event_observer* const Observer)
      {
            // Note - a NULL focus is allowed ...
            m_mouse_focus.set_value(Observer);
      }

      k3d::imouse_event_observer* mouse_focus()
      {
            return m_mouse_focus.value();
      }

      k3d::idocument::safe_to_close_signal_t& safe_to_close_signal()
      {
            return m_safe_to_close_signal;
      }

      k3d::idocument::close_signal_t& close_signal()
      {
            return m_close_signal;
      }

      k3d::idocument::title_signal_t& title_signal()
      {
            return m_title.changed_signal();
      }

      k3d::idocument::mouse_focus_signal_t& mouse_focus_signal()
      {
            return m_mouse_focus.changed_signal();
      }

private:
      /** \todo Get rid of this in 0.5 / 0.6 */
      typedef std::map<k3d::iobject::id_type, k3d::iobject::id_type> object_id_map_t;

      /** \brief Helper function for retrieving <variable> tags as a part of converting legacy documents
            \todo Get rid of this in 0.5 / 0.6
      */
      
      sdpxml::Element& get_variable(sdpxml::Element& XMLObject, const std::string Name)
      {
            return k3d::xml::safe_element(k3d::xml::safe_element(XMLObject, "variables"), sdpxml::Element("variable", "", sdpxml::Attribute("name", Name)));
      }

      /** \brief Helper function for renaming <variable> tags as a part of converting legacy documents
            \todo Get rid of this in 0.5 / 0.6
      */
      void rename_variable(sdpxml::Element& XMLObject, const std::string& OldName, const std::string& NewName)
      {
            sdpxml::Element* const xml_variables = sdpxml::FindElement(XMLObject, sdpxml::SameName("variables"));
            if(!xml_variables)
                  return;
                  
            for(sdpxml::ElementCollection::iterator xml_variable = xml_variables->Children().begin(); xml_variable != xml_variables->Children().end(); ++xml_variable)
                  {
                        if(xml_variable->Name() != "variable")
                              continue;
                              
                        if(OldName == sdpxml::GetAttribute(*xml_variable, "name", std::string()))
                              {
                                    sdpxml::SetAttribute(*xml_variable, sdpxml::Attribute("name", NewName));
                                    return;
                              }
                  }
      }

      void reconnect_channel(sdpxml::Element& XMLObject, const std::string& Property, sdpxml::ElementCollection& NewXMLDependencies, const std::string& Value)
      {
            NewXMLDependencies.push_back(
                  sdpxml::Element("dependency", "",
                        sdpxml::Attribute("from_object", sdpxml::GetAttribute(get_variable(XMLObject, Property), "value", std::string())),
                        sdpxml::Attribute("from_property", "output"),
                        sdpxml::Attribute("to_object", sdpxml::GetAttribute(XMLObject, "id", std::string())),
                        sdpxml::Attribute("to_property", Property)));

            sdpxml::SetAttribute(get_variable(XMLObject, Property), sdpxml::Attribute("value", Value));
      }

      /** \brief Helper functor for converting legacy hierarchy data into DAG dependencies
            \todo Get rid of this in 0.5 / 0.6
      */
      class add_legacy_dependencies
      {
      public:
            add_legacy_dependencies(sdpxml::Element& XMLParent, object_id_map_t& SourceObjectIDMap, object_id_map_t& TargetObjectIDMap, sdpxml::ElementCollection& NewXMLDependencies) :
                  parent_id(sdpxml::GetAttribute(XMLParent, "id", 0)),
                  source_object_id_map(SourceObjectIDMap),
                  target_object_id_map(TargetObjectIDMap),
                  new_xml_dependencies(NewXMLDependencies)
            {
            }
            
            void operator()(sdpxml::Element& XMLObject)
            {
                  if(XMLObject.Name() != "object")
                        return;

                  new_xml_dependencies.push_back(
                        sdpxml::Element("dependency", "",
                              sdpxml::Attribute("from_object", k3d::string_cast(source_object_id_map[parent_id])),
                              sdpxml::Attribute("from_property", "output_matrix"),
                              sdpxml::Attribute("to_object", k3d::string_cast(target_object_id_map[sdpxml::GetAttribute(XMLObject, "id", 0)])),
                              sdpxml::Attribute("to_property", "input_matrix")));

                  std::for_each(XMLObject.Children().begin(), XMLObject.Children().end(), add_legacy_dependencies(XMLObject, source_object_id_map, target_object_id_map, new_xml_dependencies));
            }
            
      private:
            const k3d::iobject::id_type parent_id;
            object_id_map_t& source_object_id_map;
            object_id_map_t& target_object_id_map;
            sdpxml::ElementCollection& new_xml_dependencies;
      };

      /** \brief Modifies XML as-needed so that both legacy and recent documents can be loaded with the same code
            \todo Get rid of this in 0.5 / 0.6
      */
      void upgrade_document(sdpxml::Element& XMLDocument)
      {
            // If this document doesn't contain the version of the program that wrote it, we consider it "ancient", which has an impact on how we map certain values to channels
//          const bool ancient_document = sdpxml::GetAttribute(XMLDocument, "version", std::string()).empty();
      
            sdpxml::Element& xml_objects = k3d::xml::safe_element(XMLDocument, "objects");
            sdpxml::Element& xml_dag = k3d::xml::safe_element(XMLDocument, "dag");

            // Keep track of any additional objects or dependencies we decide to add to the document ...
            sdpxml::ElementCollection new_xml_objects;
            sdpxml::ElementCollection new_xml_dependencies;

            // Store a mapping of original object IDs to (possibly) new object IDs for converting from the hierarchy to the DAG ...
            object_id_map_t source_object_id_map;
            object_id_map_t target_object_id_map;

            // Get the largest ID in the existing document, in case we need to generate any new unique IDs ...
            k3d::iobject::id_type largest_id = 0;
            for(sdpxml::ElementCollection::iterator xml_object = xml_objects.Children().begin(); xml_object != xml_objects.Children().end(); ++xml_object)
                  {
                        if(xml_object->Name() == "object")
                              largest_id = std::max(largest_id, sdpxml::GetAttribute(*xml_object, "id", k3d::iobject::id_type(0)));
                  }

            // Convert legacy document-time data into a TimeSource object ...
            std::string xml_time_source_id;
            sdpxml::ElementPointer const xml_time = sdpxml::FindElement(XMLDocument, sdpxml::SameName("time"));
            if(xml_time)
                  {
                        sdpxml::Element xml_time_source("object");
                        sdpxml::SetAttribute(xml_time_source, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::TimeSource())));
                        sdpxml::SetAttribute(xml_time_source, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                        sdpxml::SetAttribute(xml_time_source, sdpxml::Attribute("name", "TimeSource"));
                        xml_time_source.Children().insert(xml_time_source.Children().end(), xml_time->Children().begin(), xml_time->Children().end());
                        
                        rename_variable(xml_time_source, "starttime", "start_time");
                        rename_variable(xml_time_source, "endtime", "end_time");
                        rename_variable(xml_time_source, "framerate", "frame_rate");
                        rename_variable(xml_time_source, "viewtime", "time");
                        
                        new_xml_objects.push_back(xml_time_source);

                        xml_time_source_id = sdpxml::GetAttribute(xml_time_source, "id", std::string());
                  }

            // Look for specific older objects, and replace them with their newer counterparts ...
            for(sdpxml::ElementCollection::iterator xml_object = xml_objects.Children().begin(); xml_object != xml_objects.Children().end(); ++xml_object)
                  {
                        if(xml_object->Name() != "object")
                              continue;

                        // The <state> tag is a reliable way to identify legacy versions of objects
                        const bool legacy_object = sdpxml::FindElement(*xml_object, sdpxml::SameName("state"));
                        const k3d::uuid class_id = sdpxml::GetAttribute(*xml_object, "class", k3d::uuid::null());
                        const k3d::iobject::id_type object_id = sdpxml::GetAttribute(*xml_object, "id", 0);
                        const std::string name = sdpxml::GetAttribute(*xml_object, "name", std::string());

                        // By default, hierarchical relationships apply to the original object
                        source_object_id_map[object_id] = object_id;
                        target_object_id_map[object_id] = object_id;
                        
                        // Convert legacy mesh objects into FrozenMesh / MeshInstance pairs ...
                        if(class_id == k3d::uuid(0x00000001, 0x00000000, 0x00000000, 0x00000018))
                              {
                                    sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::FrozenMesh())));

                                    sdpxml::Element xml_mesh_instance(*xml_object);
                                    sdpxml::SetAttribute(xml_mesh_instance, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::MeshInstance())));
                                    sdpxml::SetAttribute(xml_mesh_instance, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                    sdpxml::SetAttribute(xml_mesh_instance, sdpxml::Attribute("name", name + " Instance"));
                                    
                                    new_xml_objects.push_back(xml_mesh_instance);
                                    new_xml_dependencies.push_back(sdpxml::Element("dependency", "", sdpxml::Attribute("from_object", k3d::string_cast(object_id)), sdpxml::Attribute("from_property", "output_mesh"), sdpxml::Attribute("to_object", k3d::string_cast(largest_id)), sdpxml::Attribute("to_property", "input_mesh")));

                                    // Any hierarchical relationships should be applied to the mesh instance, not the mesh ...
                                    source_object_id_map[object_id] = largest_id;
                                    target_object_id_map[object_id] = largest_id;
                                    
                                    std::cerr << warning << "Converting legacy Mesh object into a FrozenMesh / MeshInstance pair" << std::endl;
                              }
                        // Convert legacy camera objects into RenderManEngine / Viewport pairs ...
                        else if(class_id == k3d::uuid(0x00000001, 0x00000000, 0x00000000, 0x0000000d))
                              {
                                    sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::RenderManEngine())));

                                    sdpxml::Element xml_viewport(*xml_object);
                                    sdpxml::SetAttribute(xml_viewport, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::Viewport())));
                                    sdpxml::SetAttribute(xml_viewport, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                    sdpxml::SetAttribute(xml_viewport, sdpxml::Attribute("name", name + " Viewport"));
                                    k3d::xml::safe_element(xml_viewport, "variables").Append(sdpxml::Element("variable", "", sdpxml::Attribute("name", "viewport_host"), sdpxml::Attribute("value", k3d::string_cast(object_id))));

                                    rename_variable(xml_viewport, "editorfog", "fog");
                                    rename_variable(xml_viewport, "editorfognear", "fog_near");
                                    rename_variable(xml_viewport, "editorfogfar", "fog_far");
                                    rename_variable(xml_viewport, "editortwosided", "draw_two_sided");
                                    rename_variable(xml_viewport, "editororientation", "draw_face_orientations");
                                    rename_variable(xml_viewport, "editorfogfar", "fog_far");
                                    
                                    rename_variable(*xml_object, "riengine", "render_engine");
                                    rename_variable(*xml_object, "outputpixelwidth", "pixel_width");
                                    rename_variable(*xml_object, "outputpixelheight", "pixel_height");
                                    rename_variable(*xml_object, "outputpixelaspectratio", "pixel_aspect_ratio");
                                    rename_variable(*xml_object, "nearplane", "near");
                                    rename_variable(*xml_object, "farplane", "far");
                                    rename_variable(*xml_object, "rixbucketsize", "bucket_width");
                                    rename_variable(*xml_object, "riybucketsize", "bucket_height");
                                    rename_variable(*xml_object, "rigridsize", "grid_size");
                                    rename_variable(*xml_object, "rieyesplits", "eye_splits");
                                    rename_variable(*xml_object, "ritexturememory", "texture_memory");
                                    rename_variable(*xml_object, "transparentbackground", "render_alpha");
                                    rename_variable(*xml_object, "rixsamples", "pixel_xsamples");
                                    rename_variable(*xml_object, "riysamples", "pixel_ysamples");
                                    rename_variable(*xml_object, "ripixelfiltername", "pixel_filter");
                                    rename_variable(*xml_object, "ripixelfilterwidth", "pixel_filter_width");
                                    rename_variable(*xml_object, "ripixelfilterheight", "pixel_filter_height");
                                    rename_variable(*xml_object, "ridepthoffield", "render_dof");
                                    rename_variable(*xml_object, "focallength", "focal_length");
                                    rename_variable(*xml_object, "focalplane", "focus_plane");
                                    rename_variable(*xml_object, "rishadingrate", "shading_rate");
                                    rename_variable(*xml_object, "rishadinginterpolation", "shading_interpolation");
                                    rename_variable(*xml_object, "ritwosided", "two_sided");
                                    rename_variable(*xml_object, "ricropwindowleft", "crop_window_left");
                                    rename_variable(*xml_object, "ricropwindowright", "crop_window_right");
                                    rename_variable(*xml_object, "ricropwindowtop", "crop_window_top");
                                    rename_variable(*xml_object, "ricropwindowbottom", "crop_window_bottom");
                                    rename_variable(*xml_object, "rimotionblur", "render_motion_blur");

                                    reconnect_channel(*xml_object, "focal_length", new_xml_dependencies, "35");
                                    reconnect_channel(*xml_object, "focal_plane", new_xml_dependencies, "35");
                                    reconnect_channel(*xml_object, "fstop", new_xml_dependencies, "1.8");
                                    reconnect_channel(*xml_object, "exposure", new_xml_dependencies, "1");
                                    reconnect_channel(*xml_object, "gamma", new_xml_dependencies, "1");
                                    reconnect_channel(*xml_object, "far", new_xml_dependencies, "1");
                                    reconnect_channel(*xml_object, "near", new_xml_dependencies, "10000");

                                    // Get rid of any viewport transformations
                                    xml_viewport.Children().erase(std::remove_if(xml_viewport.Children().begin(), xml_viewport.Children().end(), sdpxml::SameName("transformation")), xml_viewport.Children().end());

                                    // Set the render engine clipping frustum explicitly (used to be implicit)
                                    const double pixel_width = sdpxml::GetAttribute(get_variable(*xml_object, "pixel_width"), "value", 0.0);
                                    const double pixel_height = sdpxml::GetAttribute(get_variable(*xml_object, "pixel_height"), "value", 0.0);
                                    const double pixel_aspect_ratio = sdpxml::GetAttribute(get_variable(*xml_object, "pixel_aspect_ratio"), "value", 0.0);
                                    if(pixel_height)
                                          {
                                                const double world_aspect_ratio = (pixel_width / pixel_height) * pixel_aspect_ratio;
                                                const double perspective_focal_length = sdpxml::GetAttribute(get_variable(*xml_object, "focal_length"), "value", 0.0);
                                                if(perspective_focal_length)
                                                      {
                                                            sdpxml::SetAttribute(get_variable(*xml_object, "left"), sdpxml::Attribute("value", k3d::string_cast(-0.5 * world_aspect_ratio / perspective_focal_length)));
                                                            sdpxml::SetAttribute(get_variable(*xml_object, "right"), sdpxml::Attribute("value", k3d::string_cast(0.5 * world_aspect_ratio / perspective_focal_length)));
                                                            sdpxml::SetAttribute(get_variable(*xml_object, "top"), sdpxml::Attribute("value", k3d::string_cast(0.5 / perspective_focal_length)));
                                                            sdpxml::SetAttribute(get_variable(*xml_object, "bottom"), sdpxml::Attribute("value", k3d::string_cast(-0.5 / perspective_focal_length)));
                                                            sdpxml::SetAttribute(get_variable(*xml_object, "near"), sdpxml::Attribute("value", "1.0"));
                                                      }
                                          }
                                                                                                            
                                    new_xml_objects.push_back(xml_viewport);
                                    new_xml_dependencies.push_back(sdpxml::Element("dependency", "", sdpxml::Attribute("from_object", k3d::string_cast(object_id)), sdpxml::Attribute("from_property", "output_matrix"), sdpxml::Attribute("to_object", k3d::string_cast(largest_id)), sdpxml::Attribute("to_property", "input_matrix")));
                                                                        
                                    std::cerr << warning << "Converting legacy Camera object into a RenderManEngine / Viewport pair" << std::endl;
                              }
                        // Convert legacy transformer objects into Transform objects ...
                        else if(class_id == k3d::uuid(0x00000001, 0x00000000, 0x00000000, 0x00000040))
                              {
                                    sdpxml::Element xml_scale(*xml_object);
                                    sdpxml::SetAttribute(xml_scale, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::Scale())));
                                    sdpxml::SetAttribute(xml_scale, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                    sdpxml::SetAttribute(xml_scale, sdpxml::Attribute("name", name + " Scale"));
                                    rename_variable(xml_scale, "xscale", "x");
                                    rename_variable(xml_scale, "yscale", "y");
                                    rename_variable(xml_scale, "zscale", "z");
                                    reconnect_channel(xml_scale, "x", new_xml_dependencies, "1");
                                    reconnect_channel(xml_scale, "y", new_xml_dependencies, "1");
                                    reconnect_channel(xml_scale, "z", new_xml_dependencies, "1");

                                    target_object_id_map[object_id] = largest_id;

                                    sdpxml::Element xml_orientation(*xml_object);
                                    sdpxml::SetAttribute(xml_orientation, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::Orientation())));
                                    sdpxml::SetAttribute(xml_orientation, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                    sdpxml::SetAttribute(xml_orientation, sdpxml::Attribute("name", name + " Orientation"));
                                    rename_variable(xml_orientation, "xrotation", "x");
                                    rename_variable(xml_orientation, "yrotation", "y");
                                    rename_variable(xml_orientation, "zrotation", "z");
                                    reconnect_channel(xml_orientation, "x", new_xml_dependencies, "0");
                                    reconnect_channel(xml_orientation, "y", new_xml_dependencies, "0");
                                    reconnect_channel(xml_orientation, "z", new_xml_dependencies, "0");

                                    sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::Position())));
                                    sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("name", name + " Position"));
                                    rename_variable(*xml_object, "xoffset", "x");
                                    rename_variable(*xml_object, "yoffset", "y");
                                    rename_variable(*xml_object, "zoffset", "z");
                                    reconnect_channel(*xml_object, "x", new_xml_dependencies, "0");
                                    reconnect_channel(*xml_object, "y", new_xml_dependencies, "0");
                                    reconnect_channel(*xml_object, "z", new_xml_dependencies, "0");
                              
                                    new_xml_objects.push_back(xml_orientation);
                                    new_xml_objects.push_back(xml_scale);

                                    new_xml_dependencies.push_back(
                                          sdpxml::Element("dependency", "",
                                          sdpxml::Attribute("from_object", sdpxml::GetAttribute(xml_scale, "id", std::string())),
                                          sdpxml::Attribute("from_property", "output_matrix"),
                                          sdpxml::Attribute("to_object", sdpxml::GetAttribute(xml_orientation, "id", std::string())),
                                          sdpxml::Attribute("to_property", "input_matrix")));

                                    new_xml_dependencies.push_back(
                                          sdpxml::Element("dependency", "",
                                          sdpxml::Attribute("from_object", sdpxml::GetAttribute(xml_orientation, "id", std::string())),
                                          sdpxml::Attribute("from_property", "output_matrix"),
                                          sdpxml::Attribute("to_object", sdpxml::GetAttribute(*xml_object, "id", std::string())),
                                          sdpxml::Attribute("to_property", "input_matrix")));

                                    std::cerr << warning << "Converting legacy Transformer object into a Position / Orientation / Scale triplet" << std::endl;
                              }
                        // Convert legacy channel objects into ScalarBezierChannel and ColorBezierChannel objects ...
                        else if(class_id == k3d::uuid(0x00000001, 0x00000000, 0x00000000, 0x0000000e))
                              {
                                    const std::string datatype = sdpxml::GetAttribute(get_variable(*xml_object, "datatype"), "value", std::string());

                                    if(datatype == "color")
                                          {
                                                sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::ColorBezierChannel())));
                                                std::cerr << warning << "Converting legacy Channel object into a ColorBezierChannel object" << std::endl;
                                          }
                                    else
                                          {
                                                sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::ScalarBezierChannel())));
                                                std::cerr << warning << "Converting legacy Channel object into a ScalarBezierChannel object" << std::endl;
                                          }
                                    
                                    new_xml_dependencies.push_back(
                                          sdpxml::Element("dependency", "",
                                          sdpxml::Attribute("from_object", xml_time_source_id),
                                          sdpxml::Attribute("from_property", "time"),
                                          sdpxml::Attribute("to_object", sdpxml::GetAttribute(*xml_object, "id", std::string())),
                                          sdpxml::Attribute("to_property", "input")));
                              }
                        // Update legacy FileBitmap objects ...
                        else if(class_id == k3d::classes::FileBitmap())
                              {
                                    rename_variable(*xml_object, "imagepath", "file");
                              }
                        // Update RenderManMaterial objects ...
                        else if(class_id == k3d::classes::RenderManMaterial() && legacy_object)
                              {
                                    reconnect_channel(*xml_object, "color", new_xml_dependencies, "1 1 1");
                                    reconnect_channel(*xml_object, "opacity", new_xml_dependencies, "1 1 1");
                              }
                        // Ignore old-fashioned FractalTerrain objects ...
                        else if(class_id == k3d::uuid(0x00000001, 0x00000000, 0x00000000, 0x00000051))
                              {
                                    sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("do_not_load", "true"));
                                    std::cerr << warning << "Legacy FractalTerrain object will not be loaded" << std::endl;
                              }
                        // Convert old-fashioned Fog objects into RenderManVolumeShader objects ...
                        else if(class_id == k3d::uuid(0xeefa8085, 0xac3146a0, 0xaff19aac, 0x4a2b5a5d))
                              {
                                    sdpxml::SetAttribute(*xml_object, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::RenderManVolumeShader())));
                                    
                                    const std::string shader_name = sdpxml::GetAttribute(get_variable(*xml_object, "shader"), "value", std::string());
                                    xml_object->Children() = get_variable(*xml_object, "shader").Children();
                                    sdpxml::SetAttribute(get_variable(*xml_object, "shader_name"), sdpxml::Attribute("value", shader_name));

                                    std::cerr << warning << "Converting legacy Fog object into a RenderManVolumeShader - manually reconnect it" << std::endl;
                              }
                        // Handle updates to quadric primitivies ...
                        else if(
                                    class_id == k3d::classes::Cone() ||
                                    class_id == k3d::classes::Cylinder() ||
                                    class_id == k3d::classes::Disk() ||
                                    class_id == k3d::classes::Hyperboloid() ||
                                    class_id == k3d::classes::Paraboloid() ||
                                    class_id == k3d::classes::Sphere() ||
                                    class_id == k3d::classes::Torus())
                              {
                                    rename_variable(*xml_object, "sweepangle", "thetamax");
                                    
                                    if(class_id == k3d::classes::Paraboloid())
                                          {
                                                rename_variable(*xml_object, "height", "zmax");
                                          }
                              }
                                          
                        // Create separate shader objects when we run across embedded shader information ...
                        else if(class_id == k3d::classes::RenderManLight())
                              {
                                    sdpxml::Element& shader = get_variable(*xml_object, "shader");
                                    if(shader.Children().size())
                                          {
                                                const std::string shader_name = sdpxml::GetAttribute(get_variable(*xml_object, "shader"), "value", std::string());
                                          
                                                sdpxml::Element xml_shader("object");
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::RenderManLightShader())));
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("name", name + " Shader"));
                                                
                                                shader.Children().swap(xml_shader.Children());
                                                sdpxml::SetAttribute(get_variable(xml_shader, "shader_name"), sdpxml::Attribute("value", shader_name));
                                                
                                                new_xml_objects.push_back(xml_shader);

                                                sdpxml::SetAttribute(get_variable(*xml_object, "shader"), sdpxml::Attribute("value", k3d::string_cast(largest_id)));
                                                                                                                                                
                                                std::cerr << warning << "Creating a RenderManLightShader object from a legacy RenderManLight" << std::endl;
                                          }
                              }
                        else if(class_id == k3d::classes::RenderManMaterial())
                              {
                                    sdpxml::Element& surface_shader = get_variable(*xml_object, "surface");
                                    if(surface_shader.Children().size())
                                          {
                                                const std::string shader_name = sdpxml::GetAttribute(get_variable(*xml_object, "surface"), "value", std::string());
                                          
                                                sdpxml::Element xml_shader("object");
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::RenderManSurfaceShader())));
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("name", name + " Shader"));
                                                
                                                surface_shader.Children().swap(xml_shader.Children());
                                                sdpxml::SetAttribute(get_variable(xml_shader, "shader_name"), sdpxml::Attribute("value", shader_name));
                                                
                                                new_xml_objects.push_back(xml_shader);

                                                sdpxml::SetAttribute(get_variable(*xml_object, "surface_shader"), sdpxml::Attribute("value", k3d::string_cast(largest_id)));
                                                                                                                                                
                                                std::cerr << warning << "Creating a RenderManSurfaceShader object from a legacy RenderManMaterial" << std::endl;
                                          }
                                          
                                    sdpxml::Element& displacement_shader = get_variable(*xml_object, "displacement");
                                    if(displacement_shader.Children().size())
                                          {
                                                const std::string shader_name = sdpxml::GetAttribute(get_variable(*xml_object, "displacement"), "value", std::string());
                                          
                                                sdpxml::Element xml_shader("object");
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("class", k3d::string_cast(k3d::classes::RenderManDisplacementShader())));
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("id", k3d::string_cast(++largest_id)));
                                                sdpxml::SetAttribute(xml_shader, sdpxml::Attribute("name", name + " Displacement Shader"));
                                                
                                                displacement_shader.Children().swap(xml_shader.Children());
                                                sdpxml::SetAttribute(get_variable(xml_shader, "shader_name"), sdpxml::Attribute("value", shader_name));
                                                
                                                new_xml_objects.push_back(xml_shader);

                                                sdpxml::SetAttribute(get_variable(*xml_object, "displacement_shader"), sdpxml::Attribute("value", k3d::string_cast(largest_id)));
                                                                                                                                                
                                                std::cerr << warning << "Creating a RenderManDisplacementShader object from a legacy RenderManMaterial" << std::endl;
                                          }
                              }
                  }

            // Convert the hierarchy into DAG dependencies ...
            sdpxml::Element* const xml_hierarchy = sdpxml::FindElement(XMLDocument, sdpxml::SameName("hierarchy"));
            if(xml_hierarchy)
                  {
                        std::cerr << warning << "Converting legacy Hierarchy into DAG dependencies ... may generate harmless downstream errors" << std::endl;
                  
                        for(sdpxml::ElementCollection::iterator xml_object = xml_hierarchy->Children().begin(); xml_object != xml_hierarchy->Children().end(); ++xml_object)
                              {
                                    if(xml_object->Name() != "object")
                                          continue;
                                          
                                    std::for_each(xml_object->Children().begin(), xml_object->Children().end(), add_legacy_dependencies(*xml_object, source_object_id_map, target_object_id_map, new_xml_dependencies));
                              }
                  }

            // Add new objects to the document ...
            xml_objects.Children().insert(xml_objects.Children().end(), new_xml_objects.begin(), new_xml_objects.end());
            xml_dag.Children().insert(xml_dag.Children().end(), new_xml_dependencies.begin(), new_xml_dependencies.end());
      }

      bool load(const boost::filesystem::path DocumentPath)
      {
            // Sanity checks ...
            return_val_if_fail(!DocumentPath.empty(), false);

            // Try loading the file as XML ...
            sdpxml::Document xml("k3dml");
            boost::filesystem::ifstream stream(DocumentPath);
            if(!xml.Load(stream, DocumentPath.native_file_string()))
                  return false;

            // Make sure it's a K3D document ...
            if(xml.Name() != "k3dml")
                  return false;

            // Look for a document version ...
            std::stringstream version(sdpxml::GetAttribute(xml, "version", std::string()));
            unsigned long major_version = 0;
            unsigned long minor_version = 0;
            unsigned long revision = 0;
            unsigned long build = 0;
            char point;
            version >> major_version >> point >> minor_version >> point >> revision >> point >> build;

            // Look for the document element ...
            sdpxml::ElementPointer const xml_document = sdpxml::FindElement(xml, sdpxml::SameName("document"));
            if(xml_document)
                  {
                        // Handle documents from older versions of the software by modifying the document ...
                        upgrade_document(*xml_document);

std::ofstream temp("/tmp/converted.xml");
temp << *xml_document << std::endl;

                        // Load objects
                        k3d::iobject_collection::objects_t objects;
                        sdpxml::ElementPointer const xml_objects = sdpxml::FindElement(*xml_document, sdpxml::SameName("objects"));
                        if(xml_objects)
                              {
                                    for(sdpxml::ElementCollection::iterator xml_object = xml_objects->Children().begin(); xml_object != xml_objects->Children().end(); ++xml_object)
                                          {
                                                if(xml_object->Name() != "object")
                                                      continue;

                                                if(sdpxml::GetAttribute(*xml_object, "do_not_load", false))
                                                      continue;

                                                const std::string name = sdpxml::GetAttribute(*xml_object, "name", std::string());
                                                const k3d::uuid class_id = sdpxml::GetAttribute(*xml_object, "class", k3d::uuid::null());
                                                if(class_id == k3d::uuid::null())
                                                      {
                                                            std::cerr << error << "Object [" << name << "] with unspecified class ID will not be loaded" << std::endl;
                                                            continue;
                                                      }

                                                const k3d::iobject::id_type object_id = sdpxml::GetAttribute(*xml_object, "id", 0);
                                                if(object_id == 0)
                                                      {
                                                            std::cerr << error << "Object [" << name << "] with unspecified ID will not be loaded" << std::endl;
                                                            continue;
                                                      }

                                                k3d::iplugin_factory* const plugin_factory = k3d::plugin(class_id);
                                                if(!plugin_factory)
                                                      {
                                                            std::cerr << error << "Object [" << name << "] with unknown class ID [" << class_id << "] will not be loaded" << std::endl;
                                                            continue;
                                                      }

                                                k3d::idocument_plugin_factory* const document_plugin_factory = dynamic_cast<k3d::idocument_plugin_factory*>(plugin_factory);
                                                if(!document_plugin_factory)
                                                      {
                                                            std::cerr << error << "Non-document object [" << name << "] will not be loaded" << std::endl;
                                                            continue;
                                                      }
                                                      
                                                k3d::iobject* const object = document_plugin_factory->create_plugin(*this);
                                                if(!object)
                                                      {
                                                            std::cerr << error << "Error creating object [" << name << "] instance" << std::endl;
                                                            continue;
                                                      }

                                                k3d::ipersistent* const persist = dynamic_cast<k3d::ipersistent*>(object);
                                                if(!persist)
                                                      {
                                                            std::cerr << error << "Error loading object [" << name << "]" << std::endl;

                                                            delete dynamic_cast<k3d::ideletable*>(object);
                                                            continue;
                                                      }

                                                k3d::undoable_new(dynamic_cast<k3d::ideletable*>(object), *this);

                                                persist->load(xml, *xml_object);
                                                object->set_id(object_id);
                                                objects.insert(object);
                                          }

                                    m_objects.add_objects(objects);
                              }

                        // Load the DAG ...
                        k3d::load_dag(*this, *xml_document);

                        // Let all our objects know we're done loading ...
                        for(k3d::iobject_collection::objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
                              {
                                    k3d::ipersistent* const persist = dynamic_cast<k3d::ipersistent*>(*object);
                                    if(persist)
                                          persist->load_complete();
                              }
                  }

            return true;
      }

      /// Used to find-out whether it's safe to close the document
01519       safe_to_close_signal_t m_safe_to_close_signal;
      /// Notifies observers that the document is being closed
01521       close_signal_t m_close_signal;

      /// Records changes made by the user for Undo / Redo purposes
      k3d::istate_recorder& m_state_recorder;
      /// Stores document objects ...
      k3d::iobject_collection& m_objects;
      /// Stores a reference to an implementation of idag
      k3d::idag& m_dag;
      /// Stores the set of selected objects
01530       selection_implementation m_selection;

      /// Stores the full document filepath
      k3d_data_property(boost::filesystem::path, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_path;
      /// Stores the document title
      k3d_data_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_title;
      /// Keeps track of which object (if any) has grabbed the mouse focus
      k3d_data(k3d::imouse_event_observer*, k3d::no_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_mouse_focus;
};

/// This is a real abortion, but it solves our interdependency problems among state recorder, dag, and property collection implementations
class document_implementation
{
public:
      document_implementation() :
            m_state_recorder(new state_recorder_implementation()),
            m_objects(new object_collection_implementation(*m_state_recorder)),
            m_dag(new dag_implementation(*m_state_recorder)),
            m_document(new public_document_implementation(*m_state_recorder, *m_objects, *m_dag))
      {
      }

      ~document_implementation()
      {
            m_document->close_signal().emit();
            
            m_dag->on_close_document();
            m_objects->on_close_document();

            delete m_document;
            delete m_dag;
            delete m_objects;
            delete m_state_recorder;
      }

      state_recorder_implementation* const m_state_recorder;
      object_collection_implementation* const m_objects;
      dag_implementation* const m_dag;
      public_document_implementation* const m_document;

private:
      document_implementation(const document_implementation&);
      document_implementation& operator=(const document_implementation&);
};

typedef std::vector<document_implementation*> documents_t;

documents_t& documents()
{
      static documents_t g_documents;
      return g_documents;
}

} // namespace

namespace k3d
{

01588 idocument* create_document()
{
      std::auto_ptr<document_implementation> document(new document_implementation());
      if(!document->m_document->create())
            return 0;

      documents().push_back(document.get());
      return document.release()->m_document;
}

01598 idocument* open_document(const boost::filesystem::path& DocumentFile)
{
      std::auto_ptr<document_implementation> document(new document_implementation());
      if(!document->m_document->open(DocumentFile))
            return 0;

      documents().push_back(document.get());
      return document.release()->m_document;
}

01608 void close_document(idocument& Document)
{
      for(documents_t::iterator document = documents().begin(); document != documents().end(); ++document)
            {
                  if((*document)->m_document == &Document)
                        {
                              delete *document;
                              documents().erase(document);

                              return;
                        }
            }

      std::cerr << error << "close_document(): could not find document to destroy" << std::endl;
}

} // namespace k3d


Generated by  Doxygen 1.6.0   Back to index