p_group.cpp

Go to the documentation of this file.
00001 /*
00002  * p_group.cpp
00003  *
00004  * Copyright (C) 2007,2009,2010   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 the group primitive.
00032  */
00033 
00034 // includes --------------------------------------------------------------------
00035 #include "vgfx.h"               // always include our own header first!
00036 #include "drawer.h"
00037 
00038 #include <istream>
00039 #include <sstream>
00040 
00041 #include "bezier/fit.h"
00042 #include "common/wave_ex.h"
00043 #include "objtree/objtree.h"    // for isValidPropertyName() only!
00044 #include "perf/perf.h"
00045 #include "util/parsing.h"
00046 #include "util/token_stream.h"
00047 
00048 
00049 // typedefs and constants ------------------------------------------------------
00050 
00051 namespace vgfx {
00052 
00053 
00054 static const eParseBehavior s_line_behavior     = (eParseBehavior) (
00055         eParse_StripComments |
00056         eParse_StripBogus |
00057         eParse_RespectQuotes
00058         );
00059 
00060 
00061 enum eGroupType {
00062         eGT_Normal              = 1,
00063         eGT_Path                = 2,
00064 
00065         eGT_Invalid             = 0
00066 };
00067 
00068 
00069 
00070 struct brush_entry_t {
00071         const char *            key;    // meta key name to look for
00072         eBrushAttribute         attrib; // brush attribute to set
00073 };
00074 
00075 
00076 #define BRUSH_ENTRY(key, attrib) { "vgfx" #key , eBrush_ ##attrib },
00077 
00078 static const brush_entry_t s_brushTable[] = {
00079         BRUSH_ENTRY(Color,              PenColor)
00080         BRUSH_ENTRY(FillColor,          FillColor)
00081         BRUSH_ENTRY(Thickness,          PenThickness)
00082         BRUSH_ENTRY(Font,               Font)
00083         BRUSH_ENTRY(TextFlags,          TextFlags)
00084 
00085         // must be last!
00086         { NULL, eBrush_Invalid }
00087 };
00088 
00089 
00090 
00091 static dword_t
00092 getDwordFromPointer
00093 (
00094 IN void * ptr
00095 )
00096 throw()
00097 {
00098         // ASSERT(ptr) -- can be null
00099         const dword_t * pdw = (const dword_t *) &ptr;
00100         return *pdw;
00101 }
00102 
00103 
00104 
00105 static int
00106 getPathElement
00107 (
00108 IN const char * path,
00109 IO char * buffer,
00110 IN int bufsize
00111 )
00112 {
00113         ASSERT(path, "null path");
00114         ASSERT(buffer, "null");
00115         ASSERT(bufsize > 1, "bad buffer size: %d", bufsize);
00116 
00117         // special case
00118         if (!strcmp("*", path)) {
00119                 buffer[0] = '*';
00120                 buffer[1] = 0;
00121                 return 0;
00122         }
00123 
00124         // skip the first character if it's a slash
00125         const char * start = path;
00126         if ('/' == *path)
00127                 ++path;
00128 
00129         // look for next slash...
00130         int n = 0;
00131         while (n < bufsize && *path && '/' != *path) {
00132                 buffer[n] = *path;
00133                 ++path;
00134                 ++n;
00135         }
00136 
00137         if (n >= bufsize)
00138                 return -1;      // too big for buffer
00139         buffer[n] = 0;          // null-terminate
00140 
00141         if ('/' == *path)
00142                 ++path;         // skip slash
00143 
00144         return path - start;
00145 }
00146 
00147 
00148 
00149 static void
00150 updateBrush
00151 (
00152 IN const Dictionary& d,
00153 IN Drawer * drawer
00154 )
00155 {
00156         ASSERT(drawer, "null");
00157 
00158         for (const brush_entry_t * p = s_brushTable; p->key; ++p) {
00159 
00160                 const char * value = getValue(d, p->key);
00161                 if (value) {
00162                         drawer->setBrushAttribute(p->attrib, value);
00163                 }
00164         }
00165 }
00166 
00167 
00168 
00169 
00170 static void
00171 adjustRefCount
00172 (
00173 IN ObjectMap * map,
00174 IN const char * id,
00175 IN long delta
00176 )
00177 {
00178         ASSERT(map, "null map");
00179         ASSERT(id, "null id");
00180 
00181         Primitive * p = map->findObject(id);
00182         if (!p) {
00183                 if (delta > 0) {
00184                         WAVE_EX(wex);
00185                         wex << "Cannot reference object '" << id << "': it does not ";
00186                         wex << "exist.";
00187                 }
00188 
00189                 // when an entire map is going away, we can see cases where
00190                 // an object goes away before its refs
00191                 return;
00192         }
00193 
00194         if (+1 == delta) {
00195                 p->incrementRefCount();
00196         } else if (-1 == delta) {
00197                 p->decrementRefCount();
00198         } else {
00199                 ASSERT(false, "bad ref count delta: %ld", delta);
00200         }
00201 }
00202 
00203 
00204 
00205 class ObjectRef {
00206 public:
00207         // constructors, destructor --------------------------------------------
00208         ObjectRef(void) throw() : m_map(NULL), m_id("") { }
00209         ObjectRef(IN const ObjectRef& r) : m_map(NULL), m_id("") {
00210                         *this = r;
00211                 }
00212         ~ObjectRef(void) {
00213                         // DPRINTF("ObjectRef destructor");
00214                         this->clear();
00215                 }
00216 
00217         // accessors -----------------------------------------------------------
00218         const char * getID(void) const throw() { return m_id.c_str(); }
00219         void setID(IN ObjectMap * map, IN const char * id) {
00220                         ASSERT(map, "null");
00221                         ASSERT(id, "null");
00222                         this->clear();
00223                         m_map = map;
00224                         m_id = id;
00225                         if (m_id != "") {
00226                                 adjustRefCount(m_map, id, +1);
00227                         }
00228                 }
00229         void dump(IN const char * title) const throw() {
00230                         DPRINTF("  %s: map=%p  id='%s'",
00231                             title, m_map, m_id.c_str());
00232                 }
00233 
00234         const ObjectRef& operator= (IN const ObjectRef& r) {
00235                         this->clear();
00236                         if (r.m_map) {
00237                                 this->setID(r.m_map, r.getID());
00238                         }
00239                         return *this;
00240                 }
00241 
00242 private:
00243 
00244         void clear(void) {
00245                         if (m_map && m_id != "") {
00246                                 adjustRefCount(m_map, m_id.c_str(), -1);
00247                         }
00248                         m_map = NULL;
00249                         m_id = "";
00250                 }
00251 
00252         ObjectMap      *m_map;
00253         std::string     m_id;
00254 };
00255 
00256 
00257 struct object_t {
00258         void dump(IN const char * title) const throw() {
00259                         ref.dump(title);
00260                 }
00261         const char * getTag(void) const throw() { return tag.c_str(); }
00262         void clear(void) throw() {
00263                         x = y = z = phi = 0.0;
00264                         scale = 1.0;
00265                         xform.clear();
00266                 }
00267 
00268         // data fields
00269         ObjectRef       ref;    // id of object this refers to
00270         std::string     tag;    // tag of object reference
00271         float           x;      // position x-coordinate
00272         float           y;
00273         float           z;      // z-order: higher number is closer to user
00274         float           phi;    // rotation around z-axis
00275         float           scale;  // scaling (all axes)
00276         xform_2d_t      xform;  // transformation (translation, rotation, etc.)
00277 };
00278 
00279 //typedef std::map<std::string, object_t> map_obj_t;
00280 
00281 // keep a list of objects in z-order
00282 typedef std::multimap<float, object_t> map_obj_t;
00283 
00284 
00285 ////////////////////////////////////////////////////////////////////////////////
00286 //
00287 //      static helper methods
00288 //
00289 ////////////////////////////////////////////////////////////////////////////////
00290 
00291 static void
00292 updateObjectTransform
00293 (
00294 IO object_t& obj
00295 )
00296 {
00297         // construct transformation
00298         // TODO: support rotations, other transformations!
00299         xform_2d_t translate;
00300         translate.setTranslate(obj.x, obj.y);
00301 
00302         xform_2d_t zrotate;
00303         zrotate.setZRotate(obj.phi);
00304 
00305         xform_2d_t temp;
00306         temp.setToProductOf(translate, zrotate);
00307 
00308         xform_2d_t scale;
00309         scale.setIdentity();
00310         scale.scale(obj.scale);
00311 
00312         obj.xform.setToProductOf(temp, scale);
00313 }
00314 
00315 
00316 
00317 ////////////////////////////////////////////////////////////////////////////////
00318 //
00319 //      Group primitive type
00320 //
00321 ////////////////////////////////////////////////////////////////////////////////
00322 
00323 // TODO: pull the group out into a separate file!!!  It's not part of the parser
00324 class Group : public Primitive {
00325 public:
00326         Group(IN ObjectMap * map) throw();
00327         ~Group(void) throw(); 
00328 
00329         // public class methods ------------------------------------------------
00330         void addMeta(IN const char * key,
00331                                 IN const char * value);
00332         void addObject(IN const object_t& obj);
00333 
00334         // vgfx::Primitive class interface methods -----------------------------
00335         virtual const char * getType(void) const throw() { return "group"; }
00336         virtual void persist(IO std::ostream& stream) const;
00337         virtual void listContainers(IN const VecString& path,
00338                                 OUT VecString& ids) const;
00339         virtual bool doesContainerExist(IN const VecString& path) const;
00340         virtual bool canCreateContainer(IN const VecString& path) const;
00341         virtual void removeContainer(IN const VecString& path);
00342         virtual void getContainerDictionary(IN const VecString& path,
00343                                 OUT dictionary_t& data) const;
00344         virtual void setContainerDictionary(IN const VecString& path,
00345                                 IN const dictionary_t& data);
00346         virtual void recalcBoundingRect(IN const char * tag_path,
00347                                 IN Drawer * drawer,
00348                                 IN const xform_2d_t& T) throw();
00349         virtual bool getBoundingRect(OUT rect_t& r) const throw();
00350         virtual bool getPrimitive(IN const char * tag_path,
00351                                 IN const xform_2d_t& T,
00352                                 OUT visit_result_t& vr);
00353         virtual void draw(IN Drawer * drawer,
00354                                 IN const rect_t& r_cm,
00355                                 IN const xform_2d_t& T); // local --> absolute
00356         virtual bool visit(IN const rect_t& r,
00357                                 IN const xform_2d_t& T,
00358                                 IN const char * tag_path,
00359                                 IN callback_t callback,
00360                                 IN void * context,
00361                                 IN eHitDetect hit);
00362 
00363 private:
00364         // private helper methods ----------------------------------------------
00365         map_obj_t::iterator findObject(IN const char * tag) throw();
00366         map_obj_t::const_iterator findObject(IN const char * tag) const throw();
00367         void updateDrawer(IN Drawer * drawer,
00368                                 IN const xform_2d_t& T);
00369         void expandPath(IN const char * path);
00370         void nukePath(void);
00371 
00372         // private member data -------------------------------------------------
00373         map_obj_t               m_objects;      // map of all objects
00374         Dictionary              m_meta;         // meta key/value pairs
00375         ObjectMap              *m_map;          // weak ref
00376         rect_t                  m_bounding;     // cached bounding rect
00377         eGroupType              m_type;         // what type of group?
00378         bool                    m_have_rect;    // have a bounding rect?
00379 };
00380 
00381 
00382 
00383 ////////////////////////////////////////////////////////////////////////////////
00384 //
00385 //      Group constructor + destructor
00386 //
00387 ////////////////////////////////////////////////////////////////////////////////
00388 
00389 Group::Group
00390 (
00391 IN ObjectMap * map
00392 )
00393 throw() :
00394 m_map(map),
00395 m_type(eGT_Normal),
00396 m_have_rect(false)
00397 { 
00398 }
00399 
00400 Group::~Group(void)
00401 throw()
00402 {
00403         if (!m_map || eGT_Path != m_type)
00404                 return;
00405 
00406         this->nukePath();
00407 }
00408 
00409 
00410 
00411 ////////////////////////////////////////////////////////////////////////////////
00412 //
00413 //      Group class methods
00414 //
00415 ////////////////////////////////////////////////////////////////////////////////
00416 
00417 void
00418 Group::addMeta
00419 (
00420 IN const char * key,
00421 IN const char * value
00422 )
00423 {
00424         ASSERT(key, "null key passed to addMeta()");
00425         ASSERT(value, "null value passed to addMeta()");
00426 
00427         ASSERT(key[0], "empty key passed to addMeta()");
00428         // ASSERT(value[0]) -- this is okay
00429         m_meta[key] = value;
00430 
00431         // interception--is this a path?
00432         if (!strcmp("vgfxPath", key)) {
00433                 m_type = eGT_Path;
00434                 this->nukePath();
00435                 this->expandPath(value);
00436         }
00437 }
00438 
00439 
00440 
00441 void
00442 Group::addObject
00443 (
00444 IN const object_t& obj
00445 )
00446 {
00447         const char * tag = obj.getTag();
00448         ASSERT(*tag, "null tag?");
00449 
00450         if (!objtree::isValidPropertyName(tag)) {
00451                 WAVE_EX(wex);
00452                 wex << "Invalid object tag: '" << tag << "'";
00453         }
00454 
00455         map_obj_t::iterator i = this->findObject(tag);
00456         if (m_objects.end() != i) {
00457                 WAVE_EX(wex);
00458                 wex << "Multiple group objects with the same tag: '" << tag;
00459                 wex << "'";
00460         }
00461 
00462         m_objects.insert(map_obj_t::value_type(obj.z, obj));
00463 }
00464 
00465 
00466 
00467 static void
00468 writeXY
00469 (
00470 IO std::ostream& stream,
00471 IN float x,
00472 IN float y
00473 )
00474 {
00475         long x_l = (long)(1000.0 * x + 0.5);
00476         long y_l = (long)(1000.0 * y + 0.5);
00477 
00478         stream << "x" << x_l << "y" << y_l;
00479 }
00480 
00481 
00482 #define PERSIST_VAL2(q,v) { if (obj.q) { stream << "\t" << #q << " " << (v); } }
00483 #define PERSIST_VAL(q) PERSIST_VAL2(q, obj.q)
00484 
00485 void
00486 Group::persist
00487 (
00488 IO std::ostream& stream
00489 )
00490 const
00491 {
00492         perf::Timer timer("Group::persist");
00493 
00494         stream << "group {\n";
00495         stream << "\tid\t" << this->getID() << "\n";
00496 
00497         for (Dictionary::const_iterator i = m_meta.begin(); i != m_meta.end();
00498              ++i) {
00499                 const char * name = i->first.c_str();
00500                 const char * value = i->second.c_str();
00501 
00502                 // HACK for paths.  TODO: clean this up
00503                 if (!strcmp("vgfxPath", name)) {
00504                         continue;       // skip this (see below)
00505                 }
00506 
00507                 stream << "\tmeta\t" << name << " \"" << value << "\"\n";
00508         }
00509 
00510         // TODO: clean up the concept of path==group!
00511         //      checking for "vgfxPath" everywhere is bogus
00512         if (eGT_Path == m_type) {
00513                 // OUCH.  we have to reconstruct our vgfxPath meta value,
00514                 // since user erasures or other actions may have deleted
00515                 // some arbitrary number of child objects.  Even worse, we
00516                 // have to reconstruct ordering based on IDs.
00517 
00518                 // TODO: this is so hideous it is beyond words.  But it's
00519                 // late and I haven't had any better ideas.  I suspect there
00520                 // needs to be another layer here but I'll think about it
00521                 // later.
00522 
00523                 // first: get an ordered list of child objects
00524                 SetString ordered;
00525                 for (map_obj_t::const_iterator i = m_objects.begin();
00526                      i != m_objects.end(); ++i) {
00527                         ordered.insert(i->second.ref.getID());
00528                 }
00529 
00530                 // next: walk ordered list, write paths
00531                 VecString null_path;    // empty
00532                 dictionary_t data;
00533                 float x0 = 0.0;         // previous control point
00534                 float y0 = 0.0;
00535                 stream << "\tmeta\tvgfxPath \"";
00536                 for (SetString::iterator i = ordered.begin();
00537                      i != ordered.end(); ++i) {
00538                         const char * id = i->c_str();
00539 
00540                         Primitive * p = m_map->findObject(id);
00541                         ASSERT(p, "bezier object not there '%s'", id);
00542                         p->getContainerDictionary(null_path, data);
00543 
00544                         float x = atof(getRequiredValue(data, "x0"));
00545                         float y = atof(getRequiredValue(data, "y0"));
00546 
00547                         // need to write old values?
00548                         if (x != x0 || y != y0) {
00549                                 writeXY(stream, x0, y0);
00550                                 stream << "|g0|";
00551                         }
00552                         writeXY(stream, x, y);
00553                         stream << "|";
00554 
00555                         x0 = atof(getRequiredValue(data, "x2"));
00556                         y0 = atof(getRequiredValue(data, "y2"));
00557                 }
00558 
00559                 // write closing point
00560                 writeXY(stream, x0, y0);
00561                 stream << "\"\n}";
00562                 return; // short-circuit
00563         }
00564 
00565         // happy case: this isn't a path and we can just persist child records
00566         for (map_obj_t::const_iterator i = m_objects.begin();
00567              i != m_objects.end(); ++i) {
00568                 const object_t& obj = i->second;
00569                 const char * tag = obj.getTag();
00570 
00571                 stream << "\tobject\ttag " << tag;
00572                 stream << "\tid " << obj.ref.getID();
00573                 PERSIST_VAL(x)
00574                 PERSIST_VAL(y)
00575                 PERSIST_VAL(z)
00576                 if (1 != obj.scale) {
00577                         PERSIST_VAL(scale)
00578                 }
00579                 PERSIST_VAL2(phi, 180.0 * obj.phi / M_PI);
00580                 stream << "\n";
00581         }
00582 
00583         stream << "}";
00584 }
00585 
00586 
00587 
00588 void
00589 Group::listContainers
00590 (
00591 IN const VecString& path,
00592 OUT VecString& ids
00593 )
00594 const
00595 {
00596         ids.clear();
00597         if (path[0] == "meta") {
00598                 for (Dictionary::const_iterator i = m_meta.begin();
00599                      i != m_meta.end(); ++i) {
00600                         ids.push_back(i->first);
00601                 }
00602         } else if (path[0] == "object") {
00603                 for (map_obj_t::const_iterator i = m_objects.begin();
00604                      i != m_objects.end(); ++i) {
00605                         ids.push_back(i->second.tag);
00606                 }
00607         } else {
00608                 WAVE_EX(wex);
00609                 wex << "Unknown path for group: " << path[0];
00610         }
00611 }
00612 
00613 
00614 
00615 bool
00616 Group::doesContainerExist
00617 (
00618 IN const VecString& path
00619 )
00620 const
00621 {
00622         if (path[0] == "meta") {
00623                 return (m_meta.end() != m_meta.find(path[1]));
00624         } else if (path[0] == "object") {
00625                 return (m_objects.end() != this->findObject(path[1].c_str()));
00626         }
00627         return false;
00628 }
00629 
00630 
00631 bool
00632 Group::canCreateContainer
00633 (
00634 IN const VecString& path
00635 )
00636 const
00637 {
00638         if (path[0] == "meta" || path[0] == "object")
00639                 return true;
00640         return false;
00641 }
00642 
00643 
00644 void
00645 Group::removeContainer
00646 (
00647 IN const VecString& path
00648 )
00649 {
00650         if (path.size() > 2) {
00651                 WAVE_EX(wex);
00652                 wex << "Path is too large for group (" << path.size();
00653                 wex << " elements)";
00654         }
00655 
00656         if (path[0] == "meta") {
00657                 Dictionary::iterator i = m_meta.find(path[1]);
00658                 if (m_meta.end() == i) {
00659                         WAVE_EX(wex);
00660                         wex << "Cannot remove this meta data element '";
00661                         wex << path[1] << "' since it does not exist.";
00662                 }
00663                 m_meta.erase(i);
00664         } else if (path[0] == "object") {
00665                 map_obj_t::iterator i = this->findObject(path[1].c_str());
00666                 if (m_objects.end() == i) {
00667                         WAVE_EX(wex);
00668                         wex << "Cannot remove this object '" << path[1];
00669                         wex << "' since it does not exist.";
00670                 }
00671                 m_objects.erase(i);
00672         } else {
00673                 WAVE_EX(wex);
00674                 wex << "Unknown path in group: '" << path[0] << "'";
00675         }
00676 }
00677 
00678 
00679 void
00680 Group::getContainerDictionary
00681 (
00682 IN const VecString& path,
00683 OUT dictionary_t& data
00684 )
00685 const
00686 {
00687         //perf::Timer timer("Group::getContainerDictionary");
00688 
00689         if (path.size() > 2) {
00690                 WAVE_EX(wex);
00691                 wex << "path is too large for group (" << path.size();
00692                 wex << " elements)";
00693         }
00694 
00695         data.clear();
00696         if (path[0] == "meta") {
00697                 const char * name = path[1].c_str();
00698                 Dictionary::const_iterator i = m_meta.find(name);
00699                 if (m_meta.end() == i) {
00700                         WAVE_EX(wex);
00701                         wex << "No meta data field with name '" << name;
00702                         wex << "' exists in this group";
00703                 }
00704                 const char * value = i->second.c_str();
00705                 data[name] = value;
00706         } else if (path[0] == "object") {
00707                 map_obj_t::const_iterator i = this->findObject(path[1].c_str());
00708                 if (m_objects.end() == i) {
00709                         WAVE_EX(wex);
00710                         wex << "No object with tag '" << path[1] << "' exists";
00711                         wex << " in this group";
00712                 }
00713                 const object_t& obj = i->second;
00714                 const char * tag = obj.getTag();
00715                 data["tag"] = tag;
00716                 data["id"] = obj.ref.getID();
00717                 data["x"] = getStringValue(obj.x);
00718                 data["y"] = getStringValue(obj.y);
00719                 data["z"] = getStringValue(obj.z);
00720                 data["scale"] = getStringValue(obj.scale);
00721                 data["phi"] = getStringValue(180 * obj.phi / M_PI);
00722         } else {
00723                 WAVE_EX(wex);
00724                 wex << "Invalid path for dictionary retrieval: '" << path[0];
00725                 wex << "'";
00726         }
00727 }
00728 
00729 
00730 
00731 void
00732 Group::setContainerDictionary
00733 (
00734 IN const VecString& path,
00735 IN const dictionary_t& data
00736 )
00737 {
00738 //      perf::Timer timer("Group::setContainerDictionary");
00739 
00740         if (path.size() > 2) {
00741                 WAVE_EX(wex);
00742                 wex << "Path is too long for group (" << path.size();
00743                 wex << " elements)";
00744         }
00745 
00746         if (path[0] == "meta") {
00747                 const char * name = path[1].c_str();
00748                 const char * value = getRequiredValue(data, name);
00749                 this->addMeta(name, value);
00750         } else if (path[0] == "object") {
00751                 map_obj_t::iterator i = this->findObject(path[1].c_str());
00752                 if (m_objects.end() == i) {
00753                         // need to create!
00754                         object_t obj;
00755                         const char * in_tag = path[1].c_str();
00756                         const char * tag = getOptionalValue(data, "tag", in_tag);
00757                         if (strcmp(tag, in_tag)) {
00758                                 WAVE_EX(wex);
00759                                 wex << "tag for new object '" << tag << "' ";
00760                                 wex << "does not match path '" << path[1];
00761                                 wex << "'";
00762                         }
00763                         const char * id = getValue(data, "id");
00764                         ASSERT_THROW(id, "Cannot setContainerDictionary(): " <<
00765                             "need to create new object, and no 'id' field " <<
00766                             "exists on input");
00767                         obj.ref.setID(m_map, id);
00768                         obj.tag = tag;
00769                         obj.x = atof(getOptionalValue(data, "x", "0.0"));
00770                         obj.y = atof(getOptionalValue(data, "y", "0.0"));
00771                         obj.z = atof(getOptionalValue(data, "z", "0.0"));
00772                         obj.scale = atof(getOptionalValue(data, "scale", "1.0"));
00773                         obj.phi = atof(getOptionalValue(data, "phi", "0.0"));
00774                         obj.phi *= M_PI / 180.0;
00775                         updateObjectTransform(obj);
00776                         this->addObject(obj);
00777                 } else {
00778                         // update existing
00779                         object_t& obj = i->second;
00780                         for (dictionary_t::const_iterator j = data.begin();
00781                              j != data.end(); ++j) {
00782                                 const char * name = j->first.c_str();
00783                                 const char * value = j->second.c_str();
00784 
00785                                 if (!strcmp("id", name)) {
00786                                         obj.ref.setID(m_map, value);
00787                                 } else if (!strcmp("x", name)) {
00788                                         obj.x = atof(value);
00789                                 } else if (!strcmp("y", name)) {
00790                                         obj.y = atof(value);
00791                                 } else if (!strcmp("z", name)) {
00792                                         obj.z = atof(value);
00793                                 } else if (!strcmp("scale", name)) {
00794                                         obj.scale = atof(value);
00795                                 } else if (!strcmp("phi", name)) {
00796                                         obj.phi = M_PI * atof(value) / 180.0;
00797                                 } else if (!strcmp("tag", name)) {
00798                                         if (path[1] != value) {
00799                                                 WAVE_EX(wex);
00800                                                 wex << "Cannot change tag ";
00801                                                 wex << "value from '" << path[1];
00802                                                 wex << "' to '" << value << "'";
00803                                         }
00804                                 } else {
00805                                         WAVE_EX(wex);
00806                                         wex << "Cannot set this field for a ";
00807                                         wex << "group object element: '";
00808                                         wex << name << "'";
00809                                 }
00810                         }
00811                         updateObjectTransform(obj);
00812                 }
00813         } else {
00814                 WAVE_EX(wex);
00815                 wex << "Invalid path in group: '" << path[0] << "'";
00816         }
00817 }
00818 
00819 
00820 
00821 void
00822 Group::recalcBoundingRect
00823 (
00824 IN const char * tag_path,
00825 IN Drawer * drawer,
00826 IN const xform_2d_t& T
00827 )
00828 throw()
00829 {
00830         ASSERT(tag_path, "null tag path");
00831         ASSERT(drawer, "null drawer");
00832         ASSERT(m_map, "null object map");
00833 
00834 //      DPRINTF("In Group::getBoundingRect(%s)", this->getID());
00835 
00836         static const int s_tokSize = 256;
00837         char element[s_tokSize];
00838         int offset = getPathElement(tag_path, element, s_tokSize);
00839         ASSERT(offset >= 0, "Failed to get next path element: '%s'", tag_path);
00840         const char * next_path = tag_path + offset;
00841 //      DPRINTF("Path is '%s',  element is '%s', remainder is '%s'",
00842 //           tag_path, element, next_path); 
00843 
00844         bool isWildcard = !strcmp("*", element);
00845 
00846         // update brush
00847         PushPopBrush ppb(drawer, getDwordFromPointer(this));
00848         this->updateDrawer(drawer, T);
00849 
00850         // T.dump("input transformation");
00851 
00852         // our bounding rect is the sum of all interior rects
00853         m_have_rect = false;            // need to compute at least one child
00854         rect_t r;
00855         r.left = r.top = r.right = r.bottom = 0.0;      // default
00856 
00857         // loop through objects
00858         for (map_obj_t::const_iterator i = m_objects.begin();
00859             i != m_objects.end(); ++i) {
00860                 const object_t& obj = i->second;
00861 
00862                 // get this object
00863                 Primitive * p = m_map->findObject(obj.ref.getID());
00864                 ASSERT(p, "all required sub objects should exist");
00865 
00866                 // only recurse if we think this child needs it
00867                 if (isWildcard || !strcmp(element, obj.getTag())) {
00868                         // construct child's transformation
00869                         xform_2d_t subT;        // local --> absolute
00870                         subT.setToProductOf(T, obj.xform);
00871 
00872                         // obj.xform.dump("parent->child transform");
00873                         // subT.dump("Total child->global transform");
00874 
00875                         // recalculate bounding rects for this child tree
00876                         //    (recursive call--don't wrap in a timer)
00877                         p->recalcBoundingRect(next_path, drawer, subT);
00878                 }
00879 
00880                 // another scope for clean timing
00881                 {
00882                         // only enable for debugging!
00883                         // perf::Timer timer("Group::recalc-child-xform2");
00884 
00885                         // get bounding rect in object's local coordinates
00886                         rect_t child;
00887                         if (!p->getBoundingRect(child)) {
00888                                 // child doesn't have a bounding rect--skip it
00889                                 continue;
00890                         }
00891 
00892                         // child.dump("Child's bounding rect");
00893                         rect_t xc;      // xc - transformed child rectangle
00894                         obj.xform.transformRect(child, xc);
00895                         // xc.dump("Transformed child rect");
00896 
00897                         if (!m_have_rect) {
00898                                 r = xc;
00899                                 m_have_rect = true;
00900                         } else {
00901                                 r.inflate(xc);
00902                         }
00903                         // r.dump("  Resulting rect");
00904                 }
00905         }
00906 
00907         // have a clip rect?  Then it's easy
00908         const char * w = getValue(m_meta, "vgfxClipWidth");
00909         const char * h = getValue(m_meta, "vgfxClipHeight");
00910         if (w && h) {
00911                 r.left = r.top = 0.0;
00912                 r.right = atof(w);
00913                 r.bottom = atof(h);
00914                 m_have_rect = true;
00915         }
00916 
00917         m_bounding = r;
00918 //      r.dump(this->getID());
00919         // DPRINTF("  ...exiting Group::getBoundingRect(%s)", this->getID());
00920 }
00921 
00922 
00923 
00924 bool
00925 Group::getBoundingRect
00926 (
00927 OUT rect_t& r
00928 )
00929 const throw()
00930 {
00931         r = m_bounding;
00932         return m_have_rect;
00933 }
00934 
00935 
00936 
00937 bool
00938 Group::getPrimitive
00939 (
00940 IN const char * tag_path,
00941 IN const xform_2d_t& T,
00942 OUT visit_result_t& vr
00943 )
00944 {
00945         ASSERT(tag_path, "null tag path");
00946 
00947         // DPRINTF("Looking for object at path: '%s'", tag_path);
00948 
00949         static const int s_tokSize = 256;
00950         char element[s_tokSize];
00951         int offset = getPathElement(tag_path, element, s_tokSize);
00952         ASSERT(-1 != offset, "path element is too large");
00953 
00954         // special case
00955         if (!*element) {
00956                 // DPRINTF("This must be the root!");
00957                 vr.tag_path = tag_path;
00958                 vr.T = T;
00959                 vr.p = this;
00960                 return true;
00961         }
00962 
00963         map_obj_t::iterator i = this->findObject(element);
00964         if (m_objects.end() == i) {
00965                 // DPRINTF("No object with tag '%s'", element);
00966                 vr.clear();
00967                 return false;
00968         }
00969 
00970         object_t& obj = i->second;
00971 
00972         // update transformation
00973         xform_2d_t newT;
00974         newT.setToProductOf(T, obj.xform);
00975 
00976         // look up this child
00977         vgfx::Primitive * p = m_map->findObject(obj.ref.getID());
00978         ASSERT(p, "tag found but no primitive");
00979 
00980         // is this the primitive we want, or just a parent?
00981         if (*(tag_path + offset)) {
00982                 // there is more to the path
00983                 return p->getPrimitive(tag_path + offset, newT, vr);
00984         }
00985 
00986         // just return this child directly
00987         vr.tag_path = tag_path;
00988         vr.T = newT;
00989         vr.p = p;
00990         return true;
00991 }
00992 
00993 
00994 
00995 void
00996 Group::draw
00997 (
00998 IN Drawer * drawer,
00999 IN const rect_t& r_cm,  // draw rect in absolute coordinates (cm)
01000 IN const xform_2d_t& T  // local --> absolute (cm)
01001 )
01002 {
01003         ASSERT(drawer, "null drawer");
01004 
01005         /*
01006         DPRINTF("Group::draw(%s)", this->getID());
01007         r_cm.dump("draw rect");
01008         T.dump("transform");
01009         */
01010 
01011         // if this is a printer, see if we should print at all
01012         if (drawer->isPrinter()) {
01013                 const char * val = getValue(m_meta, "vgfxPrintable");
01014                 if (val && !strcmp(val, "false")) {
01015                         DPRINTF("'%s' has requested not to print",
01016                             this->getID());
01017                         return;
01018                 }
01019         }
01020 
01021         // update brush
01022         PushPopBrush ppb(drawer, getDwordFromPointer(this));
01023         this->updateDrawer(drawer, T);
01024 
01025         // walk through all children, draw
01026         for (map_obj_t::const_iterator i = m_objects.begin();
01027             i != m_objects.end(); ++i) {
01028                 const object_t& obj = i->second;
01029 
01030                 // put in separate scope so we get a clean timer (does not
01031                 //  include recursion to children)
01032                 Primitive * p = NULL;
01033                 xform_2d_t subT;        // child coords --> absolute (CM)
01034                 {
01035                         // actually, this is fast enough that the timer itself
01036                         //   impacts performance!
01037                         // only enable timer for debugging
01038                         // perf::Timer timer("Group::draw-child-xform");
01039 
01040                         // get this object
01041                         p = m_map->findObject(obj.ref.getID());
01042                         ASSERT(p, "all required sub objects should exist");
01043 
01044                         // construct child's transformation
01045                         subT.setToProductOf(T, obj.xform);
01046                         // obj.xform.dump("child transform");
01047 
01048                         // get bounding rect in object's local coordinates
01049                         rect_t child;
01050                         if (!p->getBoundingRect(child)) {
01051                                 // child doesn't have a bounding rect--skip it
01052                                 continue;
01053                         }
01054 
01055                         // transform to global -- hit?
01056                         rect_t globalR;
01057                         subT.transformRect(child, globalR);
01058 
01059                         // child.dump("child in local coords");
01060                         // globalR.dump("child in global coords");
01061 
01062                         // intersects at all?
01063                         if (!r_cm.intersects(globalR))
01064                                 continue;       // nope, doesn't intersect
01065                 }
01066 
01067                 // allow child tree to draw
01068                 p->draw(drawer, r_cm, subT);
01069         }
01070 
01071         // supposed to draw bounding rect?  do so last
01072         const char * draw_bounds = getValue(m_meta, "vgfxDrawBounds");
01073         if (draw_bounds && !strcmp("true", draw_bounds)) {
01074                 drawer->setTransform(T);
01075                 drawer->fillRect(m_bounding);
01076         }
01077 
01078         // close out any paths
01079         drawer->endPath(); 
01080 }
01081 
01082 
01083 
01084 bool
01085 Group::visit
01086 (
01087 IN const rect_t& r,             // global coordinates (cm)
01088 IN const xform_2d_t& T,         // local --> global transformation
01089 IN const char * tag_path,
01090 IN callback_t callback,
01091 IN void * context,
01092 IN eHitDetect hit
01093 )
01094 {
01095         // perf::Timer timer("Group::visit"); -- too high level
01096         ASSERT(callback, "Group::visit() called with null callback");
01097         ASSERT(tag_path, "null tag path?");
01098 
01099         // A note about hit-detection logic.
01100         //  If hit = eHit_Always, we don't make any checks, and always recurse
01101         //  If hit = eHit_Intersects, we will recurse on any intersection
01102         //  If hit = eHit_Contains, we'll recurse on any intersection (yes!)
01103         //     when hit = eHit_Contains, we'll callback only if we are contained
01104         //     We have to recurse on simple intersections in case a contained
01105         //     object is fully contained by the input rect.
01106 
01107         // call with ourselves (maybe)
01108         bool notify = true;
01109         if (eHit_Contained == hit) {
01110                 rect_t me;
01111                 this->getBoundingRect(me);
01112                 rect_t xme;             // transformed me
01113                 T.transformRect(me, xme);
01114                 notify = r.contains(xme);
01115         }
01116 
01117         if (notify) {
01118                 visit_result_t vr;
01119                 vr.p = this;
01120                 vr.tag_path = tag_path;
01121                 vr.T = T;
01122                 if (!callback(context, vr))
01123                         return false;   // client asked to stop iterating
01124         }
01125 
01126         // recurse into children
01127         for (map_obj_t::const_iterator i = m_objects.begin();
01128              i != m_objects.end(); ++i) {
01129                 const object_t& obj = i->second;
01130 
01131                 // separate scope for clean timing
01132                 bool recurse = true;
01133                 xform_2d_t childT;
01134                 Primitive * p = NULL;
01135                 {
01136                         perf::Timer timer("Group::visit--child xform");
01137 
01138                         // find object
01139                         p = m_map->findObject(obj.ref.getID());
01140                         ASSERT(p, "all subobjects should exist");
01141         //              DPRINTF("testing child '%s'", p->getID());
01142 
01143                         // get transform from this child --> global
01144                         childT.setToProductOf(T, obj.xform);
01145 
01146                         if (eHit_Always != hit) {
01147                                 // get child's bounding rect
01148                                 rect_t childR;
01149                                 p->getBoundingRect(childR);
01150 
01151                                 // child rect in global coordinates
01152                                 rect_t globalR; 
01153                                 childT.transformRect(childR, globalR);
01154 
01155                                 // r.dump("draw rect");
01156                                 // globalR.dump("child rect");
01157                                 recurse = r.intersects(globalR);
01158                         }
01159                 }
01160 
01161                 if (recurse) {
01162                         ASSERT(p, "null node for recursion?");
01163                         // intersects, so recurse
01164                         std::string new_path = tag_path;
01165                         new_path += "/";
01166                         new_path += obj.getTag();
01167                         if (!p->visit(r, childT, new_path.c_str(), callback,
01168                              context, hit)) {
01169                                 return false;   // client wanted to stop
01170                         }
01171                 }
01172         }
01173 
01174         // success
01175         return true;
01176 }
01177 
01178 
01179 map_obj_t::iterator
01180 Group::findObject
01181 (
01182 IN const char * tag
01183 )
01184 throw()
01185 {
01186         ASSERT(tag, "null");
01187 
01188         for (map_obj_t::iterator i = m_objects.begin(); i != m_objects.end();
01189              ++i) {
01190                 object_t& obj = i->second;
01191                 if (obj.tag == tag) {
01192                         return i;
01193                 }
01194         }
01195 
01196         return m_objects.end();
01197 }
01198 
01199 
01200 
01201 map_obj_t::const_iterator
01202 Group::findObject
01203 (
01204 IN const char * tag
01205 )
01206 const throw()
01207 {
01208         ASSERT(tag, "null");
01209 
01210         for (map_obj_t::const_iterator i = m_objects.begin();
01211              i != m_objects.end(); ++i) {
01212                 const object_t& obj = i->second;
01213                 if (obj.tag == tag) {
01214                         return i;
01215                 }
01216         }
01217 
01218         return m_objects.end();
01219 }
01220 
01221 
01222 
01223 void
01224 Group::updateDrawer
01225 (
01226 IN Drawer * drawer,
01227 IN const xform_2d_t& T          // local --> global
01228 )
01229 {
01230 //      perf::Timer timer("Group::updateDrawer");
01231 
01232         ASSERT(drawer, "null");
01233 
01234         // set transformation
01235         drawer->setTransform(T);
01236 
01237         // update brush based on group meta values
01238         updateBrush(m_meta, drawer);
01239 
01240         // clip rect?
01241         const char * w = getValue(m_meta, "vgfxClipWidth");
01242         const char * h = getValue(m_meta, "vgfxClipHeight");
01243         if (w && h) {
01244                 drawer->setClipRect(0.0, 0.0, atof(w), atof(h));
01245         }
01246 }
01247 
01248 
01249 
01250 void
01251 Group::expandPath
01252 (
01253 IN const char * path
01254 )
01255 {
01256         ASSERT(path, "null");
01257 
01258 //      DPRINTF("expanding path = '%s'", path);
01259 
01260         // first, construct vector of points
01261         typedef std::vector<point_t> vec_pt_t;
01262         vec_pt_t strokes;
01263         const char * p = path;
01264         point_t pt;     // current point
01265         bool is_gap = false;
01266         int id_ctr = 0;
01267         while (*p) {
01268                 // determine token
01269                 char a = *p;
01270                 ++p;
01271 
01272                 // get value
01273                 long l = atol(p);
01274                 float f = 0.001 * l;    // convert to CM
01275 
01276                 // walk forward to next token
01277                 if ('-' == *p) { ++p; }
01278                 while (*p && isdigit(*p)) { ++p; }
01279 
01280                 // update point if necessary
01281                 if ('x' == a)
01282                         pt.x = f;
01283                 if ('y' == a)
01284                         pt.y = f;
01285                 if ('g' == a)
01286                         is_gap = true;
01287 
01288                 // keep parsing event?
01289                 if (*p && '|' != *p)
01290                         continue;
01291 
01292                 // just add point?
01293                 if (!is_gap) {
01294         //              DPRINTF("  (%5.2f, %5.2f)", pt.x, pt.y);
01295                         strokes.push_back(pt);
01296                 }
01297 
01298                 if (!is_gap && *p)
01299                         continue;
01300 
01301                 // gap or end -- add strokes as bezier path!
01302                 // ignore short paths
01303                 if (strokes.size() < 2) {
01304                         continue;
01305                 }
01306 
01307                 // now, convert to beziers
01308                 float ds = 1.0;         // WTF?
01309                 bezier::quad_path_t b_path;
01310                 bezier::getQuadPathFromRawPoints(&strokes[0], strokes.size(),
01311                     ds, b_path);
01312 
01313                 // add beziers as child objects
01314                 for (bezier::quad_path_t::iterator i = b_path.begin();
01315                      i != b_path.end(); ++i) {
01316                         const bezier::quad_bezier_t& qb = *i;
01317         
01318                         // create bezier object
01319                         init_quad_t iq;
01320                         iq.id = this->getID();
01321                         iq.id += "K";
01322                         iq.id += getStringValue(id_ctr);
01323                         ++id_ctr;
01324                         iq.quad = qb;
01325                         smart_ptr<Primitive> quad = create_quad(iq);
01326                         ASSERT(quad, "failed to create new quad bezier");
01327 
01328                         // add to map
01329                         m_map->addObject(quad);
01330 
01331                         // construct object record
01332                         object_t obj;
01333                         obj.clear();
01334                         obj.ref.setID(m_map, iq.id.c_str());
01335                         obj.tag = iq.id;
01336                         this->addObject(obj);
01337                 }
01338                 strokes.clear();
01339                 is_gap = false;
01340         }
01341 }
01342 
01343 
01344 
01345 void
01346 Group::nukePath
01347 (
01348 void
01349 )
01350 {
01351         ASSERT(eGT_Path == m_type, "only valid for path objects!");
01352 
01353         // this is a path--nuke all children from map!
01354         VecString ids;
01355         for (map_obj_t::iterator i = m_objects.begin(); i != m_objects.end();
01356              ++i) {
01357                 const char * id = i->second.ref.getID();
01358                 ids.push_back(id);
01359         }
01360         m_objects.clear();
01361         for (VecString::iterator i = ids.begin(); i != ids.end(); ++i) {
01362                 const char * id = i->c_str();
01363                 // DPRINTF("Removing from map: '%s'", id);
01364                 m_map->removeObject(id);
01365         }
01366 }
01367 
01368 
01369 
01370 ////////////////////////////////////////////////////////////////////////////////
01371 //
01372 //      static helper methods
01373 //
01374 ////////////////////////////////////////////////////////////////////////////////
01375 
01376 static void
01377 constructObject
01378 (
01379 IN ObjectMap * map,
01380 IN const dictionary_t& data,
01381 OUT object_t& obj
01382 )
01383 {
01384         ASSERT(map, "null");
01385 
01386         obj.tag = getRequiredValue(data, "tag");
01387         obj.ref.setID(map, getRequiredValue(data, "id"));
01388         obj.x = atof(getOptionalValue(data, "x", "0.0"));
01389         obj.y = atof(getOptionalValue(data, "y", "0.0"));
01390         obj.z = atof(getOptionalValue(data, "z", "0.0"));
01391         obj.scale = atof(getOptionalValue(data, "scale", "1.0"));
01392 
01393         obj.phi = atof(getOptionalValue(data, "phi", "0.0"));
01394         obj.phi = M_PI * obj.phi / 180.0;       // degrees --> radians
01395 
01396         updateObjectTransform(obj);
01397 }
01398 
01399 
01400 
01401 smart_ptr<Primitive>
01402 parseGroup
01403 (
01404 IN std::istream& stream,
01405 IO ObjectMap * map
01406 )
01407 {
01408         perf::Timer timer("parseGroup");
01409 
01410         ASSERT(map, "null object map passed to parseGroup()");
01411 
01412         smart_ptr<Group> group = new Group(map);
01413         ASSERT(group, "out of memory");
01414 
01415         std::string line, token, value;
01416 
01417         // keep reading lines
01418         while (true) {
01419                 line = getNextLineFromStream(stream, s_line_behavior);
01420                 const char * cursor =
01421                     getNextTokenFromString(line.c_str(), token, eParse_None);
01422 
01423                 // empty?
01424                 if ("" == token) {
01425                         continue;       // empty line
01426                 }
01427 
01428                 if ("id" == token) {
01429                         getNextTokenFromString(cursor, value, eParse_RespectQuotes);
01430                         group->setID(value.c_str());
01431                 } else if ("meta" == token) {
01432                         std::string name;
01433                         cursor = getNextTokenFromString(cursor, name, eParse_None);
01434                         cursor = getNextTokenFromString(cursor, value, eParse_RespectQuotes);
01435 
01436                         group->addMeta(name.c_str(), value.c_str());
01437                 } else if ("object" == token) {
01438                         dictionary_t dict;
01439                         getDictionaryFromString(cursor, "object", dict);
01440 
01441                         object_t obj;
01442                         constructObject(map, dict, obj);
01443                         // DPRINTF("  Adding object with tag='%s', id='%s'",
01444                         //    tag.c_str(), obj.ref.getID());
01445                         // obj.xform.dump("object transform");
01446                         group->addObject(obj);
01447                 } else if ("}" == token) {
01448                         break;          // end of group!
01449                 } else {
01450                         WAVE_EX(wex);
01451                         wex << "Unknown line keyword in group: '" << token;
01452                         wex << "'";
01453                 }
01454         }
01455 
01456         return group;
01457 }
01458 
01459 
01460 };      // vgfx namespace
01461