XTI extensions
[wxWidgets.git] / src / common / xti.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/xti.cpp
3 // Purpose: runtime metadata information (extended class info
4 // Author: Stefan Csomor
5 // Modified by:
6 // Created: 27/07/03
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997 Julian Smart
9 // (c) 2003 Stefan Csomor
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
14 #pragma implementation "xti.h"
15 #endif
16
17 // For compilers that support precompilation, includes "wx.h".
18 #include "wx/wxprec.h"
19
20 #ifdef __BORLANDC__
21 #pragma hdrstop
22 #endif
23
24 #ifndef WX_PRECOMP
25 #include "wx/hash.h"
26 #include "wx/object.h"
27 #endif
28
29 #include "wx/xml/xml.h"
30 #include "wx/tokenzr.h"
31 #include "wx/notebook.h"
32 #include "wx/list.h"
33 #include <string.h>
34
35 #if wxUSE_EXTENDED_RTTI
36
37 #include "wx/beforestd.h"
38 #include <map>
39 #include <string>
40 #include "wx/afterstd.h"
41
42 using namespace std ;
43
44 // ----------------------------------------------------------------------------
45 // Enum Support
46 // ----------------------------------------------------------------------------
47
48 wxEnumData::wxEnumData( wxEnumMemberData* data )
49 {
50 m_members = data ;
51 for ( m_count = 0; m_members[m_count].m_name ; m_count++)
52 {} ;
53 }
54
55 bool wxEnumData::HasEnumMemberValue(const wxChar *name, int *value)
56 {
57 int i;
58 for (i = 0; m_members[i].m_name ; i++ )
59 {
60 if (!strcmp(name, m_members[i].m_name))
61 {
62 if ( value )
63 *value = m_members[i].m_value;
64 return true ;
65 }
66 }
67 return false ;
68 }
69
70 int wxEnumData::GetEnumMemberValue(const wxChar *name)
71 {
72 int i;
73 for (i = 0; m_members[i].m_name ; i++ )
74 {
75 if (!strcmp(name, m_members[i].m_name))
76 {
77 return m_members[i].m_value;
78 }
79 }
80 return 0 ;
81 }
82
83 const wxChar *wxEnumData::GetEnumMemberName(int value)
84 {
85 int i;
86 for (i = 0; m_members[i].m_name ; i++)
87 if (value == m_members[i].m_value)
88 return m_members[i].m_name;
89
90 return wxT("") ;
91 }
92
93 int wxEnumData::GetEnumMemberValueByIndex( int idx )
94 {
95 // we should cache the count in order to avoid out-of-bounds errors
96 return m_members[idx].m_value ;
97 }
98
99 const char * wxEnumData::GetEnumMemberNameByIndex( int idx )
100 {
101 // we should cache the count in order to avoid out-of-bounds errors
102 return m_members[idx].m_name ;
103 }
104
105 // ----------------------------------------------------------------------------
106 // Type Information
107 // ----------------------------------------------------------------------------
108 // ----------------------------------------------------------------------------
109 // value streaming
110 // ----------------------------------------------------------------------------
111
112 // streamer specializations
113 // for all built-in types
114
115 // bool
116
117 template<> void wxStringReadValue(const wxString &s , bool &data )
118 {
119 int intdata ;
120 wxSscanf(s, _T("%d"), &intdata ) ;
121 data = bool(intdata) ;
122 }
123
124 template<> void wxStringWriteValue(wxString &s , const bool &data )
125 {
126 s = wxString::Format("%d", data ) ;
127 }
128
129 // char
130
131 template<> void wxStringReadValue(const wxString &s , char &data )
132 {
133 int intdata ;
134 wxSscanf(s, _T("%d"), &intdata ) ;
135 data = char(intdata) ;
136 }
137
138 template<> void wxStringWriteValue(wxString &s , const char &data )
139 {
140 s = wxString::Format("%d", data ) ;
141 }
142
143 // unsigned char
144
145 template<> void wxStringReadValue(const wxString &s , unsigned char &data )
146 {
147 int intdata ;
148 wxSscanf(s, _T("%d"), &intdata ) ;
149 data = (unsigned char)(intdata) ;
150 }
151
152 template<> void wxStringWriteValue(wxString &s , const unsigned char &data )
153 {
154 s = wxString::Format("%d", data ) ;
155 }
156
157 // int
158
159 template<> void wxStringReadValue(const wxString &s , int &data )
160 {
161 wxSscanf(s, _T("%d"), &data ) ;
162 }
163
164 template<> void wxStringWriteValue(wxString &s , const int &data )
165 {
166 s = wxString::Format("%d", data ) ;
167 }
168
169 // unsigned int
170
171 template<> void wxStringReadValue(const wxString &s , unsigned int &data )
172 {
173 wxSscanf(s, _T("%d"), &data ) ;
174 }
175
176 template<> void wxStringWriteValue(wxString &s , const unsigned int &data )
177 {
178 s = wxString::Format("%d", data ) ;
179 }
180
181 // long
182
183 template<> void wxStringReadValue(const wxString &s , long &data )
184 {
185 wxSscanf(s, _T("%ld"), &data ) ;
186 }
187
188 template<> void wxStringWriteValue(wxString &s , const long &data )
189 {
190 s = wxString::Format("%ld", data ) ;
191 }
192
193 // unsigned long
194
195 template<> void wxStringReadValue(const wxString &s , unsigned long &data )
196 {
197 wxSscanf(s, _T("%ld"), &data ) ;
198 }
199
200 template<> void wxStringWriteValue(wxString &s , const unsigned long &data )
201 {
202 s = wxString::Format("%ld", data ) ;
203 }
204
205 // float
206
207 template<> void wxStringReadValue(const wxString &s , float &data )
208 {
209 wxSscanf(s, _T("%f"), &data ) ;
210 }
211
212 template<> void wxStringWriteValue(wxString &s , const float &data )
213 {
214 s = wxString::Format("%f", data ) ;
215 }
216
217 // double
218
219 template<> void wxStringReadValue(const wxString &s , double &data )
220 {
221 wxSscanf(s, _T("%lf"), &data ) ;
222 }
223
224 template<> void wxStringWriteValue(wxString &s , const double &data )
225 {
226 s = wxString::Format("%lf", data ) ;
227 }
228
229 // wxString
230
231 template<> void wxStringReadValue(const wxString &s , wxString &data )
232 {
233 data = s ;
234 }
235
236 template<> void wxStringWriteValue(wxString &s , const wxString &data )
237 {
238 s = data ;
239 }
240
241 /*
242 Custom Data Streaming / Type Infos
243 we will have to add this for all wx non object types, but it is also an example
244 for custom data structures
245 */
246
247 // wxPoint
248
249 template<> void wxStringReadValue(const wxString &s , wxPoint &data )
250 {
251 wxSscanf(s, _T("%d,%d"), &data.x , &data.y ) ;
252 }
253
254 template<> void wxStringWriteValue(wxString &s , const wxPoint &data )
255 {
256 s = wxString::Format("%d,%d", data.x , data.y ) ;
257 }
258
259 template<> void wxStringReadValue(const wxString & , wxPoint* & )
260 {
261 assert(0) ;
262 }
263
264 template<> void wxStringWriteValue(wxString & , wxPoint* const & )
265 {
266 assert(0) ;
267 }
268
269 WX_CUSTOM_TYPE_INFO(wxPoint)
270
271 template<> void wxStringReadValue(const wxString &s , wxSize &data )
272 {
273 wxSscanf(s, _T("%d,%d"), &data.x , &data.y ) ;
274 }
275
276 template<> void wxStringWriteValue(wxString &s , const wxSize &data )
277 {
278 s = wxString::Format("%d,%d", data.x , data.y ) ;
279 }
280
281 template<> void wxStringReadValue(const wxString & , wxSize* & )
282 {
283 assert(0) ;
284 }
285
286 template<> void wxStringWriteValue(wxString & , wxSize * const & )
287 {
288 assert(0) ;
289 }
290
291 WX_CUSTOM_TYPE_INFO(wxSize)
292
293
294 template<> const wxTypeInfo* wxGetTypeInfo( void * )
295 {
296 static wxBuiltInTypeInfo s_typeInfo( wxT_VOID ) ;
297 return &s_typeInfo ;
298 }
299
300 template<> const wxTypeInfo* wxGetTypeInfo( bool * )
301 {
302 static wxBuiltInTypeInfo s_typeInfo( wxT_BOOL , &wxToStringConverter<bool> , &wxFromStringConverter<bool>) ;
303 return &s_typeInfo ;
304 }
305
306 template<> const wxTypeInfo* wxGetTypeInfo( char * )
307 {
308 static wxBuiltInTypeInfo s_typeInfo( wxT_CHAR , &wxToStringConverter<char> , &wxFromStringConverter<char>) ;
309 return &s_typeInfo ;
310 }
311
312 template<> const wxTypeInfo* wxGetTypeInfo( unsigned char * )
313 {
314 static wxBuiltInTypeInfo s_typeInfo( wxT_UCHAR , &wxToStringConverter< unsigned char > , &wxFromStringConverter<unsigned char>) ;
315 return &s_typeInfo ;
316 }
317
318 template<> const wxTypeInfo* wxGetTypeInfo( int * )
319 {
320 static wxBuiltInTypeInfo s_typeInfo( wxT_CHAR , &wxToStringConverter<int> , &wxFromStringConverter<int>) ;
321 return &s_typeInfo ;
322 }
323
324 template<> const wxTypeInfo* wxGetTypeInfo( unsigned int * )
325 {
326 static wxBuiltInTypeInfo s_typeInfo( wxT_UCHAR , &wxToStringConverter<unsigned int> , &wxFromStringConverter<unsigned int>) ;
327 return &s_typeInfo ;
328 }
329
330 template<> const wxTypeInfo* wxGetTypeInfo( long * )
331 {
332 static wxBuiltInTypeInfo s_typeInfo( wxT_LONG , &wxToStringConverter<long> , &wxFromStringConverter<long>) ;
333 return &s_typeInfo ;
334 }
335
336 template<> const wxTypeInfo* wxGetTypeInfo( unsigned long * )
337 {
338 static wxBuiltInTypeInfo s_typeInfo( wxT_ULONG , &wxToStringConverter<unsigned long> , &wxFromStringConverter<unsigned long>) ;
339 return &s_typeInfo ;
340 }
341
342 template<> const wxTypeInfo* wxGetTypeInfo( float * )
343 {
344 static wxBuiltInTypeInfo s_typeInfo( wxT_FLOAT , &wxToStringConverter<float> , &wxFromStringConverter<float>) ;
345 return &s_typeInfo ;
346 }
347
348 template<> const wxTypeInfo* wxGetTypeInfo( double * )
349 {
350 static wxBuiltInTypeInfo s_typeInfo( wxT_DOUBLE , &wxToStringConverter<double> , &wxFromStringConverter<double>) ;
351 return &s_typeInfo ;
352 }
353
354 template<> const wxTypeInfo* wxGetTypeInfo( wxString * )
355 {
356 static wxBuiltInTypeInfo s_typeInfo( wxT_STRING , &wxToStringConverter<wxString> , &wxFromStringConverter<wxString>) ;
357 return &s_typeInfo ;
358 }
359
360 // this are compiler induced specialization which are never used anywhere
361
362 WX_ILLEGAL_TYPE_SPECIALIZATION( char const * )
363 WX_ILLEGAL_TYPE_SPECIALIZATION( char * )
364 WX_ILLEGAL_TYPE_SPECIALIZATION( unsigned char * )
365 WX_ILLEGAL_TYPE_SPECIALIZATION( int * )
366 WX_ILLEGAL_TYPE_SPECIALIZATION( bool * )
367 WX_ILLEGAL_TYPE_SPECIALIZATION( long * )
368 WX_ILLEGAL_TYPE_SPECIALIZATION( wxString * )
369
370 //
371
372 // make wxWindowList known
373
374 template<> const wxTypeInfo* wxGetTypeInfo( wxArrayString * )
375 {
376 static wxCollectionTypeInfo s_typeInfo( (wxTypeInfo*) wxGetTypeInfo( (wxString *) NULL) ) ;
377 return &s_typeInfo ;
378 }
379
380 template<> void wxCollectionToVariantArray( wxArrayString const &theArray, wxxVariantArray &value)
381 {
382 wxArrayCollectionToVariantArray( theArray , value ) ;
383 }
384
385
386
387 /*
388
389 template<> void wxStringReadValue(const wxString &s , wxColour &data )
390 {
391 // copied from VS xrc
392 unsigned long tmp = 0;
393
394 if (s.Length() != 7 || s[0u] != wxT('#') ||
395 wxSscanf(s.c_str(), wxT("#%lX"), &tmp) != 1)
396 {
397 wxLogError(_("String To Colour : Incorrect colour specification : %s"),
398 s.c_str() );
399 data = wxNullColour;
400 }
401 else
402 {
403 data = wxColour((unsigned char) ((tmp & 0xFF0000) >> 16) ,
404 (unsigned char) ((tmp & 0x00FF00) >> 8),
405 (unsigned char) ((tmp & 0x0000FF)));
406 }
407 }
408
409 template<> void wxStringWriteValue(wxString &s , const wxColour &data )
410 {
411 s = wxString::Format("#%2X%2X%2X", data.Red() , data.Green() , data.Blue() ) ;
412 }
413
414 WX_CUSTOM_TYPE_INFO(wxColour)
415
416 */
417
418 // removing header dependancy on string tokenizer
419
420 void wxSetStringToArray( const wxString &s , wxArrayString &array )
421 {
422 wxStringTokenizer tokenizer(s, wxT("| \t\n"), wxTOKEN_STRTOK);
423 wxString flag;
424 array.Clear() ;
425 while (tokenizer.HasMoreTokens())
426 {
427 array.Add(tokenizer.GetNextToken()) ;
428 }
429 }
430
431 // ----------------------------------------------------------------------------
432 // wxClassInfo
433 // ----------------------------------------------------------------------------
434
435 const wxPropertyAccessor *wxClassInfo::FindAccessor(const char *PropertyName) const
436 {
437 const wxPropertyInfo* info = FindPropertyInfo( PropertyName ) ;
438
439 if ( info )
440 return info->GetAccessor() ;
441
442 return NULL ;
443 }
444
445 const wxPropertyInfo *wxClassInfo::FindPropertyInfoInThisClass (const char *PropertyName) const
446 {
447 const wxPropertyInfo* info = GetFirstProperty() ;
448
449 while( info )
450 {
451 if ( strcmp( info->GetName() , PropertyName ) == 0 )
452 return info ;
453 info = info->GetNext() ;
454 }
455
456 return 0;
457 }
458
459 const wxPropertyInfo *wxClassInfo::FindPropertyInfo (const char *PropertyName) const
460 {
461 const wxPropertyInfo* info = FindPropertyInfoInThisClass( PropertyName ) ;
462 if ( info )
463 return info ;
464
465 const wxClassInfo** parents = GetParents() ;
466 for ( int i = 0 ; parents[i] ; ++ i )
467 {
468 if ( ( info = parents[i]->FindPropertyInfo( PropertyName ) ) != NULL )
469 return info ;
470 }
471
472 return 0;
473 }
474
475 const wxHandlerInfo *wxClassInfo::FindHandlerInfoInThisClass (const char *PropertyName) const
476 {
477 const wxHandlerInfo* info = GetFirstHandler() ;
478
479 while( info )
480 {
481 if ( strcmp( info->GetName() , PropertyName ) == 0 )
482 return info ;
483 info = info->GetNext() ;
484 }
485
486 return 0;
487 }
488
489 const wxHandlerInfo *wxClassInfo::FindHandlerInfo (const char *PropertyName) const
490 {
491 const wxHandlerInfo* info = FindHandlerInfoInThisClass( PropertyName ) ;
492
493 if ( info )
494 return info ;
495
496 const wxClassInfo** parents = GetParents() ;
497 for ( int i = 0 ; parents[i] ; ++ i )
498 {
499 if ( ( info = parents[i]->FindHandlerInfo( PropertyName ) ) != NULL )
500 return info ;
501 }
502
503 return 0;
504 }
505
506
507 void wxClassInfo::SetProperty(wxObject *object, const char *propertyName, const wxxVariant &value) const
508 {
509 const wxPropertyAccessor *accessor;
510
511 accessor = FindAccessor(propertyName);
512 wxASSERT(accessor->HasSetter());
513 accessor->SetProperty( object , value ) ;
514 }
515
516 wxxVariant wxClassInfo::GetProperty(wxObject *object, const char *propertyName) const
517 {
518 const wxPropertyAccessor *accessor;
519
520 accessor = FindAccessor(propertyName);
521 wxASSERT(accessor->HasGetter());
522 wxxVariant result ;
523 accessor->GetProperty(object,result);
524 return result ;
525 }
526
527 wxxVariantArray wxClassInfo::GetPropertyCollection(wxObject *object, const wxChar *propertyName) const
528 {
529 const wxPropertyAccessor *accessor;
530
531 accessor = FindAccessor(propertyName);
532 wxASSERT(accessor->HasGetter());
533 wxxVariantArray result ;
534 accessor->GetPropertyCollection(object,result);
535 return result ;
536 }
537
538 void wxClassInfo::AddToPropertyCollection(wxObject *object, const wxChar *propertyName , const wxxVariant& value) const
539 {
540 const wxPropertyAccessor *accessor;
541
542 accessor = FindAccessor(propertyName);
543 wxASSERT(accessor->HasAdder());
544 accessor->AddToPropertyCollection( object , value ) ;
545 }
546
547 /*
548 VARIANT TO OBJECT
549 */
550
551 wxObject* wxxVariant::GetAsObject()
552 {
553 const wxClassTypeInfo *ti = dynamic_cast<const wxClassTypeInfo*>( m_data->GetTypeInfo() ) ;
554 if ( ti )
555 return ti->GetClassInfo()->VariantToInstance(*this) ;
556 else
557 return NULL ;
558 }
559
560 // ----------------------------------------------------------------------------
561 // wxDynamicObject support
562 // ----------------------------------------------------------------------------
563 //
564 // Dynamic Objects are objects that have a real superclass instance and carry their
565 // own attributes in a hash map. Like this it is possible to create the objects and
566 // stream them, as if their class information was already available from compiled data
567
568 struct wxDynamicObject::wxDynamicObjectInternal
569 {
570 map<string,wxxVariant> m_properties ;
571 } ;
572
573 // instantiates this object with an instance of its superclass
574 wxDynamicObject::wxDynamicObject(wxObject* superClassInstance, const wxDynamicClassInfo *info)
575 {
576 m_superClassInstance = superClassInstance ;
577 m_classInfo = info ;
578 m_data = new wxDynamicObjectInternal ;
579 }
580
581 wxDynamicObject::~wxDynamicObject()
582 {
583 delete m_data ;
584 delete m_superClassInstance ;
585 }
586
587 void wxDynamicObject::SetProperty (const wxChar *propertyName, const wxxVariant &value)
588 {
589 wxASSERT_MSG(m_classInfo->FindPropertyInfoInThisClass(propertyName),wxT("Accessing Unknown Property in a Dynamic Object") ) ;
590 m_data->m_properties[propertyName] = value ;
591 }
592
593 wxxVariant wxDynamicObject::GetProperty (const wxChar *propertyName) const
594 {
595 wxASSERT_MSG(m_classInfo->FindPropertyInfoInThisClass(propertyName),wxT("Accessing Unknown Property in a Dynamic Object") ) ;
596 return m_data->m_properties[propertyName] ;
597 }
598
599 // ----------------------------------------------------------------------------
600 // wxDynamiClassInfo
601 // ----------------------------------------------------------------------------
602
603 wxDynamicClassInfo::wxDynamicClassInfo( const wxChar *unitName, const wxChar *className , const wxClassInfo* superClass ) :
604 wxClassInfo( unitName, className , new const wxClassInfo*[2])
605 {
606 GetParents()[0] = superClass ;
607 GetParents()[1] = NULL ;
608 }
609
610 wxDynamicClassInfo::~wxDynamicClassInfo()
611 {
612 delete[] GetParents() ;
613 }
614
615 wxObject *wxDynamicClassInfo::AllocateObject() const
616 {
617 wxObject* parent = GetParents()[0]->AllocateObject() ;
618 return new wxDynamicObject( parent , this ) ;
619 }
620
621 void wxDynamicClassInfo::Create (wxObject *object, int paramCount, wxxVariant *params) const
622 {
623 wxDynamicObject *dynobj = dynamic_cast< wxDynamicObject *>( object ) ;
624 wxASSERT_MSG( dynobj , wxT("cannot call wxDynamicClassInfo::Create on an object other than wxDynamicObject") ) ;
625 GetParents()[0]->Create( dynobj->GetSuperClassInstance() , paramCount , params ) ;
626 }
627
628 // get number of parameters for constructor
629 int wxDynamicClassInfo::GetCreateParamCount() const
630 {
631 return GetParents()[0]->GetCreateParamCount() ;
632 }
633
634 // get i-th constructor parameter
635 const wxChar* wxDynamicClassInfo::GetCreateParamName(int i) const
636 {
637 return GetParents()[0]->GetCreateParamName( i ) ;
638 }
639
640 void wxDynamicClassInfo::SetProperty(wxObject *object, const char *propertyName, const wxxVariant &value) const
641 {
642 wxDynamicObject* dynobj = dynamic_cast< wxDynamicObject * >( object ) ;
643 wxASSERT_MSG( dynobj , wxT("cannot call wxDynamicClassInfo::SetProperty on an object other than wxDynamicObject") ) ;
644 if ( FindPropertyInfoInThisClass(propertyName) )
645 dynobj->SetProperty( propertyName , value ) ;
646 else
647 GetParents()[0]->SetProperty( dynobj->GetSuperClassInstance() , propertyName , value ) ;
648 }
649
650 wxxVariant wxDynamicClassInfo::GetProperty(wxObject *object, const char *propertyName) const
651 {
652 wxDynamicObject* dynobj = dynamic_cast< wxDynamicObject * >( object ) ;
653 wxASSERT_MSG( dynobj , wxT("cannot call wxDynamicClassInfo::SetProperty on an object other than wxDynamicObject") ) ;
654 if ( FindPropertyInfoInThisClass(propertyName) )
655 return dynobj->GetProperty( propertyName ) ;
656 else
657 return GetParents()[0]->GetProperty( dynobj->GetSuperClassInstance() , propertyName ) ;
658 }
659
660 void wxDynamicClassInfo::AddProperty( const wxChar *propertyName , const wxTypeInfo* typeInfo )
661 {
662 new wxPropertyInfo( m_firstProperty , propertyName , typeInfo , new wxGenericPropertyAccessor( propertyName ) , wxxVariant() ) ;
663 }
664
665 void wxDynamicClassInfo::AddHandler( const wxChar *handlerName , wxObjectEventFunction address , const wxClassInfo* eventClassInfo )
666 {
667 new wxHandlerInfo( m_firstHandler , handlerName , address , eventClassInfo ) ;
668 }
669
670 // ----------------------------------------------------------------------------
671 // wxGenericPropertyAccessor
672 // ----------------------------------------------------------------------------
673
674 struct wxGenericPropertyAccessor::wxGenericPropertyAccessorInternal
675 {
676 char filler ;
677 } ;
678
679 wxGenericPropertyAccessor::wxGenericPropertyAccessor( const wxString& propertyName )
680 : wxPropertyAccessor( NULL , NULL , NULL , NULL )
681 {
682 m_data = new wxGenericPropertyAccessorInternal ;
683 m_propertyName = propertyName ;
684 m_getterName = wxT("Get")+propertyName ;
685 m_setterName = wxT("Set")+propertyName ;
686 }
687
688 wxGenericPropertyAccessor::~wxGenericPropertyAccessor()
689 {
690 delete m_data ;
691 }
692 void wxGenericPropertyAccessor::SetProperty(wxObject *object, const wxxVariant &value) const
693 {
694 wxDynamicObject* dynobj = dynamic_cast< wxDynamicObject * >( object ) ;
695 wxASSERT_MSG( dynobj , wxT("cannot call wxDynamicClassInfo::SetProperty on an object other than wxDynamicObject") ) ;
696 dynobj->SetProperty(m_propertyName , value ) ;
697 }
698
699 void wxGenericPropertyAccessor::GetProperty(const wxObject *object, wxxVariant& value) const
700 {
701 const wxDynamicObject* dynobj = dynamic_cast< const wxDynamicObject * >( object ) ;
702 wxASSERT_MSG( dynobj , wxT("cannot call wxDynamicClassInfo::SetProperty on an object other than wxDynamicObject") ) ;
703 value = dynobj->GetProperty( m_propertyName ) ;
704 }
705 #endif