mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-01 11:36:09 +08:00
1280 lines
31 KiB
C++
1280 lines
31 KiB
C++
//
|
|
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
|
|
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
|
|
// McNeel & Associates.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
|
|
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
|
|
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
|
|
//
|
|
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
#include "opennurbs.h"
|
|
|
|
#if !defined(ON_COMPILING_OPENNURBS)
|
|
// This check is included in all opennurbs source .c and .cpp files to insure
|
|
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
|
|
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
|
|
// and the opennurbs .h files alter what is declared and how it is declared.
|
|
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
|
|
#endif
|
|
|
|
ON_VIRTUAL_OBJECT_IMPLEMENT(ON_UserData,ON_Object,"850324A7-050E-11d4-BFFA-0010830122F0");
|
|
|
|
ON_UserData::ON_UserData()
|
|
: m_userdata_uuid(ON_nil_uuid),
|
|
m_application_uuid(ON_nil_uuid),
|
|
m_userdata_copycount(0),
|
|
m_userdata_xform(ON_Xform::IdentityTransformation),
|
|
m_userdata_owner(0),
|
|
m_userdata_next(0)
|
|
{}
|
|
|
|
ON_UserData::ON_UserData(const ON_UserData& src)
|
|
: ON_Object(src),
|
|
m_userdata_uuid(src.m_userdata_uuid),
|
|
m_application_uuid(src.m_application_uuid),
|
|
m_userdata_copycount(src.m_userdata_copycount),
|
|
m_userdata_xform(src.m_userdata_xform),
|
|
m_userdata_owner(0), // do not copy owner
|
|
m_userdata_next(0) // do not copy next
|
|
{
|
|
if ( m_userdata_copycount)
|
|
{
|
|
m_userdata_copycount++;
|
|
if ( !m_userdata_copycount )
|
|
m_userdata_copycount = 1;
|
|
}
|
|
}
|
|
|
|
//virtual
|
|
bool ON_UserData::Archive() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//virtual
|
|
bool ON_UserData::WriteToArchive(
|
|
const class ON_BinaryArchive& archive,
|
|
const class ON_Object* parent_object
|
|
) const
|
|
{
|
|
return Archive() ? true : false;
|
|
}
|
|
|
|
// virtual
|
|
bool ON_UserData::DeleteAfterWrite(
|
|
const ON_BinaryArchive& archive,
|
|
const ON_Object* parent_object
|
|
) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// virtual
|
|
bool ON_UserData::DeleteAfterRead(
|
|
const ON_BinaryArchive& archive,
|
|
ON_Object* parent_object
|
|
) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//virtual
|
|
bool ON_UserData::Transform(const ON_Xform& x )
|
|
{
|
|
m_userdata_xform = x*m_userdata_xform;
|
|
return true;
|
|
}
|
|
|
|
ON_UserData& ON_UserData::operator=(const ON_UserData& src)
|
|
{
|
|
// 16 January 2004 Dale Lear
|
|
// Do not copy the m_userdata_uuid, m_application_uuid,
|
|
// m_userdata_owner, or m_userdata_next values.
|
|
// The m_userdata_uuid and m_application_uuid are
|
|
// set when the class is constructed and should not be
|
|
// changed. The m_userdata_owner and m_userdata_next
|
|
// values are set when the user data is attached
|
|
// to a parent object.
|
|
if ( this != &src )
|
|
{
|
|
ON_Object::operator=(src);
|
|
m_userdata_copycount = src.m_userdata_copycount;
|
|
m_userdata_xform = src.m_userdata_xform;
|
|
if ( 0 != m_userdata_copycount )
|
|
{
|
|
m_userdata_copycount++;
|
|
if ( !m_userdata_copycount )
|
|
m_userdata_copycount = 1;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
ON_UserData::~ON_UserData()
|
|
{
|
|
if ( 0 != m_userdata_owner )
|
|
{
|
|
// remove this piece of user data from owner->m_userdata_list
|
|
m_userdata_owner->DetachUserData(this);
|
|
m_userdata_owner = 0;
|
|
}
|
|
}
|
|
|
|
void ON_UserData::Dump( ON_TextLog& text_log ) const
|
|
{
|
|
text_log.Print("User Data:\n");
|
|
text_log.PushIndent();
|
|
|
|
// print class name and class uuid
|
|
ON_Object::Dump(text_log);
|
|
|
|
// developer's user data description
|
|
ON_wString description;
|
|
const_cast<ON_UserData*>(this)->GetDescription(description);
|
|
if ( description.IsEmpty() )
|
|
description = L"none";
|
|
const wchar_t* ws = static_cast< const wchar_t* >(description);
|
|
text_log.Print("user data description: %ls\n",ws);
|
|
text_log.Print("user data uuid: ");
|
|
text_log.Print(m_userdata_uuid);
|
|
text_log.Print("\n");
|
|
text_log.Print("user data copy count: %d\n",this->m_userdata_copycount);
|
|
|
|
// archive setting
|
|
text_log.Print("user data saved in 3dm archive: %s\n",Archive() ? "yes" : "no");
|
|
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
unsigned int ON_UserData::SizeOf() const
|
|
{
|
|
unsigned int sz = ON_Object::SizeOf();
|
|
sz += (sizeof(*this) - sizeof(ON_Object));
|
|
return sz;
|
|
}
|
|
|
|
bool ON_UserData::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
if ( 0 == ON_UuidCompare( &m_userdata_uuid, &ON_nil_uuid ) )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("invalid userdata - m_userdata_uuid = nil\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ( 0 == ON_UuidCompare( m_userdata_uuid, ON_UserData::ClassId()->Uuid() ) )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("invalid userdata - m_userdata_uuid in use. Use guidgen to get a unique id.\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ( Archive() && 0 == ON_UuidCompare( ClassId()->Uuid(), ON_UserData::ClassId()->Uuid() ) )
|
|
{
|
|
// 8 January 2004 Dale Lear:
|
|
// I added this test to help developers remember to use
|
|
// the ON_DECLARE_OBJECT/ON_IMPLEMENT_OBJECT macros when
|
|
// they create user data that gets archived.
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("invalid userdata - classes derived from ON_UserData that get saved in 3dm archives must have a class id and name defined by ON_OBJECT_DECLARE/ON_OBJECT_IMPLEMENT.\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ON_Object* ON_UserData::Owner() const
|
|
{
|
|
return m_userdata_owner;
|
|
}
|
|
|
|
ON_UserData* ON_UserData::Next() const
|
|
{
|
|
return m_userdata_next;
|
|
}
|
|
|
|
ON_UUID ON_UserData::UserDataClassUuid() const
|
|
{
|
|
const ON_ClassId* this_rtti = ClassId();
|
|
|
|
if (this_rtti == &ON_CLASS_RTTI(ON_UnknownUserData))
|
|
return ((ON_UnknownUserData*)this)->m_unknownclass_uuid;
|
|
|
|
if (this_rtti == &ON_CLASS_RTTI(ON_ObsoleteUserData))
|
|
return ((ON_ObsoleteUserData*)this)->m_archive_class_uuid;
|
|
|
|
return this_rtti->Uuid();
|
|
}
|
|
|
|
bool ON_UserData::IsUnknownUserData() const
|
|
{
|
|
return (ClassId() == &ON_CLASS_RTTI(ON_UnknownUserData))?true:false;
|
|
}
|
|
|
|
bool ON_UserData::GetDescription( ON_wString& description )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_ObsoleteUserData, ON_UserData, "7C9305E5-947A-4E46-B102-8016849FABB1");
|
|
|
|
ON_ObsoleteUserData::ON_ObsoleteUserData()
|
|
: m_archive_class_uuid(ON_nil_uuid)
|
|
{}
|
|
|
|
ON_ObsoleteUserData::~ON_ObsoleteUserData()
|
|
{}
|
|
|
|
ON_ObsoleteUserData::ON_ObsoleteUserData(const ON_ObsoleteUserData& src)
|
|
: ON_UserData(src)
|
|
, m_archive_class_uuid(ON_nil_uuid)
|
|
{}
|
|
|
|
ON_ObsoleteUserData& ON_ObsoleteUserData::operator=(const ON_ObsoleteUserData& src)
|
|
{
|
|
if (this != &src)
|
|
{
|
|
ON_UserData::operator=(src);
|
|
m_archive_class_uuid = src.m_archive_class_uuid;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_UnknownUserData,ON_UserData,"850324A8-050E-11d4-BFFA-0010830122F0");
|
|
|
|
ON_UnknownUserData::ON_UnknownUserData()
|
|
: m_unknownclass_uuid(ON_nil_uuid)
|
|
, m_sizeof_buffer(0)
|
|
, m_buffer(0)
|
|
, m_3dm_version(0)
|
|
, m_3dm_opennurbs_version_number(0)
|
|
{}
|
|
|
|
ON_UnknownUserData::ON_UnknownUserData(const ON_UnknownUserData& src)
|
|
: ON_UserData(src)
|
|
, m_unknownclass_uuid(ON_nil_uuid)
|
|
, m_sizeof_buffer(0)
|
|
, m_buffer(0)
|
|
, m_3dm_version(0)
|
|
, m_3dm_opennurbs_version_number(0)
|
|
{
|
|
if ( m_userdata_copycount > 0 && src.m_sizeof_buffer > 0 && src.m_buffer )
|
|
{
|
|
// For most kinds of user data except ON_UnknownUserData,
|
|
// m_userdata_uuid is set by the constructor and should not
|
|
// be copied from src (which may be a derived class). However,
|
|
// for ON_UnknownUserData, the value of m_userdata_uuid is
|
|
// varies because it is set by the missing userdata class.
|
|
// So it has to be copied here.
|
|
m_userdata_uuid = src.m_userdata_uuid;
|
|
|
|
m_unknownclass_uuid = src.m_unknownclass_uuid;
|
|
m_sizeof_buffer = src.m_sizeof_buffer;
|
|
m_buffer = onmemdup( src.m_buffer, src.m_sizeof_buffer);
|
|
m_3dm_version = src.m_3dm_version;
|
|
m_3dm_opennurbs_version_number = src.m_3dm_opennurbs_version_number;
|
|
}
|
|
}
|
|
|
|
ON_UnknownUserData& ON_UnknownUserData::operator=(const ON_UnknownUserData& src)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
m_sizeof_buffer = 0;
|
|
if ( 0 != m_buffer )
|
|
{
|
|
onfree(m_buffer);
|
|
m_buffer = 0;
|
|
}
|
|
|
|
// ON_UserData::operator= handles setting m_userdata_copycount and
|
|
// m_userdata_xform.
|
|
ON_UserData::operator=(src);
|
|
|
|
// For most kinds of user data except ON_UnknownUserData,
|
|
// m_userdata_uuid and m_application_uuid are set by the
|
|
// constructor and should not be altered by an operator=.
|
|
// However, for ON_UnknownUserData, the value of m_userdata_uuid
|
|
// and m_application_uuid vary because they are set by the
|
|
// missing userdata class. So they have to be copied here.
|
|
m_userdata_uuid = src.m_userdata_uuid;
|
|
m_application_uuid = src.m_application_uuid; // fix added 26 January 2010
|
|
|
|
if ( m_userdata_copycount > 0 && src.m_sizeof_buffer > 0 && src.m_buffer )
|
|
{
|
|
m_unknownclass_uuid = src.m_unknownclass_uuid;
|
|
m_sizeof_buffer = src.m_sizeof_buffer;
|
|
m_buffer = onmemdup( src.m_buffer, src.m_sizeof_buffer);
|
|
m_3dm_version = src.m_3dm_version;
|
|
m_3dm_opennurbs_version_number = src.m_3dm_opennurbs_version_number;
|
|
}
|
|
else
|
|
{
|
|
// The unknown user data is not supposed to copy
|
|
m_userdata_uuid = ON_nil_uuid;
|
|
m_unknownclass_uuid = ON_nil_uuid;
|
|
m_sizeof_buffer = 0;
|
|
m_buffer = 0;
|
|
m_3dm_version = 0;
|
|
m_3dm_opennurbs_version_number = 0;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
ON_UnknownUserData::~ON_UnknownUserData()
|
|
{
|
|
if ( m_buffer )
|
|
onfree(m_buffer);
|
|
}
|
|
|
|
unsigned int ON_UnknownUserData::SizeOf() const
|
|
{
|
|
return ON_UserData::SizeOf()
|
|
+ (sizeof(ON_UnknownUserData)-sizeof(ON_UserData))
|
|
+ m_sizeof_buffer;
|
|
}
|
|
|
|
bool ON_UnknownUserData::GetDescription( ON_wString& s )
|
|
{
|
|
s = "Unknown user data. (Definition of class was not available when object was read.)";
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnknownUserData::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
bool rc = ON_UserData::IsValid(text_log);
|
|
|
|
// valid unknown user data must have something in it
|
|
if (rc)
|
|
rc = (m_sizeof_buffer>0);
|
|
|
|
if (rc)
|
|
rc = (m_buffer != nullptr);
|
|
|
|
if (rc)
|
|
{
|
|
// the unknown class uuid cannot be nil
|
|
if (ON_nil_uuid == m_unknownclass_uuid)
|
|
rc = false;
|
|
}
|
|
|
|
if (rc)
|
|
{
|
|
// the unknown class uuid cannot be the ON_UnknownUserData class uuid
|
|
const ON_UUID ON_UnknownUserData_classuuid = ON_CLASS_ID(ON_UnknownUserData);
|
|
if ( m_unknownclass_uuid == ON_UnknownUserData_classuuid )
|
|
rc = false;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
void ON_UnknownUserData::Dump( ON_TextLog& dump ) const
|
|
{
|
|
ON_UserData::Dump(dump);
|
|
dump.PushIndent();
|
|
dump.Print( "unknown class uuid: ");
|
|
dump.Print( m_unknownclass_uuid );
|
|
dump.Print( "\n");
|
|
dump.Print( "Data size in 3dm archive: %d bytes\n",m_sizeof_buffer);
|
|
dump.PopIndent();
|
|
}
|
|
|
|
bool ON_UnknownUserData::Archive() const
|
|
{
|
|
// 22 January 2004 Dale Lear
|
|
// Since unknown userdata is only created when
|
|
// an archive is read, it has to be saved.
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnknownUserData::Write( ON_BinaryArchive& file ) const
|
|
{
|
|
return file.WriteByte(m_sizeof_buffer,m_buffer);
|
|
}
|
|
|
|
bool ON_UnknownUserData::Read( ON_BinaryArchive& file )
|
|
{
|
|
m_buffer = onrealloc( m_buffer, m_sizeof_buffer );
|
|
m_3dm_version = file.Archive3dmVersion();
|
|
return file.ReadByte(m_sizeof_buffer,m_buffer);
|
|
}
|
|
|
|
|
|
class ON_UnknownUserDataArchive : public ON_BinaryArchive
|
|
{
|
|
// This class is used to define an ON_BinaryArchive that can be used
|
|
// in ON_UnknownUserData::Convert() to initialize a ON_UserData class
|
|
// from a memory buffer.
|
|
public:
|
|
ON_UnknownUserDataArchive( const ON_UnknownUserData& );
|
|
~ON_UnknownUserDataArchive();
|
|
|
|
protected:
|
|
// ON_BinaryArchive overrides
|
|
ON__UINT64 Internal_CurrentPositionOverride() const override;
|
|
bool Internal_SeekFromCurrentPositionOverride(int byte_offset) override;
|
|
bool Internal_SeekToStartOverride() override;
|
|
|
|
public:
|
|
// ON_BinaryArchive overrides
|
|
bool AtEnd() const override;
|
|
|
|
protected:
|
|
// ON_BinaryArchive overrides
|
|
size_t Internal_ReadOverride( size_t, void* ) override; // return actual number of bytes read (like fread())
|
|
size_t Internal_WriteOverride( size_t, const void* ) override;
|
|
bool Flush() override;
|
|
|
|
private:
|
|
ON_UnknownUserDataArchive();
|
|
|
|
size_t m_sizeof_buffer = 0;
|
|
const unsigned char* m_buffer = nullptr;
|
|
size_t m_buffer_position = 0;
|
|
};
|
|
|
|
ON_UnknownUserDataArchive::ON_UnknownUserDataArchive(const ON_UnknownUserData& ud) : ON_BinaryArchive(ON::archive_mode::read3dm)
|
|
{
|
|
SetArchive3dmVersion(ud.m_3dm_version);
|
|
m_sizeof_buffer = ud.m_sizeof_buffer;
|
|
m_buffer = (const unsigned char*)ud.m_buffer;
|
|
m_buffer_position = 0;
|
|
}
|
|
|
|
ON_UnknownUserDataArchive::~ON_UnknownUserDataArchive()
|
|
{
|
|
}
|
|
|
|
ON__UINT64 ON_UnknownUserDataArchive::Internal_CurrentPositionOverride() const
|
|
{
|
|
return (ON__UINT64)m_buffer_position;
|
|
}
|
|
|
|
bool ON_UnknownUserDataArchive::Internal_SeekFromCurrentPositionOverride( int offset )
|
|
{
|
|
bool rc = false;
|
|
if ( offset >= 0 || m_buffer_position >= ((size_t)(-offset)) )
|
|
{
|
|
size_t newpos = m_buffer_position + offset;
|
|
if ( newpos < m_sizeof_buffer )
|
|
{
|
|
m_buffer_position = newpos;
|
|
rc = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_UnknownUserDataArchive::Internal_SeekToStartOverride()
|
|
{
|
|
m_buffer_position = 0;
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnknownUserDataArchive::AtEnd() const
|
|
{
|
|
return (m_buffer_position >= m_sizeof_buffer) ? true : false;
|
|
}
|
|
|
|
size_t ON_UnknownUserDataArchive::Internal_ReadOverride( size_t count, void* buffer )
|
|
{
|
|
size_t maxcount = 0;
|
|
|
|
if ( m_sizeof_buffer > m_buffer_position )
|
|
{
|
|
maxcount = m_sizeof_buffer - m_buffer_position;
|
|
}
|
|
|
|
if ( count > maxcount )
|
|
{
|
|
count = maxcount;
|
|
}
|
|
|
|
if ( count > 0 )
|
|
{
|
|
memcpy( buffer, m_buffer+m_buffer_position, count );
|
|
m_buffer_position += count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
size_t ON_UnknownUserDataArchive::Internal_WriteOverride( size_t, const void* )
|
|
{
|
|
// ON_UnknownUserDataArchive does not support Write() and Flush()
|
|
return 0;
|
|
}
|
|
|
|
bool ON_UnknownUserDataArchive::Flush()
|
|
{
|
|
// ON_UnknownUserDataArchive does not support Write() and Flush()
|
|
return false;
|
|
}
|
|
|
|
ON_UserData* ON_UnknownUserData::Convert() const
|
|
{
|
|
ON_UserData* ud = nullptr;
|
|
if ( IsValid() ) {
|
|
const ON_ClassId* pID = ON_ClassId::ClassId( m_unknownclass_uuid );
|
|
// if pID is nullptr, it means the definiton of the unknown user data
|
|
// is still not available
|
|
if ( pID ) {
|
|
// The class definition has been dynamically loaded since the
|
|
// user data was read from an archive. Use the class's Read()
|
|
// to convert the buffer into a valid class
|
|
ON_Object* pObject = pID->Create();
|
|
if ( pObject ) {
|
|
ud = ON_UserData::Cast(pObject);
|
|
if ( !ud )
|
|
delete pObject;
|
|
else
|
|
{
|
|
// use class's Read() function to initialize class members from buffer
|
|
ON_UnknownUserDataArchive file(*this);
|
|
// copy values that would be set by reading the base class
|
|
ud->m_userdata_copycount = m_userdata_copycount;
|
|
ud->m_userdata_xform = m_userdata_xform;
|
|
ud->Read(file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ud;
|
|
}
|
|
|
|
|
|
unsigned int ON_UserDataHolder::MoveUserDataFrom( const ON_Object& source_object )
|
|
{
|
|
PurgeUserData();
|
|
unsigned int item_count = MoveUserData(const_cast< ON_Object& >(source_object), ON_nil_uuid, ON_Object::UserDataConflictResolution::source_object,true);
|
|
return item_count;
|
|
}
|
|
|
|
unsigned int ON_UserDataHolder::CopyUserDataFrom( const ON_Object& source_object, ON_UUID user_data_item_id )
|
|
{
|
|
PurgeUserData();
|
|
unsigned int item_count = CopyUserData(source_object, user_data_item_id, ON_Object::UserDataConflictResolution::source_object);
|
|
return item_count;
|
|
}
|
|
|
|
|
|
unsigned int ON_UserDataHolder::MoveUserDataTo(
|
|
const ON_Object& destination_object,
|
|
bool bAppend
|
|
)
|
|
{
|
|
if ( !bAppend )
|
|
const_cast< ON_Object& >(destination_object).PurgeUserData();
|
|
|
|
const ON_Object::UserDataConflictResolution userdata_conflict_resolution
|
|
= bAppend
|
|
? ON_Object::UserDataConflictResolution::destination_object
|
|
: ON_Object::UserDataConflictResolution::source_object;
|
|
return MoveUserDataTo(destination_object,ON_nil_uuid,userdata_conflict_resolution);
|
|
}
|
|
|
|
unsigned int ON_UserDataHolder::MoveUserDataTo(
|
|
const ON_Object& destination_object,
|
|
ON_UUID user_data_item_id,
|
|
ON_Object::UserDataConflictResolution userdata_conflict_resolution
|
|
)
|
|
{
|
|
unsigned int moved_count = const_cast< ON_Object& >(destination_object).MoveUserData(*this, user_data_item_id, userdata_conflict_resolution,true);
|
|
return moved_count;
|
|
|
|
}
|
|
|
|
|
|
bool ON_UserDataHolder::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Built in tool for attaching arbitrary string userdata
|
|
// to any opennurbs object. The savvy user will probably
|
|
// use XML in the string.
|
|
|
|
|
|
ON_UserString::ON_UserString()
|
|
{
|
|
}
|
|
|
|
ON_UserString::~ON_UserString()
|
|
{
|
|
}
|
|
|
|
|
|
bool ON_UserString::Write(ON_BinaryArchive& archive) const
|
|
{
|
|
bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0);
|
|
if (!rc)
|
|
return false;
|
|
|
|
for(;;)
|
|
{
|
|
rc = archive.WriteString(m_key);
|
|
if (!rc) break;
|
|
rc = archive.WriteString(m_string_value);
|
|
if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !archive.EndWrite3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_UserString::Read(ON_BinaryArchive& archive)
|
|
{
|
|
m_key.Empty();
|
|
m_string_value.Empty();
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
|
|
if (!rc)
|
|
return false;
|
|
|
|
for(;;)
|
|
{
|
|
rc = ( 1 == major_version );
|
|
if (!rc) break;
|
|
rc = archive.ReadString(m_key);
|
|
if (!rc) break;
|
|
rc = archive.ReadString(m_string_value);
|
|
if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !archive.EndRead3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
void ON_UserString::Dump(ON_TextLog& text_log) const
|
|
{
|
|
const wchar_t* ws = static_cast< const wchar_t* >(m_key);
|
|
if ( !ws )
|
|
ws = L"";
|
|
text_log.Print("Key: %ls\n", ws);
|
|
|
|
ws = static_cast< const wchar_t* >(m_string_value);
|
|
if ( !ws )
|
|
ws = L"";
|
|
text_log.Print("Value: %ls\n",ws);
|
|
}
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_UserStringList,ON_UserData,"CE28DE29-F4C5-4faa-A50A-C3A6849B6329");
|
|
|
|
ON_UserStringList::ON_UserStringList()
|
|
{
|
|
m_userdata_uuid = ON_CLASS_ID(ON_UserStringList);
|
|
m_application_uuid = ON_opennurbs4_id; // opennurbs.dll reads/writes this userdata
|
|
// The id must be the version 4 id because
|
|
// V5 SaveAs V4 needs to work.
|
|
m_userdata_copycount = 1;
|
|
}
|
|
|
|
ON_UserStringList::~ON_UserStringList()
|
|
{
|
|
}
|
|
|
|
bool ON_UserStringList::GetDescription( ON_wString& description )
|
|
{
|
|
description.Format(L"User text (%d entries)",m_e.Count());
|
|
return true;
|
|
}
|
|
|
|
bool ON_UserStringList::Archive() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
unsigned int ON_UserStringList::SizeOf() const
|
|
{
|
|
unsigned int sz = ON_UserData::SizeOf();
|
|
sz += sizeof(*this) - sizeof(ON_UserData);
|
|
sz += m_e.SizeOfArray();
|
|
int i = m_e.Count();
|
|
while (i--)
|
|
sz += m_e[i].m_string_value.Length()*sizeof(wchar_t);
|
|
return sz;
|
|
}
|
|
|
|
|
|
ON__UINT32 ON_UserStringList::DataCRC(ON__UINT32 current_remainder) const
|
|
{
|
|
int count = m_e.Count();
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
current_remainder = m_e[i].m_key.DataCRC(current_remainder);
|
|
current_remainder = m_e[i].m_string_value.DataCRC(current_remainder);
|
|
}
|
|
return current_remainder;
|
|
}
|
|
|
|
|
|
void ON_UserStringList::Dump( ON_TextLog& text_log ) const
|
|
{
|
|
int i, count = m_e.Count();
|
|
text_log.Print("%d entries\n",count);
|
|
text_log.PushIndent();
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
m_e[i].Dump(text_log);
|
|
}
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
bool ON_UserStringList::Write(ON_BinaryArchive& archive) const
|
|
{
|
|
bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0);
|
|
if ( !rc )
|
|
return false;
|
|
|
|
for(;;)
|
|
{
|
|
int count = m_e.Count();
|
|
rc = archive.WriteInt(count);
|
|
if(!rc) break;
|
|
|
|
for ( int i = 0; i < count && rc; i++ )
|
|
{
|
|
rc = m_e[i].Write(archive);
|
|
}
|
|
if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !archive.EndWrite3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_UserStringList::Read(ON_BinaryArchive& archive)
|
|
{
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
|
|
if ( !rc )
|
|
return false;
|
|
|
|
for(;;)
|
|
{
|
|
rc = (1 == major_version);
|
|
if (!rc) break;
|
|
|
|
int count = 0;
|
|
rc = archive.ReadInt(&count);
|
|
if(!rc) break;
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
rc = m_e.AppendNew().Read(archive);
|
|
if ( !rc)
|
|
{
|
|
m_e.Remove();
|
|
break;
|
|
}
|
|
}
|
|
if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !archive.EndRead3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_UserStringList::SetUserString( const wchar_t* key, const wchar_t* string_value )
|
|
{
|
|
if ( !key || !key[0] )
|
|
return false;
|
|
|
|
int i, count = m_e.Count();
|
|
for (i = 0; i < count; i++ )
|
|
{
|
|
if ( !m_e[i].m_key.CompareOrdinal(key,true) )
|
|
{
|
|
if ( string_value && string_value[0] )
|
|
{
|
|
m_e[i].m_string_value = string_value;
|
|
}
|
|
else
|
|
{
|
|
m_e.Remove(i);
|
|
}
|
|
m_userdata_copycount++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( string_value && string_value[0] )
|
|
{
|
|
ON_UserString& e = m_e.AppendNew();
|
|
e.m_key = key;
|
|
e.m_string_value = string_value;
|
|
m_userdata_copycount++;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ON_UserStringList::GetUserString( const wchar_t* key, ON_wString& string_value ) const
|
|
{
|
|
if ( key && key[0] )
|
|
{
|
|
int i, count = m_e.Count();
|
|
for (i = 0; i < count; i++ )
|
|
{
|
|
if ( !m_e[i].m_key.CompareOrdinal(key,true) )
|
|
{
|
|
string_value = m_e[i].m_string_value;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
string_value = ON_wString::EmptyString;
|
|
return false;
|
|
}
|
|
|
|
static int cmp_hash_2dex_ij(const void* a, const void* b)
|
|
{
|
|
const int* ai = (const int*)a;
|
|
const int* bi = (const int*)b;
|
|
// 26 January 2012 Dale Lear
|
|
// Part of the fix for http://dev.mcneel.com/bugtrack/?q=97693
|
|
//
|
|
// The "i" values are actually 32 bit hashes of a string
|
|
// and are often large. The sign of (ai[0] - bi[0]) cannot
|
|
// be used to compare ai[0] and bi[0] because integer
|
|
// overflow occures with values that are large.
|
|
//
|
|
////// NO!
|
|
//////if ( 0 == (rc = ai[0] - bi[0]) )
|
|
////// rc = ai[1] - bi[1];
|
|
//////return rc;
|
|
|
|
if ( ai[0] < bi[0] )
|
|
return -1;
|
|
if ( ai[0] > bi[0] )
|
|
return 1;
|
|
if ( ai[1] < bi[1] )
|
|
return -1;
|
|
if ( ai[1] > bi[1] )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int ON_UserStringList::SetUserStrings( int count, const ON_UserString* us, bool bReplace )
|
|
{
|
|
int added_count = 0;
|
|
int i;
|
|
|
|
if ( count <= 0 || 0 == us )
|
|
return 0;
|
|
|
|
if ( 1 == count )
|
|
{
|
|
// skip the hash table hoo haa
|
|
if ( us[0].m_key.IsEmpty() )
|
|
return 0;
|
|
for ( i = 0; i < m_e.Count(); i++ )
|
|
{
|
|
if ( m_e[i].m_key.CompareOrdinal(us[0].m_key, true ) )
|
|
continue;
|
|
if ( bReplace )
|
|
{
|
|
if ( us[0].m_string_value.IsEmpty() )
|
|
m_e.Remove(i);
|
|
else
|
|
m_e[i] = us[0];
|
|
added_count++;
|
|
}
|
|
break;
|
|
}
|
|
return added_count;
|
|
}
|
|
|
|
size_t k0, k1;
|
|
int count0 = m_e.Count();
|
|
size_t count0_plus_count = (size_t)(count0 + count);
|
|
ON_2dex* hash = (ON_2dex*)onmalloc( (count0_plus_count + count)*sizeof(hash[0]) );
|
|
ON_2dex* hash1 = hash + (count0_plus_count);
|
|
const ON_2dex* h;
|
|
int deleted_count = 0;
|
|
|
|
for ( i = 0; i < count0; i++ )
|
|
{
|
|
hash[i].i = (int)m_e[i].m_key.DataCRCLower(0);
|
|
hash[i].j = i;
|
|
}
|
|
|
|
for ( i = 0; i < count; i++ )
|
|
{
|
|
hash1[i].i = (int)us[i].m_key.DataCRCLower(0);
|
|
hash1[i].j = i;
|
|
hash[i+count0].i = hash1[i].i;
|
|
hash[i+count0].j = hash1[i].j+count0;
|
|
}
|
|
ON_qsort(hash,count0_plus_count,sizeof(hash[0]),cmp_hash_2dex_ij);
|
|
|
|
m_e.Reserve(count0+count);
|
|
for ( i = 0; i < count; i++)
|
|
{
|
|
if ( us[i].m_key.IsEmpty() )
|
|
continue;
|
|
|
|
// Set k0, k1 so that hash[k0]....,hash[k1-1] are
|
|
// the hash[] entries keys with the same hash code
|
|
// as us[i].m_key.
|
|
h = ON_BinarySearch2dexArray(hash1[i].i,hash,count0_plus_count);
|
|
if ( 0 == h )
|
|
{
|
|
ON_ERROR("There is a bug in this function.");
|
|
continue;
|
|
}
|
|
k0 = h-hash;
|
|
while ( k0 > 0 && h[-1].i == h[0].i )
|
|
{
|
|
// set h = first element in hash[] with this hash code.
|
|
k0--;
|
|
h--;
|
|
}
|
|
for (k1 = k0+1; k1 < count0_plus_count; k1++ )
|
|
{
|
|
if ( hash[k1].i != hash[k0].i )
|
|
break;
|
|
if ( hash[k1].j > i+count0 )
|
|
break;
|
|
}
|
|
|
|
if ( hash[k0].j >= count0 )
|
|
{
|
|
// There are no entries in m_e[] with key matching hash,
|
|
// so us[i].m_key is not present in m_e.
|
|
if ( !us[i].m_string_value.IsEmpty() )
|
|
{
|
|
hash[k0].j = count0++;
|
|
m_e.Append(us[i]);
|
|
added_count++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (/* empty init*/; k0 < k1; k0++ )
|
|
{
|
|
if ( hash[k0].j < count0 )
|
|
{
|
|
if ( m_e[hash[k0].j].m_key.CompareOrdinal(us[i].m_key,true) )
|
|
continue; // different keys with same hash
|
|
if ( bReplace )
|
|
{
|
|
m_e[hash[k0].j] = us[i];
|
|
added_count++;
|
|
if ( us[i].m_string_value.IsEmpty() )
|
|
deleted_count++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( k0 >= k1 )
|
|
{
|
|
// hash is unique up to this point, so us[i].m_key is unique,
|
|
// so we add it if it is valid.
|
|
if ( !us[i].m_string_value.IsEmpty() )
|
|
{
|
|
hash[k0].j = count0++;
|
|
m_e.Append(us[i]);
|
|
added_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
onfree(hash);
|
|
|
|
// remove deleted items.
|
|
i = m_e.Count();
|
|
while ( i-- > 0 && deleted_count > 0 )
|
|
{
|
|
if ( m_e[i].m_string_value.IsEmpty() )
|
|
{
|
|
m_e.Remove(i);
|
|
deleted_count--;
|
|
}
|
|
}
|
|
|
|
return added_count;
|
|
}
|
|
|
|
|
|
|
|
ON_UserStringList* ON_UserStringList::FromObject(
|
|
const ON_Object* p
|
|
)
|
|
{
|
|
return p
|
|
? ON_UserStringList::Cast(p->GetUserData(ON_CLASS_ID(ON_UserStringList)))
|
|
: 0;
|
|
}
|
|
|
|
bool ON_Object::SetUserString( const wchar_t* key, const wchar_t* string_value )
|
|
{
|
|
ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
|
|
bool b = false;
|
|
if ( nullptr == us )
|
|
{
|
|
us = new ON_UserStringList();
|
|
if ( !AttachUserData(us) )
|
|
{
|
|
delete us;
|
|
us = nullptr;
|
|
}
|
|
else
|
|
{
|
|
b = true;
|
|
}
|
|
}
|
|
|
|
if ( us )
|
|
{
|
|
if ( us->SetUserString(key,string_value) )
|
|
{
|
|
if ( b && 2 == us->m_userdata_copycount )
|
|
{
|
|
// user data is brand new - roll back the
|
|
// m_userdata_copycount++ that happens in
|
|
// SetUserString().
|
|
us->m_userdata_copycount = 1;
|
|
}
|
|
b = true;
|
|
}
|
|
else if ( b )
|
|
{
|
|
// user data was new-ed up and has nothing in it
|
|
// because the input was bogus.
|
|
delete us;
|
|
us = nullptr;
|
|
b = false;
|
|
}
|
|
}
|
|
return b;
|
|
}
|
|
|
|
|
|
int ON_Object::SetUserStrings( int count, const ON_UserString* user_strings, bool bReplace )
|
|
{
|
|
if ( 0 == count || 0 == user_strings )
|
|
return 0;
|
|
|
|
int add_count = 0;
|
|
int del_count = 0;
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( user_strings[i].m_key.IsEmpty() )
|
|
continue;
|
|
if ( user_strings[i].m_string_value.IsEmpty() )
|
|
del_count++;
|
|
else
|
|
add_count++;
|
|
}
|
|
if ( 0 == add_count && 0 == del_count )
|
|
return 0;
|
|
|
|
ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
if ( !us && add_count > 0)
|
|
{
|
|
us = new ON_UserStringList();
|
|
if ( !AttachUserData(us) )
|
|
{
|
|
delete us;
|
|
us = 0;
|
|
}
|
|
}
|
|
|
|
return us ? us->SetUserStrings(count,user_strings,bReplace ) : 0;
|
|
}
|
|
|
|
|
|
bool ON_Object::GetUserString( const wchar_t* key, ON_wString& string_value ) const
|
|
{
|
|
const ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
if (nullptr != us)
|
|
return us->GetUserString(key,string_value);
|
|
|
|
// because key and string_value might be the same, don't empty string_value until it is no longer needed.
|
|
string_value = ON_wString::EmptyString;
|
|
return false;
|
|
}
|
|
|
|
int ON_Object::UserStringCount() const
|
|
{
|
|
const ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
return ( 0 != us )
|
|
? us->m_e.Count()
|
|
: 0;
|
|
}
|
|
|
|
|
|
int ON_Object::GetUserStrings(
|
|
ON_ClassArray<ON_UserString>& user_strings
|
|
) const
|
|
{
|
|
const int count0 = user_strings.Count();
|
|
const ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
if ( us )
|
|
user_strings.Append(us->m_e.Count(),us->m_e.Array());
|
|
|
|
return user_strings.Count() - count0;
|
|
}
|
|
|
|
int ON_Object::GetUserStringKeys(
|
|
ON_ClassArray<ON_wString>& user_string_keys
|
|
) const
|
|
{
|
|
const int count0 = user_string_keys.Count();
|
|
const ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
if ( us )
|
|
{
|
|
user_string_keys.Reserve( count0 + us->m_e.Count() );
|
|
for (int i = 0; i < us->m_e.Count(); i++ )
|
|
{
|
|
user_string_keys.Append(us->m_e[i].m_key);
|
|
}
|
|
}
|
|
|
|
return user_string_keys.Count() - count0;
|
|
}
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_DocumentUserStringList,ON_Object,"06F3218E-F5EC-4f6c-B74C-14583F0ED7BC");
|
|
|
|
ON_DocumentUserStringList::ON_DocumentUserStringList()
|
|
{
|
|
}
|
|
|
|
ON_DocumentUserStringList::~ON_DocumentUserStringList()
|
|
{
|
|
}
|
|
|
|
bool ON_DocumentUserStringList::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void ON_DocumentUserStringList::Dump( ON_TextLog& ) const
|
|
{
|
|
}
|
|
|
|
ON__UINT32 ON_DocumentUserStringList::DataCRC(ON__UINT32 current_remainder) const
|
|
{
|
|
const ON_UserStringList* us = ON_UserStringList::FromObject(this);
|
|
if ( us )
|
|
current_remainder = us->DataCRC(current_remainder);
|
|
return current_remainder;
|
|
}
|
|
|
|
bool ON_DocumentUserStringList::Write(ON_BinaryArchive& binary_archive) const
|
|
{
|
|
// The key/value pairs are saved as ON_UserStringList user data.
|
|
|
|
// A single char with value 1 is written to permit adding additional
|
|
// information later. In that case change the 1 to a 2, uncomment the
|
|
// begin block code, and the IO will still work.
|
|
//
|
|
unsigned char c = 1;
|
|
bool rc = binary_archive.WriteChar(c);
|
|
|
|
////rc = binary_archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0);
|
|
////if (!rc)
|
|
//// return false;
|
|
////for(;;)
|
|
////{
|
|
//// rc = binary_archive.Write...();
|
|
//// if (!rc) break;
|
|
|
|
//// break;
|
|
////}
|
|
////if ( !binary_archive.EndWrite3dmChunk() )
|
|
//// rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_DocumentUserStringList::Read(ON_BinaryArchive& binary_archive)
|
|
{
|
|
// The key/value pairs are saved as ON_UserStringList user data.
|
|
|
|
unsigned char c = 0;
|
|
bool rc = binary_archive.ReadChar(&c);
|
|
if ( !rc || c < 1 || c > 2 )
|
|
return false;
|
|
|
|
if ( 2 == c )
|
|
{
|
|
// The code in this if(2==c) will not be used unless
|
|
// the 1 in Write is changed to a 2. This code is here
|
|
// so old versions of Rhino will be able to skip over
|
|
// any new information that is added at a later date.
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
rc = binary_archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
|
|
if (!rc)
|
|
return false;
|
|
for(;;)
|
|
{
|
|
rc = (1 == major_version);
|
|
if (!rc) break;
|
|
|
|
////rc = binary_archive.Read(...);
|
|
////if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
if (!binary_archive.EndRead3dmChunk() )
|
|
rc = false;
|
|
}
|
|
|
|
return rc;
|
|
}
|