root/library/bdm/base/user_info.h @ 659

Revision 659, 23.0 kB (checked in by mido, 15 years ago)

synchronization of documentation pages names

  • Property svn:eol-style set to native
RevLine 
[396]1/*!
2  \file
[471]3  \brief UI (user info) class for loading/saving objects from/to configuration files.
[399]4  It is designed with use of libconfig C/C++ Configuration File Library
[659]5  \ref ui
[396]6  \author Vaclav Smidl.
7
8  -----------------------------------
9  BDM++ - C++ library for Bayesian Decision Making under Uncertainty
10
11  Using IT++ for numerical operations
12  -----------------------------------
13*/
14
[357]15#ifndef USER_INFO_H
16#define USER_INFO_H
[254]17
[344]18#include <stdio.h>
19#include <string>
20#include <typeinfo>
21#include <map>
[396]22#include <stdexcept>
[243]23
[351]24#include "libconfig/libconfig.h++"
[384]25#include "../bdmroot.h"
[527]26#include "../shared_ptr.h"
[357]27#include "itpp/itbase.h"
[351]28
29
[344]30using std::string;
31using namespace std;
[281]32using namespace libconfig;
[246]33
[471]34namespace bdm {
[246]35
[540]36//! Generic exception for reporting configuration errors
[471]37//!
[659]38//!  \ref ui
[471]39class UIException : public std::exception {
[540]40private:
[396]41        //! Error message
42        const string message;
[271]43
[540]44public:
45        /*!
46          \brief The constructor
47          \param message the reason for throwing the exception. Should be a complete English sentence (or a couple sentences), starting with "UIException: ".
48        */
49        UIException ( const string &message ) :
50                message ( message ) {
51        }
[471]52
[540]53        //! Overriden method for reporting the error message
54        virtual const char* what() const throw() {
55                return message.c_str();
56        }
[483]57
[540]58        ~UIException() throw() {};
59
60protected:
61        /*!
62          Formats error messages for derived classes, which use a
63          Setting path in addition to the message.
64        */
65        static string format_message( const string &reason, const string &path );
66};
67
68//! Exception for reporting configuration errors related to some concrete Setting path
69//!
[659]70//!  \ref ui
[540]71class UISettingException : public UIException {
[483]72public:
[396]73        //! Use this constructor when you can pass the problematical Setting as a parameter
[540]74        UISettingException ( const string &message, const Setting &element ):
75                UIException ( format_message ( message, string ( element.getPath() ) ) ) {
[396]76        }
77
78        //! This constructor is for other occasions, when only path of problematical Setting is known
[540]79        UISettingException ( const string &message, const string &path ):
80                UIException ( format_message ( message, path ) ) {
[396]81        }
82
[540]83        ~UISettingException() throw() {};
84};
85
86//! Exception for reporting configuration errors in the "class" attribute
87//!
[659]88//!  \ref ui
[540]89class UIClassException : public UIException {
90public:
91        //! Use this constructor when you can pass the problematical Setting as a parameter
92        UIClassException ( const string &message, const Setting &element ):
93                UIException ( format_message ( message, string ( element.getPath() ) ) ) {
[396]94        }
[483]95
[540]96        //! This constructor is for other occasions, when only path of problematical Setting is known
97        UIClassException ( const string &message, const string &path ):
98                UIException ( format_message ( message, path ) ) {
99        }
[483]100
[540]101        ~UIClassException() throw() {};
[392]102};
[246]103
[394]104/*!
[471]105@brief This class serves to load and/or save user-infos into/from
[394]106configuration files stored on a hard-disk.
[357]107
[471]108Firstly, save some user-infos into the new UIFile instance. Then,
[394]109call the save method with a filename as its only argument:
[351]110
[344]111\code
112        CAudi audi;
[394]113        UIFile file;
114        UI::save( audi, file, "TT");
115        file.save("cars.cfg");
[344]116\endcode
117
[471]118In the other way round, when loading object from a configuration file,
[394]119the appropriate code looks like this:
120
[344]121\code
[394]122        UIFile file("cars.cfg");
123        CAudi *audi = UI::build<CAudi>(file,"TT");
[344]124\endcode
[471]125
[659]126\ref ui
[344]127*/
[471]128class UIFile : public Config {
[394]129public:
[396]130        //! Create empty file instance prepared to store Settings
[394]131        UIFile();
[344]132
[396]133        //! Creates instance and fills it from the configuration file file_name
[471]134        UIFile ( const string &file_name );
[344]135
[396]136        //! Save all the stored Settings into the configuration file file_name
[471]137        void save ( const string &file_name );
[394]138
[396]139        //! This operator allows the ability of substituting Setting parameter by UIFile instance
[394]140        operator Setting&();
141};
142
[396]143/*!
[471]144@brief This class serves to expand links used within configuration files.
[394]145
[396]146Value of any type but string can be linked to some other value of the same type
147defined elsewhere in the current configuration file or even in some different
[471]148configuration file.
[394]149
[471]150Link have three parts, \<name\> : \<path\> \<\@filename\>. Field \<name\> contains the
[396]151name of the new setting, \<path\> is the relative path to the referenced setting, which
[471]152has to be taken from the %root Setting element. The last part \<\@filename\> is optional,
[396]153it contains filename in the case the link should refer to a variable stored in a different
154file. From the previous part \<path\>, it has to be separated by '@'.
155
156\code
157    ...
[471]158        jardovo :
[396]159        {
160          class = "Car";
161          year = 1992;
162          manufacturer = "liaz";
163          kilometers = 1555000;
164        };
[471]165        ondrejovo :
[396]166        {
167          class = "Bike";
168          year = 1996;
169          manufacturer = "author";
170          electricLights = true;
171          matr = ( 2, 2, [ 1.0, 0.0, 0.0, 1.0 ] );
172        };
[471]173
174        #this is the example of local link to another mean of transport
[396]175        elisky = "jardovo";
176
177        ...
178
[471]179        # And this link is external link pointing to the file "other_cars.cfg" stored in the
180        # same directory. In that file, it refers to the local Setting "magic_cars.skubankovo".
[396]181        kati = "magic_cars.skubankovo@other_cars.cfg";
182
183    ...
184\endcode
185
186When you want to expand a possible linked setting "element" within your code, it has to be treated this way:
187
188\code
189        ...
190
191        const SettingResolver link( element );
192
193        ...
194
195        int len = link.result.getLength();
196
197        ...
198\endcode
199
[471]200The whole point is that a resolved link (class member #result, i.e., "link.result" in the previous example) could point
[396]201into a different configuration file. In that case there has to be an UIFile instance managing reading from this
[471]202file. As the libconfig::Config deletes all its Settings when dealocated, UIFile must not be dealocated until all
203the necessary operation on the linked Setting are finished (otherwise, the link #result would be invalid just after
204the UIFile dealocation). And that is exactly the mechanism implemented within SettingResolver class. It assures,
[396]205that the #result Setting reference is valid within the scope of SettingResolver instance.
[477]206
[659]207\ref ui
[396]208 */
[471]209class SettingResolver : root {
[396]210private:
211        //! If necessary, this pointer stores an addres of an opened UIFile, else it equals NULL
212        UIFile *file;
213
214        //! This method initialize #result reference, i.e., it executes the main code of SettingResolver class
215        //!
[471]216        //! This code could be also located directly in constructor. The only reason why we made this
[396]217        //! method is the keyword 'const' within the declaration of #result reference . Such a reference
218        //! have to be intialized before any other constructor command, exactly in the way it is implemented now.
[471]219        const Setting &initialize_reference ( UIFile* &file, const Setting &potential_link );
[396]220
221public:
222        //! Reference to a resolved link or to the original Setting in the case it does not contain a link
223        const Setting &result;
224
225        //! If potential_link contains a link to some other setting, it is resolved here. Anyway, the Setting reference #result is prepared for use.
[471]226        SettingResolver ( const Setting &potential_link );
227
[396]228        //! An opened UIFile file is closed here if necessary.
[471]229        ~SettingResolver();
[396]230};
231
[344]232/*!
[471]233@brief UI is an abstract class which collects all the auxiliary functions useful to prepare some concrete
234user-infos.
[344]235
[600]236See static methods 'build', 'get' and 'save'. Writing user-infos with these methods is rather simple. The
[471]237rest of this class is intended for internal purposes only. Its meaning is to allow pointers to its templated
238descendant ParticularUI<T>.
239
[659]240\ref ui
[344]241*/
[471]242class UI {
[344]243private:
[417]244        //! Class with state shared across all its instances ("monostate"), encapsulating two maps, one mapping names to UI instances and the other mapping type_infos to class names
[471]245        //!
[417]246        //! The key property of this class is that it initializes the internal maps on global init,
[471]247        //! before the instance is used for a first time. Therefore, we do not have to care about initialization
[396]248        //! during a call of UIREGISTER macro operating with both these mappings.
[471]249        class MappedUI {
[344]250        private:
[394]251                //! Type definition of mapping which transforms class names to the related UI instances
252                typedef map< const string, const UI* const > StringToUIMap;
[344]253
[394]254                //! Type definition of mapping which transforms RTTI type_infos to the related class names
255                typedef map< const type_info * const, const string > TypeInfoToStringMap;
[344]256
[396]257                //! Immediately initialized instance of type StringToUIMap
[394]258                static StringToUIMap& mapped_strings();
[351]259
[396]260                //! Immediately initialized instance of type TypeInfoToStringMap
[394]261                static TypeInfoToStringMap& mapped_type_infos();
[351]262
[396]263                //! Method for reporting a error when an attempt to operate with an unregistered class occures
[471]264                static void unregistered_class_error ( const string &unregistered_class_name );
[394]265
[344]266        public:
[396]267                //! Add a pair key-userinfo into the internal map
[471]268                static void add_class ( const string &class_name, const type_info * const class_type_info, const UI* const ui );
[344]269
[396]270                //! Search for an userinfo related to the passed class name within the internal map
[471]271                static const UI& retrieve_ui ( const string &class_name );
[351]272
[396]273                //! Search for an class name related to the passed type_info within the internal map
[471]274                static const string& retrieve_class_name ( const type_info* const class_type_info );
[344]275        };
[351]276
[396]277        //! Function assertting that the setting element is of the SettingType type
[471]278        static void assert_type ( const Setting &element, Setting::Type type );
[396]279
[527]280        /*!
281          \brief Method constructing a configured instance
282
283          The returned pointer must be allocated using operator new
284          (it's deleted at the end of its life cycle). The method is
285          implemented in descendant class ParticularUI<T>, which knows
286          the correct type T.
287        */
[390]288        virtual root* new_instance() const = 0;
[246]289
[471]290        //! Method switching from the \a element to its child Setting according the passed \a index, it also does all the necessary error-checking
291        static const Setting& to_child_setting ( const Setting &element, const int index );
[351]292
[471]293        //! Method switching from the \a element to its child Setting according the passed \a name, it also does all the necessary error-checking
294        static const Setting& to_child_setting ( const Setting &element, const string &name );
295
296        //! This method converts a Setting into a matrix
297        static void from_setting ( mat& matrix, const Setting &element );
[396]298        //! This method converts a Setting into an integer vector
[471]299        static void from_setting ( ivec &vector, const Setting &element );
[396]300        //! This method converts a Setting into a string
[471]301        static void from_setting ( string &str, const Setting &element );
[396]302        //! This method converts a Setting into a real vector
[471]303        static void from_setting ( vec &vector, const Setting &element );
304        //! This method converts a Setting into a integer scalar
305        static void from_setting ( int &integer, const Setting &element );
306        //! This method converts a Setting into a real scalar
307        static void from_setting ( double &real, const Setting &element );
[527]308
[396]309        //! This method converts a Setting into a class T descendant
[471]310        template<class T> static void from_setting ( T* &instance, const Setting &element ) {
311                const SettingResolver link ( element );
312                assert_type ( link.result, Setting::TypeGroup );
[246]313
[471]314                // we get a value stored in the "class" attribute
[345]315                string class_name;
[471]316                if ( !link.result.lookupValue ( "class", class_name ) )
[540]317                        throw UIClassException ( "UIException: the obligatory \"class\" identifier is missing.", link.result );
[471]318
[394]319                // then we find a user-info related to this type
[471]320                const UI& related_UI = MappedUI::retrieve_ui ( class_name );
321
[527]322                root *typeless_instance = related_UI.new_instance();
[620]323                bdm_assert ( typeless_instance, "UI::new_instance failed" );
[344]324
[471]325                instance = dynamic_cast<T*> ( typeless_instance );
[527]326                if ( !instance ) {
327                        delete typeless_instance;
[540]328                        throw UIClassException ( "UIException: class " + class_name + " is not a descendant of the desired output class. Try to call the UI::build<T> function with a different type parameter.", link.result );
[527]329                }
[471]330
331                try {
332                        instance->from_setting ( link.result );
[544]333                } catch ( SettingException &sttng_xcptn ) {
334                        delete instance;
335                        instance = 0;
[540]336                        string msg = "UIException: method ";
337                        msg += class_name;
338                        msg += ".from_setting(Setting&) has thrown a SettingException.";
339                        throw UISettingException(msg, sttng_xcptn.getPath());
[635]340                } catch (std::runtime_error &e) {
341                        delete instance;
342                        instance = 0;
343                        string msg = "UIException: method ";
344                        msg += class_name;
345                        msg += " says: ";
346                        msg += e.what();
347                        throw UISettingException(msg, link.result);
[544]348                } catch (...) {
349                        delete instance;
350                        instance = 0;
351                        throw;
[344]352                }
[635]353}
[344]354
[527]355        //! This method converts a Setting into a descendant of class
356        //! T, wrapped in an instance of shared_ptr<T> .
357        template<class T>
358        static void from_setting ( shared_ptr<T> &instance, const Setting &element ) {
359                T *tmp_inst = 0;
360                from_setting ( tmp_inst, element );
[620]361                bdm_assert ( tmp_inst, "UI::from_setting failed" );
[527]362                instance = tmp_inst;
363        }
364
[396]365        //! This methods converts a Setting into a new templated array of type Array<T>
[478]366        template<class T> static void from_setting ( Array<T> &array_to_load, const Setting &element ) {
[471]367                const SettingResolver link ( element );
[345]368
[471]369                assert_type ( link.result, Setting::TypeList );
[344]370
[377]371                int len = link.result.getLength();
[471]372                array_to_load.set_length ( len );
373                if ( len == 0 ) return;
374
375                for ( int i = 0; i < len; i++ )
376                        from_setting ( array_to_load ( i ), link.result[i] );
[281]377        }
[243]378
[471]379        //! This is dummy version of the from_setting method for other, unsupported types. It just throws an exception.
380        //!
381        //! At the moment, this is the only way how to compile the library without obtaining the compiler error c2665.
382        //! The exception can help to find the place where the template is misused and also to correct it.
[478]383        template<class T> static void from_setting ( T &variable_to_load, const Setting &element ) {
[540]384                std::string msg = "UIException: from_setting is not implemented for type ";
[527]385                msg += typeid(T).name();
[540]386                msg += '.';
387                throw UISettingException ( msg, element );
[471]388        }
389
390
[345]391protected:
[534]392        //! Constructor for internal use only, see \sa ParticularUI<T>
[471]393        UI ( const string& class_name, const type_info * const class_type_info ) {
394                MappedUI::add_class ( class_name, class_type_info, this );
[345]395        }
396
[471]397public:
[281]398
[534]399        //! Enum type used to determine whether the data for concrete Settingis is compulsory or optional
[471]400        enum SettingPresence { optional, compulsory } ;
401
402        //! \name Initialization of classes
[396]403        //!@{
[471]404        //! The type T has to be a #bdm::root descendant class
[394]405
[396]406        //! The new instance of type T* is constructed and initialized with values stored in the Setting element[name]
[471]407        //!
[527]408        //! If there is not any sub-element named #name and settingPresence is #optional, an empty shared_ptr<T> is returned. When settingPresence is #compulsory, the returned shared_ptr<T> is never empty (an exception is thrown when the object isn't found).
409        template<class T>
410        static shared_ptr<T> build ( const Setting &element, const string &name, SettingPresence settingPresence = optional ) {
[477]411                if ( !element.exists ( name ) ) {
412                        if ( settingPresence == optional )
[527]413                                return shared_ptr<T>();
[471]414                        else
[540]415                                throw UISettingException ( "UIException: the compulsory Setting named \"" + name + "\" is missing.", element );
[471]416                }
417
[527]418                shared_ptr<T> instance;
[471]419                from_setting<T> ( instance, to_child_setting ( element, name ) );
[396]420                return instance;
421        }
[471]422
[396]423        //! The new instance of type T* is constructed and initialized with values stored in the Setting element[index]
[471]424        //!
[534]425        //! If there is not any sub-element indexed by #index, and settingPresence is #optional, an empty shared_ptr<T> is returned. When settingPresence is #compulsory, the returned shared_ptr<T> is never empty (an exception is thrown when the object isn't found).
426        template<class T>
427        static shared_ptr<T> build ( const Setting &element, const int index, SettingPresence settingPresence = optional ) {
[477]428                if ( element.getLength() <= index ) {
429                        if ( settingPresence == optional )
[534]430                                return shared_ptr<T>();
[477]431                        else {
[471]432                                stringstream stream;
433                                stream << index;
[540]434                                throw UISettingException ( "UIException: the compulsory Setting with the index " + stream.str() + " is missing.", element );
[471]435                        }
436                }
437
[534]438                shared_ptr<T> instance;
[471]439                from_setting<T> ( instance, to_child_setting ( element, index ) );
[351]440                return instance;
[344]441        }
[471]442
[394]443        //!@}
[281]444
[471]445        //! \name Initialization of structures
[394]446        //!@{
[396]447        //! The type T has to be int, double, string, vec, ivec or mat.
448
449        //! The existing instance of type T is initialized with values stored in the Setting element[name]
[471]450        //! If there is not any sub-element named #name, this method returns false.
451        template<class T> static bool get ( T &instance, const Setting &element, const string &name, SettingPresence settingPresence = optional ) {
[477]452                if ( !element.exists ( name ) ) {
453                        if ( settingPresence == optional )
[471]454                                return false;
455                        else
[540]456                                throw UISettingException ( "UIException: the compulsory Setting named \"" + name + "\" is missing.", element );
[471]457                }
458
459                from_setting ( instance, to_child_setting ( element, name ) );
460                return true;
[344]461        }
462
[471]463        //! The existing instance of type T is initialized with values stored in the Setting element[index]
464        //! If there is not any sub-element indexed by #index, this method returns false.
465        template<class T> static bool get ( T &instance, const Setting &element, const int index, SettingPresence settingPresence = optional ) {
[477]466                if ( element.getLength() <= index ) {
467                        if ( settingPresence == optional )
[471]468                                return false;
[477]469                        else {
[471]470                                stringstream stream;
[540]471                                stream << "UIException: the compulsory Setting with the index " << index << " is missing.";
[471]472                                stream << index;
[540]473                                throw UISettingException (stream.str(), element );
[471]474                        }
475                }
476
477                from_setting ( instance, to_child_setting ( element, index ) );
478                return true;
[344]479        }
480
[396]481        //! The existing instance of type T is initialized with values stored in the Setting element directly
[471]482        template<class T> static bool get ( T &instance, const Setting &element ) {
483                from_setting ( instance, element );
484                return true;
[394]485        }
[396]486        //!@}
[394]487
[396]488        //! \name Initialization of arrays Array<T>
489        //!@{
490        //! The type T has to be int, double, string, vec, ivec or mat, or pointer to any root descendant.
491
492        //! The existing array of type T is initialized with values stored in the Setting element[name]
[471]493        //! If there is not any sub-element named #name, this method returns false.
494        template<class T> static bool get ( Array<T> &array_to_load, const Setting &element, const string &name, SettingPresence settingPresence = optional ) {
495                if ( !element.exists ( name ) )
496                        return false;
497
498                from_setting ( array_to_load, to_child_setting ( element, name ) );
499                return true;
[344]500        }
501
[396]502        //! The existing array of type T is initialized with values stored in the Setting element[index]
[471]503        //! If there is not any sub-element indexed by #index, this method returns false.
504        template<class T> static bool get ( Array<T> &array_to_load, const Setting &element, const int index, SettingPresence settingPresence = optional ) {
505                if ( element.getLength() <= index )
506                        return false;
507
508                from_setting ( array_to_load, to_child_setting ( element, index ) );
509                return true;
[344]510        }
511
[396]512        //! The existing array of type T is initialized with values stored in the Setting element
[471]513        template<class T> static bool get ( Array<T> &array_to_load, const Setting &element ) {
514                from_setting ( array_to_load, element );
515                return true;
[394]516        }
517        //!@}
518
[471]519        //! \name Serialization of objects and structures into a new Setting
[396]520        //!@{
521        //! The new child Setting can be accessed either by its name - if some name is passed as a parameter -
522        //! or by its integer index. In that case, the new element is added at the very end of the current list of child Settings.
523
524        //! A root descendant instance is stored in the new child Setting appended to the passed element
[471]525        template< class T> static void save ( const T * const instance, Setting &element, const string &name = "" ) {
526                Setting &set = ( name == "" ) ? element.add ( Setting::TypeGroup )
527                               : element.add ( name, Setting::TypeGroup );
[344]528
[471]529                const string &class_name = MappedUI::retrieve_class_name ( &typeid ( *instance ) );
530
531                // add attribute "class"
532                Setting &type = set.add ( "class", Setting::TypeString );
[351]533                type = class_name;
[344]534
[471]535                try {
536                        instance->to_setting ( set );
[544]537                } catch ( SettingException &sttng_xcptn ) {
[540]538                        string msg = "UIException: method ";
539                        msg += class_name;
540                        msg += ".to_setting(Setting&) has thrown a SettingException.";
541                        throw UISettingException(msg, sttng_xcptn.getPath());
[351]542                }
[344]543        }
544
[527]545        template< class T> static void save ( const shared_ptr<T> &instance, Setting &element, const string &name = "" ) {
546                save<T> ( instance.get(), element, name );
547        }
548
[396]549        //! An Array<T> instance is stored in the new child Setting appended to the passed element
[471]550        template<class T> static void save ( const Array<T> &array_to_save, Setting &element, const string &name = "" ) {
551                assert_type ( element, Setting::TypeGroup );
552                Setting &list = ( name == "" ) ? element.add ( Setting::TypeList )
553                                : element.add ( name, Setting::TypeList );
554                for ( int i = 0; i < array_to_save.length(); i++ )
555                        save ( array_to_save ( i ), list );
[344]556        }
[351]557
[396]558        //! A matrix(of type mat) is stored in the new child Setting appended to the passed element
[471]559        static void save ( const mat &matrix, Setting &element, const string &name = "" );
[351]560
[396]561        //! An integer vector (of type ivec) is stored in the new child Setting appended to the passed element
[471]562        static void save ( const ivec &vec, Setting &element, const string &name = "" );
563
[396]564        //! A double vector (of type vec) is stored in the new child Setting appended to the passed element
[471]565        static void save ( const vec &vector, Setting &element, const string &name = "" );
[396]566
567        //! A string is stored in the new child Setting appended to the passed element
[471]568        static void save ( const string &str, Setting &element, const string &name = "" );
[351]569
[396]570        //! An integer is stored in the new child Setting appended to the passed element
[471]571        static void save ( const int &integer, Setting &element, const string &name = "" );
[394]572
[396]573        //! A double is stored in the new child Setting appended to the passed element
[471]574        static void save ( const double &real, Setting &element, const string &name = "" );
[396]575        //!@}
[394]576
[281]577};
578
579
[493]580//! The only UI descendant class which is not intended for direct use. It should be accessed within the UIREGISTER macro only.
[659]581//! \ref ui
[471]582template<typename T> class ParticularUI : private UI {
[493]583public:
584        //! Constructor used by the UIREGISTER macro.
[471]585        ParticularUI<T> ( const string &class_name ) : UI ( class_name, &typeid ( T ) ) {};
[344]586
[529]587        //! A method returning a brand new instance of class T, this method is the reason why there have to be a parameterless constructor in class T
[471]588        root* new_instance() const {
[344]589                return new T();
590        }
[281]591};
592
[344]593}
594
[471]595/*!
596  \def UIREGISTER(class_name)
597  \brief Macro for registration of class into map of user-infos, registered class is scriptable using UI static methods
598
[529]599  Argument \a class_name has to be a descendant of root class and also to have a default constructor.
[471]600  This macro should be used in header file, immediately after a class declaration.
601
[659]602  \ref ui
[471]603*/
604#ifndef BDMLIB
[535]605#define UIREGISTER(class_name) static bdm::ParticularUI<class_name> UI##class_name(#class_name)
[471]606#else
607#define UIREGISTER(class_name)
608#endif
609
[535]610//! Instrumental macro for UIREGISTER2
611#define QUOTEME(x) #x
612
613/*!
614  \def UIREGISTER2(class_name,template_name)
615  \brief Variant of UIREGISTER for templated classes
616
617  Technical meann of registering UIREGISTER(class_name<template_name>).
618
[659]619  \ref ui
[535]620 */
621#ifndef BDMLIB
622#define UIREGISTER2(class_name, temp_name) static bdm::ParticularUI<class_name<temp_name> > UI##class_name##_##temp_name( QUOTEME(class_name<temp_name>) )
623#else
624#define UIREGISTER2(class_name,temp_name)
625#endif
626
[358]627#endif // #ifndef USER_INFO_H
Note: See TracBrowser for help on using the browser.