vgfx-util.cpp

Go to the documentation of this file.
00001 /*
00002  * vgfx-util.cpp
00003  *
00004  * Copyright (C) 2007,2009  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Implementation of DAG helper routines for vgfx objects.
00032  * (see vgfx-util.h)
00033  */
00034 
00035 // includes --------------------------------------------------------------------
00036 #include "vgfx-util.h"          // always include our own header first!
00037 
00038 #include "common/wave_ex.h"
00039 #include "perf/perf.h"
00040 #include "util/file.h"
00041 #include "util/parsing.h"
00042 #include "util/token_stream.h"
00043 
00044 #include "request.h"
00045 
00046 
00047 namespace vgfx {
00048 
00049 static const eParseBehavior s_line_behavior     = (eParseBehavior) (
00050         eParse_StripComments |
00051         eParse_StripBogus |
00052         eParse_RespectQuotes
00053         );
00054 
00055 
00056 // root node for all drag/drop/copy/paste collections
00057 static const char * s_keyTop                    = "collectionRoot";
00058 
00059 NodeChecker::~NodeChecker(void) throw() { }
00060 
00061 
00062 ////////////////////////////////////////////////////////////////////////////////
00063 //
00064 //      static helper methods
00065 //
00066 ////////////////////////////////////////////////////////////////////////////////
00067 
00068 static bool
00069 containsSpace
00070 (
00071 IN const char * p
00072 )
00073 throw()
00074 {
00075         ASSERT(p, "null");
00076 
00077         for(; *p; ++p) {
00078                 if (isspace(*p))
00079                         return true;
00080         }
00081 
00082         return false;
00083 }
00084 
00085 
00086 
00087 static void
00088 getIdPrefix
00089 (
00090 IN const char * id,
00091 OUT std::string& prefix
00092 )
00093 {
00094         ASSERT(id, "null");
00095         prefix.clear();
00096 
00097         const char * p = id;
00098         while (*p && *p != '_') {
00099                 prefix += *p;
00100                 ++p;
00101         }
00102 }
00103 
00104 
00105 
00106 void
00107 addDependenciesToGraphInternal
00108 (
00109 IN ObjectMap * map,
00110 IN const char * id,
00111 IO graph::DAG * dag
00112 )
00113 {
00114         ASSERT(map, "null");
00115         ASSERT(id, "null");
00116         ASSERT(dag, "null");
00117 
00118         // look up this object
00119         Primitive * p = map->findObject(id);
00120         ASSERT(p, "Object does not exist? '%s'", id);
00121 
00122         if (strcmp("group", p->getType())) {
00123                 return;         // not a group?  stop recursing
00124         }
00125 
00126         // is this a path?  Self-contained
00127         VecString path;
00128         path.push_back("meta");
00129         path.push_back("vgfxPath");
00130         if (p->doesContainerExist(path)) {
00131                 dag->addNode(id);
00132                 return;
00133         }
00134 
00135         // get all subobjects for p
00136         path.clear();
00137         path.push_back("object");
00138         VecString ids;
00139         p->listContainers(path, ids);
00140 
00141         // loop through all subobjects
00142         for (VecString::iterator i = ids.begin(); i != ids.end(); ++i) {
00143                 VecString obj_path;
00144                 obj_path.push_back("object");
00145                 obj_path.push_back(*i);
00146 
00147                 dictionary_t data;
00148                 p->getContainerDictionary(obj_path, data);
00149 
00150                 // get ID
00151                 const char * sub_id = getRequiredValue(data, "id");
00152 
00153                 // add to dag
00154                 dag->addEdge(id, sub_id);
00155 
00156                 // recurse
00157                 addDependenciesToGraphInternal(map, sub_id, dag);
00158         }
00159 }
00160 
00161 
00162 
00163 static void
00164 replaceIDs
00165 (
00166 IN ObjectMap * map,
00167 IN std::istream& in,
00168 OUT std::string& request
00169 )
00170 {
00171         ASSERT(map, "null");
00172         ASSERT(in.good(), "bad");
00173         request = "";
00174 
00175         // DPRINTF("parsing input request and replacing IDs...");
00176 
00177         // given an input stream, find all IDs and replace them
00178         Dictionary old_new;
00179         old_new[s_keyTop] = s_keyTop;
00180 
00181         // copy data as we parse into a new stream
00182         std::ostringstream out;
00183 
00184         std::string line, token;
00185 
00186         while (!in.eof()) {
00187                 line = getNextLineFromStream(in, s_line_behavior);
00188 
00189                 // look for any IDs in the line
00190                 const char * cursor = line.c_str();
00191                 while (true) {
00192                         cursor = getNextTokenFromString(cursor, token,
00193                             eParse_RespectQuotes);
00194                         if ("" == token) {
00195                                 out << "\n";
00196                                 break;  // end of line
00197                         }
00198                         // DPRINTF("  parsed '%s'", token.c_str());
00199 
00200                         if (containsSpace(token.c_str())) {
00201                                 out << "\"" << token << "\"";
00202                         } else {
00203                                 out << token;
00204                         }
00205                         out << " ";
00206                         if ("set" == token) {
00207                                 // it's a set instruction.  get next token and
00208                                 //      replace the ID
00209                                 cursor = getNextTokenFromString(cursor, token,
00210                                     eParse_None);
00211                                 if ("" == token)
00212                                         continue;       // not a real set
00213                                 // extract old ID
00214                                 std::string old_id;
00215                                 const char * p = token.c_str();
00216                                 while (*p && '.' != *p) {
00217                                         old_id += *p;
00218                                         ++p;
00219                                 }
00220                                 // DPRINTF("'%s' --> id='%s', remainder = '%s'",
00221                                 //    token.c_str(), old_id.c_str(), p);
00222 
00223                                 Dictionary::iterator i = old_new.find(old_id);
00224                                 if (old_new.end() == i) {
00225                                         WAVE_EX(wex);
00226                                         wex << "ID not yet encountered in set:";
00227                                         wex << "'" << old_id << "'";
00228                                 }
00229                                 out << i->second;
00230                                 if (*p) {
00231                                         out << p;
00232                                 }
00233                                 out << " ";
00234                                 continue;
00235 
00236                         } else if ("id" != token)
00237                                 continue;       // not an "id" tag
00238 
00239                         // it's an ID field!  get the next token (the ID)
00240                         cursor =
00241                             getNextTokenFromString(cursor, token, eParse_None);
00242                         if ("" == token) {
00243                                 WAVE_EX(wex);
00244                                 wex << "malformed drag/drop stream--missing ";
00245                                 wex << "token after id specifier";
00246                         }
00247                         // DPRINTF("    Need to replace '%s'", token.c_str());
00248 
00249                         Dictionary::iterator i = old_new.find(token);
00250                         if (old_new.end() == i) {
00251                                 std::string prefix;
00252                                 getIdPrefix(token.c_str(), prefix);
00253                                 std::string new_id;
00254                                 map->newObjectID(prefix.c_str(), new_id);
00255                                 old_new[token] = new_id;
00256                                 out << new_id;
00257                         } else {
00258                                 out << i->second;
00259                         }
00260                         out << " ";
00261                 }
00262         }
00263 
00264         // all done
00265         request = out.str();
00266 }
00267 
00268 
00269 
00270 ////////////////////////////////////////////////////////////////////////////////
00271 //
00272 //      public API
00273 //
00274 ////////////////////////////////////////////////////////////////////////////////
00275 
00276 const char *
00277 getDragDropRootNodeID
00278 (
00279 void
00280 )
00281 throw()
00282 {
00283         return s_keyTop;
00284 }
00285 
00286 
00287 
00288 void
00289 getDragRequest
00290 (
00291 IN ObjectMap * map,
00292 IN Primitive * root,
00293 IN const SetString& selection,
00294 IN float x_cm,
00295 IN float y_cm,
00296 OUT std::string& request
00297 )
00298 {
00299         perf::Timer timer("vgfx::getDragRequest");
00300         ASSERT(map, "null");
00301         ASSERT(root, "null");
00302 
00303         // first, figure out what items we need
00304         xform_2d_t T;           // identity transform
00305         smart_ptr<graph::DAG> dag = graph::DAG::create();
00306         ASSERT(dag, "failed to create directed acyclic graph");
00307         for (SetString::const_iterator i = selection.begin();
00308              i != selection.end(); ++i) {
00309                 const char * tag_path = i->c_str();
00310 
00311                 visit_result_t vr;
00312                 root->getPrimitive(tag_path, T, vr);
00313                 ASSERT(vr.p, "could not find in folio: '%s'", tag_path);
00314 
00315                 addDependenciesToGraph(map, vr.p->getID(), dag);
00316         }
00317 
00318         // get the list of all the ids we really need
00319         VecString ids;
00320         dag->getOrderedNodeList(ids);
00321 
00322         // get a request to add all items
00323         Request r;
00324         std::ostream& oss = r.getStream();
00325 
00326         for (VecString::iterator i = ids.begin(); i != ids.end(); ++i) {
00327                 const char * id = i->c_str();
00328 
00329                 Primitive * p = map->findObject(id);
00330                 ASSERT(p, "could not find in map: '%s'", id);
00331 
00332                 oss << "add ";
00333                 p->persist(oss);
00334                 oss << "\n";
00335         }
00336 
00337         // create top-level group that has all of the objects
00338         r.addSimpleGroup(s_keyTop);
00339         for (SetString::const_iterator i = selection.begin();
00340              i != selection.end(); ++i) {
00341                 const char * tag_path = i->c_str();
00342 
00343                 visit_result_t vr;
00344                 root->getPrimitive(tag_path, T, vr);
00345                 ASSERT(vr.p, "could not find in folio: '%s'", tag_path);
00346                 const char * id = vr.p->getID();
00347 
00348                 // map origin from node-local to global
00349                 point_t p(0.0, 0.0);              // node-local origin
00350                 point_t q;                        // global
00351                 vr.T.transformPoint(p, q);
00352 
00353                 float dx = q.x - x_cm;
00354                 float dy = q.y - y_cm;
00355 
00356                 // DPRINTF("'%s' is at (%5.2f, %5.2f) --> (%5.2f, %5.2f)",
00357                 //      tag_path, q.x, q.y, dx, dy);
00358 
00359                 r.addObject(s_keyTop, id, id, dx, dy, 1.0);
00360         }
00361 
00362         request = r.get();
00363 
00364         // DPRINTF("Drag request:\n%s\n", request.c_str());
00365 }
00366 
00367 
00368 
00369 void
00370 addDependenciesToGraph
00371 (
00372 IN ObjectMap * map,
00373 IN const char * id,
00374 IO graph::DAG * dag
00375 )
00376 {
00377         // wrap call in a timer that won't be confused by recursion
00378         perf::Timer timer("vgfx::addDependenciesToGraph");
00379         addDependenciesToGraphInternal(map, id, dag);
00380 }
00381 
00382 
00383 
00384 bool
00385 getDropRequest
00386 (
00387 IN ObjectMap * map,
00388 IN Primitive * root,
00389 IN const SetString& selection,
00390 IN std::istream& drag_request,
00391 IN float x_cm,
00392 IN float y_cm,
00393 IN NodeChecker& okayToReceiveDrop,
00394 OUT std::string& request,
00395 OUT SetString& out_selection
00396 )
00397 {
00398         perf::Timer timer("vgfx::getDropRequest");
00399         ASSERT(map, "null");
00400         ASSERT(drag_request.good(), "not good");
00401         request.clear();
00402         out_selection.clear();
00403 
00404         // replace IDs in incoming request
00405         std::string swapped;
00406         replaceIDs(map, drag_request, swapped);
00407         // DPRINTF("Swapped request:\n%s\n--------", swapped.c_str());
00408 
00409         // create temporary map
00410         smart_ptr<ObjectMap> workspace = ObjectMap::create();
00411         ASSERT(workspace, "failed to create temporary vgfx map");
00412 
00413         // drop there
00414         std::istringstream iss(swapped);
00415         std::string undo, diagnostic;
00416         if (!processRequest(workspace, iss, false, undo, diagnostic)) {
00417                 DPRINTF("Failed to add drag request to map");
00418                 DPRINTF("Diagnostic: %s", diagnostic.c_str());
00419                 return false;
00420         }
00421 
00422         // get root node
00423         Primitive * drag_root = workspace->findObject(s_keyTop);
00424         if (!drag_root) {
00425                 DPRINTF("Drag request did not contain root node: '%s'",
00426                     s_keyTop);
00427                 return false;
00428         }
00429 
00430         // get ordered list of all objects in map
00431         smart_ptr<graph::DAG> dag = graph::DAG::create();
00432         ASSERT(dag, "Failed to create dag");
00433         addDependenciesToGraph(workspace, s_keyTop, dag);
00434         VecString ids;
00435         dag->getOrderedNodeList(ids);
00436         // DPRINTF("%d objects in drag request", ids.size());
00437 
00438         // create output request to add to current map
00439         Request r;
00440 
00441         // remove all selected items
00442         // TODO: actually have separate codepaths for moves vs. drops?
00443         // What I'm doing here is very general: handles drops or moves.
00444         // But the move behavior is funky: items on the canvas are deleted,
00445         //   then added again at the new location with new IDs!
00446         // At the moment I'm sticking with a single code path.
00447         for (SetString::const_iterator i = selection.begin();
00448              i != selection.end(); ++i) {
00449                 const char * tag_path = i->c_str();
00450                 // DPRINTF("removing '%s'...", tag_path);
00451 
00452                 // look up the parent
00453                 std::string parent_path;
00454                 GetParentDirectory(tag_path, parent_path);
00455                 xform_2d_t T;   // identity
00456                 vgfx::visit_result_t vr;
00457                 ASSERT(root->getPrimitive(parent_path.c_str(), T, vr),
00458                     "Parent '%s' does not exist?", parent_path.c_str());
00459                 ASSERT(vr.p, "Should have a primitive now");
00460                 const char * parent_id = vr.p->getID();
00461                 // DPRINTF("  parent_id = '%s'", parent_id);
00462 
00463                 // get tag
00464                 const char * tag = GetFilename(tag_path);
00465 
00466                 // issue removal request
00467                 std::ostream& out = r.getStream();
00468                 out << "remove " << parent_id << ".object." << tag << "\n";
00469         }
00470 
00471         // walk through all but root node, add to request
00472         for (VecString::iterator i = ids.begin(); i != ids.end(); ++i) {
00473                 const char * id = i->c_str();
00474                 if (!strcmp(id, s_keyTop))
00475                         continue;       // this is the root node--skip
00476 
00477                 // look up this object
00478                 Primitive * p = workspace->findObject(id);
00479                 ASSERT(p, "failed to find new id? '%s'", id);
00480 
00481                 std::ostream& out = r.getStream();
00482                 out << "add ";
00483                 p->persist(out);
00484                 out << "\n";
00485         }
00486 
00487         // get children of root node
00488         VecString path, children;
00489         path.push_back("object");
00490         drag_root->listContainers(path, children);
00491 
00492         // for each child, get root-relative coordinates, add delta
00493         for (VecString::iterator i = children.begin(); i != children.end();
00494              ++i) {
00495                 const char * tag = i->c_str();
00496 
00497                 // get child x,y position from root
00498                 path.clear();
00499                 path.push_back("object");
00500                 path.push_back(tag);
00501                 dictionary_t data;
00502                 drag_root->getContainerDictionary(path, data);
00503                 float x = atof(getRequiredValue(data, "x"));
00504                 float y = atof(getRequiredValue(data, "y"));
00505                 float z = atof(getRequiredValue(data, "z"));
00506                 const char * id = getRequiredValue(data, "id");
00507 
00508                 // update for drop position
00509                 x += x_cm;
00510                 y += y_cm;
00511 
00512                 // DPRINTF("Looking for receiver for '%s' at (%5.2f, %5.2f)",
00513                 //     tag, x, y);
00514 
00515                 // TODO: is there an object here that can receive it?
00516                 hit_result_t wr;
00517                 if (!getObjectAt(root, x, y, okayToReceiveDrop, wr))
00518                         continue;       // nope, nothing to receive
00519 
00520                 // DPRINTF("Found object to receive at '%s'", wr.tag_path.c_str());
00521 
00522                 // add to parent
00523                 r.addObject(wr.p->getID(), id, id, wr.x, wr.y, z);
00524 
00525                 // create new tag path
00526                 std::string new_tag = wr.tag_path;
00527                 new_tag += "/";
00528                 new_tag += id;
00529                 out_selection.insert(new_tag);
00530         }
00531 
00532         // have enough children?
00533         // DPRINTF("%d children being dropped", out_selection.size());
00534         if (!out_selection.size())
00535                 return false;
00536 
00537         // successful
00538         request = r.get();
00539         // DPRINTF("Drop request:\n%s\n", request.c_str());
00540 
00541         return true;
00542 }
00543 
00544 
00545 
00546 void
00547 getSelectionRect
00548 (
00549 IN ObjectMap * map,
00550 IN Primitive * root,
00551 IN const SetString& selection,
00552 OUT rect_t& r_cm
00553 )
00554 {
00555         ASSERT(map, "null");
00556         ASSERT(root, "null");
00557         r_cm.set(0.0, 0.0, 0.0, 0.0);
00558         if (!selection.size())
00559                 return; // nothing to do!
00560 
00561         // starting transformation
00562         xform_2d_t T;   // identity
00563 
00564         // loop through and keep expanding rect
00565         for (SetString::const_iterator i = selection.begin();
00566              i != selection.end(); ++i) {
00567                 const char * tag_path = i->c_str();
00568 
00569                 visit_result_t vr;
00570                 ASSERT(root->getPrimitive(tag_path, T, vr),
00571                     "Failed to find selected object at '%s'", tag_path);
00572                 ASSERT(vr.p, "null primitive?");
00573                 rect_t r;
00574                 vr.p->getBoundingRect(r);
00575                 rect_t gr;              // bounding rect in global coordinates
00576                 vr.T.transformRect(r, gr);
00577 
00578                 if (selection.begin() == i)
00579                         r_cm = gr;
00580                 else
00581                         r_cm.inflate(gr);
00582         }
00583 }
00584 
00585 
00586 
00587 // context struct used for getObjectAt
00588 struct walk_context_t {
00589         walk_context_t(IN NodeChecker& nc) throw() :
00590             pwr(NULL),
00591             checker(nc)
00592                 { }
00593 
00594         // data fields
00595         hit_result_t *  pwr;
00596         NodeChecker&    checker;
00597         float           x;      // global x-value
00598         float           y;      // global y-value
00599 };
00600 
00601 
00602 
00603 // callback used for getObjectAt
00604 static bool
00605 getObjectAtCallback
00606 (
00607 IN void * context,
00608 IN visit_result_t& vr
00609 )
00610 {
00611         walk_context_t * pwc = (walk_context_t *) context;
00612         ASSERT(pwc, "null context");
00613         ASSERT(pwc->pwr, "null walk result");
00614         ASSERT(vr.p, "null primitive");
00615         ASSERT(vr.tag_path, "null tag path");
00616 
00617         // what does the node checker say?
00618         if (!pwc->checker(vr))
00619                 return true;    // keep looking
00620 
00621         // this is a hit!
00622         hit_result_t& wr = *(pwc->pwr);
00623         wr.p = vr.p;
00624         wr.tag_path = vr.tag_path;
00625         wr.T = vr.T;
00626 
00627         // get object-relative coordinates
00628         xform_2d_t Tinv;
00629         Tinv.setToInverseOf(wr.T);
00630         point_t q;      // object-relative coordinates
00631         Tinv.transformPoint(point_t(pwc->x, pwc->y), q);
00632         wr.x = q.x;
00633         wr.y = q.y;
00634 
00635         return false;   // stop looking--we hit something
00636 }
00637 
00638 
00639 
00640 bool
00641 getObjectAt
00642 (
00643 IN Primitive * root,
00644 IN float x_cm,
00645 IN float y_cm,
00646 IN NodeChecker& checker,
00647 OUT hit_result_t& wr
00648 )
00649 {
00650         perf::Timer("vgfx::getObjectAt");
00651         ASSERT(root, "null root in getObjectAt()");
00652         wr.clear();
00653 
00654         // set up context
00655         walk_context_t wc(checker);
00656         wc.pwr = &wr;
00657         wc.x = x_cm;
00658         wc.y = y_cm;
00659 
00660         // set up hit rect
00661         rect_t r;
00662         r.set(x_cm, y_cm, x_cm, y_cm);
00663         r.expand(0.01);
00664 
00665         // identity transform (already working in CM)
00666         xform_2d_t T;
00667 
00668         // recursive walk
00669         return !root->visit(r, T, "", getObjectAtCallback, &wc,
00670             vgfx::eHit_Overlap);
00671 }
00672 
00673 
00674 
00675 ////////////////////////////////////////////////////////////////////////////////
00676 //
00677 //      node checkers
00678 //
00679 ////////////////////////////////////////////////////////////////////////////////
00680 
00681 MetaKeyValueChecker::~MetaKeyValueChecker(void) throw() { }
00682 
00683 MetaKeyValueChecker::MetaKeyValueChecker
00684 (
00685 IN const char * key,
00686 IN const char * value
00687 )
00688 {
00689         ASSERT(key, "null key");
00690         ASSERT(value, "null value");
00691 
00692         m_key = key;
00693         m_value = value;
00694 }
00695 
00696 
00697 
00698 bool
00699 MetaKeyValueChecker::checkNode
00700 (
00701 IN visit_result_t& vr
00702 )
00703 {
00704         ASSERT(vr.p, "null pointer");
00705 
00706         // does the key even exist?
00707         VecString path;
00708         path.push_back("meta");
00709         path.push_back(m_key);
00710         if (!vr.p->doesContainerExist(path)) {
00711                 // nope, doesn't have this meta key
00712                 return false;
00713         }
00714 
00715         // key exists.  Does the value match?
00716         dictionary_t data;
00717         vr.p->getContainerDictionary(path, data);
00718         if (m_value != getRequiredValue(data, m_key.c_str())) {
00719                 return false;    // keep looking
00720         }
00721 
00722         // matches!
00723         return true;
00724 }
00725 
00726 
00727 
00728 meta_check_t
00729 getMeta
00730 (
00731 IN Primitive * p,
00732 IN const char * name
00733 )
00734 {
00735         ASSERT(p, "null");
00736         ASSERT(name, "null");
00737         validateHashKeyName(name);
00738 
00739         meta_check_t mc;
00740 
00741         VecString path;
00742         path.push_back("meta");
00743         path.push_back(name);
00744         if (!p->doesContainerExist(path)) {
00745                 return mc;      // no such property
00746         }
00747 
00748         dictionary_t data;
00749         p->getContainerDictionary(path, data);
00750         mc.value = getRequiredValue(data, name);
00751         mc.exists = true;
00752         return mc;
00753 }
00754 
00755 
00756 
00757 std::string
00758 getOptionalMeta
00759 (
00760 IN Primitive * p,
00761 IN const char * name,
00762 IN const char * default_value
00763 )
00764 {
00765         ASSERT(p, "null");
00766         ASSERT(name, "null");
00767         ASSERT(default_value, "null");
00768 
00769         meta_check_t mc = getMeta(p, name);
00770         if (!mc.exists)
00771                 return default_value;
00772         return mc.value;
00773 }
00774 
00775 
00776 
00777 };      // vgfx namespace
00778