[
  {
    "path": ".gitattributes",
    "content": "*.cmd   text eol=crlf\n\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: acaudwell\n"
  },
  {
    "path": ".gitignore",
    "content": "*.avi\n*.mpg\n*.mkv\n*.mp4\ngource-*.png\n.objs\n/gource\n/gource.exe\ngource.pro.user\ndebug.log\nMakefile\nMakefile.in\n*.dll\n*.dll.a\nlogs/*\n/aclocal.m4\n/autom4te.cache\n/configure\n/config.log\n/config.status\n/dev/win64\n/build-aux\n*.swp\n*.d\n.deps\n.dirstamp\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"src/core\"]\n\tpath = src/core\n\turl = https://github.com/acaudwell/Core.git\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "ChangeLog",
    "content": "0.57:\n * Handle loading indexed colour images (sokripon).\n\n0.56:\n * Added --author-time option (onlyJakob).\n * Fixed build with Boost 1.89.0 by no longer linking boost system.\n * Increased minimum required version of Boost to 1.69.\n * Request compatibility profile when creating OpenGL context.\n\n0.55:\n * Fixed build with Boost 1.85.0 (cho-m).\n\n0.54:\n * Added experimental support for Wayland (beroset).\n * Fixed building on Apple M1 by updating autoconf macros (Ckhardin).\n\n0.53:\n * Added --high-dpi option.\n * Fixed various bugs with Retina displays on MacOS.\n * Fixed slider timestamp font not affected by --font-scale.\n * Fixed hovering over end of slider stopping time advancing.\n\n0.52:\n * Support rendering at native resolution on Retina / Ultra HD displays.\n * Support ISO 8601 timestamp format.\n * Allow custom log and caption file timestamps to be strings.\n * Change regular expression library to PCRE2.\n * Fixed filenames not being affected by --font-scale (Carl Colena).\n * Fixed file key not being affected by --font-scale.\n * Added --file-idle-time-at-end option.\n * Added --fixed-user-size option.\n\n0.51:\n * Fixed --font-file relative path handling.\n * Fixed a bug in resolving the repository root directory from a relative path.\n\n0.50:\n * Right mouse button rotation now pivots around the camera.\n * Added --font-file option.\n * Added --enable-font-file option to configure.\n * Added --no-time-travel option (Lars Schmertmann).\n * Added --dir-name-position option (Lars Schmertmann).\n * Added --file-extension-fallback option (Lars Schmertmann).\n * Added --user-show-filter option (Victor Lopez).\n * Added --disable-input option (Joey Parrish).\n * Added --loop-delay-seconds option (Joey Parrish).\n * Added --font-scale option.\n * Added filename, dirname and user font size options (Carl Colena).\n * Added workaround for FFMpeg error parsing Gource PPM video output.\n * Fixed a bug in the Mercurial log parser that caused changes to be missed.\n * Fixed file removal being cancelled by an action with an earlier timestamp.\n * Fixed a bug in the log file format detection that could result in the wrong\n   first entry being displayed for a custom log.\n * Fixed a bug where automatically cycling through a config with multiple\n   repositories reset settings to their defaults.\n\n0.49:\n * Fixed compatibility with GLM 0.9.9.0.\n\n0.48:\n * Can now generate logs from git sub module directories.\n\n0.47:\n * Fixed low mouse movement speed with some mice while repositioning camera.\n\n0.46:\n * Added --screen option.\n\n0.45:\n * Added --window-position XxY option.\n * Added --frameless option.\n * Added --file-show-filter REGEX option (swoogles).\n * Added --filename-time and filename-colour options (gh2k).\n * Improved handling of very low seconds-per-day values (malengrin).\n * Fixed crash when SVN log lacks author information (obarthel).\n * Additional git log command validation (cym13).\n * Allow lower case hex colours in custom logs (HSorensen).\n * Enabled STDOUT support with --output-stream '-' on Windows (circulosmeos).\n * Now requires SDL 2 to build (deprecated SDL 1.2 support removed).\n\n0.44:\n * Fixed crash when taking a screenshot with an odd resolution.\n * Fixed type deduction compilation error with newer versions of GCC.\n * Documentation improvements.\n\n0.43:\n * Updated boost autoconf macros to fix multi-arch detection.\n\n0.42:\n * Fixed bzr log command when no start date was specified (chrisf).\n * Fixed hg log commit order when date range specified.\n * Fixed hg log command line on Windows.\n * Fixed parser bug in date range filtering code.\n\n0.41:\n * Multi-monitor support using SDL 2.0 when available.\n * SDL 1.2 support is deprecated.\n * Full screen mode now uses desktop resolution by default.\n * Added --start-date, --stop-date 'YYYY-MM-DD hh:mm:ss' options.\n * Added --dir-name-depth option.\n * Changed --file-idle-time default value to 0.\n * Changed screenshot format to PNG.\n\n0.40:\n * Added caption support.\n * Improved command line interoperability on Windows.\n * Fixed directory deletion short circuiting processing the rest of a commit.\n * Fixed issue loading non-ascii user image filenames on windows.\n * Ignore UTF-8 byte order mark at the start of lines in custom log files.\n * Fix to boost macros for Macs and non-GNU systems (mistydemeo).\n * Autotools improvements (flameeyes).\n\n0.39:\n * Fixed blurry non power of 2 logos.\n * File colour changes now supported in custom logs (rmyorston).\n * Fixed building against Boost 1.50 (svenstaro).\n * Updated boost autoconf macros (flameeyes).\n * Autogen script (matthiaskrgr).\n\n0.38:\n * New high quality sprites.\n * Fullscreen toggle with alt + enter.\n * Window is now resizable. -WIDTHxHEIGHT! creates a non-resizable window.\n * Lowered minimum zoom distance.\n * Use AM_CPPFLAGS in Makefile.am to allow passing custom CPPFLAGS.\n * Don't add files that match the path of a known directory.\n * Fixed divide by zero in text shader causing artifacts on some video cards.\n * Recursively search for repository directory when log-format not specified\n   (thanks to Jörg Bachmann for original concept / prototype).\n * New dependency on Boost Filesystem.\n * Doubled the maximum zoom out distance.\n * Allow negative timestamps before 1970 in custom log (artzub).\n * Fix for UTF8-CPP checked.h compilation issue (vszakats).\n * Fixed bug causing missing characters in text.\n * Fixed --highlight-users option not using highlight-colour.\n * highlight-colour default changed to white.\n * Added --selection-colour option (applied to selected users and files).\n * Added --dir-colour option (applied to directories).\n\n0.37:\n * Made SVN log GMT timestamp conversion fix portable.\n\n0.36:\n * Fixed SVN log GMT timestamp conversion.\n * Fixed issue with sub-dirs of deleted dir not being removed in some cases.\n\n0.35:\n * Added long file extension truncation handling to file key (--key).\n * Treat changes in Mercurial log files with the same time/user as one commit.\n * Fixed handling of spaces in directory names with Mercurial.\n * Fixed --font-colour option.\n\n0.34:\n * Now using VBOs and shaders for faster rendering when OpenGL 2.0 is available.\n * Eliminated bloom colour banding artifacts (requires OpenGL 2.0).\n * New font rendering library derived from FTGL (FTGL no longer required).\n * Single pass font/shadow rendering (with lots of help from Chris Forbes).\n * Added --no-vsync option.\n * Fixed bug where tree is out of alignment with object positions in windowed\n   mode due to using the wrong display dimensions internally.\n * Removed default max-files limit.\n\n0.33:\n * Added --hide root option to not draw branches from the root directory.\n * Fixed log parsing of Bazaar merges and tagged commits.\n * --output-custom-log now skips unparsed log entries instead of exiting.\n\n0.32:\n * Fixed behaviour of user camera tracking.\n\n0.31:\n * Added --with-tinyxml option to configure (use the system TinyXML library).\n\n0.30:\n * Fixed crash when SVN log entry contains no 'paths' element.\n * Handle directory deletion (happens in SVN logs).\n\n0.29:\n * SVN built-in support.\n * cvs2cl log support (cvs-exp support is now deprecated).\n * Made camera behaviour when zooming and selecting objects more intuitive.\n * Improved interactive performance.\n * Added file extension key (--key or toggled with 'K').\n * Added mouse-over tool tips.\n * Added --highlight-colour option.\n * Added --hash-seed option. The S key now randomizes colours.\n * Added --output-custom-log option.\n * Exposed --time-scale option (previously only available interactively).\n * Removed arbitrary 1024 maximum length limit for log lines.\n * Fixed two file colouring bugs (quoted files from git, period in file path).\n * Fix handling of avatars for UTF-8 usernames on MACOSX (Christian Köstlin).\n * Recover from video mode failing to set due to multi-sampling (Siddhesh Poyarekar).\n\n0.28:\n * Bazaar support for merged commits (Nick Moffit).\n * C++ efficiency improvements (Oliver Smith).\n * Improved cvs-exp log compatibility.\n * Re-show name of user when adding a new action if user is idle.\n * Added --padding option to control camera view padding.\n * More accurate camera tracking (tracks the bounding boxes of objects).\n * Improved automatic rotation logic.\n\n0.27:\n * Display time stops at the time of the last commit.\n * Users fade out when end reached rather than ending abruptly.\n * Position slider is now hidden by default if recording a video.\n * Automatic camera rotation for better use of screen space.\n * Support international keyboards (Peter Kjellerstedt).\n * C++ efficiency improvements (Jacob Metcalfe).\n * Fixed crash when reading from STDIN.\n * Fixed intermittent crash closing Gource when using --output-ppm-stream.\n * Added ini style config file support (see --load/save-config).\n * Added screenshot button (F12). Screenshots respect the alpha channel.\n * Added --transparent to make the background see-through (for screenshots).\n * Added --logo and --background-image options.\n * Added --dont-stop option for manual exiting when recording videos.\n * Added --stop-at-time option to stop Gource after a number of seconds.\n * Added --hide 'mouse' option.\n * Added --highlight-dirs option.\n * Added --file-extensions to show filename extensions only.\n * Added --user-filter REGEX option.\n * Allow --file-idle-time 0 (files will never expire).\n * Allow --start-position 'random' to set a random start position.\n * --log-command VCS replaces multiple --log-command-VCS options.\n * Replaced --disable-progress and --disable-bloom with arguments to --hide.\n\n0.26a:\n * Updated to latest version of GL autoconf macros.\n\n0.26:\n * Improved mouse dragging.\n * Holding right mouse button and moving the mouse rotates the view.\n * The middle mouse button now toggles the camera mode.\n * Username positions now scale correctly.\n * Simulation time no longer incremented while paused, counting towards file time lag.\n * M key now toggles mouse visibility.\n * Added --hide option to use instead of multiple --hide-ELEMENT options.\n\n0.25:\n * Bazaar support (John Arbash Meinel).\n * Dragging the mouse on the background moves the camera.\n * Added --camera-mode option (track,overview).\n * Support DOS line endings in logs.\n * Improved compatibility of hg log command (Brendan Cully).\n * Fixed PPM exporter producing blank images on some video cards.\n * Fixed parsing of negative timezones from cvs-exp.pl logs.\n * Fixed various gdb and compiler warnings.\n\n0.24:\n * PPM output speed improvements, including using a second thread (HighlandSun).\n * Now using standard autotools (Flameeyes).\n * Fixed --max-file-lag not working with some custom log files.\n * Gource will stop at the end of the log by default when recording a video.\n * Fixed STDIN stopping working after the first lot of records with tail -f.\n * Added proper exception handling.\n * Print errors to stderr.\n\n0.23:\n * Hiding filenames no longer hides directory names.\n * Fixed --stop-on-idle option.\n * Added --stop-at-end option (more intuitive than --stop-position 1.0).\n\n0.22:\n * Fixed Mercurial log order.\n\n0.21b:\n * Fixed windows build.\n\n0.21: \n * Some documentation fixes.\n\n0.20:\n * Added bloom effect (turn off with --disable-bloom).\n * Added support for Mercurial (thanks to Justin Love for gource.style).\n * --start-position option now works in combination with --disable-progress.\n\n0.19:\n * Use time_t instead of long for timestamps.\n\n0.18:\n * Fixed camera movement while the simulation is paused.\n\n0.17:\n * Show correct month for CVS logs.\n * Added time scaling (Bitshifter).\n\n0.16:\n * Added --hide-dirnames option.\n * Added --user-scale option.\n * Added --date-format option (Pat Notz).\n * Fix bug when encountering long log lines.\n * Fixed incorrect parsing of trivial merges.\n * Fixed building issues on OSX.\n\n0.15:\n * Added PPM output support for creating videos (Johannes Schindelin).\n * Added experimental Apache combined access log support (--log-format apache).\n * Added --stop-position and --stop-on-idle options (useful for making videos).\n * Added --max-file-lag option to limit how long after a commit file changes can take to appear.\n * Added --no-renames to the git log command as they don't display correctly.\n * Added --max-user-speed and --user-friction as options.\n * Now builds on OSX Leopard (with the required libaries installed).\n * Caught raw exception from replace_invalid().\n * Added CXXFLAGS. You may want to override the default (eg CXXFLAGS=-O3 ./configure).\n\n0.14:\n * Updated SVN instructions.\n\n0.13:\n * Removed single quotes from log command (fixes running gource --git-log-command in back ticks)\n * Added SVN instructions.\n * Fixed manpage hyphens.\n\n0.12:\n * Added --enable-ttf-font-dir=DIR option to configure (to specify the ttf-freefont directory).\n * UTF-8 support using UTF8-CPP (http://utfcpp.sourceforge.net/).\n * Changed the git log command (see --git-log-command) to require less work to parse.\n   Log files generated with the previous git-log command used by gource should continue to work.\n * Allow --seconds-per-day value less than 1.0.\n * Added --git-branch command.\n * Added --loop command.\n * Added --crop command.\n\n0.11:\n * Made N key skip to next entry.\n * Documented --user-image-dir flag.\n * temp file name now uses uid instead of time\n\n0.1:\n * First release.\n"
  },
  {
    "path": "INSTALL",
    "content": "This file is to help you configure, build and install Gource for your system.\n\nContents\n========\n\n1. Dependencies\n2. Building\n3. Configure Options\n\n1. Dependencies\n===============\n\nGource requires the following libraries to compile (package names may vary):\n\n    SDL 2.0 (libsdl2-dev)\n    SDL Image 2.0 (libsdl2-image-dev)\n    PCRE2 (libpcre2-dev)\n    Freetype 2 (libfreetype6-dev)\n    GLEW (libglew-dev)\n    GLM >= 0.9.3 (libglm-dev)\n    Boost Filesystem >= 1.69 (libboost-filesystem-dev)\n    PNG >= 1.2 (libpng-dev)\n\nOptional:\n\n   TinyXML (libtinyxml-dev)\n\nSDL Image 2.0 needs to have been built with support PNG and JPEG images.\n\n2. Building\n===========\n\nGource requires a GNU compatible C++ compiler that supports c++0x features such as 'auto' and the new 'for' loop syntax.\n\nGCC 4.6+ or Clang recommended.\n\nIf you got the source directly from the Gource.git repository, you will first\nneed to run autogen.sh which will generate the configure script and\ninitialize and update the submodules.\n\n    ./autogen.sh\n\nGeneric build instructions for Linux/Mac OS:\n\n    ./configure\n    make\n    make install\n\nBuilding on Windows:\n\nOn Windows compile gource.pro with Qt Creator.\n\nA pre-built version for Windows is normally available from the homepage.\n\nGource expects SDL to have been built with the NO_STDIO_REDIRECT flag.\n\n3. Configure Options\n====================\n\nBy default Gource will install some GNU FreeFont TTF Fonts on your system.\n\nIf you already have these fonts, you can configure Gource to use them with:\n\n    ./configure --enable-ttf-font-dir=/path/to/freefont/\n\nYou can also build Gource with a different font:\n\n    ./configure --enable-font-file=/path/to/alternate/font.ttf\n\nThe font file format must be supported by Free Type 2.\n\nGource includes a copy of TinyXML. To build against the system version of the\nlibrary use:\n\n    ./configure --with-tinyxml\n\n"
  },
  {
    "path": "Makefile.am",
    "content": "ACLOCAL_AMFLAGS = -I m4\n\nbin_PROGRAMS = gource\n\ngource_CXXFLAGS = -std=gnu++0x -Wall -Wno-sign-compare -Wno-reorder -Wno-unused-but-set-variable -Wno-unused-variable\n\nsources = \\\n\tsrc/action.cpp \\\n\tsrc/bloom.cpp \\\n\tsrc/caption.cpp \\\n\tsrc/core/conffile.cpp \\\n\tsrc/core/display.cpp \\\n\tsrc/core/frustum.cpp \\\n\tsrc/core/fxfont.cpp \\\n\tsrc/core/logger.cpp \\\n\tsrc/core/mousecursor.cpp \\\n\tsrc/core/plane.cpp \\\n\tsrc/core/ppm.cpp \\\n\tsrc/core/quadtree.cpp \\\n\tsrc/core/regex.cpp \\\n\tsrc/core/resource.cpp \\\n\tsrc/core/sdlapp.cpp \\\n\tsrc/core/seeklog.cpp \\\n\tsrc/core/settings.cpp \\\n\tsrc/core/shader.cpp \\\n\tsrc/core/shader_common.cpp \\\n\tsrc/core/stringhash.cpp \\\n\tsrc/core/texture.cpp \\\n\tsrc/core/png_writer.cpp \\\n\tsrc/core/timezone.cpp \\\n\tsrc/core/vbo.cpp \\\n\tsrc/core/vectors.cpp \\\n\tsrc/dirnode.cpp \\\n\tsrc/file.cpp \\\n\tsrc/formats/apache.cpp \\\n\tsrc/formats/bzr.cpp \\\n\tsrc/formats/commitlog.cpp \\\n\tsrc/formats/custom.cpp \\\n\tsrc/formats/cvs-exp.cpp \\\n\tsrc/formats/cvs2cl.cpp \\\n\tsrc/formats/git.cpp \\\n\tsrc/formats/gitraw.cpp \\\n\tsrc/formats/hg.cpp \\\n\tsrc/formats/svn.cpp \\\n\tsrc/gource.cpp \\\n\tsrc/gource_shell.cpp \\\n\tsrc/gource_settings.cpp \\\n\tsrc/key.cpp \\\n\tsrc/logmill.cpp \\\n\tsrc/pawn.cpp \\\n\tsrc/slider.cpp \\\n\tsrc/spline.cpp \\\n\tsrc/textbox.cpp \\\n\tsrc/user.cpp \\\n\tsrc/zoomcamera.cpp\n\nif USE_BUNDLED_TINYXML\nsources += \\\n\tsrc/tinyxml/tinyxmlerror.cpp \\\n\tsrc/tinyxml/tinystr.cpp \\\n\tsrc/tinyxml/tinyxml.cpp \\\n\tsrc/tinyxml/tinyxmlparser.cpp\nendif\n\ngource_SOURCES = src/main.cpp ${sources}\n\nAM_CPPFLAGS = -DSDLAPP_RESOURCE_DIR=\\\"$(pkgdatadir)\\\"\n\ndist_pkgdata_DATA = data/beam.png data/bloom.tga data/bloom_alpha.tga data/file.png data/user.png data/gource.style\n\nshadersdir = $(pkgdatadir)/shaders\ndist_shaders_DATA = data/shaders/shadow.vert data/shaders/shadow.frag data/shaders/bloom.vert data/shaders/bloom.frag data/shaders/text.vert data/shaders/text.frag\n\ninstall-data-hook:\n\t$(MKDIR_P) $(DESTDIR)$(mandir)/man1\n\tgzip -cf9 $(srcdir)/data/gource.1 > $(DESTDIR)$(mandir)/man1/gource.1.gz\n\nuninstall-hook:\n\trm -f $(DESTDIR)$(mandir)/man1/gource.1.gz\n\nif FONTFILE\nAM_CPPFLAGS += -DGOURCE_FONT_FILE=\\\"$(gourcefontfile)\\\"\nelse\nif FONTDIR\nAM_CPPFLAGS += -DSDLAPP_FONT_DIR=\\\"$(gourcefontdir)\\\"\nelse\nfontsdir = $(pkgdatadir)/fonts\ndist_fonts_DATA = data/fonts/README data/fonts/FreeSans.ttf\nendif\nendif\n\ncheck_PROGRAMS = gource_tests\ngource_tests_CPPFLAGS = -I src/test/ ${BOOST_CPPFLAGS}\ngource_tests_LDFLAGS = ${BOOST_LDFLAGS}\ngource_tests_LDADD = ${BOOST_UNIT_TEST_FRAMEWORK_LIB}\n\ngource_tests_SOURCES = \\\n\tsrc/test/main.cpp \\\n\tsrc/test/datetime_tests.cpp \\\n\tsrc/test/regex_tests.cpp \\\n\t${sources}\n\nTESTS = gource_tests\n"
  },
  {
    "path": "README.md",
    "content": "Gource\n======\n\nhttps://gource.io\n\nDescription\n===========\n\nGource is a visualization tool for source control repositories.\n\nThe repository is displayed as a tree where the root of the repository is the\ncentre, directories are branches and files are leaves. Contributors to the\nsource code appear and disappear as they contribute to specific files and\ndirectories.\n\nRequirements\n============\n\nGource's display is rendered using OpenGL and requires a 3D accelerated video\ncard to run.\n\nUsing Gource\n============\n\n```\ngource [options] [path]\n\noptions:\n\n    -h, --help\n            Help ('-H' for extended help).\n\n    -WIDTHxHEIGHT, --viewport WIDTHxHEIGHT\n            Set the viewport size. If -f is also supplied, will attempt to set\n            the video mode to this also. Add ! to make the window non-resizable.\n\n    --screen SCREEN\n            Set the number of the screen to display on.\n\n    --high-dpi\n            Request a high DPI display when creating the window.\n\n            On some platforms such as MacOS, the window resolution is specified in points instead of pixels.\n            The --high-dpi flag may be required to access some higher resolutions.\n\n            E.g. requesting a high DPI 800x600 window may produce a window that is 1600x1200 pixels.\n\n    --window-position XxY\n            Initial window position on your desktop which may be made up of\n            multiple monitors.\n\n            This will override the screen setting so don't specify both.\n\n    --frameless\n            Frameless window.\n\n    -f, --fullscreen\n            Fullscreen.\n\n    -w, --windowed\n            Windowed.\n\n    --transparent\n            Make the background transparent. Only really useful for screenshots.\n\n    --start-date \"YYYY-MM-DD hh:mm:ss +tz\"\n            Start with the first entry after the supplied date and optional time.\n\n            If a time zone offset isn't specified the local time zone is used.\n\n            Example accepted formats:\n\n                \"2012-06-30\"\n                \"2012-06-30 12:00\"\n                \"2012-06-30 12:00:00 +12\"\n\n    --stop-date \"YYYY-MM-DD hh:mm:ss +tz\"\n            Stop after the last entry prior to the supplied date and optional time.\n\n            Uses the same format as --start-date.\n\n    -p, --start-position POSITION\n            Begin at some position in the log (between 0.0 and 1.0 or 'random').\n\n        --stop-position  POSITION\n            Stop (exit) at some position in the log (does not work with STDIN).\n\n    -t, --stop-at-time SECONDS\n            Stop (exit) after a specified number of seconds.\n\n        --stop-at-end\n            Stop (exit) at the end of the log / stream.\n\n        --loop\n            Loop back to the start of the log when the end is reached.\n\n        --loop-delay-seconds\n            Seconds to delay before looping.\n\n    -a, --auto-skip-seconds SECONDS\n            Skip to next entry if nothing happens for a number of seconds.\n\n    -s, --seconds-per-day SECONDS\n            Speed of simulation in seconds per day.\n\n        --realtime\n            Realtime playback speed.\n\n        --no-time-travel\n            Use the time of the last commit if the time of a commit is in the past.\n\n        --author-time\n            Use the timestamp of the author instead of the timestamp of the committer.\n\n    -c, --time-scale SCALE\n            Change simulation time scale. This affects the movement speed of user avatars.\n\n            E.g. 0.5 for half speed, 2 for double speed.\n\n    -i, --file-idle-time SECONDS\n            Time in seconds files remain idle before they are removed or 0\n            for no limit.\n\n        --file-idle-time-at-end SECONDS\n            Time in seconds files remain idle at the end before they are\n            removed.\n\n    -e, --elasticity FLOAT\n            Elasticity of nodes.\n\n    -b, --background-colour FFFFFF\n            Background colour in hex.\n\n    --background-image IMAGE\n            Set a background image.\n\n    --logo IMAGE\n            Logo to display in the foreground.\n\n    --logo-offset XxY\n            Offset position of the logo.\n\n    --title TITLE\n            Set a title.\n\n    --font-file FILE\n            Specify the font. Should work with most font file formats supported by FreeType, such as TTF and OTF, among others.\n\n    --font-scale SCALE\n            Scale the size of all fonts.\n\n    --font-size SIZE\n            Font size used by the date and title.\n\n    --file-font-size SIZE\n            Font size of filenames.\n\n    --dir-font-size SIZE\n            Font size of directory names\n\n    --user-font-size SIZE\n            Font size of user names.\n\n    --font-colour FFFFFF\n            Font colour used by the date and title in hex.\n\n    --key\n            Show file extension key.\n\n    --date-format FORMAT\n            Specify display date string (strftime format).\n\n    --log-command VCS\n            Show the VCS log command used by gource (git,svn,hg,bzr,cvs2cl).\n\n    --log-format VCS\n            Specify the log format (git,svn,hg,bzr,cvs2cl,custom).\n\n            Required when reading from STDIN.\n\n    --git-branch\n            Get the git log of a branch other than the current one.\n\n    --follow-user USER\n            Have the camera automatically follow a particular user.\n\n    --highlight-dirs\n            Highlight the names of all directories.\n\n    --highlight-user USER\n            Highlight the names of a particular user.\n\n    --highlight-users\n            Highlight the names of all users.\n\n    --highlight-colour FFFFFF\n            Font colour for highlighted users in hex.\n\n    --selection-colour FFFFFF\n            Font colour for selected users and files.\n\n    --filename-colour FFFFFF\n            Font colour for filenames.\n\n    --dir-colour FFFFFF\n            Font colour for directories.\n\n    --dir-name-depth DEPTH\n            Draw names of directories down to a specific depth in the tree.\n\n    --dir-name-position FLOAT\n            Position along edge of the directory name\n            (between 0.1 and 1.0, default is 0.5).\n\n    --filename-time SECONDS\n            Duration to keep filenames on screen (>= 2.0).\n\n    --file-extensions\n            Show filename extensions only.\n\n    --file-extension-fallback\n            Use filename as extension if the extension is missing or empty.\n\n    --file-filter REGEX\n            Filter out file paths matching the specified regular expression.\n\n    --file-show-filter REGEX\n            Show only file paths matching the specified regular expression.\n\n    --user-filter REGEX\n            Filter usernames matching the specified regular expression.\n\n    --user-show-filter REGEX\n            Show only usernames matching the specified regular expression.\n\n    --user-image-dir DIRECTORY\n            Directory containing .jpg or .png images of users\n            (eg \"Full Name.png\") to use as avatars.\n\n    --default-user-image IMAGE\n            Path of .jpg or .png to use as the default user image.\n\n    --fixed-user-size\n            Forces the size of the user image to remain fixed throughout.\n\n    --colour-images\n            Colourize user images.\n\n    --crop AXIS\n            Crop view on an axis (vertical,horizontal).\n\n    --padding FLOAT\n            Camera view padding.\n\n    --multi-sampling\n            Enable multi-sampling.\n\n    --no-vsync\n            Disable vsync.\n\n    --bloom-multiplier FLOAT\n            Adjust the amount of bloom.\n\n    --bloom-intensity FLOAT\n            Adjust the intensity of the bloom.\n\n    --max-files NUMBER\n            Set the maximum number of files or 0 for no limit.\n\n            Excess files will be discarded.\n\n    --max-file-lag SECONDS\n            Max time files of a commit can take to appear.\n\n            Use -1 for no limit.\n\n    --max-user-speed UNITS\n            Max speed users can travel per second.\n\n    --user-friction SECONDS\n            Time users take to come to a halt.\n\n    --user-scale SCALE\n            Change scale of user avatars.\n\n    --camera-mode MODE\n            Camera mode (overview,track).\n\n    --disable-auto-rotate\n            Disable automatic camera rotation.\n\n    --disable-input\n            Disable keyboard and mouse input.\n\n    --hide DISPLAY_ELEMENT\n            Hide one or more display elements from the list below:\n\n            bloom     - bloom effect\n            date      - current date\n            dirnames  - names of directories\n            files     - file icons\n            filenames - names of files\n            mouse     - mouse cursor\n            progress  - progress bar widget\n            root      - root directory of tree\n            tree      - animated tree structure\n            users     - user avatars\n            usernames - names of users\n\n            Separate multiple elements with commas (eg \"mouse,progress\")\n\n    --hash-seed SEED\n            Change the seed of hash function.\n\n    --caption-file FILE\n            Caption file (see Caption Log Format).\n\n    --caption-size SIZE\n            Caption size.\n\n    --caption-colour FFFFFF\n            Caption colour in hex.\n\n    --caption-duration SECONDS\n            Caption duration.\n\n    --caption-offset X\n            Caption horizontal offset (0 to centre captions).\n\n    -o, --output-ppm-stream FILE\n            Output a PPM image stream to a file ('-' for STDOUT).\n\n            This will automatically hide the progress bar initially and\n            enable 'stop-at-end' unless other behaviour is specified.\n\n    -r, --output-framerate FPS\n            Framerate of output (25,30,60). Used with --output-ppm-stream.\n\n    --output-custom-log FILE\n            Output a custom format log file ('-' for STDOUT).\n\n    --load-config CONFIG_FILE\n            Load a gource conf file.\n\n    --save-config CONFIG_FILE\n            Save a gource conf file with the current options.\n\n    --path PATH\n\n    path    Either a supported version control directory, a pre-generated log\n            file (see log commands or the custom log format), a Gource conf\n            file or '-' to read STDIN.\n\n            If path is omitted, gource will attempt to read a log from the\n            current directory.\n```\n\nGit, Bazaar, Mercurial and SVN Examples:\n\nView the log of the repository in the current path:\n\n```\n    gource\n```\n\nView the log of a project in the specified directory:\n\n```\n    gource my-project-dir\n```\n\nFor large projects, generating a log of the project history may take a long\ntime. For centralized VCS like SVN, generating the log may also put load on\nthe central VCS server.\n\nIn these cases, you may like to save a copy of the log for later use.\n\nYou can generate a log in the VCS specific log format using\nthe --log-command VCS option:\n\n```\n    cd my-svn-project\n    `gource --log-command svn` > my-svn-project.log\n    gource my-svn-project.log\n```\n\nYou can also have Gource write a copy of the log file in its own format:\n\n```\n    gource --output-custom-log my-project-custom.log\n```\n\nCVS Support:\n\nUse 'cvs2cl' to generate the log and then pass it to Gource:\n\n```\n    cvs2cl --chrono --stdout --xml -g-q > my-cvs-project.log\n    gource my-cvs-project.log\n```\n\nCustom Log Format:\n\nIf you want to use Gource with something other than the supported systems,\nthere is a pipe ('|') delimited custom log format:\n\n    timestamp - An ISO 8601 or unix timestamp of when the update occurred.\n    username  - The name of the user who made the update.\n    type      - initial for the update type - (A)dded, (M)odified or (D)eleted.\n    file      - Path of the file updated.\n    colour    - A colour for the file in hex (FFFFFF) format. Optional.\n\nCaption Log Format:\n\nGource can display captions along the timeline by specifying a caption file\n(using --caption-file) in the pipe ('|') delimited format below:\n\n    timestamp - An ISO 8601 or A unix timestamp of when to display the caption.\n    caption   - The caption\n\nRecording Videos:\n\nSee the guide on the homepage for examples of recording videos with Gource:\n\nhttps://github.com/acaudwell/Gource/wiki/Videos\n\nMore Information:\n\nVisit the Gource homepage for guides and examples of using Gource with various\nversion control systems:\n\nhttps://gource.io\n\nInterface:\n\nThe time shown in the top left of the screen is set initially from the first\nlog entry read and is incremented according to the simulation speed\n(--seconds-per-day).\n\nPressing SPACE at any time will pause/resume the simulation. While paused you\nmay use the mouse to inspect the detail of individual files and users.\n\nTAB cycles through selecting the current visible users.\n\nThe camera mode, either tracking activity or showing the entire code tree, can\nbe toggled using the Middle mouse button.\n\nYou can drag the left mouse button to manually control the camera. The right\nmouse button rotates the view.\n\nInteractive keyboard commands:\n\n    (V)   Toggle camera mode\n    (C)   Displays Gource logo\n    (K)   Toggle file extension key\n    (M)   Toggle mouse visibility\n    (N)   Jump forward in time to next log entry\n    (S)   Randomize colours\n    (D)   Toggle directory name display mode\n    (F)   Toggle file name display mode\n    (U)   Toggle user name display mode\n    (G)   Toggle display of users\n    (T)   Toggle display of directory tree edges\n    (R)   Toggle display of root directory edges\n    (<>)  Adjust time scale / user avatar movement speed\n    (+-)  Adjust simulation speed\n    (Keypad +-) Adjust camera zoom\n    (TAB) Cycle through visible users\n    (F12) Screenshot\n    (Alt+Enter) Fullscreen toggle\n    (ESC) Quit\n\nCopyright\n=========\n\nGource - software version control visualization\nCopyright (C) 2009 Andrew Caudwell <acaudwell@gmail.com>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "THANKS",
    "content": "Cheers to everyone at Catalyst IT for their support and encouragement.\n"
  },
  {
    "path": "autogen.sh",
    "content": "#! /bin/sh\nif (autoreconf -f -i) ; then\n\techo \"autoreconf ran successfully.\"\n\techo \"Initializing submodules...\"\n\tif (git submodule init) ; then\n\t\techo \"Updating submodules...\"\n\t\tif (git submodule update --init --recursive) ; then\n\t\t\techo \"Run './configure && make' to continue.\"\n\t\telse\n\t\t\techo \"Error: could not update submodules!\"\n\t\tfi\n\telse\n\t\techo \"Error: could not initialize submodules!\"\n\tfi\nelse\n\techo \"Running autoreconf failed, please make sure you have autoconf installed.\"\nfi\n"
  },
  {
    "path": "cmd/gource",
    "content": "#!/bin/sh\nGOURCE_CMD_DIR=`dirname \"$0\"`\n\"$GOURCE_CMD_DIR/../gource.exe\" \"$@\"\n"
  },
  {
    "path": "cmd/gource.cmd",
    "content": "@echo off\r\n\"%~dp0\\..\\gource.exe\" %*\r\n"
  },
  {
    "path": "configure.ac",
    "content": "#                                               -*- Autoconf -*-\n# Process this file with autoconf to produce a configure script.\n\nAC_PREREQ(2.61)\n\nAC_INIT(Gource, 0.57, [acaudwell@gmail.com])\nAC_CONFIG_AUX_DIR([build-aux])\nAC_CONFIG_SRCDIR([src/main.h])\nAM_INIT_AUTOMAKE([dist-bzip2 foreign subdir-objects])\n\nAC_CANONICAL_HOST\n\n# Checks for programs.\nAC_PROG_CXX\nAC_LANG(C++)\n\nAC_CHECK_FUNCS([timegm unsetenv])\n\n#Disable X11 on Macs unless required\nAS_IF([test \"$with_x\" != yes], [\n    case \"$host_os\" in\n    darwin*)\n        with_x=\"no\"\n        LIBS=\"$LIBS -framework CoreFoundation\"\n    ;;\n    esac\n], [])\n\n#GL\nAX_CHECK_GL\nAX_CHECK_GLU\nCXXFLAGS=\"$CXXFLAGS $GL_CFLAGS $GLU_CFLAGS\"\nCPPFLAGS=\"$CPPFLAGS $GL_CFLAGS $GLU_CFLAGS\"\nLIBS=\"$LIBS $GL_LIBS $GLU_LIBS\"\n\nPKG_CHECK_MODULES([FT2], [freetype2 >= 9.0.3])\nPKG_CHECK_MODULES([PCRE2], [libpcre2-8])\nPKG_CHECK_MODULES([GLEW], [glew])\nPKG_CHECK_MODULES([SDL2], [sdl2 SDL2_image]);\nPKG_CHECK_MODULES([PNG], [libpng >= 1.2])\n\nCPPFLAGS=\"${CPPFLAGS} ${FT2_CFLAGS} ${PCRE2_CFLAGS} ${GLEW_CFLAGS} ${SDL2_CFLAGS} ${PNG_CFLAGS}\"\nLIBS=\"${LIBS} ${FT2_LIBS} ${PCRE2_LIBS} ${GLEW_LIBS} ${SDL2_LIBS} ${PNG_LIBS}\"\n\nAC_CHECK_FUNCS([IMG_LoadPNG_RW], , AC_MSG_ERROR([SDL2_image with PNG support required. Please see INSTALL]))\nAC_CHECK_FUNCS([IMG_LoadJPG_RW], , AC_MSG_ERROR([SDL2_image with JPEG support required. Please see INSTALL]))\n\n#BOOST\nAX_BOOST_BASE([1.69], , AC_MSG_ERROR(Boost Filesystem >= 1.69 is required. Please see INSTALL))\nAX_BOOST_FILESYSTEM\n\nCPPFLAGS=\"$CPPFLAGS $BOOST_CPPFLAGS\"\nLIBS=\"$LIBS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB\"\n\nAC_ARG_ENABLE(font-file,[AS_HELP_STRING([--enable-font-file=FILE],[override the default font])],[gourcefontfile=\"$enableval\"],[gourcefontfile=\"\"])\nAM_CONDITIONAL([FONTFILE], [test \"x$gourcefontfile\" != \"x\"])\nAC_SUBST(gourcefontfile)\n\n#see if ttf-font-dir option is enabled\nAC_ARG_ENABLE(ttf-font-dir,[AS_HELP_STRING([--enable-ttf-font-dir=DIR],[directory containing GNU FreeFont TTF fonts])],[gourcefontdir=\"$enableval\"],[gourcefontdir=\"\"])\nAM_CONDITIONAL([FONTDIR], [test \"x$gourcefontdir\" != \"x\"])\nAC_SUBST(gourcefontdir)\n\n#GLM\nAC_CHECK_HEADER([glm/glm.hpp],, AC_MSG_ERROR(GLM headers are required. Please see INSTALL))\n\n#see if building against system TinyXML library\nuse_tinyxml_system_library=no\nAC_ARG_WITH(tinyxml, AS_HELP_STRING([--with-tinyxml],[Use system installed TinyXML library]), use_tinyxml_system_library=$withval)\nif test \"x$use_tinyxml_system_library\" = \"xyes\"; then\n    AC_CHECK_LIB([tinyxml],[main],[],[AC_MSG_ERROR(TinyXML library was requested but not found)],[])ac_cv_lib_tinyxml=ac_cv_lib_tinyxml_main\n\nfi\nAM_CONDITIONAL(USE_BUNDLED_TINYXML, test \"x$use_tinyxml_system_library\" != \"xyes\")\n\nAC_CONFIG_FILES([Makefile])\nAC_OUTPUT\n"
  },
  {
    "path": "contrib/svn-gource.py",
    "content": "#!/usr/bin/python\n## Copyright (c) 2009 Cameron Hart (cam@bitshifter.net.nz)\n## All rights reserved.\n##\n## Redistribution and use in source and binary forms, with or without\n## modification, are permitted provided that the following conditions\n## are met:\n## 1. Redistributions of source code must retain the above copyright\n##    notice, this list of conditions and the following disclaimer.\n## 2. Redistributions in binary form must reproduce the above copyright\n##    notice, this list of conditions and the following disclaimer in the\n##    documentation and/or other materials provided with the distribution.\n## 3. The name of the author may not be used to endorse or promote products\n##    derived from this software without specific prior written permission.\n##\n## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\nimport sys\nimport time\nimport getopt\nimport re\nfrom xml.etree import ElementTree\n\nopt_filter_dirs = False\n\n_USAGE = \"\"\"\nsvn-gource.py [--help] [--filter-dirs] <file>\n\nThe input file must be the output of the command svn log --verbose --xml.\n\"\"\"\n\n# regular expression for matching any file with an extension\nextn_prog = re.compile(\".*/?[^/]+\\.[^\\.]+$\")\n\ndef reverse(data):\n    \"\"\"Returns the log entries in reverse.\"\"\"\n    for index in range(len(data)-1, -1, -1):\n        yield data[index]\n\ndef processXmltree(xmltree):\n    global opt_filter_dirs\n    for logentry in reverse(xmltree.getiterator(\"logentry\")):\n        datetext = logentry.find(\"date\").text\n\n        # svn xml logs always use UTC\n        timestamp = (time.mktime(time.strptime(datetext[:-8], \"%Y-%m-%dT%H:%M:%S\")))\n        # a bit of a hack to get it into local time again...\n        #timestamp = timestamp - time.timezone\n\n        #author might not exist\n        try:\n            author = logentry.find(\"author\").text\n        except:\n            author = \"\"\n\n        # output all affected files\n        for pathentry in logentry.getiterator(\"path\"):\n\n            # apply directory filtering strategy\n            if opt_filter_dirs and not re.match(extn_prog, pathentry.text):\n                continue;\n\n            # join output\n            print( \"|\".join( ( \"%d\" % int(timestamp), \"%s\" % author.encode(\"utf-8\"), \"%s\" % pathentry.get(\"action\"), \"%s\" % pathentry.text.encode(\"utf-8\"), \"\" ) ) )\n\ndef printUsage(message):\n    sys.stderr.write(_USAGE)\n    if message:\n        sys.exit('\\nFATAL ERROR: ' + message)\n    else:\n        sys.exit(1)\n\ndef processArguments():\n    global opt_filter_dirs\n\n    try:\n        opts, filenames = getopt.getopt(sys.argv[1:], '', ['help', 'filter-dirs'])\n    except getopt.GetoptError:\n        printUsage('Invalid arguments.')\n\n    for (opt, val) in opts:\n        if opt == '--help':\n            printUsage(None)\n        elif opt == '--filter-dirs':\n            opt_filter_dirs = True\n\n    if not filenames:\n        printUsage('No input file specified.')\n\n    return filenames[0]\n\n\nif __name__ == \"__main__\":\n    filename = processArguments()\n\n    xmltree = ElementTree.parse(filename)\n\n    processXmltree(xmltree)\n\n\n\n"
  },
  {
    "path": "data/fonts/README",
    "content": "-*-text-*-\n                          GNU FreeFont\n\nThe GNU FreeFont project aims to provide a useful set of free scalable\n(i.e., OpenType) fonts covering as much as possible of the ISO 10646/Unicode\nUCS (Universal Character Set).\n\nStatement of Purpose\n--------------------\n\nThe practical reason for putting glyphs together in a single font face is\nto conveniently mix symbols and characters from different writing systems,\nwithout having to switch fonts.\n\nCoverage\n--------\n\nFreeFont covers the following character sets\n\n* ISO 8859 parts 1-15\n* CEN MES-3 European Unicode Subset\n  http://www.evertype.com/standards/iso10646/pdf/cwa13873.pdf\n* IBM/Microsoft code pages 437, 850, 852, 1250, 1252 and more\n* Microsoft/Adobe Windows Glyph List 4 (WGL4)\n  http://www.microsoft.com/typography/otspec/WGL4.htm\n* KOI8-R and KOI8-RU\n* DEC VT100 graphics symbols\n* International Phonetic Alphabet\n* Arabic, Hebrew, Armenian, Georgian, Ethiopian and Thai alphabets,\n  including Arabic presentation forms A/B\n* mathematical symbols, including the whole TeX repertoire of symbols\n* APL symbols\n  etc.\n\nEditing\n-------\n\nThe free outline font editor, George Williams's FontForge\n<http://fontforge.sourceforge.net/> is used for editing the fonts.\n\nDesign Issues\n-------------\n\nWhich font shapes should be made?  Historical style terms like Renaissance\nor Baroque letterforms cannot be applied beyond Latin/Cyrillic/Greek\nscripts to any greater extent than Kufi or Nashki can be applied beyond\nArabic script; \"italic\" is really only meaningful for Latin letters. \n\nHowever, most modern writing systems have typographic formulations for\ncontrasting uniform and modulated character stroke widths, and have some\nhistory with \"oblique\", faces.  Since the advent of the typewriter, most\nhave developed a typographic style with uniform-width characters.\n\nAccordingly, the FreeFont family has one monospaced - FreeMono - and two\nproportional faces (one with uniform stroke - FreeSans - and one with\nmodulated stroke - FreeSerif).\n\nTo make text from different writing systems look good side-by-side, each\nFreeFont face is meant to contain characters of similar style and weight.\n\nLicensing\n---------\n\nFree UCS scalable fonts is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License as published\nby the Free Software Foundation; either version 3 of the License, or\n(at your option) any later version.\n\nThe fonts are distributed in the hope that they will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\nfor more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n\nAs a special exception, if you create a document which uses this font, and\nembed this font or unaltered portions of this font into the document, this\nfont does not by itself cause the resulting document to be covered by the\nGNU General Public License. This exception does not however invalidate any\nother reasons why the document might be covered by the GNU General Public\nLicense. If you modify this font, you may extend this exception to your\nversion of the font, but you are not obligated to do so.  If you do not\nwish to do so, delete this exception statement from your version.\n\n\nFiles and their suffixes\n------------------------\n\nThe files with .sfd (Spline Font Database) are in FontForge's native format. \nPlease use these if you plan to modify the font files.\n\nTrueType fonts for immediate consumption are the files with the .ttf\n(TrueType Font) suffix.  These are ready to use in Xwindows based\nsystems using FreeType, on Mac OS, and on older Windows systems.\n\nOpenType fonts (with suffix .otf) are for use in Windows Vista. \nNote that although they can be installed on Linux, but many applications\nin Linux still don't support them.\n\n\n--------------------------------------------------------------------------\nPrimoz Peterlin, <primoz.peterlin@biofiz.mf.uni-lj.si>\nSteve White <stevan.white@googlemail.com>\n\nFree UCS scalable fonts: http://savannah.gnu.org/projects/freefont/\n$Id: README,v 1.7 2009/01/13 08:43:23 Stevan_White Exp $\n"
  },
  {
    "path": "data/gource.1",
    "content": ".TH Gource 1\n.SH NAME\nGource - a software version control visualization\n.SH SYNOPSIS\n\\fIgource\\fR\n[options] [path]\n.SH DESCRIPTION\n\\fIgource\\fR is an OpenGL-based 3D visualisation tool for source control repositories.\n\nThe repository is displayed as a tree where the root of the repository is the centre, directories are branches and files are leaves. Contributors to the source code appear and disappear as they contribute to specific files and directories.\n.SH REQUIREMENTS\n\\fIgource\\fR\nrequires a OpenGL capable video card to run.\n.SH OPTIONS\n.TP 8\n\\fB\\-h, \\-\\-help\\fR\nHelp ('\\fB-H\\fR' for extended help).\n.TP\n\\fB\\-WIDTHxHEIGHT, \\-\\-viewport WIDTHxHEIGHT\\fR\nSet the viewport size. If \\-f is also supplied, will attempt to set the video mode to this also. Add ! to make the window non-resizable.\n.TP\n\\fB\\-f\\fR\nFullscreen.\n.TP\n\\fB\\-\\-screen SCREEN\\fR\nSet the number of the screen to display on.\n.TP\n\\fB\\-\\-high\\-dpi\\fR\nRequest a high DPI display when creating the window.\n\nOn some platforms such as MacOS, the window resolution is specified in points instead of pixels.\nThe \\-\\-high-dpi flag may be required to access some higher resolutions.\n\nE.g. requesting a high DPI 800x600 window may produce a window that is 1600x1200 pixels.\n\n.TP\n\\fB\\-\\-window\\-position XxY\\fR\nInitial window position on your desktop which may be made up of multiple monitors.\n\nThis will override the screen setting so don't specify both.\n.TP\n\\fB\\-\\-frameless\\fR\nFrameless window.\n.TP\n\\fB\\-\\-transparent\\fR\nMake the background transparent. Only really useful for screenshots.\n.TP\n\\fB\\-\\-start\\-date \"YYYY\\-MM\\-DD hh:mm:ss +tz\"\\fR\nStart with the first entry after the supplied date and optional time.\n\nIf a time zone offset isn't specified the local time zone is used.\n\nExample accepted formats:\n\n    \"2012-06-30\"\n    \"2012-06-30 12:00\"\n    \"2012-06-30 12:00:00 +12\"\n.TP\n\\fB\\-\\-stop\\-date \"YYYY\\-MM\\-DD hh:mm:ss +tz\"\\fR\nStop at the last entry prior to the supplied date and optional time.\n\nUses the same format as \\-\\-start\\-date.\n.TP\n\\fB\\-p, \\-\\-start\\-position POSITION\\fR\nBegin at some position in the log (between 0.0 and 1.0 or 'random').\n.TP\n\\fB\\-\\-stop\\-position  POSITION\\fR\nStop (exit) at some position in the log (does not work with STDIN).\n.TP\n\\fB\\-t, \\-\\-stop\\-at\\-time SECONDS\\fR\nStop (exit) after a specified number of seconds.\n.TP\n\\fB\\-\\-stop\\-at\\-end\\fR\nStop (exit) at the end of the log / stream.\n.TP\n\\fB\\-\\-loop\\fR\nLoop back to the start of the log when the end is reached.\n.TP\n\\fB\\-\\-loop\\-delay\\-seconds SECONDS\\fR\nSeconds to delay before looping.\n.TP\n\\fB\\-a, \\-\\-auto\\-skip\\-seconds SECONDS\\fR\nAutomatically skip to next entry if nothing happens for a specified number of seconds.\n.TP\n\\fB\\-s, \\-\\-seconds\\-per\\-day SECONDS\\fR\nSpeed of simulation in seconds per day.\n.TP\n\\fB\\-\\-realtime\\fR\nRealtime playback speed.\n.TP\n\\fB\\-\\-no\\-time\\-travel\\fR\nUse the time of the last commit if the time of a commit is in the past.\n.TP\n\\fB\\-\\-author\\-time\\fR\nUse the timestamp of the author instead of the timestamp of the committer.\n.TP\n\\fB\\-c, \\-\\-time\\-scale SCALE\\fR\nChange simulation time scale. This affects the movement speed of user avatars.\n\nE.g. 0.5 for half speed, 2 for double speed.\n.TP\n\\fB\\-i, \\-\\-file\\-idle\\-time SECONDS\\fR\nTime in seconds files remain idle before they are removed or 0 for no limit.\n.TP\n\\fB\\-\\-file\\-idle\\-time\\-at\\-end SECONDS\\fR\nTime in seconds files remain idle at the end before they are removed.\n.TP\n\\fB\\-e, \\-\\-elasticity FLOAT\\fR\nElasticity of nodes.\n.TP\n\\fB\\-b, \\-\\-background-colour FFFFFF\\fR\nBackground colour in hex.\n.TP\n\\fB\\-\\-background\\-image IMAGE\\fR\nSet a background image.\n.TP\n\\fB\\-\\-title TITLE\\fR\nSet a title\n.TP\n\\fB\\-\\-font\\-file FILE\\fR\nSpecify the font. Should work with most font file formats supported by FreeType, such as TTF and OTF, among others.\n.TP\n\\fB\\-\\-font\\-scale SCALE\\fR\nScale the size of all fonts.\n.TP\n\\fB\\-\\-font\\-size SIZE\\fR\nFont size used by the date and title.\n.TP\n\\fB\\-\\-file\\-font\\-size SIZE\\fR\nFont size of filenames.\n.TP\n\\fB\\-\\-dir\\-font\\-size SIZE\\fR\nFont size of directory names.\n.TP\n\\fB\\-\\-user\\-font\\-size SIZE\\fR\nFont size of user names.\n.TP\n\\fB\\-\\-font\\-colour FFFFFF\\fR\nFont colour used by the date and title in hex.\n.TP\n\\fB\\-\\-key\\fR\nShow file extension key.\n.TP\n\\fB\\-\\-logo IMAGE\\fR\nLogo to display in the foreground.\n.TP\n\\fB\\-\\-logo\\-offset XxY\\fR\nOffset position of the logo.\n.TP\n\\fB\\-\\-date\\-format FORMAT\\fR\nSpecify display date string (strftime format).\n.TP\n\\fB\\-\\-log\\-command VCS\\fR\nShow the log command used by gource (git,svn,hg,bzr,cvs2cl).\n.TP\n\\fB\\-\\-log\\-format VCS\\fR\nSpecify format of the log being read (git,svn,hg,bzr,cvs2cl,custom). Required when reading from STDIN.\n.TP\n\\fB\\-\\-git\\-branch\\fR\nGet the git log of a branch other than the current one.\n.TP\n\\fB\\-\\-follow\\-user USER\\fR\nHave the camera automatically follow a particular user.\n.TP\n\\fB\\-\\-highlight\\-dirs\\fR\nHighlight the names of all directories.\n.TP\n\\fB\\-\\-highlight\\-user USER\\fR\nHighlight the names of a particular user.\n.TP\n\\fB\\-\\-highlight\\-users\\fR\nHighlight the names of all users.\n.TP\n\\fB\\-\\-highlight\\-colour FFFFFF\\fR\nFont colour for highlighted users in hex.\n.TP\n\\fB\\-\\-selection\\-colour FFFFFF\\fR\nFont colour for selected users and files.\n.TP\n\\fB\\-\\-filename\\-colour FFFFFF\\fR\nFont colour for filenames.\n.TP\n\\fB\\-\\-dir\\-colour FFFFFF\\fR\nFont colour for directories.\n.TP\n\\fB\\-\\-dir\\-name\\-depth DEPTH\\fR\nDraw names of directories down to a specific depth in the tree.\n.TP\n\\fB\\-\\-dir\\-name\\-position FLOAT\nPosition along edge of the directory name (between 0.1 and 1.0, default is 0.5).\n.TP\n\\fB\\-\\-filename\\-time SECONDS\\fR\nDuration to keep filenames on screen (>= 2.0).\n.TP\n\\fB\\-\\-file\\-extensions\\fR\nShow filename extensions only.\n.TP\n\\fB\\-\\-file\\-extension\\-fallback\\fR\nUse filename as extension if the extension is missing or empty.\n.TP\n\\fB\\-\\-file\\-filter REGEX\\fR\nFilter out file paths matching the specified regular expression.\n.TP\n\\fB\\-\\-file\\-show\\-filter REGEX\\fR\nShow only file paths matching the specified regular expression.\n.TP\n\\fB\\-\\-user\\-filter REGEX\\fR\nFilter usernames matching the specified regular expression.\n.TP\n\\fB\\-\\-user\\-show\\-filter REGEX\\fR\nShow only usernames matching the specified regular expression.\n.TP\n\\fB\\-\\-user\\-image\\-dir DIRECTORY\\fR\nDirectory containing .jpg or .png images of users (eg \"Full Name.png\") to use as avatars.\n.TP\n\\fB\\-\\-default\\-user\\-image IMAGE\\fR\nPath of .jpg to use as the default user image.\n.TP\n\\fB\\-\\-fixed\\-user\\-size\\fR\nForces the size of the user image to remain fixed throughout.\n.TP\n\\fB\\-\\-colour\\-images\\fR\nColourize user images.\n.TP\n\\fB\\-\\-crop AXIS\\fR\nCrop view on an axis (vertical,horizontal).\n.TP\n\\fB\\-\\-padding FLOAT\\fR\nCamera view padding.\n.TP\n\\fB\\-\\-multi\\-sampling\\fR\nEnable multi-sampling.\n.TP\n\\fB\\-\\-no\\-vsync\\fR\nDisable vsync.\n.TP\n\\fB\\-\\-bloom\\-multiplier FLOAT\\fR\nAdjust the amount of bloom.\n.TP\n\\fB\\-\\-bloom\\-intensity FLOAT\\fR\nAdjust the intensity of the bloom.\n.TP\n\\fB\\-\\-max\\-files NUMBER\\fR\nSet the maximum number of files or 0 for no limit. Excess files will be discarded.\n.TP\n\\fB\\-\\-max\\-file\\-lag SECONDS\\fR\nMax time files of a commit can take to appear. Use \\-1 for no limit.\n.TP\n\\fB\\-\\-max\\-user\\-speed UNITS\\fR\nMax speed users can travel per second.\n.TP\n\\fB\\-\\-user\\-friction SECONDS\\fR\nTime users take to come to a halt.\n.TP\n\\fB\\-\\-user\\-scale SCALE\\fR\nChange scale of user avatars.\n.TP\n\\fB\\-\\-camera\\-mode MODE\\fR\nCamera mode (overview,track).\n.TP\n\\fB\\-\\-disable\\-auto\\-rotate\\fR\nDisable automatic camera rotation.\n.TP\n\\fB\\-\\-disable\\-input\\fR\nDisable keyboard and mouse input.\n.TP\n\\fB\\-\\-hide DISPLAY_ELEMENT\\fR\nHide one or more display elements from the list below:\n\n    bloom     \\- bloom effect\n    date      \\- current date\n    dirnames  \\- names of directories\n    files     \\- file icons\n    filenames \\- names of files\n    mouse     \\- mouse cursor\n    progress  \\- progress bar widget\n    root      \\- root directory of the tree\n    tree      \\- animated tree structure\n    users     \\- user avatars\n    usernames \\- names of users\n\nSeparate multiple elements with commas (eg \"mouse,progress\")\n\n.TP\n\\fB\\-\\-hash\\-seed SEED\\fR\nChange the seed of hash function.\n.TP\n\\fB\\-\\-caption-file FILE\nCaption file (see Caption Log Format).\n.TP\n\\fB\\-\\-caption-size SIZE\nCaption size.\n.TP\n\\fB\\-\\-caption-colour FFFFFF\nCaption colour in hex.\n.TP\n\\fB\\-\\-caption-duration SECONDS\nCaption duration.\n.TP\n\\fB\\-\\-caption-offset X\nCaption horizontal offset (0 to centre captions).\n.TP\n\\fB\\-o, \\-\\-output\\-ppm\\-stream FILE\\fR\nOutput a PPM image stream to a file ('\\-' for STDOUT).\n\nThis will automatically hide the progress bar initially and enable 'stop\\-at\\-end' unless other behaviour is specified.\n\n.TP\n\\fB\\-r, \\-\\-output\\-framerate FPS\\fR\nFramerate of output (25,30,60). Used with \\-\\-output\\-ppm\\-stream.\n.TP\n\\fB\\-\\-output\\-custom\\-log FILE\\fR\nOutput a custom format log file ('\\-' for STDOUT).\n.TP\n\\fB\\-\\-load\\-config CONFIG_FILE\\fR\nLoad a config file.\n.TP\n\\fB\\-\\-save\\-config CONFIG_FILE\\fR\nSave a config file with the current options.\n.TP\n\\fB\\-\\-path PATH\\fR\n.TP\n\\fBpath\\fR\nEither a supported version control directory, a pre-generated log file (see log commands or the custom log format), a Gource conf file or '-' to read STDIN.\n\nIf path is omitted, gource will attempt to read a log from the current directory.\n\n.SS Git, Bazaar, Mercurial and SVN Examples\n\nView the log of the repository in the current path:\n\n.ti 10\n\\fIgource\\fR\n\nView the log of a project in the specified directory:\n\n.ti 10\n\\fIgource\\fR my\\-project\\-dir\n\nFor large projects, generating a log of the project history may take a long time. For centralized VCS like SVN, generating the log will put load on the central VCS server.\n\nIn these cases, you may like to save a copy of the log for later use.\n\nYou can generate a log in the VCS specific log format using the \\-\\-log\\-command VCS option:\n\n.ti 10\ncd my\\-svn\\-project\n.ti 10\n\\`\\fIgource\\fR \\-\\-log\\-command svn\\` > my\\-svn\\-project.log\n.ti 10\n\\fIgource\\fR my\\-svn\\-project.log\n\nYou can also have Gource write a copy of the log file in its own format:\n\n.ti 10\n\\fIgource\\fR \\-\\-output\\-custom\\-log my\\-project\\-custom.log\n\n.SS CVS Support\n\nUse 'cvs2cl' to generate the log and then pass it to Gource:\n\n.ti 10\ncvs2cl \\-\\-chrono \\-\\-stdout \\-\\-xml \\-g\\-q > my\\-cvs\\-project.log\n.ti 10\ngource my\\-cvs\\-project.log\n\n.SS Custom Log Format\n\nIf you want to use Gource with something other than the supported systems, there is a pipe ('|') delimited custom log format:\n\n.ti 10\ntimestamp - An ISO 8601 or unix timestamp of when the update occurred.\n.ti 10\nusername  - The name of the user who made the update.\n.ti 10\ntype      - Single character for the update type - (A)dded, (M)odified or (D)eleted.\n.ti 10\nfile      - Path of the file updated.\n.ti 10\ncolour    - A colour for the file in hex (FFFFFF) format. Optional.\n\n.SS Caption Log Format\n\nGource can display captions along the timeline by specifying a caption file (using \\-\\-caption\\-file) in the pipe ('|') delimited format below:\n\n.ti 10\ntimestamp - An ISO 8601 or unix timestamp of when to display the caption.\n.ti 10\ncaption   - The caption\n\n.SS Recording Videos\n\nSee the guide on the homepage for examples of recording videos with Gource:\n\n.ti 10\nhttps://github.com/acaudwell/Gource/wiki/Videos\n\n.SS More Information\n\nVisit the Gource homepage for guides and examples of using Gource with various version control systems:\n\n.ti 10\nhttp://gource.io\n\n.SH INTERFACE\nThe time shown in the top left of the screen is set initially from the first log entry read and is incremented according to the simulation speed (\\-\\-seconds\\-per\\-day).\n\nPressing SPACE at any time will pause/resume the simulation. While paused you may use the mouse to inspect the detail of individual files and users.\n\nTAB cycles through selecting the current visible users.\n\nThe camera mode, either tracking activity or showing the entire code tree, can\nbe toggled using the Middle mouse button.\n\nYou can drag the left mouse button to manually control the camera. The right\nmouse button rotates the view.\n\nInteractive keyboard commands:\n.sp\n.ti 10\n(V)   Toggle camera mode\n.ti 10\n(C)   Displays Gource logo\n.ti 10\n(K)   Toggle file extension key\n.ti 10\n(M)   Toggle mouse visibility\n.ti 10\n(N)   Jump forward in time to next log entry\n.ti 10\n(S)   Randomize colours\n.ti 10\n(D)   Toggle directory name display mode\n.ti 10\n(F)   Toggle file name display mode\n.ti 10\n(U)   Toggle user name display mode\n.ti 10\n(G)   Toggle display of users\n.ti 10\n(T)   Toggle display of directory tree edges\n.ti 10\n(R)   Toggle display of root directory edges\n.ti 10\n(<>)  Adjust time scale / user avatar movement speed\n.ti 10\n(+-)  Adjust simulation speed\n.ti 10\n(Keypad +-) Adjust camera zoom\n.ti 10\n(TAB) Cycle through visible users\n.ti 10\n(F12) Screenshot\n.ti 10\n(Alt+Enter) Fullscreen toggle\n.ti 10\n(ESC) Quit\n.SH AUTHOR\n.nf\n Written by Andrew Caudwell\n\n Project Homepage: http://gource.io\n.SH COPYRIGHT\n.nf\n Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n This program is free software; you can redistribute it and/or\n modify it under the terms of the GNU General Public License\n as published by the Free Software Foundation; either version\n 3 of the License, or (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n GNU General Public License for more details.\n\n You should have received a copy of the GNU General Public License\n along with this program.  If not, see <http://www.gnu.org/licenses/>.\n.fi\n.SH ACKNOWLEDGEMENTS\n.nf\n Catalyst IT (catalyst.net.nz)\n\n For supporting the development of Gource!\n.fi\n"
  },
  {
    "path": "data/gource.style",
    "content": "changeset = '{file_adds}{file_copies}{file_mods}{file_dels}'\nfile_mod  = \"{date|hgdate}|{author|person}|M|{file_mod}\\n\"\nfile_add  = \"{date|hgdate}|{author|person}|A|{file_add}\\n\"\nfile_del  = \"{date|hgdate}|{author|person}|D|{file_del}\\n\"\nfile_copy = \"{date|hgdate}|{author|person}|A|{name}\\n\"\n"
  },
  {
    "path": "data/shaders/bloom.frag",
    "content": "\nvarying vec3 pos;\n\nvoid main()\n{\n    float r = fract(sin(dot(pos.xy ,vec2(11.3713,67.3219))) * 2351.3718);\n\n    float offset = (0.5 - r) * gl_TexCoord[0].x * 0.045;\n\n    float intensity = min(1.0, cos((length(pos*2.0)+offset)/gl_TexCoord[0].x));\n    float gradient  = intensity * smoothstep(0.0, 2.0, intensity);\n\n    gradient *= smoothstep(1.0,0.67+r*0.33, 1.0-intensity);\n\n    gl_FragColor = gl_Color * gradient;\n}\n"
  },
  {
    "path": "data/shaders/bloom.vert",
    "content": "\nvarying vec3 pos;\n\nvoid main()\n{\n    pos = gl_Vertex.xyz - gl_MultiTexCoord0.yzw;\n    gl_TexCoord[0] = gl_MultiTexCoord0;\n    gl_FrontColor = gl_Color;\n    gl_Position = ftransform();\n}\n"
  },
  {
    "path": "data/shaders/shadow.frag",
    "content": "uniform sampler2D tex;\nuniform float shadow_strength;\n\nvoid main(void)\n{\n    vec4 colour = texture2D(tex,gl_TexCoord[0].st);\n\n    gl_FragColor = vec4(0.0, 0.0, 0.0, gl_Color.w * colour.w * shadow_strength);\n}\n"
  },
  {
    "path": "data/shaders/shadow.vert",
    "content": "void main(void)\n{\n  gl_TexCoord[0] = gl_MultiTexCoord0;\n  gl_FrontColor  = gl_Color;\n  gl_Position    = ftransform();\n}\n"
  },
  {
    "path": "data/shaders/text.frag",
    "content": "uniform sampler2D tex;\nuniform float shadow_strength;\nuniform float texel_size;\n\nvoid main(void)\n{\n    float colour_alpha = texture2D(tex,gl_TexCoord[0].xy).w;\n    float shadow_alpha = texture2D(tex,gl_TexCoord[0].xy - vec2(texel_size)).w * shadow_strength;\n\n    float combined_alpha = 1.0 - (1.0-shadow_alpha)*(1.0-colour_alpha);\n\n    if(combined_alpha > 0.0) colour_alpha /= combined_alpha;\n\n    gl_FragColor = gl_Color * vec4(vec3(colour_alpha), combined_alpha);\n}\n"
  },
  {
    "path": "data/shaders/text.vert",
    "content": "void main(void)\n{\n  gl_TexCoord[0] = gl_MultiTexCoord0;\n  gl_FrontColor  = gl_Color;\n  gl_Position    = ftransform();\n}\n"
  },
  {
    "path": "dev/.gitignore",
    "content": "builds/\n"
  },
  {
    "path": "dev/bin/build_tar.pl",
    "content": "#!/usr/bin/perl\n#copy stuff we want to go into the gource source tar ball\n\nuse strict;\nuse warnings;\n\n\nuse FindBin;\nuse File::Path;\nuse File::Copy;\nuse Date::Format;\nuse Getopt::Long qw(GetOptions);\nuse Cwd;\n\nsub gource_version {\n    my $version = `cat $FindBin::Bin/../../src/gource_settings.h | grep GOURCE_VERSION`;\n    $version =~ /\"([^\"]+)\"/ or die(\"could not determine version\\n\");\n    $version = $1;\n    return $version;\n}\n\nmy $VERSION = gource_version();\n\nmy @exclusions = (\n    qr{^/autogen\\.sh$},\n    qr{^/backup/},\n    qr{^/confs/},\n    qr{^/cmd/},\n    qr{^/resources/},\n    qr{^/tests/},\n    qr{^/scripts/},\n    qr{^/contrib/},\n    qr{^/config.status$},\n    qr{^/config.log$},\n    qr{^/gource$},\n    qr{^/dev/},\n    qr{^/logs/},\n    qr{/\\.},\n    qr{Makefile$},\n    qr{\\.o$},\n    qr{^/todo.txt$},\n    qr{^/build-stamp$},\n    qr{^/autom4te},\n    qr{^/src/core/README$},\n    qr{^/src/core/ui/},\n    qr{\\.d$},\n);\n\nmy @inclusions = (\n    qr{^/gource\\.pro$},\n    qr{^/ChangeLog$},\n    qr{^/THANKS$},\n    qr{^/COPYING$},\n    qr{^/INSTALL$},\n    qr{^/README\\.md$},\n    qr{/Makefile\\.(?:am|in)$},\n    qr{^/aclocal\\.m4$},\n    qr{^/m4/.+\\.m4$},\n    qr{^/configure(?:\\.ac)?$},\n    qr{^/src/.+\\.(?:cpp|h|cc|hh)$},\n    qr{^/data/file\\.png$},\n    qr{^/data/user\\.png$},\n    qr{^/data/beam\\.png$},\n    qr{^/data/bloom\\.tga$},\n    qr{^/data/bloom_alpha\\.tga$},\n    qr{^/data/cursor\\.png$},\n    qr{^/data/fonts/FreeSans\\.ttf$},\n    qr{^/data/gource\\.1$},\n    qr{^/data/gource\\.style$},\n    qr{^/data/fonts/README$},\n    qr{^/data/shaders/bloom\\.(?:vert|frag)$},\n    qr{^/data/shaders/shadow\\.(?:vert|frag)$},\n    qr{^/data/shaders/text\\.(?:vert|frag)$},\n    qr{^/build-aux/(?:compile|config.(?:guess|sub)|depcomp|install-sh|missing|test-driver)$},\n);\n\nmy $tmp_path = \"/var/tmp/gource-$VERSION\";\n\nsystem(\"rm -r $tmp_path\") if -d $tmp_path;\nmkpath($tmp_path) or die(\"failed to make temp folder $tmp_path\");\n\nchdir(\"$FindBin::Bin/../../\");\n\nmy @files = `find .`;\n\n#check configure.ac has been updated\nunless(`cat configure.ac` =~ /AC_INIT\\(Gource, $VERSION,/) {\n    die(\"configure.ac does not mention current version number\\n\");\n}\n\n#check ChangeLog has been updated\nunless(`cat ChangeLog` =~ /^$VERSION:/) {\n    die(\"ChangeLog does not mention current version number\\n\");\n}\n\n#if Makefile exists, do distclean\nif(-e 'Makefile') {\n    if(system(\"make distclean\") != 0) {\n        die(\"make distclean failed: $!\\n\");\n    }\n}\n\n#reconfigure\nif(system(\"autoreconf -f -i -v\") != 0) {\n    die(\"autoreconf failed: $!\\n\");\n}\n\nforeach my $file (@files) {\n    $file =~ s/[\\r\\n]+//;\n    $file =~ s/^\\.//;\n\n    (my $relfile = $file) =~ s{^/}{};\n    (my $dir = $file)    =~ s{[^/]+$}{/};\n\n    next if $file =~ /^\\s*$/;\n    next if -d $relfile;\n\n    next if grep { $file =~ $_ } @exclusions;\n\n    unless(grep { $file =~ $_ } @inclusions) {\n        warn \"WARNING: nothing known about $file\\n\";\n        next;\n    }\n\n    mkpath(\"$tmp_path/$dir\");\n    if(system(\"cp\", \"$relfile\", \"$tmp_path/$relfile\") != 0) {\n        die(\"error copying $file to $tmp_path/$relfile: $? $!\\n\");\n    }\n}\n\nmy $current_dir = cwd;\nchdir(\"/var/tmp/\");\n\nmy $archive = \"gource-$VERSION.tar.gz\";\n\nif(system(\"tar -czf $archive gource-$VERSION\") !=0) {\n    die(\"failed to make archive $archive\");\n}\n\nunlink(\"$FindBin::Bin/../builds/$archive\");\n\nmy $builds_dir = \"$FindBin::Bin/../builds/\";\n\nsystem('mkdir', '-p', $builds_dir);\n\nunlink(\"$builds_dir/$archive\") if -e \"$builds_dir/$archive\";\n\nmove(\"$archive\", $builds_dir);\nsystem(\"rm -r $tmp_path\");\n\nprint \"Built $archive\\n\";\n"
  },
  {
    "path": "dev/bin/build_win64.pl",
    "content": "#!/usr/bin/perl\n# windows archive + installer generator\n\nuse strict;\nuse warnings;\nuse FindBin;\nuse File::Copy;\n\nmy $base_dir     = \"$FindBin::Bin/../..\";\nmy $build_dir    = \"$base_dir/../build-gource-Msys2_64_bit-Release/release\";\nmy $binaries_dir = \"$base_dir/dev/win64\";\n\nmy $builds_output_dir = \"$base_dir/dev/builds\";\n\nmy $makensis = \"'C:\\\\Program Files (x86)\\\\NSIS\\\\makensis.exe'\";\n\nsub gource_version {\n    my $version = `cat $base_dir/src/gource_settings.h | grep GOURCE_VERSION`;\n    $version =~ /\"([^\"]+)\"/ or die(\"could not determine version\\n\");\n    $version = $1;\n    return $version;\n}\n\nsub doit {\n    my $cmd = shift;\n    \n    if(system($cmd) != 0) {\n\tdie(\"command '$cmd' failed: $!\");\n    }\n}\n\nsub dosify {\n    my($src, $dest) = @_;\n    \n    my $content = `cat $src`;\n    $content =~ s/\\r?\\n/\\r\\n/g;\n    \n    open  OUTPUT, \">$dest\" or die(\"$!\");\n    print OUTPUT $content;\n    close OUTPUT;\n}\n\nmy @dll_files;\n\nsub update_binaries {\n    copy(\"$build_dir/gource.exe\", \"$binaries_dir/gource.exe\") or die(\"failed to copy $build_dir/gource.exe: $!\\n\");\n\n    chdir($binaries_dir) or die(\"failed to change directory to $binaries_dir\\n\");\n\n    for my $existing_dll (glob(\"*.dll\")) {\n        unlink($existing_dll) or die(\"failed to remove existing dll $existing_dll: $!\\n\");\n    }\n\n    my @dlls = `cygcheck ./gource.exe | grep msys`;\n\n    for my $dll (@dlls) {\n        $dll =~ s/^\\s+//g;\n        $dll =~ s/[\\r\\n]//g;\n        my($name) = $dll =~ m{\\\\([^\\\\]+\\.dll)$};\n        \n        warn \"adding $name\\n\";\n            \n        copy($dll, \"$binaries_dir/$name\") or die \"failed to copy $name: $!\\n\"; \n\n        push @dll_files, $name;\n    }\n}\n\nmy $nsis_script = q[\n!define MULTIUSER_MUI\n!define MULTIUSER_EXECUTIONLEVEL Highest\n!define MULTIUSER_INSTALLMODE_COMMANDLINE\n!define MULTIUSER_USE_PROGRAMFILES64\n!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY \"Software\\Gource\"\n!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY \"Software\\Gource\"\n!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME \"Install_Mode\"\n!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME \"Install_Dir\"\n!define MULTIUSER_INSTALLMODE_INSTDIR \"Gource\"\n\n!include \"x64.nsh\"\n!include \"MultiUser.nsh\"\n!include \"MUI2.nsh\"\n!include \"LogicLib.nsh\"\n!include \"SafeEnvVarUpdate.nsh\"\n\nName \"Gource GOURCE_VERSION\"\n\nOutFile \"GOURCE_INSTALLER\"\n\n!define MUI_WELCOMEFINISHPAGE_BITMAP   \"..\\..\\nsis\\welcome.bmp\"\n!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"..\\..\\nsis\\welcome.bmp\"\n\n!define MUI_COMPONENTSPAGE_NODESC\n\n!insertmacro MULTIUSER_PAGE_INSTALLMODE\n!insertmacro MUI_PAGE_WELCOME\n!define MUI_PAGE_HEADER_TEXT \"Legal Disclaimer\"\n!insertmacro MUI_PAGE_LICENSE \"..\\..\\nsis\\disclaimer.txt\"\n!insertmacro MUI_PAGE_COMPONENTS\n!insertmacro MUI_PAGE_DIRECTORY\n!insertmacro MUI_PAGE_INSTFILES\n!insertmacro MUI_PAGE_FINISH\n\n!insertmacro MUI_LANGUAGE \"English\"\n\nFunction .onInit\n  ${IfNot} ${RunningX64}\n    MessageBox MB_OK \"This installer requires 64-bit Windows\"\n    Quit\n  ${EndIf}\n  !insertmacro MULTIUSER_INIT\n  ReadRegStr $R0 SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"UninstallString\"\n  StrCmp $R0 \"\" done\n  MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \\\n  \"Gource appears to already be installed. $\\n$\\nClick OK to remove the previous version and continue the installation.\" \\\n  IDOK uninst\n  Abort\n \n uninst:\n  ClearErrors\n  ExecWait $R0\n\n done:\n \nFunctionEnd\n\nFunction un.onInit\n  !insertmacro MULTIUSER_UNINIT\nFunctionEnd \n\nSection \"Gource\" SecGource\n  SectionIn RO\n\n  GOURCE_INSTALL_LIST\n\n  writeUninstaller $INSTDIR\\uninstall.exe\n\n  WriteRegStr SHCTX \"Software\\Gource\" ${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME} \"$INSTDIR\"\n  WriteRegStr SHCTX \"Software\\Gource\" ${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME} \"$MultiUser.InstallMode\"\n\n  WriteRegStr   SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"DisplayName\"          \"Gource\"\n  WriteRegStr   SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"DisplayVersion\"       \"GOURCE_VERSION\"\n  WriteRegStr   SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"Publisher\"            \"acaudwell\"\n  WriteRegStr   SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"UninstallString\"      '\"$INSTDIR\\uninstall.exe\"'\n  WriteRegStr   SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"QuietUninstallString\" '\"$INSTDIR\\uninstall.exe\" /S'\n  WriteRegDWORD SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"NoModify\" 1\n  WriteRegDWORD SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\" \"NoRepair\" 1\n\nSectionEnd\n\nSection \"Add to PATH\" SecAddtoPath\n \n  ${If} $MultiUser.InstallMode == \"AllUsers\"\n    ${EnvVarUpdate} $0 \"PATH\" \"A\" \"HKLM\" \"$INSTDIR\\cmd\"\n  ${ElseIf} $MultiUser.InstallMode == \"CurrentUser\"\n    ${EnvVarUpdate} $0 \"PATH\" \"A\" \"HKCU\" \"$INSTDIR\\cmd\"\n  ${EndIf}\n\nSectionEnd\n\nSection \"Uninstall\"\n\n  ${If} $MultiUser.InstallMode == \"AllUsers\"\n    ${un.EnvVarUpdate} $0 \"PATH\" \"R\" \"HKLM\" \"$INSTDIR\\cmd\"\n  ${ElseIf} $MultiUser.InstallMode == \"CurrentUser\"\n    ${un.EnvVarUpdate} $0 \"PATH\" \"R\" \"HKCU\" \"$INSTDIR\\cmd\"\n  ${EndIf}\n\n  DeleteRegKey SHCTX \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Gource\"\n  DeleteRegKey SHCTX \"Software\\Gource\"\n\n  GOURCE_UNINSTALL_LIST\n  GOURCE_UNINSTALL_DIRS\n\n  Delete $INSTDIR\\uninstall.exe\n  RMDir \"$INSTDIR\"\nSectionEnd\n];\n\nmy @gource_files = qw(\n    data/beam.png\n    data/file.png\n    data/user.png\n    data/bloom.tga\n    data/bloom_alpha.tga\n    data/gource.style\n    data/fonts/FreeSans.ttf\n    data/shaders/bloom.frag \n    data/shaders/bloom.vert\n    data/shaders/shadow.frag\n    data/shaders/shadow.vert\n    data/shaders/text.frag\n    data/shaders/text.vert\n    cmd/gource.cmd\n    cmd/gource\n);\n\nmy @gource_txts = qw(\n    README.md\n    ChangeLog\n    data/fonts/README\n    COPYING\n    THANKS\n);\n\nmy @bin_files = qw(\n    gource.exe\n);\n\nmy @gource_dirs = qw(\n    data\n    data/fonts\n    data/shaders\n    cmd\n);\n\nmkdir($binaries_dir)      unless -d $binaries_dir;\nmkdir($builds_output_dir) unless -d $builds_output_dir;\n\nmy $tmp_dir = \"$builds_output_dir/gource-build.$$\";\n\ndoit(\"rm $tmp_dir\") if -d $tmp_dir;\nmkdir($tmp_dir);\n\n# create directories\nforeach my $dir (@gource_dirs) {\n    mkdir(\"$tmp_dir/$dir\");\n}\n\nmy @gource_bundle;\n\nupdate_binaries();\n\nchdir(\"$base_dir\") or die(\"chdir to $base_dir failed\");\n\n# copy binaries\nforeach my $file (@bin_files, @dll_files) {\n    doit(\"cp $binaries_dir/$file $tmp_dir/$file\");\n    push @gource_bundle, $file;\n}\n\n# copy general files\nforeach my $file (@gource_files) {\n    doit(\"cp $file $tmp_dir/$file\");\n    push @gource_bundle, $file;\n}\n\n# convert text files\nforeach my $file (@gource_txts) {\n    (my $file_prefix = $file) =~ s/\\..+$//;\n    my $txt_file = \"$file_prefix.txt\";\n    dosify(\"$file\", \"$tmp_dir/$txt_file\");\n    push @gource_bundle, $txt_file;\n}\n\nmy $version = gource_version();\n\nmy $installer_name = \"gource-${version}.win64-setup.exe\";\nmy $archive_name   = \"gource-${version}.win64.zip\";\n\nmy $install_list = '';\n\nforeach my $dir ('', @gource_dirs) {\n\n    my @dir_files = map  { my $f = $_; $f =~ s{/}{\\\\}g; $f; }\n                    grep { my $d = /^(.+)\\// ? $1 : ''; $d eq $dir }\n                    @gource_bundle;\n\n    (my $output_dir = $dir) =~ s{/}{\\\\}g;\n\n    $install_list .= \"\\n\" . '  SetOutPath \"$INSTDIR' . ( $dir ? \"\\\\$output_dir\" : \"\" ) . \"\\\"\\n\\n\";\n\n    foreach my $file (@dir_files) {\n        $install_list .= '  File '.$file.\"\\n\";\n    }\n}\n\nmy $uninstall_list = join(\"\\n\", map { my $f = $_; $f =~ s{/}{\\\\}g; '  Delete $INSTDIR\\\\'.$f } @gource_bundle);\nmy $uninstall_dirs = join(\"\\n\", map { my $d = $_; $d =~ s{/}{\\\\}g; '  RMDir  $INSTDIR\\\\'.$d } reverse @gource_dirs);\n\n$nsis_script =~ s/GOURCE_VERSION/$version/g;\n$nsis_script =~ s/GOURCE_INSTALLER/$installer_name/g;\n$nsis_script =~ s/GOURCE_INSTALL_LIST/$install_list/;\n$nsis_script =~ s/GOURCE_UNINSTALL_LIST/$uninstall_list/;\n$nsis_script =~ s/GOURCE_UNINSTALL_DIRS/$uninstall_dirs/;\n$nsis_script =~ s/\\n/\\r\\n/g;\n\nchdir($tmp_dir) or die(\"failed to change directory to '$tmp_dir'\\n\");\n\n# remove existing copies of the version installer if they exist\n\nunlink(\"../$installer_name\") if -e \"../$installer_name\";\nunlink(\"../$archive_name\")   if -e \"../$archive_name\";\n\nmy $output_file = \"gource.nsi\";\n\nopen my $NSIS_HANDLE, \">$output_file\" or die(\"failed to open $output_file: $!\");\nprint $NSIS_HANDLE $nsis_script;\nclose $NSIS_HANDLE;\n\n# generate installer\n\n# assert we have the long string build of NSIS\ndoit(\"$makensis -HDRINFO | grep -q NSIS_MAX_STRLEN=8192\");\n\ndoit(\"$makensis $output_file\");\n\ndoit(\"rm $output_file\");\ndoit(\"mv $installer_name ..\");\n\n# also create zip archive\n\ndoit(\"zip -r $archive_name *\");\ndoit(\"mv $archive_name ..\");\n\nchdir(\"$tmp_dir/..\");\ndoit(\"rm -rf $tmp_dir\");\n"
  },
  {
    "path": "dev/nsis/disclaimer.txt",
    "content": "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE."
  },
  {
    "path": "gource.pro",
    "content": "# Note: this project file currently only implements building on Windows with Mingw-w64\n# See the INSTALL file for building instructions\n\nTEMPLATE = app\nCONFIG += console\nCONFIG -= app_bundle\nCONFIG -= qt\nDEFINES -= UNICODE\n\nCONFIG += c++11\nCONFIG += object_parallel_to_source\n\ngcc {\n    QMAKE_CXXFLAGS_WARN_ON = -Wall -Wno-unused-variable -Wno-sign-compare -Wno-unused-parameter -Wno-reorder\n    QMAKE_CXXFLAGS_DEBUG += -DASSERTS_ENABLED\n}\n\nmingw {\n    QMAKE_CXXFLAGS += -Dmain=SDL_main\n    QMAKE_LFLAGS   += -mconsole\n\n    INCLUDEPATH += C:\\msys64\\mingw64\\include\\SDL2\n    INCLUDEPATH += C:\\msys64\\mingw64\\include\\freetype2\n\n    LIBS += -lmingw32 -lSDL2main -lSDL2.dll\n    LIBS += -lSDL2_image.dll -lfreetype.dll -lpcre2-8.dll -lpng.dll -lglew32.dll -lboost_system-mt -lboost_filesystem-mt -lopengl32 -lglu32\n    LIBS += -static-libgcc -static-libstdc++\n    LIBS += -lcomdlg32\n}\n\nVPATH += ./src\n\nSOURCES += \\\n    action.cpp \\\n    bloom.cpp \\\n    caption.cpp \\\n    dirnode.cpp \\\n    file.cpp \\\n    gource.cpp \\\n    gource_settings.cpp \\\n    gource_shell.cpp \\\n    key.cpp \\\n    logmill.cpp \\\n    main.cpp \\\n    pawn.cpp \\\n    slider.cpp \\\n    spline.cpp \\\n    textbox.cpp \\\n    user.cpp \\\n    zoomcamera.cpp \\\n    formats/apache.cpp \\\n    formats/bzr.cpp \\\n    formats/commitlog.cpp \\\n    formats/custom.cpp \\\n    formats/cvs-exp.cpp \\\n    formats/cvs2cl.cpp \\\n    formats/git.cpp \\\n    formats/gitraw.cpp \\\n    formats/hg.cpp \\\n    formats/svn.cpp \\\n    tinyxml/tinystr.cpp \\\n    tinyxml/tinyxml.cpp \\\n    tinyxml/tinyxmlerror.cpp \\\n    tinyxml/tinyxmlparser.cpp \\\n    core/conffile.cpp \\\n    core/display.cpp \\\n    core/frustum.cpp \\\n    core/fxfont.cpp \\\n    core/logger.cpp \\\n    core/mousecursor.cpp \\\n    core/plane.cpp \\\n    core/png_writer.cpp \\\n    core/ppm.cpp \\\n    core/quadtree.cpp \\\n    core/regex.cpp \\\n    core/resource.cpp \\\n    core/sdlapp.cpp \\\n    core/seeklog.cpp \\\n    core/settings.cpp \\\n    core/shader.cpp \\\n    core/shader_common.cpp \\\n    core/stringhash.cpp \\\n    core/texture.cpp \\\n    core/tga.cpp \\\n    core/timezone.cpp \\\n    core/vbo.cpp \\\n    core/vectors.cpp\n\nHEADERS += \\\n    action.h \\\n    bloom.h \\\n    caption.h \\\n    dirnode.h \\\n    file.h \\\n    gource.h \\\n    gource_settings.h \\\n    gource_shell.h \\\n    key.h \\\n    logmill.h \\\n    main.h \\\n    pawn.h \\\n    slider.h \\\n    spline.h \\\n    textbox.h \\\n    user.h \\\n    zoomcamera.h \\\n    formats/apache.h \\\n    formats/bzr.h \\\n    formats/commitlog.h \\\n    formats/custom.h \\\n    formats/cvs-exp.h \\\n    formats/cvs2cl.h \\\n    formats/git.h \\\n    formats/gitraw.h \\\n    formats/hg.h \\\n    formats/svn.h \\\n    tinyxml/tinystr.h \\\n    tinyxml/tinyxml.h \\\n    core/bounds.h \\\n    core/conffile.h \\\n    core/display.h \\\n    core/frustum.h \\\n    core/fxfont.h \\\n    core/gl.h \\\n    core/logger.h \\\n    core/mousecursor.h \\\n    core/pi.h \\\n    core/plane.h \\\n    core/png_writer.h \\\n    core/ppm.h \\\n    core/quadtree.h \\\n    core/regex.h \\\n    core/resource.h \\\n    core/sdlapp.h \\\n    core/seeklog.h \\\n    core/settings.h \\\n    core/shader.h \\\n    core/shader_common.h \\\n    core/stringhash.h \\\n    core/texture.h \\\n    core/tga.h \\\n    core/timezone.h \\\n    core/vbo.h \\\n    core/vectors.h\n\nDISTFILES += \\\n    data/shaders/bloom.frag \\\n    data/shaders/shadow.frag \\\n    data/shaders/text.frag \\\n    data/shaders/bloom.vert \\\n    data/shaders/shadow.vert \\\n    data/shaders/text.vert\n"
  },
  {
    "path": "m4/ax_boost_base.m4",
    "content": "# ===========================================================================\n#      https://www.gnu.org/software/autoconf-archive/ax_boost_base.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\n#\n# DESCRIPTION\n#\n#   Test for the Boost C++ libraries of a particular version (or newer)\n#\n#   If no path to the installed boost library is given the macro searches\n#   under /usr, /usr/local, /opt, /opt/local and /opt/homebrew and evaluates\n#   the $BOOST_ROOT environment variable. Further documentation is available\n#   at <http://randspringer.de/boost/index.html>.\n#\n#   This macro calls:\n#\n#     AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS)\n#\n#   And sets:\n#\n#     HAVE_BOOST\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Thomas Porschberg <thomas@randspringer.de>\n#   Copyright (c) 2009 Peter Adolphs\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 55\n\n# example boost program (need to pass version)\nm4_define([_AX_BOOST_BASE_PROGRAM],\n          [AC_LANG_PROGRAM([[\n#include <boost/version.hpp>\n]],[[\n(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));\n]])])\n\nAC_DEFUN([AX_BOOST_BASE],\n[\nAC_ARG_WITH([boost],\n  [AS_HELP_STRING([--with-boost@<:@=ARG@:>@],\n    [use Boost library from a standard location (ARG=yes),\n     from the specified location (ARG=<path>),\n     or disable it (ARG=no)\n     @<:@ARG=yes@:>@ ])],\n    [\n     AS_CASE([$withval],\n       [no],[want_boost=\"no\";_AX_BOOST_BASE_boost_path=\"\"],\n       [yes],[want_boost=\"yes\";_AX_BOOST_BASE_boost_path=\"\"],\n       [want_boost=\"yes\";_AX_BOOST_BASE_boost_path=\"$withval\"])\n    ],\n    [want_boost=\"yes\"])\n\n\nAC_ARG_WITH([boost-libdir],\n  [AS_HELP_STRING([--with-boost-libdir=LIB_DIR],\n    [Force given directory for boost libraries.\n     Note that this will override library path detection,\n     so use this parameter only if default library detection fails\n     and you know exactly where your boost libraries are located.])],\n  [\n   AS_IF([test -d \"$withval\"],\n         [_AX_BOOST_BASE_boost_lib_path=\"$withval\"],\n    [AC_MSG_ERROR([--with-boost-libdir expected directory name])])\n  ],\n  [_AX_BOOST_BASE_boost_lib_path=\"\"])\n\nBOOST_LDFLAGS=\"\"\nBOOST_CPPFLAGS=\"\"\nAS_IF([test \"x$want_boost\" = \"xyes\"],\n      [_AX_BOOST_BASE_RUNDETECT([$1],[$2],[$3])])\nAC_SUBST(BOOST_CPPFLAGS)\nAC_SUBST(BOOST_LDFLAGS)\n])\n\n\n# convert a version string in $2 to numeric and affect to polymorphic var $1\nAC_DEFUN([_AX_BOOST_BASE_TONUMERICVERSION],[\n  AS_IF([test \"x$2\" = \"x\"],[_AX_BOOST_BASE_TONUMERICVERSION_req=\"1.20.0\"],[_AX_BOOST_BASE_TONUMERICVERSION_req=\"$2\"])\n  _AX_BOOST_BASE_TONUMERICVERSION_req_shorten=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\\([[0-9]]*\\.[[0-9]]*\\)'`\n  _AX_BOOST_BASE_TONUMERICVERSION_req_major=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\\([[0-9]]*\\)'`\n  AS_IF([test \"x$_AX_BOOST_BASE_TONUMERICVERSION_req_major\" = \"x\"],\n        [AC_MSG_ERROR([You should at least specify libboost major version])])\n  _AX_BOOST_BASE_TONUMERICVERSION_req_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\\.\\([[0-9]]*\\)'`\n  AS_IF([test \"x$_AX_BOOST_BASE_TONUMERICVERSION_req_minor\" = \"x\"],\n        [_AX_BOOST_BASE_TONUMERICVERSION_req_minor=\"0\"])\n  _AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\\.[[0-9]]*\\.\\([[0-9]]*\\)'`\n  AS_IF([test \"X$_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor\" = \"X\"],\n        [_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor=\"0\"])\n  _AX_BOOST_BASE_TONUMERICVERSION_RET=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req_major \\* 100000 \\+  $_AX_BOOST_BASE_TONUMERICVERSION_req_minor \\* 100 \\+ $_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor`\n  AS_VAR_SET($1,$_AX_BOOST_BASE_TONUMERICVERSION_RET)\n])\n\ndnl Run the detection of boost should be run only if $want_boost\nAC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[\n    _AX_BOOST_BASE_TONUMERICVERSION(WANT_BOOST_VERSION,[$1])\n    succeeded=no\n\n\n    AC_REQUIRE([AC_CANONICAL_HOST])\n    dnl On 64-bit systems check for system libraries in both lib64 and lib.\n    dnl The former is specified by FHS, but e.g. Debian does not adhere to\n    dnl this (as it rises problems for generic multi-arch support).\n    dnl The last entry in the list is chosen by default when no libraries\n    dnl are found, e.g. when only header-only libraries are installed!\n    AS_CASE([${host_cpu}],\n      [x86_64],[libsubdirs=\"lib64 libx32 lib lib64\"],\n      [mips*64*],[libsubdirs=\"lib64 lib32 lib lib64\"],\n      [ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64|e2k|loongarch64],[libsubdirs=\"lib64 lib lib64\"],\n      [libsubdirs=\"lib\"]\n    )\n\n    dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give\n    dnl them priority over the other paths since, if libs are found there, they\n    dnl are almost assuredly the ones desired.\n    AS_CASE([${host_cpu}],\n      [i?86],[multiarch_libsubdir=\"lib/i386-${host_os}\"],\n      [armv7l],[multiarch_libsubdir=\"lib/arm-${host_os}\"],\n      [multiarch_libsubdir=\"lib/${host_cpu}-${host_os}\"]\n    )\n\n    dnl first we check the system location for boost libraries\n    dnl this location is chosen if boost libraries are installed with the --layout=system option\n    dnl or if you install boost with RPM\n    AS_IF([test \"x$_AX_BOOST_BASE_boost_path\" != \"x\"],[\n        AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) includes in \"$_AX_BOOST_BASE_boost_path/include\"])\n         AS_IF([test -d \"$_AX_BOOST_BASE_boost_path/include\" && test -r \"$_AX_BOOST_BASE_boost_path/include\"],[\n           AC_MSG_RESULT([yes])\n           BOOST_CPPFLAGS=\"-I$_AX_BOOST_BASE_boost_path/include\"\n           for _AX_BOOST_BASE_boost_path_tmp in $multiarch_libsubdir $libsubdirs; do\n                AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) lib path in \"$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp\"])\n                AS_IF([test -d \"$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp\" && test -r \"$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp\" ],[\n                        AC_MSG_RESULT([yes])\n                        BOOST_LDFLAGS=\"-L$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp\";\n                        break;\n                ],\n      [AC_MSG_RESULT([no])])\n           done],[\n      AC_MSG_RESULT([no])])\n    ],[\n        if test X\"$cross_compiling\" = Xyes; then\n            search_libsubdirs=$multiarch_libsubdir\n        else\n            search_libsubdirs=\"$multiarch_libsubdir $libsubdirs\"\n        fi\n        for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local /opt/homebrew ; do\n            if test -d \"$_AX_BOOST_BASE_boost_path_tmp/include/boost\" && test -r \"$_AX_BOOST_BASE_boost_path_tmp/include/boost\" ; then\n                for libsubdir in $search_libsubdirs ; do\n                    if ls \"$_AX_BOOST_BASE_boost_path_tmp/$libsubdir/libboost_\"* >/dev/null 2>&1 ; then break; fi\n                done\n                BOOST_LDFLAGS=\"-L$_AX_BOOST_BASE_boost_path_tmp/$libsubdir\"\n                BOOST_CPPFLAGS=\"-I$_AX_BOOST_BASE_boost_path_tmp/include\"\n                break;\n            fi\n        done\n    ])\n\n    dnl overwrite ld flags if we have required special directory with\n    dnl --with-boost-libdir parameter\n    AS_IF([test \"x$_AX_BOOST_BASE_boost_lib_path\" != \"x\"],\n          [BOOST_LDFLAGS=\"-L$_AX_BOOST_BASE_boost_lib_path\"])\n\n    AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION)])\n    CPPFLAGS_SAVED=\"$CPPFLAGS\"\n    CPPFLAGS=\"$CPPFLAGS $BOOST_CPPFLAGS\"\n    export CPPFLAGS\n\n    LDFLAGS_SAVED=\"$LDFLAGS\"\n    LDFLAGS=\"$LDFLAGS $BOOST_LDFLAGS\"\n    export LDFLAGS\n\n    AC_REQUIRE([AC_PROG_CXX])\n    AC_LANG_PUSH(C++)\n        AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[\n        AC_MSG_RESULT(yes)\n    succeeded=yes\n    found_system=yes\n        ],[\n        ])\n    AC_LANG_POP([C++])\n\n\n\n    dnl if we found no boost with system layout we search for boost libraries\n    dnl built and installed without the --layout=system option or for a staged(not installed) version\n    if test \"x$succeeded\" != \"xyes\" ; then\n        CPPFLAGS=\"$CPPFLAGS_SAVED\"\n        LDFLAGS=\"$LDFLAGS_SAVED\"\n        BOOST_CPPFLAGS=\n        if test -z \"$_AX_BOOST_BASE_boost_lib_path\" ; then\n            BOOST_LDFLAGS=\n        fi\n        _version=0\n        if test -n \"$_AX_BOOST_BASE_boost_path\" ; then\n            if test -d \"$_AX_BOOST_BASE_boost_path\" && test -r \"$_AX_BOOST_BASE_boost_path\"; then\n                for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do\n                    _version_tmp=`echo $i | sed \"s#$_AX_BOOST_BASE_boost_path##\" | sed 's/\\/include\\/boost-//' | sed 's/_/./'`\n                    V_CHECK=`expr $_version_tmp \\> $_version`\n                    if test \"x$V_CHECK\" = \"x1\" ; then\n                        _version=$_version_tmp\n                    fi\n                    VERSION_UNDERSCORE=`echo $_version | sed 's/\\./_/'`\n                    BOOST_CPPFLAGS=\"-I$_AX_BOOST_BASE_boost_path/include/boost-$VERSION_UNDERSCORE\"\n                done\n                dnl if nothing found search for layout used in Windows distributions\n                if test -z \"$BOOST_CPPFLAGS\"; then\n                    if test -d \"$_AX_BOOST_BASE_boost_path/boost\" && test -r \"$_AX_BOOST_BASE_boost_path/boost\"; then\n                        BOOST_CPPFLAGS=\"-I$_AX_BOOST_BASE_boost_path\"\n                    fi\n                fi\n                dnl if we found something and BOOST_LDFLAGS was unset before\n                dnl (because \"$_AX_BOOST_BASE_boost_lib_path\" = \"\"), set it here.\n                if test -n \"$BOOST_CPPFLAGS\" && test -z \"$BOOST_LDFLAGS\"; then\n                    for libsubdir in $libsubdirs ; do\n                        if ls \"$_AX_BOOST_BASE_boost_path/$libsubdir/libboost_\"* >/dev/null 2>&1 ; then break; fi\n                    done\n                    BOOST_LDFLAGS=\"-L$_AX_BOOST_BASE_boost_path/$libsubdir\"\n                fi\n            fi\n        else\n            if test \"x$cross_compiling\" != \"xyes\" ; then\n                for _AX_BOOST_BASE_boost_path in /usr /usr/local /opt /opt/local /opt/homebrew ; do\n                    if test -d \"$_AX_BOOST_BASE_boost_path\" && test -r \"$_AX_BOOST_BASE_boost_path\" ; then\n                        for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do\n                            _version_tmp=`echo $i | sed \"s#$_AX_BOOST_BASE_boost_path##\" | sed 's/\\/include\\/boost-//' | sed 's/_/./'`\n                            V_CHECK=`expr $_version_tmp \\> $_version`\n                            if test \"x$V_CHECK\" = \"x1\" ; then\n                                _version=$_version_tmp\n                                best_path=$_AX_BOOST_BASE_boost_path\n                            fi\n                        done\n                    fi\n                done\n\n                VERSION_UNDERSCORE=`echo $_version | sed 's/\\./_/'`\n                BOOST_CPPFLAGS=\"-I$best_path/include/boost-$VERSION_UNDERSCORE\"\n                if test -z \"$_AX_BOOST_BASE_boost_lib_path\" ; then\n                    for libsubdir in $libsubdirs ; do\n                        if ls \"$best_path/$libsubdir/libboost_\"* >/dev/null 2>&1 ; then break; fi\n                    done\n                    BOOST_LDFLAGS=\"-L$best_path/$libsubdir\"\n                fi\n            fi\n\n            if test -n \"$BOOST_ROOT\" ; then\n                for libsubdir in $libsubdirs ; do\n                    if ls \"$BOOST_ROOT/stage/$libsubdir/libboost_\"* >/dev/null 2>&1 ; then break; fi\n                done\n                if test -d \"$BOOST_ROOT\" && test -r \"$BOOST_ROOT\" && test -d \"$BOOST_ROOT/stage/$libsubdir\" && test -r \"$BOOST_ROOT/stage/$libsubdir\"; then\n                    version_dir=`expr //$BOOST_ROOT : '.*/\\(.*\\)'`\n                    stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'`\n                        stage_version_shorten=`expr $stage_version : '\\([[0-9]]*\\.[[0-9]]*\\)'`\n                    V_CHECK=`expr $stage_version_shorten \\>\\= $_version`\n                    if test \"x$V_CHECK\" = \"x1\" && test -z \"$_AX_BOOST_BASE_boost_lib_path\" ; then\n                        AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT)\n                        BOOST_CPPFLAGS=\"-I$BOOST_ROOT\"\n                        BOOST_LDFLAGS=\"-L$BOOST_ROOT/stage/$libsubdir\"\n                    fi\n                fi\n            fi\n        fi\n\n        CPPFLAGS=\"$CPPFLAGS $BOOST_CPPFLAGS\"\n        export CPPFLAGS\n        LDFLAGS=\"$LDFLAGS $BOOST_LDFLAGS\"\n        export LDFLAGS\n\n        AC_LANG_PUSH(C++)\n            AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[\n            AC_MSG_RESULT(yes)\n        succeeded=yes\n        found_system=yes\n            ],[\n            ])\n        AC_LANG_POP([C++])\n    fi\n\n    if test \"x$succeeded\" != \"xyes\" ; then\n        if test \"x$_version\" = \"x0\" ; then\n            AC_MSG_NOTICE([[We could not detect the boost libraries (version $1 or higher). If you have a staged boost library (still not installed) please specify \\$BOOST_ROOT in your environment and do not give a PATH to --with-boost option.  If you are sure you have boost installed, then check your version number looking in <boost/version.hpp>. See http://randspringer.de/boost for more documentation.]])\n        else\n            AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).])\n        fi\n        BOOST_LDFLAGS=\"\"\n        BOOST_CPPFLAGS=\"\"\n        # execute ACTION-IF-NOT-FOUND (if present):\n        ifelse([$3], , :, [$3])\n    else\n        AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available])\n        # execute ACTION-IF-FOUND (if present):\n        ifelse([$2], , :, [$2])\n    fi\n\n    CPPFLAGS=\"$CPPFLAGS_SAVED\"\n    LDFLAGS=\"$LDFLAGS_SAVED\"\n\n])\n"
  },
  {
    "path": "m4/ax_boost_filesystem.m4",
    "content": "# ===========================================================================\n#   https://www.gnu.org/software/autoconf-archive/ax_boost_filesystem.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_BOOST_FILESYSTEM\n#\n# DESCRIPTION\n#\n#   Test for Filesystem library from the Boost C++ libraries. The macro\n#   requires a preceding call to AX_BOOST_BASE. Further documentation is\n#   available at <http://randspringer.de/boost/index.html>.\n#\n#   This macro calls:\n#\n#     AC_SUBST(BOOST_FILESYSTEM_LIB)\n#\n#   And sets:\n#\n#     HAVE_BOOST_FILESYSTEM\n#\n# LICENSE\n#\n#   Copyright (c) 2009 Thomas Porschberg <thomas@randspringer.de>\n#   Copyright (c) 2009 Michael Tindal\n#   Copyright (c) 2009 Roman Rybalko <libtorrent@romanr.info>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 28\n\nAC_DEFUN([AX_BOOST_FILESYSTEM],\n[\n\tAC_ARG_WITH([boost-filesystem],\n\tAS_HELP_STRING([--with-boost-filesystem@<:@=special-lib@:>@],\n                   [use the Filesystem library from boost - it is possible to specify a certain library for the linker\n                        e.g. --with-boost-filesystem=boost_filesystem-gcc-mt ]),\n        [\n        if test \"$withval\" = \"no\"; then\n\t\t\twant_boost=\"no\"\n        elif test \"$withval\" = \"yes\"; then\n            want_boost=\"yes\"\n            ax_boost_user_filesystem_lib=\"\"\n        else\n\t\t    want_boost=\"yes\"\n\t\tax_boost_user_filesystem_lib=\"$withval\"\n\t\tfi\n        ],\n        [want_boost=\"yes\"]\n\t)\n\n\tif test \"x$want_boost\" = \"xyes\"; then\n        AC_REQUIRE([AC_PROG_CC])\n\t\tCPPFLAGS_SAVED=\"$CPPFLAGS\"\n\t\tCPPFLAGS=\"$CPPFLAGS $BOOST_CPPFLAGS\"\n\t\texport CPPFLAGS\n\n\t\tLDFLAGS_SAVED=\"$LDFLAGS\"\n\t\tLDFLAGS=\"$LDFLAGS $BOOST_LDFLAGS\"\n\t\texport LDFLAGS\n\n\t\tLIBS_SAVED=$LIBS\n\t\tLIBS=\"$LIBS $BOOST_SYSTEM_LIB\"\n\t\texport LIBS\n\n        AC_CACHE_CHECK(whether the Boost::Filesystem library is available,\n\t\t\t\t\t   ax_cv_boost_filesystem,\n        [AC_LANG_PUSH([C++])\n         AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <boost/filesystem/path.hpp>]],\n                                   [[using namespace boost::filesystem;\n                                   path my_path( \"foo/bar/data.txt\" );\n                                   return 0;]])],\n\t\t\t\t\t       ax_cv_boost_filesystem=yes, ax_cv_boost_filesystem=no)\n         AC_LANG_POP([C++])\n\t\t])\n\t\tif test \"x$ax_cv_boost_filesystem\" = \"xyes\"; then\n\t\t\tAC_DEFINE(HAVE_BOOST_FILESYSTEM,,[define if the Boost::Filesystem library is available])\n            BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\\/@:>@*//'`\n            if test \"x$ax_boost_user_filesystem_lib\" = \"x\"; then\n                for libextension in `ls -r $BOOSTLIBDIR/libboost_filesystem* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\\..*,,'` ; do\n                     ax_lib=${libextension}\n\t\t\t\t    AC_CHECK_LIB($ax_lib, exit,\n                                 [BOOST_FILESYSTEM_LIB=\"-l$ax_lib\"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem=\"yes\"; break],\n                                 [link_filesystem=\"no\"])\n\t\t\t\tdone\n                if test \"x$link_filesystem\" != \"xyes\"; then\n                for libextension in `ls -r $BOOSTLIBDIR/boost_filesystem* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\\..*,,'` ; do\n                     ax_lib=${libextension}\n\t\t\t\t    AC_CHECK_LIB($ax_lib, exit,\n                                 [BOOST_FILESYSTEM_LIB=\"-l$ax_lib\"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem=\"yes\"; break],\n                                 [link_filesystem=\"no\"])\n\t\t\t\tdone\n\t\t    fi\n            else\n               for ax_lib in $ax_boost_user_filesystem_lib boost_filesystem-$ax_boost_user_filesystem_lib; do\n\t\t\t\t      AC_CHECK_LIB($ax_lib, exit,\n                                   [BOOST_FILESYSTEM_LIB=\"-l$ax_lib\"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem=\"yes\"; break],\n                                   [link_filesystem=\"no\"])\n                  done\n\n            fi\n            if test \"x$ax_lib\" = \"x\"; then\n                AC_MSG_ERROR(Could not find a version of the Boost::Filesystem library!)\n            fi\n\t\t\tif test \"x$link_filesystem\" != \"xyes\"; then\n\t\t\t\tAC_MSG_ERROR(Could not link against $ax_lib !)\n\t\t\tfi\n\t\tfi\n\n\t\tCPPFLAGS=\"$CPPFLAGS_SAVED\"\n\t\tLDFLAGS=\"$LDFLAGS_SAVED\"\n\t\tLIBS=\"$LIBS_SAVED\"\n\tfi\n])\n"
  },
  {
    "path": "m4/ax_check_gl.m4",
    "content": "# ===========================================================================\n#       https://www.gnu.org/software/autoconf-archive/ax_check_gl.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_CHECK_GL([ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])\n#\n# DESCRIPTION\n#\n#   Checks for an OpenGL implementation. If a valid OpenGL implementation is\n#   found, this macro would set C preprocessor symbol HAVE_GL to 1.\n#\n#   If either a valid OpenGL header or library was not found, by default the\n#   configuration would exits on error. This behavior can be overwritten by\n#   providing a custom \"ACTION-IF-NOT-FOUND\" hook.\n#\n#   If the header, library was found, and been tested for compiling and\n#   linking the configuration would export the required compiler flags to\n#   \"GL_CFLAGS\" and \"GL_LIBS\". These two variables can also be overwritten\n#   by user from the command line if they want to link against the library\n#   they specified instead of having the configuration script to detect the\n#   flags automatically. Note that having \"GL_CFLAGS\" or \"GL_LIBS\" set\n#   doesn't mean it can compile or link with the flags, since it could be\n#   overwritten by user. However the \"HAVE_GL\" symbol and \"ACTION-IF-FOUND\"\n#   hook is always guaranteed to reflect a valid OpenGL implementation.\n#\n#   If user didn't specify the \"ACTION-IF-FOUND\" hook, the configuration\n#   would prepend \"GL_CFLAGS\" and \"GL_LIBS\" to \"CFLAGS\" and \"LIBS\", like\n#   many other autoconf macros do.\n#\n#   OpenGL is one of the libraries that has different header names on\n#   different platforms. This macro does the header detection, and will\n#   export the following symbol: \"HAVE_GL_GL_H\" for having \"GL/gl.h\" or\n#   \"HAVE_OPENGL_GL_H\" for having \"OpenGL/gl.h\". To write a portable OpenGL\n#   code, you should include OpenGL header like so:\n#\n#     #if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n#     # include <windows.h>\n#     #endif\n#     #ifdef HAVE_GL_GL_H\n#     # include <GL/gl.h>\n#     #elif defined(HAVE_OPENGL_GL_H)\n#     # include <OpenGL/gl.h>\n#     #else\n#     # error no gl.h\n#     #endif\n#\n#   On the OSX platform, there's two possible OpenGL implementation. One is\n#   the OpenGL that ships with OSX, the other comes with X11/XQuartz\n#   (http://www.xquartz.org). To use the xquartz variant, user can use the\n#   option --with-xquartz-gl[=path to xquartz root]. By default the\n#   configuration will check \"/opt/X11\", which is the default X11 install\n#   location on OSX.\n#\n# LICENSE\n#\n#   Copyright (c) 2009 Braden McDaniel <braden@endoframe.com>\n#   Copyright (c) 2012 Bastien Roucaries <roucaries.bastien+autoconf@gmail.com>\n#   Copyright (c) 2016 Felix Chern <idryman@gmail.com>\n#\n#   This program is free software; you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation; either version 2 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <https://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 22\n\n# example gl program\nm4_define([_AX_CHECK_GL_PROGRAM],\n          [AC_LANG_PROGRAM([[\n# if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n#   include <windows.h>\n# endif\n# ifdef HAVE_GL_GL_H\n#   include <GL/gl.h>\n# elif defined(HAVE_OPENGL_GL_H)\n#   include <OpenGL/gl.h>\n# else\n#   error no gl.h\n# endif\n]],[[glBegin(0)]])])\n\ndnl Default include : add windows.h\ndnl see http://www.opengl.org/wiki/Platform_specifics:_Windows\ndnl (acceded 20120801)\nAC_DEFUN([_AX_CHECK_GL_INCLUDES_DEFAULT],dnl\n[\n  AC_INCLUDES_DEFAULT\n  [\n  # if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n  #   include <windows.h>\n  # endif\n  ]\n])\n\n\n# _AX_CHECK_GL_SAVE_FLAGS(LIST-OF-FLAGS)\n# Use this macro before you modify the flags.\n# Restore the flags by _AX_CHECK_GL_RESTORE_FLAGS\n#\n# Example: _AX_CHECK_GL_SAVE_FLAGS([[CFLAGS],[LIBS]]) expands to\n# gl_saved_flag_cflags=$CFLAGS\n# gl_saved_flag_libs=$LIBS\n# CFLAGS=\"$GL_CFLAGS $CFLAGS\"\n# LIBS=\"$GL_LIBS $LIBS\"\nAC_DEFUN([_AX_CHECK_GL_SAVE_FLAGS], [\n AX_SAVE_FLAGS_WITH_PREFIX([GL],[$1])\n AC_LANG_PUSH([C])\n])\n\n# _AX_CHECK_GL_RESTORE_FLAGS(LIST-OF-FLAGS)\n# Use this marcro to restore the flags you saved using\n# _AX_CHECK_GL_SAVE_FLAGS\n#\n# Example: _AX_CHECK_GL_RESTORE_FLAGS([[CFLAGS],[LIBS]]) expands to\n# CFLAGS=\"$gl_saved_flag_cflags\"\n# LIBS=\"$gl_saved_flag_libs\"\nAC_DEFUN([_AX_CHECK_GL_RESTORE_FLAGS], [\n AX_RESTORE_FLAGS_WITH_PREFIX([GL],[$1])\n AC_LANG_POP([C])\n])\n\n# Check if the program compiles\nAC_DEFUN([_AX_CHECK_GL_COMPILE],\n[dnl\n _AX_CHECK_GL_SAVE_FLAGS([CFLAGS])\n AC_COMPILE_IFELSE([_AX_CHECK_GL_PROGRAM],\n                   [ax_check_gl_compile_opengl=\"yes\"],\n                   [ax_check_gl_compile_opengl=\"no\"])\n _AX_CHECK_GL_RESTORE_FLAGS([CFLAGS])\n])\n\n# Compile the example program (cache)\nAC_DEFUN([_AX_CHECK_GL_COMPILE_CV],\n[dnl\n AC_CACHE_CHECK([for compiling a minimal OpenGL program],[ax_cv_check_gl_compile_opengl],\n                [_AX_CHECK_GL_COMPILE()\n                 ax_cv_check_gl_compile_opengl=\"${ax_check_gl_compile_opengl}\"])\n ax_check_gl_compile_opengl=\"${ax_cv_check_gl_compile_opengl}\"\n])\n\n# Link the example program\nAC_DEFUN([_AX_CHECK_GL_LINK],\n[dnl\n _AX_CHECK_GL_SAVE_FLAGS([[CFLAGS],[LIBS],[LDFLAGS]])\n AC_LINK_IFELSE([_AX_CHECK_GL_PROGRAM],\n                [ax_check_gl_link_opengl=\"yes\"],\n                [ax_check_gl_link_opengl=\"no\"])\n _AX_CHECK_GL_RESTORE_FLAGS([[CFLAGS],[LIBS],[LDFLAGS]])\n])\n\n# Link the example program (cache)\nAC_DEFUN([_AX_CHECK_GL_LINK_CV],\n[dnl\n AC_CACHE_CHECK([for linking a minimal OpenGL program],[ax_cv_check_gl_link_opengl],\n                [_AX_CHECK_GL_LINK()\n                 ax_cv_check_gl_link_opengl=\"${ax_check_gl_link_opengl}\"])\n ax_check_gl_link_opengl=\"${ax_cv_check_gl_link_opengl}\"\n])\n\n\n# _AX_CHECK_GL_MANUAL_LIBS_GENERIC(LIBRARIES-TO-SEARCH)\n# Searches library provided in $1, and output the flag\n# $ax_check_gl_lib_opengl\nAC_DEFUN([_AX_CHECK_GL_MANUAL_LIBS_GENERIC], [\n  AS_IF([test -n \"$GL_LIBS\"],[], [\n    ax_check_gl_manual_libs_generic_extra_libs=\"$1\"\n    AS_IF([test \"X$ax_check_gl_manual_libs_generic_extra_libs\" = \"X\"],\n          [AC_MSG_ERROR([AX_CHECK_GL_MANUAL_LIBS_GENERIC argument must no be empty])])\n\n    _AX_CHECK_GL_SAVE_FLAGS([CFLAGS])\n    AC_SEARCH_LIBS([glBegin],[$ax_check_gl_manual_libs_generic_extra_libs], [\n                   ax_check_gl_lib_opengl=\"yes\"\n                   break\n                   ])\n    AS_IF([test \"X$ax_check_gl_lib_opengl\"=\"Xyes\"],\n          [GL_LIBS=\"${ac_cv_search_glBegin}\"])\n    _AX_CHECK_GL_RESTORE_FLAGS([CFLAGS])\n ])\n])\n\n# _WITH_XQUARTZ_GL\n# ----------------\n# Provides an option in command line to specify the XQuartz installation\n# path on OSX, so that user can link to it instead of using the default\n# OSX OpenGL framework. (Mac OSX only)\nAC_DEFUN_ONCE([_WITH_XQUARTZ_GL],[\n  AC_ARG_WITH([xquartz-gl],\n   [AS_HELP_STRING([--with-xquartz-gl@<:@=DIR@:>@],\n                   [On Mac OSX, use opengl provided by X11/XQuartz instead of the built-in framework.\n                    If enabled, the default location is @<:@DIR=/opt/X11@:>@.\n                    This option is default to false.])],\n   [AS_IF([test \"X$with_xquartz_gl\"=\"Xyes\"],\n          [with_xquartz_gl=\"/opt/X11\"])],\n   [with_xquartz_gl=no])\n  AS_IF([test \"X$with_xquartz_gl\" != \"Xno\"],\n        [AC_MSG_CHECKING([OSX X11 path])\n         AS_IF([test -e \"$with_xquartz_gl\"],\n               [AC_MSG_RESULT([\"$with_xquartz_gl\"])\n                CFLAGS=\"-I$with_xquartz_gl/include $CFLAGS\"\n                LIBS=\"-L$with_xquartz_gl/lib $LIBS\"\n               ],\n               [with_xquartz_gl=no\n                AC_MSG_RESULT([no])\n                AC_MSG_WARN([--with-xquartz-gl was given, but test for X11 failed. Fallback to system framework])\n               ])\n        ])\n])\n\n# OSX specific setup for OpenGL check\nAC_DEFUN([_AX_CHECK_DARWIN_GL], [\n AC_REQUIRE([_WITH_XQUARTZ_GL])\n AS_IF([test \"x$with_xquartz_gl\" != \"xno\"],\n       [GL_LIBS=\"${GL_LIBS:--lGL}\"],\n       [GL_LIBS=\"${GL_LIBS:--framework OpenGL}\"])\n])\n\n\n# AX_CHECK_GL_LIB([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\n# ---------------------------------------------------------\n# Checks OpenGL headers and library and provides hooks for success and failures.\n# When $1 is not set, this macro would modify CFLAGS and LIBS environment variables.\n# However, user can override this behavior by providing their own hooks.\n# The CFLAGS and LIBS flags required by OpenGL is always exported in\n# GL_CFLAGS and GL_LIBS environment variable.\n#\n# In other words, the default behavior of AX_CHECK_GL_LIB() is equivalent to\n# AX_CHECK_GL_LIB(\n#   [CFLAGS=\"$GL_CFLAGS $CFLAGS\"\n#    LIBS=\"$GL_LIBS $LIBS\"]\n# )\nAC_DEFUN([AX_CHECK_GL],\n[AC_REQUIRE([AC_CANONICAL_HOST])\n AC_REQUIRE([PKG_PROG_PKG_CONFIG])\n AC_ARG_VAR([GL_CFLAGS],[C compiler flags for GL, overriding configure script defaults])\n AC_ARG_VAR([GL_LIBS],[Linker flags for GL, overriding configure script defaults])\n\n dnl --with-gl or not can be implemented outside of check-gl\n AS_CASE([${host}],\n         [*-darwin*],[_AX_CHECK_DARWIN_GL],\n         dnl some windows may support X11 opengl, and should be able to linked\n         dnl by -lGL. However I have no machine to test it.\n         [*-cygwin*|*-mingw*],[\n          _AX_CHECK_GL_MANUAL_LIBS_GENERIC([opengl32 GL gl])\n          AC_CHECK_HEADERS([windows.h])\n          ],\n         [PKG_PROG_PKG_CONFIG\n          PKG_CHECK_MODULES([GL],[gl],\n          [],\n          [_AX_CHECK_GL_MANUAL_LIBS_GENERIC([GL gl])])\n         ]) dnl host specific checks\n\n dnl this was cache\n _AX_CHECK_GL_SAVE_FLAGS([CFLAGS])\n AC_CHECK_HEADERS([GL/gl.h OpenGL/gl.h],\n   [ax_check_gl_have_headers=\"yes\";break])\n _AX_CHECK_GL_RESTORE_FLAGS([CFLAGS])\n\n AS_IF([test \"X$ax_check_gl_have_headers\" = \"Xyes\"],\n       [_AX_CHECK_GL_COMPILE_CV()],\n       [no_gl=yes])\n AS_IF([test \"X$ax_check_gl_compile_opengl\" = \"Xyes\"],\n       [_AX_CHECK_GL_LINK_CV()],\n       [no_gl=yes])\n AS_IF([test \"X$no_gl\" = \"X\"],\n   [AC_DEFINE([HAVE_GL], [1], [Defined if a valid OpenGL implementation is found.])\n    m4_ifval([$1],\n      [$1],\n      [CFLAGS=\"$GL_CFLAGS $CFLAGS\"\n       LIBS=\"$GL_LIBS $LIBS\"])\n   ],\n   [m4_ifval([$2],\n     [$2],\n     [AC_MSG_ERROR([Could not find a valid OpenGL implementation])])\n   ])\n])\n"
  },
  {
    "path": "m4/ax_check_glu.m4",
    "content": "# ===========================================================================\n#       https://www.gnu.org/software/autoconf-archive/ax_check_glu.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_CHECK_GLU([ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])\n#\n# DESCRIPTION\n#\n#   Checks for GLUT. If a valid GLU implementation is found, the configure\n#   script would export the C preprocessor symbol \"HAVE_GLU=1\".\n#\n#   If either a valid GLU header or library was not found, by default the\n#   configure script would exit on error. This behavior can be overwritten\n#   by providing a custom \"ACTION-IF-NOT-FOUND\" hook.\n#\n#   If the header, library was found, and been tested for compiling and\n#   linking the configuration would export the required compiler flags to\n#   \"GLU_CFLAGS\" and \"GLU_LIBS\" environment variables. These two variables\n#   can also be overwritten by defining the environment variables before\n#   executing the configure program. If it was predefined, configure would\n#   not try to overwrite it, but it would still perform the compile and link\n#   test. Only when the tests succeeded does the configure script to export\n#   \"HAVE_GLU=1\" and to run \"ACTION-IF-FOUND\" hook.\n#\n#   If user didn't specify the \"ACTION-IF-FOUND\" hook, the configuration\n#   would prepend \"GLU_CFLAGS\" and \"GLU_LIBS\" to \"CFLAGS\" and \"LIBS\", like\n#   many other autoconf macros do.\n#\n#   If the header \"GL/glu.h\" is found, \"HAVE_GL_GLU_H\" is defined. If the\n#   header \"OpenGL/glu.h\" is found, HAVE_OPENGL_GLU_H is defined.\n#\n#   You should use something like this in your headers:\n#\n#     # if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n#     #  include <windows.h>\n#     # endif\n#     # if defined(HAVE_GL_GLU_H)\n#     #  include <GL/glu.h>\n#     # elif defined(HAVE_OPENGL_GLU_H)\n#     #  include <OpenGL/glu.h>\n#     # else\n#     #  error no glu.h\n#     # endif\n#\n#   On the OSX platform, you can use the option --with-xquartz-gl to use\n#   X11/Xquartz GLU implementation instead of the system built in GLU\n#   framework.\n#\n#   Some implementations (in particular, some versions of Mac OS X) are\n#   known to treat the GLU tesselator callback function type as \"GLvoid\n#   (*)(...)\" rather than the standard \"GLvoid (*)()\". If the former\n#   condition is detected, this macro defines \"HAVE_VARARGS_GLU_TESSCB\".\n#\n# LICENSE\n#\n#   Copyright (c) 2009 Braden McDaniel <braden@endoframe.com>\n#   Copyright (c) 2013 Bastien Roucaries <roucaries.bastien+autoconf@gmail.com>\n#   Copyright (c) 2016 Felix Chern <idryman@gmail.com>\n#\n#   This program is free software; you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation; either version 2 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <https://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 23\n\n# example program\nm4_define([_AX_CHECK_GLU_PROGRAM],\n          [AC_LANG_PROGRAM([[\n# if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n#   include <windows.h>\n# endif\n# ifdef HAVE_GL_GLU_H\n#   include <GL/glu.h>\n# elif defined(HAVE_OPENGL_GLU_H)\n#   include <OpenGL/glu.h>\n# else\n#   error no glu.h\n# endif\n]],[[gluBeginCurve(0)]])])\n\n\ndnl Default include : add windows.h\ndnl see http://www.opengl.org/wiki/Platform_specifics:_Windows\ndnl (acceded 20120801)\nAC_DEFUN([_AX_CHECK_GLU_INCLUDES_DEFAULT],dnl\n[\n  AC_INCLUDES_DEFAULT\n  [\n  # if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n  #   include <windows.h>\n  # endif\n  ]\n])\n\n# check tesselation callback function signature.\nm4_define([_AX_CHECK_GLU_VARARGS_TESSVB_PROGRAM],\n[AC_LANG_PROGRAM([[\n# if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n#   include <windows.h>\n# endif\n# ifdef HAVE_GL_GLU_H\n#   include <GL/glu.h>\n# elif defined(HAVE_OPENGL_GLU_H)\n#   include <OpenGL/glu.h>\n# else\n#   error no glu.h\n# endif\n]],\n[[GLvoid (*func)(...); gluTessCallback(0, 0, func)]])\n])\n\n\n# _AX_CHECK_GLU_SAVE_FLAGS(LIST-OF-FLAGS,[LANG])\n# ----------------------------------------------\n# Save the flags to shell variables.\n# Example: _AX_CHECK_GLU_SAVE_FLAGS([[CFLAGS],[LIBS]]) expands to\n# AC_LANG_PUSH([C])\n# glu_saved_flag_cflags=$CFLAGS\n# glu_saved_flag_libs=$LIBS\n# CFLAGS=\"$GLU_CFLAGS $CFLAGS\"\n# LIBS=\"$GLU_LIBS $LIBS\"\n#\n# Can optionally support other LANG by specifying $2\nAC_DEFUN([_AX_CHECK_GLU_SAVE_FLAGS], [\n m4_ifval([$2],\n          [AC_LANG_PUSH([$2])],\n          [AC_LANG_PUSH([C])])\n AX_SAVE_FLAGS_WITH_PREFIX([GLU],[$1]) dnl defined in ax_check_gl\n])\n\n# _AX_CHECK_GLU_RESTORE_FLAGS(LIST-OF-FLAGS)\n# Use this marcro to restore the flags you saved using\n# _AX_CHECK_GLU_SAVE_FLAGS\n#\n# Example: _AX_CHECK_GLU_RESTORE_FLAGS([[CFLAGS],[LIBS]]) expands to\n# CFLAGS=\"$glu_saved_flag_cflags\"\n# LIBS=\"$glu_saved_flag_libs\"\n# AC_LANG_POP([C])\nAC_DEFUN([_AX_CHECK_GLU_RESTORE_FLAGS], [\n AX_RESTORE_FLAGS_WITH_PREFIX([GLU],[$1]) dnl defined in ax_check_gl\n m4_ifval([$2],\n          [AC_LANG_POP([$2])],\n          [AC_LANG_POP([C])])\n])\n\n\n# Search headers and export $ax_check_glu_have_headers\nAC_DEFUN([_AX_CHECK_GLU_HEADERS], [\n  _AX_CHECK_GLU_SAVE_FLAGS([CFLAGS])\n  AC_CHECK_HEADERS([$1],\n                   [ax_check_glu_have_headers=\"yes\";],\n                   [],\n                   [_AX_CHECK_GLU_INCLUDES_DEFAULT()])\n  _AX_CHECK_GLU_RESTORE_FLAGS([CFLAGS])\n])\n\n\n# _AX_CHECK_GLU_SEARCH_LIBS(LIBS)\n# -------------------------------\n# Search for a valid GLU lib from $1 and set\n# GLU_LIBS respectively\nAC_DEFUN([_AX_CHECK_GLU_SEARCH_LIBS], [\n _AX_CHECK_GLU_SAVE_FLAGS([[CFLAGS],[LIBS]])\n AC_SEARCH_LIBS([gluBeginCurve],[$1],\n \t        [GLU_LIBS=\"${GLU_LIBS:-$ac_cv_search_gluBeginCurve}\"])\n  _AX_CHECK_GLU_RESTORE_FLAGS([[CFLAGS],[LIBS]])\n])\n\n# OSX specific GLU checks\nAC_DEFUN([_AX_CHECK_DARWIN_GLU], [\n  AC_REQUIRE([_WITH_XQUARTZ_GL])\n  AS_IF([test \"x$with_xquartz_gl\" != \"xno\"],\n        [GLU_LIBS=\"${GLU_LIBS:--lGLU}\"],\n        [GLU_LIBS=\"${GLU_LIBS:--framework OpenGL}\"])\n])\n\n# AX_CHECK_GLU([ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])\n# -----------------------------------------------------\n# Checks GLU and provides hooks for success and failures\nAC_DEFUN([AX_CHECK_GLU],[\n  AC_REQUIRE([AC_CANONICAL_HOST])\n  AC_REQUIRE([_WITH_XQUARTZ_GL])\n  AC_REQUIRE([PKG_PROG_PKG_CONFIG])\n  AC_ARG_VAR([GLU_CFLAGS],[C compiler flags for GLU, overriding system check])\n  AC_ARG_VAR([GLU_LIBS],[Linker flags for GLU, overriding system check])\n\n  dnl Setup GLU_CFLAGS and GLU_LIBS\n  AS_CASE([${host}],\n          [*-darwin*],[_AX_CHECK_DARWIN_GLU],\n          [*-cygwin*],[_AX_CHECK_GLU_SEARCH_LIBS([GLU glu MesaGLU glu32])\n                       AC_CHECK_HEADERS([windows.h])],\n          # try first native\n \t  [*-mingw*],[_AX_CHECK_GLU_SEARCH_LIBS([glu32 GLU glu MesaGLU])\n                      AC_CHECK_HEADERS([windows.h])],\n          [PKG_PROG_PKG_CONFIG\n           PKG_CHECK_MODULES([GLU],[glu],\n           [],\n           [_AX_CHECK_GLU_SEARCH_LIBS([GLU glu MesaGLU])])\n          ])\n\n  AS_CASE([$host],\n          [*-darwin*],\n            [AS_IF([test \"X$with_xquartz_gl\" = \"Xno\"],\n                   [_AX_CHECK_GLU_HEADERS([OpenGL/glu.h])],\n                   [_AX_CHECK_GLU_HEADERS([GL/glu.h])]\n                   )],\n          [_AX_CHECK_GLU_HEADERS([GL/glu.h])])\n\n  dnl compile test\n  AS_IF([test \"X$ax_check_glu_have_headers\" = \"Xyes\"],\n        [AC_CACHE_CHECK([for compiling a minimal OpenGL Utility (GLU) program],\n                        [ax_cv_check_glu_compile],\n                        [_AX_CHECK_GLU_SAVE_FLAGS([CFLAGS])\n                         AC_COMPILE_IFELSE([_AX_CHECK_GLU_PROGRAM],\n                                           [ax_cv_check_glu_compile=\"yes\"],\n                                           [ax_cv_check_glu_compile=\"no\"])\n                         _AX_CHECK_GLU_RESTORE_FLAGS([CFLAGS])])\n         ])\n\n  dnl link test\n  AS_IF([test \"X$ax_cv_check_glu_compile\" = \"Xyes\"],\n        [AC_CACHE_CHECK([for linking a minimal GLU program],\n                        [ax_cv_check_glu_link],\n                        [_AX_CHECK_GLU_SAVE_FLAGS([[CFLAGS],[LIBS]])\n                         AC_LINK_IFELSE([_AX_CHECK_GLU_PROGRAM],\n                                        [ax_cv_check_glu_link=\"yes\"],\n                                        [ax_cv_check_glu_link=\"no\"])\n                         _AX_CHECK_GLU_RESTORE_FLAGS([[CFLAGS],[LIBS]])])\n        ])\n\n#\n# Some versions of Mac OS X include a broken interpretation of the GLU\n# tesselation callback function signature.\n  AS_IF([test \"X$ax_cv_check_glu_link\" = \"Xyes\"],\n        [AC_CACHE_CHECK([if GLU varargs tesselator is using non-standard form],\n                        [ax_cv_varargs_glu_tesscb],\n                        [_AX_CHECK_GLU_SAVE_FLAGS([CFLAGS],[C++])\n                         AC_COMPILE_IFELSE([_AX_CHECK_GLU_VARARGS_TESSVB_PROGRAM],\n                                           [ax_cv_varargs_glu_tesscb=\"yes\"],\n                                           [ax_cv_varargs_glu_tesscb=\"no\"])\n                         _AX_CHECK_GLU_RESTORE_FLAGS([CFLAGS],[C++])])\n        AS_IF([test \"X$ax_cv_varargs_glu_tesscb\" = \"yes\"],\n              [AC_DEFINE([HAVE_VARARGS_GLU_TESSCB], [1],\n                         [Use nonstandard varargs form for the GLU tesselator callback])])\n        ])\n\n  dnl hook\n  AS_IF([test \"X$ax_cv_check_glu_link\" = \"Xyes\"],\n        [AC_DEFINE([HAVE_GLU],[1],[Defined if a valid GLU implementation is found.])\n         m4_ifval([$1],\n                  [$1],\n                  [CFLAGS=\"$GLU_CFLAGS $CFLAGS\"\n                   LIBS=\"$GLU_LIBS $LIBS\"])],\n        [m4_ifval([$2],\n                  [$2],\n                  [AC_MSG_ERROR([Could not find a valid GLU implementation])])\n        ])\n])\n"
  },
  {
    "path": "m4/ax_check_glut.m4",
    "content": "# ===========================================================================\n#      https://www.gnu.org/software/autoconf-archive/ax_check_glut.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_CHECK_GLUT([ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])\n#\n# DESCRIPTION\n#\n#   Checks for GLUT. If a valid GLUT implementation is found, the configure\n#   script would export the C preprocessor symbol \"HAVE_GLUT=1\".\n#\n#   If either a valid GLUT header or library was not found, by default the\n#   configure script would exit on error. This behavior can be overwritten\n#   by providing a custom \"ACTION-IF-NOT-FOUND\" hook.\n#\n#   If the header, library was found, and been tested for compiling and\n#   linking the configuration would export the required compiler flags to\n#   \"GLUT_CFLAGS\" and \"GLUT_LIBS\" environment variables. These two variables\n#   can also be overwritten by defining the environment variables before\n#   executing the configure program. If it was predefined, configure would\n#   not try to overwrite it, but it would still perform the compile and link\n#   test. Only when the tests succeeded does the configure script to export\n#   \"HAVE_GLUT=1\" and to run \"ACTION-IF-FOUND\" hook.\n#\n#   If user didn't specify the \"ACTION-IF-FOUND\" hook, the configuration\n#   would prepend \"GLUT_CFLAGS\" and \"GLUT_LIBS\" to \"CFLAGS\" and \"LIBS\", like\n#   many other autoconf macros do.\n#\n#   If the header \"GL/glut.h\" is found, \"HAVE_GL_GLUT_H\" is defined. If the\n#   header \"GLUT/glut.h\" is found, HAVE_GLUT_GLUT_H is defined.\n#\n#   You should use something like this in your headers:\n#\n#     # if HAVE_WINDOWS_H && defined(_WIN32)\n#     #  include <windows.h>\n#     # endif\n#     # if defined(HAVE_GL_GLUT_H)\n#     #  include <GL/glut.h>\n#     # elif defined(HAVE_GLUT_GLUT_H)\n#     #  include <GLUT/glut.h>\n#     # else\n#     #  error no glut.h\n#     # endif\n#\n#   On the OSX platform, you can use the option --with-xquartz-gl to use\n#   X11/Xquartz GLUT implementation instead of the system built in GLUT\n#   framework.\n#\n# LICENSE\n#\n#   Copyright (c) 2009 Braden McDaniel <braden@endoframe.com>\n#   Copyright (c) 2013 Bastien Roucaries <roucaries.bastien+autoconf@gmail.com>\n#   Copyright (c) 2016 Felix Chern <idryman@gmail.com>\n#\n#   This program is free software; you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation; either version 2 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <https://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 17\n\nAC_DEFUN([_AX_CHECK_GLUT_SAVE_FLAGS], [\n AX_SAVE_FLAGS_WITH_PREFIX([GLUT],[$1]) dnl defined in ax_check_gl\n AC_LANG_PUSH([C])\n])\n\nAC_DEFUN([_AX_CHECK_GLUT_RESTORE_FLAGS], [\n AX_RESTORE_FLAGS_WITH_PREFIX([GLUT],[$1]) dnl defined in ax_check_gl\n AC_LANG_POP([C])\n])\n\ndnl Default include : add windows.h\ndnl see http://www.opengl.org/wiki/Platform_specifics:_Windows\ndnl (acceded 20120801)\nAC_DEFUN([_AX_CHECK_GLUT_INCLUDES_DEFAULT],dnl\n[\n  AC_INCLUDES_DEFAULT\n  [\n  # if defined(HAVE_WINDOWS_H) && defined(_WIN32)\n  #   include <windows.h>\n  # endif\n  ]\n])\n\nm4_define([_AX_CHECK_GLUT_PROGRAM],\n          [AC_LANG_PROGRAM([[\n# if HAVE_WINDOWS_H && defined(_WIN32)\n#   include <windows.h>\n# endif\n# ifdef HAVE_GL_GLUT_H\n#   include <GL/glut.h>\n# elif defined(HAVE_GLUT_GLUT_H)\n#   include <GLUT/glut.h>\n# else\n#   error no glut.h\n# endif]],\n[[glutMainLoop()]])])\n\n\n# _AX_CHECK_GLUT_MANUAL_LIBS_GENERIC(LIST-OF-LIBS)\n# ------------------------------------------------\n# Searches libraries provided in $1, and export variable\n# $ax_check_glut_lib_glut\nAC_DEFUN([_AX_CHECK_GLUT_MANUAL_LIBS_GENERIC],\n[\n _AX_CHECK_GLUT_SAVE_FLAGS([[CFLAGS],[LIBS]])\n AC_SEARCH_LIBS([glutMainLoop],[$1],\n                [GLUT_LIBS=\"${GLUT_LIBS:-$ac_cv_search_glutMainLoop}\"])\n _AX_CHECK_GLUT_RESTORE_FLAGS([[CFLAGS],[LIBS]])\n])\n\n# Wrapper macro to check GLUT header\nAC_DEFUN([_AX_CHECK_GLUT_HEADER],[\n  _AX_CHECK_GLUT_SAVE_FLAGS([CFLAGS])\n  AC_CHECK_HEADERS([$1],\n                   [ax_check_glut_have_headers=yes])\n  _AX_CHECK_GLUT_RESTORE_FLAGS([CFLAGS])\n])\n\n\n# AX_CHECK_GLUT_LIB([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\n# ---------------------------------------------------------\n# Checks GLUT headers and library and provides hooks for success and failures.\nAC_DEFUN([AX_CHECK_GLUT],\n[AC_REQUIRE([AC_CANONICAL_HOST])\n AC_REQUIRE([_WITH_XQUARTZ_GL])\n AC_ARG_VAR([GLUT_CFLAGS],[C compiler flags for GLUT, overriding configure script defaults])\n AC_ARG_VAR([GLUT_LIBS],[Linker flags for GLUT, overriding configure script defaults])\n\n AS_CASE([${host}],\n         [*-darwin*],[AS_IF([test \"x$with_xquartz_gl\" != \"xno\"],\n                            [GLUT_LIBS=\"${GLUT_LIBS:--lGLUT}\"],\n                            [GLUT_LIBS=\"${GLUT_LIBS:--framework GLUT}\"])],\n         [*-cygwin*|*-mingw*],[\n            _AX_CHECK_GLUT_MANUAL_LIBS_GENERIC([glut32 glut])\n            AC_CHECK_HEADERS([windows.h])\n          ],\n         [_AX_CHECK_GLUT_MANUAL_LIBS_GENERIC([glut])\n         ]) dnl host specific checks\n\n dnl checks header\n AS_CASE([${host}],\n   [*-darwin*],[AS_IF([test \"x$with_xquartz_gl\" = \"xno\"],\n                      [_AX_CHECK_GLUT_HEADER([GLUT/glut.h])],\n                      [_AX_CHECK_GLUT_HEADER([GL/glut.h])]\n                      )],\n   [_AX_CHECK_GLUT_HEADER([GL/glut.h])])\n\n dnl compile\n AS_IF([test \"X$ax_check_glut_have_headers\" = \"Xyes\"],\n       [AC_CACHE_CHECK([for compiling a minimal GLUT program],\n                       [ax_cv_check_glut_compile],\n                       [_AX_CHECK_GLUT_SAVE_FLAGS([CFLAGS])\n                        AC_COMPILE_IFELSE([_AX_CHECK_GLUT_PROGRAM],\n                                          [ax_cv_check_glut_compile=\"yes\"],\n                                          [ax_cv_check_glut_compile=\"no\"])\n                        _AX_CHECK_GLUT_RESTORE_FLAGS([CFLAGS])\n                       ])\n      ])\n\n dnl link\n AS_IF([test \"X$ax_cv_check_glut_compile\" = \"Xyes\"],\n       [AC_CACHE_CHECK([for linking a minimal GLUT program],\n                       [ax_cv_check_glut_link],\n                       [_AX_CHECK_GLUT_SAVE_FLAGS([[CFLAGS],[LIBS]])\n                        AC_LINK_IFELSE([_AX_CHECK_GLUT_PROGRAM],\n                                       [ax_cv_check_glut_link=\"yes\"],\n                                       [ax_cv_check_glut_link=\"no\"])\n                        _AX_CHECK_GLUT_RESTORE_FLAGS([[CFLAGS],[LIBS]])\n                       ])\n       ])\n\n dnl hook\n AS_IF([test \"X$ax_cv_check_glut_link\" = \"Xyes\"],\n   [AC_DEFINE([HAVE_GLUT], [1], [Defined if a valid GLUT implementation is found])\n    m4_ifval([$1],\n     [$1],\n     [CFLAGS=\"$GLUT_CFLAGS $CFLAGS\"\n      LIBS=\"$GLUT_LIBS $LIBS\"])\n   ],\n   [m4_ifval([$2],\n     [$2],\n     [AC_MSG_ERROR([Could not find a valid GLUT implementation])]\n     )\n   ])\n\n])\n"
  },
  {
    "path": "m4/ax_pthread.m4",
    "content": "# ===========================================================================\n#           http://www.nongnu.org/autoconf-archive/ax_pthread.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])\n#\n# DESCRIPTION\n#\n#   This macro figures out how to build C programs using POSIX threads. It\n#   sets the PTHREAD_LIBS output variable to the threads library and linker\n#   flags, and the PTHREAD_CFLAGS output variable to any special C compiler\n#   flags that are needed. (The user can also force certain compiler\n#   flags/libs to be tested by setting these environment variables.)\n#\n#   Also sets PTHREAD_CC to any special C compiler that is needed for\n#   multi-threaded programs (defaults to the value of CC otherwise). (This\n#   is necessary on AIX to use the special cc_r compiler alias.)\n#\n#   NOTE: You are assumed to not only compile your program with these flags,\n#   but also link it with them as well. e.g. you should link with\n#   $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS\n#\n#   If you are only building threads programs, you may wish to use these\n#   variables in your default LIBS, CFLAGS, and CC:\n#\n#     LIBS=\"$PTHREAD_LIBS $LIBS\"\n#     CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n#     CC=\"$PTHREAD_CC\"\n#\n#   In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant\n#   has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name\n#   (e.g. PTHREAD_CREATE_UNDETACHED on AIX).\n#\n#   ACTION-IF-FOUND is a list of shell commands to run if a threads library\n#   is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it\n#   is not found. If ACTION-IF-FOUND is not specified, the default action\n#   will define HAVE_PTHREAD.\n#\n#   Please let the authors know if this macro fails on any platform, or if\n#   you have any other suggestions or comments. This macro was based on work\n#   by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help\n#   from M. Frigo), as well as ac_pthread and hb_pthread macros posted by\n#   Alejandro Forero Cuervo to the autoconf macro repository. We are also\n#   grateful for the helpful feedback of numerous users.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\nAU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])\nAC_DEFUN([AX_PTHREAD], [\nAC_REQUIRE([AC_CANONICAL_HOST])\nAC_LANG_SAVE\nAC_LANG_C\nax_pthread_ok=no\n\n# We used to check for pthread.h first, but this fails if pthread.h\n# requires special compiler flags (e.g. on True64 or Sequent).\n# It gets checked for in the link test anyway.\n\n# First of all, check if the user has set any of the PTHREAD_LIBS,\n# etcetera environment variables, and if threads linking works using\n# them:\nif test x\"$PTHREAD_LIBS$PTHREAD_CFLAGS\" != x; then\n        save_CFLAGS=\"$CFLAGS\"\n        CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n        save_LIBS=\"$LIBS\"\n        LIBS=\"$PTHREAD_LIBS $LIBS\"\n        AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])\n        AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes)\n        AC_MSG_RESULT($ax_pthread_ok)\n        if test x\"$ax_pthread_ok\" = xno; then\n                PTHREAD_LIBS=\"\"\n                PTHREAD_CFLAGS=\"\"\n        fi\n        LIBS=\"$save_LIBS\"\n        CFLAGS=\"$save_CFLAGS\"\nfi\n\n# We must check for the threads library under a number of different\n# names; the ordering is very important because some systems\n# (e.g. DEC) have both -lpthread and -lpthreads, where one of the\n# libraries is broken (non-POSIX).\n\n# Create a list of thread flags to try.  Items starting with a \"-\" are\n# C compiler flags, and other items are library names, except for \"none\"\n# which indicates that we try without any flags at all, and \"pthread-config\"\n# which is a program returning the flags for the Pth emulation library.\n\nax_pthread_flags=\"pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config\"\n\n# The ordering *is* (sometimes) important.  Some notes on the\n# individual items follow:\n\n# pthreads: AIX (must check this before -lpthread)\n# none: in case threads are in libc; should be tried before -Kthread and\n#       other compiler flags to prevent continual compiler warnings\n# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)\n# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)\n# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)\n# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)\n# -pthreads: Solaris/gcc\n# -mthreads: Mingw32/gcc, Lynx/gcc\n# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it\n#      doesn't hurt to check since this sometimes defines pthreads too;\n#      also defines -D_REENTRANT)\n#      ... -mt is also the pthreads flag for HP/aCC\n# pthread: Linux, etcetera\n# --thread-safe: KAI C++\n# pthread-config: use pthread-config program (for GNU Pth library)\n\ncase \"${host_cpu}-${host_os}\" in\n        *solaris*)\n\n        # On Solaris (at least, for some versions), libc contains stubbed\n        # (non-functional) versions of the pthreads routines, so link-based\n        # tests will erroneously succeed.  (We need to link with -pthreads/-mt/\n        # -lpthread.)  (The stubs are missing pthread_cleanup_push, or rather\n        # a function called by this macro, so we could check for that, but\n        # who knows whether they'll stub that too in a future libc.)  So,\n        # we'll just look for -pthreads and -lpthread first:\n\n        ax_pthread_flags=\"-pthreads pthread -mt -pthread $ax_pthread_flags\"\n        ;;\nesac\n\nif test x\"$ax_pthread_ok\" = xno; then\nfor flag in $ax_pthread_flags; do\n\n        case $flag in\n                none)\n                AC_MSG_CHECKING([whether pthreads work without any flags])\n                ;;\n\n                -*)\n                AC_MSG_CHECKING([whether pthreads work with $flag])\n                PTHREAD_CFLAGS=\"$flag\"\n                ;;\n\n\t\tpthread-config)\n\t\tAC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no)\n\t\tif test x\"$ax_pthread_config\" = xno; then continue; fi\n\t\tPTHREAD_CFLAGS=\"`pthread-config --cflags`\"\n\t\tPTHREAD_LIBS=\"`pthread-config --ldflags` `pthread-config --libs`\"\n\t\t;;\n\n                *)\n                AC_MSG_CHECKING([for the pthreads library -l$flag])\n                PTHREAD_LIBS=\"-l$flag\"\n                ;;\n        esac\n\n        save_LIBS=\"$LIBS\"\n        save_CFLAGS=\"$CFLAGS\"\n        LIBS=\"$PTHREAD_LIBS $LIBS\"\n        CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n\n        # Check for various functions.  We must include pthread.h,\n        # since some functions may be macros.  (On the Sequent, we\n        # need a special flag -Kthread to make this header compile.)\n        # We check for pthread_join because it is in -lpthread on IRIX\n        # while pthread_create is in libc.  We check for pthread_attr_init\n        # due to DEC craziness with -lpthreads.  We check for\n        # pthread_cleanup_push because it is one of the few pthread\n        # functions on Solaris that doesn't have a non-functional libc stub.\n        # We try pthread_create on general principles.\n        AC_TRY_LINK([#include <pthread.h>\n\t             static void routine(void* a) {a=0;}\n\t             static void* start_routine(void* a) {return a;}],\n                    [pthread_t th; pthread_attr_t attr;\n                     pthread_join(th, 0);\n                     pthread_attr_init(&attr);\n                     pthread_cleanup_push(routine, 0);\n                     pthread_create(&th,0,start_routine,0);\n                     pthread_cleanup_pop(0); ],\n                    [ax_pthread_ok=yes])\n\n        LIBS=\"$save_LIBS\"\n        CFLAGS=\"$save_CFLAGS\"\n\n        AC_MSG_RESULT($ax_pthread_ok)\n        if test \"x$ax_pthread_ok\" = xyes; then\n                break;\n        fi\n\n        PTHREAD_LIBS=\"\"\n        PTHREAD_CFLAGS=\"\"\ndone\nfi\n\n# Various other checks:\nif test \"x$ax_pthread_ok\" = xyes; then\n        save_LIBS=\"$LIBS\"\n        LIBS=\"$PTHREAD_LIBS $LIBS\"\n        save_CFLAGS=\"$CFLAGS\"\n        CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n\n        # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.\n\tAC_MSG_CHECKING([for joinable pthread attribute])\n\tattr_name=unknown\n\tfor attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do\n\t    AC_TRY_LINK([#include <pthread.h>], [int attr=$attr; return attr;],\n                        [attr_name=$attr; break])\n\tdone\n        AC_MSG_RESULT($attr_name)\n        if test \"$attr_name\" != PTHREAD_CREATE_JOINABLE; then\n            AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name,\n                               [Define to necessary symbol if this constant\n                                uses a non-standard name on your system.])\n        fi\n\n        AC_MSG_CHECKING([if more special flags are required for pthreads])\n        flag=no\n        case \"${host_cpu}-${host_os}\" in\n            *-aix* | *-freebsd* | *-darwin*) flag=\"-D_THREAD_SAFE\";;\n            *solaris* | *-osf* | *-hpux*) flag=\"-D_REENTRANT\";;\n        esac\n        AC_MSG_RESULT(${flag})\n        if test \"x$flag\" != xno; then\n            PTHREAD_CFLAGS=\"$flag $PTHREAD_CFLAGS\"\n        fi\n\n        LIBS=\"$save_LIBS\"\n        CFLAGS=\"$save_CFLAGS\"\n\n        # More AIX lossage: must compile with xlc_r or cc_r\n\tif test x\"$GCC\" != xyes; then\n          AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC})\n        else\n          PTHREAD_CC=$CC\n\tfi\nelse\n        PTHREAD_CC=\"$CC\"\nfi\n\nAC_SUBST(PTHREAD_LIBS)\nAC_SUBST(PTHREAD_CFLAGS)\nAC_SUBST(PTHREAD_CC)\n\n# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:\nif test x\"$ax_pthread_ok\" = xyes; then\n        ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])\n        :\nelse\n        ax_pthread_ok=no\n        $2\nfi\nAC_LANG_RESTORE\n])dnl AX_PTHREAD\n"
  },
  {
    "path": "m4/ax_restore_flags_with_prefix.m4",
    "content": "# =================================================================================\n#  https://www.gnu.org/software/autoconf-archive/ax_restore_flags_with_prefix.html\n# =================================================================================\n#\n# SYNOPSIS\n#\n#   AX_RESTORE_FLAGS_WITH_PREFIX(PREFIX, LIST-OF-FLAGS)\n#\n# DESCRIPTION\n#\n#   Restore the flags saved by AX_SAVE_FLAGS_WITH_PREFIX.\n#\n#   Expansion example: AX_RESTORE_FLAGS_WITH_PREFIX([GL], [[CFLAGS],[LIBS]])\n#   expands to\n#\n#     CFLAGS=\"$gl_saved_flag_cflags\"\n#     LIBS=\"$gl_saved_flag_libs\"\n#\n#   One common use case is to define a package specific wrapper macro around\n#   this one, and also restore other variables if needed. For example:\n#\n#     AC_DEFUN([_AX_CHECK_GL_RESTORE_FLAGS], [\n#       AX_RESTORE_FLAGS_WITH_PREFIX([GL],[$1])\n#       AC_LANG_POP([C])\n#     ])\n#\n#     # Restores CFLAGS, LIBS and language state\n#     _AX_CHECK_GL_RESTORE_FLAGS([[CFLAGS],[LIBS]])\n#\n# LICENSE\n#\n#   Copyright (c) 2016 Felix Chern <idryman@gmail.com>\n#\n#   This program is free software; you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation; either version 2 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <https://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 3\n\nAC_DEFUN([AX_RESTORE_FLAGS_WITH_PREFIX],[\nm4_ifval([$2], [\nm4_car($2)=\"$_ax_[]m4_tolower($1)_saved_flag_[]m4_tolower(m4_car($2))\"\n$0($1, m4_cdr($2))])\n])\n"
  },
  {
    "path": "m4/ax_save_flags_with_prefix.m4",
    "content": "# ==============================================================================\n#  https://www.gnu.org/software/autoconf-archive/ax_save_flags_with_prefix.html\n# ==============================================================================\n#\n# SYNOPSIS\n#\n#   AX_SAVE_FLAGS_WITH_PREFIX(PREFIX, LIST-OF-FLAGS)\n#\n# DESCRIPTION\n#\n#   For each flag in LIST-OF-FLAGS, it expands to lower-cased shell variable\n#   with the prefix holding the flag original value.  The saved variables\n#   can be restored by AX_RESTORE_FLAGS_WITH_PREFIX\n#\n#   As an example: AX_SAVE_FLAGS_WITH_PREFIX([GL], [[CFLAGS],[LIBS]])\n#   expands to\n#\n#     gl_saved_flag_cflags=\"$CFLAGS\"\n#     gl_saved_flag_libs=\"$LIBS\"\n#     CFLAGS=\"$GL_CFLAGS $CFLAGS\"\n#     LIBS=\"$GL_LIBS $LIBS\"\n#\n#   One common use case is to define a package specific wrapper macro around\n#   this one, and also setup other variables if needed. For example:\n#\n#     AC_DEFUN([_AX_CHECK_GL_SAVE_FLAGS], [\n#       AX_SAVE_FLAGS_WITH_PREFIX([GL],[$1])\n#       AC_LANG_PUSH([C])\n#     ])\n#\n#     # pushes GL_CFLAGS and GL_LIBS to CFLAGS and LIBS\n#     # also set the current language to test to C\n#     _AX_CHECK_GL_SAVE_FLAGS([[CFLAGS],[LIBS]])\n#\n# LICENSE\n#\n#   Copyright (c) 2016 Felix Chern <idryman@gmail.com>\n#\n#   This program is free software; you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation; either version 2 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <https://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 3\n\nAC_DEFUN([AX_SAVE_FLAGS_WITH_PREFIX],[\nm4_ifval([$2], [\n_ax_[]m4_tolower($1)_saved_flag_[]m4_tolower(m4_car($2))=\"$m4_car($2)\"\nm4_car($2)=\"$$1_[]m4_car($2) $m4_car($2)\"\n$0($1, m4_cdr($2))\n])])\n"
  },
  {
    "path": "m4/pkg.m4",
    "content": "# pkg.m4 - Macros to locate and utilise pkg-config.            -*- Autoconf -*-\n# \n# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n#\n# As a special exception to the GNU General Public License, if you\n# distribute this file as part of a program that contains a\n# configuration script generated by Autoconf, you may include it under\n# the same distribution terms that you use for the rest of that program.\n\n# PKG_PROG_PKG_CONFIG([MIN-VERSION])\n# ----------------------------------\nAC_DEFUN([PKG_PROG_PKG_CONFIG],\n[m4_pattern_forbid([^_?PKG_[A-Z_]+$])\nm4_pattern_allow([^PKG_CONFIG(_PATH)?$])\nAC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl\nif test \"x$ac_cv_env_PKG_CONFIG_set\" != \"xset\"; then\n\tAC_PATH_TOOL([PKG_CONFIG], [pkg-config])\nfi\nif test -n \"$PKG_CONFIG\"; then\n\t_pkg_min_version=m4_default([$1], [0.9.0])\n\tAC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])\n\tif $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then\n\t\tAC_MSG_RESULT([yes])\n\telse\n\t\tAC_MSG_RESULT([no])\n\t\tPKG_CONFIG=\"\"\n\tfi\n\t\t\nfi[]dnl\n])# PKG_PROG_PKG_CONFIG\n\n# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\n#\n# Check to see whether a particular set of modules exists.  Similar\n# to PKG_CHECK_MODULES(), but does not set variables or print errors.\n#\n#\n# Similar to PKG_CHECK_MODULES, make sure that the first instance of\n# this or PKG_CHECK_MODULES is called, or make sure to call\n# PKG_CHECK_EXISTS manually\n# --------------------------------------------------------------\nAC_DEFUN([PKG_CHECK_EXISTS],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl\nif test -n \"$PKG_CONFIG\" && \\\n    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors \"$1\"]); then\n  m4_ifval([$2], [$2], [:])\nm4_ifvaln([$3], [else\n  $3])dnl\nfi])\n\n\n# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])\n# ---------------------------------------------\nm4_define([_PKG_CONFIG],\n[if test -n \"$PKG_CONFIG\"; then\n    if test -n \"$$1\"; then\n        pkg_cv_[]$1=\"$$1\"\n    else\n        PKG_CHECK_EXISTS([$3],\n                         [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 \"$3\" 2>/dev/null`],\n\t\t\t [pkg_failed=yes])\n    fi\nelse\n\tpkg_failed=untried\nfi[]dnl\n])# _PKG_CONFIG\n\n# _PKG_SHORT_ERRORS_SUPPORTED\n# -----------------------------\nAC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])\nif $PKG_CONFIG --atleast-pkgconfig-version 0.20; then\n        _pkg_short_errors_supported=yes\nelse\n        _pkg_short_errors_supported=no\nfi[]dnl\n])# _PKG_SHORT_ERRORS_SUPPORTED\n\n\n# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],\n# [ACTION-IF-NOT-FOUND])\n#\n#\n# Note that if there is a possibility the first call to\n# PKG_CHECK_MODULES might not happen, you should be sure to include an\n# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac\n#\n#\n# --------------------------------------------------------------\nAC_DEFUN([PKG_CHECK_MODULES],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl\nAC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl\nAC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl\n\npkg_failed=no\nAC_MSG_CHECKING([for $1])\n\n_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])\n_PKG_CONFIG([$1][_LIBS], [libs], [$2])\n\nm4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS\nand $1[]_LIBS to avoid the need to call pkg-config.\nSee the pkg-config man page for more details.])\n\nif test $pkg_failed = yes; then\n        _PKG_SHORT_ERRORS_SUPPORTED\n        if test $_pkg_short_errors_supported = yes; then\n\t        $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors \"$2\"`\n        else \n\t        $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors \"$2\"`\n        fi\n\t# Put the nasty error message in config.log where it belongs\n\techo \"$$1[]_PKG_ERRORS\" >&AS_MESSAGE_LOG_FD\n\n\tifelse([$4], , [AC_MSG_ERROR(dnl\n[Package requirements ($2) were not met:\n\n$$1_PKG_ERRORS\n\nConsider adjusting the PKG_CONFIG_PATH environment variable if you\ninstalled software in a non-standard prefix.\n\n_PKG_TEXT\n])],\n\t\t[AC_MSG_RESULT([no])\n                $4])\nelif test $pkg_failed = untried; then\n\tifelse([$4], , [AC_MSG_FAILURE(dnl\n[The pkg-config script could not be found or is too old.  Make sure it\nis in your PATH or set the PKG_CONFIG environment variable to the full\npath to pkg-config.\n\n_PKG_TEXT\n\nTo get pkg-config, see <http://pkg-config.freedesktop.org/>.])],\n\t\t[$4])\nelse\n\t$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS\n\t$1[]_LIBS=$pkg_cv_[]$1[]_LIBS\n        AC_MSG_RESULT([yes])\n\tifelse([$3], , :, [$3])\nfi[]dnl\n])# PKG_CHECK_MODULES\n"
  },
  {
    "path": "scripts/gource-ps.pl",
    "content": "#!/usr/bin/perl\n#poll ps and convert changes into Gource format.\n\n#usage:\n#    gource-ps.pl [[USER@]SERVER] | gource --log-format custom -\n#\n# (requires Gource 0.27 or later as needs working STDIN support)\n\nuse strict;\nuse warnings;\nuse Getopt::Long qw(GetOptions);\n\n#use Data::Dumper;\n\nmy %opt = ();\n\nif(!GetOptions(\\%opt, 'debug|d')) {\n    die(\"usage: gource-ps.pl [[USER\\@]SERVER] | gource --log-format custom - ...\\n\");\n}\n\nmy $windows = $^O =~ /win32|msys/i;\n\nif($windows) {\n    require Win32::Process::Info;\n    Win32::Process::Info->import();\n}\n\nmy($server) = @ARGV;\nmy $user = $ENV{USER};\n\nif($server && $server =~ /(.+)\\@(.+)/) {\n    $user = $1;\n    $server = $2;\n}\n\ndie('remote ps via ssh unimplemented on Win32') if $windows && $server;\n\n$|=1;\n\nmy %process;\n\nsub _proc_list {\n\n    my $ps_command =  'ps axo pid,ppid,user,time,comm';\n\n    my @pslist = $server ? `ssh $user\\@$server \"$ps_command\"` : `$ps_command`;\n\n    if($?) {\n        die(\"ps command failed: $!\\n\");\n    }\n\n    shift @pslist;\n\n    chomp(@pslist);\n\n    my @stack;\n\n    #build process tree\n    foreach my $line (@pslist) {\n        $line =~ s/^\\s+//;\n\n        my ($pid, $ppid, $username, $time, @command) = split(/\\s+/, $line);\n\n        my $command = join(' ', @command) || '';\n        $command =~ s{^.+/}{}g;\n\n        my $proc;\n\n        if($proc = $process{$pid}) {\n            $proc->{status} = ($proc->{'time'} ne $time) ? 'M' : '';\n        } else {\n            $proc = {\n                pid      => $pid,\n                ppid     => $ppid,\n                username => $username,\n                command  => $command,\n                'time'   => $time,\n                status   => 'A',\n            };\n\n            $process{$pid} = $proc;\n        }\n\n        #warn Dumper($process{$pid});\n    }\n}\n\nsub _win32_proc_list {\n    my $pi = Win32::Process::Info->new ();\n\n    foreach my $winproc ($pi->GetProcInfo) {\n\n#        use Data::Dumper;\n#        print Dumper($proc);\n\n        my $pid      = $winproc->{ProcessId};\n        my $ppid     = $winproc->{ParentProcessId};\n        my $command  = $winproc->{Description};\n        my $username = $winproc->{Owner} || 'System';\n        my $time     = $winproc->{UserModeTime};\n\n        $username =~ s/^.+\\\\//;               \n        $command  =~ s/\\.exe$//i;\n\n        next unless $command && $pid && $username;\n\n        my $proc;\n\n        if($proc = $process{$pid}) {\n            $proc->{status} = ($proc->{'time'} ne $time) ? 'M' : '';\n        } else {\n            $proc = {\n                pid      => $pid,\n                ppid     => $ppid,\n                username => $username,\n                command  => $command,\n                'time'   => $time,\n                status   => 'A',\n            };\n\n            $process{$pid} = $proc;\n        }\n\n\n    }\n}\n\nsub _make_command_path {\n    my $proc = shift;\n\n    my @path;\n\n    my $node = $proc;\n\n    while($node) {\n        push @path, $node->{pid} . '.' . $node->{command};\n        $node = $node->{ppid} ? $process{$node->{ppid}} : 0;\n    }\n\n    return join('/', reverse @path);\n}\n\nwhile(1) {\n\n    my @proclist = $windows ? _win32_proc_list : _proc_list;\n\n    my @filter_pids;\n\n    #filter ps / gource processes and parent processes owned by this user\n    foreach my $pid (sort {$a <=> $b} keys %process) {\n        my $proc = $process{$pid};\n\n        next unless $proc;\n\n        # delete ps process and parents of\n        if($proc->{command} eq 'ps' && $proc->{username} eq $user || $proc->{command} eq 'gource') {\n            while($proc) {\n                push @filter_pids, $proc->{pid};\n                $proc = $proc->{ppid} ? $process{$proc->{ppid}} : undef;\n            }\n        }\n    }\n\n    delete $process{$_} for @filter_pids;\n\n    my @expired_pids;\n\n    my $current_time = time;\n\n    foreach my $pid (sort {$a <=> $b} keys %process) {\n\n        my $proc = $process{$pid};\n\n        if($proc->{status}) {\n            my $output_line = join('|', $current_time, $proc->{username}, $proc->{status}, _make_command_path($proc)). \"\\n\";\n            print $output_line;\n            print STDERR $output_line if $opt{debug};\n        }\n\n        #delete if not seen next time\n        if($proc->{status} eq 'D') {\n            push @expired_pids,  $pid;\n        } else {\n            $proc->{status} = 'D';\n        }\n    }\n\n    #remove expired pids\n    delete $process{$_} for @expired_pids;\n\n    sleep(1);\n}\n\n"
  },
  {
    "path": "src/.gitignore",
    "content": "*.o\n"
  },
  {
    "path": "src/action.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"action.h\"\n\nRAction::RAction(RUser* source, RFile* target, time_t timestamp, float t, const vec3& colour)\n    : colour(colour), source(source), target(target), timestamp(timestamp), t(t), progress(0.0f), rate(0.5f) {\n}\n\nvoid RAction::apply() {\n    target->touch(timestamp, colour);\n}\n\nvoid RAction::logic(float dt) {\n    if(progress >= 1.0) return;\n\n    if(progress == 0.0) {\n        apply();\n    }\n\n    float action_rate = std::min(10.0f, rate * std::max(1.0f, ((float)source->getPendingActionCount())));\n\n    progress = std::min(progress + action_rate * dt, 1.0f);\n}\n\nvoid RAction::drawToVBO(quadbuf& buffer) const {\n    if(isFinished()) return;\n\n    vec2 src  = source->getPos();\n    vec2 dest = target->getAbsolutePos();\n\n    //TODO: could use glm::perp\n\n    vec2 n    = normalise(dest - src);\n    vec2 perp = vec2(-n.y, n.x);\n\n    vec2 offset     = perp * target->getSize() * 0.5f;\n    vec2 offset_src = offset * 0.3f;\n\n    float alpha = 1.0 - progress;\n    float alpha2 = alpha * 0.1;\n\n    vec4 col1 = vec4(colour, alpha);\n    vec4 col2 = vec4(colour, alpha2);\n\n    quadbuf_vertex v1(src  - offset_src,  col2, vec2(0.0f, 0.0f));\n    quadbuf_vertex v2(src  + offset_src,  col2, vec2(0.0f, 1.0f));\n    quadbuf_vertex v3(dest + offset,      col1, vec2(1.0f, 1.0f));\n    quadbuf_vertex v4(dest - offset,      col1, vec2(1.0f, 0.0f));\n\n    buffer.add(0, v1, v2, v3, v4);\n}\n\nvoid RAction::draw(float dt) {\n    if(isFinished()) return;\n\n    vec2 src  = source->getPos();\n    vec2 dest = target->getAbsolutePos();\n\n    vec2 n    = normalise(dest - src);\n    vec2 perp = vec2(-n.y, n.x);\n\n    vec2 offset     = perp * target->getSize() * 0.5f;\n    vec2 offset_src = offset * 0.3f;\n\n    float alpha = 1.0 - progress;\n    float alpha2 = alpha * 0.1;\n\n    vec4 col1 = vec4(colour, alpha);\n    vec4 col2 = vec4(colour, alpha2);\n\n    glBegin(GL_QUADS);\n        glColor4fv(glm::value_ptr(col2));\n        glTexCoord2f(0.0,0.0);\n        glVertex2f(src.x - offset_src.x, src.y - offset_src.y);\n        glTexCoord2f(0.0,1.0);\n        glVertex2f(src.x + offset_src.x, src.y + offset_src.y);\n\n        glColor4fv(glm::value_ptr(col1));\n        glTexCoord2f(1.0,1.0);\n        glVertex2f(dest.x + offset.x, dest.y + offset.y);\n        glTexCoord2f(1.0,0.0);\n       glVertex2f(dest.x - offset.x, dest.y - offset.y);\n    glEnd();\n}\n\nCreateAction::CreateAction(RUser* source, RFile* target, time_t timestamp, float t)\n    : RAction(source, target, timestamp, t, vec3(0.0f, 1.0f, 0.0f)) {\n}\n\nRemoveAction::RemoveAction(RUser* source, RFile* target, time_t timestamp, float t)\n    : RAction(source, target, timestamp, t, vec3(1.0f, 0.0f, 0.0f)) {\n}\n\nvoid RemoveAction::logic(float dt) {\n    float old_progress = progress;\n\n    RAction::logic(dt);\n\n    if(old_progress < 1.0 && progress >= 1.0) {\n        target->remove(timestamp);\n    }\n}\n\nModifyAction::ModifyAction(RUser* source, RFile* target, time_t timestamp, float t, const vec3& modify_colour)\n    : RAction(source, target, timestamp, t, vec3(1.0f, 0.7f, 0.3f)), modify_colour(modify_colour) {\n}\n\nvoid ModifyAction::apply() {\n    RAction::apply();\n    target->setFileColour(modify_colour);\n}\n"
  },
  {
    "path": "src/action.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef RACTION_H\n#define RACTION_H\n\n#include \"user.h\"\n#include \"file.h\"\n\nclass RUser;\nclass RFile;\n\nclass RAction {\nprotected:\n    vec3 colour;\n    virtual void apply();\npublic:\n    RUser* source;\n    RFile* target;\n\n    time_t timestamp;\n    float t;\n\n    float progress;\n    float rate;\n\n    RAction(RUser* source, RFile* target, time_t timestamp, float t, const vec3& colour);\n    virtual ~RAction() {};\n    \n    inline bool isFinished() const { return (progress >= 1.0); };\n\n    virtual void logic(float dt);\n\n    void drawToVBO(quadbuf& buffer) const ;\n    void draw(float dt);\n};\n\nclass CreateAction : public RAction {\npublic:\n    CreateAction(RUser* source, RFile* target, time_t timestamp, float t);\n};\n\nclass RemoveAction : public RAction {\npublic:\n    RemoveAction(RUser* source, RFile* target, time_t timestamp, float t);\n\n    void logic(float dt);\n};\n\nclass ModifyAction : public RAction {\nprotected:\n    vec3 modify_colour;\npublic:\n    ModifyAction(RUser* source, RFile* target, time_t timestamp, float t, const vec3& modify_colour);\n\n    void apply();\n};\n\n#endif\n\n"
  },
  {
    "path": "src/bloom.cpp",
    "content": "/*\n    Copyright (C) 2011 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"bloom.h\"\n\n//bloombuf\n\nbloombuf::bloombuf(int data_size) : data_size(data_size) {\n    bufferid     = 0;\n    buffer_size  = 0;\n    vertex_count = 0;\n\n    data = data_size > 0 ? new bloom_vertex[data_size] : 0;\n\n    //fprintf(stderr, \"size of bloom_vertex = %d\\n\", sizeof(bloom_vertex));\n}\n\nbloombuf::~bloombuf() {\n    if(bufferid !=0) glDeleteBuffers(1, &bufferid);\n    if(data != 0) delete[] data;\n}\n\nvoid bloombuf::resize(int new_size) {\n\n    bloom_vertex* _data = data;\n\n    data = new bloom_vertex[new_size];\n\n    for(int i=0;i<data_size;i++) {\n        data[i] = _data[i];\n    }\n\n    data_size = new_size;\n\n    if(_data != 0) delete[] _data;\n}\n\nvoid bloombuf::reset() {\n    vertex_count = 0;\n}\n\nvoid bloombuf::unload() {\n    if(bufferid !=0) glDeleteBuffers(1, &bufferid);\n    bufferid = 0;\n    buffer_size = 0;\n}\n\nsize_t bloombuf::vertices() {\n    return vertex_count;\n}\n\nsize_t bloombuf::capacity() {\n    return data_size;\n}\n\nvoid bloombuf::add(GLuint textureid, const vec2& pos, const vec2& dims, const vec4& colour, const vec4& texcoord) {\n\n    bloom_vertex v1(pos,                       colour, texcoord);\n    bloom_vertex v2(pos + vec2(dims.x, 0.0f), colour, texcoord);\n    bloom_vertex v3(pos + dims,                colour, texcoord);\n    bloom_vertex v4(pos + vec2(0.0f, dims.y), colour, texcoord);\n\n    int i = vertex_count;\n\n    vertex_count += 4;\n\n    if(vertex_count > data_size) {\n        resize(vertex_count*2);\n    }\n\n    data[i]   = v1;\n    data[i+1] = v2;\n    data[i+2] = v3;\n    data[i+3] = v4;\n}\n\nvoid bloombuf::update() {\n    if(vertex_count==0) return;\n\n    //note possibly better to have a queue and cycle them here\n    if(bufferid==0) {\n        glGenBuffers(1, &bufferid);\n    }\n\n    glBindBuffer(GL_ARRAY_BUFFER, bufferid);\n\n    //recreate buffer if less than the vertex_count\n    if(buffer_size < vertex_count) {\n        buffer_size = data_size;\n        glBufferData(GL_ARRAY_BUFFER, buffer_size*sizeof(bloom_vertex), &(data[0].pos.x), GL_DYNAMIC_DRAW);\n    } else {\n        glBufferSubData(GL_ARRAY_BUFFER, 0, vertex_count*sizeof(bloom_vertex), &(data[0].pos.x));\n    }\n\n    glBindBuffer(GL_ARRAY_BUFFER, 0);\n}\n\nvoid bloombuf::draw() {\n    if(vertex_count==0 || bufferid==0) return;\n\n    glBindBuffer(GL_ARRAY_BUFFER, bufferid);\n\n    glEnableClientState(GL_VERTEX_ARRAY);\n    glEnableClientState(GL_COLOR_ARRAY);\n    glEnableClientState(GL_TEXTURE_COORD_ARRAY);\n\n    glVertexPointer(2,   GL_FLOAT, sizeof(bloom_vertex), 0);\n    glColorPointer(4,    GL_FLOAT, sizeof(bloom_vertex), (GLvoid*)8);  // offset pos (2x4 bytes)\n    glTexCoordPointer(4, GL_FLOAT, sizeof(bloom_vertex), (GLvoid*)24); // offset pos + colour (2x4 + 4x4 bytes)\n\n    glDrawArrays(GL_QUADS, 0, vertex_count);\n\n    glDisableClientState(GL_VERTEX_ARRAY);\n    glDisableClientState(GL_COLOR_ARRAY);\n    glDisableClientState(GL_TEXTURE_COORD_ARRAY);\n\n    glBindBuffer(GL_ARRAY_BUFFER, 0);\n}\n"
  },
  {
    "path": "src/bloom.h",
    "content": "/*\n    Copyright (C) 2011 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GOURCE_BLOOM_VBO_H\n#define GOURCE_BLOOM_VBO_H\n\n#include <stack>\n\n#include \"core/display.h\"\n#include \"core/vectors.h\"\n#include \"core/logger.h\"\n\n//note this should be 64 bytes\nclass bloom_vertex {\npublic:\n    bloom_vertex() {};\n    bloom_vertex(const vec2& pos, const vec4& colour, const vec4& texcoord) :\n        pos(pos), colour(colour), texcoord(texcoord) {};\n\n    vec2 pos;\n    vec4 colour;\n    vec4 texcoord;\n    char padding[24];\n};\n\nclass bloombuf {\n\n    bloom_vertex* data;\n    int data_size;\n\n    GLuint bufferid;\n    int buffer_size;\n\n    int vertex_count;\n\n    void resize(int new_size);\npublic:\n    bloombuf(int data_size = 0);\n    ~bloombuf();\n\n    void unload();\n    void reset();\n\n    size_t vertices();\n    size_t capacity();\n\n    void add(GLuint textureid, const vec2& pos, const vec2& dims, const vec4& colour, const vec4& texcoord);\n\n    void update();\n    void draw();\n};\n\n#endif\n"
  },
  {
    "path": "src/caption.cpp",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"caption.h\"\n\nRCaption::RCaption(const std::string& caption, time_t timestamp, const FXFont& font) {\n\n    this->caption   = caption;\n    this->timestamp = timestamp;\n    this->font      = font;\n    \n    alpha   = 0.0;\n    elapsed = 0.0;\n    colour  = gGourceSettings.caption_colour;\n}\n\nvoid RCaption::setPos(const vec2& pos) {\n    this->pos = pos;\n}\n\nconst vec2& RCaption::getPos() const {\n    return pos;\n}\n\nconst std::string& RCaption::getCaption() const {\n    return caption;\n}\n\nbool RCaption::isFinished() const {\n    return elapsed >= gGourceSettings.caption_duration;\n}\n\nvoid RCaption::logic(float dt) {\n    float fade_in = glm::min(2.0f, gGourceSettings.caption_duration / 3.0f);\n    elapsed += dt;\n    alpha = glm::min(1.0f, glm::min(elapsed,glm::max(0.0f,gGourceSettings.caption_duration-elapsed)) / fade_in);\n}\n\nvoid RCaption::draw() {\n    font.setColour(vec4(colour.x, colour.y, colour.z, alpha));\n    font.draw(pos.x, pos.y, caption);\n}\n"
  },
  {
    "path": "src/caption.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef RCAPTION_H\n#define RCAPTION_H\n\n#include \"core/display.h\"\n#include \"core/fxfont.h\"\n#include \"gource_settings.h\"\n\nclass RCaption {    \n    float alpha;\n    float elapsed;\n\n    vec2 pos;\n\n    vec3 colour;\n    \n    FXFont font;\n    std::string caption;\n    \npublic:\n    time_t timestamp;\n\n    RCaption(const std::string& caption, time_t timestamp, const FXFont& font);\n\n    void setPos(const vec2& pos);\n\n    const vec2& getPos() const;\n    const std::string& getCaption() const;\n    \n    bool isFinished() const;\n    \n    void logic(float dt);\n\n    void draw();    \n};\n\n#endif\n"
  },
  {
    "path": "src/dirnode.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"dirnode.h\"\n\nfloat gGourceMinDirSize   = 15.0;\n\nfloat gGourceForceGravity = 10.0;\nfloat gGourceDirPadding   = 1.5;\n\nbool  gGourceNodeDebug    = false;\nbool  gGourceGravity      = true;\n\n//debugging\nint  gGourceDirNodeInnerLoops = 0;\nint  gGourceFileInnerLoops = 0;\n\nstd::map<std::string, RDirNode*> gGourceDirMap;\n\nRDirNode::RDirNode(RDirNode* parent, const std::string & abspath) {\n\n    changePath(abspath);\n\n    parent = 0;\n    setParent(parent);\n\n    accel = spos = prev_accel = vel = vec2(0.0f);\n\n    //NOTE: parent is always being set to 0 so this never gets called ...\n\n    //figure out starting position\n    if(parent !=0) {\n        vec2 parentPos = parent->getPos();\n        vec2 offset;\n\n        pos = parentPos;\n    } else {\n        pos = vec2(0.0f, 0.0f);\n    }\n\n    float padded_file_radius  = gGourceFileDiameter * 0.5;\n\n    file_area  = padded_file_radius * padded_file_radius * PI;\n\n    visible_count = 0;\n\n    visible = false;\n    position_initialized = false;\n\n    since_node_visible = 0.0;\n    since_last_file_change = 0.0;\n    since_last_node_change = 0.0;\n\n    calcRadius();\n    calcColour();\n}\n\nvoid RDirNode::changePath(const std::string & abspath) {\n    //fix up path\n\n    gGourceDirMap.erase(this->abspath);\n    this->abspath = abspath;\n\n    if(abspath.empty() || abspath[abspath.size()-1] != '/') {\n        this->abspath += std::string(\"/\");\n    }\n\n    //debugLog(\"new dirnode %s\\n\", abspath.c_str());\n\n    gGourceDirMap[this->abspath] = this;\n}\n\nRDirNode::~RDirNode() {\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        delete (*it);\n    }\n\n    gGourceDirMap.erase(abspath);\n}\n\nint RDirNode::getTokenOffset() const{\n    return path_token_offset;\n}\n\nvoid RDirNode::fileUpdated(bool userInitiated) {\n    calcRadius();\n\n    since_last_file_change = 0.0;\n\n    nodeUpdated(userInitiated);\n}\n\nvoid RDirNode::nodeUpdated(bool userInitiated) {\n    if(userInitiated) since_last_node_change = 0.0;\n\n    calcRadius();\n    updateFilePositions();\n    if(visible && noDirs() && noFiles()) visible = false;\n    if(parent !=0) parent->nodeUpdated(true);\n}\n\nvoid RDirNode::rotate(float s, float c) {\n    pos  = rotate_vec2(pos,  s, c);\n    spos = rotate_vec2(spos, s, c);\n\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child = (*it);\n        child->rotate(s, c);\n    }\n}\n\nvoid RDirNode::rotate(float s, float c, const vec2& centre) {\n\n    pos  = rotate_vec2(pos - centre,  s, c) + centre;\n    spos = rotate_vec2(spos - centre, s, c) + centre;\n\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child = (*it);\n        child->rotate(s, c, centre);\n    }\n}\n\nvoid RDirNode::setPos(const vec2 & pos) {\n    this->pos = pos;\n}\n\n//returns true if supplied path prefixes the nodes path\nbool RDirNode::prefixedBy(const std::string & path) const {\n    if(path.empty()) return false;\n\n    if(path[path.size()-1] != '/')\n        return abspath.find(path + std::string(\"/\")) == 0;\n    else\n        return abspath.find(path) == 0;\n}\n\nconst std::string & RDirNode::getPath() const{\n    return abspath;\n}\n\nRDirNode* RDirNode::getParent() const{\n    return parent;\n}\n\n\nbool RDirNode::isDir(const std::string& path) const {\n\n    if(prefixedBy(path)) return true;\n\n    if(path.find(abspath) != 0) return false;\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        if((*it)->isDir(path)) return true;\n    }\n\n    return false;\n}\n\n//finds directories closest to the root directory prefixed by path (eg foo/ may match just foo/ or could also match foo/bar1, foo/bar2, ... if foo/ doesn't exist).\nvoid RDirNode::findDirs(const std::string& path, std::list<RDirNode*>& dirs) {\n\n    if(prefixedBy(path)) {\n        dirs.push_back(this);\n        return;\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        (*it)->findDirs(path, dirs);\n    }\n}\n\nvoid RDirNode::getFilesRecursive(std::list<RFile*>& files) const {\n\n    //add this dirs files\n    files.insert(files.begin(), this->files.begin(), this->files.end());\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        (*it)->getFilesRecursive(files);\n    }\n}\n\nint RDirNode::getDepth() const{\n    return depth;\n}\n\nvoid RDirNode::adjustPath() {\n\n    //update display path\n    int parent_token_offset = 0;\n\n    path_token_offset = abspath.size();\n\n    if(parent != 0) {\n        parent_token_offset  = parent->getTokenOffset();\n\n        //debugLog(\"abspath.substr arguments: %d %d %s size = %d\\n\", parent_token_offset, abspath.size()-parent_token_offset-1, abspath.c_str(), abspath.size());\n\n        path_token        = abspath.substr(parent_token_offset, abspath.size()-parent_token_offset-1);\n        path_token_offset = abspath.size();\n\n        //debugLog(\"new token %s\\n\", path_token.c_str());\n    }\n}\n\nvoid RDirNode::setParent(RDirNode* parent) {\n    if(parent != 0 && this->parent == parent) return;\n\n    this->parent = parent;\n\n    adjustPath();\n    adjustDepth();\n}\n\nvoid RDirNode::adjustDepth() {\n\n    if(parent == 0) {\n        depth = 1;\n    } else {\n        depth = parent->getDepth() + 1;\n    }\n\n    for(RDirNode* child : children) {\n        child->adjustDepth();\n    }\n}\n\nvoid RDirNode::addNode(RDirNode* node) {\n    // does this node prefix any other nodes, if so, add them to it\n\n    std::vector<RDirNode*> matches;\n    std::string path = node->getPath();\n\n    //debugLog(\"adding node %s to %s\\n\", path.c_str(), abspath.c_str());\n\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); ) {\n        RDirNode* child = (*it);\n\n        if(child->prefixedBy(path)) {\n            it = children.erase(it);\n            node->addNode(child);\n        } else {\n            it++;\n        }\n    }\n\n    // add to this node\n    children.push_back(node);\n    node->setParent(this);\n\n    //debugLog(\"added node %s to %s\\n\", node->getPath().c_str(), getPath().c_str());\n\n    nodeUpdated(false);\n}\n\nRDirNode* RDirNode::getRoot() {\n    if(parent==0) return this;\n    return parent->getRoot();\n}\n\n// note - you still need to delete the file yourself\nbool RDirNode::removeFile(RFile* f) {\n    //doesnt match this path at all\n    if(f->path.find(abspath) != 0) {\n        return false;\n    }\n\n    //is this dir - add to this node\n    if(f->path.compare(abspath) == 0) {\n\n        for(std::list<RFile*>::iterator it = files.begin(); it != files.end(); it++) {\n            if((*it)==f) {\n                files.erase(it);\n                if(!f->isHidden()) visible_count--;\n\n                fileUpdated(false);\n\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    //does this belong to one of the children ?\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        bool removed = node->removeFile(f);\n\n        if(removed) {\n            //fprintf(stderr, \"%s file removed from a child. child file count=%d, dir count =%d\\n\", getPath().c_str(), node->fileCount(), node->dirCount());\n            //node->printFiles();\n\n            //node is now empty, reap!\n            if(node->noFiles() && node->noDirs()) {\n                children.erase(it);\n                //fprintf(stderr, \"deleting node %s from %s\\n\", node->getPath().c_str(), getPath().c_str());\n                delete node;\n                nodeUpdated(false);\n            }\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\n\nvoid RDirNode::printFiles() {\n    for(std::list<RFile*>::iterator it = files.begin(); it != files.end(); it++) {\n        RFile* file = (*it);\n        fprintf(stderr, \"%s: %s %s\\n\", getPath().c_str(), file->fullpath.c_str() , file->isHidden() ? \"hidden \" : \"\");\n    }\n}\n\n\nvoid RDirNode::addVisible() {\n    visible_count++;\n    visible = true;\n}\n\nbool RDirNode::isVisible() {\n\n    if(visible) return true;\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        if((*it)->isVisible()) {\n            visible = true;\n            return true;\n        }\n    }\n\n    return false;\n}\n\nint RDirNode::visibleFileCount() const{\n    return visible_count;\n}\n\nint RDirNode::fileCount() const{\n    return files.size();\n}\n\nbool RDirNode::noFiles() const{\n    return files.empty();\n}\n\nstd::string RDirNode::commonPathPrefix(const std::string & str) const{\n    size_t c = 0;\n    int slash = -1;\n\n    while(c<abspath.size() && c<str.size() && abspath[c] == str[c]) {\n        if(abspath[c] == '/') {\n            slash = c;\n        }\n        c++;\n    }\n\n    if(slash==-1) return \"\";\n    return str.substr(0,slash+1);\n}\n\nbool RDirNode::addFile(RFile* f) {\n\n    //doesnt match this path at all\n    if(f->path.find(abspath) != 0) {\n\n        if(parent != 0) return false;\n\n        //if this is the root node (ie no parent), we fork it\n        //if we encounter a file with a non matching path to the\n        //current root path. the calling process then checks if\n        //the root now has a parent node, and changes the pointer.\n\n        RDirNode* newparent;\n\n        std::string common = commonPathPrefix(f->path);\n        if(common.size()==0) common = \"/\";\n\n        newparent = new RDirNode(0, common);\n        newparent->addNode(this);\n        return newparent->addFile(f);\n    }\n\n    //simply change path of node and add this to it\n    if(   parent==0 && abspath == \"/\"\n       && f->path.compare(abspath) != 0 && noFiles() && noDirs()) {\n        debugLog(\"modifying root path to %s\", f->path.c_str());\n        changePath(f->path);\n    }\n\n    //is this dir - add to this node\n    if(f->path.compare(abspath) == 0) {\n        //debugLog(\"addFile %s to %s\\n\", f->fullpath.c_str(), abspath.c_str());\n\n        files.push_back(f);\n        if(!f->isHidden()) visible_count++;\n        f->setDir(this);\n\n        fileUpdated(false);\n\n        return true;\n    }\n\n    bool added = false;\n\n    //does this belong to one of the children ?\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child =  (*it);\n\n        added = child->addFile(f);\n\n        if(added) break;\n    }\n\n    if(added && parent != 0) return true;\n\n    //do we have a file in this directory thats fullpath is a prefix of this file, if so\n    //that file is actually a directory - the file should be removed, and a directory with that path added\n    //if this is the root node we do this regardless of if the file was added to a child node\n\n    for(std::list<RFile*>::const_iterator it = files.begin(); it != files.end(); it++) {\n        RFile* file = (*it);\n\n        if(f->path.find(file->fullpath) == 0) {\n            //fprintf(stderr, \"removing %s as is actually the directory of %s\\n\", file->fullpath.c_str(), f->fullpath.c_str());\n            file->remove();\n            break;\n        }\n    }\n\n    if(added) return true;\n\n    //add new child, add it to that\n    //if commonpath is longer than abspath, add intermediate node, else just add at the files path\n    RDirNode* node = new RDirNode(this, f->path);\n\n    node->addFile(f);\n\n    addNode(node);\n\n    // do we have dir nodes, with a common path element greater than abspath,\n    // if so create another node, and move those nodes there\n\n     std::string commonpath;\n     vec2 commonPos;\n     for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n         RDirNode* child =  (*it);\n\n         std::string common = child->commonPathPrefix(f->path);\n         if(common.size() > abspath.size() && common != f->path) {\n            commonpath = common;\n            commonPos = child->getPos();\n            break;\n         }\n     }\n\n    // redistribute to new common node\n    if(commonpath.size() > abspath.size()) {\n        //debugLog(\"common path %s\\n\", commonpath.c_str());\n\n        RDirNode* cnode = new RDirNode(this, commonpath);\n        cnode->setPos(commonPos);\n\n        for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end();) {\n            RDirNode* child =  (*it);\n\n            if(child->prefixedBy(commonpath)) {\n                //debugLog(\"this path = %s, commonpath = %s, path = %s\\n\", abspath.c_str(), commonpath.c_str(), child->getPath().c_str());\n                it = children.erase(it);\n                cnode->addNode(child);\n                continue;\n            }\n\n            it++;\n        }\n\n        addNode(cnode);\n    }\n\n    return true;\n}\n\nfloat RDirNode::getParentRadius() const{\n    return parent_radius;\n}\n\nfloat RDirNode::getRadius() const{\n    return dir_radius;\n}\n\nfloat RDirNode::getRadiusSqrt() const{\n    return dir_radius_sqrt;\n}\n\nvec3 RDirNode::averageFileColour() const{\n\n    vec3 av;\n    int count = 0;\n\n    for(std::list<RFile*>::const_iterator it = files.begin(); it != files.end(); it++) {\n        RFile* file = (*it);\n\n        if(file->isHidden()) continue;\n\n        av += file->getColour();\n\n        count++;\n    }\n\n    if(count>0) av *= (1.0f/(float)count);\n\n    count = 0;\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end();it++) {\n            RDirNode* child =  (*it);\n\n            av += child->averageFileColour();\n            count++;\n    }\n\n    if(count>0) av *= (1.0f/(float)count);\n\n    return av;\n}\n\nconst vec4 & RDirNode::getColour() const{\n    return col;\n}\n\nvoid RDirNode::calcColour() {\n\n    // make branch brighter if recently accessed\n    float brightness = std::max(0.6f, 1.0f - std::min(1.0f, since_last_node_change / 3.0f));\n\n    col = vec4(brightness, brightness, brightness, 1.0);\n\n    int fcount = 0;\n\n    for(std::list<RFile*>::iterator it = files.begin(); it != files.end(); it++) {\n        RFile* file = (*it);\n\n        if(file->isHidden()) continue;;\n\n        vec3 filecol = file->getColour() * brightness;\n        float a       = file->getAlpha();\n\n        col += vec4(filecol.x, filecol.y, filecol.z, a);\n\n        fcount++;\n    }\n\n    this->col /= (float) fcount + 1.0;\n}\n\nfloat RDirNode::getArea() const{\n    return dir_area;\n}\n\nvoid RDirNode::calcRadius() {\n\n    float total_file_area = file_area * visible_count;\n\n    dir_area = total_file_area;\n\n    //float parent_circ        = 0.0;\n\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n\n        dir_area += node->getArea();\n    //    parent_circ += node->getRadiusSqrt();\n    }\n\n    this->dir_radius = std::max(1.0f, (float)sqrt(dir_area)) * gGourceDirPadding;\n    //this->dir_radius_sqrt = sqrt(dir_radius); //dir_radius_sqrt is not used\n\n//    this->parent_radius = std::max(1.0, parent_circ / PI);\n    this->parent_radius = std::max(1.0f, (float) sqrt(total_file_area) * gGourceDirPadding);\n}\n\nfloat RDirNode::distanceToParent() const{\n\n    float posd     = glm::length(parent->getPos() - pos);\n    float distance = posd - (dir_radius + parent->getParentRadius());\n\n    return distance;\n}\n\nvoid RDirNode::applyForceDir(RDirNode* node) {\n    if(node == this) return;\n\n    vec2 dir = node->getPos() - pos;\n\n    float posd2       = glm::length2(dir);\n    float myradius    = getRadius();\n    float your_radius = node->getRadius();\n\n    float sumradius = (myradius + your_radius);\n\n    float distance2 = posd2 - sumradius*sumradius;\n\n    if(distance2>0.0) return;\n\n    float posd = sqrt(posd2);\n\n    float distance = posd - myradius - your_radius;\n\n    //resolve overlap\n    if(posd < 0.00001) {\n        accel += normalise(vec2( (rand() % 100) - 50, (rand() % 100) - 50));\n        return;\n    }\n\n    accel += distance * normalise(dir);\n}\n\nconst vec2 & RDirNode::getPos() const{\n    return pos;\n}\n\nbool RDirNode::isParent(RDirNode* node) const {\n    if(node==parent) return true;\n    if(parent==0) return false;\n\n    return parent->isParent(node);\n}\n\nbool RDirNode::empty() const{\n    return (visible_count==0 && noDirs()) ? true : false;\n}\n\nvoid RDirNode::applyForces(QuadTree & quadtree) {\n\n    //child nodes\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n\n        node->applyForces(quadtree);\n    }\n\n    if(parent == 0) return;\n\n    DirForceFunctor dff(this);\n    quadtree.visitItemsInBounds(quadItemBounds, dff);\n    gGourceDirNodeInnerLoops += dff.getLoopCount();\n\n    //always call on parent no matter how far away\n    applyForceDir(parent);\n\n    //pull towards parent\n    float parent_dist = distanceToParent();\n\n    //  * dirs should attract to sit on the radius of the parent dir ie:\n    //    should attract to distance_to_parent * normal_to_parent\n\n\n\n    accel += gGourceForceGravity * parent_dist * normalise(parent->getPos() - pos);\n\n    //  * dirs should be pushed along the parent_parent to parent normal by a force smaller than the parent radius force\n    RDirNode* pparent = parent->getParent();\n\n    if(pparent != 0) {\n        vec2 parent_edge = (parent->getPos() - pparent->getPos());\n        vec2 parent_edge_normal = normalise(parent_edge);\n\n        vec2 dest = (parent->getPos() + (parent->getRadius() + getRadius()) * parent_edge_normal) - pos;\n\n        accel += dest;\n    }\n\n    //  * dirs should repulse from other dirs of this parent\n    const std::list<RDirNode*> & siblings = parent->getChildren();\n    if(!siblings.empty()) {\n        vec2 sib_accel;\n\n        int visible = 1;\n\n        for(std::list<RDirNode*>::const_iterator it = siblings.begin(); it != siblings.end(); it++) {\n            RDirNode* node = (*it);\n\n            if(node == this) continue;\n            if(!node->isVisible()) continue;\n\n            visible++;\n\n            sib_accel -= normalise(node->getPos() - pos);\n        }\n\n        //parent circumfrence divided by the number of visible child nodes\n        if(visible>1) {\n            float slice_size = (parent->getRadius() * PI) / (float) (visible+1);\n            sib_accel *= slice_size;\n\n            accel += sib_accel;\n        }\n    }\n\n}\n\nvoid RDirNode::debug(int indent) const{\n    std::string indentstr;\n    while(indentstr.size() < indent) indentstr += \" \";\n\n    debugLog(\"%s%s\", indentstr.c_str(), abspath.c_str());\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->debug(indent+1);\n    }\n}\n\nint RDirNode::totalFileCount() const{\n    int total = visibleFileCount();\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        total += node->visibleFileCount();\n    }\n\n    return total;\n}\n\nint RDirNode::totalDirCount() const{\n    int total = 1;\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        total += node->totalDirCount();\n    }\n\n    return total;\n}\n\nint RDirNode::dirCount() const{\n    return children.size();\n}\n\nbool RDirNode::noDirs() const{\n    return children.empty();\n}\n\nconst std::list<RDirNode*> & RDirNode::getChildren() const{\n    return children;\n}\n\nvoid RDirNode::updateSplinePoint(float dt) {\n    if(parent == 0) return;\n\n    //update the spline point\n    vec2 td = (parent->getPos() - pos) * 0.5f;\n\n    vec2 mid = pos + td;// - td.perpendicular() * pos.normal();// * 10.0;\n\n    vec2 delta = (mid - spos);\n\n    //dont let spos get more than half the length of the distance behind\n    if(glm::length2(delta) > glm::length2(td)) {\n        spos += normalise(delta) * (glm::length(delta) - glm::length(td));\n    }\n\n    spos += delta * std::min(1.0f, dt * 2.0f);\n}\n\nvoid RDirNode::setInitialPosition() {\n    RDirNode* parentP = parent->getParent();\n\n    pos = parent->getPos();\n\n    //offset position by some pseudo-randomness\n    if(parentP != 0) {\n        //pos += ((parent->getPos() - parentP->getPos()).normal() * 2.0 + vec2Hash(abspath)).normal();\n        pos += normalise(normalise(parent->getPos() - parentP->getPos()) * 2.0f + vec2Hash(abspath));\n\n    }  else {\n        pos += vec2Hash(abspath);\n    }\n\n    //the spline point\n    spos = pos - (parent->getPos() - pos) * 0.5f;\n    position_initialized=true;\n}\n\nvoid RDirNode::move(float dt) {\n\n    //the root node is the centre of the world\n    if(parent == 0) {\n        return;\n    }\n\n    //initial position\n    if(!empty() && !position_initialized) {\n        setInitialPosition();\n    }\n\n    pos += accel * dt;\n\n    if(gGourceSettings.elasticity>0.0) {\n        vec2 diff = (accel - prev_accel);\n\n        float m = dt * gGourceSettings.elasticity;\n\n        vec2 accel3 = prev_accel * (1.0f-m) + diff * m;\n        pos += accel3;\n        prev_accel = accel3;\n    }\n\n    //accel = accel * std::max(0.0f, (1.0f - dt*10.0f));\n    accel = vec2(0.0, 0.0);\n}\n\nconst vec2 & RDirNode::getNodeNormal() const{\n    return node_normal;\n}\n\nvec2 RDirNode::calcFileDest(int max_files, int file_no) {\n\n    float arc   = 1.0/(float)max_files;\n\n    float frac = arc * 0.5 + arc * file_no;\n\n    vec2 dest = vec2(sinf(frac*PI*2.0), cosf(frac*PI*2.0));\n\n    return dest;\n}\n\nvoid RDirNode::updateFilePositions() {\n\n    int max_files = 1;\n    int diameter  = 1;\n    int file_no   = 0;\n    float d = 0.0;\n\n    int files_left = visible_count;\n\n    for(std::list<RFile*>::iterator it = files.begin(); it!=files.end(); it++) {\n        RFile* f = *it;\n\n        if(f->isHidden()) {\n            f->setDest(vec2(0.0,0.0));\n            f->setDistance(0.0f);\n            continue;\n        }\n\n        vec2 dest = calcFileDest(max_files, file_no);\n\n        f->setDest(dest);\n        f->setDistance(d);\n\n        files_left--;\n        file_no++;\n\n        if(file_no>=max_files) {\n            diameter++;\n            d += gGourceFileDiameter;\n            max_files = (int) std::max(1.0, diameter*PI);\n\n            if(files_left<max_files) {\n                max_files = files_left;\n            }\n\n            file_no=0;\n        }\n    }\n}\n\nvoid RDirNode::calcEdges() {\n\n    if(parent != 0) {\n        spline.update(parent->getProjectedPos(), parent->getColour(), projected_pos, col, projected_spos);\n    }\n\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child = *it;\n\n        child->calcEdges();\n    }\n}\n\nvoid RDirNode::logic(float dt) {\n\n    //move\n    move(dt);\n    updateSplinePoint(dt);\n\n    //update node normal\n    if(parent != 0) {\n        node_normal = normalise(pos - parent->getPos());\n    }\n\n    //update files\n     for(std::list<RFile*>::iterator it = files.begin(); it!=files.end(); it++) {\n         RFile* f = *it;\n\n         f->logic(dt);\n     }\n\n    //update child nodes\n    for(std::list<RDirNode*>::iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n\n        node->logic(dt);\n    }\n\n    //update colour\n    calcColour();\n\n    //update tickers\n    if(visible) since_node_visible += dt;\n\n    since_last_file_change += dt;\n    since_last_node_change += dt;\n}\n\nvoid RDirNode::drawDirName(FXFont& dirfont) const{\n    if(parent==0) return;\n    if(gGourceSettings.hide_dirnames) return;\n    if(gGourceSettings.dir_name_depth > 0 && gGourceSettings.dir_name_depth < (depth-1)) return;\n\n    if(!gGourceSettings.highlight_dirs && since_last_node_change > 5.0) return;\n\n    float alpha = gGourceSettings.highlight_dirs ? 1.0 : std::max(0.0f, 5.0f - since_last_node_change) / 5.0f;\n\n    vec2 label_pos = spline.getLabelPos();\n\n    dirfont.setAlpha(alpha);\n    dirfont.draw(label_pos.x, label_pos.y, path_token);\n}\n\nvoid RDirNode::calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection) {\n\n    static GLdouble screen_x, screen_y, screen_z;\n\n    gluProject( pos.x, pos.y, 0.0f, modelview, projection, viewport, &screen_x, &screen_y, &screen_z);\n    screen_y = (float)viewport[3] - screen_y;\n    projected_pos.x = screen_x;\n    projected_pos.y = screen_y;\n\n    gluProject( spos.x, spos.y, 0.0f, modelview, projection, viewport, &screen_x, &screen_y, &screen_z);\n    screen_y = (float)viewport[3] - screen_y;\n    projected_spos.x = screen_x;\n    projected_spos.y = screen_y;\n\n    static vec2 selected_offset(5.5f, -2.0f);\n    static vec2 unselected_offset(5.5f, -1.0f);\n\n    if(!gGourceSettings.hide_filenames) {\n\n        //first pass - calculate positions of names\n        for(std::list<RFile*>::const_iterator it = files.begin(); it!=files.end(); it++) {\n            RFile* f = *it;\n            f->calcScreenPos(viewport, modelview, projection);\n        }\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->calcScreenPos(viewport, modelview, projection);\n    }\n}\n\nvoid RDirNode::drawNames(FXFont& dirfont) {\n\n    if(!gGourceSettings.hide_dirnames && isVisible()) {\n        drawDirName(dirfont);\n    }\n\n    if(!gGourceSettings.hide_filenames) {\n\n        if(!(gGourceSettings.hide_filenames || gGourceSettings.hide_files) && in_frustum) {\n            for(std::list<RFile*>::const_iterator it = files.begin(); it!=files.end(); it++) {\n                RFile* f = *it;\n                if(!f->isSelected()) f->drawName();\n            }\n        }\n\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->drawNames(dirfont);\n    }\n}\n\nvoid RDirNode::checkFrustum(const Frustum& frustum) {\n\n    in_frustum = frustum.intersects(quadItemBounds);\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->checkFrustum(frustum);\n    }\n}\n\nvoid RDirNode::drawShadows(float dt) const{\n\n    if(in_frustum) {\n\n        glPushMatrix();\n        glTranslatef(pos.x, pos.y, 0.0);\n\n        //draw files\n        for(std::list<RFile*>::const_iterator it = files.begin(); it!=files.end(); it++) {\n            RFile* f = *it;\n            if(f->isHidden()) continue;\n\n            f->drawShadow(dt);\n        }\n\n        glPopMatrix();\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->drawShadows(dt);\n    }\n}\n\nvoid RDirNode::updateFilesVBO(quadbuf& buffer, float dt) const{\n\n    if(in_frustum) {\n\n        for(std::list<RFile*>::const_iterator it = files.begin(); it!=files.end(); it++) {\n            RFile* f = *it;\n\n            if(f->isHidden()) continue;\n\n            vec3 col   = f->getColour();\n            float alpha = f->getAlpha();\n\n            buffer.add(f->graphic->textureid, f->getAbsolutePos() - f->dims*0.5f, f->dims, vec4(col.x, col.y, col.z, alpha));\n        }\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->updateFilesVBO(buffer,dt);\n    }\n}\n\nvoid RDirNode::updateBloomVBO(bloombuf& buffer, float dt) {\n\n    if(in_frustum && isVisible()) {\n\n        float bloom_radius   = dir_radius * 2.0 * gGourceSettings.bloom_multiplier;\n        float bloom_diameter = bloom_radius * 2.0;\n        vec4 bloom_col      = col * gGourceSettings.bloom_intensity;\n\n        vec4 bloom_texcoords(bloom_radius, pos.x, pos.y, 0.0f);\n\n        vec2 bloom_dims(bloom_diameter, bloom_diameter);\n\n        buffer.add(0, pos - bloom_dims*0.5f,bloom_dims, vec4(bloom_col.x, bloom_col.y, bloom_col.z, 1.0f), bloom_texcoords);\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->updateBloomVBO(buffer,dt);\n    }\n}\n\nvoid RDirNode::drawFiles(float dt) const{\n\n    if(in_frustum) {\n        glPushMatrix();\n            glTranslatef(pos.x, pos.y, 0.0);\n\n            //draw files\n\n            for(std::list<RFile*>::const_iterator it = files.begin(); it!=files.end(); it++) {\n                RFile* f = *it;\n                if(f->isHidden()) continue;\n\n                f->draw(dt);\n            }\n\n        glPopMatrix();\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->drawFiles(dt);\n    }\n}\n\nconst vec2 & RDirNode::getSPos() const{\n    return projected_spos;\n}\n\nconst vec2 & RDirNode::getProjectedPos() const{\n    return projected_pos;\n}\n\nvoid RDirNode::updateEdgeVBO(quadbuf& buffer) const {\n\n    if(parent!=0 && (!gGourceSettings.hide_root || parent->parent !=0)) spline.drawToVBO(buffer);\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child = (*it);\n\n        if(child->isVisible()) {\n            child->updateEdgeVBO(buffer);\n        }\n    }\n}\n\nvoid RDirNode::drawEdgeShadows() const{\n\n    if(parent!=0 && (!gGourceSettings.hide_root || parent->parent !=0)) spline.drawShadow();\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child = (*it);\n\n        //draw edge - assumes calcEdges() called before hand so spline will exist\n        if(child->isVisible()) {\n           child->drawEdgeShadows();\n        }\n    }\n}\n\nvoid RDirNode::drawEdges() const{\n\n   if(parent!=0 && (!gGourceSettings.hide_root || parent->parent !=0)) spline.draw();\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* child = (*it);\n\n        //draw edge - assumes calcEdges() called before hand so spline will exist\n        if(child->isVisible()) {\n           child->drawEdges();\n        }\n    }\n}\n\nvoid RDirNode::drawBloom(float dt){\n\n    if(in_frustum && isVisible()) {\n\n        float bloom_radius = dir_radius * 2.0 * gGourceSettings.bloom_multiplier;\n\n        vec4 bloom_col = col * gGourceSettings.bloom_intensity;\n\n        glColor4f(bloom_col.x, bloom_col.y, bloom_col.z, 1.0);\n\n        glPushMatrix();\n            glTranslatef(pos.x, pos.y, 0.0);\n\n            glBegin(GL_QUADS);\n                glTexCoord2f(1.0f, 1.0f);\n                glVertex2f(bloom_radius,bloom_radius);\n                glTexCoord2f(1.0f, 0.0f);\n                glVertex2f(bloom_radius,-bloom_radius);\n                glTexCoord2f(0.0f, 0.0f);\n                glVertex2f(-bloom_radius,-bloom_radius);\n                glTexCoord2f(0.0f, 1.0f);\n                glVertex2f(-bloom_radius,bloom_radius);\n            glEnd();\n        glPopMatrix();\n    }\n\n    for(std::list<RDirNode*>::const_iterator it = children.begin(); it != children.end(); it++) {\n        RDirNode* node = (*it);\n        node->drawBloom(dt);\n    }\n}\n\nvoid RDirNode::updateQuadItemBounds() {\n    float radius = getRadius();\n\n    vec2 radoffset(radius, radius);\n\n    //set bounds\n    quadItemBounds.set(pos - radoffset, pos + radoffset);\n}\n\n"
  },
  {
    "path": "src/dirnode.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef RDIRNODE_H\n#define RDIRNODE_H\n\n#include \"core/sdlapp.h\"\n#include \"core/bounds.h\"\n#include \"core/quadtree.h\"\n#include \"core/pi.h\"\n#include \"core/vbo.h\"\n\n#include \"gource_settings.h\"\n\n#include \"spline.h\"\n#include \"file.h\"\n#include \"bloom.h\"\n\n#include <list>\n#include <set>\n\nclass RFile;\n\nclass RDirNode : public QuadItem {\n    std::string abspath;\n\n    std::string path_token;\n    int         path_token_offset;\n\n    RDirNode* parent;\n    std::list<RDirNode*> children;\n    std::list<RFile*> files;\n\n    SplineEdge spline;\n\n    vec4 col;\n\n    vec2 spos;\n\n    vec2 projected_pos;\n    vec2 projected_spos;\n\n    vec2 pos;\n    vec2 vel;\n    vec2 accel, prev_accel;\n\n    float dir_area;\n\n    bool visible;\n    bool in_frustum;\n    bool position_initialized;\n\n    float since_node_visible;\n    float since_last_file_change;\n    float since_last_node_change;\n\n    float file_area;\n    float dir_radius;\n    float dir_radius_sqrt;\n    float parent_radius;\n\n    int depth;\n\n    int visible_count;\n\n    vec3 screenpos;\n    vec2 node_normal;\n\n    void calcRadius();\n    void calcColour();\n\n    std::string commonPathPrefix(const std::string& str) const;\n\n    void changePath(const std::string & abspath);\n\n    void setInitialPosition();\n\n    void drawEdge(RDirNode* child) const;\n    void updateSplinePoint(float dt);\n    void move(float dt);\n\n    vec2 calcFileDest(int layer_no, int file_no);\n    void updateFilePositions();\n\n    void adjustDepth();\n    void adjustPath();\n    void drawDirName(FXFont& dirfont) const;\npublic:\n    RDirNode(RDirNode* parent, const std::string & abspath);\n    ~RDirNode();\n\n    void printFiles();\n\n    bool empty() const;\n\n    bool isAnchor(RDirNode* node) const;\n\n    RDirNode* getRoot();\n\n    void fileUpdated(bool userInitiated);\n    void nodeUpdated(bool userInitiated);\n\n    void addVisible();\n    bool isVisible();\n\n    float getArea() const;\n\n    int totalDirCount() const;\n    int totalFileCount() const;\n\n    int getTokenOffset() const;\n\n    int dirCount() const;\n    int fileCount() const;\n    int visibleFileCount() const;\n    bool noDirs() const;\n    bool noFiles() const;\n\n    bool prefixedBy(const std::string & path) const;\n\n    const std::string & getPath() const;\n\n    const vec2 & getNodeNormal() const;\n\n    bool isParent(RDirNode* node) const;\n\n    bool addFile(RFile* f);\n    bool removeFile(RFile* f);\n\n    int getDepth() const;\n\n    const std::list<RDirNode*> & getChildren() const;\n\n    void updateQuadItemBounds();\n\n    float getParentRadius() const;\n    float getRadius() const;\n    float getRadiusSqrt() const;\n\n    const std::list<RFile*>* getFiles() const { return &files; };\n    void getFilesRecursive(std::list<RFile*>& files) const;\n\n    vec3 averageFileColour() const;\n\n    const vec4 & getColour() const;\n\n    RDirNode* getParent() const;\n\n    bool isDir(const std::string& path) const;\n    void findDirs(const std::string& path, std::list<RDirNode*>& dirs);\n\n    const vec2 & getPos() const;\n\n    void calcEdges();\n\n    const vec2 & getProjectedPos() const;\n    const vec2 & getSPos() const;\n\n    void setPos(const vec2 & pos);\n\n    void rotate(float s, float c);\n    void rotate(float s, float c, const vec2& centre);\n\n    void setParent(RDirNode* parent);\n\n    float distanceToParent() const;\n\n    void addNode(RDirNode* node);\n\n    void debug(int indent=0) const;\n\n    void applyForceDir(RDirNode* dir);\n    void applyForces(QuadTree &quadtree);\n\n    void logic(float dt);\n\n    void updateEdgeVBO(quadbuf& buffer) const;\n    \n    void drawEdges() const;\n    void drawEdgeShadows() const;\n\n    void checkFrustum(const Frustum & frustum);\n\n    void updateFilesVBO(quadbuf& buffer, float dt) const;\n    void updateBloomVBO(bloombuf& buffer, float dt);\n\n    void drawShadows(float dt) const;\n    void drawFiles(float dt) const;\n    void drawBloom(float dt);\n\n    void drawNames(FXFont& dirfont);\n\n    void calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection);\n\n    void nodeCount() const;\n};\n\nclass DirForceFunctor : public VisitFunctor<QuadItem>{\n  private:\n    RDirNode * this_dir;\n    std::set<RDirNode*> seen;\n    size_t loopCount;\n\n  public:\n    DirForceFunctor(RDirNode * dir) : this_dir(dir), seen(), loopCount(0){}\n    int getLoopCount() const{ return loopCount; }\n    void operator()(QuadItem * item){\n\n        std::set<RDirNode*>::iterator seentest;\n        RDirNode* d = (RDirNode*) (item);\n\n        if(d==this_dir) return;\n        if(d==this_dir->getParent()) return;\n        if(d->getParent() == this_dir) return;\n        if(this_dir->isParent(d)) return;\n        if(d->isParent(this_dir)) return;\n\n        if(d->node_count != 1) {\n            if((seentest = seen.find(d)) != seen.end())\n                return;\n\n            seen.insert(d);\n        }\n\n        this_dir->applyForceDir(d);\n\n        loopCount++;\n\n    }\n\n};\n\nextern int gGourceDirNodeInnerLoops;\nextern int gGourceFileInnerLoops;\n\nextern float gGourcePointSize;\nextern bool  gGourceNodeDebug;\nextern bool  gGourceGravity;\nextern float gGourceForceGravity;\n\nextern std::map<std::string, RDirNode*> gGourceDirMap;\n\n#endif\n"
  },
  {
    "path": "src/file.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"file.h\"\n\nfloat gGourceFileDiameter  = 8.0;\n\nstd::vector<RFile*> gGourceRemovedFiles;\n\nFXFont file_selected_font;\nFXFont file_font;\n\nRFile::RFile(const std::string & name, const vec3 & colour, const vec2 & pos, int tagid) : Pawn(name,pos,tagid) {\n    hidden = true;\n    size = gGourceFileDiameter * 1.05;\n    radius = size * 0.5;\n\n    setGraphic(gGourceSettings.file_graphic);\n\n    speed = 5.0;\n    nametime = gGourceSettings.filename_time;\n    name_interval = nametime;\n\n    namecol     = vec3(1.0, 1.0, 1.0);\n    file_colour = colour;\n\n    last_action    = 0.0f;\n    fade_start     = -1.0f;\n    removed_timestamp = 0;\n    expired        = false;\n    forced_removal = false;\n    removing       = false;\n\n    shadow = true;\n\n    distance = 0;\n\n    setFilename(name);\n\n    if(!file_selected_font.initialized()) {\n        file_selected_font = fontmanager.grab(gGourceSettings.font_file, 18);\n        file_selected_font.dropShadow(true);\n        file_selected_font.roundCoordinates(false);\n        file_selected_font.setColour(vec4(gGourceSettings.selection_colour, 1.0f));\n    }\n\n    if(!file_font.initialized()) {\n        file_font = fontmanager.grab(gGourceSettings.font_file, gGourceSettings.scaled_filename_font_size);\n        file_font.dropShadow(true);\n        file_font.roundCoordinates(false);\n        file_font.setColour(vec4(gGourceSettings.filename_colour, 1.0f));\n    }\n\n    setSelected(false);\n\n    dir = 0;\n}\n\nRFile::~RFile() {\n}\n\nvoid RFile::remove(time_t removed_timestamp) {\n    last_action = elapsed;\n    fade_start  = elapsed;\n    removing = true;\n    this->removed_timestamp = removed_timestamp;\n}\n\nvoid RFile::remove() {\n    forced_removal = true;\n    remove(0);\n}\n\nvoid RFile::setDir(RDirNode* dir) {\n    this->dir = dir;\n}\n\nRDirNode* RFile::getDir() const{\n    return dir;\n}\n\nvec2 RFile::getAbsolutePos() const{\n    return pos + dir->getPos();\n}\n\nbool RFile::overlaps(const vec2& pos) const {\n\n    vec2 abs_pos = getAbsolutePos();\n\n    float halfsize_x = size * 0.5f;\n    vec2 halfsize ( halfsize_x, halfsize_x * graphic_ratio );\n\n    Bounds2D file_bounds(abs_pos - halfsize, abs_pos + halfsize);\n\n    return file_bounds.contains(pos);\n}\n\nvoid RFile::setFilename(const std::string& abs_file_path) {\n\n    fullpath = abs_file_path;\n\n    size_t pos = fullpath.rfind('/');\n\n    if(pos != std::string::npos) {\n        path = name.substr(0,pos+1);\n        name = name.substr(pos+1, std::string::npos);\n    } else {\n        path = std::string(\"\");\n        name = abs_file_path;\n    }\n\n    //trim name to just extension\n    size_t dotsep = name.rfind(\".\");\n\n    if(dotsep != std::string::npos && dotsep != name.size()-1) {\n        ext = name.substr(dotsep+1);\n    } else if(gGourceSettings.file_extension_fallback) {\n        ext = name;\n    }\n}\n\nvoid RFile::colourize() {\n    file_colour = ext.size() ? colourHash(ext) : vec3(1.0f, 1.0f, 1.0f);\n}\n\nconst vec3& RFile::getNameColour() const{\n    return selected ? gGourceSettings.selection_colour : namecol;\n}\n\nvoid RFile::setFileColour(const vec3 & colour) {\n    file_colour = colour;\n}\n\nconst vec3 & RFile::getFileColour() const{\n    return file_colour;\n}\n\nvec3 RFile::getColour() const{\n    if(selected) return vec3(1.0f);\n\n    float lc = elapsed - last_action;\n\n    if(lc<1.0f) {\n        return touch_colour * (1.0f-lc) + file_colour * lc;\n    }\n\n    return file_colour;\n}\n\nfloat RFile::getAlpha() const{\n    float alpha = Pawn::getAlpha();\n\n    //user fades out if not doing anything\n    if(fade_start > 0.0f) {\n        alpha = 1.0 - glm::clamp(elapsed - fade_start, 0.0f, 1.0f);\n    }\n\n    return alpha;\n}\n\nvoid RFile::logic(float dt) {\n    Pawn::logic(dt);\n\n    vec2 dest_pos = dest;\n/*\n    if(dir->getParent() != 0 && dir->noDirs()) {\n        vec2 dirnorm = dir->getNodeNormal();\n        dest_pos = dirnorm + dest;\n    }*/\n\n    dest_pos = dest_pos * distance;\n\n    accel = dest_pos - pos;\n\n    // apply accel\n    vec2 accel2 = accel * speed * dt;\n\n    if(glm::length2(accel2) > glm::length2(accel)) {\n        accel2 = accel;\n    }\n\n    pos += accel2;\n\n    //files have no momentum\n    accel = vec2(0.0f, 0.0f);\n\n    if(fade_start < 0.0f && gGourceSettings.file_idle_time > 0.0f && (elapsed - last_action) > gGourceSettings.file_idle_time) {\n        fade_start = elapsed;\n    }\n    \n    // has completely faded out\n    if(fade_start > 0.0f && !expired && (elapsed - fade_start) >= 1.0) {\n\n        expired = true;\n\n        bool found = false;\n        for(std::vector<RFile*>::iterator it = gGourceRemovedFiles.begin(); it != gGourceRemovedFiles.end(); it++) {\n            if((*it) == this) {\n                found = true;\n                break;\n            }\n        }\n\n        if(!found) {\n            gGourceRemovedFiles.push_back(this);\n            //fprintf(stderr, \"expiring %s\\n\", fullpath.c_str());\n        }\n    }\n\n    if(isHidden() && !forced_removal) elapsed = 0.0;\n}\n\nvoid RFile::touch(time_t touched_timestamp, const vec3 & colour) {\n    if(forced_removal || (removing && touched_timestamp < removed_timestamp)) return;\n\n    //fprintf(stderr, \"touch %s\\n\", fullpath.c_str());\n\n    fade_start = -1.0f;\n    removing = false;\n    removed_timestamp = 0;\n    last_action = elapsed;\n    touch_colour = colour;\n\n    //un expire file if touched after being removed\n    if(expired) {\n        for(std::vector<RFile*>::iterator it = gGourceRemovedFiles.begin(); it != gGourceRemovedFiles.end(); it++) {\n            if((*it) == this) {\n                gGourceRemovedFiles.erase(it);\n                break;\n            }\n        }\n        expired=false;\n    }\n\n    showName();\n    setHidden(false);\n    dir->fileUpdated(true);\n}\n\nvoid RFile::setHidden(bool hidden) {\n    if(this->hidden==true && hidden==false && dir !=0) {\n        dir->addVisible();\n    }\n\n    Pawn::setHidden(hidden);\n}\n\nvoid RFile::calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection) {\n\n    static GLdouble screen_x, screen_y, screen_z;\n\n    vec2 text_pos = getAbsolutePos();\n    text_pos.x += 5.5f;\n\n    if(selected)\n        text_pos.y -= 2.0f;\n    else\n        text_pos.y -= 1.0f;\n\n    gluProject( text_pos.x, text_pos.y, 0.0f, modelview, projection, viewport, &screen_x, &screen_y, &screen_z);\n    screen_y = (float)viewport[3] - screen_y;\n\n    screenpos.x = screen_x;\n    screenpos.y = screen_y;\n}\n\nvoid RFile::drawNameText(float alpha) {\n    if(!selected && alpha <= 0.01) return;\n\n    float name_alpha = selected ? 1.0 : alpha;\n\n    if(selected) {\n        file_selected_font.draw(screenpos.x, screenpos.y, name);\n    } else {\n        file_font.setAlpha(name_alpha);\n        file_font.draw(screenpos.x, screenpos.y, gGourceSettings.file_extensions ? ext : name);\n    }\n}\n\nvoid RFile::draw(float dt) {\n    Pawn::draw(dt);\n\n    glLoadName(0);\n}\n"
  },
  {
    "path": "src/file.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef RFILE_H\n#define RFILE_H\n\n#include \"pawn.h\"\n#include \"dirnode.h\"\n#include \"core/stringhash.h\"\n\nclass RDirNode;\n\nclass RFile : public Pawn {\n    vec3 file_colour;\n    vec3 touch_colour;\n\n    RDirNode* dir;\n\n    time_t removed_timestamp;\n    bool forced_removal;\n    bool expired;\n    bool removing;\n\n    float fade_start;\n    \n    float last_action;\n\n    float radius;\n\n    vec2 dest;\n    float distance;\n\n   // FXLabel* label;\n\n    //GLuint namelist;\n\n    void setFilename(const std::string& abs_file_path);\n\n    const vec3& getNameColour() const;\n    void drawNameText(float alpha);\npublic:\n    std::string path;\n    std::string fullpath;\n    std::string ext;\n\n    RFile(const std::string & name, const vec3 & colour, const vec2 & pos, int tagid);\n    ~RFile();\n\n    bool overlaps(const vec2& pos) const;\n\n    void setFileColour(const vec3 & colour);\n    const vec3 & getFileColour() const;\n    vec3 getColour() const;\n    void colourize();\n\n    float getAlpha() const;\n\n    void touch(time_t touch_timestamp, const vec3& colour);\n\n    void setHidden(bool hidden);\n\n    void setDest(const vec2 & dest){ this->dest = dest; }\n    void setDistance(float distance){ this->distance = distance; }\n\n    void calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection);\n\n    void logic(float dt);\n    void draw(float dt);\n\n    void remove(time_t removed_timestamp);\n    void remove();\n\n    vec2 getAbsolutePos() const;\n\n    RDirNode* getDir() const;\n    void setDir(RDirNode* dir);\n};\n\nextern float gGourceFileDiameter;\n\nextern std::vector<RFile*> gGourceRemovedFiles;\n\n#endif\n"
  },
  {
    "path": "src/formats/apache.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"apache.h\"\n#include <time.h>\n\nconst char* months[] = {\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\" , \"Sep\", \"Oct\", \"Nov\", \"Dec\" };\nRegex apache_entry_start(\"^(?:[^ ]+ )?([^ ]+) +[^ ]+ +([^ ]+) +\\\\[(.*?)\\\\] +(.*)$\");\nRegex apache_entry_date(\"(\\\\d+)/([A-Za-z]+)/(\\\\d+):(\\\\d+):(\\\\d+):(\\\\d+) ([+-])(\\\\d+)\");\nRegex apache_entry_request(\"\\\"([^ ]+) +([^ ]+) +([^ ]+)\\\" +([^ ]+) +([^\\\\s+]+)(.*)\");\nRegex apache_entry_agent(\" +\\\"([^\\\"]+)\\\" +\\\"([^\\\"]+)\\\" +\\\"([^\\\"]+)\\\"\");\nRegex apache_hostname_parts(\"([^.]+)(?:\\\\.([^.]+))?(?:\\\\.([^.]+))?(?:\\\\.([^.]+))?(?:\\\\.([^.]+))?(?:\\\\.([^.]+))?(?:\\\\.([^.]+))?(?:\\\\.([^.]+))?$\");\n\nApacheCombinedLog::ApacheCombinedLog(const std::string& logfile) : RCommitLog(logfile) {\n}\n\n//parse apache access.log entry into components\nbool ApacheCombinedLog::parseCommit(RCommit& commit) {\n\n    std::string line;\n    std::vector<std::string> matches;\n\n    if(!logf->getNextLine(line)) return false;\n\n    apache_entry_start.match(line, &matches);\n\n    if(matches.size()!=4) {\n        return 0;\n    }\n\n    //get details\n    commit.username = matches[0];\n\n    std::string request_str = matches[3];\n    std::string datestr     = matches[2];\n\n    apache_entry_date.match(datestr, &matches);\n\n    if(matches.size()!=8) {\n        return 0;\n    }\n\n    //parse timestamp\n    int day    = atoi(matches[0].c_str());\n    int year   = atoi(matches[2].c_str());\n    int hour   = atoi(matches[3].c_str());\n    int minute = atoi(matches[4].c_str());\n    int second = atoi(matches[5].c_str());\n\n    int month=0;\n    for(int i=0;i<12;i++) {\n        if(matches[1] == months[i]) {\n            month=i;\n            break;\n        }\n    }\n\n    struct tm time_str;\n    time_str.tm_year = year - 1900;\n    time_str.tm_mon  = month;\n    time_str.tm_mday = day;\n    time_str.tm_hour = hour;\n    time_str.tm_min = minute;\n    time_str.tm_sec = second;\n    time_str.tm_isdst = -1;\n\n    commit.timestamp = mktime(&time_str);\n\n    matches.clear();\n    apache_entry_request.match(request_str, &matches);\n\n    if(matches.size() < 5) {\n        return false;\n    }\n\n    std::string rtype = matches[0];\n    std::string file  = matches[1];\n    std::string proto = matches[2];\n\n    int code      = atoi(matches[3].c_str());\n    int bytes     = atol(matches[4].c_str());\n\n    //remove args from url\n    size_t argpos = file.rfind(\"?\");\n    if(argpos != std::string::npos) {\n        file = file.substr(0,argpos);\n    }\n\n    if(file.size()==0) file = \"/\";\n\n   //name index pages\n    if(file[file.size()-1] == '/') {\n        file += \"index.html\";\n    }\n\n    std::string action = \"A\";\n    commit.addFile(file, action);\n\n    std::string refer;\n    std::string agent;\n\n    if(matches.size() > 5) {\n        std::string agentstr = matches[5];\n        matches.clear();\n        apache_entry_agent.match(agentstr, &matches);\n\n        if(matches.size()>1) {\n            refer     = matches[0];\n            agent     = matches[1];\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/apache.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GOURCE_APACHE_H\n#define GOURCE_APACHE_H\n\n#include <string>\n\n#include \"commitlog.h\"\n\nclass ApacheCombinedLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n    BaseLog* generateLog(const std::string& dir);\npublic:\n    ApacheCombinedLog(const std::string& logfile);\n};\n\n#endif\n"
  },
  {
    "path": "src/formats/bzr.cpp",
    "content": "/*\n    Copyright (C) 2010 John Arbash Meinel <john@arbash-meinel.com>\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"bzr.h\"\n#include \"../gource_settings.h\"\n\n#include <boost/format.hpp>\n\nRegex bzr_commit_regex(\"^ *([\\\\d.]+) (.+)\\t(\\\\d{4})-(\\\\d+)-(\\\\d+)(?: \\\\{[^}]+})?(?: \\\\[merge\\\\])?$\");\nRegex bzr_file_regex(\"^ *([AMDR])  (.*[^/])$\");\n\n// parse Bazaar log entries (using the gource.style template)\n\nstd::string BazaarLog::logCommand() {\n\n    std::string start = (!gGourceSettings.start_date.empty()) ? \"date:\"+gGourceSettings.start_date : \"1\";\n    std::string stop  = (!gGourceSettings.stop_date.empty())  ? \"date:\"+gGourceSettings.stop_date  : \"-1\";\n\n    std::string range = str(boost::format(\"%s..%s\") % start % stop);\n\n    std::string log_command = str(boost::format(\"bzr log --verbose -r %s --short -n0 --forward\") % range);\n\n    return log_command;\n}\n\nBazaarLog::BazaarLog(const std::string& logfile) : RCommitLog(logfile) {\n\n    log_command = logCommand();\n\n    //can generate log from directory\n    if(!logf && is_dir) {\n        logf = generateLog(logfile);\n\n        if(logf) {\n            success  = true;\n            seekable = true;\n        }\n    }\n}\n\nBaseLog* BazaarLog::generateLog(const std::string& dir) {\n\n    //does directory have a .bzr ?\n    std::string bzrdir = dir + std::string(\"/.bzr\");\n    struct stat dirinfo;\n    int stat_rc = stat(bzrdir.c_str(), &dirinfo);\n    if(stat_rc!=0 || !(dirinfo.st_mode & S_IFDIR)) {\n        return 0;\n    }\n\n    std::string command = getLogCommand();\n\n    // do we have this client installed\n    requireExecutable(\"bzr\");\n\n    createTempLog();\n\n    if(temp_file.size()==0) return 0;\n\n    char cmd_buff[2048];\n    snprintf(cmd_buff, 2048, \"%s %s > %s\", command.c_str(), dir.c_str(), temp_file.c_str());\n\n    int command_rc = systemCommand(cmd_buff);\n\n    if(command_rc != 0) {\n        return 0;\n    }\n\n    BaseLog* seeklog = new SeekLog(temp_file);\n\n    return seeklog;\n}\n\nbool BazaarLog::parseCommit(RCommit& commit) {\n\n    std::string line;\n    std::vector<std::string> entries;\n    int year, month, day;\n\n    if(!logf->getNextLine(line)) return false;\n\n    if (!bzr_commit_regex.match(line, &entries)) {\n        //debugLog(\"regex failed\\n\");\n        return false;\n    }\n\n    commit.username = entries[1];\n\n    year  = atoi(entries[2].c_str());\n    month = atoi(entries[3].c_str());\n    day   = atoi(entries[4].c_str());\n\n    struct tm time_str;\n\n    time_str.tm_year  = year - 1900;\n    time_str.tm_mon   = month - 1;\n    time_str.tm_mday  = day;\n    time_str.tm_hour  = 0;\n    time_str.tm_min   = 0;\n    time_str.tm_sec   = 0;\n    time_str.tm_isdst = -1;\n\n    commit.timestamp = mktime(&time_str);\n\n    while(logf->getNextLine(line) && line.size()) {\n        if (!bzr_file_regex.match(line, &entries)) continue;\n        commit.addFile(entries[1], entries[0]);\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/bzr.h",
    "content": "/*\n    Copyright (C) 2010 John Arbash Meinel <john@arbash-meinel.com>\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef BAZAARLOG_H\n#define BAZAARLOG_H\n\n#include \"commitlog.h\"\n\nclass BazaarLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n    BaseLog* generateLog(const std::string& dir);\npublic:\n    BazaarLog(const std::string& logfile);\n\n    static std::string logCommand();    \n};\n\n#endif\n\n"
  },
  {
    "path": "src/formats/commitlog.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"commitlog.h\"\n#include \"../gource_settings.h\"\n#include \"../core/sdlapp.h\"\n\n#include \"../core/utf8/utf8.h\"\n\nstd::string RCommitLog::filter_utf8(const std::string& str) {\n\n    std::string filtered;\n\n    try {\n        utf8::replace_invalid(str.begin(), str.end(), back_inserter(filtered), '?');\n    }\n    catch(...) {\n        filtered = \"???\";\n    }\n\n    return filtered;\n}\n\n//RCommitLog\n\nRCommitLog::RCommitLog(const std::string& logfile, int firstChar) {\n\n    logf     = 0;\n    seekable = false;\n    success  = false;\n    is_dir   = false;\n    buffered = false;\n\n    if(logfile == \"-\") {\n\n        //check first char\n        if(checkFirstChar(firstChar, std::cin)) {\n            logf     = new StreamLog();\n            is_dir   = false;\n            seekable = false;\n            success  = true;\n        }\n\n        return;\n    }\n\n    struct stat fileinfo;\n    int rc = stat(logfile.c_str(), &fileinfo);\n\n    if(rc==0) {\n        is_dir = (fileinfo.st_mode & S_IFDIR) ? true : false;\n\n        if(!is_dir) {\n\n            //check first char\n            std::ifstream testf(logfile.c_str());\n\n            bool firstOK = checkFirstChar(firstChar, testf);\n\n            testf.close();\n\n            if(firstOK) {\n                logf = new SeekLog(logfile);\n                seekable = true;\n                success = true;\n            }\n        }\n    }\n}\n\nRCommitLog::~RCommitLog() {\n    if(logf!=0) delete logf;\n\n    if(!temp_file.empty()) {\n        remove(temp_file.c_str());\n    }\n}\n\nint RCommitLog::systemCommand(const std::string& command) {\n    int rc = system(command.c_str());\n    return rc;\n}\n\n// TODO: implement check for 'nix OSs\nvoid RCommitLog::requireExecutable(const std::string& exename) {\n\n#ifdef _WIN32\n    TCHAR exePath[MAX_PATH];\n    DWORD result = SearchPath(0, exename.c_str(), \".exe\", MAX_PATH, exePath, 0);\n\n    if(result) return;\n\n    throw SDLAppException(\"unable to find %s.exe\", exename.c_str());\n#endif\n}\n\n//check firstChar of stream is as expected. if no firstChar defined just returns true.\nbool RCommitLog::checkFirstChar(int firstChar, std::istream& stream) {\n\n    //cant check this\n    if(firstChar == -1) return true;\n\n    int c = stream.peek();\n\n    if(firstChar == c) return true;\n\n    return false;\n}\n\nbool RCommitLog::checkFormat() {\n    if(!success) return false;\n\n    //read a commit to see if the log is in the correct format\n    if(nextCommit(lastCommit, false)) {\n\n        if(seekable) {\n            //if the log is seekable, go back to the start\n            ((SeekLog*)logf)->seekTo(0.0);\n            lastline.clear();\n        } else {\n            //otherwise set the buffered flag as we have bufferd one commit\n            buffered = true;\n        }\n\n        return true;\n    }\n\n    return false;\n}\n\nstd::string RCommitLog::getLogCommand() {\n    return log_command;\n}\n\nbool RCommitLog::isSeekable() {\n    return seekable;\n}\n\nbool RCommitLog::getCommitAt(float percent, RCommit& commit) {\n    if(!seekable) return false;\n\n    SeekLog* seeklog = ((SeekLog*)logf);\n\n    //save settings\n    long currpointer = seeklog->getPointer();\n    std::string currlastline = lastline;\n\n    seekTo(percent);\n    bool success = findNextCommit(commit,500);\n\n    //restore settings\n    seeklog->setPointer(currpointer);\n    lastline = currlastline;\n\n    return success;\n}\n\nbool RCommitLog::getNextLine(std::string& line) {\n    if(!lastline.empty()) {\n        line = lastline;\n        lastline.clear();\n        return true;\n    }\n\n    return logf->getNextLine(line);\n}\n\n\nvoid RCommitLog::seekTo(float percent) {\n    if(!seekable) return;\n\n    lastline.clear();\n\n    ((SeekLog*)logf)->seekTo(percent);\n}\n\nfloat RCommitLog::getPercent() {\n    if(seekable) return ((SeekLog*)logf)->getPercent();\n\n    return 0.0;\n}\n\nbool RCommitLog::findNextCommit(RCommit& commit, int attempts) {\n\n    for(int i=0;i<attempts;i++) {\n        RCommit c;\n\n        if(nextCommit(c)) {\n            commit = c;\n            return true;\n        }\n    }\n\n    return false;\n}\n\nvoid RCommitLog::bufferCommit(RCommit& commit) {\n    lastCommit = commit;\n    buffered = true;\n}\n\nbool RCommitLog::nextCommit(RCommit& commit, bool validate) {\n\n    if(buffered) {\n        commit = lastCommit;\n        buffered = false;\n        return true;\n    }\n\n    // ensure commit is re-initialized\n    commit = RCommit();\n\n    bool success = parseCommit(commit);\n\n    if(!success) return false;\n\n    commit.postprocess();\n\n    if(validate) return commit.isValid();\n\n    return true;\n}\n\nbool RCommitLog::isFinished() {\n    if(seekable && logf->isFinished()) return true;\n\n    return false;\n}\n\nbool RCommitLog::hasBufferedCommit() {\n    return buffered;\n}\n\n//create temp file\nbool RCommitLog::createTempLog() {\n    return createTempFile(temp_file);\n}\n\nbool RCommitLog::createTempFile(std::string& temp_file) {\n\n    std::string tempdir;\n\n#ifdef _WIN32\n    DWORD tmplen = GetTempPath(0, 0);\n\n    if(tmplen == 0) return false;\n\n    std::vector<TCHAR> temp(tmplen+1);\n\n    tmplen = GetTempPath(static_cast<DWORD>(temp.size()), &temp[0]);\n\n    if(tmplen == 0 || tmplen >= temp.size()) return false;\n\n    tempdir = std::string(temp.begin(), temp.begin() + static_cast<std::size_t>(tmplen));\n    tempdir += \"\\\\\";\n#else\n    tempdir = \"/tmp/\";\n#endif\n\n    char tmplate[1024];\n    snprintf(tmplate, 1024, \"%sgource-XXXXXX\", tempdir.c_str());\n\n#ifdef _WIN32\n    if(mktemp(tmplate) == NULL) return false;\n#else\n    if(mkstemp(tmplate) < 0) return false;\n#endif\n\n    temp_file = std::string(tmplate);\n\n    return true;\n}\n\n// RCommitFile\n\nRCommitFile::RCommitFile(const std::string& filename, const std::string& action, vec3 colour) {\n\n    this->filename = RCommitLog::filter_utf8(filename);\n\n    //prepend a root slash\n    if(this->filename[0] != '/') {\n        this->filename.insert(0, 1, '/');\n    }\n\n    this->action   = action;\n    this->colour   = colour;\n}\n\nRCommit::RCommit() {\n    timestamp = 0;\n}\n\nvec3 RCommit::fileColour(const std::string& filename) {\n\n    size_t slash = filename.rfind('/');\n    size_t dot   = filename.rfind('.');\n\n    if(dot != std::string::npos && dot+1<filename.size() && (slash == std::string::npos || slash < dot)) {\n        std::string file_ext = filename.substr(dot+1);\n\n        return colourHash(file_ext);\n    } else {\n        return vec3(1.0, 1.0, 1.0);\n    }\n}\n\nvoid RCommit::addFile(const std::string& filename, const std::string& action) {\n    addFile(filename, action, fileColour(filename));\n}\n\nvoid RCommit::addFile(const std::string& filename, const  std::string& action, const vec3& colour) {\n    //check filename against filters\n    if(!gGourceSettings.file_filters.empty()) {\n\n        for(std::vector<Regex*>::iterator ri = gGourceSettings.file_filters.begin(); ri != gGourceSettings.file_filters.end(); ri++) {\n            Regex* r = *ri;\n\n            if(r->match(filename)) {\n                return;\n            }\n        }\n    }\n\n    // Only allow files that have been whitelisted\n    if(!gGourceSettings.file_show_filters.empty()) {\n\n        for(std::vector<Regex*>::iterator ri = gGourceSettings.file_show_filters.begin(); ri != gGourceSettings.file_show_filters.end(); ri++) {\n            Regex* r = *ri;\n\n            if(!r->match(filename)) {\n                return;\n            }\n        }\n    }\n\n    files.push_back(RCommitFile(filename, action, colour));\n}\n\nvoid RCommit::postprocess() {\n    username = RCommitLog::filter_utf8(username);\n}\n\nbool RCommit::isValid() {\n\n    //check user against filters, if found, discard commit\n    if(!gGourceSettings.user_filters.empty()) {\n        for(std::vector<Regex*>::iterator ri = gGourceSettings.user_filters.begin(); ri != gGourceSettings.user_filters.end(); ri++) {\n            Regex* r = *ri;\n\n            if(r->match(username)) {\n                return false;\n            }\n        }\n    }\n\n    // Only allow users that have been whitelisted\n    if(!gGourceSettings.user_show_filters.empty()) {\n\n        for(std::vector<Regex*>::iterator ri = gGourceSettings.user_show_filters.begin(); ri != gGourceSettings.user_show_filters.end(); ri++) {\n            Regex* r = *ri;\n\n            if(!r->match(username)) {\n                return false;\n            }\n        }\n    }\n\n\n    return !files.empty();\n}\n\nvoid RCommit::debug() {\n    debugLog(\"files:\\n\");\n\n    for(std::list<RCommitFile>::iterator it = files.begin(); it != files.end(); it++) {\n        RCommitFile f = *it;\n        debugLog(\"%s %s\\n\", f.action.c_str(), f.filename.c_str());\n    }\n}\n"
  },
  {
    "path": "src/formats/commitlog.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef RCOMMIT_LOG_H\n#define RCOMMIT_LOG_H\n\n\n#include \"../core/seeklog.h\"\n#include \"../core/display.h\"\n#include \"../core/regex.h\"\n#include \"../core/stringhash.h\"\n\n#include <time.h>\n#include <string>\n#include <list>\n\n#include \"sys/stat.h\"\n\nclass RCommitFile {\npublic:\n    std::string filename;\n    std::string action;\n    vec3 colour;\n\n    RCommitFile(const std::string& filename, const std::string& action, vec3 colour);\n};\n\nclass RCommit {\n    vec3 fileColour(const std::string& filename);\npublic:\n    time_t timestamp;\n    std::string username;\n\n    std::list<RCommitFile> files;\n\n    void postprocess();\n    bool isValid();\n\n    void addFile(const std::string& filename, const std::string& action);\n    void addFile(const std::string& filename, const std::string& action, const vec3& colour);\n\n    RCommit();\n    void debug();\n    virtual bool parse(BaseLog* logf) { return false; };\n};\n\nclass RCommitLog {\nprotected:\n    BaseLog* logf;\n\n    std::string temp_file;\n    std::string log_command;\n\n    std::string lastline;\n\n    bool is_dir;\n    bool success;\n    bool seekable;\n\n    RCommit lastCommit;\n    bool buffered;\n\n    bool checkFirstChar(int firstChar, std::istream& stream);\n\n    bool createTempLog();\n    static bool createTempFile(std::string& temp_file);\n\n    bool getNextLine(std::string& line);\n\n    virtual bool parseCommit(RCommit& commit) { return false; };\npublic:\n    RCommitLog(const std::string& logfile, int firstChar = -1);\n    virtual ~RCommitLog();\n\n    static std::string filter_utf8(const std::string& str);\n\n    void seekTo(float percent);\n\n    bool checkFormat();\n\n    std::string getLogCommand();\n\n    static int systemCommand(const std::string& command);\n    void requireExecutable(const std::string& exename);\n\n    void bufferCommit(RCommit& commit);\n\n    bool getCommitAt(float percent, RCommit& commit);\n    bool findNextCommit(RCommit& commit, int attempts);\n    bool nextCommit(RCommit& commit, bool validate = true);\n    bool hasBufferedCommit();\n    bool isFinished();\n    bool isSeekable();\n    float getPercent();\n};\n\n#endif\n"
  },
  {
    "path": "src/formats/custom.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"custom.h\"\n#include \"../gource_settings.h\"\n\nRegex custom_regex(\"^(?:\\\\xEF\\\\xBB\\\\xBF)?([^|]+)\\\\|([^|]*)\\\\|([ADM]?)\\\\|([^|]+)(?:\\\\|#?([a-fA-F0-9]{6}))?\");\n\nCustomLog::CustomLog(const std::string& logfile) : RCommitLog(logfile) {\n}\n\nvec3 CustomLog::parseColour(const std::string& cstr) {\n\n    vec3 colour;\n    int r,g,b;\n\n    if(sscanf(cstr.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n        colour = vec3( r, g, b );\n        colour /= 255.0f;\n    }\n\n    return colour;\n}\n\n// parse modified cvs format log entries\n\nbool CustomLog::parseCommit(RCommit& commit) {\n\n    while(parseCommitEntry(commit));\n\n    return !commit.files.empty();\n}\n\nbool CustomLog::parseCommitEntry(RCommit& commit) {\n\n    std::string line;\n    std::vector<std::string> entries;\n\n    if(!getNextLine(line)) return false;\n\n    //custom line\n    if(!custom_regex.match(line, &entries)) return false;\n\n    time_t timestamp;\n\n    // Allow timestamp to be a string\n    if(entries[0].size() > 1 && entries[0].find(\"-\", 1) != std::string::npos) {\n        if(!SDLAppSettings::parseDateTime(entries[0], timestamp))\n            return false;\n    } else {\n        timestamp = (time_t) atoll(entries[0].c_str());\n        if(!timestamp && entries[0] != \"0\")\n            return false;\n    }\n\n    std::string username = (entries[1].size()>0) ? entries[1] : \"Unknown\";\n    std::string action   = (entries[2].size()>0) ? entries[2] : \"A\";\n\n    //if this file is for the same person and timestamp\n    //we add to the commit, else we save the lastline\n    //and return false\n    if(commit.files.empty()) {\n        commit.timestamp = timestamp;\n        commit.username  = username;\n    } else {\n        if(commit.timestamp != timestamp || commit.username  != username) {\n            lastline = line;\n            return false;\n        }\n    }\n\n    bool has_colour = false;\n    vec3 colour;\n\n    if(entries.size()>=5 && entries[4].size()>0) {\n        has_colour = true;\n        colour = parseColour(entries[4]);\n    }\n\n    if(has_colour) {\n        commit.addFile(entries[3], action, colour);\n    } else {\n        commit.addFile(entries[3], action);\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/custom.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef CUSTOMLOG_H\n#define CUSTOMLOG_H\n\n#include \"commitlog.h\"\n\nclass CustomLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n    bool parseCommitEntry(RCommit& commit);\n    vec3 parseColour(const std::string& cstr);\npublic:\n    CustomLog(const std::string& logfile);\n};\n\n#endif\n"
  },
  {
    "path": "src/formats/cvs-exp.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"cvs-exp.h\"\n\nRegex cvsexp_commitno_regex(\"^([0-9]{6}):\");\nRegex cvsexp_branch_regex(\"^BRANCH \\\\[(.+)\\\\]$\");\nRegex cvsexp_date_regex(\"^\\\\(date: ([0-9]{4})[-/]([0-9]{2})[-/]([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(?: [+-][0-9]{4})?;(.+)$\");\nRegex cvsexp_detail_regex(\"author: ([^;]+);  state: ([^;]+);(.+)$\");\n//Regex cvsexp_lines_regex(\"lines: \\\\+([0-9]+) -([0-9]+)\");\nRegex cvsexp_entry_regex(\"\\\\| (.+),v:([0-9.]+),?\");\nRegex cvsexp_end_regex(\"^(=+)$\");\n\nstd::string CVSEXPCommitLog::logCommand() {\n    std::string log_command = \"cvs-exp.pl -notree\";\n    return log_command;\n}\n\nCVSEXPCommitLog::CVSEXPCommitLog(const std::string& logfile) : RCommitLog(logfile) {\n}\n\n// parse modified cvs format log entries\n\nbool CVSEXPCommitLog::parseCommit(RCommit& commit) {\n\n    std::string line;\n    std::vector<std::string> entries;\n\n    if(!logf->getNextLine(line)) return false;\n\n    //skip empty line if there is one\n    if(line.size() == 0) {\n        if(!logf->getNextLine(line)) return false;\n    }\n\n    //read commit no\n    if(!cvsexp_commitno_regex.match(line, &entries)) return false;\n\n    //int commitno = atoi(entries[0].c_str());\n    //debugLog(\"commitno matched\\n\");\n\n    if(!logf->getNextLine(line)) return false;\n\n    //should be a branch\n    if(cvsexp_branch_regex.match(line, &entries)) {\n\n        //read next blank line\n        if(!logf->getNextLine(line)) return false;\n        if(line.size()) return false;\n        if(!logf->getNextLine(line)) return false;\n   }\n\n    //parse date\n    if(!cvsexp_date_regex.match(line, &entries)) return false;\n\n    //debugLog(\"date matched\\n\");\n\n    struct tm time_str;\n\n    time_str.tm_year = atoi(entries[0].c_str()) - 1900;\n    time_str.tm_mon  = atoi(entries[1].c_str()) - 1;\n    time_str.tm_mday = atoi(entries[2].c_str());\n    time_str.tm_hour = atoi(entries[3].c_str());\n    time_str.tm_min  = atoi(entries[4].c_str());\n    time_str.tm_sec  = atoi(entries[5].c_str());\n    time_str.tm_isdst = -1;\n\n    commit.timestamp = mktime(&time_str);\n\n    //parse author,state\n    std::string rest = entries[6];\n    if(!cvsexp_detail_regex.match(rest, &entries)) return false;\n\n    //debugLog(\"author/state matched\\n\");\n\n    commit.username = entries[0];\n\n    std::string commit_state = entries[1];\n\n    /* not used\n    //if rest is not ')' parse lines\n    rest = entries[2];\n\n    // need to parse lines\n    if(rest.size() > 2) {\n        if(!cvsexp_lines_regex.match(rest, &entries)) return false;\n    }\n    */\n\n    if(!logf->getNextLine(line)) return false;\n\n    std::string commit_action = (commit_state == \"dead\") ? \"D\" : \"M\";\n\n    while(cvsexp_entry_regex.match(line, &entries)) {\n\n        //ignore files in Attic - previously deleted file\n        if(entries[0].find(\"/Attic/\") == std::string::npos) {\n            commit.addFile(entries[0], commit_action);\n        }\n\n        if(!logf->getNextLine(line)) return false;\n    }\n\n    //read blank line\n    if(!logf->getNextLine(line)) return false;\n\n    //std::string message;\n\n    //read commit message\n    while(logf->getNextLine(line) && line.size()) {\n        //if(message.size()) message += std::string(\"\\n\");\n        //message += line;\n    }\n\n    //read until end of commit or eof\n    while(logf->getNextLine(line)) {\n        if(cvsexp_end_regex.match(line,&entries)) {\n            //debugLog(\"read end of commit %s\\n\", entries[0].c_str());\n            break;\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/cvs-exp.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef CVSLOG_EXP_H\n#define CVSLOG_EXP_H\n\n#include \"commitlog.h\"\n\nclass CVSEXPCommitLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\npublic:\n    CVSEXPCommitLog(const std::string& logfile);\n\n    static std::string logCommand();    \n};\n\n#endif\n"
  },
  {
    "path": "src/formats/cvs2cl.cpp",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"cvs2cl.h\"\n#include \"../gource_settings.h\"\n\n#ifdef HAVE_LIBTINYXML\n#include <tinyxml.h>\n#else\n#include \"../tinyxml/tinyxml.h\"\n#endif\n\nRegex cvs2cl_xml_tag(\"^<\\\\??xml\");\nRegex cvs2cl_logentry_start(\"^<entry\");\nRegex cvs2cl_logentry_end(\"^</entry>\");\nRegex cvs2cl_logentry_timestamp(\"(\\\\d{4})-(\\\\d{2})-(\\\\d{2})T(\\\\d{2}):(\\\\d{2}):(\\\\d{2})Z\");\n\nstd::string CVS2CLCommitLog::logCommand() {\n    std::string log_command = \"cvs2cl --chrono --stdout --xml -g-q\";\n    return log_command;\n}\n\nCVS2CLCommitLog::CVS2CLCommitLog(const std::string& logfile) : RCommitLog(logfile, '<') {\n}\n\nbool CVS2CLCommitLog::parseCommit(RCommit& commit) {\n\n    //fprintf(stderr,\"parsing cvs2cl log\\n\");\n\n    std::string line;\n\n    if(!getNextLine(line)) return false;\n\n    //start of log entry\n    if(!cvs2cl_logentry_start.match(line)) {\n\n        //is this the start of the document\n        if(!cvs2cl_xml_tag.match(line)) return false;\n\n        //fprintf(stderr,\"found xml tag\\n\");\n\n        //if so find the first logentry tag\n\n        bool found_logentry = false;\n        \n        while(getNextLine(line)) {\n            if(cvs2cl_logentry_start.match(line)) {\n                found_logentry = true;\n                break;\n            }\n        }\n\n        if(!found_logentry) return false;   \n    }\n\n    //fprintf(stderr,\"found logentry\\n\");\n\n    logentry.clear();\n\n    logentry.append(line);\n    logentry.append(\"\\n\");\n\n    //fprintf(stderr,\"found opening tag\\n\");\n\n    bool endfound = false;\n    \n    while(getNextLine(line)) {\n        logentry.append(line);\n        logentry.append(\"\\n\");\n        if(cvs2cl_logentry_end.match(line)) {\n            //fprintf(stderr,\"found closing tag\\n\");\n            endfound=true;\n            break;\n        }\n    }\n\n    //incomplete commit\n    if(!endfound) return false;\n\n    //fprintf(stderr,\"read logentry\\n\");\n\n    TiXmlDocument doc;\n\n    if(!doc.Parse(logentry.c_str())) return false;\n\n    //fprintf(stderr,\"try to parse logentry: %s\\n\", logentry.c_str());\n    \n    TiXmlElement* leE = doc.FirstChildElement( \"entry\" );\n    \n    std::vector<std::string> entries;\n    \n    if(!leE) return false;\n\n    //parse date\n    TiXmlElement* dateE = leE->FirstChildElement( \"isoDate\" );\n\n    if(!dateE) return false;\n\n    std::string timestamp_str(dateE->GetText());\n\n    if(!cvs2cl_logentry_timestamp.match(timestamp_str, &entries))\n        return false;\n                    \n    struct tm time_str;\n\n    time_str.tm_year  = atoi(entries[0].c_str()) - 1900;\n    time_str.tm_mon   = atoi(entries[1].c_str()) - 1;\n    time_str.tm_mday  = atoi(entries[2].c_str());\n    time_str.tm_hour  = atoi(entries[3].c_str());\n    time_str.tm_min   = atoi(entries[4].c_str());\n    time_str.tm_sec   = atoi(entries[5].c_str());\n    time_str.tm_isdst = -1;\n\n    commit.timestamp = mktime(&time_str);            \n   \n    //parse author\n    TiXmlElement* authorE = leE->FirstChildElement(\"author\");\n    \n    if(authorE != 0) {\n    \n        std::string author(authorE->GetText());\n\n        if(author.empty()) author = \"Unknown\";\n    \n        commit.username = author;\n    }\n\n    //parse changes\n    \n    for(TiXmlElement* fileE = leE->FirstChildElement(\"file\"); fileE != 0; fileE = fileE->NextSiblingElement()) {\n        \n        TiXmlElement* state = fileE->FirstChildElement(\"cvsstate\");\n        TiXmlElement* name  = fileE->FirstChildElement(\"name\");\n\n        //check for state\n        if(name == 0 || state == 0) continue;\n\n        std::string status = strcmp(state->GetText(), \"dead\") == 0 ? \"D\" : \"M\";\n        std::string file(name->GetText());\n\n        if(file.empty()) continue;\n        \n        commit.addFile(file, status);\n    }\n    \n    //fprintf(stderr,\"parsed logentry\\n\");\n\n    //read files\n    \n    return true;\n}\n"
  },
  {
    "path": "src/formats/cvs2cl.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef CVS2CL_H\n#define CVS2CL_H\n\n#include \"commitlog.h\"\n\nclass CVS2CLCommitLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n\n    std::string logentry;\npublic:\n    CVS2CLCommitLog(const std::string& logfile);\n\n    static std::string logCommand();    \n};\n\n#endif\n"
  },
  {
    "path": "src/formats/git.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"git.h\"\n#include \"../gource_settings.h\"\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n// parse git log entries\n\n//git-log command notes:\n// - no single quotes on WIN32 as system call treats them differently\n// - 'user:' prefix allows us to quickly tell if the log is the wrong format\n//   and try a different format (eg cvs-exp)\n\nint git_version_major = 0;\nint git_version_minor = 0;\nint git_version_patch = 0;\n\nRegex git_version_regex(\"([0-9]+)(?:\\\\.([0-9]+))?(?:\\\\.([0-9]+))?\");\n\nvoid GitCommitLog::readGitVersion() {\n    if(git_version_major != 0) return;\n\n    std::string temp_file;\n    if(!createTempFile(temp_file)) {\n        return;\n    }\n\n    char cmd_buff[2048];\n    int result = snprintf(cmd_buff, sizeof(cmd_buff), \"git --version > %s\", temp_file.c_str());\n\n    if(result < 0 || result >= sizeof(cmd_buff)) {\n        remove(temp_file.c_str());\n        return;\n    }\n\n    int command_rc = systemCommand(cmd_buff);\n\n    if(command_rc != 0) {\n        remove(temp_file.c_str());\n        return;\n    }\n\n    std::ifstream in(temp_file.c_str());\n\n    if(!in.is_open()) {\n        remove(temp_file.c_str());\n        return;\n    }\n\n    std::string version_str;\n    std::getline(in, version_str);\n    in.close();\n\n    remove(temp_file.c_str());\n\n    std::vector<std::string> entries;\n    if(!git_version_regex.match(version_str, &entries)) return;\n\n    git_version_major = atoi(entries[0].c_str());\n\n    if(entries.size() > 1) {\n        git_version_minor = atoi(entries[1].c_str());\n    }\n\n    if(entries.size() > 2) {\n        git_version_patch = atoi(entries[2].c_str());\n    }\n}\n\nstd::string GitCommitLog::logCommand() {\n\n    std::string log_command = \"git log \"\n    \"--reverse --raw --encoding=UTF-8 \"\n    \"--no-renames\";\n\n    readGitVersion();\n\n    // Add --no-show-signature either\n    // if git version couldn't be determined or if version\n    // is at least 2.10\n    if(   git_version_major == 0\n       || git_version_major > 2\n       || (git_version_major == 2 && git_version_minor >= 10))\n    {\n        log_command += \" --no-show-signature\";\n    }\n\n    if(gGourceSettings.author_time) {\n        log_command += \" --pretty=format:user:%aN%n%at\";\n    } else {\n        log_command += \" --pretty=format:user:%aN%n%ct\";\n    }\n\n    if(!gGourceSettings.start_date.empty()) {\n        log_command += \" --since \";\n        log_command += gGourceSettings.start_date;\n    }\n\n    if(!gGourceSettings.stop_date.empty()) {\n        log_command += \" --until \";\n        log_command += gGourceSettings.stop_date;\n    }\n\n    if(!gGourceSettings.git_branch.empty()) {\n        log_command += \" \";\n        log_command += gGourceSettings.git_branch;\n    }\n\n    return log_command;\n}\n\nGitCommitLog::GitCommitLog(const std::string& logfile) : RCommitLog(logfile, 'u') {\n\n    log_command = logCommand();\n\n    //can generate log from directory\n    if(!logf && is_dir) {\n        logf = generateLog(logfile);\n\n        if(logf) {\n            success  = true;\n            seekable = true;\n        }\n    }\n}\n\nBaseLog* GitCommitLog::generateLog(const std::string& dir) {\n    //get working directory\n    char cwd_buff[1024];\n\n    if(getcwd(cwd_buff, 1024) != cwd_buff) {\n        return 0;\n    }\n\n    //does directory have a .git ?\n    std::string gitdir = dir + std::string(\"/.git\");\n    struct stat dirinfo;\n    int stat_rc = stat(gitdir.c_str(), &dirinfo);\n    if(stat_rc!=0 || !(dirinfo.st_mode & S_IFDIR || dirinfo.st_mode & S_IFREG)) {\n        return 0;\n    }\n\n    // do we have this client installed\n    requireExecutable(\"git\");\n\n    std::string command = getLogCommand();\n\n    //create temp file\n    createTempLog();\n\n    if(temp_file.size()==0) return 0;\n\n    if(chdir(dir.c_str()) != 0) {\n        return 0;\n    }\n\n    char cmd_buff[2048];\n    int written = snprintf(cmd_buff, 2048, \"%s > %s\", command.c_str(), temp_file.c_str());\n\n    if(written < 0 || written >= 2048) {\n        return 0;\n    }\n\n    int command_rc = systemCommand(cmd_buff);\n\n    //change back to original directory\n    chdir(cwd_buff);\n\n    if(command_rc != 0) {\n        return 0;\n    }\n\n    BaseLog* seeklog = new SeekLog(temp_file);\n\n    return seeklog;\n}\n\n// parse modified git format log entries\n\nbool GitCommitLog::parseCommit(RCommit& commit) {\n\n    std::string line;\n\n    commit.username = \"\";\n\n    while(logf->getNextLine(line) && line.size()) {\n\n        if(line.find(\"user:\") == 0) {\n\n            //username follows user prefix\n            commit.username = line.substr(5);\n\n            if(!logf->getNextLine(line)) return false;\n\n            commit.timestamp = atol(line.c_str());\n\n            //this isnt a commit we are parsing, abort\n            if(commit.timestamp == 0) return false;\n\n            continue;\n        }\n\n        //should see username before files\n        if(commit.username.empty()) return false;\n\n        size_t tab = line.find('\\t');\n\n        //incorrect log format\n        if(tab == std::string::npos || tab == 0 || tab == line.size()-1) continue;\n\n        std::string status = line.substr(tab - 1, 1);\n        std::string file   = line.substr(tab + 1);\n\n        if(file.empty()) continue;\n\n        //check for and remove double quotes\n        if(file.find('\"') == 0 && file.rfind('\"') == file.size()-1) {\n            if(file.size()<=2) continue;\n\n            file = file.substr(1,file.size()-2);\n        }\n\n        commit.addFile(file, status);\n    }\n\n    //check we at least got a username\n    if(commit.username.empty()) return false;\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/git.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GITLOG_H\n#define GITLOG_H\n\n#include \"commitlog.h\"\n\nclass GitCommitLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n    BaseLog* generateLog(const std::string& dir);\n    static void readGitVersion();\npublic:\n    GitCommitLog(const std::string& logfile);\n    \n    static std::string logCommand();\n};\n\n#endif\n"
  },
  {
    "path": "src/formats/gitraw.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"gitraw.h\"\n\nRegex git_raw_commit(\"^commit ([0-9a-z]+)\");\nRegex git_raw_tree(\"^tree ([0-9a-z]+)\");\nRegex git_raw_parent(\"^parent ([0-9a-z]+)\");\nRegex git_raw_author(\"^author (.+) <([^@>]+)@?([^>]*)> (\\\\d+) ([-+]\\\\d+)\");\nRegex git_raw_committer(\"^committer (.+) <([^@>]+)@?([^>]*)> (\\\\d+) ([-+]\\\\d+)\");\nRegex git_raw_file(\"^:[0-9]+ [0-9]+ [0-9a-z]+\\\\.* ([0-9a-z]+)\\\\.* ([A-Z])[ \\\\t]+(.+)\");\n\n// parse git log entries\n\n// NOTE: this format is deprecated and exists\n// to allow existing log files produced in this format to work\n\nstd::string gGourceGitRawLogCommand = \"git log --reverse --raw --pretty=raw\";\n\nGitRawCommitLog::GitRawCommitLog(const std::string& logfile) : RCommitLog(logfile, 'c') {\n\n    log_command = gGourceGitRawLogCommand;\n}\n\nbool GitRawCommitLog::parseCommit(RCommit& commit) {\n\n    std::string line;\n    std::vector<std::string> entries;\n\n    //read commit ref/ branch\n    if(!logf->getNextLine(line)) return false;\n\n    //commit\n    if(!git_raw_commit.match(line, &entries)) return false;\n\n    if(!logf->getNextLine(line)) return false;\n\n    //tree\n    if(!git_raw_tree.match(line, &entries)) return false;\n\n    if(!logf->getNextLine(line)) return false;\n\n    //0 or more parents\n    while(git_raw_parent.match(line, &entries)) {\n        if(!logf->getNextLine(line)) return false;\n    }\n\n    //author - used for display name\n    if(!git_raw_author.match(line, &entries)) return false;\n\n    commit.username = entries[0];\n\n    if(!logf->getNextLine(line)) return false;\n\n    //committer - used for time (most likely chronological)\n    if(!git_raw_committer.match(line, &entries)) return false;\n\n    commit.timestamp = atol(entries[3].c_str());\n\n    //blank line before message\n    if(!logf->getNextLine(line)) return false;\n\n    //read commit message\n    while(logf->getNextLine(line) && line.size()) {\n    }\n\n    //read files\n    while(logf->getNextLine(line) && line.size()) {\n        //debugLog(\"file??? %s\\n\", line.c_str());\n\n        if(git_raw_file.match(line, &entries)) {\n                commit.addFile(entries[2], entries[1]);\n        }\n    }\n\n//    commit.debug();\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/gitraw.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GITLOG_RAW_H\n#define GITLOG_RAW_H\n\n#include \"commitlog.h\"\n\n#include <unistd.h>\n\nextern std::string gGourceGitRawLogCommand;\n\nclass GitRawCommitLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\npublic:\n    GitRawCommitLog(const std::string& logfile);\n};\n\n#endif\n\n"
  },
  {
    "path": "src/formats/hg.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"hg.h\"\n#include \"../core/sdlapp.h\"\n#include \"../gource_settings.h\"\n\n#include <boost/format.hpp>\n\nRegex hg_regex(\"^([0-9]+) -?[0-9]+\\\\|([^|]+)\\\\|([ADM]?)\\\\|(.+)$\");\n\nstd::string MercurialLog::logCommand() {\n\n    // parse Mercurial log entries (using the gource.style template)\n    std::string gource_style_path = gSDLAppResourceDir + std::string(\"gource.style\");\n\n    std::string range =\n        // date range\n        (!gGourceSettings.start_date.empty() && !gGourceSettings.stop_date.empty()) ?\n          str(boost::format(\"--date '%s to %s'\") % gGourceSettings.start_date % gGourceSettings.stop_date)\n\n        // start date only\n        : (!gGourceSettings.start_date.empty()) ?\n          str(boost::format(\"--date '>%s'\") % gGourceSettings.start_date)\n\n        // stop date only\n        : (!gGourceSettings.stop_date.empty()) ?\n          str(boost::format(\"--date '<%s'\") % gGourceSettings.stop_date)\n\n        // default\n        : \"\";\n\n    std::string log_command = str(boost::format(\"hg log %s -r 0:tip --style '%s'\") % range % gource_style_path);\n\n#ifdef _WIN32\n    std::replace(log_command.begin(), log_command.end(), '\\'', '\"');\n#endif\n\n    return log_command;\n}\n\nMercurialLog::MercurialLog(const std::string& logfile) : RCommitLog(logfile) {\n\n    log_command = logCommand();\n\n    //can generate log from directory\n    if(!logf && is_dir) {\n        logf = generateLog(logfile);\n\n        if(logf) {\n            success  = true;\n            seekable = true;\n        }\n    }\n}\n\nBaseLog* MercurialLog::generateLog(const std::string& dir) {\n\n    //does directory have a .hg ?\n    std::string hgdir = dir + std::string(\"/.hg\");\n    struct stat dirinfo;\n    int stat_rc = stat(hgdir.c_str(), &dirinfo);\n    if(stat_rc!=0 || !(dirinfo.st_mode & S_IFDIR)) {\n        return 0;\n    }\n\n    // do we have this client installed\n    requireExecutable(\"hg\");\n\n    std::string command = getLogCommand();\n\n    createTempLog();\n\n    if(temp_file.size()==0) return 0;\n\n    char cmd_buff[2048];\n    snprintf(cmd_buff, 2048, \"%s -R \\\"%s\\\" > %s\", command.c_str(), dir.c_str(), temp_file.c_str());\n\n    int command_rc = systemCommand(cmd_buff);\n\n    if(command_rc != 0) {\n        return 0;\n    }\n\n    BaseLog* seeklog = new SeekLog(temp_file);\n\n    return seeklog;\n}\n\n\nbool MercurialLog::parseCommit(RCommit& commit) {\n\n    while(parseCommitEntry(commit));\n\n    return !commit.files.empty();\n}\n\nbool MercurialLog::parseCommitEntry(RCommit& commit) {\n\n    std::string line;\n    std::vector<std::string> entries;\n\n    if(!getNextLine(line)) return false;\n\n    //custom line\n    if(!hg_regex.match(line, &entries)) return false;\n\n    time_t timestamp     = atol(entries[0].c_str());\n    std::string username = entries[1];\n\n    //if this file is for the same person and timestamp\n    //we add to the commit, else we save the lastline\n    //and return false\n    if(commit.files.empty()) {\n        commit.timestamp = timestamp;\n        commit.username  = username;\n    } else {\n        if(commit.timestamp != timestamp || commit.username  != username) {\n            lastline = line;\n            return false;\n        }\n    }\n\n    std::string action = \"A\";\n\n    if(!entries[2].empty()) {\n        action = entries[2];\n    }\n\n    commit.addFile(entries[3], action);\n\n    //commit.debug();\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/hg.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef MERCURIALLOG_H\n#define MERCURIALLOG_H\n\n#include \"commitlog.h\"\n\nclass MercurialLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n    bool parseCommitEntry(RCommit& commit);\n    BaseLog* generateLog(const std::string& dir);\npublic:\n    MercurialLog(const std::string& logfile);\n\n    static std::string logCommand();    \n};\n\n#endif"
  },
  {
    "path": "src/formats/svn.cpp",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"svn.h\"\n#include \"../gource_settings.h\"\n\n#include <boost/format.hpp>\n\n#ifdef HAVE_LIBTINYXML\n#include <tinyxml.h>\n#else\n#include \"../tinyxml/tinyxml.h\"\n#endif\n\nRegex svn_xml_tag(\"^<\\\\??xml\");\nRegex svn_logentry_start(\"^<logentry\");\nRegex svn_logentry_end(\"^</logentry>\");\nRegex svn_logentry_timestamp(\"(\\\\d{4})-(\\\\d{2})-(\\\\d{2})T(\\\\d{2}):(\\\\d{2}):(\\\\d{2})\");\n\nstd::string SVNCommitLog::logCommand() {\n\n    std::string start = (!gGourceSettings.start_date.empty())\n        ? str(boost::format(\"{%s}\") % gGourceSettings.start_date) : \"1\";\n\n    std::string stop  = (!gGourceSettings.stop_date.empty())\n        ? str(boost::format(\"{%s}\") % gGourceSettings.stop_date) : \"HEAD\";\n\n    std::string range = str(boost::format(\"%s:%s\") % start % stop);\n\n    std::string log_command = str(boost::format(\"svn log -r %s --xml --verbose --quiet\") % range);\n\n    return log_command;\n}\n\nSVNCommitLog::SVNCommitLog(const std::string& logfile) : RCommitLog(logfile, '<') {\n\n    log_command = logCommand();\n\n    //can generate log from directory\n    if(!logf && is_dir) {\n        logf = generateLog(logfile);\n\n        if(logf) {\n            success  = true;\n            seekable = true;\n        }\n    }\n\n    logentry.reserve(1024);\n}\n\n\nBaseLog* SVNCommitLog::generateLog(const std::string& dir) {\n    //get working directory\n    char cwd_buff[1024];\n\n    if(getcwd(cwd_buff, 1024) != cwd_buff) {\n        return 0;\n    }\n\n    //does directory have a .svn ?\n    std::string gitdir = dir + std::string(\"/.svn\");\n    struct stat dirinfo;\n    int stat_rc = stat(gitdir.c_str(), &dirinfo);\n    if(stat_rc!=0 || !(dirinfo.st_mode & S_IFDIR)) {\n        return 0;\n    }\n\n    // do we have this client installed\n    requireExecutable(\"svn\");\n\n    std::string command = getLogCommand();\n\n    //create temp file\n    createTempLog();\n\n    if(temp_file.size()==0) return 0;\n\n    if(chdir(dir.c_str()) != 0) {\n        return 0;\n    }\n\n    char cmd_buff[2048];\n    snprintf(cmd_buff, 2048, \"%s > %s\", command.c_str(), temp_file.c_str());\n\n    int command_rc = systemCommand(cmd_buff);\n\n    chdir(cwd_buff);\n\n    if(command_rc != 0) {\n        return 0;\n    }\n\n    BaseLog* seeklog = new SeekLog(temp_file);\n\n    return seeklog;\n}\n\n#ifndef HAVE_TIMEGM\n\nstd::string system_tz;\nbool system_tz_init = false;\n\ntime_t __timegm_hack(struct tm* tm) {\n\n    if(!system_tz_init) {\n        char* current_tz_env = getenv(\"TZ\");\n\n        if(current_tz_env != 0) {\n            system_tz  = std::string(\"TZ=\") + current_tz_env;\n        }\n\n        system_tz_init = true;\n    }\n\n    putenv((char*)\"TZ=UTC\");\n    tzset();\n\n    time_t timestamp = mktime(tm);\n\n    if(!system_tz.empty()) {\n        putenv((char*)system_tz.c_str());\n    } else {\n#ifdef HAVE_UNSETENV\n        unsetenv(\"TZ\");\n#else\n        putenv((char*)\"TZ=\");\n#endif\n    }\n    tzset();\n\n    return timestamp;\n}\n#endif\n\nbool SVNCommitLog::parseCommit(RCommit& commit) {\n\n    //fprintf(stderr,\"parsing svn log\\n\");\n\n    std::string line;\n\n    if(!getNextLine(line)) return false;\n\n    //start of log entry\n    if(!svn_logentry_start.match(line)) {\n\n        //is this the start of the document\n        if(!svn_xml_tag.match(line)) return false;\n\n        //fprintf(stderr,\"found xml tag\\n\");\n\n        //if so find the first logentry tag\n\n        bool found_logentry = false;\n\n        while(getNextLine(line)) {\n            if(svn_logentry_start.match(line)) {\n                found_logentry = true;\n                break;\n            }\n        }\n\n        if(!found_logentry) return false;\n    }\n\n    //fprintf(stderr,\"found logentry\\n\");\n\n    logentry.clear();\n\n    logentry.append(line);\n    logentry.append(\"\\n\");\n\n    //fprintf(stderr,\"found opening tag\\n\");\n\n    bool endfound = false;\n\n    while(getNextLine(line)) {\n        logentry.append(line);\n        logentry.append(\"\\n\");\n        if(svn_logentry_end.match(line)) {\n            //fprintf(stderr,\"found closing tag\\n\");\n            endfound=true;\n            break;\n        }\n    }\n\n    //incomplete commit\n    if(!endfound) return false;\n\n    //fprintf(stderr,\"read logentry\\n\");\n\n    TiXmlDocument doc;\n\n    if(!doc.Parse(logentry.c_str())) return false;\n\n    //fprintf(stderr,\"try to parse logentry: %s\\n\", logentry.c_str());\n\n    TiXmlElement* leE = doc.FirstChildElement( \"logentry\" );\n\n    std::vector<std::string> entries;\n\n    if(!leE) return false;\n\n    //parse date\n    TiXmlElement* dateE = leE->FirstChildElement( \"date\" );\n\n    if(!dateE) return false;\n\n    std::string timestamp_str(dateE->GetText());\n\n    if(!svn_logentry_timestamp.match(timestamp_str, &entries))\n        return false;\n\n    struct tm time_str;\n\n    time_str.tm_year  = atoi(entries[0].c_str()) - 1900;\n    time_str.tm_mon   = atoi(entries[1].c_str()) - 1;\n    time_str.tm_mday  = atoi(entries[2].c_str());\n    time_str.tm_hour  = atoi(entries[3].c_str());\n    time_str.tm_min   = atoi(entries[4].c_str());\n    time_str.tm_sec   = atoi(entries[5].c_str());\n    time_str.tm_isdst = -1;\n\n#ifdef HAVE_TIMEGM\n    commit.timestamp = timegm(&time_str);\n#else\n    commit.timestamp = __timegm_hack(&time_str);\n#endif\n\n    //parse author\n    TiXmlElement* authorE = leE->FirstChildElement(\"author\");\n\n    if(authorE != 0) {\n        // GetText() may return NULL, causing author instantiation to crash.\n        std::string author;\n        if(authorE->GetText()) author = authorE->GetText();\n        if(author.empty()) author = \"Unknown\";\n\n        commit.username = author;\n    }\n\n    TiXmlElement* pathsE = leE->FirstChildElement( \"paths\" );\n\n    //log entries sometimes dont have any paths\n    if(!pathsE) return true;\n\n    //parse changes\n\n    for(TiXmlElement* pathE = pathsE->FirstChildElement(\"path\"); pathE != 0; pathE = pathE->NextSiblingElement()) {\n        //parse path\n\n        const char* kind   = pathE->Attribute(\"kind\");\n        const char* action = pathE->Attribute(\"action\");\n\n        //check for action\n        if(action == 0) continue;\n\n        bool is_dir = false;\n\n        //if has the 'kind' attribute (old versions of svn dont have this), check if it is a dir\n        if(kind != 0 && strcmp(kind,\"dir\") == 0) {\n\n            //accept only deletes for directories\n            if(strcmp(action, \"D\") != 0) continue;\n\n            is_dir = true;\n        }\n\n        std::string file(pathE->GetText());\n        std::string status(action);\n\n        if(file.empty()) continue;\n        if(status.empty()) continue;\n\n        //append trailing slash if is directory\n        if(is_dir && file[file.size()-1] != '/') {\n            file = file + std::string(\"/\");\n        }\n\n        commit.addFile(file, status);\n    }\n\n    //fprintf(stderr,\"parsed logentry\\n\");\n\n    //read files\n\n    return true;\n}\n"
  },
  {
    "path": "src/formats/svn.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef SVNLOG_H\n#define SVNLOG_H\n\n#include \"commitlog.h\"\n\n#include <sstream>\n\nextern std::string gGourceSVNLogCommand;\n\nclass SVNCommitLog : public RCommitLog {\nprotected:\n    bool parseCommit(RCommit& commit);\n    BaseLog* generateLog(const std::string& dir);\n\n    std::string logentry;\npublic:\n    SVNCommitLog(const std::string& logfile);\n    \n    static std::string logCommand();\n};\n\n#endif\n"
  },
  {
    "path": "src/gource.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"gource.h\"\n#include \"core/png_writer.h\"\n\nbool  gGourceDrawBackground  = true;\nbool  gGourceQuadTreeDebug   = false;\nint   gGourceMaxQuadTreeDepth = 6;\n\nint gGourceUserInnerLoops = 0;\n\nGource::Gource(FrameExporter* exporter) {\n\n    this->logfile = gGourceSettings.path;\n    commitlog = 0;\n\n    //disable OpenGL 2.0 functions if not supported\n    if(!GLEW_VERSION_2_0) gGourceSettings.ffp = true;\n\n    if(!gGourceSettings.file_graphic) {\n        gGourceSettings.file_graphic = texturemanager.grab(\"file.png\", true, GL_CLAMP_TO_EDGE);\n    }\n\n    if(gGourceSettings.default_font_scale) {\n        if(display.viewport_dpi_ratio.x > 1.0f) {\n            gGourceSettings.font_scale = display.viewport_dpi_ratio.x;\n        } else {\n            int threshold = 1600;\n            gGourceSettings.font_scale = (float) (1 + glm::max(0, display.height / threshold));\n        }\n        debugLog(\"setting font scale for resolution %d x %d to %.2f\", display.width, display.height, gGourceSettings.font_scale);\n        gGourceSettings.setScaledFontSizes();\n    }\n\n    fontlarge = fontmanager.grab(gGourceSettings.font_file, 42 * gGourceSettings.font_scale);\n    fontlarge.dropShadow(true);\n    fontlarge.roundCoordinates(true);\n\n    fontmedium = fontmanager.grab(gGourceSettings.font_file, gGourceSettings.scaled_font_size);\n    fontmedium.dropShadow(true);\n    fontmedium.roundCoordinates(false);\n\n    fontcaption = fontmanager.grab(gGourceSettings.font_file, gGourceSettings.caption_size);\n    fontcaption.dropShadow(true);\n    fontcaption.roundCoordinates(false);\n    fontcaption.alignTop(false);\n\n    font = fontmanager.grab(gGourceSettings.font_file, 14 * gGourceSettings.font_scale);\n    font.dropShadow(true);\n    font.roundCoordinates(true);\n\n    fontdirname = fontmanager.grab(gGourceSettings.font_file, gGourceSettings.scaled_dirname_font_size);\n    fontdirname.dropShadow(true);\n    fontdirname.roundCoordinates(true);\n\n    slider.init();\n\n    //only use bloom with alpha channel if transparent due to artifacts on some video cards\n    std::string bloom_tga = gGourceSettings.transparent ? \"bloom_alpha.tga\" : \"bloom.tga\";\n\n    bloomtex = texturemanager.grab(bloom_tga);\n    beamtex  = texturemanager.grab(\"beam.png\");\n    usertex  = texturemanager.grab(\"user.png\", true, GL_CLAMP_TO_EDGE);\n\n    shadow_shader = text_shader = bloom_shader = 0;\n\n    if(!gGourceSettings.ffp) {\n        shadow_shader      = shadermanager.grab(\"shadow\");\n        bloom_shader       = shadermanager.grab(\"bloom\");\n        text_shader        = shadermanager.grab(\"text\");\n    }\n\n    //calculate once\n    GLint max_texture_size;\n    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);\n    font_texel_size = 1.0f / (float) std::min( 512, max_texture_size );\n\n    logotex = 0;\n    backgroundtex = 0;\n\n    //load logo\n    if(gGourceSettings.logo.size() > 0) {\n        bool mipmap_logo = !(GLEW_ARB_texture_non_power_of_two || GLEW_VERSION_2_0);\n        logotex = texturemanager.grabFile(gGourceSettings.logo, mipmap_logo);\n    }\n\n    //load background image\n    if(gGourceSettings.background_image.size() > 0) {\n        backgroundtex = texturemanager.grabFile(gGourceSettings.background_image);\n    }\n\n    stop_position_reached=false;\n\n    reloaded     = false;\n    paused       = false;\n    first_read   = true;\n\n    grab_mouse   = false;\n    mousemoved   = false;\n    mousedragged = false;\n    mouseclicked = false;\n\n    take_screenshot = false;\n\n    if(gGourceSettings.hide_mouse) {\n        cursor.showCursor(false);\n    }\n\n    splash = -1.0;\n\n    debug = false;\n    trace_debug = false;\n\n    frameExporter = 0;\n\n    dirNodeTree = 0;\n    userTree = 0;\n\n    selectedFile = 0;\n    hoverFile = 0;\n    selectedUser = 0;\n    hoverUser = 0;\n\n    date_x_offset = 0;\n    starting_z = -300.0f;\n\n    textbox = TextBox(fontmanager.grab(gGourceSettings.font_file, 18 * gGourceSettings.font_scale));\n    textbox.setBrightness(0.5f);\n    textbox.show();\n\n    file_key = FileKey(1.0f);\n\n    camera = ZoomCamera(vec3(0,0, starting_z), vec3(0.0, 0.0, 0.0), gGourceSettings.camera_zoom_default, gGourceSettings.camera_zoom_max);\n    camera.setPadding(gGourceSettings.padding);\n\n    setCameraMode(gGourceSettings.camera_mode);\n\n    root = 0;\n\n    //min physics rate 60fps (ie maximum allowed delta 1.0/60)\n    max_tick_rate = 1.0 / 60.0;\n    runtime = 0.0f;\n    frameskip = 0;\n    framecount = 0;\n\n    reset();\n\n    logmill = new RLogMill(logfile);\n\n    if(exporter!=0) setFrameExporter(exporter, gGourceSettings.output_framerate);\n\n    //if recording a video or in demo mode, or multiple repos, the slider is initially hidden\n    if(exporter==0 && gGourceSettings.repo_count==1) slider.show();\n}\n\nvoid Gource::writeCustomLog(const std::string& logfile, const std::string& output_file) {\n\n    RLogMill logmill(logfile);\n    RCommitLog* commitlog = logmill.getLog();\n\n    // TODO: exception handling\n\n    if(!commitlog) {\n        std::string error = logmill.getError();\n        if(!error.empty()) SDLAppQuit(error);\n        return;\n    }\n\n    RCommit commit;\n\n    FILE* fh = stdout;\n\n    if(output_file != \"-\") {\n        fh = fopen(output_file.c_str(), \"w\");\n\n        if(!fh) return;\n    }\n\n    while(!commitlog->isFinished()) {\n\n        RCommit commit;\n\n        if(!commitlog->nextCommit(commit)) {\n             if(!commitlog->isSeekable()) {\n                 break;\n             }\n            continue;\n        }\n\n        for(std::list<RCommitFile>::iterator it = commit.files.begin(); it != commit.files.end(); it++) {\n            RCommitFile& cf = *it;\n            fprintf(fh, \"%lld|%s|%s|%s\\n\", (long long int) commit.timestamp, commit.username.c_str(), cf.action.c_str(), cf.filename.c_str());\n        }\n\n        commit.files.clear();\n    }\n\n    if(output_file != \"-\") fclose(fh);\n}\n\nGource::~Gource() {\n    reset();\n\n    if(logmill!=0)   delete logmill;\n    if(root!=0)      delete root;\n\n    //reset settings\n    gGourceSettings.setGourceDefaults();\n}\n\nvoid Gource::init() {\n}\n\nvoid Gource::unload() {\n\n    file_vbo.unload();\n    user_vbo.unload();\n    edge_vbo.unload();\n    action_vbo.unload();\n    bloom_vbo.unload();\n\n}\n\nvoid Gource::reload() {\n    reloaded = true;\n}\n\nvoid Gource::quit() {\n}\n\nvoid Gource::update(float t, float dt) {\n\n    float scaled_dt = std::min(dt, max_tick_rate);\n\n    //if exporting a video use a fixed tick rate rather than time based\n    if(frameExporter != 0) {\n        scaled_dt = max_tick_rate;\n    }\n\n    //apply time scaling\n    scaled_dt *= gGourceSettings.time_scale;\n\n    //have to manage runtime internally as we're messing with dt\n    if(!paused) runtime += scaled_dt;\n\n    if(gGourceSettings.stop_at_time > 0.0 && runtime >= gGourceSettings.stop_at_time) stop_position_reached = true;\n\n    logic_time = SDL_GetTicks();\n\n    logic(runtime, scaled_dt);\n\n    logic_time = SDL_GetTicks() - logic_time;\n\n    draw(runtime, scaled_dt);\n\n    //extract frames based on frameskip setting if frameExporter defined\n    if(frameExporter != 0 && commitlog && !gGourceSettings.shutdown) {\n        if(framecount % (frameskip+1) == 0) {\n            frameExporter->dump();\n        }\n    }\n\n    if(!gGourceSettings.hide_mouse) {\n        //note: cursor uses real dt\n        cursor.logic(dt);\n        cursor.draw();\n    }\n\n    framecount++;\n}\n\n//peek at the date under the mouse pointer on the slider\nstd::string Gource::dateAtPosition(float percent) {\n\n    RCommit commit;\n    std::string date;\n\n    if(percent<1.0 && commitlog->getCommitAt(percent, commit)) {\n        //display date\n        char datestr[256];\n\n        // TODO: memory leak ??\n        struct tm* timeinfo = localtime ( &(commit.timestamp) );\n        strftime(datestr, 256, \"%A, %d %B, %Y\", timeinfo);\n\n        date = std::string(datestr);\n    }\n\n    return date;\n}\n\nvoid Gource::grabMouse(bool grab_mouse) {\n    this->grab_mouse = grab_mouse;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    if(grab_mouse) {\n        if(!SDL_GetRelativeMouseMode()) {\n            // NOTE: SDL_SetWindowGrab needed as well to work around this bug:\n            // http://bugzilla.libsdl.org/show_bug.cgi?id=1967\n\n            SDL_SetWindowGrab(display.sdl_window, SDL_TRUE);\n            SDL_SetRelativeMouseMode(SDL_TRUE);\n        }\n    } else {\n        if(SDL_GetRelativeMouseMode()) {\n            SDL_SetWindowGrab(display.sdl_window, SDL_FALSE);\n            SDL_SetRelativeMouseMode(SDL_FALSE);\n            SDL_WarpMouseInWindow(display.sdl_window, mousepos.x, mousepos.y);\n        }\n    }\n#endif\n\n#if not SDL_VERSION_ATLEAST(2,0,0)\n    if(!grab_mouse) {\n        // restore old mouse position\n        SDL_WarpMouse(mousepos.x, mousepos.y);\n    }\n#endif\n\n    cursor.showCursor(!grab_mouse);\n}\n\nvoid Gource::mouseMove(SDL_MouseMotionEvent *e) {\n    if(commitlog==0) return;\n    if(gGourceSettings.disable_input) return;\n    if(gGourceSettings.hide_mouse) return;\n\n    // debugLog(\"mouse move %d, %d (change %d, %d)\", e->x, e->y, e->xrel, e->yrel);\n\n    if(grab_mouse) {\n#if not SDL_VERSION_ATLEAST(2,0,0)\n         int warp_x = display.width/2;\n         int warp_y = display.height/2;\n\n         //this is an even we generated by warping the mouse below\n         if(e->x == warp_x && e->y == warp_y) return;\n\n         SDL_WarpMouse(warp_x, warp_y);\n#endif\n    }\n\n    bool rightmouse = cursor.rightButtonPressed();\n\n    //move camera in direction the user dragged the mouse\n    if(mousedragged || rightmouse) {\n        vec2 mag( e->xrel, e->yrel );\n\n        //if right mouse button is held while dragging, rotate tree instead of\n        //moving camera\n\n        if(rightmouse) {\n            manual_rotate = true;\n            if(fabs(mag.x) > fabs(mag.y)) {\n                rotate_angle = std::min(1.0f, (float) fabs(mag.x) / 10.0f) * 5.0f * DEGREES_TO_RADIANS;\n                if(mag.x < 0.0f) rotate_angle = -rotate_angle;\n            } else {\n                rotate_angle = std::min(1.0f, (float) fabs(mag.y) / 10.0f) * 5.0f * DEGREES_TO_RADIANS;\n                if(mag.y < 0.0f) rotate_angle = -rotate_angle;\n            }\n\n            return;\n        }\n\n        cursor_move += mag;\n\n        return;\n    }\n\n    if(grab_mouse) return;\n\n    mousepos = vec2(e->x, e->y);\n    mousemoved=true;\n\n    cursor.updatePos(mousepos);\n\n    float pos;\n\n    if(!gGourceSettings.hide_progress && slider.mouseOver(mousepos, &pos)) {\n        std::string date = dateAtPosition(pos);\n        slider.setCaption(date);\n    }\n\n}\n\nvoid Gource::zoom(bool zoomin) {\n\n    manual_zoom = true;\n\n    float zoom_multi = 1.1;\n\n    float distance = -camera.getDest().z;\n\n    if(zoomin) {\n        distance /= zoom_multi;\n\n        if(distance < gGourceSettings.camera_zoom_min) distance = gGourceSettings.camera_zoom_min;\n    } else {\n        distance *= zoom_multi;\n\n        if(distance > gGourceSettings.camera_zoom_max) distance = gGourceSettings.camera_zoom_max;\n    }\n\n    camera.setDistance(distance);\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nvoid Gource::mouseWheel(SDL_MouseWheelEvent *e) {\n    if(gGourceSettings.disable_input) return;\n\n    if(e->y > 0) {\n        zoom(true);\n    }\n\n    if(e->y < 0) {\n        zoom(false);\n    }\n}\n\n#endif\n\nvoid Gource::mouseClick(SDL_MouseButtonEvent *e) {\n    if(commitlog==0) return;\n    if(gGourceSettings.disable_input) return;\n    if(gGourceSettings.hide_mouse) return;\n\n    //mouse click should stop the cursor being idle\n    cursor.updatePos(mousepos);\n\n    if(e->type == SDL_MOUSEBUTTONUP) {\n\n        if(e->button == SDL_BUTTON_LEFT) {\n            //stop dragging mouse, return the mouse to where\n            //the user started dragging.\n            mousedragged=false;\n        }\n\n        if(e->button == SDL_BUTTON_LEFT || e->button == SDL_BUTTON_RIGHT) {\n            if(!cursor.buttonPressed()) {\n                grabMouse(false);\n            }\n        }\n    }\n\n    if(e->type != SDL_MOUSEBUTTONDOWN) return;\n\n\n#if not SDL_VERSION_ATLEAST(2,0,0)\n    //wheel up\n    if(e->button == SDL_BUTTON_WHEELUP) {\n        zoom(true);\n        return;\n    }\n\n    //wheel down\n    if(e->button == SDL_BUTTON_WHEELDOWN) {\n        zoom(false);\n        return;\n    }\n#endif\n\n    if(e->button == SDL_BUTTON_MIDDLE) {\n        toggleCameraMode();\n        return;\n    }\n\n\n    if(e->button == SDL_BUTTON_RIGHT) {\n        grabMouse(true);\n        return;\n    }\n\n    if(e->button == SDL_BUTTON_LEFT) {\n\n        //mousepos = vec2(e->x, e->y);\n        mouseclicked=true;\n\n        if(canSeek()) {\n            float position;\n            if(slider.click(mousepos, &position)) {\n                seekTo(position);\n            }\n        }\n    }\n}\n\nvoid Gource::showSplash() {\n    splash = 15.0;\n}\n\nvoid Gource::setFrameExporter(FrameExporter* exporter, int video_framerate) {\n\n    int gource_framerate = video_framerate;\n\n\n    this->framecount = 0;\n    this->frameskip  = 0;\n\n    //calculate appropriate tick rate for video frame rate\n    while(gource_framerate<60) {\n        gource_framerate += video_framerate;\n        this->frameskip++;\n    }\n\n    this->max_tick_rate = 1.0f / ((float) gource_framerate);\n\n    this->frameExporter = exporter;\n}\n\nvoid Gource::setCameraMode(const std::string& mode) {\n    setCameraMode(mode == \"track\");\n}\n\nvoid Gource::setCameraMode(bool track_users) {\n    manual_rotate   = false;\n    manual_zoom     = false;\n\n    this->track_users = track_users;\n    if(selectedUser!=0) camera.lockOn(track_users);\n\n    manual_camera = false;\n\n    gGourceSettings.camera_mode = track_users ? \"track\" : \"overview\";\n}\n\nvoid Gource::toggleCameraMode() {\n    setCameraMode(!track_users);\n}\n\n//trace click of mouse on background\nvoid Gource::selectBackground() {\n    //is the left mouse button down?\n\n    if(!cursor.leftButtonPressed()) return;\n\n    selectUser(0);\n\n    manual_camera = true;\n\n    mousedragged=true;\n\n    grabMouse(true);\n}\n\n//select a user, deselect current file/user\nvoid Gource::selectUser(RUser* user) {\n    //already selected do nothing\n    if(user!=0 && selectedUser==user) return;\n\n    if(selectedFile != 0) {\n        selectedFile->setSelected(false);\n        selectedFile = 0;\n    }\n\n    // deselect current user\n    if(selectedUser != 0) {\n        selectedUser->setSelected(false);\n        selectedUser = 0;\n    }\n\n    //if no user return\n    if(user == 0) {\n        camera.lockOn(false);\n        return;\n    }\n\n    selectedUser = user;\n\n    //select user, lock on camera\n    selectedUser->setSelected(true);\n\n    if(track_users) camera.lockOn(true);\n}\n\n//select a file, deselect current file/user\nvoid Gource::selectFile(RFile* file) {\n\n    //already selected do nothing\n    if(file!=0 && selectedFile==file) return;\n\n    if(selectedUser != 0) {\n        selectedUser->setSelected(false);\n        selectedUser = 0;\n    }\n\n    // deselect current file\n    if(selectedFile != 0) {\n        selectedFile->setSelected(false);\n        selectedFile = 0;\n    }\n\n    //if no file return\n    if(file == 0) {\n        return;\n    }\n\n    selectedFile = file;\n\n    //select user, lock on camera\n    selectedFile->setSelected(true);\n}\n\n\nvoid Gource::selectNextUser() {\n    //debugLog(\"selectNextUser()\\n\");\n\n    int currTagId = -1;\n\n    if(selectedUser != 0) {\n        currTagId = selectedUser->getTagID();\n    }\n\n    RUser* newSelectedUser = 0;\n\n    // find next user after this user\n    for(std::map<int,RUser*>::iterator it = tagusermap.begin(); it != tagusermap.end(); it++) {\n        RUser* user = it->second;\n\n        if(!user->isInactive() && user->getTagID() > currTagId && user->getAlpha() >= 1.0) {\n            newSelectedUser = user;\n            break;\n        }\n    }\n\n    // just get first user\n    if(newSelectedUser == 0) {\n\n        for(std::map<int,RUser*>::iterator it = tagusermap.begin(); it != tagusermap.end(); it++) {\n            RUser* user = it->second;\n\n            if(!user->isInactive() && user->getAlpha() >= 1.0) {\n                newSelectedUser = user;\n                break;\n            }\n        }\n    }\n\n    selectUser(newSelectedUser);\n}\n\nvoid Gource::keyPress(SDL_KeyboardEvent *e) {\n    if (gGourceSettings.disable_input) return;\n\n    if (e->type == SDL_KEYUP) return;\n\n    if (e->type == SDL_KEYDOWN) {\n\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n        bool key_escape       = e->keysym.sym == SDLK_ESCAPE;\n        bool key_tab          = e->keysym.sym == SDLK_TAB;\n        bool key_space        = e->keysym.sym == SDLK_SPACE;\n        bool key_plus         = e->keysym.sym == SDLK_PLUS;\n        bool key_equals       = e->keysym.sym == SDLK_EQUALS;\n        bool key_minus        = e->keysym.sym == SDLK_MINUS;\n        bool key_leftbracket  = e->keysym.sym == SDLK_LEFTBRACKET;\n        bool key_rightbracket = e->keysym.sym == SDLK_RIGHTBRACKET;\n        bool key_comma        = e->keysym.sym == SDLK_COMMA;\n        bool key_period       = e->keysym.sym == SDLK_PERIOD;\n        bool key_slash        = e->keysym.sym == SDLK_SLASH;\n#else\n        bool key_escape       = e->keysym.unicode == SDLK_ESCAPE;\n        bool key_tab          = e->keysym.unicode == SDLK_TAB;\n        bool key_space        = e->keysym.unicode == SDLK_SPACE;\n        bool key_plus         = e->keysym.unicode == SDLK_PLUS;\n        bool key_equals       = e->keysym.unicode == SDLK_EQUALS;\n        bool key_minus        = e->keysym.unicode == SDLK_MINUS;\n        bool key_leftbracket  = e->keysym.unicode == SDLK_LEFTBRACKET;\n        bool key_rightbracket = e->keysym.unicode == SDLK_RIGHTBRACKET;\n        bool key_comma        = e->keysym.unicode == SDLK_COMMA;\n        bool key_period       = e->keysym.unicode == SDLK_PERIOD;\n        bool key_slash        = e->keysym.unicode == SDLK_SLASH;\n#endif\n        if (key_escape) {\n            quit();\n        }\n\n        if(commitlog==0) return;\n\n        if(e->keysym.sym == SDLK_F12) {\n            take_screenshot = true;\n        }\n\n        if (e->keysym.sym == SDLK_q) {\n            debug = !debug;\n        }\n\n        if (e->keysym.sym == SDLK_w) {\n            trace_debug = !trace_debug;\n        }\n\n        if (e->keysym.sym == SDLK_m) {\n\n            //toggle mouse visibility unless mouse clicked/pressed/dragged\n            if(!(mousedragged || mouseclicked || cursor.leftButtonPressed() )) {\n\n                if(!cursor.isHidden()) {\n                    cursor.showCursor(false);\n                    gGourceSettings.hide_mouse = true;\n                } else {\n                    cursor.showCursor(true);\n                    gGourceSettings.hide_mouse = false;\n                }\n            }\n        }\n\n        if (e->keysym.sym == SDLK_n) {\n            idle_time = gGourceSettings.auto_skip_seconds;\n        }\n\n        if (e->keysym.sym == SDLK_y) {\n            gGourceQuadTreeDebug = !gGourceQuadTreeDebug;\n        }\n\n        if (e->keysym.sym == SDLK_t) {\n            gGourceSettings.hide_tree = !gGourceSettings.hide_tree;\n        }\n\n        if (e->keysym.sym == SDLK_g) {\n            gGourceSettings.hide_users = !gGourceSettings.hide_users;\n        }\n\n        if (e->keysym.sym == SDLK_u) {\n\n            if(gGourceSettings.hide_usernames && !gGourceSettings.highlight_all_users) {\n                gGourceSettings.hide_usernames      = false;\n                gGourceSettings.highlight_all_users = true;\n\n            } else if (gGourceSettings.highlight_all_users && !gGourceSettings.hide_usernames) {\n                gGourceSettings.hide_usernames      = false;\n                gGourceSettings.highlight_all_users = false;\n            } else {\n                gGourceSettings.hide_usernames      = true;\n                gGourceSettings.highlight_all_users = false;\n            }\n\n        }\n\n        if (e->keysym.sym == SDLK_d) {\n            if(gGourceSettings.hide_dirnames && !gGourceSettings.highlight_dirs) {\n\n                gGourceSettings.hide_dirnames  = false;\n                gGourceSettings.highlight_dirs = true;\n\n            } else if(gGourceSettings.highlight_dirs && !gGourceSettings.hide_dirnames) {\n\n                gGourceSettings.hide_dirnames  = false;\n                gGourceSettings.highlight_dirs = false;\n\n            } else {\n                gGourceSettings.hide_dirnames  = true;\n                gGourceSettings.highlight_dirs = false;\n            }\n        }\n\n        if (e->keysym.sym == SDLK_f) {\n\n            if(gGourceSettings.hide_filenames && !gGourceSettings.file_extensions) {\n                gGourceSettings.hide_filenames  = false;\n            } else if(!gGourceSettings.hide_filenames && gGourceSettings.file_extensions) {\n                gGourceSettings.file_extensions = false;\n                gGourceSettings.hide_filenames = true;\n            } else {\n                gGourceSettings.file_extensions = true;\n                gGourceSettings.hide_filenames  = false;\n            }\n        }\n\n        if (e->keysym.sym == SDLK_r) {\n            gGourceSettings.hide_root = !gGourceSettings.hide_root;\n        }\n\n        if (e->keysym.sym == SDLK_k) {\n            gGourceSettings.show_key = !gGourceSettings.show_key;\n        }\n\n        if(e->keysym.sym == SDLK_c) {\n            splash = 15.0f;\n        }\n\n        if (e->keysym.sym == SDLK_v) {\n            toggleCameraMode();\n        }\n\n        if (e->keysym.sym == SDLK_p) {\n            if(GLEW_VERSION_2_0 && bloom_shader != 0) {\n                gGourceSettings.ffp = !gGourceSettings.ffp;\n            }\n        }\n\n        if (e->keysym.sym == SDLK_z) {\n            gGourceGravity = !gGourceGravity;\n        }\n\n        if (e->keysym.sym == SDLK_s) {\n            recolour=true;\n        }\n\n        if(key_tab) {\n            selectNextUser();\n        }\n\n        if (key_space) {\n            paused = !paused;\n        }\n\n        if (key_equals || key_plus) {\n            if(gGourceSettings.days_per_second>=1.0) {\n                gGourceSettings.days_per_second = std::min(30.0f, floorf(gGourceSettings.days_per_second) + 1.0f);\n            } else {\n                gGourceSettings.days_per_second = std::min(1.0f, gGourceSettings.days_per_second * 2.0f);\n            }\n        }\n\n        if (key_minus) {\n            if(gGourceSettings.days_per_second>1.0) {\n                gGourceSettings.days_per_second = std::max(0.0f, floorf(gGourceSettings.days_per_second) - 1.0f);\n            } else {\n                gGourceSettings.days_per_second = std::max(0.0f, gGourceSettings.days_per_second * 0.5f);\n            }\n        }\n\n        if(e->keysym.sym == SDLK_KP_MINUS) {\n            zoom(true);\n        }\n\n        if(e->keysym.sym == SDLK_KP_PLUS) {\n            zoom(false);\n        }\n\n        if(key_leftbracket) {\n            gGourceForceGravity /= 1.1;\n        }\n\n        if(key_rightbracket) {\n            gGourceForceGravity *= 1.1;\n        }\n\n        if(key_period) {\n\n            if(gGourceSettings.time_scale>=1.0) {\n                gGourceSettings.time_scale = std::min(4.0f, floorf(gGourceSettings.time_scale) + 1.0f);\n            } else {\n                gGourceSettings.time_scale = std::min(1.0f, gGourceSettings.time_scale * 2.0f);\n            }\n        }\n\n        if(key_comma) {\n\n            if(gGourceSettings.time_scale>1.0) {\n                gGourceSettings.time_scale = std::max(0.0f, floorf(gGourceSettings.time_scale) - 1.0f);\n            } else {\n                gGourceSettings.time_scale = std::max(0.25f, gGourceSettings.time_scale * 0.5f);\n            }\n        }\n\n        if(key_slash) {\n            gGourceSettings.time_scale = 1.0f;\n        }\n    }\n}\n\nvoid Gource::reset() {\n    camera.reset();\n    user_bounds.reset();\n    active_user_bounds.reset();\n    dir_bounds.reset();\n    commitqueue.clear();\n    tagusermap.clear();\n    gGourceRemovedFiles.clear();\n\n    if(userTree!=0) delete userTree;\n    if(dirNodeTree!=0) delete dirNodeTree;\n\n    recolour = false;\n\n    userTree = 0;\n    dirNodeTree = 0;\n\n    selectedFile = 0;\n    hoverFile = 0;\n\n    use_selection_bounds = false;\n    selection_bounds.reset();\n\n    manual_rotate   = false;\n    manual_zoom     = false;\n    rotation_remaining_angle = 0.0f;\n\n    message_timer = 0.0f;\n\n    cursor_move = vec2(0.0f, 0.0f);\n\n    selectedUser = 0;\n    hoverUser = 0;\n\n    manual_camera = false;\n\n    grab_mouse = false;\n\n    mouseclicked=false;\n    mousemoved=false;\n    mousedragged = false;\n\n    commitqueue_max_size = 100;\n\n    rotate_angle = 0.0f;\n\n    if(root!=0) delete root;\n    root = new RDirNode(0, \"/\");\n\n    //delete users\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it != users.end(); it++) {\n        delete it->second;\n    }\n\n    users.clear();\n\n    //delete\n    for(std::map<std::string,RFile*>::iterator it = files.begin(); it != files.end(); it++) {\n        delete it->second;\n    }\n\n    for(std::list<RCaption*>::iterator it = captions.begin(); it!=captions.end();it++) {\n        delete (*it);\n    }\n\n    for(std::list<RCaption*>::iterator it = active_captions.begin(); it!=active_captions.end();it++) {\n        delete (*it);\n    }\n\n    files.clear();\n    captions.clear();\n    active_captions.clear();\n\n    last_percent = 0.0;\n\n    file_key.clear();\n\n    idle_time=0;\n    currtime=0;\n    lasttime=0;\n    subseconds=0.0;\n    tag_seq = 1;\n    commit_seq = 1;\n}\n\nvoid Gource::deleteFile(RFile* file) {\n    //debugLog(\"removing file %s\\n\", file->fullpath.c_str());\n\n    root->removeFile(file);\n\n    if(hoverFile == file) {\n        hoverFile = 0;\n    }\n\n    if(selectedFile == file) {\n        selectFile(0);\n    }\n\n    //remove from any users with actions against this file - wrong way around? meh\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n        RUser* user = it->second;\n\n        user->fileRemoved(file);\n    }\n\n    files.erase(file->fullpath);\n    file_key.dec(file);\n\n    //debugLog(\"removed file %s\\n\", file->fullpath.c_str());\n\n    delete file;\n}\n\n\nRFile* Gource::addFile(const RCommitFile& cf) {\n\n    //if we already have max files in circulation\n    //we cant add any more\n    if(gGourceSettings.max_files > 0 && files.size() >= gGourceSettings.max_files) return 0;\n\n    //see if this is a directory\n    std::string file_as_dir = cf.filename;\n    if(file_as_dir[file_as_dir.size()-1] != '/') file_as_dir.append(\"/\");\n\n    if(root->isDir(file_as_dir)) return 0;\n\n    int tagid = tag_seq++;\n\n    RFile* file = new RFile(cf.filename, cf.colour, vec2(0.0,0.0), tagid);\n\n    files[cf.filename] = file;\n\n    root->addFile(file);\n\n    file_key.inc(file);\n\n    while(root->getParent() != 0) {\n        debugLog(\"parent changed to %s\", root->getPath().c_str());\n        root = root->getParent();\n    }\n\n    return file;\n}\n\nRUser* Gource::addUser(const std::string& username) {\n\n    vec2 pos;\n\n    if(dir_bounds.area() > 0) {\n        pos = dir_bounds.centre();\n    } else {\n        pos = vec2(0,0);\n    }\n\n    int tagid = tag_seq++;\n\n    RUser* user = new RUser(username, pos, tagid);\n\n    users[username]   = user;\n    tagusermap[tagid] = user;\n\n    //debugLog(\"added user %s, tagid = %d\\n\", username.c_str(), tagid);\n\n    return user;\n}\n\nvoid Gource::deleteUser(RUser* user) {\n\n    if(hoverUser == user) {\n        hoverUser = 0;\n    }\n\n    if(selectedUser == user) {\n        selectUser(0);\n    }\n\n    users.erase(user->getName());\n    tagusermap.erase(user->getTagID());\n\n    //debugLog(\"deleted user %s, tagid = %d\\n\", user->getName().c_str(), user->getTagID());\n\n    delete user;\n}\n\nbool Gource::canSeek() {\n    if(gGourceSettings.hide_progress || commitlog == 0 || !commitlog->isSeekable()) return false;\n\n    return true;\n}\n\nvoid Gource::seekTo(float percent) {\n    //debugLog(\"seekTo(%.2f)\\n\", percent);\n\n    if(commitlog == 0 || !commitlog->isSeekable()) return;\n\n    // end pause\n    if(paused) paused = false;\n\n    reset();\n\n    commitlog->seekTo(percent);\n}\n\nRegex caption_regex(\"^(?:\\\\xEF\\\\xBB\\\\xBF)?([^|]+)\\\\|(.+)$\");\n\nvoid Gource::loadCaptions() {\n    if(!gGourceSettings.caption_file.size()) return;\n\n    std::ifstream cf(gGourceSettings.caption_file.c_str());\n\n    if(!cf.is_open()) return;\n\n    std::string line;\n    std::vector<std::string> matches;\n\n    time_t last_timestamp = 0;\n\n    while(std::getline(cf, line)) {\n\n        ConfFile::trim(line);\n\n        if(!caption_regex.match(line, &matches)) continue;\n\n        time_t timestamp;\n\n        // Allow timestamp to be a string\n        if(matches[0].size() > 1 && matches[0].find(\"-\", 1) != std::string::npos) {\n            if(!SDLAppSettings::parseDateTime(matches[0], timestamp))\n                continue;\n        } else {\n            timestamp = (time_t) atoll(matches[0].c_str());\n            if(!timestamp && matches[0] != \"0\")\n                continue;\n        }\n\n        std::string caption = RCommitLog::filter_utf8(matches[1]);\n\n        //ignore older captions\n        if(timestamp < currtime) continue;\n\n        //ignore out of order captions\n        if(timestamp < last_timestamp) continue;\n\n        last_timestamp = timestamp;\n\n        //fprintf(stderr, \"%d %s\\n\", timestamp, matches[1].c_str());\n\n        captions.push_back(new RCaption(caption, timestamp, fontcaption));\n    }\n\n    //fprintf(stderr, \"loaded %d captions\\n\", captions.size());\n\n}\n\nvoid Gource::readLog() {\n    if(stop_position_reached) return;\n\n    //debugLog(\"readLog()\\n\");\n\n    // read commits until either we are ahead of currtime\n    while((commitlog->hasBufferedCommit() || !commitlog->isFinished()) && (commitqueue.empty() || (commitqueue.back().timestamp <= currtime && commitqueue.size() < commitqueue_max_size)) ) {\n\n        RCommit commit;\n\n        if(!commitlog->nextCommit(commit)) {\n            if(!commitlog->isSeekable()) {\n                break;\n            }\n            continue;\n        }\n\n        if(gGourceSettings.stop_timestamp != 0 && commit.timestamp > gGourceSettings.stop_timestamp) {\n            stop_position_reached = true;\n            break;\n        }\n\n        commitqueue.push_back(commit);\n    }\n\n    if(first_read && commitqueue.empty()) {\n        throw SDLAppException(\"no commits found\");\n    }\n\n    first_read = false;\n\n    if(!commitlog->isFinished() && commitlog->isSeekable()) {\n        last_percent = commitlog->getPercent();\n        slider.setPercent(last_percent);\n    }\n\n    bool is_finished = commitlog->isFinished();\n\n\n    if(\n       // end reached\n       (gGourceSettings.stop_at_end && is_finished)\n\n       // stop position reached\n       || (gGourceSettings.stop_position > 0.0 && commitlog->isSeekable() && (is_finished || last_percent >= gGourceSettings.stop_position))\n    ) {\n        stop_position_reached = true;\n    }\n\n    if((is_finished || stop_position_reached) && gGourceSettings.file_idle_time_at_end > 0.0f) {\n      gGourceSettings.file_idle_time = gGourceSettings.file_idle_time_at_end;\n    }\n\n    // useful to figure out where we have crashes\n    //debugLog(\"current date: %s\\n\", displaydate.c_str());\n}\n\nvoid Gource::processCommit(const RCommit& commit, float t) {\n\n    //find files of this commit or create it\n    for(std::list<RCommitFile>::const_iterator it = commit.files.begin(); it != commit.files.end(); it++) {\n\n        const RCommitFile& cf = *it;\n        RFile* file = 0;\n\n        //is this a directory (ends in slash)\n        //deleting a directory - find directory: then for each file, remove each file\n\n        if(!cf.filename.empty() && cf.filename[cf.filename.size()-1] == '/') {\n\n            //ignore unless it is a delete: we cannot 'add' or 'modify' a directory\n            //as its not a physical entity in Gource, only files are.\n\n            if(cf.action != \"D\") continue;\n\n            std::list<RDirNode*> dirs;\n\n            root->findDirs(cf.filename, dirs);\n\n            for(std::list<RDirNode*>::iterator it = dirs.begin(); it != dirs.end(); it++) {\n\n                RDirNode* dir = (*it);\n\n                //fprintf(stderr, \"deleting everything under %s because of %s\\n\", dir->getPath().c_str(), cf.filename.c_str());\n\n                //foreach dir files\n                std::list<RFile*> dir_files;\n\n                dir->getFilesRecursive(dir_files);\n\n                for(std::list<RFile*>::iterator it = dir_files.begin(); it != dir_files.end(); it++) {\n                    RFile* file = *it;\n\n                    addFileAction(commit, cf, file, t);\n                }\n            }\n\n            continue;\n        }\n\n        std::map<std::string, RFile*>::iterator seen_file = files.find(cf.filename);\n        if(seen_file != files.end()) file = seen_file->second;\n\n        if(file == 0) {\n            file = addFile(cf);\n\n            if(!file) continue;\n        }\n\n        addFileAction(commit, cf, file, t);\n    }\n}\n\nvoid Gource::addFileAction(const RCommit& commit, const RCommitFile& cf, RFile* file, float t) {\n    //create user if havent yet. do it here to ensure at least one of there files\n    //was added (incase we hit gGourceSettings.max_files)\n\n    //find user of this commit or create them\n    RUser* user = 0;\n\n    //see if user already exists\n    std::map<std::string, RUser*>::iterator seen_user = users.find(commit.username);\n    if(seen_user != users.end()) user = seen_user->second;\n\n    if(user == 0) {\n        user = addUser(commit.username);\n\n        if(gGourceSettings.highlight_all_users) user->setHighlighted(true);\n        else {\n\n            // set the highlighted flag if name matches a highlighted user\n            for(std::vector<std::string>::iterator hi = gGourceSettings.highlight_users.begin(); hi != gGourceSettings.highlight_users.end(); hi++) {\n                std::string highlight = *hi;\n\n                if(!highlight.empty() && user->getName() == highlight) {\n                    user->setHighlighted(true);\n                    break;\n                }\n            }\n        }\n    }\n\n    //create action\n\n    RAction* userAction = 0;\n\n    commit_seq++;\n\n    if(cf.action == \"D\") {\n        userAction = new RemoveAction(user, file, commit.timestamp, t);\n    } else {\n        if(cf.action == \"A\") {\n            userAction = new CreateAction(user, file, commit.timestamp, t);\n        } else {\n            userAction = new ModifyAction(user, file, commit.timestamp, t, cf.colour);\n        }\n    }\n\n    user->addAction(userAction);\n}\n\nvoid Gource::interactUsers() {\n\n\n    // update quad tree\n    Bounds2D quadtreebounds = user_bounds;\n\n    quadtreebounds.min -= vec2(1.0f, 1.0f);\n    quadtreebounds.max += vec2(1.0f, 1.0f);\n\n    update_user_tree_time = SDL_GetTicks();\n\n    if(userTree != 0) delete userTree;\n\n    int max_depth = 1;\n\n    //dont use deep quad tree initially when all the nodes are in one place\n    if(dir_bounds.area() > 10000.0) {\n        max_depth = gGourceMaxQuadTreeDepth;\n    }\n\n    userTree = new QuadTree(quadtreebounds, max_depth, 1);\n\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n        RUser* user = it->second;\n\n        userTree->addItem(user);\n    }\n\n    //move users - interact with other users and files\n    for(std::map<std::string,RUser*>::iterator ait = users.begin(); ait!=users.end(); ait++) {\n\n        RUser* a = ait->second;\n\n        UserForceFunctor uff(a);\n        userTree->visitItemsInBounds(a->quadItemBounds, uff);\n        gGourceUserInnerLoops += uff.getLoopCount();\n\n        a->applyForceToActions();\n    }\n\n    update_user_tree_time = SDL_GetTicks() - update_user_tree_time;\n}\n\nvoid Gource::updateBounds() {\n\n    user_bounds.reset();\n    active_user_bounds.reset();\n\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n        RUser* user = it->second;\n\n        user->updateQuadItemBounds();\n        user_bounds.update(user->quadItemBounds);\n\n        if(!user->isIdle()) {\n            active_user_bounds.update(user->quadItemBounds);\n        }\n\n    }\n\n    dir_bounds.reset();\n\n    for(std::map<std::string,RDirNode*>::iterator it = gGourceDirMap.begin(); it!=gGourceDirMap.end(); it++) {\n        RDirNode* node = it->second;\n\n        if(node->isVisible()) {\n            node->updateQuadItemBounds();\n            dir_bounds.update(node->quadItemBounds);\n        }\n    }\n}\n\n\nvoid Gource::updateUsers(float t, float dt) {\n    std::vector<RUser*> inactiveUsers;\n\n    size_t idle_users = 0;\n\n    // move users\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n        RUser* u = it->second;\n\n        u->logic(t, dt);\n\n        //deselect user if fading out from inactivity\n        if(u->isFading() && selectedUser == u) {\n            selectUser(0);\n        }\n\n        if(u->isInactive()) {\n            inactiveUsers.push_back(u);\n        }\n\n        if(u->isIdle()) {\n            idle_users++;\n        } else {\n            //if nothing is selected, and this user is active and this user is the specified user to follow, select them\n            if(selectedUser == 0 && selectedFile == 0) {\n                for(std::vector<std::string>::iterator ui = gGourceSettings.follow_users.begin(); ui != gGourceSettings.follow_users.end(); ui++) {\n                    std::string follow = *ui;\n\n                    if(follow.size() && u->getName() == follow) {\n                        selectUser(u);\n                    }\n                }\n            }\n        }\n    }\n\n    if(users.empty() && stop_position_reached) {\n        appFinished = true;\n    }\n\n    //nothing is moving so increment idle\n    if(idle_users==users.size()) {\n        idle_time += dt;\n    } else {\n        idle_time = 0;\n    }\n\n    // delete inactive users\n    for(std::vector<RUser*>::iterator it = inactiveUsers.begin(); it != inactiveUsers.end(); it++) {\n        deleteUser(*it);\n    }\n}\n\nvoid Gource::interactDirs() {\n\n    // update quad tree\n    Bounds2D quadtreebounds = dir_bounds;\n\n    quadtreebounds.min -= vec2(1.0f, 1.0f);\n    quadtreebounds.max += vec2(1.0f, 1.0f);\n\n    update_dir_tree_time = SDL_GetTicks();\n\n    if(dirNodeTree !=0) delete dirNodeTree;\n\n    int max_depth = 1;\n\n    //dont use deep quad tree initially when all the nodes are in one place\n    if(dir_bounds.area() > 10000.0) {\n        max_depth = gGourceMaxQuadTreeDepth;\n    }\n\n    dirNodeTree = new QuadTree(quadtreebounds, max_depth, 1);\n\n    //apply forces with other directories\n    for(std::map<std::string,RDirNode*>::iterator it = gGourceDirMap.begin(); it!=gGourceDirMap.end(); it++) {\n        RDirNode* node = it->second;\n\n        if(!node->empty()) {\n            dirNodeTree->addItem(node);\n        }\n    }\n\n    update_dir_tree_time = SDL_GetTicks() - update_dir_tree_time;\n}\n\nvoid Gource::updateDirs(float dt) {\n    root->applyForces(*dirNodeTree);\n    root->logic(dt);\n}\n\nvoid Gource::updateTime(time_t display_time) {\n\n    if(display_time == 0) {\n        displaydate = \"\";\n        return;\n    }\n\n    //display date\n    char datestr[256];\n    struct tm* timeinfo = localtime ( &display_time );\n\n    strftime(datestr, 256, gGourceSettings.date_format.c_str(), timeinfo);\n    displaydate = datestr;\n\n    //avoid wobbling by only moving font if change is sufficient\n    int date_offset = (int) fontmedium.getWidth(displaydate) * 0.5;\n    if(abs(date_x_offset - date_offset) > 5) date_x_offset = date_offset;\n}\n\nvoid Gource::updateCamera(float dt) {\n\n    //camera tracking\n\n    bool auto_rotate = !manual_rotate && !gGourceSettings.disable_auto_rotate;\n\n\n    if(manual_camera) {\n\n        if(glm::length2(cursor_move) > 0.0f) {\n\n            float cam_rate = ( -camera.getPos().z ) / ( 5000.0f );\n\n            vec3 cam_pos = camera.getPos();\n\n            vec2 cursor_delta = cursor_move * cam_rate * 10.0f;\n\n            cam_pos.x += cursor_delta.x;\n            cam_pos.y += cursor_delta.y;\n\n            camera.setPos(cam_pos, true);\n            camera.stop();\n\n            auto_rotate = false;\n\n            cursor_move = vec2(0.0f, 0.0f);\n        }\n\n    } else {\n\n        Bounds2D cambounds;\n\n        if(track_users && (selectedFile !=0 || selectedUser !=0)) {\n            Bounds2D focusbounds;\n\n            if(selectedUser !=0) focusbounds.update(selectedUser->getPos());\n            if(selectedFile !=0) focusbounds.update(selectedFile->getAbsolutePos());\n\n            cambounds = focusbounds;\n        } else {\n            if(track_users && idle_time==0) cambounds = active_user_bounds;\n            else cambounds = dir_bounds;\n        }\n\n        camera.adjust(cambounds, !manual_zoom);\n    }\n\n    camera.logic(dt);\n\n    //automatically rotate camera\n    if(auto_rotate) {\n\n        if(rotation_remaining_angle > 0.0f) {\n\n            //rotation through 90 degrees, speed peaks at half way\n            float angle_rate = std::max(dt, (float) (1.0f - fabs((rotation_remaining_angle / 90.0f) - 0.5) * 2.0f)) * dt;\n\n            rotate_angle = std::min(rotation_remaining_angle, 90.0f * angle_rate);\n            rotation_remaining_angle -= rotate_angle;\n\n            rotate_angle *= DEGREES_TO_RADIANS;\n\n        } else if(!cursor.rightButtonPressed() && dir_bounds.area() > 10000.0f) {\n\n            float aspect_ratio = display.width / (float) display.height;\n\n            float bounds_ratio = (aspect_ratio > 1.0f) ? dir_bounds.width() / dir_bounds.height() : dir_bounds.height() / dir_bounds.width();\n\n            if(bounds_ratio < 0.67f) {\n                rotation_remaining_angle = 90.0f;\n            }\n        }\n    } else {\n        rotation_remaining_angle = 0.0f;\n    }\n}\n\n//change the string hashing seed and recolour files and users\nvoid Gource::changeColours() {\n\n    gStringHashSeed = (rand() % 10000) + 1;\n\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it != users.end(); it++) {\n        it->second->colourize();\n    }\n\n    for(std::map<std::string,RFile*>::iterator it = files.begin(); it != files.end(); it++) {\n        it->second->colourize();\n    }\n\n    file_key.colourize();\n}\n\nvoid Gource::logic(float t, float dt) {\n\n    if(gGourceSettings.shutdown && logmill->isFinished()) {\n        appFinished=true;\n        return;\n    }\n\n    if(message_timer>0.0f) message_timer -= dt;\n    if(splash>0.0f)        splash -= dt;\n\n    //init log file\n    if(commitlog == 0) {\n\n        if(!logmill->isFinished()) return;\n\n        commitlog = logmill->getLog();\n\n        std::string error = logmill->getError();\n\n        if(!commitlog) {\n\n            if(!error.empty()) {\n                throw SDLAppException(error);\n            } else {\n\n                if(frameExporter!=0) frameExporter->stop();\n\n                SDL_Quit();\n\n                SDLAppException exception(\"\");\n                exception.setShowHelp(true);\n\n                throw exception;\n            }\n        }\n\n        if(gGourceSettings.start_position>0.0) {\n            seekTo(gGourceSettings.start_position);\n        }\n    }\n\n    file_key.logic(dt);\n\n    slider.logic(dt);\n\n    bool right = false;\n    bool left  = false;\n    bool up    = false;\n    bool down  = false;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    const Uint8 *keystate = SDL_GetKeyboardState(0);\n\n    right = keystate[SDL_SCANCODE_RIGHT];\n    left  = keystate[SDL_SCANCODE_LEFT];\n    up    = keystate[SDL_SCANCODE_UP];\n    down  = keystate[SDL_SCANCODE_DOWN];\n#else\n    Uint8 *keystate = SDL_GetKeyState(0);\n\n    right = keystate[SDLK_RIGHT];\n    left  = keystate[SDLK_LEFT];\n    up    = keystate[SDLK_UP];\n    down  = keystate[SDLK_DOWN];\n#endif\n\n    if(right) {\n        cursor_move.x = 10.0;\n        manual_camera = true;\n    }\n\n    if(left) {\n        cursor_move.x = -10.0;\n        manual_camera = true;\n    }\n\n    if(up) {\n        cursor_move.y = -10.0;\n        manual_camera = true;\n    }\n\n    if(down) {\n        cursor_move.y = 10.0;\n        manual_camera = true;\n    }\n\n    //apply rotation\n    if(rotate_angle != 0.0f) {\n\n        float s = sinf(rotate_angle);\n        float c = cosf(rotate_angle);\n\n        if(manual_rotate) {\n            // rotate around camera position if manual\n            vec2 centre = vec2(camera.getPos());\n\n            root->rotate(s, c, centre);\n\n            for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n                RUser* user = it->second;\n\n                vec2 rotated_user_pos = rotate_vec2(user->getPos() - centre, s, c) + centre;\n                user->setPos(rotated_user_pos);\n            }\n        } else {\n            root->rotate(s, c);\n\n            for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n                RUser* user = it->second;\n\n                vec2 rotated_user_pos = rotate_vec2(user->getPos(), s, c);\n                user->setPos(rotated_user_pos);\n            }\n\n        }\n\n        rotate_angle = 0.0f;\n    }\n\n    if(recolour) {\n        changeColours();\n        recolour = false;\n    }\n\n    //still want to update camera while paused\n    if(paused) {\n        updateBounds();\n        interactUsers();\n        interactDirs();\n        updateCamera(dt);\n        return;\n    }\n\n    // get more entries\n    if(commitqueue.empty()) {\n        readLog();\n    }\n\n    //loop in attempt to find commits\n    if(gGourceSettings.loop && commitqueue.empty() && commitlog->isSeekable()) {\n        if(idle_time >= gGourceSettings.loop_delay_seconds) {\n            first_read=true;\n            seekTo(0.0);\n            readLog();\n        }\n    }\n\n    if(currtime==0 && !commitqueue.empty()) {\n        currtime   = lasttime = commitqueue[0].timestamp;\n        subseconds = 0.0;\n\n        loadCaptions();\n    }\n\n    //set current time\n    float time_inc = (dt * 86400.0 * gGourceSettings.days_per_second);\n    int seconds    = (int) time_inc;\n\n    subseconds += time_inc - ((float) seconds);\n\n    if(subseconds >= 1.0) {\n        currtime   += (int) subseconds;\n        subseconds -= (int) subseconds;\n    }\n\n    currtime += seconds;\n\n    // delete files\n    for(std::vector<RFile*>::iterator it = gGourceRemovedFiles.begin(); it != gGourceRemovedFiles.end(); it++) {\n        deleteFile(*it);\n    }\n\n    gGourceRemovedFiles.clear();\n\n\n    //add commits up until the current time\n    while(!commitqueue.empty()) {\n\n        RCommit commit = commitqueue.front();\n\n        //auto skip ahead, unless stop_position_reached\n        if(gGourceSettings.auto_skip_seconds>=0.0 && idle_time >= gGourceSettings.auto_skip_seconds && !stop_position_reached) {\n            currtime = lasttime = commit.timestamp;\n            idle_time = 0.0;\n        }\n\n        if(commit.timestamp > currtime) break;\n\n        processCommit(commit, t);\n\n        if(gGourceSettings.no_time_travel) {\n            if(commit.timestamp > lasttime) {\n                lasttime = commit.timestamp;\n            }\n\n        } else {\n            // allow for non linear time lines\n            if(lasttime > commit.timestamp) {\n                currtime = commit.timestamp;\n            }\n            lasttime = commit.timestamp;\n        }\n\n        subseconds = 0.0;\n\n        commitqueue.pop_front();\n    }\n\n    slider.resize();\n\n    float caption_height  = fontcaption.getMaxHeight();\n    float caption_start_y = canSeek() ? slider.getBounds().min.y - 35.0f : display.height - fontmedium.getMaxHeight() - 20.0f;\n\n    if(!gGourceSettings.title.empty()) {\n        caption_start_y = glm::min( caption_start_y, display.height - 20.0f - fontmedium.getMaxHeight() );\n    }\n\n    caption_start_y = glm::floor(caption_start_y);\n\n    if(reloaded) {\n        // reposition active captions\n        float y = caption_start_y;\n\n        for(RCaption* cap : active_captions) {\n\n            int caption_offset_x = gGourceSettings.caption_offset;\n\n            // centre\n            if(caption_offset_x == 0) {\n                caption_offset_x = (display.width / 2) - (fontcaption.getWidth(cap->getCaption()) / 2);\n            } else if(caption_offset_x < 0) {\n                caption_offset_x = display.width + caption_offset_x - fontcaption.getWidth(cap->getCaption());\n            }\n\n            cap->setPos(vec2(caption_offset_x, y));\n            y -= caption_height;\n        }\n\n        reloaded = false;\n    }\n\n    while(captions.size() > 0) {\n        RCaption* caption = captions.front();\n\n        if(caption->timestamp > currtime) break;\n\n        float y = caption_start_y;\n\n        while(1) {\n\n            bool found = false;\n\n            for(RCaption* cap : active_captions) {\n\n                if(cap->getPos().y == y) {\n                    found = true;\n                    break;\n                }\n            }\n\n            if(!found) break;\n\n            y -= caption_height;\n        }\n\n        int caption_offset_x = gGourceSettings.caption_offset;\n\n        // centre\n        if(caption_offset_x == 0) {\n            caption_offset_x = (display.width / 2) - (fontcaption.getWidth(caption->getCaption()) / 2);\n        } else if(caption_offset_x < 0) {\n            caption_offset_x = display.width + caption_offset_x - fontcaption.getWidth(caption->getCaption());\n        }\n\n        caption->setPos(vec2(caption_offset_x, y));\n\n        captions.pop_front();\n        active_captions.push_back(caption);\n    }\n\n    for(std::list<RCaption*>::iterator it = active_captions.begin(); it!=active_captions.end();) {\n         RCaption* caption = *it;\n\n         caption->logic(dt);\n\n         if(caption->isFinished()) {\n             it = active_captions.erase(it);\n             delete caption;\n             continue;\n         }\n\n         it++;\n    }\n\n    //reset loop counters\n    gGourceUserInnerLoops = 0;\n    gGourceDirNodeInnerLoops = 0;\n    gGourceFileInnerLoops = 0;\n\n    updateBounds();\n\n    interactUsers();\n    updateUsers(t, dt);\n\n    interactDirs();\n    updateDirs(dt);\n\n    updateCamera(dt);\n\n    updateTime(!commitqueue.empty() ? currtime : lasttime);\n}\n\nvoid Gource::mousetrace(float dt) {\n\n    vec3 cam_pos = camera.getPos();\n\n    vec2 projected_mouse = vec2( -(mousepos.x * 2.0f - ((float)display.width)) / ((float)display.height),\n                                   (1.0f - (2.0f * mousepos.y) / ((float)display.height)))\n                                   * cam_pos.z;\n    projected_mouse.x += cam_pos.x;\n    projected_mouse.y += cam_pos.y;\n\n    //find user/file under mouse\n\n    RFile* fileSelection = 0;\n    RUser* userSelection = 0;\n\n    if(!gGourceSettings.hide_users) {\n\n        std::set<QuadItem*> userset;\n\n        userTree->getItemsAt(userset, projected_mouse);\n\n        for(std::set<QuadItem*>::iterator it = userset.begin(); it != userset.end(); it++) {\n            RUser* user = (RUser*) *it;\n            if(!user->isFading() && user->quadItemBounds.contains(projected_mouse)) {\n                userSelection = user;\n                break;\n            }\n        }\n\n    }\n\n    if(!userSelection && !gGourceSettings.hide_files) {\n\n        std::set<QuadItem*> dirset;\n\n        dirNodeTree->getItemsAt(dirset, projected_mouse);\n\n        for(std::set<QuadItem*>::iterator it = dirset.begin(); it != dirset.end(); it++) {\n\n            RDirNode* dir = (RDirNode*) *it;\n\n            const std::list<RFile*>* files = dir->getFiles();\n\n            for(std::list<RFile*>::const_iterator fi = files->begin(); fi != files->end(); fi++) {\n\n                RFile* file = *fi;\n\n                if(!file->isHidden() && file->overlaps(projected_mouse)) {\n                    fileSelection = file;\n                    break;\n                }\n            }\n        }\n    }\n\n    // is over a file\n    if(fileSelection != 0) {\n        // un hover a user\n        if(hoverUser != 0) {\n            hoverUser->setMouseOver(false);\n            hoverUser = 0;\n        }\n\n        if(fileSelection != hoverFile) {\n            //deselect previous selection\n            if(hoverFile !=0) hoverFile->setMouseOver(false);\n\n            //select new\n            fileSelection->setMouseOver(true);\n            hoverFile = fileSelection;\n        }\n\n    // is over a user\n    } else if(userSelection != 0) {\n        // un hover a file\n        if(hoverFile != 0) {\n            hoverFile->setMouseOver(false);\n            hoverFile = 0;\n        }\n\n        if(userSelection != hoverUser) {\n            //deselect previous selection\n            if(hoverUser !=0) hoverUser->setMouseOver(false);\n\n            //select new\n            userSelection->setMouseOver(true);\n            hoverUser = userSelection;\n        }\n    } else {\n        if(hoverFile!=0) hoverFile->setMouseOver(false);\n        if(hoverUser!=0) hoverUser->setMouseOver(false);\n        hoverFile=0;\n        hoverUser=0;\n    }\n\n    if(mouseclicked) {\n\n        if(hoverUser!=0) {\n            camera.lockOn(false);\n            selectUser(hoverUser);\n        } else if(hoverFile!=0) {\n            camera.lockOn(false);\n            selectFile(hoverFile);\n        } else {\n            selectBackground();\n        }\n    }\n\n    //fprintf(stderr, \"end trace\\n\");\n}\n\nvoid Gource::loadingScreen() {\n    if(!gGourceDrawBackground) return;\n    display.mode2D();\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glEnable(GL_BLEND);\n    glEnable(GL_TEXTURE_2D);\n\n    glColor4f(1.0, 1.0, 1.0, 1.0);\n\n    const char* progress = \"\";\n\n    switch(int(runtime*3.0f)%4) {\n        case 0:\n            progress = \"\";\n            break;\n        case 1:\n            progress = \".\";\n            break;\n        case 2:\n            progress = \"..\";\n            break;\n        case 3:\n            progress = \"...\";\n            break;\n    }\n\n    const char* action = !gGourceSettings.shutdown ? \"Reading Log\" : \"Aborting\";\n\n    int width = font.getWidth(action);\n    font.setColour(vec4(1.0f));\n    font.print(display.width/2 - width/2, display.height/2 - 10, \"%s%s\", action, progress);\n}\n\nvoid Gource::drawBackground(float dt) {\n    if(!gGourceDrawBackground) return;\n\n    display.setClearColour(vec4(gGourceSettings.background_colour, gGourceSettings.transparent ? 0.0f : 1.0f));\n    display.clear();\n\n    if(backgroundtex!=0) {\n        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n        glEnable(GL_BLEND);\n        glEnable(GL_TEXTURE_2D);\n\n        glColor4f(1.0, 1.0, 1.0, 1.0);\n\n        glBindTexture(GL_TEXTURE_2D, backgroundtex->textureid);\n\n        glPushMatrix();\n\n            glTranslatef(display.width/2 - backgroundtex->w/2, display.height/2 - backgroundtex->h/2, 0.0f);\n\n            glBegin(GL_QUADS);\n                glTexCoord2f(0.0f,0.0f);\n                glVertex2i(0, 0);\n\n                glTexCoord2f(1.0f,0.0f);\n                glVertex2i(backgroundtex->w, 0);\n\n                glTexCoord2f(1.0f,1.0f);\n                glVertex2i(backgroundtex->w, backgroundtex->h);\n\n                glTexCoord2f(0.0f,1.0f);\n                glVertex2i(0, backgroundtex->h);\n            glEnd();\n\n        glPopMatrix();\n    }\n}\n\nvoid Gource::drawScene(float dt) {\n\n    //draw edges\n\n    draw_edges_time = SDL_GetTicks();\n\n    updateAndDrawEdges();\n\n    draw_edges_time = SDL_GetTicks() - draw_edges_time;\n\n    //draw file shadows\n\n    draw_shadows_time = SDL_GetTicks();\n\n    drawFileShadows(dt);\n\n    draw_shadows_time = SDL_GetTicks() - draw_shadows_time;\n\n    //draw actions\n\n    draw_actions_time = SDL_GetTicks();\n\n    drawActions(dt);\n\n    draw_actions_time = SDL_GetTicks() - draw_actions_time;\n\n    //draw files\n\n    draw_files_time = SDL_GetTicks();\n\n    drawFiles(dt);\n\n    draw_files_time = SDL_GetTicks() - draw_files_time;\n\n    //draw users\n\n    draw_users_time = SDL_GetTicks();\n\n    drawUserShadows(dt);\n\n    drawUsers(dt);\n\n    draw_users_time = SDL_GetTicks() - draw_users_time;\n\n    //draw bloom\n\n    draw_bloom_time = SDL_GetTicks();\n\n    drawBloom(dt);\n\n    draw_bloom_time = SDL_GetTicks() - draw_bloom_time;\n\n}\n\nvoid Gource::updateAndDrawEdges() {\n\n    root->calcEdges();\n\n    if(gGourceSettings.hide_tree) return;\n\n    //switch to 2d\n    glMatrixMode(GL_PROJECTION);\n    glPushMatrix();\n    glLoadIdentity();\n    glOrtho(0, display.width, display.height, 0, -1.0, 1.0);\n\n    glMatrixMode(GL_MODELVIEW);\n    glPushMatrix();\n    glLoadIdentity();\n\n    glEnable(GL_BLEND);\n    glEnable(GL_TEXTURE_2D);\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glBindTexture(GL_TEXTURE_2D, beamtex->textureid);\n\n    if(!gGourceSettings.ffp) {\n\n        edge_vbo.reset();\n\n        root->updateEdgeVBO(edge_vbo);\n\n        edge_vbo.update();\n\n        shadow_shader->setSampler2D(\"tex\", 0);\n        shadow_shader->setFloat(\"shadow_strength\", 0.5);\n        shadow_shader->use();\n\n        vec2 shadow_offset = vec2(2.0, 2.0);\n\n        glPushMatrix();\n            glTranslatef(shadow_offset.x, shadow_offset.y, 0.0f);\n            edge_vbo.draw();\n        glPopMatrix();\n\n        glUseProgramObjectARB(0);\n\n        edge_vbo.draw();\n\n    } else {\n        root->drawEdgeShadows();\n        root->drawEdges();\n    }\n\n    //switch back\n    glMatrixMode(GL_PROJECTION);\n    glPopMatrix();\n\n    glMatrixMode(GL_MODELVIEW);\n    glPopMatrix();\n}\n\nvoid Gource::drawActions(float dt) {\n    if(gGourceSettings.hide_users) return;\n\n    glEnable(GL_TEXTURE_2D);\n    glBindTexture(GL_TEXTURE_2D, beamtex->textureid);\n\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    if(!gGourceSettings.ffp) {\n\n        action_vbo.draw();\n\n    } else {\n\n        //draw actions\n        for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n            it->second->drawActions(dt);\n        }\n    }\n}\n\nvoid Gource::drawBloom(float dt) {\n    if(gGourceSettings.hide_bloom) return;\n\n    glEnable(GL_TEXTURE_2D);\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_ONE, GL_ONE);\n\n    if(!gGourceSettings.ffp) {\n\n        bloom_shader->use();\n\n        bloom_vbo.draw();\n\n        glUseProgramObjectARB(0);\n\n    } else {\n\n        //draw 'gourceian blur' around dirnodes\n        glBindTexture(GL_TEXTURE_2D, bloomtex->textureid);\n\n        root->drawBloom(dt);\n    }\n}\n\nvoid Gource::setMessage(const char* str, ...) {\n\n    char msgbuff[1024];\n\n    va_list vl;\n\n    va_start(vl, str);\n        vsnprintf(msgbuff, 1024, str, vl);\n    va_end(vl);\n\n    message = std::string(msgbuff);\n    message_timer = 5.0;\n}\n\nvoid Gource::screenshot() {\n\n    //get next free recording name\n    char pngname[256];\n    struct stat finfo;\n    int pngno = 1;\n\n    while(pngno < 10000) {\n        snprintf(pngname, 256, \"gource-%04d.png\", pngno);\n        if(stat(pngname, &finfo) != 0) break;\n        pngno++;\n    }\n\n    //write png\n    std::string filename(pngname);\n\n    PNGWriter png(gGourceSettings.transparent ? 4 : 3);\n    png.screenshot(filename);\n\n    setMessage(\"Wrote screenshot %s\", pngname);\n}\n\nvoid Gource::updateVBOs(float dt) {\n    if(gGourceSettings.ffp) return;\n\n    if(!gGourceSettings.hide_users) {\n\n        user_vbo.reset();\n        action_vbo.reset();\n\n        //use a separate vbo for each user texture\n        for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n            RUser* user = it->second;\n\n            float alpha = user->getAlpha();\n            vec3 col   = user->getColour();\n\n            vec2 scaled_dims = user->dims;\n\n            if(gGourceSettings.fixed_user_size) scaled_dims *= (-camera.getPos().z / -starting_z);\n\n            user_vbo.add(user->graphic->textureid, user->getPos() - scaled_dims*0.5f, scaled_dims, vec4(col.x, col.y, col.z, alpha));\n\n            //draw actions\n            user->updateActionsVBO(action_vbo);\n        }\n\n        user_vbo.update();\n        action_vbo.update();\n    }\n\n    if(!gGourceSettings.hide_bloom) {\n        bloom_vbo.reset();\n        root->updateBloomVBO(bloom_vbo, dt);\n        bloom_vbo.update();\n    }\n\n    if(!gGourceSettings.hide_files) {\n        file_vbo.reset();\n        root->updateFilesVBO(file_vbo, dt);\n        file_vbo.update();\n    }\n}\n\nvoid Gource::drawFileShadows(float dt) {\n    if(gGourceSettings.hide_files) return;\n\n    glEnable(GL_TEXTURE_2D);\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    if(!gGourceSettings.ffp) {\n\n        shadow_shader->setSampler2D(\"tex\", 0);\n        shadow_shader->setFloat(\"shadow_strength\", 0.5);\n        shadow_shader->use();\n\n        glBindTexture(GL_TEXTURE_2D, gGourceSettings.file_graphic->textureid);\n\n        glPushMatrix();\n            glTranslatef(2.0f, 2.0f, 0.0f);\n            file_vbo.draw();\n        glPopMatrix();\n\n        glUseProgramObjectARB(0);\n    } else {\n        root->drawShadows(dt);\n    }\n}\n\nvoid Gource::drawUserShadows(float dt) {\n    if(gGourceSettings.hide_users) return;\n\n    glEnable(GL_TEXTURE_2D);\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    if(!gGourceSettings.ffp) {\n\n        shadow_shader->setSampler2D(\"tex\", 0);\n        shadow_shader->setFloat(\"shadow_strength\", 0.5);\n        shadow_shader->use();\n\n        vec2 shadow_offset = vec2(2.0, 2.0) * gGourceSettings.user_scale;\n\n        glPushMatrix();\n            glTranslatef(shadow_offset.x, shadow_offset.y, 0.0f);\n\n            user_vbo.draw();\n\n        glPopMatrix();\n\n        glUseProgramObjectARB(0);\n    } else {\n        for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n            it->second->drawShadow(dt);\n        }\n    }\n}\n\nvoid Gource::drawFiles(float dt) {\n    if(gGourceSettings.hide_files) return;\n\n\n    if(trace_debug) {\n        glDisable(GL_TEXTURE_2D);\n    } else {\n        glEnable(GL_TEXTURE_2D);\n    }\n\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    if(!gGourceSettings.ffp) {\n        glBindTexture(GL_TEXTURE_2D, gGourceSettings.file_graphic->textureid);\n\n        file_vbo.draw();\n    } else {\n        root->drawFiles(dt);\n    }\n}\n\nvoid Gource::drawUsers(float dt) {\n    if(gGourceSettings.hide_users) return;\n\n    if(trace_debug) {\n        glDisable(GL_TEXTURE_2D);\n    } else {\n        glEnable(GL_TEXTURE_2D);\n    }\n\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    if(!gGourceSettings.ffp) {\n\n        user_vbo.draw();\n\n    } else {\n\n        for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n            it->second->draw(dt);\n        }\n    }\n\n}\n\nvoid Gource::draw(float t, float dt) {\n\n    display.mode2D();\n\n    drawBackground(dt);\n\n    if(!commitlog) {\n        loadingScreen();\n        return;\n    }\n\n    Frustum frustum(camera.getPos(), camera.getTarget(), camera.getUp(), camera.getFOV(), camera.getZNear(), camera.getZFar());\n\n    trace_time = SDL_GetTicks();\n\n    if(!gGourceSettings.hide_mouse && cursor.isVisible()) {\n        mousetrace(dt);\n    } else {\n        if(hoverUser) {\n            hoverUser->setMouseOver(false);\n            hoverUser = 0;\n        }\n        if(hoverFile) {\n            hoverFile->setMouseOver(false);\n            hoverFile = 0;\n        }\n    }\n\n    trace_time = SDL_GetTicks() - trace_time;\n\n    glMatrixMode(GL_PROJECTION);\n    glLoadIdentity();\n\n    camera.focus();\n\n    //check visibility\n    root->checkFrustum(frustum);\n\n    screen_project_time = SDL_GetTicks();\n\n    GLint viewport[4];\n    GLdouble modelview[16];\n    GLdouble projection[16];\n\n    glGetIntegerv( GL_VIEWPORT, viewport );\n    glGetDoublev( GL_MODELVIEW_MATRIX, modelview );\n    glGetDoublev( GL_PROJECTION_MATRIX, projection );\n\n    root->calcScreenPos(viewport, modelview, projection);\n\n    for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n        it->second->calcScreenPos(viewport, modelview, projection);\n    }\n\n    //need to calc screen pos of selected file if hiding other\n    //file names\n    if(selectedFile!=0 && gGourceSettings.hide_filenames) {\n        selectedFile->calcScreenPos(viewport, modelview, projection);\n    }\n\n    screen_project_time = SDL_GetTicks() - screen_project_time;\n\n    //update file and user vbos\n\n    update_vbos_time = SDL_GetTicks();\n\n    updateVBOs(dt);\n\n    update_vbos_time = SDL_GetTicks() - update_vbos_time;\n\n    //draw scene\n\n    draw_scene_time = SDL_GetTicks();\n\n    drawScene(dt);\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glEnable(GL_BLEND);\n    glEnable(GL_TEXTURE_2D);\n\n    draw_scene_time = SDL_GetTicks() - draw_scene_time;\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    text_time = text_update_time = SDL_GetTicks();\n\n    //switch to 2D, preserve current state\n    display.push2D();\n\n    if(!gGourceSettings.ffp) {\n        fontmanager.startBuffer();\n    }\n\n    fontdirname.roundCoordinates(false);\n    fontdirname.setColour(vec4(gGourceSettings.dir_colour, 1.0f));\n\n    root->drawNames(fontdirname);\n\n   if(!(gGourceSettings.hide_usernames || gGourceSettings.hide_users)) {\n        for(std::map<std::string,RUser*>::iterator it = users.begin(); it!=users.end(); it++) {\n            RUser* user = it->second;\n            if(!user->isSelected()) {\n                user->drawName();\n            }\n        }\n    }\n\n    text_update_time = SDL_GetTicks() - text_update_time;\n\n    text_vbo_commit_time = 0;\n    text_vbo_draw_time   = 0;\n\n    if(!gGourceSettings.ffp) {\n\n        text_vbo_commit_time = SDL_GetTicks();\n\n        fontmanager.commitBuffer();\n\n        text_vbo_commit_time = SDL_GetTicks() - text_vbo_commit_time;\n\n        text_vbo_draw_time = SDL_GetTicks();\n\n        text_shader->setSampler2D(\"tex\", 0);\n        text_shader->setFloat(\"shadow_strength\", 0.7);\n        text_shader->setFloat(\"texel_size\", font_texel_size);\n        text_shader->use();\n\n        fontmanager.drawBuffer();\n\n        glUseProgramObjectARB(0);\n\n        text_vbo_draw_time = SDL_GetTicks() - text_vbo_draw_time;\n    }\n\n    //draw selected item names again so they are over the top\n    if(selectedUser !=0) selectedUser->drawName();\n\n    if(selectedFile !=0) {\n            selectedFile->drawName();\n    }\n\n    //switch back\n    display.pop2D();\n\n    text_time = SDL_GetTicks() - text_time;\n\n    if(debug) {\n        glDisable(GL_TEXTURE_2D);\n        glLineWidth(2.0);\n        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);\n\n        track_users ? active_user_bounds.draw() : dir_bounds.draw();\n    }\n\n    if(gGourceQuadTreeDebug) {\n        glDisable(GL_TEXTURE_2D);\n\n        glLineWidth(1.0);\n\n        glColor4f(0.0f, 1.0f, 0.0f, 1.0f);\n\n        dirNodeTree->outline();\n\n        glColor4f(0.0f, 1.0f, 1.0f, 1.0f);\n\n        userTree->outline();\n\n        glColor4f(0.0f, 1.0f, 0.5f, 1.0f);\n\n        userTree->outlineItems();\n    }\n\n    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);\n\n    display.mode2D();\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glEnable(GL_BLEND);\n    glEnable(GL_TEXTURE_2D);\n\n    vec3 campos = camera.getPos();\n\n    if(logotex!=0) {\n        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n        glEnable(GL_BLEND);\n        glEnable(GL_TEXTURE_2D);\n\n        glColor4f(1.0, 1.0, 1.0, 1.0);\n\n        glBindTexture(GL_TEXTURE_2D, logotex->textureid);\n\n        vec2 logopos = vec2(display.width, display.height) - vec2(logotex->w, logotex->h) - gGourceSettings.logo_offset;\n\n        glPushMatrix();\n\n            glTranslatef(logopos.x, logopos.y, 0.0);\n\n            glBegin(GL_QUADS);\n                glTexCoord2f(0.0f,0.0f);\n                glVertex2i(0, 0);\n\n                glTexCoord2f(1.0f,0.0f);\n                glVertex2i(logotex->w, 0);\n\n                glTexCoord2f(1.0f,1.0f);\n                glVertex2i(logotex->w, logotex->h);\n\n                glTexCoord2f(0.0f,1.0f);\n                glVertex2i(0, logotex->h);\n            glEnd();\n\n        glPopMatrix();\n    }\n\n    font.roundCoordinates(true);\n\n    if(splash>0.0f) {\n        int logowidth = fontlarge.getWidth(\"Gource\");\n        int logoheight = 100 * gGourceSettings.font_scale;\n        int cwidth    = font.getWidth(\"Software Version Control Visualization\");\n        int awidth    = font.getWidth(\"(C) 2009 Andrew Caudwell\");\n\n        vec2 corner(display.width/2 - logowidth/2 - 30.0f * gGourceSettings.font_scale, display.height/2 - 40 * gGourceSettings.font_scale);\n\n        glDisable(GL_TEXTURE_2D);\n        glColor4f(0.0f, 0.5f, 1.0f, splash * 0.015f);\n        glBegin(GL_QUADS);\n            glVertex2f(0.0f,                 corner.y);\n            glVertex2f(0.0f,                 corner.y + logoheight);\n            glVertex2f(display.width, corner.y + logoheight);\n            glVertex2f(display.width, corner.y);\n        glEnd();\n\n        glEnable(GL_TEXTURE_2D);\n\n        fontlarge.setColour(vec4(1.0f));\n        fontlarge.draw(display.width/2 - logowidth/2,display.height/2 - 30 * gGourceSettings.font_scale, \"Gource\");\n\n        font.setColour(vec4(1.0f));\n        font.draw(display.width/2 - cwidth/2,display.height/2 + 10 * gGourceSettings.font_scale, \"Software Version Control Visualization\");\n        font.draw(display.width/2 - awidth/2,display.height/2 + 30 * gGourceSettings.font_scale, \"(C) 2009 Andrew Caudwell\");\n    }\n\n    // text using the specified font goes here\n\n    fontmedium.setColour(vec4(gGourceSettings.font_colour, 1.0f));\n\n    if(!gGourceSettings.hide_date) {\n        fontmedium.draw(display.width/2 - date_x_offset, 20, displaydate);\n    }\n\n    if(!gGourceSettings.title.empty()) {\n        fontmedium.alignTop(false);\n        fontmedium.draw(10, display.height - 10, gGourceSettings.title);\n        fontmedium.alignTop(true);\n    }\n\n    for(std::list<RCaption*>::iterator it = active_captions.begin(); it!=active_captions.end(); it++) {\n        RCaption* caption = *it;\n\n        caption->draw();\n    }\n\n    //file key\n    file_key.draw();\n    file_key.setShow(gGourceSettings.show_key);\n\n    //slider\n    if(canSeek()) {\n        slider.draw(dt);\n    }\n\n    //text box\n    if(hoverFile && hoverFile != selectedFile) {\n\n        std::string display_path = hoverFile->path;\n        display_path.erase(0,1);\n\n        textbox.setText(hoverFile->getName());\n        if(display_path.size()) textbox.addLine(display_path);\n        textbox.setColour(hoverFile->getColour());\n\n        textbox.setPos(mousepos, true);\n        textbox.draw();\n    } else if(hoverUser && hoverUser != selectedUser) {\n\n        textbox.setText(hoverUser->getName());\n        textbox.setColour(hoverUser->getColour());\n\n        textbox.setPos(mousepos, true);\n        textbox.draw();\n    }\n\n    //debug info\n\n    if(debug) {\n        font.setColour(vec4(1.0f));\n\n        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n        glEnable(GL_BLEND);\n        glEnable(GL_TEXTURE_2D);\n\n        font.print(1,20, \"FPS: %.2f\", fps);\n        font.print(1,40,\"Days Per Second: %.2f\",\n            gGourceSettings.days_per_second);\n        font.print(1,60,\"Commit Queue: %d\", commitqueue.size());\n        font.print(1,80,\"Users: %d\", users.size());\n        font.print(1,100,\"Files: %d\", files.size());\n        font.print(1,120,\"Dirs: %d\",  gGourceDirMap.size());\n\n        font.print(1,140,\"Log Position: %.4f\", commitlog->getPercent());\n        font.print(1,160,\"Camera: (%.2f, %.2f, %.2f)\", campos.x, campos.y, campos.z);\n        font.print(1,180,\"Gravity: %.2f\", gGourceForceGravity);\n        font.print(1,200,\"Update Tree: %u ms\", update_dir_tree_time);\n        font.print(1,220,\"Update VBOs: %u ms\", update_vbos_time);\n        font.print(1,240,\"Projection: %u ms\",  screen_project_time);\n\n        font.print(1,260,\"Draw Scene: %u ms\",  draw_scene_time);\n        font.print(1,280,\" - Edges: %u ms\",   draw_edges_time);\n        font.print(1,300,\" - Shadows: %u ms\", draw_shadows_time);\n        font.print(1,320,\" - Actions: %u ms\", draw_actions_time);\n        font.print(1,340,\" - Files: %u ms\",   draw_files_time);\n        font.print(1,360,\" - Users: %u ms\",   draw_users_time);\n        font.print(1,380,\" - Bloom: %u ms\",   draw_bloom_time);\n        font.print(1,400,\"Text: %u ms\",       text_time);\n        font.print(1,420,\"- Update: %u ms\",   text_update_time);\n        font.print(1,440,\"- VBO Commit: %u ms\", text_vbo_commit_time);\n        font.print(1,460,\"- VBO Draw: %u ms\",   text_vbo_draw_time);\n        font.print(1,480,\"Mouse Trace: %u ms\", trace_time);\n        font.print(1,500,\"Logic Time: %u ms\", logic_time);\n        font.print(1,520,\"File Inner Loops: %d\", gGourceFileInnerLoops);\n        font.print(1,540,\"User Inner Loops: %d\", gGourceUserInnerLoops);\n\n        font.print(1,560,\"Dir Inner Loops: %d (QTree items = %d, nodes = %d, max node depth = %d)\", gGourceDirNodeInnerLoops,\n            dirNodeTree->item_count, dirNodeTree->node_count, dirNodeTree->max_node_depth);\n\n        font.print(1,580,\"Dir Bounds Ratio: %.2f, %.5f\", dir_bounds.width() / dir_bounds.height(), rotation_remaining_angle);\n        font.print(1,600,\"String Hash Seed: %d\", gStringHashSeed);\n\n        if(!gGourceSettings.ffp) {\n            font.print(1,620,\"Text VBO: %d/%d vertices, %d texture changes\", fontmanager.font_vbo.vertices(), fontmanager.font_vbo.capacity(), fontmanager.font_vbo.texture_changes());\n            font.print(1,640,\"File VBO: %d/%d vertices, %d texture changes\", file_vbo.vertices(), file_vbo.capacity(), file_vbo.texture_changes());\n            font.print(1,660,\"User VBO: %d/%d vertices, %d texture changes\", user_vbo.vertices(), user_vbo.capacity(), user_vbo.texture_changes());\n            font.print(1,680,\"Action VBO: %d/%d vertices\", action_vbo.vertices(), action_vbo.capacity());\n            font.print(1,700,\"Bloom VBO: %d/%d vertices\", bloom_vbo.vertices(), bloom_vbo.capacity());\n            font.print(1,720,\"Edge VBO: %d/%d vertices\",  edge_vbo.vertices(), edge_vbo.capacity());\n        }\n\n        if(selectedUser != 0) {\n\n        }\n\n        if(selectedFile != 0) {\n            font.print(1,740,\"%s: %d files (%d visible)\", selectedFile->getDir()->getPath().c_str(),\n                    selectedFile->getDir()->fileCount(), selectedFile->getDir()->visibleFileCount());\n        }\n    }\n\n    mousemoved=false;\n    mouseclicked=false;\n\n    if(take_screenshot) {\n        screenshot();\n        take_screenshot = false;\n    }\n\n    if(message_timer > 0.0f) {\n        font.setColour(vec4(1.0f));\n\n        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n        glEnable(GL_BLEND);\n        glEnable(GL_TEXTURE_2D);\n\n        font.draw(1, 3, message);\n    }\n}\n"
  },
  {
    "path": "src/gource.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GOURCE_H\n#define GOURCE_H\n\n#include <deque>\n#include <list>\n#include <fstream>\n\n#include \"core/display.h\"\n#include \"core/shader.h\"\n#include \"core/sdlapp.h\"\n#include \"core/fxfont.h\"\n#include \"core/bounds.h\"\n#include \"core/seeklog.h\"\n#include \"core/frustum.h\"\n#include \"core/regex.h\"\n#include \"core/ppm.h\"\n#include \"core/mousecursor.h\"\n\n#include \"gource_settings.h\"\n\n#include \"logmill.h\"\n\n#include \"core/vbo.h\"\n#include \"bloom.h\"\n#include \"slider.h\"\n#include \"textbox.h\"\n#include \"action.h\"\n#include \"caption.h\"\n#include \"file.h\"\n#include \"user.h\"\n#include \"dirnode.h\"\n#include \"zoomcamera.h\"\n#include \"key.h\"\n\nclass Gource : public SDLApp {\n    std::string logfile;\n\n    FrameExporter* frameExporter;\n\n    RLogMill* logmill;\n\n    RCommitLog* commitlog;\n    PositionSlider slider;\n    ZoomCamera camera;\n\n    FileKey file_key;\n\n    bool debug, trace_debug;\n\n    bool manual_zoom;\n    bool manual_rotate;\n    bool manual_camera;\n\n    float rotation_remaining_angle;\n\n    MouseCursor cursor;\n\n    bool grab_mouse;\n    bool mousemoved;\n    bool mouseclicked;\n    bool mousedragged;\n\n    vec2 cursor_move;\n\n    bool recolour;\n\n    bool use_selection_bounds;\n    Bounds2D selection_bounds;\n\n    float rotate_angle;\n\n    vec2 mousepos;\n\n    float last_percent;\n\n    bool stop_position_reached;\n\n    int tag_seq, commit_seq;\n\n    GLint mouse_hits;\n\n    RFile* hoverFile;\n    RFile* selectedFile;\n\n    RUser* hoverUser;\n    RUser* selectedUser;\n\n    quadbuf  file_vbo;\n    quadbuf  user_vbo;\n    quadbuf  edge_vbo;\n    quadbuf  action_vbo;\n\n    bloombuf bloom_vbo;\n\n    GLuint selectionDepth;\n\n    RDirNode* root;\n\n    std::string displaydate;\n    int date_x_offset;\n\n    TextureResource* bloomtex;\n    TextureResource* beamtex;\n    TextureResource* logotex;\n    TextureResource* backgroundtex;\n    TextureResource* usertex;\n\n    Shader*          shadow_shader;\n    Shader*          text_shader;\n    Shader*          bloom_shader;\n\n    float font_texel_size;\n\n    TextBox textbox;\n\n    FXFont font, fontlarge, fontmedium, fontcaption, fontdirname;\n\n    bool first_read;\n    bool paused;\n    bool reloaded;\n\n    bool take_screenshot;\n\n    float max_tick_rate;\n    int frameskip;\n    int framecount;\n\n    time_t currtime;\n    time_t lasttime;\n    float runtime;\n    float subseconds;\n\n    float splash;\n\n    float idle_time;\n\n    Uint32 screen_project_time;\n    Uint32 draw_edges_time;\n    Uint32 draw_shadows_time;\n    Uint32 draw_actions_time;\n    Uint32 draw_files_time;\n    Uint32 draw_users_time;\n    Uint32 draw_bloom_time;\n    Uint32 update_vbos_time;\n    Uint32 update_dir_tree_time;\n    Uint32 update_user_tree_time;\n    Uint32 draw_scene_time;\n    Uint32 logic_time;\n    Uint32 trace_time;\n    Uint32 text_time;\n    Uint32 text_update_time;\n    Uint32 text_vbo_commit_time;\n    Uint32 text_vbo_draw_time;\n\n    bool track_users;\n\n    Bounds2D dir_bounds;\n    Bounds2D user_bounds;\n    Bounds2D active_user_bounds;\n\n    int commitqueue_max_size;\n    float starting_z;\n\n    std::deque<RCommit> commitqueue;\n    std::map<std::string, RUser*> users;\n    std::map<std::string, RFile*> files;\n    std::map<int, RUser*> tagusermap;\n\n    std::list<RCaption*> captions;\n    std::list<RCaption*> active_captions;\n\n    QuadTree* dirNodeTree;\n    QuadTree* userTree;\n\n    std::string message;\n    float message_timer;\n\n    void setMessage(const char* str, ...);\n\n    void reset();\n\n    RUser* addUser(const std::string& username);\n    RFile* addFile(const RCommitFile& cf);\n\n    void deleteUser(RUser* user);\n    void deleteFile(RFile* file);\n\n    void selectBackground();\n    void selectUser(RUser* user);\n    void selectFile(RFile* file);\n    void selectNextUser();\n\n    void loadCaptions();\n\n    void readLog();\n\n    void logReadingError(const std::string& error);\n\n    void processCommit(const RCommit& commit, float t);\n    void addFileAction(const RCommit& commit, const RCommitFile& cf, RFile* file, float t);\n\n    std::string dateAtPosition(float percent);\n\n    void toggleCameraMode();\n\n    void updateCamera(float dt);\n\n    void updateUsers(float t, float dt);\n    void updateDirs(float dt);\n\n    void interactUsers();\n    void interactDirs();\n\n    void updateBounds();\n\n    void updateTime(time_t display_time);\n\n    void mousetrace(float dt);\n\n    bool canSeek();\n    void seekTo(float percent);\n\n    void zoom(bool zoomin);\n\n    void loadingScreen();\n    void drawBackground(float dt);\n\n    void drawScene(float dt);\n\n    void updateVBOs(float dt);\n\n    void updateAndDrawEdges();\n\n    void drawFileShadows(float dt);\n    void drawUserShadows(float dt);\n    void drawActions(float dt);\n    void drawFiles(float dt);\n    void drawUsers(float dt);\n    void drawBloom(float dt);\n\n    void screenshot();\n\n    void changeColours();\n\n    void grabMouse(bool grab_mouse);\npublic:\n    Gource(FrameExporter* frameExporter = 0);\n    ~Gource();\n\n    static void writeCustomLog(const std::string& logfile, const std::string& output_file);\n\n    void setCameraMode(const std::string& mode);\n    void setCameraMode(bool track_users);\n    void setFrameExporter(FrameExporter* exporter, int video_framerate);\n\n    void showSplash();\n\n    bool isBusy();\n\n    void logic(float t, float dt);\n    void draw(float t, float dt);\n\n    void init();\n\n    void unload();\n    void reload();\n\n    void quit();\n\n    void update(float t, float dt);\n    void keyPress(SDL_KeyboardEvent *e);\n    void mouseMove(SDL_MouseMotionEvent *e);\n    void mouseClick(SDL_MouseButtonEvent *e);\n#if SDL_VERSION_ATLEAST(2,0,0)\n    void mouseWheel(SDL_MouseWheelEvent *e);\n#endif\n};\n\n#endif\n"
  },
  {
    "path": "src/gource_settings.cpp",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"gource_settings.h\"\n#include \"core/sdlapp.h\"\n\n#include <boost/filesystem.hpp>\n#include <boost/format.hpp>\n#include <boost/algorithm/string.hpp>\n\n#include \"core/utf8/utf8.h\"\n#include <time.h>\n\n#include \"formats/hg.h\"\n#include \"formats/git.h\"\n#include \"formats/bzr.h\"\n#include \"formats/cvs-exp.h\"\n#include \"formats/cvs2cl.h\"\n#include \"formats/svn.h\"\n\n#ifndef GOURCE_FONT_FILE\n#define GOURCE_FONT_FILE \"FreeSans.ttf\"\n#endif\n\nGourceSettings gGourceSettings;\n\n//display help message\nvoid GourceSettings::help(bool extended_help) {\n\n#ifdef _WIN32\n    //resize window to fit help message\n    SDLApp::resizeConsole(1040);\n    SDLApp::showConsole(true);\n#endif\n\n    printf(\"Gource v%s\\n\", GOURCE_VERSION);\n\n    printf(\"Usage: gource [options] [path]\\n\");\n    printf(\"\\nOptions:\\n\");\n    printf(\"  -h, --help                       Help\\n\\n\");\n    printf(\"  -WIDTHxHEIGHT, --viewport        Set viewport size\\n\");\n    printf(\"  -f, --fullscreen                 Fullscreen\\n\");\n    printf(\"      --screen SCREEN              Screen number\\n\");\n    printf(\"      --multi-sampling             Enable multi-sampling\\n\");\n#ifndef _WIN32\n    printf(\"      --high-dpi                   Request a high DPI display\\n\");\n#endif\n    printf(\"      --no-vsync                   Disable vsync\\n\\n\");\n\n    printf(\"  --start-date 'YYYY-MM-DD hh:mm:ss +tz'  Start at a date and optional time\\n\");\n    printf(\"  --stop-date  'YYYY-MM-DD hh:mm:ss +tz'  Stop at a date and optional time\\n\\n\");\n    printf(\"  -p, --start-position POSITION    Start at some position (0.0-1.0 or 'random')\\n\");\n    printf(\"      --stop-position  POSITION    Stop at some position\\n\");\n    printf(\"  -t, --stop-at-time SECONDS       Stop after a specified number of seconds\\n\");\n    printf(\"      --stop-at-end                Stop at end of the log\\n\");\n    printf(\"      --dont-stop                  Keep running after the end of the log\\n\");\n    printf(\"      --loop                       Loop at the end of the log\\n\\n\");\n\n    printf(\"  -a, --auto-skip-seconds SECONDS  Auto skip to next entry if nothing happens\\n\");\n    printf(\"                                   for a number of seconds (default: 3)\\n\");\n    printf(\"      --disable-auto-skip          Disable auto skip\\n\");\n    printf(\"  -s, --seconds-per-day SECONDS    Speed in seconds per day (default: 10)\\n\");\n    printf(\"      --realtime                   Realtime playback speed\\n\");\n    printf(\"      --no-time-travel             Use the time of the last commit if the\\n\");\n    printf(\"                                   time of a commit is in the past\\n\");\n    printf(\"      --author-time                Use the timestamp of the author instead of\\n\");\n    printf(\"                                   the timestamp of the committer\\n\");\n    printf(\"  -c, --time-scale SCALE           Change simulation time scale (default: 1.0)\\n\");\n    printf(\"  -e, --elasticity FLOAT           Elasticity of nodes (default: 0.0)\\n\\n\");\n\n    printf(\"  --key                            Show file extension key\\n\\n\");\n\n    printf(\"  --user-image-dir DIRECTORY       Dir containing images to use as avatars\\n\");\n    printf(\"  --default-user-image IMAGE       Default user image file\\n\");\n    printf(\"  --fixed-user-size                Use a fixed size throughout\\n\");\n    printf(\"  --colour-images                  Colourize user images\\n\\n\");\n\n    printf(\"  -i, --file-idle-time SECONDS     Time files remain idle (default: 0)\\n\");\n    printf(\"  --file-idle-time-at-end SECONDS  Time files remain idle at end (default: 0)\\n\\n\");\n\n    printf(\"  --max-files NUMBER      Max number of files or 0 for no limit\\n\");\n    printf(\"  --max-file-lag SECONDS  Max time files of a commit can take to appear\\n\\n\");\n\n    printf(\"  --log-command VCS       Show the VCS log command (git,svn,hg,bzr,cvs2cl)\\n\");\n    printf(\"  --log-format  VCS       Specify the log format (git,svn,hg,bzr,cvs2cl,custom)\\n\\n\");\n\n    printf(\"  --load-config CONF_FILE  Load a config file\\n\");\n    printf(\"  --save-config CONF_FILE  Save a config file with the current options\\n\\n\");\n\n    printf(\"  -o, --output-ppm-stream FILE    Output PPM stream to a file ('-' for STDOUT)\\n\");\n    printf(\"  -r, --output-framerate  FPS     Framerate of output (25,30,60)\\n\\n\");\n\nif(extended_help) {\n    printf(\"Extended Options:\\n\\n\");\n\n    printf(\"  --window-position XxY    Initial window position\\n\");\n    printf(\"  --frameless              Frameless window\\n\\n\");\n\n    printf(\"  --output-custom-log FILE  Output a custom format log file ('-' for STDOUT).\\n\\n\");\n\n    printf(\"  -b, --background-colour  FFFFFF    Background colour in hex\\n\");\n    printf(\"      --background-image   IMAGE     Set a background image\\n\\n\");\n\n    printf(\"  --bloom-multiplier       Adjust the amount of bloom (default: 1.0)\\n\");\n    printf(\"  --bloom-intensity        Adjust the intensity of the bloom (default: 0.75)\\n\\n\");\n\n    printf(\"  --camera-mode MODE       Camera mode (overview,track)\\n\");\n    printf(\"  --crop AXIS              Crop view on an axis (vertical,horizontal)\\n\");\n    printf(\"  --padding FLOAT          Camera view padding (default: 1.1)\\n\\n\");\n\n    printf(\"  --disable-auto-rotate    Disable automatic camera rotation\\n\\n\");\n\n    printf(\"  --disable-input          Disable keyboard and mouse input\\n\\n\");\n\n    printf(\"  --date-format FORMAT     Specify display date string (strftime format)\\n\\n\");\n\n    printf(\"  --font-file FILE         Specify the font\\n\");\n    printf(\"  --font-scale SCALE       Scale the size of all fonts\\n\");\n    printf(\"  --font-size SIZE         Font size used by date and title\\n\");\n    printf(\"  --file-font-size SIZE    Font size for filenames\\n\");\n    printf(\"  --dir-font-size SIZE     Font size for directory names\\n\");\n    printf(\"  --user-font-size SIZE    Font size for user names\\n\");\n    printf(\"  --font-colour FFFFFF     Font colour used by date and title in hex\\n\\n\");\n\n    printf(\"  --file-extensions          Show filename extensions only\\n\");\n    printf(\"  --file-extension-fallback  Use filename as extension if the extension\\n\");\n    printf(\"                             is missing or empty\\n\\n\");\n\n    printf(\"  --git-branch             Get the git log of a particular branch\\n\\n\");\n\n    printf(\"  --hide DISPLAY_ELEMENT   bloom,date,dirnames,files,filenames,mouse,progress,\\n\");\n    printf(\"                           root,tree,users,usernames\\n\\n\");\n\n    printf(\"  --logo IMAGE             Logo to display in the foreground\\n\");\n    printf(\"  --logo-offset XxY        Offset position of the logo\\n\\n\");\n\n    printf(\"  --loop-delay-seconds SECONDS Seconds to delay before looping (default: 3)\\n\\n\");\n\n    printf(\"  --title TITLE            Set a title\\n\\n\");\n\n    printf(\"  --transparent            Make the background transparent\\n\\n\");\n\n    printf(\"  --user-filter REGEX      Ignore usernames matching this regex\\n\");\n    printf(\"  --user-show-filter REGEX Show only usernames matching this regex\\n\\n\");\n    printf(\"  --file-filter REGEX      Ignore file paths matching this regex\\n\");\n    printf(\"  --file-show-filter REGEX Show only file paths matching this regex\\n\\n\");\n\n    printf(\"  --user-friction SECONDS  Change the rate users slow down (default: 0.67)\\n\");\n    printf(\"  --user-scale SCALE       Change scale of users (default: 1.0)\\n\");\n    printf(\"  --max-user-speed UNITS   Speed users can travel per second (default: 500)\\n\\n\");\n\n    printf(\"  --follow-user USER       Camera will automatically follow this user\\n\");\n    printf(\"  --highlight-dirs         Highlight the names of all directories\\n\");\n    printf(\"  --highlight-user USER    Highlight the names of a particular user\\n\");\n    printf(\"  --highlight-users        Highlight the names of all users\\n\\n\");\n\n    printf(\"  --highlight-colour       Font colour for highlighted users in hex.\\n\");\n    printf(\"  --selection-colour       Font colour for selected users and files.\\n\");\n    printf(\"  --filename-colour        Font colour for filenames.\\n\");\n    printf(\"  --dir-colour             Font colour for directories.\\n\\n\");\n\n    printf(\"  --dir-name-depth DEPTH    Draw names of directories down to a specific depth.\\n\");\n    printf(\"  --dir-name-position FLOAT Position along edge of the directory name\\n\");\n    printf(\"                            (between 0.0 and 1.0, default is 0.5).\\n\\n\");\n\n    printf(\"  --filename-time SECONDS  Duration to keep filenames on screen (default: 4.0)\\n\\n\");\n\n    printf(\"  --caption-file FILE         Caption file\\n\");\n    printf(\"  --caption-size SIZE         Caption font size\\n\");\n    printf(\"  --caption-colour FFFFFF     Caption colour in hex\\n\");\n    printf(\"  --caption-duration SECONDS  Caption duration (default: 10.0)\\n\");\n    printf(\"  --caption-offset X          Caption horizontal offset\\n\\n\");\n\n    printf(\"  --hash-seed SEED         Change the seed of hash function.\\n\\n\");\n\n    printf(\"  --path PATH\\n\\n\");\n}\n\n    printf(\"PATH may be a supported version control directory, a log file, a gource config\\n\");\n    printf(\"file, or '-' to read STDIN. If omitted, gource will attempt to generate a log\\n\");\n    printf(\"from the current directory.\\n\\n\");\n\n    if(!extended_help) {\n        printf(\"To see the full command line options use '-H'\\n\\n\");\n    }\n\n#ifdef _WIN32\n    if(!SDLApp::existing_console) {\n        printf(\"Press Enter\\n\");\n        getchar();\n    }\n#endif\n\n    exit(0);\n}\n\nGourceSettings::GourceSettings() {\n    repo_count = 0;\n    file_graphic = 0;\n    log_level = LOG_LEVEL_OFF;\n    shutdown = false;\n\n    setGourceDefaults();\n\n    default_section_name = \"gource\";\n\n    //translate args\n    arg_aliases[\"p\"] = \"start-position\";\n    arg_aliases[\"a\"] = \"auto-skip-seconds\";\n    arg_aliases[\"s\"] = \"seconds-per-day\";\n    arg_aliases[\"t\"] = \"stop-at-time\";\n    arg_aliases[\"i\"] = \"file-idle-time\";\n    arg_aliases[\"e\"] = \"elasticity\";\n    arg_aliases[\"h\"] = \"help\";\n    arg_aliases[\"?\"] = \"help\";\n    arg_aliases[\"H\"] = \"extended-help\";\n    arg_aliases[\"b\"] = \"background-colour\";\n    arg_aliases[\"c\"] = \"time-scale\";\n    arg_aliases[\"background\"]          = \"background-colour\";\n    arg_aliases[\"disable-bloom\"]       = \"hide-bloom\";\n    arg_aliases[\"disable-progress\"]    = \"hide-progress\";\n    arg_aliases[\"highlight-all-users\"] = \"highlight-users\";\n\n    //command line only options\n    conf_sections[\"help\"]            = \"command-line\";\n    conf_sections[\"extended-help\"]   = \"command-line\";\n    conf_sections[\"log-command\"]     = \"command-line\";\n    conf_sections[\"git-log-command\"] = \"command-line\";\n    conf_sections[\"cvs-exp-command\"] = \"command-line\";\n    conf_sections[\"cvs2cl-command\"]  = \"command-line\";\n    conf_sections[\"hg-log-command\"]  = \"command-line\";\n    conf_sections[\"bzr-log-command\"] = \"command-line\";\n    conf_sections[\"svn-log-command\"] = \"command-line\";\n    conf_sections[\"load-config\"]     = \"command-line\";\n    conf_sections[\"save-config\"]     = \"command-line\";\n    conf_sections[\"output-custom-log\"] = \"command-line\";\n    conf_sections[\"log-level\"]         = \"command-line\";\n\n    //boolean args\n    arg_types[\"help\"]                    = \"bool\";\n    arg_types[\"extended-help\"]           = \"bool\";\n    arg_types[\"stop-on-idle\"]            = \"bool\";\n    arg_types[\"stop-at-end\"]             = \"bool\";\n    arg_types[\"dont-stop\"]               = \"bool\";\n    arg_types[\"loop\"]                    = \"bool\";\n    arg_types[\"realtime\"]                = \"bool\";\n    arg_types[\"no-time-travel\"]          = \"bool\";\n    arg_types[\"colour-images\"]           = \"bool\";\n    arg_types[\"hide-date\"]               = \"bool\";\n    arg_types[\"hide-files\"]              = \"bool\";\n    arg_types[\"hide-users\"]              = \"bool\";\n    arg_types[\"hide-tree\"]               = \"bool\";\n    arg_types[\"hide-usernames\"]          = \"bool\";\n    arg_types[\"hide-filenames\"]          = \"bool\";\n    arg_types[\"hide-dirnames\"]           = \"bool\";\n    arg_types[\"hide-progress\"]           = \"bool\";\n    arg_types[\"hide-bloom\"]              = \"bool\";\n    arg_types[\"hide-mouse\"]              = \"bool\";\n    arg_types[\"hide-root\"]               = \"bool\";\n    arg_types[\"highlight-users\"]         = \"bool\";\n    arg_types[\"highlight-dirs\"]          = \"bool\";\n    arg_types[\"file-extensions\"]         = \"bool\";\n    arg_types[\"file-extension-fallback\"] = \"bool\";\n    arg_types[\"fixed-user-size\"]         = \"bool\";\n    arg_types[\"author-time\"]             = \"bool\";\n    arg_types[\"key\"]                     = \"bool\";\n    arg_types[\"ffp\"]                     = \"bool\";\n\n    arg_types[\"disable-auto-rotate\"] = \"bool\";\n    arg_types[\"disable-auto-skip\"]   = \"bool\";\n    arg_types[\"disable-input\"]       = \"bool\";\n\n    arg_types[\"git-log-command\"]= \"bool\";\n    arg_types[\"cvs-exp-command\"]= \"bool\";\n    arg_types[\"cvs2cl-command\"] = \"bool\";\n    arg_types[\"svn-log-command\"]= \"bool\";\n    arg_types[\"hg-log-command\"] = \"bool\";\n    arg_types[\"bzr-log-command\"]= \"bool\";\n\n    arg_types[\"bloom-intensity\"]   = \"float\";\n    arg_types[\"bloom-multiplier\"]  = \"float\";\n    arg_types[\"elasticity\"]        = \"float\";\n    arg_types[\"seconds-per-day\"]   = \"float\";\n    arg_types[\"auto-skip-seconds\"] = \"float\";\n    arg_types[\"stop-at-time\"]      = \"float\";\n    arg_types[\"max-user-speed\"]    = \"float\";\n    arg_types[\"user-friction\"]     = \"float\";\n    arg_types[\"padding\"]           = \"float\";\n    arg_types[\"time-scale\"]        = \"float\";\n    arg_types[\"dir-name-position\"] = \"float\";\n    arg_types[\"loop-delay-seconds\"] = \"float\";\n\n    arg_types[\"max-files\"] = \"int\";\n    arg_types[\"font-size\"] = \"int\";\n    arg_types[\"font-scale\"] = \"float\";\n    arg_types[\"file-font-size\"] = \"int\";\n    arg_types[\"dir-font-size\"] = \"int\";\n    arg_types[\"user-font-size\"] = \"int\";\n    arg_types[\"hash-seed\"] = \"int\";\n\n    arg_types[\"user-filter\"]      = \"multi-value\";\n    arg_types[\"user-show-filter\"] = \"multi-value\";\n    arg_types[\"file-filter\"]      = \"multi-value\";\n    arg_types[\"file-show-filter\"] = \"multi-value\";\n    arg_types[\"follow-user\"]      = \"multi-value\";\n    arg_types[\"highlight-user\"]   = \"multi-value\";\n\n    arg_types[\"log-level\"]          = \"string\";\n    arg_types[\"background-image\"]   = \"string\";\n    arg_types[\"logo\"]               = \"string\";\n    arg_types[\"logo-offset\"]        = \"string\";\n    arg_types[\"log-command\"]        = \"string\";\n    arg_types[\"load-config\"]        = \"string\";\n    arg_types[\"save-config\"]        = \"string\";\n    arg_types[\"output-custom-log\"]  = \"string\";\n    arg_types[\"path\"]               = \"string\";\n    arg_types[\"log-command\"]        = \"string\";\n    arg_types[\"background-colour\"]  = \"string\";\n    arg_types[\"file-idle-time\"]     = \"string\";\n    arg_types[\"file-idle-time-at-end\"] = \"string\";\n    arg_types[\"user-image-dir\"]     = \"string\";\n    arg_types[\"default-user-image\"] = \"string\";\n    arg_types[\"date-format\"]        = \"string\";\n    arg_types[\"log-format\"]         = \"string\";\n    arg_types[\"git-branch\"]         = \"string\";\n    arg_types[\"start-position\"]     = \"string\";\n    arg_types[\"start-date\"]         = \"string\";\n    arg_types[\"stop-date\"]          = \"string\";\n    arg_types[\"stop-position\"]      = \"string\";\n    arg_types[\"crop\"]               = \"string\";\n    arg_types[\"hide\"]               = \"string\";\n    arg_types[\"max-file-lag\"]       = \"string\";\n    arg_types[\"user-scale\"]         = \"string\";\n    arg_types[\"camera-mode\"]        = \"string\";\n    arg_types[\"title\"]              = \"string\";\n    arg_types[\"font-file\"]          = \"string\";\n    arg_types[\"font-colour\"]        = \"string\";\n    arg_types[\"highlight-colour\"]   = \"string\";\n    arg_types[\"selection-colour\"]   = \"string\";\n    arg_types[\"dir-colour\"]         = \"string\";\n\n    arg_types[\"caption-file\"]       = \"string\";\n    arg_types[\"caption-size\"]       = \"int\";\n    arg_types[\"caption-duration\"]   = \"float\";\n    arg_types[\"caption-colour\"]     = \"string\";\n    arg_types[\"caption-offset\"]     = \"int\";\n\n    arg_types[\"filename-colour\"]    = \"string\";\n    arg_types[\"filename-time\"]      = \"float\";\n\n    arg_types[\"dir-name-depth\"]     = \"int\";\n}\n\nvoid GourceSettings::setGourceDefaults() {\n\n    path = \".\";\n    default_path = true;\n\n    ffp = false;\n\n    hide_date      = false;\n    hide_users     = false;\n    hide_tree      = false;\n    hide_files     = false;\n    hide_usernames = false;\n    hide_filenames = false;\n    hide_dirnames  = false;\n    hide_progress  = false;\n    hide_bloom     = false;\n    hide_mouse     = false;\n    hide_root      = false;\n\n    start_timestamp = 0;\n    start_date = \"\";\n\n    stop_timestamp = 0;\n    stop_date = \"\";\n\n    start_position  = 0.0f;\n    stop_position   = 0.0f;\n    stop_at_time    = -1.0f;\n    stop_on_idle    = false;\n    stop_at_end     = false;\n    dont_stop       = false;\n    no_time_travel  = false;\n    fixed_user_size = false;\n    author_time     = false;\n\n    show_key = false;\n\n    disable_auto_rotate = false;\n\n    disable_input = false;\n\n    auto_skip_seconds     = 3.0f;\n    days_per_second       = 0.1f; // TODO: check this is right\n    file_idle_time        = 0.0f;\n    file_idle_time_at_end = 0.0f;\n    time_scale            = 1.0f;\n\n    loop = false;\n    loop_delay_seconds = 3.0f;\n\n    logo = \"\";\n    logo_offset = vec2(20.0f,20.0f);\n\n    colour_user_images = false;\n    default_user_image = \"\";\n    user_image_dir     = \"\";\n    user_image_map.clear();\n\n    camera_zoom_min     = 50.0f;\n    camera_zoom_default = 100.0f;\n    camera_zoom_max     = 10000.0f;\n\n    camera_mode     = \"overview\";\n    padding         = 1.1f;\n\n    crop_vertical   = false;\n    crop_horizontal = false;\n\n    bloom_multiplier = 1.0f;\n    bloom_intensity  = 0.75f;\n\n    background_colour = vec3(0.1f, 0.1f, 0.1f);\n    background_image  = \"\";\n\n    title             = \"\";\n\n    font_scale = 1.0f;\n    default_font_scale = true;\n    font_file = GOURCE_FONT_FILE;\n    font_size = 16;\n    filename_font_size = 14;\n    dirname_font_size = 14;\n    user_font_size = 14;\n\n    dir_colour       = vec3(1.0f);\n    font_colour      = vec3(1.0f);\n    highlight_colour = vec3(1.0f);\n    selection_colour = vec3(1.0, 1.0, 0.3f);\n\n    dir_name_depth = 0;\n    dir_name_position = 0.5f;\n\n    elasticity = 0.0f;\n\n    git_branch = \"\";\n\n    log_format  = \"\";\n    date_format = \"%A, %d %B, %Y %X\";\n\n    max_files      = 0;\n    max_user_speed = 500.0f;\n    max_file_lag   = 5.0f;\n\n    user_idle_time = 3.0f;\n    user_friction  = 1.0f;\n    user_scale     = 1.0f;\n\n    follow_users.clear();\n    highlight_users.clear();\n    highlight_all_users = false;\n    highlight_dirs = false;\n\n    caption_file     = \"\";\n    caption_duration = 10.0f;\n    caption_size     = 16;\n    caption_offset   = 0;\n    caption_colour   = vec3(1.0f, 1.0f, 1.0f);\n\n    filename_colour  = vec3(1.0f, 1.0f, 1.0f);\n    filename_time = 4.0f;\n\n    gStringHashSeed = 31;\n\n    //delete file filters\n    for(std::vector<Regex*>::iterator it = file_filters.begin(); it != file_filters.end(); it++) {\n        delete (*it);\n    }\n    file_filters.clear();\n\n    //delete file whitelists\n    for(std::vector<Regex*>::iterator it = file_show_filters.begin(); it != file_show_filters.end(); it++) {\n        delete (*it);\n    }    \n    file_show_filters.clear();\n\n    file_extensions = false;\n    file_extension_fallback = false;\n\n    //delete user filters\n    for(std::vector<Regex*>::iterator it = user_filters.begin(); it != user_filters.end(); it++) {\n        delete (*it);\n    }\n    user_filters.clear();\n\n    //delete user whitelist\n    for(std::vector<Regex*>::iterator it = user_show_filters.begin(); it != user_show_filters.end(); it++) {\n        delete (*it);\n    }\n    user_show_filters.clear();\n\n\n    setScaledFontSizes();\n}\n\nvoid GourceSettings::setScaledFontSizes() {\n    scaled_font_size           = glm::clamp((int)(font_size * font_scale), 1, 100);\n    scaled_user_font_size      = glm::clamp((int)(user_font_size * font_scale), 1, 100);\n    scaled_dirname_font_size   = glm::clamp((int)(dirname_font_size * font_scale), 1, 100);\n    scaled_filename_font_size  = glm::clamp((int)(filename_font_size * font_scale), 1, 100);\n}\n\nvoid GourceSettings::commandLineOption(const std::string& name, const std::string& value) {\n\n    if(name == \"help\") {\n        help();\n    }\n\n    if(name == \"extended-help\") {\n        help(true);\n    }\n\n    if(name == \"load-config\" && value.size() > 0) {\n        load_config = value;\n        return;\n    }\n\n    if(name == \"save-config\" && value.size() > 0) {\n        save_config = value;\n        return;\n    }\n\n    std::string log_command;\n\n    if(name == \"log-command\") {\n        log_command = value;\n    }\n\n    if(name == \"git-log-command\" || log_command == \"git\") {\n        SDLAppInfo(GitCommitLog::logCommand());\n    }\n\n    if(name == \"cvs-exp-command\" || log_command == \"cvs-exp\") {\n        SDLAppInfo(CVSEXPCommitLog::logCommand());\n    }\n\n    if(log_command == \"cvs\") {\n        throw ConfFileException(\"please use either 'cvs2cl' or 'cvs-exp'\", \"\", 0);\n    }\n\n    if(name == \"cvs2cl-command\" || log_command == \"cvs2cl\") {\n        SDLAppInfo(CVS2CLCommitLog::logCommand());\n    }\n\n    if(name == \"svn-log-command\" || log_command == \"svn\") {\n        SDLAppInfo(SVNCommitLog::logCommand());\n    }\n\n    if(name == \"hg-log-command\" || log_command == \"hg\") {\n        SDLAppInfo(MercurialLog::logCommand());\n    }\n\n    if(name == \"bzr-log-command\" || log_command == \"bzr\") {\n        SDLAppInfo(BazaarLog::logCommand());\n    }\n\n    if(name == \"output-custom-log\" && value.size() > 0) {\n        output_custom_filename = value;\n        return;\n    }\n\n    if(name == \"log-level\") {\n        if(value == \"warn\") {\n            log_level = LOG_LEVEL_WARN;\n        } else if(value == \"debug\") {\n            log_level = LOG_LEVEL_DEBUG;\n        } else if(value == \"info\") {\n            log_level = LOG_LEVEL_INFO;\n        } else if(value == \"error\") {\n            log_level = LOG_LEVEL_ERROR;\n        } else if(value == \"pedantic\") {\n            log_level = LOG_LEVEL_PEDANTIC;\n        }\n        return;\n    }\n\n    std::string invalid_error = std::string(\"invalid \") + name + std::string(\" value\");\n    throw ConfFileException(invalid_error, \"\", 0);\n}\n\n\n#ifdef __APPLE__\n#include <CoreFoundation/CoreFoundation.h>\n#endif\nvoid GourceSettings::importGourceSettings(ConfFile& conffile, ConfSection* gource_settings) {\n\n    setGourceDefaults();\n\n    if(gource_settings == 0) gource_settings = conffile.getSection(default_section_name);\n\n    if(gource_settings == 0) {\n        gource_settings = conffile.addSection(\"gource\");\n    }\n\n    ConfEntry* entry = 0;\n\n    //hide flags\n\n    std::vector<std::string> hide_fields;\n\n    if((entry = gource_settings->getEntry(\"hide\")) != 0) {\n\n        if(!entry->hasValue()) conffile.missingValueException(entry);\n\n        std::string hide_string = entry->getString();\n\n        size_t sep;\n        while((sep = hide_string.find(\",\")) != std::string::npos) {\n\n            if(sep == 0 && hide_string.size()==1) break;\n\n            if(sep == 0) {\n                hide_string = hide_string.substr(sep+1, hide_string.size()-1);\n                continue;\n            }\n\n            std::string hide_field  = hide_string.substr(0, sep);\n            hide_fields.push_back(hide_field);\n            hide_string = hide_string.substr(sep+1, hide_string.size()-1);\n        }\n\n        if(hide_string.size() > 0 && hide_string != \",\") hide_fields.push_back(hide_string);\n\n        //validate field list\n\n        for(std::vector<std::string>::iterator it = hide_fields.begin(); it != hide_fields.end(); it++) {\n            std::string hide_field = (*it);\n\n            if(   hide_field != \"date\"\n               && hide_field != \"users\"\n               && hide_field != \"tree\"\n               && hide_field != \"files\"\n               && hide_field != \"usernames\"\n               && hide_field != \"filenames\"\n               && hide_field != \"dirnames\"\n               && hide_field != \"bloom\"\n               && hide_field != \"progress\"\n               && hide_field != \"mouse\"\n               && hide_field != \"root\") {\n                std::string unknown_hide_option = std::string(\"unknown option hide \") + hide_field;\n                conffile.entryException(entry, unknown_hide_option);\n            }\n        }\n    }\n\n    //check hide booleans\n    for(std::map<std::string,std::string>::iterator it = arg_types.begin(); it != arg_types.end(); it++) {\n        if(it->first.find(\"hide-\") == 0 && it->second == \"bool\") {\n\n            if(gource_settings->getBool(it->first)) {\n                std::string hide_field = it->first.substr(5, it->first.size()-5);\n                hide_fields.push_back(hide_field);\n            }\n        }\n    }\n\n    if(hide_fields.size()>0) {\n\n        for(std::vector<std::string>::iterator it = hide_fields.begin(); it != hide_fields.end(); it++) {\n            std::string hidestr = (*it);\n\n                if(hidestr == \"date\")       hide_date      = true;\n            else if(hidestr == \"users\")     hide_users     = true;\n            else if(hidestr == \"tree\")      hide_tree      = true;\n            else if(hidestr == \"files\")     hide_files     = true;\n            else if(hidestr == \"usernames\") hide_usernames = true;\n            else if(hidestr == \"filenames\") hide_filenames = true;\n            else if(hidestr == \"dirnames\")  hide_dirnames  = true;\n            else if(hidestr == \"bloom\")     hide_bloom     = true;\n            else if(hidestr == \"progress\")  hide_progress  = true;\n            else if(hidestr == \"root\")      hide_root      = true;\n            else if(hidestr == \"mouse\")     {\n                hide_mouse     = true;\n                hide_progress  = true;\n            }\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"date-format\")) != 0) {\n\n        if(!entry->hasValue()) conffile.missingValueException(entry);\n\n        date_format = entry->getString();\n    }\n\n    if(gource_settings->getBool(\"disable-auto-rotate\")) {\n        disable_auto_rotate=true;\n    }\n\n    if(gource_settings->getBool(\"disable-auto-skip\")) {\n        auto_skip_seconds = -1.0;\n    }\n\n    if(gource_settings->getBool(\"disable-input\")) {\n        disable_input=true;\n    }\n\n    if(gource_settings->getBool(\"loop\")) {\n        loop = true;\n    }\n\n    if((entry = gource_settings->getEntry(\"loop-delay-seconds\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify loop-delay-seconds (float)\");\n\n        loop_delay_seconds = entry->getFloat();\n\n        if(loop_delay_seconds<=0.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"git-branch\")) != 0) {\n\n        if(!entry->hasValue()) conffile.missingValueException(entry);\n\n        Regex branch_regex(\"^(?!-)[/\\\\w.,;_=+{}\\\\[\\\\]-]+$\");\n\n        std::string branch = entry->getString();\n\n        if(branch_regex.match(branch)) {\n            git_branch = branch;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if(gource_settings->getBool(\"colour-images\")) {\n        colour_user_images = true;\n    }\n\n    if((entry = gource_settings->getEntry(\"crop\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify crop (vertical,horizontal)\");\n\n        std::string crop = entry->getString();\n\n        if(crop == \"vertical\") {\n            crop_vertical = true;\n        } else if (crop == \"horizontal\") {\n            crop_horizontal = true;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"log-format\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify log-format (format)\");\n\n        log_format = entry->getString();\n\n        if(log_format == \"cvs\") {\n            conffile.entryException(entry, \"please use either 'cvs2cl' or 'cvs-exp'\");\n        }\n\n        if(   log_format != \"git\"\n           && log_format != \"cvs-exp\"\n           && log_format != \"cvs2cl\"\n           && log_format != \"svn\"\n           && log_format != \"custom\"\n           && log_format != \"hg\"\n           && log_format != \"bzr\"\n           && log_format != \"apache\") {\n\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"default-user-image\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify default-user-image (image path)\");\n\n        default_user_image = entry->getString();\n    }\n\n    if((entry = gource_settings->getEntry(\"user-image-dir\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify user-image-dir (directory)\");\n\n        user_image_dir = entry->getString();\n\n        //append slash\n        if(user_image_dir[user_image_dir.size()-1] != '/') {\n            user_image_dir += std::string(\"/\");\n        }\n\n        user_image_map.clear();\n\n        boost::filesystem::path image_dir_path(user_image_dir);\n\n        if(!is_directory(image_dir_path)) {\n             conffile.entryException(entry, \"specified user-image-dir is not a directory\");\n        }\n\n        std::vector<boost::filesystem::path> image_dir_files;\n\n        try {\n            copy(boost::filesystem::directory_iterator(image_dir_path), boost::filesystem::directory_iterator(), back_inserter(image_dir_files));\n        } catch(const boost::filesystem::filesystem_error& exception) {\n             conffile.entryException(entry, \"error reading specified user-image-dir\");\n        }\n\n        for(boost::filesystem::path& p : image_dir_files) {\n\n            std::string dirfile;\n\n#ifdef _WIN32\n            std::wstring dirfile_16 = p.filename().wstring();\n            utf8::utf16to8(dirfile_16.begin(), dirfile_16.end(), back_inserter(dirfile));\n#else\n            dirfile = p.filename().string();\n#endif\n            std::string file_ext = p.extension().string();\n            boost::algorithm::to_lower(file_ext);\n\n            if(file_ext != \".jpg\" && file_ext != \".jpeg\" && file_ext != \".png\") continue;\n\n            std::string image_path = gGourceSettings.user_image_dir + dirfile;\n            std::string name       = dirfile.substr(0,dirfile.size() - file_ext.size());\n\n#ifdef __APPLE__\n                CFMutableStringRef help = CFStringCreateMutable(kCFAllocatorDefault, 0);\n                CFStringAppendCString(help, name.c_str(), kCFStringEncodingUTF8);\n                CFStringNormalize(help, kCFStringNormalizationFormC);\n                char data[4096];\n                CFStringGetCString(help,\n                                   data,\n                                   sizeof(data),\n                                   kCFStringEncodingUTF8);\n                name = data;\n#endif\n\n            debugLog(\"%s => %s\", name.c_str(), image_path.c_str());\n\n            user_image_map[name] = image_path;\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"caption-file\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify caption file (filename)\");\n\n        caption_file = entry->getString();\n\n        if(!boost::filesystem::exists(caption_file)) {\n            conffile.entryException(entry, \"caption file not found\");\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"caption-duration\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify caption duration (seconds)\");\n\n        caption_duration = entry->getFloat();\n\n        if(caption_duration <= 0.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"caption-size\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify caption size\");\n\n        caption_size = entry->getInt();\n\n        if(caption_size<1 || caption_size>100) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"caption-offset\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify caption offset\");\n\n        caption_offset = entry->getInt();\n    }\n\n    if((entry = gource_settings->getEntry(\"caption-colour\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify caption colour (FFFFFF)\");\n\n        int r,g,b;\n\n        std::string colstring = entry->getString();\n\n        if(entry->isVec3()) {\n            caption_colour = entry->getVec3();\n        } else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            caption_colour = vec3(r,g,b);\n            caption_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"filename-colour\")) != 0) {\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify filename colour (FFFFFF)\");\n\n\tint r,g,b;\n\n\tstd::string colstring = entry->getString();\n\n\tif(entry->isVec3()) {\n\t    filename_colour = entry->getVec3();\n\t} else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            filename_colour = vec3(r,g,b);\n            filename_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"filename-time\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify duration to keep files on screen (float)\");\n\n        filename_time = entry->getFloat();\n\n        if(filename_time<2.0f) {\n            conffile.entryException(entry, \"filename-time must be >= 2.0\");\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"bloom-intensity\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify bloom-intensity (float)\");\n\n        bloom_intensity = entry->getFloat();\n\n        if(bloom_intensity<=0.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"bloom-multiplier\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify bloom-multiplier (float)\");\n\n        bloom_multiplier = entry->getFloat();\n\n        if(bloom_multiplier<=0.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"elasticity\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify elasticity (float)\");\n\n        elasticity = entry->getFloat();\n\n        if(elasticity<=0.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"font-file\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font file\");\n\n        font_file = entry->getString();\n\n        boost::filesystem::path font_file_path(font_file);\n\n        if(!boost::filesystem::exists(font_file_path)) {\n            conffile.invalidValueException(entry);\n        }\n\n        font_file = boost::filesystem::canonical(font_file_path).string();\n\n        if(font_file.empty()) {\n           conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"font-size\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font size\");\n\n        font_size = entry->getInt();\n\n        if(font_size<1 || font_size>100) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"file-font-size\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font size\");\n\n        filename_font_size = entry->getInt();\n\n        if(filename_font_size<1 || filename_font_size>100) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"dir-font-size\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font size\");\n\n        dirname_font_size = entry->getInt();\n\n        if(dirname_font_size<1 || dirname_font_size>100) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"user-font-size\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font size\");\n\n        user_font_size = entry->getInt();\n\n        if(user_font_size<1 || user_font_size>100) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"font-scale\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font scale\");\n\n        font_scale = entry->getFloat();\n        default_font_scale = false;\n\n        if(font_scale<0.0f || font_scale>10.0f) {\n            conffile.invalidValueException(entry);\n        }\n\n        setScaledFontSizes();\n    }\n\n    if((entry = gource_settings->getEntry(\"hash-seed\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify hash seed (integer)\");\n\n        gStringHashSeed = entry->getInt();\n    }\n\n    if((entry = gource_settings->getEntry(\"font-colour\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify font colour (FFFFFF)\");\n\n        int r,g,b;\n\n        std::string colstring = entry->getString();\n\n        if(entry->isVec3()) {\n            font_colour = entry->getVec3();\n        } else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            font_colour = vec3(r,g,b);\n            font_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"background-colour\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify background colour (FFFFFF)\");\n\n        int r,g,b;\n\n        std::string colstring = entry->getString();\n\n        if(entry->isVec3()) {\n            background_colour = entry->getVec3();\n        } else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            background_colour = vec3(r,g,b);\n            background_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"highlight-colour\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify highlight colour (FFFFFF)\");\n\n        int r,g,b;\n\n        std::string colstring = entry->getString();\n\n        if(entry->isVec3()) {\n            highlight_colour = entry->getVec3();\n        } else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            highlight_colour = vec3(r,g,b);\n            highlight_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"selection-colour\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify selection colour (FFFFFF)\");\n\n        int r,g,b;\n\n        std::string colstring = entry->getString();\n\n        if(entry->isVec3()) {\n            selection_colour = entry->getVec3();\n        } else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            selection_colour = vec3(r,g,b);\n            selection_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"dir-colour\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify dir colour (FFFFFF)\");\n\n        int r,g,b;\n\n        std::string colstring = entry->getString();\n\n        if(entry->isVec3()) {\n            dir_colour = entry->getVec3();\n        } else if(colstring.size()==6 && sscanf(colstring.c_str(), \"%02x%02x%02x\", &r, &g, &b) == 3) {\n            dir_colour = vec3(r,g,b);\n            dir_colour /= 255.0f;\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"background-image\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify background image (image path)\");\n\n        background_image = entry->getString();\n    }\n\n    if((entry = gource_settings->getEntry(\"title\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify title\");\n\n        title = entry->getString();\n    }\n\n    if((entry = gource_settings->getEntry(\"logo\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify logo (image path)\");\n\n        logo = entry->getString();\n    }\n\n    if((entry = gource_settings->getEntry(\"logo-offset\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify logo-offset (XxY)\");\n\n        std::string logo_offset_str = entry->getString();\n\n        int posx = 0;\n        int posy = 0;\n\n        if(parseRectangle(logo_offset_str, posx, posy)) {\n            logo_offset = vec2(posx, posy);\n        } else {\n            conffile.invalidValueException(entry);\n        }\n\n    }\n\n    if((entry = gource_settings->getEntry(\"seconds-per-day\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify seconds-per-day (seconds)\");\n\n        float seconds_per_day = entry->getFloat();\n\n        if(seconds_per_day<=0.0f) {\n            conffile.invalidValueException(entry);\n        }\n\n        // convert seconds-per-day to days-per-second\n        days_per_second = 1.0 / seconds_per_day;\n    }\n\n    if((entry = gource_settings->getEntry(\"auto-skip-seconds\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify auto-skip-seconds (seconds)\");\n\n        auto_skip_seconds = entry->getFloat();\n\n        if(auto_skip_seconds <= 0.0) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"file-idle-time\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify file-idle-time (seconds)\");\n\n        std::string file_idle_str = entry->getString();\n\n        file_idle_time = (float) atoi(file_idle_str.c_str());\n\n        if(file_idle_time<0.0f || (file_idle_time == 0.0f && file_idle_str[0] != '0') ) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"file-idle-time-at-end\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify file-idle-time-at-end (seconds)\");\n\n        std::string file_idle_at_end_str = entry->getString();\n\n        file_idle_time_at_end = (float) atoi(file_idle_at_end_str.c_str());\n\n        if(file_idle_time_at_end<0.0f || (file_idle_time_at_end == 0.0f && file_idle_at_end_str[0] != '0') ) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"user-idle-time\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify user-idle-time (seconds)\");\n\n        user_idle_time = entry->getFloat();\n\n        if(user_idle_time < 0.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"time-scale\")) != 0) {\n\n        if(!entry->hasValue())\n            conffile.entryException(entry, \"specify time-scale (scale)\");\n\n        time_scale = entry->getFloat();\n\n        if(time_scale <= 0.0f || time_scale > 4.0f) {\n            conffile.entryException(entry, \"time-scale outside of range 0.0 - 4.0\");\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"start-date\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify start-date (YYYY-MM-DD hh:mm:ss)\");\n\n        std::string start_date_string = entry->getString();\n\n        if(parseDateTime(start_date_string, start_timestamp)) {\n\n            char datestr[256];\n            strftime(datestr, 256, \"%Y-%m-%d\", localtime ( &start_timestamp ));\n            start_date = datestr;\n\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"stop-date\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify stop-date (YYYY-MM-DD hh:mm:ss)\");\n\n        std::string end_date_string = entry->getString();\n\n        if(parseDateTime(end_date_string, stop_timestamp)) {\n\n            struct tm * timeinfo;\n            timeinfo = localtime ( &stop_timestamp );\n\n            time_t stop_timestamp_rounded = stop_timestamp;\n\n            if(timeinfo->tm_hour > 0 || timeinfo->tm_min > 0 || timeinfo->tm_sec > 0) {\n                stop_timestamp_rounded += 60*60*24;\n            }\n\n            char datestr[256];\n            strftime(datestr, 256, \"%Y-%m-%d\", localtime ( &stop_timestamp_rounded ));\n            stop_date = datestr;\n\n        } else {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"start-position\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify start-position (float,random)\");\n\n        if(entry->getString() == \"random\") {\n            srand(time(0));\n            start_position = (rand() % 1000) / 1000.0f;\n        } else {\n            start_position = entry->getFloat();\n\n            if(start_position<=0.0 || start_position>=1.0) {\n                conffile.entryException(entry, \"start-position outside of range 0.0 - 1.0 (non-inclusive)\");\n            }\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"stop-position\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify stop-position (float)\");\n\n        stop_position = entry->getFloat();\n\n        if(stop_position<=0.0 || stop_position>1.0) {\n            conffile.entryException(entry, \"stop-position outside of range 0.0 - 1.0 (inclusive)\");\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"stop-at-time\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify stop-at-time (seconds)\");\n\n        stop_at_time = entry->getFloat();\n\n        if(stop_at_time <= 0.0) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if(gource_settings->getBool(\"key\")) {\n        show_key = true;\n    }\n\n    if(gource_settings->getBool(\"ffp\")) {\n        ffp = true;\n    }\n\n    if(gource_settings->getBool(\"realtime\")) {\n        days_per_second = 1.0 / 86400.0;\n    }\n\n    if(gource_settings->getBool(\"no-time-travel\")) {\n        no_time_travel = true;\n    }\n\n    if(gource_settings->getBool(\"dont-stop\")) {\n        dont_stop = true;\n    }\n\n    if(gource_settings->getBool(\"stop-at-end\")) {\n        stop_at_end = true;\n    }\n\n    //NOTE: this no longer does anything\n    if(gource_settings->getBool(\"stop-on-idle\")) {\n        stop_on_idle = true;\n    }\n\n    if(gource_settings->getBool(\"fixed-user-size\")) {\n        fixed_user_size = true;\n    }\n\n    if(gource_settings->getBool(\"author-time\")) {\n        author_time = true;\n    }\n\n    if((entry = gource_settings->getEntry(\"max-files\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify max-files (number)\");\n\n        max_files = entry->getInt();\n\n        if( max_files<0 || (max_files == 0 && entry->getString() != \"0\") ) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"max-file-lag\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify max-file-lag (seconds)\");\n\n        max_file_lag = entry->getFloat();\n\n        if(max_file_lag==0.0) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"user-friction\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify user-friction (seconds)\");\n\n        user_friction = entry->getFloat();\n\n        if(user_friction<=0.0) {\n            conffile.invalidValueException(entry);\n        }\n\n        user_friction = 1.0 / user_friction;\n    }\n\n    if((entry = gource_settings->getEntry(\"user-scale\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify user-scale (scale)\");\n\n        user_scale = entry->getFloat();\n\n        if(user_scale<=0.0 || user_scale>100.0) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"max-user-speed\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify max-user-speed (units)\");\n\n        max_user_speed = entry->getFloat();\n\n        if(max_user_speed<=0) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if(   gource_settings->getBool(\"highlight-users\")\n       || gource_settings->getBool(\"highlight-all-users\")) {\n        highlight_all_users = true;\n    }\n\n    if(gource_settings->getBool(\"highlight-dirs\")) {\n        highlight_dirs = true;\n    }\n\n    if((entry = gource_settings->getEntry(\"camera-mode\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify camera-mode (overview,track)\");\n\n        camera_mode = entry->getString();\n\n        if(camera_mode != \"overview\" && camera_mode != \"track\") {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"padding\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify padding (float)\");\n\n        padding = entry->getFloat();\n\n        if(padding <= 0.0f || padding >= 2.0f) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    // multi-value entries\n\n    if((entry = gource_settings->getEntry(\"highlight-user\")) != 0) {\n\n        ConfEntryList* highlight_user_entries = gource_settings->getEntries(\"highlight-user\");\n\n        for(ConfEntryList::iterator it = highlight_user_entries->begin(); it != highlight_user_entries->end(); it++) {\n\n            entry = *it;\n\n            if(!entry->hasValue()) conffile.entryException(entry, \"specify highlight-user (user)\");\n\n            highlight_users.push_back(entry->getString());\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"follow-user\")) != 0) {\n\n        ConfEntryList* follow_user_entries = gource_settings->getEntries(\"follow-user\");\n\n        for(ConfEntryList::iterator it = follow_user_entries->begin(); it != follow_user_entries->end(); it++) {\n\n            entry = *it;\n\n            if(!entry->hasValue()) conffile.entryException(entry, \"specify follow-user (user)\");\n\n            follow_users.push_back(entry->getString());\n        }\n    }\n\n    if(gource_settings->getBool(\"file-extensions\")) {\n        file_extensions=true;\n    }\n\n    if(gource_settings->getBool(\"file-extension-fallback\")) {\n        file_extension_fallback=true;\n    }\n\n    if((entry = gource_settings->getEntry(\"file-filter\")) != 0) {\n\n        ConfEntryList* filters = gource_settings->getEntries(\"file-filter\");\n\n        for(ConfEntryList::iterator it = filters->begin(); it != filters->end(); it++) {\n\n            entry = *it;\n\n            if(!entry->hasValue()) conffile.entryException(entry, \"specify file-filter (regex)\");\n\n            std::string filter_string = entry->getString();\n\n            Regex* r = new Regex(filter_string, 1);\n\n            if(!r->isValid()) {\n                delete r;\n                conffile.entryException(entry, \"invalid file-filter regular expression\");\n            }\n\n            file_filters.push_back(r);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"file-show-filter\")) != 0) {\n\n        ConfEntryList* filters = gource_settings->getEntries(\"file-show-filter\");\n\n        for(ConfEntryList::iterator it = filters->begin(); it != filters->end(); it++) {\n\n            entry = *it;\n\n            if(!entry->hasValue()) conffile.entryException(entry, \"specify file-show-filter (regex)\");\n\n            std::string filter_string = entry->getString();\n\n            Regex* r = new Regex(filter_string, 1);\n\n            if(!r->isValid()) {\n                delete r;\n                conffile.entryException(entry, \"invalid file-show-filter regular expression\");\n            }\n\n            file_show_filters.push_back(r);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"user-filter\")) != 0) {\n\n        ConfEntryList* filters = gource_settings->getEntries(\"user-filter\");\n\n        for(ConfEntryList::iterator it = filters->begin(); it != filters->end(); it++) {\n\n            entry = *it;\n\n            if(!entry->hasValue()) conffile.entryException(entry, \"specify user-filter (regex)\");\n\n            std::string filter_string = entry->getString();\n\n            Regex* r = new Regex(filter_string, 1);\n\n            if(!r->isValid()) {\n                delete r;\n                conffile.entryException(entry, \"invalid user-filter regular expression\");\n            }\n\n            user_filters.push_back(r);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"user-show-filter\")) != 0) {\n\n        ConfEntryList* filters = gource_settings->getEntries(\"user-show-filter\");\n\n        for(ConfEntryList::iterator it = filters->begin(); it != filters->end(); it++) {\n\n            entry = *it;\n\n            if(!entry->hasValue()) conffile.entryException(entry, \"specify user-show-filter (regex)\");\n\n            std::string filter_string = entry->getString();\n\n            Regex* r = new Regex(filter_string, 1);\n\n            if(!r->isValid()) {\n                delete r;\n                conffile.entryException(entry, \"invalid user-show-filter regular expression\");\n            }\n\n            user_show_filters.push_back(r);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"dir-name-depth\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify dir-name-depth (depth)\");\n\n        dir_name_depth = entry->getInt();\n\n        if(dir_name_depth <= 0) {\n            conffile.invalidValueException(entry);\n        }\n    }\n\n    if((entry = gource_settings->getEntry(\"dir-name-position\")) != 0) {\n\n        if(!entry->hasValue()) conffile.entryException(entry, \"specify dir-name-position (float)\");\n\n        dir_name_position = entry->getFloat();\n\n        if(dir_name_position < 0.1f || dir_name_position > 1.0f) {\n            conffile.entryException(entry, \"dir-name-position outside of range 0.1 - 1.0 (inclusive)\");\n        }\n    }\n\n    //validate path\n    if(gource_settings->hasValue(\"path\")) {\n        path = gource_settings->getString(\"path\");\n        default_path = false;\n    }\n\n    if(path == \"-\") {\n\n        if(log_format.size() == 0) {\n            throw ConfFileException(\"log-format required when reading from STDIN\", \"\", 0);\n        }\n\n#ifdef _WIN32\n        DWORD available_bytes;\n        HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);\n\n        while(PeekNamedPipe(stdin_handle, 0, 0, 0,\n            &available_bytes, 0) && available_bytes==0 && !std::cin.fail()) {\n            SDL_Delay(100);\n        }\n#else\n        while(std::cin.peek() == EOF && !std::cin.fail()) SDL_Delay(100);\n#endif\n\n        std::cin.clear();\n\n    } else if(!path.empty() && path != \".\") {\n\n        //remove trailing slash\n        if(path[path.size()-1] == '\\\\' || path[path.size()-1] == '/') {\n            path.resize(path.size()-1);\n        }\n\n        // check path exists\n        if(!boost::filesystem::exists(path)) {\n            throw ConfFileException(str(boost::format(\"'%s' does not appear to be a valid file or directory\") % path), \"\", 0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/gource_settings.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GOURCE_SETTINGS_H\n#define GOURCE_SETTINGS_H\n\n#define GOURCE_VERSION \"0.57\"\n\n#include \"core/texture.h\"\n#include \"core/settings.h\"\n#include \"core/regex.h\"\n\nclass GourceSettings : public SDLAppSettings {\nprotected:\n    void commandLineOption(const std::string& name, const std::string& value);\npublic:\n    int repo_count;\n\n    bool hide_date;\n    bool hide_users;\n    bool hide_tree;\n    bool hide_files;\n    bool hide_usernames;\n    bool hide_filenames;\n    bool hide_dirnames;\n    bool hide_progress;\n    bool hide_bloom;\n    bool hide_mouse;\n    bool hide_root;\n\n    bool disable_auto_rotate;\n\n    bool disable_input;\n\n    bool show_key;\n\n    std::string load_config;\n    std::string save_config;\n    std::string path;\n    bool default_path;\n\n    std::string logo;\n    vec2 logo_offset;\n\n    std::string start_date;\n    std::string stop_date;\n    time_t start_timestamp;\n    time_t stop_timestamp;\n\n    float start_position;\n    float stop_position;\n    float stop_at_time;\n\n    bool shutdown;\n    bool stop_on_idle;\n    bool stop_at_end;\n    bool dont_stop;\n    bool no_time_travel;\n    bool fixed_user_size;\n    bool author_time;\n\n    float auto_skip_seconds;\n    float days_per_second;\n    float file_idle_time;\n    float file_idle_time_at_end;\n    float loop_delay_seconds;\n\n    bool loop;\n\n    bool ffp;\n\n    bool colour_user_images;\n    std::string default_user_image;\n    std::string user_image_dir;\n    std::map<std::string, std::string> user_image_map;\n\n    float camera_zoom_min;\n    float camera_zoom_max;\n    float camera_zoom_default;\n\n    std::string camera_mode;\n    float padding;\n\n    bool crop_vertical;\n    bool crop_horizontal;\n\n    float bloom_multiplier;\n    float bloom_intensity;\n\n    vec3 background_colour;\n    std::string background_image;\n\n    std::string title;\n\n    std::string font_file;\n    int font_size;\n    int filename_font_size;\n    int dirname_font_size;\n    int user_font_size;\n    vec3 font_colour;\n    float font_scale;\n    bool default_font_scale;\n\n    int scaled_font_size;\n    int scaled_filename_font_size;\n    int scaled_dirname_font_size;\n    int scaled_user_font_size;\n\n    float elasticity;\n\n    std::string git_branch;\n\n    std::string log_format;\n    std::string date_format;\n\n    int max_files;\n    float max_user_speed;\n    float max_file_lag;\n\n    float user_idle_time;\n    float user_friction;\n    float user_scale;\n    float time_scale;\n\n    bool highlight_dirs;\n    bool highlight_all_users;\n\n    vec3 dir_colour;\n    vec3 highlight_colour;\n    vec3 selection_colour;\n\n    int dir_name_depth;\n    float dir_name_position;\n\n    std::vector<std::string> highlight_users;\n    std::vector<std::string> follow_users;\n    std::vector<Regex*> file_filters;\n    std::vector<Regex*> file_show_filters;\n    std::vector<Regex*> user_filters;\n    std::vector<Regex*> user_show_filters;\n    bool file_extensions;\n    bool file_extension_fallback;\n\n    std::string caption_file;\n    vec3 caption_colour;\n    float caption_duration;\n    int caption_size;\n    int caption_offset;\n\n    vec3 filename_colour;\n    float filename_time;\n\n    std::string output_custom_filename;\n\n    TextureResource* file_graphic;\n\n    int log_level;\n    \n    GourceSettings();\n\n    void setGourceDefaults();\n\n    void setScaledFontSizes();\n\n    void importGourceSettings(ConfFile& conf, ConfSection* gource_settings = 0);\n\n    void help(bool extended_help=false);\n};\n\nextern GourceSettings gGourceSettings;\n\n#endif\n"
  },
  {
    "path": "src/gource_shell.cpp",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"gource_shell.h\"\n\nGourceShell* gGourceShell = 0;\n\n// GourceShell\n\nGourceShell::GourceShell(ConfFile* conf, FrameExporter* exporter) {\n\n    this->conf     = conf;\n    this->exporter = exporter;\n\n    min_delta_msec = 16;\n\n    next = false;\n\n    gource = 0;\n    gource_settings = conf->getSections(\"gource\")->begin();\n\n    gGourceSettings.repo_count = conf->countSection(\"gource\");\n\n    toggle_delay = 0.0;\n    transition_texture = 0;\n    transition_interval = 0.0f;\n\n    if(GLEW_ARB_texture_non_power_of_two || GLEW_VERSION_2_0) {\n        transition_texture = texturemanager.create(display.width, display.height, false, GL_CLAMP_TO_EDGE, GL_RGBA);\n    }\n}\n\nGourceShell::~GourceShell() {\n    if(gource!=0) delete gource;\n    if(transition_texture!=0) texturemanager.release(transition_texture);\n}\n\nvoid GourceShell::toggleFullscreen() {\n\n    if(exporter != 0) return;\n\n    texturemanager.unload();\n    shadermanager.unload();\n    fontmanager.unload();\n\n    if(gource!=0) gource->unload();\n\n    //recreate gl context\n    display.toggleFullscreen();\n\n    texturemanager.reload();\n    shadermanager.reload();\n    fontmanager.reload();\n\n    if(gource!=0) gource->reload();\n}\n\nvoid GourceShell::toggleWindowFrame() {\n#if SDL_VERSION_ATLEAST(2,0,0)\n    if(toggle_delay > 0.0) return;\n    if(display.isFullscreen()) return;\n    if(exporter != 0) return;\n\n    texturemanager.unload();\n    shadermanager.unload();\n    fontmanager.unload();\n\n    if(gource!=0) gource->unload();\n\n    display.toggleFrameless();\n\n    texturemanager.reload();\n    shadermanager.reload();\n    fontmanager.reload();\n\n    if(gource!=0) gource->reload();\n\n    toggle_delay = 0.25f;\n#endif\n}\n\n\nvoid GourceShell::resize(int width, int height) {\n\n    texturemanager.unload();\n    shadermanager.unload();\n    fontmanager.unload();\n\n    if(gource!=0) gource->unload();\n\n    //recreate gl context\n    display.resize(width, height);\n\n    texturemanager.reload();\n    shadermanager.reload();\n    fontmanager.reload();\n\n    if(gource!=0) gource->reload();\n}\n\nvoid GourceShell::reload() {\n    texturemanager.unload();\n    shadermanager.unload();\n    fontmanager.unload();\n\n    if(gource!=0) gource->unload();\n\n    texturemanager.reload();\n    shadermanager.reload(true);\n    fontmanager.reload();\n\n    if(gource!=0) gource->reload();\n}\n\nvoid GourceShell::keyPress(SDL_KeyboardEvent *e) {\n\n    bool repeat = false;\n#if SDL_VERSION_ATLEAST(2,0,0)\n    repeat = (e->repeat > 0);\n#endif\n\n    //Quit demo if the user presses ESC\n    if (e->type == SDL_KEYDOWN && !repeat) {\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n        bool key_escape = e->keysym.sym == SDLK_ESCAPE;\n        bool key_return = e->keysym.sym == SDLK_RETURN;\n#else\n        bool key_escape = e->keysym.unicode == SDLK_ESCAPE;\n        bool key_return = e->keysym.unicode == SDLK_RETURN;\n#endif\n\n        if (key_escape) {\n            quit();\n        }\n\n        if(gGourceSettings.disable_input) {\n            // disable keyboard input other than the escape key\n            return;\n        }\n\n        if (e->keysym.sym == SDLK_F5) {\n            reload();\n        }\n\n        if (e->keysym.sym == SDLK_F11) {\n            toggleWindowFrame();\n        }\n\n        if(key_return) {\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n            const Uint8* keystate = SDL_GetKeyboardState(NULL);\n            if(keystate[SDL_SCANCODE_RALT] || keystate[SDL_SCANCODE_LALT]) {\n#else\n            Uint8* keystate = SDL_GetKeyState(NULL);\n            if(keystate[SDLK_RALT] || keystate[SDLK_LALT]) {\n#endif\n\n                toggleFullscreen();\n\n            } else {\n                if(gGourceSettings.repo_count>1)\n                    next = true;\n            }\n        }\n    }\n\n    if(gource!=0) gource->keyPress(e);\n}\n\nvoid GourceShell::mouseMove(SDL_MouseMotionEvent *e) {\n    if(gource!=0) gource->mouseMove(e);\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nvoid GourceShell::mouseWheel(SDL_MouseWheelEvent *e) {\n    if(gource!=0) gource->mouseWheel(e);\n}\n#endif\n\nvoid GourceShell::mouseClick(SDL_MouseButtonEvent *e) {\n    if(gource!=0) gource->mouseClick(e);\n}\n\nvoid GourceShell::quit() {\n    if(gource!=0) gource->quit();\n    gGourceSettings.shutdown=true;\n}\n\nGource* GourceShell::getNext() {\n\n    if(gource != 0) {\n        transition_interval = 1.0f;\n        delete gource;\n        gource = 0;\n    }\n\n    if(gGourceSettings.shutdown || gource_settings == conf->getSections(\"gource\")->end()) {\n        // done\n        return 0;\n    }\n\n    gGourceSettings.importGourceSettings(*conf, *gource_settings);\n\n    //recording a video kind of implies you want this, unless:\n    // -- dont stop requested\n    // -- loop requested\n    // -- reading from STDIN\n    if(exporter!=0 && !(gGourceSettings.dont_stop || gGourceSettings.loop || gGourceSettings.path == \"-\"))\n        gGourceSettings.stop_at_end = true;\n\n    //multiple repo special settings\n    if(gGourceSettings.repo_count > 1) {\n\n        //set a stop condition\n        if(gGourceSettings.stop_at_time <= 0.0f && gGourceSettings.stop_position <= 0.0f) {\n            gGourceSettings.stop_at_time = 60.0f;\n        }\n    }\n\n    gource_settings++;\n\n    //loop unless only 1 repo\n    if(gource_settings == conf->getSections(\"gource\")->end()) {\n        if(gGourceSettings.repo_count>1 && exporter==0) {\n            gource_settings = conf->getSections(\"gource\")->begin();\n        }\n    }\n\n    // replace gource\n    gource = new Gource(exporter);\n\n    next = false;\n\n    return gource;\n}\n\nvoid GourceShell::blendLastFrame(float dt) {\n    if(transition_texture==0 || transition_interval <= 0.0f) return;\n\n    display.mode2D();\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glEnable(GL_BLEND);\n    glEnable(GL_TEXTURE_2D);\n\n    transition_texture->bind();\n\n    glColor4f(1.0, 1.0, 1.0, transition_interval);\n\n    glBegin(GL_QUADS);\n        glTexCoord2f(0.0f, 1.0f);\n        glVertex2f(0.0f, 0.0);\n\n        glTexCoord2f(1.0, 1.0f);\n        glVertex2f(display.width, 0.0);\n\n        glTexCoord2f(1.0, 0.0f);\n        glVertex2f(display.width, display.height);\n\n        glTexCoord2f(0.0f, 0.0f);\n        glVertex2f(0.0f, display.height);\n    glEnd();\n\n    transition_interval -= dt;\n}\n\nvoid GourceShell::update(float t, float dt) {\n\n    if(gource == 0 || gource->isFinished()) {\n        if(!getNext()) appFinished=true;\n\n        return;\n    }\n\n    gource->fps = this->fps;\n    gource->update(t, dt);\n\n    if(toggle_delay > 0.0) toggle_delay -= dt;\n\n    //copy last frame\n    if( (next|| gource->isFinished()) && transition_texture!=0) {\n\n        glEnable(GL_TEXTURE_2D);\n        transition_texture->bind();\n        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, display.width, display.height, 0);\n\n    } else {\n        //blend last frame of previous scene\n        blendLastFrame(dt);\n    }\n\n    if(next) {\n        delete gource;\n        gource = 0;\n        transition_interval = 1.0f;\n        next = false;\n    }\n}\n"
  },
  {
    "path": "src/gource_shell.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GOURCE_SHELL_H\n#define GOURCE_SHELL_H\n\n#include \"core/display.h\"\n#include \"core/sdlapp.h\"\n#include \"gource.h\"\n\nclass GourceShell : public SDLApp {\n\n    Gource* gource;\n    bool next;\n\n    TextureResource* transition_texture;\n    float transition_interval;\n    float toggle_delay;\n\n    FrameExporter* exporter;\n    ConfFile* conf;\n    ConfSectionList::iterator gource_settings;\n\n    Gource* getNext();\n    void blendLastFrame(float dt);\npublic:\n    GourceShell(ConfFile* conf, FrameExporter* exporter);\n    ~GourceShell();\n\n    void update(float t, float dt);\n\n    void resize(int width, int height);\n\n    void reload();\n\n    void toggleFullscreen();\n    void toggleWindowFrame();\n\n    void quit();\n\n    void keyPress(SDL_KeyboardEvent *e);\n    void mouseMove(SDL_MouseMotionEvent *e);\n    void mouseClick(SDL_MouseButtonEvent *e);\n#if SDL_VERSION_ATLEAST(2,0,0)\n    void mouseWheel(SDL_MouseWheelEvent *e);\n#endif\n};\n\n#endif\n\nextern GourceShell* gGourceShell;\n"
  },
  {
    "path": "src/key.cpp",
    "content": "#include \"key.h\"\n\n\n// File Key Entry\n// a string for the file ext and a colour\n\nFileKeyEntry::FileKeyEntry(const FXFont& font, const std::string& ext, const vec3& colour) {\n    this->ext    = ext;\n    this->colour = colour;\n    this->pos_y  = -1.0f;\n\n    this->font = font;\n    this->font.dropShadow(false);\n\n    shadow      = vec2(3.0, 3.0);\n\n    width       = 90.0f * gGourceSettings.font_scale;\n    height      = gGourceSettings.scaled_font_size + 4.0f;\n    left_margin = gGourceSettings.scaled_font_size + 4.0f;\n    count       = 0;\n    brightness  = 1.0f;\n    alpha       = 0.0f;\n\n    move_elapsed = 1.0f;\n    src_y        = -1.0f;\n    dest_y       = -1.0f;\n\n    show = true;\n\n    display_ext = ext;\n\n    bool truncated = false;\n\n    while(font.getWidth(display_ext) > width - 15.0f * gGourceSettings.font_scale) {\n        display_ext.resize(display_ext.size()-1);\n        truncated = true;\n    }\n\n    if(truncated) {\n        display_ext += std::string(\"...\");\n    }\n}\n\nconst vec3& FileKeyEntry::getColour() const {\n    return colour;\n}\n\nconst std::string& FileKeyEntry::getExt() const {\n    return ext;\n}\n\nvoid FileKeyEntry::setShow(bool show) {\n    this->show = show;\n}\n\nbool FileKeyEntry::isFinished() const {\n    return (count<=0 && alpha <= 0.0f);\n}\n\nvoid FileKeyEntry::colourize() {\n    colour = ext.empty() ? vec3(1.0f, 1.0f, 1.0f) : colourHash(ext);\n}\n\nvoid FileKeyEntry::inc() {\n    count++;\n}\n\nvoid FileKeyEntry::dec() {\n    count--;\n}\n\nint FileKeyEntry::getCount() const {\n    return count;\n}\n\nvoid FileKeyEntry::setCount(int count) {\n    this->count = count;\n}\n\nvoid FileKeyEntry::setDestY(float dest_y) {\n    if(dest_y == this->dest_y) return;\n\n    this->dest_y = dest_y;\n\n    src_y = pos_y;\n    move_elapsed = 0.0f;\n}\n\n\nvoid FileKeyEntry::logic(float dt) {\n\n    if(count<=0 || !show) {\n        alpha = std::max(0.0f, alpha - dt);\n    } else if(alpha < 1.0f) {\n        alpha = std::min(1.0f, alpha + dt);\n    }\n\n    //move towards dest\n    if(pos_y != dest_y) {\n        //initialize pos from dest if new\n        if(pos_y < 0.0f) pos_y = dest_y;\n        else {\n            move_elapsed += dt;\n\n            if(move_elapsed >= 1.0f) pos_y = dest_y;\n            else pos_y = src_y + (dest_y - src_y) * move_elapsed;\n        }\n    }\n\n    pos = vec2(alpha * left_margin, pos_y);\n}\n\nvoid FileKeyEntry::draw() {\n    if(isFinished()) return;\n    //label.draw();\n\n    glDisable(GL_TEXTURE_2D);\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n    glColor4f(0.0f, 0.0f, 0.0f, alpha * 0.333f);\n\n    glPushMatrix();\n        glTranslatef(shadow.x, shadow.y, 0.0f);\n\n        glBegin(GL_QUADS);\n            glVertex2f(pos.x,       pos.y);\n            glVertex2f(pos.x,       pos.y + height);\n            glVertex2f(pos.x+width, pos.y + height);\n            glVertex2f(pos.x+width, pos.y);\n        glEnd();\n    glPopMatrix();\n\n    glBegin(GL_QUADS);\n        glColor4f(colour.x * 0.5f, colour.y * 0.5f, colour.z * 0.5f, alpha);\n        glVertex2f(pos.x,         pos.y);\n        glVertex2f(pos.x,         pos.y + height);\n        glColor4f(colour.x, colour.y, colour.z, alpha);\n        glVertex2f(pos.x + width, pos.y + height);\n        glVertex2f(pos.x + width, pos.y);\n    glEnd();\n\n    glEnable(GL_TEXTURE_2D);\n\n    font.setColour(vec4(1.0f, 1.0f, 1.0f, alpha));\n\n    font.dropShadow(false);\n    font.draw((int)pos.x+2, (int)pos.y+3,  display_ext.c_str());\n\n    font.dropShadow(true);\n    font.print((int)pos.x+width+4, (int)pos.y+3, \"%d\", count);\n}\n\n// Key\n\n//maintain a key of all the current file types, updated periodically.\n//new entries slide in and out / fade in fade out\n\nFileKey::FileKey() {\n\n}\n\nFileKey::FileKey(float update_interval) {\n    this->update_interval = update_interval;\n    interval_remaining = 1.0f;\n    font = fontmanager.grab(gGourceSettings.font_file, gGourceSettings.scaled_font_size);\n    font.dropShadow(false);\n    font.roundCoordinates(false);\n    show = true;\n}\n\nFileKey::~FileKey() {\n    active_keys.clear();\n\n    for(std::map<std::string, FileKeyEntry*>::iterator it = keymap.begin(); it != keymap.end(); it++) {\n        FileKeyEntry* entry = it->second;\n        delete entry;\n    }\n    keymap.clear();\n\n}\n\nvoid FileKey::setShow(bool show) {\n    this->show = show;\n\n    for(std::vector<FileKeyEntry*>::iterator it = active_keys.begin(); it != active_keys.end(); it++) {\n\n        FileKeyEntry* entry = *it;\n\n        entry->setShow(show);\n    }\n    interval_remaining = 0.0f;\n}\n\nvoid FileKey::colourize() {\n    for(std::vector<FileKeyEntry*>::iterator it = active_keys.begin(); it != active_keys.end(); it++) {\n        FileKeyEntry* entry = *it;\n        entry->colourize();\n    }\n}\n\nvoid FileKey::clear() {\n\n    for(std::vector<FileKeyEntry*>::iterator it = active_keys.begin(); it != active_keys.end(); it++) {\n        FileKeyEntry* entry = *it;\n        entry->setCount(0);\n    }\n\n    interval_remaining = 0.0f;\n}\n\nvoid FileKey::inc(RFile* file) {\n\n    FileKeyEntry* entry = 0;\n\n    std::map<std::string, FileKeyEntry*>::iterator result = keymap.find(file->ext);\n\n    if(result != keymap.end()) {\n        entry = result->second;\n    } else {\n        entry = new FileKeyEntry(font, file->ext, file->getFileColour());\n        keymap[file->ext] = entry;\n    }\n\n    entry->inc();\n}\n\n\n//decrement count of extension. if drops to zero, mark it for removal\nvoid FileKey::dec(RFile* file) {\n\n    std::map<std::string, FileKeyEntry*>::iterator result = keymap.find(file->ext);\n\n    if(result == keymap.end()) return;\n\n    FileKeyEntry* entry = result->second;\n\n    entry->dec();\n}\n\nbool file_key_entry_sort (const FileKeyEntry* a, const FileKeyEntry* b) {\n\n    //sort by count\n    if(a->getCount() != b->getCount())\n        return (a->getCount() > b->getCount());\n\n    //then by name (tie breaker)\n    return a->getExt().compare(b->getExt()) < 0;\n}\n\nvoid FileKey::logic(float dt) {\n\n    interval_remaining -= dt;\n\n    //recalculate active_keys\n    if(interval_remaining <= 0.0f) {\n\n        if(show) {\n            active_keys.clear();\n            std::vector<FileKeyEntry*> finished_keys;\n\n            for(std::map<std::string, FileKeyEntry*>::iterator it = keymap.begin(); it != keymap.end(); it++) {\n                FileKeyEntry* entry = it->second;\n\n                if(!entry->isFinished()) {\n                    active_keys.push_back(entry);\n                } else {\n                    finished_keys.push_back(entry);\n                }\n            }\n\n            //sort\n            std::sort(active_keys.begin(), active_keys.end(), file_key_entry_sort);\n\n            //limit to entries we can put onto the screen\n            int max_visible_entries = std::max(0, (int)((display.height - 150.0f) / 20.0f));\n\n            if (active_keys.size() > max_visible_entries) {\n                active_keys.resize(max_visible_entries);\n            }\n\n            //set position\n            float offset_y = gGourceSettings.scaled_font_size + 6.0f;\n            float key_y = offset_y;\n\n            for(std::vector<FileKeyEntry*>::iterator it = active_keys.begin(); it != active_keys.end(); it++) {\n                FileKeyEntry* entry = *it;\n                if(entry->getCount()>0) {\n                    entry->setDestY(key_y);\n                }\n                key_y += offset_y;\n            }\n\n            //remove and delete finished entries\n            for(std::vector<FileKeyEntry*>::iterator it = finished_keys.begin(); it != finished_keys.end(); it++) {\n                FileKeyEntry* entry = *it;\n                keymap.erase(entry->getExt());\n                delete entry;\n            }\n        }\n\n        interval_remaining = update_interval;\n    }\n\n    for(std::vector<FileKeyEntry*>::iterator it = active_keys.begin(); it != active_keys.end(); it++) {\n\n        FileKeyEntry* entry = *it;\n\n        entry->logic(dt);\n    }\n}\n\nvoid FileKey::draw() {\n\n    for(std::vector<FileKeyEntry*>::iterator it = active_keys.begin(); it != active_keys.end(); it++) {\n        FileKeyEntry* entry = *it;\n\n        entry->draw();\n    }\n\n}\n"
  },
  {
    "path": "src/key.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef FILE_KEY_H\n#define FILE_KEY_H\n\n#include \"core/display.h\"\n#include \"core/vectors.h\"\n#include \"core/fxfont.h\"\n\n#include \"file.h\"\n\n#include <vector>\n#include <algorithm>\n\nclass FileKeyEntry {   \n    FXFont font;\n    vec3 colour;\n    std::string ext;\n    std::string display_ext;\n    float alpha;\n    float brightness;\n    int count;\n    float pos_y;\n    float src_y;\n    float dest_y;\n    float move_elapsed;\n    float left_margin;\n    float width;\n    float height;\n    vec2 pos;\n    vec2 shadow;\n    bool show;\npublic:\n    FileKeyEntry(const FXFont& font, const std::string& ext, const vec3& colour);\n\n    const vec3& getColour() const;\n    const std::string& getExt() const;\n\n    void setDestY(float dest_y);\n\n    void colourize();\n    \n    void inc();\n    void dec();\n    \n    void setShow(bool show);\n    \n    int getCount() const;\n    void setCount(int count);\n\n    bool isNew() const;\n    bool isFinished() const;\n    \n    void logic(float dt);\n\n    void draw();\n};\n\nclass FileKey {\n    std::vector<FileKeyEntry*> active_keys;\n    std::map<std::string, FileKeyEntry*> keymap;\n\n    FXFont font;\n    float update_interval;\n    float interval_remaining;\n    bool show;\npublic:\n    FileKey();\n    ~FileKey();\n    FileKey(float update_interval);\n    \n    void setShow(bool show);\n    \n    void clear();\n\n    void colourize();\n    \n    void inc(RFile* file);\n    void dec(RFile* file);\n\n    void logic(float dt);\n    \n    void draw();\n};\n\n#endif\n"
  },
  {
    "path": "src/logmill.cpp",
    "content": "/*\n    Copyright (C) 2012 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"logmill.h\"\n#include \"gource_settings.h\"\n\n#include \"formats/git.h\"\n#include \"formats/gitraw.h\"\n#include \"formats/custom.h\"\n#include \"formats/hg.h\"\n#include \"formats/bzr.h\"\n#include \"formats/svn.h\"\n#include \"formats/apache.h\"\n#include \"formats/cvs-exp.h\"\n#include \"formats/cvs2cl.h\"\n\n#include <boost/filesystem.hpp>\n\nextern \"C\" {\n\n    static int logmill_thread(void *lmill) {\n\n        RLogMill *logmill = static_cast<RLogMill*> (lmill);\n        logmill->run();\n\n        return 0;\n    }\n\n};\n\nRLogMill::RLogMill(const std::string& logfile)\n    : logfile(logfile) {\n\n    logmill_thread_state = LOGMILL_STATE_STARTUP;\n    clog = 0;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    thread = SDL_CreateThread( logmill_thread, \"logmill\", this );\n#else\n    thread = SDL_CreateThread( logmill_thread, this );\n#endif\n}\n\nRLogMill::~RLogMill() {\n\n    abort();\n\n    if(clog) delete clog;\n}\n\nvoid RLogMill::run() {\n    logmill_thread_state = LOGMILL_STATE_FETCHING;\n\n#if defined(HAVE_PTHREAD) && !defined(_WIN32)\n    sigset_t mask;\n    sigemptyset(&mask);\n\n    // unblock SIGINT so user can cancel\n    // NOTE: assumes SDL is using pthreads\n\n    sigaddset(&mask, SIGINT);\n    pthread_sigmask(SIG_UNBLOCK, &mask, 0);\n#endif\n\n    std::string log_format = gGourceSettings.log_format;\n\n    try {\n\n        clog = fetchLog(log_format);\n\n        // find first commit after start_timestamp if specified\n        if(clog != 0 && gGourceSettings.start_timestamp != 0) {\n\n            RCommit commit;\n\n            while(!gGourceSettings.shutdown && !clog->isFinished()) {\n\n                if(clog->nextCommit(commit) && commit.timestamp >= gGourceSettings.start_timestamp) {\n                    clog->bufferCommit(commit);\n                    break;\n                }\n            }\n        }\n\n    } catch(SeekLogException& exception) {\n        error = \"unable to read log file\";\n    } catch(SDLAppException& exception) {\n        error = exception.what();\n    }\n\n    if(!clog && error.empty()) {\n        if(boost::filesystem::is_directory(logfile)) {\n            if(!log_format.empty()) {\n                \n                if(gGourceSettings.start_timestamp || gGourceSettings.stop_timestamp) {\n                    error = \"failed to generate log file for the specified time period\";\n                } else {\n                    error = \"failed to generate log file\";\n                }\n#ifdef _WIN32\n            // no error - should trigger help message\n            } else if(gGourceSettings.default_path && boost::filesystem::exists(\"./gource.exe\")) {\n                error = \"\";\n#endif\n            } else {\n                error = \"directory not supported\";\n            }\n        } else {\n            error = \"unsupported log format (you may need to regenerate your log file)\";\n        }\n    }\n\n    logmill_thread_state = clog ? LOGMILL_STATE_SUCCESS : LOGMILL_STATE_FAILURE;\n}\n\nvoid RLogMill::abort() {\n    if(!thread) return;\n\n    // TODO: make abort nicer by notifying the log process\n    //       we want to shutdown\n    SDL_WaitThread(thread, 0);\n    \n    thread = 0;\n}\n\nbool RLogMill::isFinished() {\n    return logmill_thread_state > LOGMILL_STATE_FETCHING;\n}\n\nint RLogMill::getStatus() {\n    return logmill_thread_state;\n}\n\nstd::string RLogMill::getError() {\n    return error;\n}\n\n\nRCommitLog* RLogMill::getLog() {\n\n    if(thread != 0) {\n        SDL_WaitThread(thread, 0);\n        thread = 0;        \n    }\n    \n    return clog;\n}\n\nbool RLogMill::findRepository(boost::filesystem::path& dir, std::string& log_format) {\n\n    dir = canonical(dir);\n\n    //fprintf(stderr, \"find repository from initial path: %s\\n\", dir.string().c_str());\n\n    while(is_directory(dir)) {\n\n             if(is_directory(dir / \".git\") || is_regular_file(dir / \".git\")) log_format = \"git\";\n        else if(is_directory(dir / \".hg\"))  log_format = \"hg\";\n        else if(is_directory(dir / \".bzr\")) log_format = \"bzr\";\n        else if(is_directory(dir / \".svn\")) log_format = \"svn\";\n\n        if(!log_format.empty()) {\n            //fprintf(stderr, \"found '%s' repository at: %s\\n\", log_format.c_str(), dir.string().c_str());\n            return true;\n        }\n\n        if(!dir.has_parent_path()) return false;\n\n        dir = dir.parent_path();\n    }\n\n    return false;\n}\n\n\nRCommitLog* RLogMill::fetchLog(std::string& log_format) {\n\n    RCommitLog* clog = 0;\n\n    //if the log format is not specified and 'logfile' is a directory, recursively look for a version control repository.\n    //this method allows for something strange like someone who having an svn repository inside a git repository\n    //(in which case it would pick the svn directory as it would encounter that first)\n\n    if(log_format.empty() && logfile != \"-\") {\n\n        try {\n            boost::filesystem::path repo_path(logfile);\n\n            if(is_directory(repo_path)) {\n                if(findRepository(repo_path, log_format)) {\n                    logfile = repo_path.string();\n                }\n            }\n        } catch(boost::filesystem::filesystem_error& error) {\n        }\n    }\n\n    //we've been told what format to use\n    if(log_format.size() > 0) {\n        debugLog(\"log-format = %s\", log_format.c_str());\n\n        if(log_format == \"git\") {\n            clog = new GitCommitLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n\n            clog = new GitRawCommitLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"hg\") {\n            clog = new MercurialLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"bzr\") {\n            clog = new BazaarLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"cvs\") {\n            clog = new CVSEXPCommitLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"custom\") {\n            clog = new CustomLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"apache\") {\n            clog = new ApacheCombinedLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"svn\") {\n            clog = new SVNCommitLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        if(log_format == \"cvs2cl\") {\n            clog = new CVS2CLCommitLog(logfile);\n            if(clog->checkFormat()) return clog;\n            delete clog;\n        }\n\n        return 0;\n    }\n\n    // try different formats until one works\n\n    //git\n    debugLog(\"trying git...\");\n    clog = new GitCommitLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //mercurial\n    debugLog(\"trying mercurial...\");\n    clog = new MercurialLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //bzr\n    debugLog(\"trying bzr...\");\n    clog = new BazaarLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //git raw\n    debugLog(\"trying git raw...\");\n    clog = new GitRawCommitLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //cvs exp\n    debugLog(\"trying cvs-exp...\");\n    clog = new CVSEXPCommitLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //svn\n    debugLog(\"trying svn...\");\n    clog = new SVNCommitLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //cvs2cl\n    debugLog(\"trying cvs2cl...\");\n    clog = new CVS2CLCommitLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //custom\n    debugLog(\"trying custom...\");\n    clog = new CustomLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    //apache\n    debugLog(\"trying apache combined...\");\n    clog = new ApacheCombinedLog(logfile);\n    if(clog->checkFormat()) return clog;\n\n    delete clog;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/logmill.h",
    "content": "/*\n    Copyright (C) 2012 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef LOGMILL_H\n#define LOGMILL_H\n\n#include <boost/filesystem.hpp>\n\n#include \"SDL_thread.h\"\n\n#include \"core/sdlapp.h\"\n#include \"core/display.h\"\n\n#include \"formats/commitlog.h\"\n\n#if defined(HAVE_PTHREAD) && !defined(_WIN32)\n#include <signal.h>\n#endif\n\nenum {\n    LOGMILL_STATE_STARTUP,\n    LOGMILL_STATE_FETCHING,\n    LOGMILL_STATE_SUCCESS,\n    LOGMILL_STATE_FAILURE\n};\n\nclass RLogMill {\n    SDL_Thread* thread;\n    SDL_mutex* mutex;\n    SDL_cond* cond;\n    \n    int logmill_thread_state;\n\n    std::string logfile;\n    RCommitLog* clog;\n\n    std::string error;\n\n    bool findRepository(boost::filesystem::path& dir, std::string& log_format);\n    RCommitLog* fetchLog(std::string& log_format);\npublic:\n    RLogMill(const std::string& logfile);\n    ~RLogMill();\n\n    void run();\n\n    void abort();\n\n    std::string getError();\n\n    int getStatus();\n    bool isFinished();\n\n    RCommitLog* getLog();\n};\n\n#endif\n"
  },
  {
    "path": "src/main.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"main.h\"\n\nint main(int argc, char *argv[]) {\n\n    std::string exepath;\n#ifndef _WIN32\n    if(argc > 0) {\n        exepath = std::string(argv[0]);\n    }\n#endif\n\n    SDLAppInit(\"Gource\", \"gource\", exepath);\n\n#ifdef _WIN32\n        SDLApp::initConsole();\n#endif\n\n    ConfFile conf;\n    std::vector<std::string> files;\n\n    //convert args to a conf file\n    //read the conf file\n    //apply the conf file to settings\n\n    try {\n        gGourceSettings.parseArgs(argc, argv, conf, &files);\n\n        if(gGourceSettings.load_config.empty() && !files.empty()) {\n            //see if file looks like a config file\n            for(std::vector<std::string>::iterator fit = files.begin(); fit != files.end(); fit++) {\n                std::string file = *fit;\n\n                int file_length = file.size();\n\n                if(   (file.rfind(\".conf\") == (file_length-5) && file_length > 5)\n                   || (file.rfind(\".cfg\")  == (file_length-4) && file_length > 4)\n                   || (file.rfind(\".ini\")  == (file_length-4) && file_length > 4) ) {\n\n                    bool is_conf=true;\n\n                    try {\n                        ConfFile conftest;\n                        conftest.load(file);\n                    } catch(ConfFileException& exception) {\n                        is_conf = false;\n                    }\n\n                    if(is_conf) {\n                        gGourceSettings.load_config = file;\n                        files.erase(fit);\n                        break;\n                    }\n                }\n            }\n        }\n\n        //set log level\n        Logger::getDefault()->setLevel(gGourceSettings.log_level);\n\n#ifdef _WIN32\n        // hide console if not needed\n        if(gGourceSettings.log_level == LOG_LEVEL_OFF && !SDLApp::existing_console) {\n            SDLApp::showConsole(false);\n        }\n#endif\n\n        //load config\n        if(!gGourceSettings.load_config.empty()) {\n            conf.clear();\n            conf.load(gGourceSettings.load_config);\n\n            //apply args to loaded conf file\n            gGourceSettings.parseArgs(argc, argv, conf);\n        }\n\n        //set path\n        if(!files.empty()) {\n            std::string path = files[files.size()-1];\n\n            ConfSectionList* sectionlist = conf.getSections(\"gource\");\n\n            if(sectionlist!=0) {\n                for(ConfSectionList::iterator sit = sectionlist->begin(); sit != sectionlist->end(); sit++) {\n                    (*sit)->setEntry(\"path\", path);\n                }\n            } else {\n                conf.setEntry(\"gource\", \"path\", path);\n            }\n        }\n\n        //apply the config / see if its valid\n        gGourceSettings.importDisplaySettings(conf);\n        gGourceSettings.importGourceSettings(conf);\n\n        //save config\n        if(!gGourceSettings.save_config.empty()) {\n            conf.save(gGourceSettings.save_config);\n            exit(0);\n        }\n\n        //write custom log file\n        if(!gGourceSettings.output_custom_filename.empty() && !gGourceSettings.path.empty()) {\n\n            Gource::writeCustomLog(gGourceSettings.path, gGourceSettings.output_custom_filename);\n            exit(0);\n        }\n\n    } catch(ConfFileException& exception) {\n\n        SDLAppQuit(exception.what());\n    }\n\n    //enable frameless\n    display.enableFrameless(gGourceSettings.frameless);\n\n    // this causes corruption on some video drivers\n    if(gGourceSettings.multisample) {\n        display.multiSample(4);\n    }\n\n    //background needs alpha channel\n    if(gGourceSettings.transparent) {\n        display.enableAlpha(true);\n    }\n\n    //enable vsync\n    display.enableVsync(gGourceSettings.vsync);\n\n    //allow resizing window if we are not recording\n    if(gGourceSettings.resizable && gGourceSettings.output_ppm_filename.empty()) {\n        display.enableResize(true);\n    }\n\n    // Change OS High DPI display behaviour\n    // On Windows this behaves differently, it seems safe to always enable it\n    bool high_dpi = true;\n#ifndef _WIN32\n    // Requesting High DPI on MacOS may cause the pixel resolution to be doubled.\n    // If a resolution has been specified this may not be appropriate\n    // E.g. if you are recording a video at a specific resolution\n    // Can override by supplying --high-dpi option.\n    if(gGourceSettings.viewport_specified && !gGourceSettings.high_dpi) {\n        high_dpi = false;\n    }\n#endif\n    display.enableHighDPIAwareness(high_dpi);\n\n    try {\n\n        display.init(\"Gource\", gGourceSettings.display_width, gGourceSettings.display_height, gGourceSettings.fullscreen, gGourceSettings.screen);\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n        if(!display.isFullscreen() && gGourceSettings.window_x >= 0 && gGourceSettings.window_y >= 0) {\n            SDL_SetWindowPosition(display.sdl_window, gGourceSettings.window_x, gGourceSettings.window_y);\n        }\n\n        SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, \"0\");\n#endif\n        \n    } catch(SDLInitException& exception) {\n\n        char errormsg[1024];\n        snprintf(errormsg, 1024, \"SDL initialization failed - %s\", exception.what());\n\n        SDLAppQuit(errormsg);\n    }\n\n    //init frame exporter\n    FrameExporter* exporter = 0;\n\n    if(gGourceSettings.output_ppm_filename.size() > 0) {\n\n        try {\n\n            exporter = new PPMExporter(gGourceSettings.output_ppm_filename);\n\n        } catch(PPMExporterException& exception) {\n\n            char errormsg[1024];\n            snprintf(errormsg, 1024, \"could not write to '%s'\", exception.what());\n\n            SDLAppQuit(errormsg);\n        }\n    }\n\n    if(display.multiSamplingEnabled()) {\n        glEnable(GL_MULTISAMPLE_ARB);\n    }\n\n    GourceShell* gourcesh = 0;\n\n    try {\n        gourcesh = gGourceShell = new GourceShell(&conf, exporter);\n        gourcesh->run();\n\n    } catch(ResourceException& exception) {\n\n        char errormsg[1024];\n        snprintf(errormsg, 1024, \"failed to load resource '%s'\", exception.what());\n\n        SDLAppQuit(errormsg);\n\n    } catch(SDLAppException& exception) {\n\n        if(exception.showHelp()) {\n            gGourceSettings.help();\n        } else {\n            SDLAppQuit(exception.what());\n        }\n    }\n\n    gGourceShell = 0;\n\n    if(gourcesh != 0) delete gourcesh;\n    if(exporter != 0) delete exporter;\n\n    //free resources\n    display.quit();\n\n    return 0;\n}\n"
  },
  {
    "path": "src/main.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef GOURCE_MAIN_H\n#define GOURCE_MAIN_H\n\n#include \"gource_shell.h\"\n#include \"gource.h\"\n\n#endif\n"
  },
  {
    "path": "src/pawn.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"pawn.h\"\n\nfloat gGourceShadowStrength = 0.5;\n\nPawn::Pawn(const std::string& name, vec2 pos, int tagid) {\n    this->name  = name;\n    this->pos   = pos;\n    this->tagid = tagid;\n    this->hidden = false;\n    this->speed = 1.0;\n\n    selected = false;\n    mouseover = false;\n\n    shadow = false;\n\n    namewidth = 0;\n\n    this->shadowOffset = vec2(2.0, 2.0);\n    this->elapsed = 0.0;\n    this->fadetime = 1.0;\n    this->nametime = 5.0;\n    this->name_interval = 0.0;\n    this->namecol = vec3(1.0, 1.0, 1.0);\n\n    this->graphic = 0;\n    this->graphic_ratio = 1.0;\n    size = 0.0f;\n}\n\nfloat Pawn::getSize() {\n    return size;\n}\n\nvoid Pawn::setPos(vec2 pos) {\n    this->pos = pos;\n}\n\nint Pawn::getTagID() {\n    return tagid;\n}\n\nvoid Pawn::showName() {\n    if(name_interval <= 0.0) name_interval = nametime;\n}\n\nvoid Pawn::updateQuadItemBounds() {\n\n    float halfsize_x = size * 0.5f;\n\n    vec2 halfsize ( halfsize_x, halfsize_x * graphic_ratio );\n\n    //set bounds\n    quadItemBounds.set(pos - halfsize, pos + halfsize);\n}\n\nvoid Pawn::logic(float dt) {\n    elapsed += dt;\n\n    if(!isHidden()) {\n        if(name_interval>0.0) name_interval -= dt;\n    }\n}\n\nvoid Pawn::setGraphic(TextureResource* graphic) {\n\n    if(graphic) {\n        graphic_ratio = graphic->h / (float) graphic->w;\n    } else {\n        graphic_ratio = 1.0f;\n    }\n\n    this->graphic = graphic;\n    this->dims = vec2(size, size*graphic_ratio);\n}\n\n\nvoid Pawn::setMouseOver(bool over) {\n    //showName();\n    this->mouseover = over;\n}\n\nvoid Pawn::setSelected(bool selected) {\n    this->selected = selected;\n}\n\nconst vec3& Pawn::getNameColour() const {\n    return namecol;\n}\n\nvoid Pawn::calcScreenPos(const vec2& offset) {\n    screenpos = display.project(vec3(pos.x+offset.x, pos.y+offset.y, 0.0f));\n}\n\nbool Pawn::nameVisible() const {\n    return ((!selected && name_interval < 0.0) || isHidden()) ? false : true;\n}\n\nvoid Pawn::drawName() {\n    if(!nameVisible()) return;\n\n    float done = nametime - name_interval;\n\n    if(done < 1.0) {\n        drawNameText(done);\n    } else if(done > 1.0 && done < nametime - 1.0) {\n        drawNameText(1.0);\n    } else {\n        drawNameText((nametime - done));\n    }\n}\n\nvoid Pawn::drawShadow(float dt) {\n    if(isHidden() || !shadow) return;\n\n    float halfsize = size * 0.5f;\n    vec2 offsetpos = pos - vec2(halfsize, halfsize*graphic_ratio) + shadowOffset;\n\n    float alpha = getAlpha();\n\n    glBindTexture(GL_TEXTURE_2D, graphic->textureid);\n\n    glColor4f(0.0, 0.0, 0.0, alpha * gGourceShadowStrength);\n\n    glPushMatrix();\n        glTranslatef(offsetpos.x, offsetpos.y, 0.0f);\n\n        glBegin(GL_QUADS);\n            glTexCoord2f(0.0f,0.0f);\n            glVertex2f(0.0f, 0.0f);\n\n            glTexCoord2f(1.0f,0.0f);\n            glVertex2f(size, 0.0f);\n\n            glTexCoord2f(1.0f,1.0f);\n            glVertex2f(size, size*graphic_ratio);\n\n            glTexCoord2f(0.0f,1.0f);\n            glVertex2f(0.0f, size*graphic_ratio);\n        glEnd();\n    glPopMatrix();\n}\n\nvoid Pawn::draw(float dt) {\n    if(hidden) return;\n\n    float halfsize = size * 0.5f;\n    vec2 offsetpos = pos - vec2(halfsize, halfsize*graphic_ratio);\n\n    float alpha = getAlpha();\n\n    vec3 col = getColour();\n\n    glBindTexture(GL_TEXTURE_2D, graphic->textureid);\n\n    glPushMatrix();\n        glTranslatef(offsetpos.x, offsetpos.y, 0.0f);\n\n        glColor4f(col.x, col.y, col.z, alpha);\n\n        glBegin(GL_QUADS);\n            glTexCoord2f(0.0f,0.0f);\n            glVertex2f(0.0f, 0.0f);\n\n            glTexCoord2f(1.0f,0.0f);\n            glVertex2f(size, 0.0f);\n\n            glTexCoord2f(1.0f,1.0f);\n            glVertex2f(size, size*graphic_ratio);\n\n            glTexCoord2f(0.0f,1.0f);\n            glVertex2f(0.0f, size*graphic_ratio);\n        glEnd();\n\n    glPopMatrix();\n}\n"
  },
  {
    "path": "src/pawn.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef PAWN_H\n#define PAWN_H\n\n#include <string>\n\n#include \"gource_settings.h\"\n\n#include \"core/display.h\"\n#include \"core/fxfont.h\"\n#include \"core/vectors.h\"\n#include \"core/quadtree.h\"\n\nclass Pawn : public QuadItem {\nprotected:\n    vec2 pos;\n    vec2 shadowOffset;\n\n    std::string name;\n    float namewidth;\n    vec2 accel;\n    float speed;\n\n    float elapsed;\n    float fadetime;\n\n    float nametime;\n    float name_interval;\n    vec3 namecol;\n\n    bool shadow;\n\n    bool hidden;\n\n    int tagid;\n\n    FXFont font;\n\n    bool mouseover;\n\n    virtual bool nameVisible() const;\n\n    virtual void drawNameText(float alpha) {};\n    virtual const vec3& getNameColour() const;\nprotected:\n    bool selected;\npublic:\n    float size;\n    float graphic_ratio;\n    TextureResource* graphic;\n    vec3 screenpos;\n    vec2 dims;\n\n    Pawn(const std::string& name, vec2 pos, int tagid);\n    const vec2 & getPos() const { return pos; }\n    void setPos(vec2 pos);\n\n    void calcScreenPos(const vec2& offset);\n\n    void updateQuadItemBounds();\n\n    void showName();\n\n    void setMouseOver(bool over);\n\n    float getSize();\n    int getTagID();\n\n    const std::string& getName() const { return name; }\n\n    virtual void setSelected(bool selected);\n    bool isSelected() { return selected; };\n\n    void setHidden(bool hidden){ this->hidden = hidden; }\n    bool isHidden() const { return hidden; }\n\n    virtual float getAlpha() const{ return std::min(elapsed/fadetime, 1.0f); }\n    virtual vec3 getColour() const { return vec3(1.0, 1.0, 1.0); }\n\n    void setGraphic(TextureResource* graphic);\n\n    void logic(float dt);\n    void draw(float dt);\n    void drawShadow(float dt);\n\n    void drawName();\n};\n\nextern float gGourceShadowStrength;\n\n#endif\n\n"
  },
  {
    "path": "src/slider.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"slider.h\"\n#include \"gource_settings.h\"\n\n// PositionSlider\n\nPositionSlider::PositionSlider(float percent) {\n    this->percent = percent;\n}\n\nvoid PositionSlider::init() {\n    font = fontmanager.grab(gGourceSettings.font_file, 16 * gGourceSettings.font_scale);\n    font.dropShadow(true);\n\n    slidercol = vec3(1.0, 1.0, 1.0);\n\n    mouseover = -1.0;\n\n    mouseover_elapsed = 1.0;\n    fade_time = 1.0;\n    alpha = 0.0;\n\n    capwidth = 0.0f;\n\n    resize();\n}\n\nconst Bounds2D& PositionSlider::getBounds() const {\n    return bounds;\n}\n\nvoid PositionSlider::resize() {\n    int gap = 35;\n\n    bounds.reset();\n    bounds.update(vec2(gap, display.height - gap*2));\n    bounds.update(vec2(display.width - gap, display.height - gap));\n}\n\nvoid PositionSlider::setColour(vec3 col) {\n    slidercol = col;\n}\n\nvoid PositionSlider::show() {\n    mouseover_elapsed = 0.0;\n}\n\nbool PositionSlider::mouseOver(vec2 pos, float* percent_ptr) {\n    if(bounds.contains(pos)) {\n\n        mouseover_elapsed = 0;\n        mouseover = pos.x;\n\n        if(percent_ptr != 0) {\n            *percent_ptr = (float) (pos.x - bounds.min.x) / (bounds.max.x - bounds.min.x);\n        }\n\n        return true;\n    }\n\n    mouseover = -1.0;\n\n    return false;\n}\n\nbool PositionSlider::click(vec2 pos, float* percent_ptr) {\n    if(mouseOver(pos, &percent)) {\n\n        if(percent_ptr != 0) {\n            *percent_ptr = percent;\n        }\n\n        return true;\n    }\n\n    return false;\n}\n\nvoid PositionSlider::setCaption(const std::string& caption) {\n    capwidth = 0.0;\n    this->caption = caption;\n\n    if(caption.size()) {\n        capwidth = font.getWidth(caption.c_str());\n    }\n}\n\nvoid PositionSlider::setPercent(float percent) {\n    this->percent = percent;\n}\n\nvoid PositionSlider::logic(float dt) {\n\n    if(mouseover < 0.0 && mouseover_elapsed < fade_time) mouseover_elapsed += dt;\n\n    if(mouseover_elapsed < fade_time && alpha < 1.0) {\n        alpha = std::min(1.0f, alpha+dt);\n\n    } else if(mouseover_elapsed >= fade_time && alpha > 0.0) {\n\n        alpha = std::max(0.0f, alpha-dt);\n\n    }\n}\n\nvoid PositionSlider::drawSlider(float pos_x) const {\n\n    glLineWidth(2.0f);\n\n    bounds.draw();\n\n    glLineWidth(2.0f);\n\n    glBegin(GL_LINES);\n        glVertex2f(pos_x, bounds.min.y);\n        glVertex2f(pos_x, bounds.max.y);\n    glEnd();\n}\n\nvoid PositionSlider::draw(float dt) {\n\n    glDisable(GL_TEXTURE_2D);\n\n    float pos_x = bounds.min.x + (bounds.max.x - bounds.min.x) * percent;\n\n    glColor4f(0.0f, 0.0f, 0.0f, 0.7*alpha);\n\n    glPushMatrix();\n        glTranslatef(2.0, 2.0, 0.0);\n        drawSlider(pos_x);\n    glPopMatrix();\n\n    glColor4f(slidercol.x, slidercol.y, slidercol.z, alpha);\n\n    drawSlider(pos_x);\n\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glEnable(GL_BLEND);\n    glEnable(GL_TEXTURE_2D);\n\n    glColor4f(1.0, 1.0, 1.0, 1.0);\n\n    if(caption.size() && mouseover >= 0.0) {\n        int height_offset = 25 * gGourceSettings.font_scale;\n        font.draw(std::min((double)display.width - capwidth - 1.0, std::max(1.0, mouseover - (capwidth/2.0))), bounds.min.y - height_offset, caption);\n    }\n\n}\n\n"
  },
  {
    "path": "src/slider.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef POS_SLIDER_H\n#define POS_SLIDER_H\n\n#include \"core/logger.h\"\n#include \"core/bounds.h\"\n#include \"core/fxfont.h\"\n\nclass PositionSlider {\n\n    FXFont font;\n\n    Bounds2D bounds;\n    float percent;\n    float mouseover;\n    float mouseover_elapsed;\n    float fade_time;\n    float alpha;\n\n    vec3 slidercol;\n\n    float capwidth;\n    std::string caption;\n\n    void drawSlider(float position) const;\npublic:\n    PositionSlider(float percent = 0.0f);\n\n    void init();\n\n    void setColour(vec3 col);\n\n    void setCaption(const std::string& cap);\n\n    void setPercent(float percent);\n\n    void resize();\n\n    void show();\n\n    const Bounds2D& getBounds() const;\n\n    bool mouseOver(vec2 pos, float* percent_ptr);\n    bool click(vec2 pos, float* percent_ptr);\n    void logic(float dt);\n    void draw(float dt);\n};\n\n#endif\n"
  },
  {
    "path": "src/spline.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"spline.h\"\n\nSplineEdge::SplineEdge() {\n}\n\nvoid SplineEdge::update(const vec2& pos1, const vec4& col1, const vec2& pos2, const vec4& col2, const vec2& spos) {\n\n    vec2 pt_last;\n    vec4 col_last;\n\n    vec2 mid = (pos1 - pos2) * 0.5f;\n    vec2 to  = vec2(pos1 - spos);\n\n    //TODO: not sure this makes any sense\n    //float dp = std::min(1.0f, to.normal().dot(mid.normal()));\n    float dp = std::min(1.0f, glm::dot(normalise(to), normalise(mid)) );\n\n    float ang = acos(dp) / PI;\n\n    int edge_detail = std::min(10, (int) (ang * 100.0));\n\n    if(edge_detail<1) edge_detail = 1;\n\n    spline_point.clear();\n    spline_colour.clear();\n\n    spline_point.reserve(edge_detail+1);\n    spline_colour.reserve(edge_detail+1);\n\n    //calculate positions\n    for(int i=0; i <= edge_detail; i++) {\n        float t = (float)i/edge_detail;\n        float tt = 1.0f-t;\n\n        vec2 p0 = pos1 * t + spos * tt;\n        vec2 p1 = spos * t + pos2 * tt;\n\n        vec2 pt = p0 * t + p1 * tt;\n\n        vec4 coln = col1 * t + col2 * tt;\n\n        spline_point.push_back(pt);\n        spline_colour.push_back(coln);\n    }\n\n    const float pos = gGourceSettings.dir_name_position;\n\n    const float s_quota = 0.5f - glm::abs(pos - 0.5f);\n    const float p_quota = 1.0f - s_quota;\n\n    label_pos = pos1 * (p_quota * (1.0f - pos)) + pos2 * (p_quota * pos) + spos * s_quota;\n}\n\nconst vec2& SplineEdge::getLabelPos() const {\n    return label_pos;\n}\n\nvoid SplineEdge::drawToVBO(quadbuf& buffer) const {\n\n    int edges_count = spline_point.size() - 1;\n\n    for(int i=0; i < edges_count; i++) {\n\n        //vec2 perp = (spline_point[i] - spline_point[i+1]).perpendicular().normal() * 2.5f;        \n        \n        vec2 perp = (spline_point[i] - spline_point[i+1]);\n        perp = normalise(vec2(-perp.y, perp.x)) * 2.5f;\n\n        quadbuf_vertex v1(spline_point[i]   + perp, spline_colour[i],   vec2(1.0f, 0.0f));\n        quadbuf_vertex v2(spline_point[i]   - perp, spline_colour[i],   vec2(0.0f, 0.0f));\n        quadbuf_vertex v3(spline_point[i+1] - perp, spline_colour[i+1], vec2(0.0f, 0.0f));\n        quadbuf_vertex v4(spline_point[i+1] + perp, spline_colour[i+1], vec2(1.0f, 0.0f));\n\n        buffer.add(0, v1, v2, v3, v4);       \n    }\n}\n\nvoid SplineEdge::drawBeam(const vec2 & pos1, const vec4 & col1, const vec2 & pos2, const vec4 & col2, float radius, bool first) const{\n\n    //vec2 perp = (pos1 - pos2).perpendicular().normal() * radius;\n\n    vec2 perp = (pos1 - pos2);\n    perp = normalise(vec2(-perp.y, perp.x)) * radius;\n    \n    \n    // src point\n    if(first) {\n        glColor4fv(glm::value_ptr(col1));\n        glTexCoord2f(1.0,0.0);\n        glVertex2f(pos1.x + perp.x, pos1.y + perp.y);\n        glTexCoord2f(0.0,0.0);\n        glVertex2f(pos1.x - perp.x, pos1.y - perp.y);\n    }\n\n    // dest point\n    glColor4fv(glm::value_ptr(col2));\n    glTexCoord2f(1.0,0.0);\n    glVertex2f(pos2.x + perp.x, pos2.y + perp.y);\n    glTexCoord2f(0.0,0.0);\n    glVertex2f(pos2.x - perp.x, pos2.y - perp.y);\n}\n\nvoid SplineEdge::drawShadow() const{\n\n    int edges_count = spline_point.size() - 1;\n\n    vec2 offset(2.0, 2.0);\n\n    glBegin(GL_QUAD_STRIP);\n\n    for(int i=0;i<edges_count;i++) {\n        drawBeam(spline_point[i] + offset, vec4(0.0, 0.0, 0.0, gGourceShadowStrength), spline_point[i+1] + offset, vec4(0.0, 0.0, 0.0, gGourceShadowStrength), 2.5, i==0);\n    }\n\n    glEnd();\n}\n\nvoid SplineEdge::draw() const{\n\n    int edges_count = spline_point.size() - 1;\n\n    glBegin(GL_QUAD_STRIP);\n\n    for(int i=0;i<edges_count;i++) {\n        drawBeam(spline_point[i], spline_colour[i], spline_point[i+1], spline_colour[i+1], 2.5, i==0);\n    }\n\n    glEnd();\n}\n"
  },
  {
    "path": "src/spline.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef SPLINE_EDGE_H\n#define SPLINE_EDGE_H\n\n#include \"core/display.h\"\n#include \"core/vectors.h\"\n#include \"core/pi.h\"\n\n#include \"pawn.h\"\n\n#include <vector>\n\nclass SplineEdge {\n\n    std::vector<vec2> spline_point;\n    std::vector<vec4> spline_colour;\n\n    vec2 label_pos;\n    \n    void drawBeam(const vec2 & pos1, const vec4 & col1, const vec2 & pos2, const vec4 & col2, float radius, bool first) const;\npublic:\n    SplineEdge();\n    \n    const vec2& getLabelPos() const;\n    \n    void update(const vec2& pos1, const vec4& col1, const vec2& pos2, const vec4& col2, const vec2& spos);\n\n    void drawToVBO(quadbuf& buffer) const;\n\n    void drawShadow() const;\n    void draw() const;\n};\n\n#endif\n"
  },
  {
    "path": "src/test/datetime_tests.cpp",
    "content": "/*\n    Copyright (C) 2021 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#include \"../main.h\"\n#include \"../gource_settings.h\"\n\n#include <stdlib.h>\n#include <boost/test/unit_test.hpp>\n\nBOOST_AUTO_TEST_CASE( parse_date_time_tests )\n{\n    time_t timestamp;\n\n    BOOST_CHECK_EQUAL(putenv((char*)\"TZ=UTC\"), 0);\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635724800);\n\n    BOOST_CHECK_EQUAL(putenv((char*)\"TZ=Pacific/Auckland\"), 0);\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635678000);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01Z\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635724800);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01+0\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635724800);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01+13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635678000);\n    \n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 +13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635678000);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01+5:30\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635705000);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721260);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01Z\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635768060);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01+0\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635768060);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01+13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721260);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01 +13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721260);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01+5:30\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635748260);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01:59\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59.123\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01:59Z\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635768119);\n    \n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01:59+0\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635768119);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01:59+13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01:59 +13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01 12:01:59+5:30\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635748319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59.123\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59Z\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635768119);\n    \n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59+0\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635768119);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59+13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59+13\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635721319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59+5:30\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635748319);\n\n    BOOST_CHECK(SDLAppSettings::parseDateTime(\"2021-11-01T12:01:59.123+5:30\", timestamp));\n    BOOST_CHECK_EQUAL(timestamp, 1635748319);\n}\n"
  },
  {
    "path": "src/test/main.cpp",
    "content": "#define BOOST_TEST_MODULE Gource Test Suite\n#include <boost/test/included/unit_test.hpp>\n"
  },
  {
    "path": "src/test/regex_tests.cpp",
    "content": "/*\n    Copyright (C) 2021 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#include \"../core/regex.h\"\n\n#include <stdlib.h>\n#include <boost/test/unit_test.hpp>\n\nBOOST_AUTO_TEST_CASE( regex_tests )\n{\n    Regex regex(\"^[A-Z]+$\");\n    BOOST_CHECK(regex.isValid());\n\n    BOOST_CHECK(regex.match(\"ABC\"));\n    BOOST_CHECK(regex.match(\"abc\") == false);\n    BOOST_CHECK(regex.match(\"ABC123\") == false);\n\n    Regex regex_copy = Regex(\"^[A-Z]+$\");\n    BOOST_CHECK(regex_copy.isValid());\n    BOOST_CHECK(regex_copy.match(\"ABC\"));\n\n    BOOST_CHECK_THROW(Regex(\"^[A-Z+$\"), RegexCompilationException);\n\n    Regex test_regex(\"^[A-Z+$\", true);\n    BOOST_CHECK(test_regex.isValid() == false);\n\n    std::vector<std::string> matches;\n    Regex capture_regex(\"([0-9]+)\");\n    BOOST_CHECK(capture_regex.match(\"ABC123\", &matches));\n    BOOST_CHECK_EQUAL(matches.size(), 1);\n    BOOST_CHECK_EQUAL(matches[0], \"123\");\n}\n"
  },
  {
    "path": "src/textbox.cpp",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"textbox.h\"\n\nTextBox::TextBox() {\n}\n\nTextBox::TextBox(const FXFont& font) {\n    this->font   = font;\n\n    shadow = vec2(3.0f, 3.0f);\n    \n    colour = vec3(0.7f, 0.7f, 0.7f);\n    corner = vec2(0.0f,0.0f);\n    alpha  = 1.0f;\n    brightness = 1.0f;\n    max_width_chars = 1024;   \n    rect_width = 0;\n    rect_height = 0;\n    visible = false;\n}\n\nvoid TextBox::hide() {\n    visible = false;\n}\n\nvoid TextBox::show() {\n    visible = true;\n}\n\nvoid TextBox::setBrightness(float brightness) {\n    this->brightness = brightness;\n}\n\nvoid TextBox::setColour(const vec3& colour) {\n    this->colour = colour;\n}\n\nvoid TextBox::setAlpha(float alpha) {\n    this->alpha = alpha;\n}\n\nvoid TextBox::clear() {\n    content.clear();\n    rect_width = 0;\n    rect_height = 2;\n}\n\nvoid TextBox::addLine(std::string str) {\n\n    if(max_width_chars> 0 && str.size() > max_width_chars) {\n        str = str.substr(0, max_width_chars);\n    }\n\n    int width = font.getWidth(str) + 6;\n\n    if(width > rect_width) rect_width = width;\n    \n    rect_height += (font.getFontSize()+4);\n    \n    content.push_back(str);   \n}\n\nvoid TextBox::setText(const std::string& str) {\n\n    clear();\n   \n    addLine(str);\n}\n\nvoid TextBox::setText(const std::vector<std::string>& content) {\n\n    clear();\n\n    for(std::vector<std::string>::const_iterator it = content.begin(); it != content.end(); it++) {\n        addLine(*it);\n    }\n}\n\nvoid TextBox::setPos(const vec2& pos, bool adjust) {\n\n    corner = pos;\n\n    if(!adjust) return;\n\n    int fontheight = font.getFontSize() + 4;\n    \n    corner.y -= rect_height;\n\n    if((corner.x + rect_width) > display.width) {\n        if((corner.x - rect_width - fontheight )>0) {\n            corner.x -= rect_width;\n        } else {\n            corner.x = display.width - rect_width;\n        }\n    }\n\n    if(corner.y < 0) corner.y += rect_height + fontheight;\n    if(corner.y +rect_height > display.height) corner.y -= rect_height;\n\n}\n\nvoid TextBox::draw() const {\n    if(!visible) return;\n\n    glDisable(GL_TEXTURE_2D);\n    glEnable(GL_BLEND);\n    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    \n    \n    glColor4f(0.0f, 0.0f, 0.0f, alpha * 0.333f);\n    \n    glPushMatrix();\n        glTranslatef(shadow.x, shadow.y, 0.0f);\n    \n        glBegin(GL_QUADS);\n            glVertex2f(corner.x,           corner.y);\n            glVertex2f(corner.x,           corner.y + rect_height);\n            glVertex2f(corner.x+rect_width, corner.y + rect_height);\n            glVertex2f(corner.x+rect_width, corner.y);\n        glEnd();\n    glPopMatrix();\n        \n    glColor4f(colour.x * brightness, colour.y * brightness, colour.z * brightness, alpha);\n\n    glBegin(GL_QUADS);\n        glVertex2f(corner.x,           corner.y);\n        glVertex2f(corner.x,           corner.y + rect_height);\n        glVertex2f(corner.x+rect_width, corner.y + rect_height);\n        glVertex2f(corner.x+rect_width, corner.y);\n    glEnd();    \n    \n    glEnable(GL_TEXTURE_2D);\n    glColor4f(1.0f, 1.0f, 1.0f, alpha);\n\n    int yinc = 3;\n\n    std::vector<std::string>::const_iterator it;\n    for(it = content.begin(); it != content.end(); it++) {\n        font.draw((int)corner.x+2, (int)corner.y+yinc,  (*it).c_str());\n        yinc += font.getFontSize() + 4;\n    }\n}\n"
  },
  {
    "path": "src/textbox.h",
    "content": "/*\n    Copyright (C) 2010 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#ifndef TEXT_BOX_H\n#define TEXT_BOX_H\n\n#include <vector>\n#include <string>\n\n#include \"core/display.h\"\n#include \"core/vectors.h\"\n#include \"core/fxfont.h\"\n\nclass TextBox {\n\n    std::vector<std::string> content;\n\n    vec3 colour;\n    float alpha;\n    float brightness;\n    vec2 corner;\n    vec2 shadow;\n    FXFont font;\n    int max_width_chars;\n    int rect_width;\n    int rect_height;\n    bool visible;\n    \npublic:\n    TextBox();\n    TextBox(const FXFont& font);\n\n    void hide();\n    void show();\n    \n    void clear();\n\n    void setPos(const vec2& pos, bool adjust = false);\n    void setColour(const vec3& colour);\n    void setAlpha(float alpha);\n    void setBrightness(float brightness);\n    \n    void addLine(std::string str);\n\n    void setText(const std::string& str);\n    void setText(const std::vector<std::string>& content);\n\n    void draw() const;\n};\n\n#endif\n"
  },
  {
    "path": "src/tinyxml/tinystr.cpp",
    "content": "/*\nwww.sourceforge.net/projects/tinyxml\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable for any\ndamages arising from the use of this software.\n\nPermission is granted to anyone to use this software for any\npurpose, including commercial applications, and to alter it and\nredistribute it freely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must\nnot claim that you wrote the original software. If you use this\nsoftware in a product, an acknowledgment in the product documentation\nwould be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and\nmust not be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source\ndistribution.\n*/\n\n\n#ifndef TIXML_USE_STL\n\n#include \"tinystr.h\"\n\n// Error value for find primitive\nconst TiXmlString::size_type TiXmlString::npos = static_cast< TiXmlString::size_type >(-1);\n\n\n// Null rep.\nTiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, { '\\0' } };\n\n\nvoid TiXmlString::reserve (size_type cap)\n{\n\tif (cap > capacity())\n\t{\n\t\tTiXmlString tmp;\n\t\ttmp.init(length(), cap);\n\t\tmemcpy(tmp.start(), data(), length());\n\t\tswap(tmp);\n\t}\n}\n\n\nTiXmlString& TiXmlString::assign(const char* str, size_type len)\n{\n\tsize_type cap = capacity();\n\tif (len > cap || cap > 3*(len + 8))\n\t{\n\t\tTiXmlString tmp;\n\t\ttmp.init(len);\n\t\tmemcpy(tmp.start(), str, len);\n\t\tswap(tmp);\n\t}\n\telse\n\t{\n\t\tmemmove(start(), str, len);\n\t\tset_size(len);\n\t}\n\treturn *this;\n}\n\n\nTiXmlString& TiXmlString::append(const char* str, size_type len)\n{\n\tsize_type newsize = length() + len;\n\tif (newsize > capacity())\n\t{\n\t\treserve (newsize + capacity());\n\t}\n\tmemmove(finish(), str, len);\n\tset_size(newsize);\n\treturn *this;\n}\n\n\nTiXmlString operator + (const TiXmlString & a, const TiXmlString & b)\n{\n\tTiXmlString tmp;\n\ttmp.reserve(a.length() + b.length());\n\ttmp += a;\n\ttmp += b;\n\treturn tmp;\n}\n\nTiXmlString operator + (const TiXmlString & a, const char* b)\n{\n\tTiXmlString tmp;\n\tTiXmlString::size_type b_len = static_cast<TiXmlString::size_type>( strlen(b) );\n\ttmp.reserve(a.length() + b_len);\n\ttmp += a;\n\ttmp.append(b, b_len);\n\treturn tmp;\n}\n\nTiXmlString operator + (const char* a, const TiXmlString & b)\n{\n\tTiXmlString tmp;\n\tTiXmlString::size_type a_len = static_cast<TiXmlString::size_type>( strlen(a) );\n\ttmp.reserve(a_len + b.length());\n\ttmp.append(a, a_len);\n\ttmp += b;\n\treturn tmp;\n}\n\n\n#endif\t// TIXML_USE_STL\n"
  },
  {
    "path": "src/tinyxml/tinystr.h",
    "content": "/*\nwww.sourceforge.net/projects/tinyxml\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable for any\ndamages arising from the use of this software.\n\nPermission is granted to anyone to use this software for any\npurpose, including commercial applications, and to alter it and\nredistribute it freely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must\nnot claim that you wrote the original software. If you use this\nsoftware in a product, an acknowledgment in the product documentation\nwould be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and\nmust not be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source\ndistribution.\n*/\n\n\n#ifndef TIXML_USE_STL\n\n#ifndef TIXML_STRING_INCLUDED\n#define TIXML_STRING_INCLUDED\n\n#include <assert.h>\n#include <string.h>\n\n/*\tThe support for explicit isn't that universal, and it isn't really\n\trequired - it is used to check that the TiXmlString class isn't incorrectly\n\tused. Be nice to old compilers and macro it here:\n*/\n#if defined(_MSC_VER) && (_MSC_VER >= 1200 )\n\t// Microsoft visual studio, version 6 and higher.\n\t#define TIXML_EXPLICIT explicit\n#elif defined(__GNUC__) && (__GNUC__ >= 3 )\n\t// GCC version 3 and higher.s\n\t#define TIXML_EXPLICIT explicit\n#else\n\t#define TIXML_EXPLICIT\n#endif\n\n\n/*\n   TiXmlString is an emulation of a subset of the std::string template.\n   Its purpose is to allow compiling TinyXML on compilers with no or poor STL support.\n   Only the member functions relevant to the TinyXML project have been implemented.\n   The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase\n   a string and there's no more room, we allocate a buffer twice as big as we need.\n*/\nclass TiXmlString\n{\n  public :\n\t// The size type used\n  \ttypedef size_t size_type;\n\n\t// Error value for find primitive\n\tstatic const size_type npos; // = -1;\n\n\n\t// TiXmlString empty constructor\n\tTiXmlString () : rep_(&nullrep_)\n\t{\n\t}\n\n\t// TiXmlString copy constructor\n\tTiXmlString ( const TiXmlString & copy) : rep_(0)\n\t{\n\t\tinit(copy.length());\n\t\tmemcpy(start(), copy.data(), length());\n\t}\n\n\t// TiXmlString constructor, based on a string\n\tTIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0)\n\t{\n\t\tinit( static_cast<size_type>( strlen(copy) ));\n\t\tmemcpy(start(), copy, length());\n\t}\n\n\t// TiXmlString constructor, based on a string\n\tTIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0)\n\t{\n\t\tinit(len);\n\t\tmemcpy(start(), str, len);\n\t}\n\n\t// TiXmlString destructor\n\t~TiXmlString ()\n\t{\n\t\tquit();\n\t}\n\n\tTiXmlString& operator = (const char * copy)\n\t{\n\t\treturn assign( copy, (size_type)strlen(copy));\n\t}\n\n\tTiXmlString& operator = (const TiXmlString & copy)\n\t{\n\t\treturn assign(copy.start(), copy.length());\n\t}\n\n\n\t// += operator. Maps to append\n\tTiXmlString& operator += (const char * suffix)\n\t{\n\t\treturn append(suffix, static_cast<size_type>( strlen(suffix) ));\n\t}\n\n\t// += operator. Maps to append\n\tTiXmlString& operator += (char single)\n\t{\n\t\treturn append(&single, 1);\n\t}\n\n\t// += operator. Maps to append\n\tTiXmlString& operator += (const TiXmlString & suffix)\n\t{\n\t\treturn append(suffix.data(), suffix.length());\n\t}\n\n\n\t// Convert a TiXmlString into a null-terminated char *\n\tconst char * c_str () const { return rep_->str; }\n\n\t// Convert a TiXmlString into a char * (need not be null terminated).\n\tconst char * data () const { return rep_->str; }\n\n\t// Return the length of a TiXmlString\n\tsize_type length () const { return rep_->size; }\n\n\t// Alias for length()\n\tsize_type size () const { return rep_->size; }\n\n\t// Checks if a TiXmlString is empty\n\tbool empty () const { return rep_->size == 0; }\n\n\t// Return capacity of string\n\tsize_type capacity () const { return rep_->capacity; }\n\n\n\t// single char extraction\n\tconst char& at (size_type index) const\n\t{\n\t\tassert( index < length() );\n\t\treturn rep_->str[ index ];\n\t}\n\n\t// [] operator\n\tchar& operator [] (size_type index) const\n\t{\n\t\tassert( index < length() );\n\t\treturn rep_->str[ index ];\n\t}\n\n\t// find a char in a string. Return TiXmlString::npos if not found\n\tsize_type find (char lookup) const\n\t{\n\t\treturn find(lookup, 0);\n\t}\n\n\t// find a char in a string from an offset. Return TiXmlString::npos if not found\n\tsize_type find (char tofind, size_type offset) const\n\t{\n\t\tif (offset >= length()) return npos;\n\n\t\tfor (const char* p = c_str() + offset; *p != '\\0'; ++p)\n\t\t{\n\t\t   if (*p == tofind) return static_cast< size_type >( p - c_str() );\n\t\t}\n\t\treturn npos;\n\t}\n\n\tvoid clear ()\n\t{\n\t\t//Lee:\n\t\t//The original was just too strange, though correct:\n\t\t//\tTiXmlString().swap(*this);\n\t\t//Instead use the quit & re-init:\n\t\tquit();\n\t\tinit(0,0);\n\t}\n\n\t/*\tFunction to reserve a big amount of data when we know we'll need it. Be aware that this\n\t\tfunction DOES NOT clear the content of the TiXmlString if any exists.\n\t*/\n\tvoid reserve (size_type cap);\n\n\tTiXmlString& assign (const char* str, size_type len);\n\n\tTiXmlString& append (const char* str, size_type len);\n\n\tvoid swap (TiXmlString& other)\n\t{\n\t\tRep* r = rep_;\n\t\trep_ = other.rep_;\n\t\tother.rep_ = r;\n\t}\n\n  private:\n\n\tvoid init(size_type sz) { init(sz, sz); }\n\tvoid set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\\0'; }\n\tchar* start() const { return rep_->str; }\n\tchar* finish() const { return rep_->str + rep_->size; }\n\n\tstruct Rep\n\t{\n\t\tsize_type size, capacity;\n\t\tchar str[1];\n\t};\n\n\tvoid init(size_type sz, size_type cap)\n\t{\n\t\tif (cap)\n\t\t{\n\t\t\t// Lee: the original form:\n\t\t\t//\trep_ = static_cast<Rep*>(operator new(sizeof(Rep) + cap));\n\t\t\t// doesn't work in some cases of new being overloaded. Switching\n\t\t\t// to the normal allocation, although use an 'int' for systems\n\t\t\t// that are overly picky about structure alignment.\n\t\t\tconst size_type bytesNeeded = sizeof(Rep) + cap;\n\t\t\tconst size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); \n\t\t\trep_ = reinterpret_cast<Rep*>( new int[ intsNeeded ] );\n\n\t\t\trep_->str[ rep_->size = sz ] = '\\0';\n\t\t\trep_->capacity = cap;\n\t\t}\n\t\telse\n\t\t{\n\t\t\trep_ = &nullrep_;\n\t\t}\n\t}\n\n\tvoid quit()\n\t{\n\t\tif (rep_ != &nullrep_)\n\t\t{\n\t\t\t// The rep_ is really an array of ints. (see the allocator, above).\n\t\t\t// Cast it back before delete, so the compiler won't incorrectly call destructors.\n\t\t\tdelete [] ( reinterpret_cast<int*>( rep_ ) );\n\t\t}\n\t}\n\n\tRep * rep_;\n\tstatic Rep nullrep_;\n\n} ;\n\n\ninline bool operator == (const TiXmlString & a, const TiXmlString & b)\n{\n\treturn    ( a.length() == b.length() )\t\t\t\t// optimization on some platforms\n\t       && ( strcmp(a.c_str(), b.c_str()) == 0 );\t// actual compare\n}\ninline bool operator < (const TiXmlString & a, const TiXmlString & b)\n{\n\treturn strcmp(a.c_str(), b.c_str()) < 0;\n}\n\ninline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); }\ninline bool operator >  (const TiXmlString & a, const TiXmlString & b) { return b < a; }\ninline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); }\ninline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); }\n\ninline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; }\ninline bool operator == (const char* a, const TiXmlString & b) { return b == a; }\ninline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); }\ninline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); }\n\nTiXmlString operator + (const TiXmlString & a, const TiXmlString & b);\nTiXmlString operator + (const TiXmlString & a, const char* b);\nTiXmlString operator + (const char* a, const TiXmlString & b);\n\n\n/*\n   TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString.\n   Only the operators that we need for TinyXML have been developed.\n*/\nclass TiXmlOutStream : public TiXmlString\n{\npublic :\n\n\t// TiXmlOutStream << operator.\n\tTiXmlOutStream & operator << (const TiXmlString & in)\n\t{\n\t\t*this += in;\n\t\treturn *this;\n\t}\n\n\t// TiXmlOutStream << operator.\n\tTiXmlOutStream & operator << (const char * in)\n\t{\n\t\t*this += in;\n\t\treturn *this;\n\t}\n\n} ;\n\n#endif\t// TIXML_STRING_INCLUDED\n#endif\t// TIXML_USE_STL\n"
  },
  {
    "path": "src/tinyxml/tinyxml.cpp",
    "content": "/*\nwww.sourceforge.net/projects/tinyxml\nOriginal code by Lee Thomason (www.grinninglizard.com)\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable for any\ndamages arising from the use of this software.\n\nPermission is granted to anyone to use this software for any\npurpose, including commercial applications, and to alter it and\nredistribute it freely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must\nnot claim that you wrote the original software. If you use this\nsoftware in a product, an acknowledgment in the product documentation\nwould be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and\nmust not be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source\ndistribution.\n*/\n\n#include <ctype.h>\n\n#ifdef TIXML_USE_STL\n#include <sstream>\n#include <iostream>\n#endif\n\n#include \"tinyxml.h\"\n\nFILE* TiXmlFOpen( const char* filename, const char* mode );\n\nbool TiXmlBase::condenseWhiteSpace = true;\n\n// Microsoft compiler security\nFILE* TiXmlFOpen( const char* filename, const char* mode )\n{\n\t#if defined(_MSC_VER) && (_MSC_VER >= 1400 )\n\t\tFILE* fp = 0;\n\t\terrno_t err = fopen_s( &fp, filename, mode );\n\t\tif ( !err && fp )\n\t\t\treturn fp;\n\t\treturn 0;\n\t#else\n\t\treturn fopen( filename, mode );\n\t#endif\n}\n\nvoid TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString )\n{\n\tint i=0;\n\n\twhile( i<(int)str.length() )\n\t{\n\t\tunsigned char c = (unsigned char) str[i];\n\n\t\tif (    c == '&' \n\t\t     && i < ( (int)str.length() - 2 )\n\t\t\t && str[i+1] == '#'\n\t\t\t && str[i+2] == 'x' )\n\t\t{\n\t\t\t// Hexadecimal character reference.\n\t\t\t// Pass through unchanged.\n\t\t\t// &#xA9;\t-- copyright symbol, for example.\n\t\t\t//\n\t\t\t// The -1 is a bug fix from Rob Laveaux. It keeps\n\t\t\t// an overflow from happening if there is no ';'.\n\t\t\t// There are actually 2 ways to exit this loop -\n\t\t\t// while fails (error case) and break (semicolon found).\n\t\t\t// However, there is no mechanism (currently) for\n\t\t\t// this function to return an error.\n\t\t\twhile ( i<(int)str.length()-1 )\n\t\t\t{\n\t\t\t\toutString->append( str.c_str() + i, 1 );\n\t\t\t\t++i;\n\t\t\t\tif ( str[i] == ';' )\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse if ( c == '&' )\n\t\t{\n\t\t\toutString->append( entity[0].str, entity[0].strLength );\n\t\t\t++i;\n\t\t}\n\t\telse if ( c == '<' )\n\t\t{\n\t\t\toutString->append( entity[1].str, entity[1].strLength );\n\t\t\t++i;\n\t\t}\n\t\telse if ( c == '>' )\n\t\t{\n\t\t\toutString->append( entity[2].str, entity[2].strLength );\n\t\t\t++i;\n\t\t}\n\t\telse if ( c == '\\\"' )\n\t\t{\n\t\t\toutString->append( entity[3].str, entity[3].strLength );\n\t\t\t++i;\n\t\t}\n\t\telse if ( c == '\\'' )\n\t\t{\n\t\t\toutString->append( entity[4].str, entity[4].strLength );\n\t\t\t++i;\n\t\t}\n\t\telse if ( c < 32 )\n\t\t{\n\t\t\t// Easy pass at non-alpha/numeric/symbol\n\t\t\t// Below 32 is symbolic.\n\t\t\tchar buf[ 32 ];\n\t\t\t\n\t\t\t#if defined(TIXML_SNPRINTF)\t\t\n\t\t\t\tTIXML_SNPRINTF( buf, sizeof(buf), \"&#x%02X;\", (unsigned) ( c & 0xff ) );\n\t\t\t#else\n\t\t\t\tsprintf( buf, \"&#x%02X;\", (unsigned) ( c & 0xff ) );\n\t\t\t#endif\t\t\n\n\t\t\t//*ME:\twarning C4267: convert 'size_t' to 'int'\n\t\t\t//*ME:\tInt-Cast to make compiler happy ...\n\t\t\toutString->append( buf, (int)strlen( buf ) );\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//char realc = (char) c;\n\t\t\t//outString->append( &realc, 1 );\n\t\t\t*outString += (char) c;\t// somewhat more efficient function call.\n\t\t\t++i;\n\t\t}\n\t}\n}\n\n\nTiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase()\n{\n\tparent = 0;\n\ttype = _type;\n\tfirstChild = 0;\n\tlastChild = 0;\n\tprev = 0;\n\tnext = 0;\n}\n\n\nTiXmlNode::~TiXmlNode()\n{\n\tTiXmlNode* node = firstChild;\n\tTiXmlNode* temp = 0;\n\n\twhile ( node )\n\t{\n\t\ttemp = node;\n\t\tnode = node->next;\n\t\tdelete temp;\n\t}\t\n}\n\n\nvoid TiXmlNode::CopyTo( TiXmlNode* target ) const\n{\n\ttarget->SetValue (value.c_str() );\n\ttarget->userData = userData; \n\ttarget->location = location;\n}\n\n\nvoid TiXmlNode::Clear()\n{\n\tTiXmlNode* node = firstChild;\n\tTiXmlNode* temp = 0;\n\n\twhile ( node )\n\t{\n\t\ttemp = node;\n\t\tnode = node->next;\n\t\tdelete temp;\n\t}\t\n\n\tfirstChild = 0;\n\tlastChild = 0;\n}\n\n\nTiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node )\n{\n\tassert( node->parent == 0 || node->parent == this );\n\tassert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() );\n\n\tif ( node->Type() == TiXmlNode::TINYXML_DOCUMENT )\n\t{\n\t\tdelete node;\n\t\tif ( GetDocument() ) \n\t\t\tGetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\n\tnode->parent = this;\n\n\tnode->prev = lastChild;\n\tnode->next = 0;\n\n\tif ( lastChild )\n\t\tlastChild->next = node;\n\telse\n\t\tfirstChild = node;\t\t\t// it was an empty list.\n\n\tlastChild = node;\n\treturn node;\n}\n\n\nTiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis )\n{\n\tif ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT )\n\t{\n\t\tif ( GetDocument() ) \n\t\t\tGetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\tTiXmlNode* node = addThis.Clone();\n\tif ( !node )\n\t\treturn 0;\n\n\treturn LinkEndChild( node );\n}\n\n\nTiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis )\n{\t\n\tif ( !beforeThis || beforeThis->parent != this ) {\n\t\treturn 0;\n\t}\n\tif ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT )\n\t{\n\t\tif ( GetDocument() ) \n\t\t\tGetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\n\tTiXmlNode* node = addThis.Clone();\n\tif ( !node )\n\t\treturn 0;\n\tnode->parent = this;\n\n\tnode->next = beforeThis;\n\tnode->prev = beforeThis->prev;\n\tif ( beforeThis->prev )\n\t{\n\t\tbeforeThis->prev->next = node;\n\t}\n\telse\n\t{\n\t\tassert( firstChild == beforeThis );\n\t\tfirstChild = node;\n\t}\n\tbeforeThis->prev = node;\n\treturn node;\n}\n\n\nTiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis )\n{\n\tif ( !afterThis || afterThis->parent != this ) {\n\t\treturn 0;\n\t}\n\tif ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT )\n\t{\n\t\tif ( GetDocument() ) \n\t\t\tGetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\n\tTiXmlNode* node = addThis.Clone();\n\tif ( !node )\n\t\treturn 0;\n\tnode->parent = this;\n\n\tnode->prev = afterThis;\n\tnode->next = afterThis->next;\n\tif ( afterThis->next )\n\t{\n\t\tafterThis->next->prev = node;\n\t}\n\telse\n\t{\n\t\tassert( lastChild == afterThis );\n\t\tlastChild = node;\n\t}\n\tafterThis->next = node;\n\treturn node;\n}\n\n\nTiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis )\n{\n\tif ( !replaceThis )\n\t\treturn 0;\n\n\tif ( replaceThis->parent != this )\n\t\treturn 0;\n\n\tif ( withThis.ToDocument() ) {\n\t\t// A document can never be a child.\tThanks to Noam.\n\t\tTiXmlDocument* document = GetDocument();\n\t\tif ( document ) \n\t\t\tdocument->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\n\tTiXmlNode* node = withThis.Clone();\n\tif ( !node )\n\t\treturn 0;\n\n\tnode->next = replaceThis->next;\n\tnode->prev = replaceThis->prev;\n\n\tif ( replaceThis->next )\n\t\treplaceThis->next->prev = node;\n\telse\n\t\tlastChild = node;\n\n\tif ( replaceThis->prev )\n\t\treplaceThis->prev->next = node;\n\telse\n\t\tfirstChild = node;\n\n\tdelete replaceThis;\n\tnode->parent = this;\n\treturn node;\n}\n\n\nbool TiXmlNode::RemoveChild( TiXmlNode* removeThis )\n{\n\tif ( !removeThis ) {\n\t\treturn false;\n\t}\n\n\tif ( removeThis->parent != this )\n\t{\t\n\t\tassert( 0 );\n\t\treturn false;\n\t}\n\n\tif ( removeThis->next )\n\t\tremoveThis->next->prev = removeThis->prev;\n\telse\n\t\tlastChild = removeThis->prev;\n\n\tif ( removeThis->prev )\n\t\tremoveThis->prev->next = removeThis->next;\n\telse\n\t\tfirstChild = removeThis->next;\n\n\tdelete removeThis;\n\treturn true;\n}\n\nconst TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const\n{\n\tconst TiXmlNode* node;\n\tfor ( node = firstChild; node; node = node->next )\n\t{\n\t\tif ( strcmp( node->Value(), _value ) == 0 )\n\t\t\treturn node;\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlNode* TiXmlNode::LastChild( const char * _value ) const\n{\n\tconst TiXmlNode* node;\n\tfor ( node = lastChild; node; node = node->prev )\n\t{\n\t\tif ( strcmp( node->Value(), _value ) == 0 )\n\t\t\treturn node;\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const\n{\n\tif ( !previous )\n\t{\n\t\treturn FirstChild();\n\t}\n\telse\n\t{\n\t\tassert( previous->parent == this );\n\t\treturn previous->NextSibling();\n\t}\n}\n\n\nconst TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const\n{\n\tif ( !previous )\n\t{\n\t\treturn FirstChild( val );\n\t}\n\telse\n\t{\n\t\tassert( previous->parent == this );\n\t\treturn previous->NextSibling( val );\n\t}\n}\n\n\nconst TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const \n{\n\tconst TiXmlNode* node;\n\tfor ( node = next; node; node = node->next )\n\t{\n\t\tif ( strcmp( node->Value(), _value ) == 0 )\n\t\t\treturn node;\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const\n{\n\tconst TiXmlNode* node;\n\tfor ( node = prev; node; node = node->prev )\n\t{\n\t\tif ( strcmp( node->Value(), _value ) == 0 )\n\t\t\treturn node;\n\t}\n\treturn 0;\n}\n\n\nvoid TiXmlElement::RemoveAttribute( const char * name )\n{\n    #ifdef TIXML_USE_STL\n\tTIXML_STRING str( name );\n\tTiXmlAttribute* node = attributeSet.Find( str );\n\t#else\n\tTiXmlAttribute* node = attributeSet.Find( name );\n\t#endif\n\tif ( node )\n\t{\n\t\tattributeSet.Remove( node );\n\t\tdelete node;\n\t}\n}\n\nconst TiXmlElement* TiXmlNode::FirstChildElement() const\n{\n\tconst TiXmlNode* node;\n\n\tfor (\tnode = FirstChild();\n\t\t\tnode;\n\t\t\tnode = node->NextSibling() )\n\t{\n\t\tif ( node->ToElement() )\n\t\t\treturn node->ToElement();\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const\n{\n\tconst TiXmlNode* node;\n\n\tfor (\tnode = FirstChild( _value );\n\t\t\tnode;\n\t\t\tnode = node->NextSibling( _value ) )\n\t{\n\t\tif ( node->ToElement() )\n\t\t\treturn node->ToElement();\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlElement* TiXmlNode::NextSiblingElement() const\n{\n\tconst TiXmlNode* node;\n\n\tfor (\tnode = NextSibling();\n\t\t\tnode;\n\t\t\tnode = node->NextSibling() )\n\t{\n\t\tif ( node->ToElement() )\n\t\t\treturn node->ToElement();\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const\n{\n\tconst TiXmlNode* node;\n\n\tfor (\tnode = NextSibling( _value );\n\t\t\tnode;\n\t\t\tnode = node->NextSibling( _value ) )\n\t{\n\t\tif ( node->ToElement() )\n\t\t\treturn node->ToElement();\n\t}\n\treturn 0;\n}\n\n\nconst TiXmlDocument* TiXmlNode::GetDocument() const\n{\n\tconst TiXmlNode* node;\n\n\tfor( node = this; node; node = node->parent )\n\t{\n\t\tif ( node->ToDocument() )\n\t\t\treturn node->ToDocument();\n\t}\n\treturn 0;\n}\n\n\nTiXmlElement::TiXmlElement (const char * _value)\n\t: TiXmlNode( TiXmlNode::TINYXML_ELEMENT )\n{\n\tfirstChild = lastChild = 0;\n\tvalue = _value;\n}\n\n\n#ifdef TIXML_USE_STL\nTiXmlElement::TiXmlElement( const std::string& _value ) \n\t: TiXmlNode( TiXmlNode::TINYXML_ELEMENT )\n{\n\tfirstChild = lastChild = 0;\n\tvalue = _value;\n}\n#endif\n\n\nTiXmlElement::TiXmlElement( const TiXmlElement& copy)\n\t: TiXmlNode( TiXmlNode::TINYXML_ELEMENT )\n{\n\tfirstChild = lastChild = 0;\n\tcopy.CopyTo( this );\t\n}\n\n\nTiXmlElement& TiXmlElement::operator=( const TiXmlElement& base )\n{\n\tClearThis();\n\tbase.CopyTo( this );\n\treturn *this;\n}\n\n\nTiXmlElement::~TiXmlElement()\n{\n\tClearThis();\n}\n\n\nvoid TiXmlElement::ClearThis()\n{\n\tClear();\n\twhile( attributeSet.First() )\n\t{\n\t\tTiXmlAttribute* node = attributeSet.First();\n\t\tattributeSet.Remove( node );\n\t\tdelete node;\n\t}\n}\n\n\nconst char* TiXmlElement::Attribute( const char* name ) const\n{\n\tconst TiXmlAttribute* node = attributeSet.Find( name );\n\tif ( node )\n\t\treturn node->Value();\n\treturn 0;\n}\n\n\n#ifdef TIXML_USE_STL\nconst std::string* TiXmlElement::Attribute( const std::string& name ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tif ( attrib )\n\t\treturn &attrib->ValueStr();\n\treturn 0;\n}\n#endif\n\n\nconst char* TiXmlElement::Attribute( const char* name, int* i ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tconst char* result = 0;\n\n\tif ( attrib ) {\n\t\tresult = attrib->Value();\n\t\tif ( i ) {\n\t\t\tattrib->QueryIntValue( i );\n\t\t}\n\t}\n\treturn result;\n}\n\n\n#ifdef TIXML_USE_STL\nconst std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tconst std::string* result = 0;\n\n\tif ( attrib ) {\n\t\tresult = &attrib->ValueStr();\n\t\tif ( i ) {\n\t\t\tattrib->QueryIntValue( i );\n\t\t}\n\t}\n\treturn result;\n}\n#endif\n\n\nconst char* TiXmlElement::Attribute( const char* name, double* d ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tconst char* result = 0;\n\n\tif ( attrib ) {\n\t\tresult = attrib->Value();\n\t\tif ( d ) {\n\t\t\tattrib->QueryDoubleValue( d );\n\t\t}\n\t}\n\treturn result;\n}\n\n\n#ifdef TIXML_USE_STL\nconst std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tconst std::string* result = 0;\n\n\tif ( attrib ) {\n\t\tresult = &attrib->ValueStr();\n\t\tif ( d ) {\n\t\t\tattrib->QueryDoubleValue( d );\n\t\t}\n\t}\n\treturn result;\n}\n#endif\n\n\nint TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tif ( !attrib )\n\t\treturn TIXML_NO_ATTRIBUTE;\n\treturn attrib->QueryIntValue( ival );\n}\n\n\nint TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const\n{\n\tconst TiXmlAttribute* node = attributeSet.Find( name );\n\tif ( !node )\n\t\treturn TIXML_NO_ATTRIBUTE;\n\n\tint ival = 0;\n\tint result = node->QueryIntValue( &ival );\n\t*value = (unsigned)ival;\n\treturn result;\n}\n\n\nint TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const\n{\n\tconst TiXmlAttribute* node = attributeSet.Find( name );\n\tif ( !node )\n\t\treturn TIXML_NO_ATTRIBUTE;\n\t\n\tint result = TIXML_WRONG_TYPE;\n\tif (    StringEqual( node->Value(), \"true\", true, TIXML_ENCODING_UNKNOWN ) \n\t\t || StringEqual( node->Value(), \"yes\", true, TIXML_ENCODING_UNKNOWN ) \n\t\t || StringEqual( node->Value(), \"1\", true, TIXML_ENCODING_UNKNOWN ) ) \n\t{\n\t\t*bval = true;\n\t\tresult = TIXML_SUCCESS;\n\t}\n\telse if (    StringEqual( node->Value(), \"false\", true, TIXML_ENCODING_UNKNOWN ) \n\t\t\t  || StringEqual( node->Value(), \"no\", true, TIXML_ENCODING_UNKNOWN ) \n\t\t\t  || StringEqual( node->Value(), \"0\", true, TIXML_ENCODING_UNKNOWN ) ) \n\t{\n\t\t*bval = false;\n\t\tresult = TIXML_SUCCESS;\n\t}\n\treturn result;\n}\n\n\n\n#ifdef TIXML_USE_STL\nint TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tif ( !attrib )\n\t\treturn TIXML_NO_ATTRIBUTE;\n\treturn attrib->QueryIntValue( ival );\n}\n#endif\n\n\nint TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tif ( !attrib )\n\t\treturn TIXML_NO_ATTRIBUTE;\n\treturn attrib->QueryDoubleValue( dval );\n}\n\n\n#ifdef TIXML_USE_STL\nint TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const\n{\n\tconst TiXmlAttribute* attrib = attributeSet.Find( name );\n\tif ( !attrib )\n\t\treturn TIXML_NO_ATTRIBUTE;\n\treturn attrib->QueryDoubleValue( dval );\n}\n#endif\n\n\nvoid TiXmlElement::SetAttribute( const char * name, int val )\n{\t\n\tTiXmlAttribute* attrib = attributeSet.FindOrCreate( name );\n\tif ( attrib ) {\n\t\tattrib->SetIntValue( val );\n\t}\n}\n\n\n#ifdef TIXML_USE_STL\nvoid TiXmlElement::SetAttribute( const std::string& name, int val )\n{\t\n\tTiXmlAttribute* attrib = attributeSet.FindOrCreate( name );\n\tif ( attrib ) {\n\t\tattrib->SetIntValue( val );\n\t}\n}\n#endif\n\n\nvoid TiXmlElement::SetDoubleAttribute( const char * name, double val )\n{\t\n\tTiXmlAttribute* attrib = attributeSet.FindOrCreate( name );\n\tif ( attrib ) {\n\t\tattrib->SetDoubleValue( val );\n\t}\n}\n\n\n#ifdef TIXML_USE_STL\nvoid TiXmlElement::SetDoubleAttribute( const std::string& name, double val )\n{\t\n\tTiXmlAttribute* attrib = attributeSet.FindOrCreate( name );\n\tif ( attrib ) {\n\t\tattrib->SetDoubleValue( val );\n\t}\n}\n#endif \n\n\nvoid TiXmlElement::SetAttribute( const char * cname, const char * cvalue )\n{\n\tTiXmlAttribute* attrib = attributeSet.FindOrCreate( cname );\n\tif ( attrib ) {\n\t\tattrib->SetValue( cvalue );\n\t}\n}\n\n\n#ifdef TIXML_USE_STL\nvoid TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value )\n{\n\tTiXmlAttribute* attrib = attributeSet.FindOrCreate( _name );\n\tif ( attrib ) {\n\t\tattrib->SetValue( _value );\n\t}\n}\n#endif\n\n\nvoid TiXmlElement::Print( FILE* cfile, int depth ) const\n{\n\tint i;\n\tassert( cfile );\n\tfor ( i=0; i<depth; i++ ) {\n\t\tfprintf( cfile, \"    \" );\n\t}\n\n\tfprintf( cfile, \"<%s\", value.c_str() );\n\n\tconst TiXmlAttribute* attrib;\n\tfor ( attrib = attributeSet.First(); attrib; attrib = attrib->Next() )\n\t{\n\t\tfprintf( cfile, \" \" );\n\t\tattrib->Print( cfile, depth );\n\t}\n\n\t// There are 3 different formatting approaches:\n\t// 1) An element without children is printed as a <foo /> node\n\t// 2) An element with only a text child is printed as <foo> text </foo>\n\t// 3) An element with children is printed on multiple lines.\n\tTiXmlNode* node;\n\tif ( !firstChild )\n\t{\n\t\tfprintf( cfile, \" />\" );\n\t}\n\telse if ( firstChild == lastChild && firstChild->ToText() )\n\t{\n\t\tfprintf( cfile, \">\" );\n\t\tfirstChild->Print( cfile, depth + 1 );\n\t\tfprintf( cfile, \"</%s>\", value.c_str() );\n\t}\n\telse\n\t{\n\t\tfprintf( cfile, \">\" );\n\n\t\tfor ( node = firstChild; node; node=node->NextSibling() )\n\t\t{\n\t\t\tif ( !node->ToText() )\n\t\t\t{\n\t\t\t\tfprintf( cfile, \"\\n\" );\n\t\t\t}\n\t\t\tnode->Print( cfile, depth+1 );\n\t\t}\n\t\tfprintf( cfile, \"\\n\" );\n\t\tfor( i=0; i<depth; ++i ) {\n\t\t\tfprintf( cfile, \"    \" );\n\t\t}\n\t\tfprintf( cfile, \"</%s>\", value.c_str() );\n\t}\n}\n\n\nvoid TiXmlElement::CopyTo( TiXmlElement* target ) const\n{\n\t// superclass:\n\tTiXmlNode::CopyTo( target );\n\n\t// Element class: \n\t// Clone the attributes, then clone the children.\n\tconst TiXmlAttribute* attribute = 0;\n\tfor(\tattribute = attributeSet.First();\n\tattribute;\n\tattribute = attribute->Next() )\n\t{\n\t\ttarget->SetAttribute( attribute->Name(), attribute->Value() );\n\t}\n\n\tTiXmlNode* node = 0;\n\tfor ( node = firstChild; node; node = node->NextSibling() )\n\t{\n\t\ttarget->LinkEndChild( node->Clone() );\n\t}\n}\n\nbool TiXmlElement::Accept( TiXmlVisitor* visitor ) const\n{\n\tif ( visitor->VisitEnter( *this, attributeSet.First() ) ) \n\t{\n\t\tfor ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )\n\t\t{\n\t\t\tif ( !node->Accept( visitor ) )\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn visitor->VisitExit( *this );\n}\n\n\nTiXmlNode* TiXmlElement::Clone() const\n{\n\tTiXmlElement* clone = new TiXmlElement( Value() );\n\tif ( !clone )\n\t\treturn 0;\n\n\tCopyTo( clone );\n\treturn clone;\n}\n\n\nconst char* TiXmlElement::GetText() const\n{\n\tconst TiXmlNode* child = this->FirstChild();\n\tif ( child ) {\n\t\tconst TiXmlText* childText = child->ToText();\n\t\tif ( childText ) {\n\t\t\treturn childText->Value();\n\t\t}\n\t}\n\treturn 0;\n}\n\n\nTiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT )\n{\n\ttabsize = 4;\n\tuseMicrosoftBOM = false;\n\tClearError();\n}\n\nTiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT )\n{\n\ttabsize = 4;\n\tuseMicrosoftBOM = false;\n\tvalue = documentName;\n\tClearError();\n}\n\n\n#ifdef TIXML_USE_STL\nTiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT )\n{\n\ttabsize = 4;\n\tuseMicrosoftBOM = false;\n    value = documentName;\n\tClearError();\n}\n#endif\n\n\nTiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT )\n{\n\tcopy.CopyTo( this );\n}\n\n\nTiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy )\n{\n\tClear();\n\tcopy.CopyTo( this );\n\treturn *this;\n}\n\n\nbool TiXmlDocument::LoadFile( TiXmlEncoding encoding )\n{\n\treturn LoadFile( Value(), encoding );\n}\n\n\nbool TiXmlDocument::SaveFile() const\n{\n\treturn SaveFile( Value() );\n}\n\nbool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )\n{\n\tTIXML_STRING filename( _filename );\n\tvalue = filename;\n\n\t// reading in binary mode so that tinyxml can normalize the EOL\n\tFILE* file = TiXmlFOpen( value.c_str (), \"rb\" );\t\n\n\tif ( file )\n\t{\n\t\tbool result = LoadFile( file, encoding );\n\t\tfclose( file );\n\t\treturn result;\n\t}\n\telse\n\t{\n\t\tSetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn false;\n\t}\n}\n\nbool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )\n{\n\tif ( !file ) \n\t{\n\t\tSetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn false;\n\t}\n\n\t// Delete the existing data:\n\tClear();\n\tlocation.Clear();\n\n\t// Get the file size, so we can pre-allocate the string. HUGE speed impact.\n\tlong length = 0;\n\tfseek( file, 0, SEEK_END );\n\tlength = ftell( file );\n\tfseek( file, 0, SEEK_SET );\n\n\t// Strange case, but good to handle up front.\n\tif ( length <= 0 )\n\t{\n\t\tSetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn false;\n\t}\n\n\t// Subtle bug here. TinyXml did use fgets. But from the XML spec:\n\t// 2.11 End-of-Line Handling\n\t// <snip>\n\t// <quote>\n\t// ...the XML processor MUST behave as if it normalized all line breaks in external \n\t// parsed entities (including the document entity) on input, before parsing, by translating \n\t// both the two-character sequence #xD #xA and any #xD that is not followed by #xA to \n\t// a single #xA character.\n\t// </quote>\n\t//\n\t// It is not clear fgets does that, and certainly isn't clear it works cross platform. \n\t// Generally, you expect fgets to translate from the convention of the OS to the c/unix\n\t// convention, and not work generally.\n\n\t/*\n\twhile( fgets( buf, sizeof(buf), file ) )\n\t{\n\t\tdata += buf;\n\t}\n\t*/\n\n\tchar* buf = new char[ length+1 ];\n\tbuf[0] = 0;\n\n\tif ( fread( buf, length, 1, file ) != 1 ) {\n\t\tdelete [] buf;\n\t\tSetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn false;\n\t}\n\n\t// Process the buffer in place to normalize new lines. (See comment above.)\n\t// Copies from the 'p' to 'q' pointer, where p can advance faster if\n\t// a newline-carriage return is hit.\n\t//\n\t// Wikipedia:\n\t// Systems based on ASCII or a compatible character set use either LF  (Line feed, '\\n', 0x0A, 10 in decimal) or \n\t// CR (Carriage return, '\\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)...\n\t//\t\t* LF:    Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others\n    //\t\t* CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS\n    //\t\t* CR:    Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9\n\n\tconst char* p = buf;\t// the read head\n\tchar* q = buf;\t\t\t// the write head\n\tconst char CR = 0x0d;\n\tconst char LF = 0x0a;\n\n\tbuf[length] = 0;\n\twhile( *p ) {\n\t\tassert( p < (buf+length) );\n\t\tassert( q <= (buf+length) );\n\t\tassert( q <= p );\n\n\t\tif ( *p == CR ) {\n\t\t\t*q++ = LF;\n\t\t\tp++;\n\t\t\tif ( *p == LF ) {\t\t// check for CR+LF (and skip LF)\n\t\t\t\tp++;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t*q++ = *p++;\n\t\t}\n\t}\n\tassert( q <= (buf+length) );\n\t*q = 0;\n\n\tParse( buf, 0, encoding );\n\n\tdelete [] buf;\n\treturn !Error();\n}\n\n\nbool TiXmlDocument::SaveFile( const char * filename ) const\n{\n\t// The old c stuff lives on...\n\tFILE* fp = TiXmlFOpen( filename, \"w\" );\n\tif ( fp )\n\t{\n\t\tbool result = SaveFile( fp );\n\t\tfclose( fp );\n\t\treturn result;\n\t}\n\treturn false;\n}\n\n\nbool TiXmlDocument::SaveFile( FILE* fp ) const\n{\n\tif ( useMicrosoftBOM ) \n\t{\n\t\tconst unsigned char TIXML_UTF_LEAD_0 = 0xefU;\n\t\tconst unsigned char TIXML_UTF_LEAD_1 = 0xbbU;\n\t\tconst unsigned char TIXML_UTF_LEAD_2 = 0xbfU;\n\n\t\tfputc( TIXML_UTF_LEAD_0, fp );\n\t\tfputc( TIXML_UTF_LEAD_1, fp );\n\t\tfputc( TIXML_UTF_LEAD_2, fp );\n\t}\n\tPrint( fp, 0 );\n\treturn (ferror(fp) == 0);\n}\n\n\nvoid TiXmlDocument::CopyTo( TiXmlDocument* target ) const\n{\n\tTiXmlNode::CopyTo( target );\n\n\ttarget->error = error;\n\ttarget->errorId = errorId;\n\ttarget->errorDesc = errorDesc;\n\ttarget->tabsize = tabsize;\n\ttarget->errorLocation = errorLocation;\n\ttarget->useMicrosoftBOM = useMicrosoftBOM;\n\n\tTiXmlNode* node = 0;\n\tfor ( node = firstChild; node; node = node->NextSibling() )\n\t{\n\t\ttarget->LinkEndChild( node->Clone() );\n\t}\t\n}\n\n\nTiXmlNode* TiXmlDocument::Clone() const\n{\n\tTiXmlDocument* clone = new TiXmlDocument();\n\tif ( !clone )\n\t\treturn 0;\n\n\tCopyTo( clone );\n\treturn clone;\n}\n\n\nvoid TiXmlDocument::Print( FILE* cfile, int depth ) const\n{\n\tassert( cfile );\n\tfor ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )\n\t{\n\t\tnode->Print( cfile, depth );\n\t\tfprintf( cfile, \"\\n\" );\n\t}\n}\n\n\nbool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const\n{\n\tif ( visitor->VisitEnter( *this ) )\n\t{\n\t\tfor ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )\n\t\t{\n\t\t\tif ( !node->Accept( visitor ) )\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn visitor->VisitExit( *this );\n}\n\n\nconst TiXmlAttribute* TiXmlAttribute::Next() const\n{\n\t// We are using knowledge of the sentinel. The sentinel\n\t// have a value or name.\n\tif ( next->value.empty() && next->name.empty() )\n\t\treturn 0;\n\treturn next;\n}\n\n/*\nTiXmlAttribute* TiXmlAttribute::Next()\n{\n\t// We are using knowledge of the sentinel. The sentinel\n\t// have a value or name.\n\tif ( next->value.empty() && next->name.empty() )\n\t\treturn 0;\n\treturn next;\n}\n*/\n\nconst TiXmlAttribute* TiXmlAttribute::Previous() const\n{\n\t// We are using knowledge of the sentinel. The sentinel\n\t// have a value or name.\n\tif ( prev->value.empty() && prev->name.empty() )\n\t\treturn 0;\n\treturn prev;\n}\n\n/*\nTiXmlAttribute* TiXmlAttribute::Previous()\n{\n\t// We are using knowledge of the sentinel. The sentinel\n\t// have a value or name.\n\tif ( prev->value.empty() && prev->name.empty() )\n\t\treturn 0;\n\treturn prev;\n}\n*/\n\nvoid TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const\n{\n\tTIXML_STRING n, v;\n\n\tEncodeString( name, &n );\n\tEncodeString( value, &v );\n\n\tif (value.find ('\\\"') == TIXML_STRING::npos) {\n\t\tif ( cfile ) {\n\t\t\tfprintf (cfile, \"%s=\\\"%s\\\"\", n.c_str(), v.c_str() );\n\t\t}\n\t\tif ( str ) {\n\t\t\t(*str) += n; (*str) += \"=\\\"\"; (*str) += v; (*str) += \"\\\"\";\n\t\t}\n\t}\n\telse {\n\t\tif ( cfile ) {\n\t\t\tfprintf (cfile, \"%s='%s'\", n.c_str(), v.c_str() );\n\t\t}\n\t\tif ( str ) {\n\t\t\t(*str) += n; (*str) += \"='\"; (*str) += v; (*str) += \"'\";\n\t\t}\n\t}\n}\n\n\nint TiXmlAttribute::QueryIntValue( int* ival ) const\n{\n\tif ( TIXML_SSCANF( value.c_str(), \"%d\", ival ) == 1 )\n\t\treturn TIXML_SUCCESS;\n\treturn TIXML_WRONG_TYPE;\n}\n\nint TiXmlAttribute::QueryDoubleValue( double* dval ) const\n{\n\tif ( TIXML_SSCANF( value.c_str(), \"%lf\", dval ) == 1 )\n\t\treturn TIXML_SUCCESS;\n\treturn TIXML_WRONG_TYPE;\n}\n\nvoid TiXmlAttribute::SetIntValue( int _value )\n{\n\tchar buf [64];\n\t#if defined(TIXML_SNPRINTF)\t\t\n\t\tTIXML_SNPRINTF(buf, sizeof(buf), \"%d\", _value);\n\t#else\n\t\tsprintf (buf, \"%d\", _value);\n\t#endif\n\tSetValue (buf);\n}\n\nvoid TiXmlAttribute::SetDoubleValue( double _value )\n{\n\tchar buf [256];\n\t#if defined(TIXML_SNPRINTF)\t\t\n\t\tTIXML_SNPRINTF( buf, sizeof(buf), \"%g\", _value);\n\t#else\n\t\tsprintf (buf, \"%g\", _value);\n\t#endif\n\tSetValue (buf);\n}\n\nint TiXmlAttribute::IntValue() const\n{\n\treturn atoi (value.c_str ());\n}\n\ndouble  TiXmlAttribute::DoubleValue() const\n{\n\treturn atof (value.c_str ());\n}\n\n\nTiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT )\n{\n\tcopy.CopyTo( this );\n}\n\n\nTiXmlComment& TiXmlComment::operator=( const TiXmlComment& base )\n{\n\tClear();\n\tbase.CopyTo( this );\n\treturn *this;\n}\n\n\nvoid TiXmlComment::Print( FILE* cfile, int depth ) const\n{\n\tassert( cfile );\n\tfor ( int i=0; i<depth; i++ )\n\t{\n\t\tfprintf( cfile,  \"    \" );\n\t}\n\tfprintf( cfile, \"<!--%s-->\", value.c_str() );\n}\n\n\nvoid TiXmlComment::CopyTo( TiXmlComment* target ) const\n{\n\tTiXmlNode::CopyTo( target );\n}\n\n\nbool TiXmlComment::Accept( TiXmlVisitor* visitor ) const\n{\n\treturn visitor->Visit( *this );\n}\n\n\nTiXmlNode* TiXmlComment::Clone() const\n{\n\tTiXmlComment* clone = new TiXmlComment();\n\n\tif ( !clone )\n\t\treturn 0;\n\n\tCopyTo( clone );\n\treturn clone;\n}\n\n\nvoid TiXmlText::Print( FILE* cfile, int depth ) const\n{\n\tassert( cfile );\n\tif ( cdata )\n\t{\n\t\tint i;\n\t\tfprintf( cfile, \"\\n\" );\n\t\tfor ( i=0; i<depth; i++ ) {\n\t\t\tfprintf( cfile, \"    \" );\n\t\t}\n\t\tfprintf( cfile, \"<![CDATA[%s]]>\\n\", value.c_str() );\t// unformatted output\n\t}\n\telse\n\t{\n\t\tTIXML_STRING buffer;\n\t\tEncodeString( value, &buffer );\n\t\tfprintf( cfile, \"%s\", buffer.c_str() );\n\t}\n}\n\n\nvoid TiXmlText::CopyTo( TiXmlText* target ) const\n{\n\tTiXmlNode::CopyTo( target );\n\ttarget->cdata = cdata;\n}\n\n\nbool TiXmlText::Accept( TiXmlVisitor* visitor ) const\n{\n\treturn visitor->Visit( *this );\n}\n\n\nTiXmlNode* TiXmlText::Clone() const\n{\t\n\tTiXmlText* clone = 0;\n\tclone = new TiXmlText( \"\" );\n\n\tif ( !clone )\n\t\treturn 0;\n\n\tCopyTo( clone );\n\treturn clone;\n}\n\n\nTiXmlDeclaration::TiXmlDeclaration( const char * _version,\n\t\t\t\t\t\t\t\t\tconst char * _encoding,\n\t\t\t\t\t\t\t\t\tconst char * _standalone )\n\t: TiXmlNode( TiXmlNode::TINYXML_DECLARATION )\n{\n\tversion = _version;\n\tencoding = _encoding;\n\tstandalone = _standalone;\n}\n\n\n#ifdef TIXML_USE_STL\nTiXmlDeclaration::TiXmlDeclaration(\tconst std::string& _version,\n\t\t\t\t\t\t\t\t\tconst std::string& _encoding,\n\t\t\t\t\t\t\t\t\tconst std::string& _standalone )\n\t: TiXmlNode( TiXmlNode::TINYXML_DECLARATION )\n{\n\tversion = _version;\n\tencoding = _encoding;\n\tstandalone = _standalone;\n}\n#endif\n\n\nTiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy )\n\t: TiXmlNode( TiXmlNode::TINYXML_DECLARATION )\n{\n\tcopy.CopyTo( this );\t\n}\n\n\nTiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy )\n{\n\tClear();\n\tcopy.CopyTo( this );\n\treturn *this;\n}\n\n\nvoid TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const\n{\n\tif ( cfile ) fprintf( cfile, \"<?xml \" );\n\tif ( str )\t (*str) += \"<?xml \";\n\n\tif ( !version.empty() ) {\n\t\tif ( cfile ) fprintf (cfile, \"version=\\\"%s\\\" \", version.c_str ());\n\t\tif ( str ) { (*str) += \"version=\\\"\"; (*str) += version; (*str) += \"\\\" \"; }\n\t}\n\tif ( !encoding.empty() ) {\n\t\tif ( cfile ) fprintf (cfile, \"encoding=\\\"%s\\\" \", encoding.c_str ());\n\t\tif ( str ) { (*str) += \"encoding=\\\"\"; (*str) += encoding; (*str) += \"\\\" \"; }\n\t}\n\tif ( !standalone.empty() ) {\n\t\tif ( cfile ) fprintf (cfile, \"standalone=\\\"%s\\\" \", standalone.c_str ());\n\t\tif ( str ) { (*str) += \"standalone=\\\"\"; (*str) += standalone; (*str) += \"\\\" \"; }\n\t}\n\tif ( cfile ) fprintf( cfile, \"?>\" );\n\tif ( str )\t (*str) += \"?>\";\n}\n\n\nvoid TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const\n{\n\tTiXmlNode::CopyTo( target );\n\n\ttarget->version = version;\n\ttarget->encoding = encoding;\n\ttarget->standalone = standalone;\n}\n\n\nbool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const\n{\n\treturn visitor->Visit( *this );\n}\n\n\nTiXmlNode* TiXmlDeclaration::Clone() const\n{\t\n\tTiXmlDeclaration* clone = new TiXmlDeclaration();\n\n\tif ( !clone )\n\t\treturn 0;\n\n\tCopyTo( clone );\n\treturn clone;\n}\n\n\nvoid TiXmlUnknown::Print( FILE* cfile, int depth ) const\n{\n\tfor ( int i=0; i<depth; i++ )\n\t\tfprintf( cfile, \"    \" );\n\tfprintf( cfile, \"<%s>\", value.c_str() );\n}\n\n\nvoid TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const\n{\n\tTiXmlNode::CopyTo( target );\n}\n\n\nbool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const\n{\n\treturn visitor->Visit( *this );\n}\n\n\nTiXmlNode* TiXmlUnknown::Clone() const\n{\n\tTiXmlUnknown* clone = new TiXmlUnknown();\n\n\tif ( !clone )\n\t\treturn 0;\n\n\tCopyTo( clone );\n\treturn clone;\n}\n\n\nTiXmlAttributeSet::TiXmlAttributeSet()\n{\n\tsentinel.next = &sentinel;\n\tsentinel.prev = &sentinel;\n}\n\n\nTiXmlAttributeSet::~TiXmlAttributeSet()\n{\n\tassert( sentinel.next == &sentinel );\n\tassert( sentinel.prev == &sentinel );\n}\n\n\nvoid TiXmlAttributeSet::Add( TiXmlAttribute* addMe )\n{\n    #ifdef TIXML_USE_STL\n\tassert( !Find( TIXML_STRING( addMe->Name() ) ) );\t// Shouldn't be multiply adding to the set.\n\t#else\n\tassert( !Find( addMe->Name() ) );\t// Shouldn't be multiply adding to the set.\n\t#endif\n\n\taddMe->next = &sentinel;\n\taddMe->prev = sentinel.prev;\n\n\tsentinel.prev->next = addMe;\n\tsentinel.prev      = addMe;\n}\n\nvoid TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe )\n{\n\tTiXmlAttribute* node;\n\n\tfor( node = sentinel.next; node != &sentinel; node = node->next )\n\t{\n\t\tif ( node == removeMe )\n\t\t{\n\t\t\tnode->prev->next = node->next;\n\t\t\tnode->next->prev = node->prev;\n\t\t\tnode->next = 0;\n\t\t\tnode->prev = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\tassert( 0 );\t\t// we tried to remove a non-linked attribute.\n}\n\n\n#ifdef TIXML_USE_STL\nTiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const\n{\n\tfor( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )\n\t{\n\t\tif ( node->name == name )\n\t\t\treturn node;\n\t}\n\treturn 0;\n}\n\nTiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name )\n{\n\tTiXmlAttribute* attrib = Find( _name );\n\tif ( !attrib ) {\n\t\tattrib = new TiXmlAttribute();\n\t\tAdd( attrib );\n\t\tattrib->SetName( _name );\n\t}\n\treturn attrib;\n}\n#endif\n\n\nTiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const\n{\n\tfor( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )\n\t{\n\t\tif ( strcmp( node->name.c_str(), name ) == 0 )\n\t\t\treturn node;\n\t}\n\treturn 0;\n}\n\n\nTiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name )\n{\n\tTiXmlAttribute* attrib = Find( _name );\n\tif ( !attrib ) {\n\t\tattrib = new TiXmlAttribute();\n\t\tAdd( attrib );\n\t\tattrib->SetName( _name );\n\t}\n\treturn attrib;\n}\n\n\n#ifdef TIXML_USE_STL\t\nstd::istream& operator>> (std::istream & in, TiXmlNode & base)\n{\n\tTIXML_STRING tag;\n\ttag.reserve( 8 * 1000 );\n\tbase.StreamIn( &in, &tag );\n\n\tbase.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING );\n\treturn in;\n}\n#endif\n\n\n#ifdef TIXML_USE_STL\t\nstd::ostream& operator<< (std::ostream & out, const TiXmlNode & base)\n{\n\tTiXmlPrinter printer;\n\tprinter.SetStreamPrinting();\n\tbase.Accept( &printer );\n\tout << printer.Str();\n\n\treturn out;\n}\n\n\nstd::string& operator<< (std::string& out, const TiXmlNode& base )\n{\n\tTiXmlPrinter printer;\n\tprinter.SetStreamPrinting();\n\tbase.Accept( &printer );\n\tout.append( printer.Str() );\n\n\treturn out;\n}\n#endif\n\n\nTiXmlHandle TiXmlHandle::FirstChild() const\n{\n\tif ( node )\n\t{\n\t\tTiXmlNode* child = node->FirstChild();\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::FirstChild( const char * value ) const\n{\n\tif ( node )\n\t{\n\t\tTiXmlNode* child = node->FirstChild( value );\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::FirstChildElement() const\n{\n\tif ( node )\n\t{\n\t\tTiXmlElement* child = node->FirstChildElement();\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const\n{\n\tif ( node )\n\t{\n\t\tTiXmlElement* child = node->FirstChildElement( value );\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::Child( int count ) const\n{\n\tif ( node )\n\t{\n\t\tint i;\n\t\tTiXmlNode* child = node->FirstChild();\n\t\tfor (\ti=0;\n\t\t\t\tchild && i<count;\n\t\t\t\tchild = child->NextSibling(), ++i )\n\t\t{\n\t\t\t// nothing\n\t\t}\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::Child( const char* value, int count ) const\n{\n\tif ( node )\n\t{\n\t\tint i;\n\t\tTiXmlNode* child = node->FirstChild( value );\n\t\tfor (\ti=0;\n\t\t\t\tchild && i<count;\n\t\t\t\tchild = child->NextSibling( value ), ++i )\n\t\t{\n\t\t\t// nothing\n\t\t}\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::ChildElement( int count ) const\n{\n\tif ( node )\n\t{\n\t\tint i;\n\t\tTiXmlElement* child = node->FirstChildElement();\n\t\tfor (\ti=0;\n\t\t\t\tchild && i<count;\n\t\t\t\tchild = child->NextSiblingElement(), ++i )\n\t\t{\n\t\t\t// nothing\n\t\t}\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nTiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const\n{\n\tif ( node )\n\t{\n\t\tint i;\n\t\tTiXmlElement* child = node->FirstChildElement( value );\n\t\tfor (\ti=0;\n\t\t\t\tchild && i<count;\n\t\t\t\tchild = child->NextSiblingElement( value ), ++i )\n\t\t{\n\t\t\t// nothing\n\t\t}\n\t\tif ( child )\n\t\t\treturn TiXmlHandle( child );\n\t}\n\treturn TiXmlHandle( 0 );\n}\n\n\nbool TiXmlPrinter::VisitEnter( const TiXmlDocument& )\n{\n\treturn true;\n}\n\nbool TiXmlPrinter::VisitExit( const TiXmlDocument& )\n{\n\treturn true;\n}\n\nbool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )\n{\n\tDoIndent();\n\tbuffer += \"<\";\n\tbuffer += element.Value();\n\n\tfor( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() )\n\t{\n\t\tbuffer += \" \";\n\t\tattrib->Print( 0, 0, &buffer );\n\t}\n\n\tif ( !element.FirstChild() ) \n\t{\n\t\tbuffer += \" />\";\n\t\tDoLineBreak();\n\t}\n\telse \n\t{\n\t\tbuffer += \">\";\n\t\tif (    element.FirstChild()->ToText()\n\t\t\t  && element.LastChild() == element.FirstChild()\n\t\t\t  && element.FirstChild()->ToText()->CDATA() == false )\n\t\t{\n\t\t\tsimpleTextPrint = true;\n\t\t\t// no DoLineBreak()!\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDoLineBreak();\n\t\t}\n\t}\n\t++depth;\t\n\treturn true;\n}\n\n\nbool TiXmlPrinter::VisitExit( const TiXmlElement& element )\n{\n\t--depth;\n\tif ( !element.FirstChild() ) \n\t{\n\t\t// nothing.\n\t}\n\telse \n\t{\n\t\tif ( simpleTextPrint )\n\t\t{\n\t\t\tsimpleTextPrint = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDoIndent();\n\t\t}\n\t\tbuffer += \"</\";\n\t\tbuffer += element.Value();\n\t\tbuffer += \">\";\n\t\tDoLineBreak();\n\t}\n\treturn true;\n}\n\n\nbool TiXmlPrinter::Visit( const TiXmlText& text )\n{\n\tif ( text.CDATA() )\n\t{\n\t\tDoIndent();\n\t\tbuffer += \"<![CDATA[\";\n\t\tbuffer += text.Value();\n\t\tbuffer += \"]]>\";\n\t\tDoLineBreak();\n\t}\n\telse if ( simpleTextPrint )\n\t{\n\t\tTIXML_STRING str;\n\t\tTiXmlBase::EncodeString( text.ValueTStr(), &str );\n\t\tbuffer += str;\n\t}\n\telse\n\t{\n\t\tDoIndent();\n\t\tTIXML_STRING str;\n\t\tTiXmlBase::EncodeString( text.ValueTStr(), &str );\n\t\tbuffer += str;\n\t\tDoLineBreak();\n\t}\n\treturn true;\n}\n\n\nbool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration )\n{\n\tDoIndent();\n\tdeclaration.Print( 0, 0, &buffer );\n\tDoLineBreak();\n\treturn true;\n}\n\n\nbool TiXmlPrinter::Visit( const TiXmlComment& comment )\n{\n\tDoIndent();\n\tbuffer += \"<!--\";\n\tbuffer += comment.Value();\n\tbuffer += \"-->\";\n\tDoLineBreak();\n\treturn true;\n}\n\n\nbool TiXmlPrinter::Visit( const TiXmlUnknown& unknown )\n{\n\tDoIndent();\n\tbuffer += \"<\";\n\tbuffer += unknown.Value();\n\tbuffer += \">\";\n\tDoLineBreak();\n\treturn true;\n}\n\n"
  },
  {
    "path": "src/tinyxml/tinyxml.h",
    "content": "/*\nwww.sourceforge.net/projects/tinyxml\nOriginal code by Lee Thomason (www.grinninglizard.com)\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable for any\ndamages arising from the use of this software.\n\nPermission is granted to anyone to use this software for any\npurpose, including commercial applications, and to alter it and\nredistribute it freely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must\nnot claim that you wrote the original software. If you use this\nsoftware in a product, an acknowledgment in the product documentation\nwould be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and\nmust not be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source\ndistribution.\n*/\n\n\n#ifndef TINYXML_INCLUDED\n#define TINYXML_INCLUDED\n\n#ifdef _MSC_VER\n#pragma warning( push )\n#pragma warning( disable : 4530 )\n#pragma warning( disable : 4786 )\n#endif\n\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n// Help out windows:\n#if defined( _DEBUG ) && !defined( DEBUG )\n#define DEBUG\n#endif\n\n#ifdef TIXML_USE_STL\n\t#include <string>\n \t#include <iostream>\n\t#include <sstream>\n\t#define TIXML_STRING\t\tstd::string\n#else\n\t#include \"tinystr.h\"\n\t#define TIXML_STRING\t\tTiXmlString\n#endif\n\n// Deprecated library function hell. Compilers want to use the\n// new safe versions. This probably doesn't fully address the problem,\n// but it gets closer. There are too many compilers for me to fully\n// test. If you get compilation troubles, undefine TIXML_SAFE\n#define TIXML_SAFE\n\n#ifdef TIXML_SAFE\n\t#if defined(_MSC_VER) && (_MSC_VER >= 1400 )\n\t\t// Microsoft visual studio, version 2005 and higher.\n\t\t#define TIXML_SNPRINTF _snprintf_s\n\t\t#define TIXML_SSCANF   sscanf_s\n\t#elif defined(_MSC_VER) && (_MSC_VER >= 1200 )\n\t\t// Microsoft visual studio, version 6 and higher.\n\t\t//#pragma message( \"Using _sn* functions.\" )\n\t\t#define TIXML_SNPRINTF _snprintf\n\t\t#define TIXML_SSCANF   sscanf\n\t#elif defined(__GNUC__) && (__GNUC__ >= 3 )\n\t\t// GCC version 3 and higher.s\n\t\t//#warning( \"Using sn* functions.\" )\n\t\t#define TIXML_SNPRINTF snprintf\n\t\t#define TIXML_SSCANF   sscanf\n\t#else\n\t\t#define TIXML_SNPRINTF snprintf\n\t\t#define TIXML_SSCANF   sscanf\n\t#endif\n#endif\t\n\nclass TiXmlDocument;\nclass TiXmlElement;\nclass TiXmlComment;\nclass TiXmlUnknown;\nclass TiXmlAttribute;\nclass TiXmlText;\nclass TiXmlDeclaration;\nclass TiXmlParsingData;\n\nconst int TIXML_MAJOR_VERSION = 2;\nconst int TIXML_MINOR_VERSION = 6;\nconst int TIXML_PATCH_VERSION = 2;\n\n/*\tInternal structure for tracking location of items \n\tin the XML file.\n*/\nstruct TiXmlCursor\n{\n\tTiXmlCursor()\t\t{ Clear(); }\n\tvoid Clear()\t\t{ row = col = -1; }\n\n\tint row;\t// 0 based.\n\tint col;\t// 0 based.\n};\n\n\n/**\n\tImplements the interface to the \"Visitor pattern\" (see the Accept() method.)\n\tIf you call the Accept() method, it requires being passed a TiXmlVisitor\n\tclass to handle callbacks. For nodes that contain other nodes (Document, Element)\n\tyou will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves\n\tare simply called with Visit().\n\n\tIf you return 'true' from a Visit method, recursive parsing will continue. If you return\n\tfalse, <b>no children of this node or its sibilings</b> will be Visited.\n\n\tAll flavors of Visit methods have a default implementation that returns 'true' (continue \n\tvisiting). You need to only override methods that are interesting to you.\n\n\tGenerally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting.\n\n\tYou should never change the document from a callback.\n\n\t@sa TiXmlNode::Accept()\n*/\nclass TiXmlVisitor\n{\npublic:\n\tvirtual ~TiXmlVisitor() {}\n\n\t/// Visit a document.\n\tvirtual bool VisitEnter( const TiXmlDocument& /*doc*/ )\t\t\t{ return true; }\n\t/// Visit a document.\n\tvirtual bool VisitExit( const TiXmlDocument& /*doc*/ )\t\t\t{ return true; }\n\n\t/// Visit an element.\n\tvirtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ )\t{ return true; }\n\t/// Visit an element.\n\tvirtual bool VisitExit( const TiXmlElement& /*element*/ )\t\t{ return true; }\n\n\t/// Visit a declaration\n\tvirtual bool Visit( const TiXmlDeclaration& /*declaration*/ )\t{ return true; }\n\t/// Visit a text node\n\tvirtual bool Visit( const TiXmlText& /*text*/ )\t\t\t\t\t{ return true; }\n\t/// Visit a comment node\n\tvirtual bool Visit( const TiXmlComment& /*comment*/ )\t\t\t{ return true; }\n\t/// Visit an unknown node\n\tvirtual bool Visit( const TiXmlUnknown& /*unknown*/ )\t\t\t{ return true; }\n};\n\n// Only used by Attribute::Query functions\nenum \n{ \n\tTIXML_SUCCESS,\n\tTIXML_NO_ATTRIBUTE,\n\tTIXML_WRONG_TYPE\n};\n\n\n// Used by the parsing routines.\nenum TiXmlEncoding\n{\n\tTIXML_ENCODING_UNKNOWN,\n\tTIXML_ENCODING_UTF8,\n\tTIXML_ENCODING_LEGACY\n};\n\nconst TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN;\n\n/** TiXmlBase is a base class for every class in TinyXml.\n\tIt does little except to establish that TinyXml classes\n\tcan be printed and provide some utility functions.\n\n\tIn XML, the document and elements can contain\n\tother elements and other types of nodes.\n\n\t@verbatim\n\tA Document can contain:\tElement\t(container or leaf)\n\t\t\t\t\t\t\tComment (leaf)\n\t\t\t\t\t\t\tUnknown (leaf)\n\t\t\t\t\t\t\tDeclaration( leaf )\n\n\tAn Element can contain:\tElement (container or leaf)\n\t\t\t\t\t\t\tText\t(leaf)\n\t\t\t\t\t\t\tAttributes (not on tree)\n\t\t\t\t\t\t\tComment (leaf)\n\t\t\t\t\t\t\tUnknown (leaf)\n\n\tA Declaration contains: Attributes (not on tree)\n\t@endverbatim\n*/\nclass TiXmlBase\n{\n\tfriend class TiXmlNode;\n\tfriend class TiXmlElement;\n\tfriend class TiXmlDocument;\n\npublic:\n\tTiXmlBase()\t:\tuserData(0)\t\t{}\n\tvirtual ~TiXmlBase()\t\t\t{}\n\n\t/**\tAll TinyXml classes can print themselves to a filestream\n\t\tor the string class (TiXmlString in non-STL mode, std::string\n\t\tin STL mode.) Either or both cfile and str can be null.\n\t\t\n\t\tThis is a formatted print, and will insert \n\t\ttabs and newlines.\n\t\t\n\t\t(For an unformatted stream, use the << operator.)\n\t*/\n\tvirtual void Print( FILE* cfile, int depth ) const = 0;\n\n\t/**\tThe world does not agree on whether white space should be kept or\n\t\tnot. In order to make everyone happy, these global, static functions\n\t\tare provided to set whether or not TinyXml will condense all white space\n\t\tinto a single space or not. The default is to condense. Note changing this\n\t\tvalue is not thread safe.\n\t*/\n\tstatic void SetCondenseWhiteSpace( bool condense )\t\t{ condenseWhiteSpace = condense; }\n\n\t/// Return the current white space setting.\n\tstatic bool IsWhiteSpaceCondensed()\t\t\t\t\t\t{ return condenseWhiteSpace; }\n\n\t/** Return the position, in the original source file, of this node or attribute.\n\t\tThe row and column are 1-based. (That is the first row and first column is\n\t\t1,1). If the returns values are 0 or less, then the parser does not have\n\t\ta row and column value.\n\n\t\tGenerally, the row and column value will be set when the TiXmlDocument::Load(),\n\t\tTiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set\n\t\twhen the DOM was created from operator>>.\n\n\t\tThe values reflect the initial load. Once the DOM is modified programmatically\n\t\t(by adding or changing nodes and attributes) the new values will NOT update to\n\t\treflect changes in the document.\n\n\t\tThere is a minor performance cost to computing the row and column. Computation\n\t\tcan be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value.\n\n\t\t@sa TiXmlDocument::SetTabSize()\n\t*/\n\tint Row() const\t\t\t{ return location.row + 1; }\n\tint Column() const\t\t{ return location.col + 1; }\t///< See Row()\n\n\tvoid  SetUserData( void* user )\t\t\t{ userData = user; }\t///< Set a pointer to arbitrary user data.\n\tvoid* GetUserData()\t\t\t\t\t\t{ return userData; }\t///< Get a pointer to arbitrary user data.\n\tconst void* GetUserData() const \t\t{ return userData; }\t///< Get a pointer to arbitrary user data.\n\n\t// Table that returns, for a given lead byte, the total number of bytes\n\t// in the UTF-8 sequence.\n\tstatic const int utf8ByteTable[256];\n\n\tvirtual const char* Parse(\tconst char* p, \n\t\t\t\t\t\t\t\tTiXmlParsingData* data, \n\t\t\t\t\t\t\t\tTiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0;\n\n\t/** Expands entities in a string. Note this should not contain the tag's '<', '>', etc, \n\t\tor they will be transformed into entities!\n\t*/\n\tstatic void EncodeString( const TIXML_STRING& str, TIXML_STRING* out );\n\n\tenum\n\t{\n\t\tTIXML_NO_ERROR = 0,\n\t\tTIXML_ERROR,\n\t\tTIXML_ERROR_OPENING_FILE,\n\t\tTIXML_ERROR_PARSING_ELEMENT,\n\t\tTIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME,\n\t\tTIXML_ERROR_READING_ELEMENT_VALUE,\n\t\tTIXML_ERROR_READING_ATTRIBUTES,\n\t\tTIXML_ERROR_PARSING_EMPTY,\n\t\tTIXML_ERROR_READING_END_TAG,\n\t\tTIXML_ERROR_PARSING_UNKNOWN,\n\t\tTIXML_ERROR_PARSING_COMMENT,\n\t\tTIXML_ERROR_PARSING_DECLARATION,\n\t\tTIXML_ERROR_DOCUMENT_EMPTY,\n\t\tTIXML_ERROR_EMBEDDED_NULL,\n\t\tTIXML_ERROR_PARSING_CDATA,\n\t\tTIXML_ERROR_DOCUMENT_TOP_ONLY,\n\n\t\tTIXML_ERROR_STRING_COUNT\n\t};\n\nprotected:\n\n\tstatic const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding );\n\n\tinline static bool IsWhiteSpace( char c )\t\t\n\t{ \n\t\treturn ( isspace( (unsigned char) c ) || c == '\\n' || c == '\\r' ); \n\t}\n\tinline static bool IsWhiteSpace( int c )\n\t{\n\t\tif ( c < 256 )\n\t\t\treturn IsWhiteSpace( (char) c );\n\t\treturn false;\t// Again, only truly correct for English/Latin...but usually works.\n\t}\n\n\t#ifdef TIXML_USE_STL\n\tstatic bool\tStreamWhiteSpace( std::istream * in, TIXML_STRING * tag );\n\tstatic bool StreamTo( std::istream * in, int character, TIXML_STRING * tag );\n\t#endif\n\n\t/*\tReads an XML name into the string provided. Returns\n\t\ta pointer just past the last character of the name,\n\t\tor 0 if the function has an error.\n\t*/\n\tstatic const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding );\n\n\t/*\tReads text. Returns a pointer past the given end tag.\n\t\tWickedly complex options, but it keeps the (sensitive) code in one place.\n\t*/\n\tstatic const char* ReadText(\tconst char* in,\t\t\t\t// where to start\n\t\t\t\t\t\t\t\t\tTIXML_STRING* text,\t\t\t// the string read\n\t\t\t\t\t\t\t\t\tbool ignoreWhiteSpace,\t\t// whether to keep the white space\n\t\t\t\t\t\t\t\t\tconst char* endTag,\t\t\t// what ends this text\n\t\t\t\t\t\t\t\t\tbool ignoreCase,\t\t\t// whether to ignore case in the end tag\n\t\t\t\t\t\t\t\t\tTiXmlEncoding encoding );\t// the current encoding\n\n\t// If an entity has been found, transform it into a character.\n\tstatic const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding );\n\n\t// Get a character, while interpreting entities.\n\t// The length can be from 0 to 4 bytes.\n\tinline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding )\n\t{\n\t\tassert( p );\n\t\tif ( encoding == TIXML_ENCODING_UTF8 )\n\t\t{\n\t\t\t*length = utf8ByteTable[ *((const unsigned char*)p) ];\n\t\t\tassert( *length >= 0 && *length < 5 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t*length = 1;\n\t\t}\n\n\t\tif ( *length == 1 )\n\t\t{\n\t\t\tif ( *p == '&' )\n\t\t\t\treturn GetEntity( p, _value, length, encoding );\n\t\t\t*_value = *p;\n\t\t\treturn p+1;\n\t\t}\n\t\telse if ( *length )\n\t\t{\n\t\t\t//strncpy( _value, p, *length );\t// lots of compilers don't like this function (unsafe),\n\t\t\t\t\t\t\t\t\t\t\t\t// and the null terminator isn't needed\n\t\t\tfor( int i=0; p[i] && i<*length; ++i ) {\n\t\t\t\t_value[i] = p[i];\n\t\t\t}\n\t\t\treturn p + (*length);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Not valid text.\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\t// Return true if the next characters in the stream are any of the endTag sequences.\n\t// Ignore case only works for english, and should only be relied on when comparing\n\t// to English words: StringEqual( p, \"version\", true ) is fine.\n\tstatic bool StringEqual(\tconst char* p,\n\t\t\t\t\t\t\t\tconst char* endTag,\n\t\t\t\t\t\t\t\tbool ignoreCase,\n\t\t\t\t\t\t\t\tTiXmlEncoding encoding );\n\n\tstatic const char* errorString[ TIXML_ERROR_STRING_COUNT ];\n\n\tTiXmlCursor location;\n\n    /// Field containing a generic user pointer\n\tvoid*\t\t\tuserData;\n\t\n\t// None of these methods are reliable for any language except English.\n\t// Good for approximation, not great for accuracy.\n\tstatic int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding );\n\tstatic int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding );\n\tinline static int ToLower( int v, TiXmlEncoding encoding )\n\t{\n\t\tif ( encoding == TIXML_ENCODING_UTF8 )\n\t\t{\n\t\t\tif ( v < 128 ) return tolower( v );\n\t\t\treturn v;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn tolower( v );\n\t\t}\n\t}\n\tstatic void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length );\n\nprivate:\n\tTiXmlBase( const TiXmlBase& );\t\t\t\t// not implemented.\n\tvoid operator=( const TiXmlBase& base );\t// not allowed.\n\n\tstruct Entity\n\t{\n\t\tconst char*     str;\n\t\tunsigned int\tstrLength;\n\t\tchar\t\t    chr;\n\t};\n\tenum\n\t{\n\t\tNUM_ENTITY = 5,\n\t\tMAX_ENTITY_LENGTH = 6\n\n\t};\n\tstatic Entity entity[ NUM_ENTITY ];\n\tstatic bool condenseWhiteSpace;\n};\n\n\n/** The parent class for everything in the Document Object Model.\n\t(Except for attributes).\n\tNodes have siblings, a parent, and children. A node can be\n\tin a document, or stand on its own. The type of a TiXmlNode\n\tcan be queried, and it can be cast to its more defined type.\n*/\nclass TiXmlNode : public TiXmlBase\n{\n\tfriend class TiXmlDocument;\n\tfriend class TiXmlElement;\n\npublic:\n\t#ifdef TIXML_USE_STL\t\n\n\t    /** An input stream operator, for every class. Tolerant of newlines and\n\t\t    formatting, but doesn't expect them.\n\t    */\n\t    friend std::istream& operator >> (std::istream& in, TiXmlNode& base);\n\n\t    /** An output stream operator, for every class. Note that this outputs\n\t\t    without any newlines or formatting, as opposed to Print(), which\n\t\t    includes tabs and new lines.\n\n\t\t    The operator<< and operator>> are not completely symmetric. Writing\n\t\t    a node to a stream is very well defined. You'll get a nice stream\n\t\t    of output, without any extra whitespace or newlines.\n\t\t    \n\t\t    But reading is not as well defined. (As it always is.) If you create\n\t\t    a TiXmlElement (for example) and read that from an input stream,\n\t\t    the text needs to define an element or junk will result. This is\n\t\t    true of all input streams, but it's worth keeping in mind.\n\n\t\t    A TiXmlDocument will read nodes until it reads a root element, and\n\t\t\tall the children of that root element.\n\t    */\t\n\t    friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base);\n\n\t\t/// Appends the XML node or attribute to a std::string.\n\t\tfriend std::string& operator<< (std::string& out, const TiXmlNode& base );\n\n\t#endif\n\n\t/** The types of XML nodes supported by TinyXml. (All the\n\t\t\tunsupported types are picked up by UNKNOWN.)\n\t*/\n\tenum NodeType\n\t{\n\t\tTINYXML_DOCUMENT,\n\t\tTINYXML_ELEMENT,\n\t\tTINYXML_COMMENT,\n\t\tTINYXML_UNKNOWN,\n\t\tTINYXML_TEXT,\n\t\tTINYXML_DECLARATION,\n\t\tTINYXML_TYPECOUNT\n\t};\n\n\tvirtual ~TiXmlNode();\n\n\t/** The meaning of 'value' changes for the specific type of\n\t\tTiXmlNode.\n\t\t@verbatim\n\t\tDocument:\tfilename of the xml file\n\t\tElement:\tname of the element\n\t\tComment:\tthe comment text\n\t\tUnknown:\tthe tag contents\n\t\tText:\t\tthe text string\n\t\t@endverbatim\n\n\t\tThe subclasses will wrap this function.\n\t*/\n\tconst char *Value() const { return value.c_str (); }\n\n    #ifdef TIXML_USE_STL\n\t/** Return Value() as a std::string. If you only use STL,\n\t    this is more efficient than calling Value().\n\t\tOnly available in STL mode.\n\t*/\n\tconst std::string& ValueStr() const { return value; }\n\t#endif\n\n\tconst TIXML_STRING& ValueTStr() const { return value; }\n\n\t/** Changes the value of the node. Defined as:\n\t\t@verbatim\n\t\tDocument:\tfilename of the xml file\n\t\tElement:\tname of the element\n\t\tComment:\tthe comment text\n\t\tUnknown:\tthe tag contents\n\t\tText:\t\tthe text string\n\t\t@endverbatim\n\t*/\n\tvoid SetValue(const char * _value) { value = _value;}\n\n    #ifdef TIXML_USE_STL\n\t/// STL std::string form.\n\tvoid SetValue( const std::string& _value )\t{ value = _value; }\n\t#endif\n\n\t/// Delete all the children of this node. Does not affect 'this'.\n\tvoid Clear();\n\n\t/// One step up the DOM.\n\tTiXmlNode* Parent()\t\t\t\t\t\t\t{ return parent; }\n\tconst TiXmlNode* Parent() const\t\t\t\t{ return parent; }\n\n\tconst TiXmlNode* FirstChild()\tconst\t\t{ return firstChild; }\t///< The first child of this node. Will be null if there are no children.\n\tTiXmlNode* FirstChild()\t\t\t\t\t\t{ return firstChild; }\n\tconst TiXmlNode* FirstChild( const char * value ) const;\t\t\t///< The first child of this node with the matching 'value'. Will be null if none found.\n\t/// The first child of this node with the matching 'value'. Will be null if none found.\n\tTiXmlNode* FirstChild( const char * _value ) {\n\t\t// Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe)\n\t\t// call the method, cast the return back to non-const.\n\t\treturn const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value ));\n\t}\n\tconst TiXmlNode* LastChild() const\t{ return lastChild; }\t\t/// The last child of this node. Will be null if there are no children.\n\tTiXmlNode* LastChild()\t{ return lastChild; }\n\t\n\tconst TiXmlNode* LastChild( const char * value ) const;\t\t\t/// The last child of this node matching 'value'. Will be null if there are no children.\n\tTiXmlNode* LastChild( const char * _value ) {\n\t\treturn const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value ));\n\t}\n\n    #ifdef TIXML_USE_STL\n\tconst TiXmlNode* FirstChild( const std::string& _value ) const\t{\treturn FirstChild (_value.c_str ());\t}\t///< STL std::string form.\n\tTiXmlNode* FirstChild( const std::string& _value )\t\t\t\t{\treturn FirstChild (_value.c_str ());\t}\t///< STL std::string form.\n\tconst TiXmlNode* LastChild( const std::string& _value ) const\t{\treturn LastChild (_value.c_str ());\t}\t///< STL std::string form.\n\tTiXmlNode* LastChild( const std::string& _value )\t\t\t\t{\treturn LastChild (_value.c_str ());\t}\t///< STL std::string form.\n\t#endif\n\n\t/** An alternate way to walk the children of a node.\n\t\tOne way to iterate over nodes is:\n\t\t@verbatim\n\t\t\tfor( child = parent->FirstChild(); child; child = child->NextSibling() )\n\t\t@endverbatim\n\n\t\tIterateChildren does the same thing with the syntax:\n\t\t@verbatim\n\t\t\tchild = 0;\n\t\t\twhile( child = parent->IterateChildren( child ) )\n\t\t@endverbatim\n\n\t\tIterateChildren takes the previous child as input and finds\n\t\tthe next one. If the previous child is null, it returns the\n\t\tfirst. IterateChildren will return null when done.\n\t*/\n\tconst TiXmlNode* IterateChildren( const TiXmlNode* previous ) const;\n\tTiXmlNode* IterateChildren( const TiXmlNode* previous ) {\n\t\treturn const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) );\n\t}\n\n\t/// This flavor of IterateChildren searches for children with a particular 'value'\n\tconst TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const;\n\tTiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) {\n\t\treturn const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) );\n\t}\n\n    #ifdef TIXML_USE_STL\n\tconst TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const\t{\treturn IterateChildren (_value.c_str (), previous);\t}\t///< STL std::string form.\n\tTiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) {\treturn IterateChildren (_value.c_str (), previous);\t}\t///< STL std::string form.\n\t#endif\n\n\t/** Add a new node related to this. Adds a child past the LastChild.\n\t\tReturns a pointer to the new object or NULL if an error occured.\n\t*/\n\tTiXmlNode* InsertEndChild( const TiXmlNode& addThis );\n\n\n\t/** Add a new node related to this. Adds a child past the LastChild.\n\n\t\tNOTE: the node to be added is passed by pointer, and will be\n\t\thenceforth owned (and deleted) by tinyXml. This method is efficient\n\t\tand avoids an extra copy, but should be used with care as it\n\t\tuses a different memory model than the other insert functions.\n\n\t\t@sa InsertEndChild\n\t*/\n\tTiXmlNode* LinkEndChild( TiXmlNode* addThis );\n\n\t/** Add a new node related to this. Adds a child before the specified child.\n\t\tReturns a pointer to the new object or NULL if an error occured.\n\t*/\n\tTiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis );\n\n\t/** Add a new node related to this. Adds a child after the specified child.\n\t\tReturns a pointer to the new object or NULL if an error occured.\n\t*/\n\tTiXmlNode* InsertAfterChild(  TiXmlNode* afterThis, const TiXmlNode& addThis );\n\n\t/** Replace a child of this node.\n\t\tReturns a pointer to the new object or NULL if an error occured.\n\t*/\n\tTiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis );\n\n\t/// Delete a child of this node.\n\tbool RemoveChild( TiXmlNode* removeThis );\n\n\t/// Navigate to a sibling node.\n\tconst TiXmlNode* PreviousSibling() const\t\t\t{ return prev; }\n\tTiXmlNode* PreviousSibling()\t\t\t\t\t\t{ return prev; }\n\n\t/// Navigate to a sibling node.\n\tconst TiXmlNode* PreviousSibling( const char * ) const;\n\tTiXmlNode* PreviousSibling( const char *_prev ) {\n\t\treturn const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) );\n\t}\n\n    #ifdef TIXML_USE_STL\n\tconst TiXmlNode* PreviousSibling( const std::string& _value ) const\t{\treturn PreviousSibling (_value.c_str ());\t}\t///< STL std::string form.\n\tTiXmlNode* PreviousSibling( const std::string& _value ) \t\t\t{\treturn PreviousSibling (_value.c_str ());\t}\t///< STL std::string form.\n\tconst TiXmlNode* NextSibling( const std::string& _value) const\t\t{\treturn NextSibling (_value.c_str ());\t}\t///< STL std::string form.\n\tTiXmlNode* NextSibling( const std::string& _value) \t\t\t\t\t{\treturn NextSibling (_value.c_str ());\t}\t///< STL std::string form.\n\t#endif\n\n\t/// Navigate to a sibling node.\n\tconst TiXmlNode* NextSibling() const\t\t\t\t{ return next; }\n\tTiXmlNode* NextSibling()\t\t\t\t\t\t\t{ return next; }\n\n\t/// Navigate to a sibling node with the given 'value'.\n\tconst TiXmlNode* NextSibling( const char * ) const;\n\tTiXmlNode* NextSibling( const char* _next ) {\n\t\treturn const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) );\n\t}\n\n\t/** Convenience function to get through elements.\n\t\tCalls NextSibling and ToElement. Will skip all non-Element\n\t\tnodes. Returns 0 if there is not another element.\n\t*/\n\tconst TiXmlElement* NextSiblingElement() const;\n\tTiXmlElement* NextSiblingElement() {\n\t\treturn const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() );\n\t}\n\n\t/** Convenience function to get through elements.\n\t\tCalls NextSibling and ToElement. Will skip all non-Element\n\t\tnodes. Returns 0 if there is not another element.\n\t*/\n\tconst TiXmlElement* NextSiblingElement( const char * ) const;\n\tTiXmlElement* NextSiblingElement( const char *_next ) {\n\t\treturn const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) );\n\t}\n\n    #ifdef TIXML_USE_STL\n\tconst TiXmlElement* NextSiblingElement( const std::string& _value) const\t{\treturn NextSiblingElement (_value.c_str ());\t}\t///< STL std::string form.\n\tTiXmlElement* NextSiblingElement( const std::string& _value)\t\t\t\t{\treturn NextSiblingElement (_value.c_str ());\t}\t///< STL std::string form.\n\t#endif\n\n\t/// Convenience function to get through elements.\n\tconst TiXmlElement* FirstChildElement()\tconst;\n\tTiXmlElement* FirstChildElement() {\n\t\treturn const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() );\n\t}\n\n\t/// Convenience function to get through elements.\n\tconst TiXmlElement* FirstChildElement( const char * _value ) const;\n\tTiXmlElement* FirstChildElement( const char * _value ) {\n\t\treturn const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) );\n\t}\n\n    #ifdef TIXML_USE_STL\n\tconst TiXmlElement* FirstChildElement( const std::string& _value ) const\t{\treturn FirstChildElement (_value.c_str ());\t}\t///< STL std::string form.\n\tTiXmlElement* FirstChildElement( const std::string& _value )\t\t\t\t{\treturn FirstChildElement (_value.c_str ());\t}\t///< STL std::string form.\n\t#endif\n\n\t/** Query the type (as an enumerated value, above) of this node.\n\t\tThe possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT,\n\t\t\t\t\t\t\t\tTINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION.\n\t*/\n\tint Type() const\t{ return type; }\n\n\t/** Return a pointer to the Document this node lives in.\n\t\tReturns null if not in a document.\n\t*/\n\tconst TiXmlDocument* GetDocument() const;\n\tTiXmlDocument* GetDocument() {\n\t\treturn const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() );\n\t}\n\n\t/// Returns true if this node has no children.\n\tbool NoChildren() const\t\t\t\t\t\t{ return !firstChild; }\n\n\tvirtual const TiXmlDocument*    ToDocument()    const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual const TiXmlElement*     ToElement()     const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual const TiXmlComment*     ToComment()     const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual const TiXmlUnknown*     ToUnknown()     const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual const TiXmlText*        ToText()        const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\n\tvirtual TiXmlDocument*          ToDocument()    { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual TiXmlElement*           ToElement()\t    { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual TiXmlComment*           ToComment()     { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual TiXmlUnknown*           ToUnknown()\t    { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual TiXmlText*\t            ToText()        { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\tvirtual TiXmlDeclaration*       ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.\n\n\t/** Create an exact duplicate of this node and return it. The memory must be deleted\n\t\tby the caller. \n\t*/\n\tvirtual TiXmlNode* Clone() const = 0;\n\n\t/** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the \n\t\tXML tree will be conditionally visited and the host will be called back\n\t\tvia the TiXmlVisitor interface.\n\n\t\tThis is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse\n\t\tthe XML for the callbacks, so the performance of TinyXML is unchanged by using this\n\t\tinterface versus any other.)\n\n\t\tThe interface has been based on ideas from:\n\n\t\t- http://www.saxproject.org/\n\t\t- http://c2.com/cgi/wiki?HierarchicalVisitorPattern \n\n\t\tWhich are both good references for \"visiting\".\n\n\t\tAn example of using Accept():\n\t\t@verbatim\n\t\tTiXmlPrinter printer;\n\t\ttinyxmlDoc.Accept( &printer );\n\t\tconst char* xmlcstr = printer.CStr();\n\t\t@endverbatim\n\t*/\n\tvirtual bool Accept( TiXmlVisitor* visitor ) const = 0;\n\nprotected:\n\tTiXmlNode( NodeType _type );\n\n\t// Copy to the allocated object. Shared functionality between Clone, Copy constructor,\n\t// and the assignment operator.\n\tvoid CopyTo( TiXmlNode* target ) const;\n\n\t#ifdef TIXML_USE_STL\n\t    // The real work of the input operator.\n\tvirtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0;\n\t#endif\n\n\t// Figure out what is at *p, and parse it. Returns null if it is not an xml node.\n\tTiXmlNode* Identify( const char* start, TiXmlEncoding encoding );\n\n\tTiXmlNode*\t\tparent;\n\tNodeType\t\ttype;\n\n\tTiXmlNode*\t\tfirstChild;\n\tTiXmlNode*\t\tlastChild;\n\n\tTIXML_STRING\tvalue;\n\n\tTiXmlNode*\t\tprev;\n\tTiXmlNode*\t\tnext;\n\nprivate:\n\tTiXmlNode( const TiXmlNode& );\t\t\t\t// not implemented.\n\tvoid operator=( const TiXmlNode& base );\t// not allowed.\n};\n\n\n/** An attribute is a name-value pair. Elements have an arbitrary\n\tnumber of attributes, each with a unique name.\n\n\t@note The attributes are not TiXmlNodes, since they are not\n\t\t  part of the tinyXML document object model. There are other\n\t\t  suggested ways to look at this problem.\n*/\nclass TiXmlAttribute : public TiXmlBase\n{\n\tfriend class TiXmlAttributeSet;\n\npublic:\n\t/// Construct an empty attribute.\n\tTiXmlAttribute() : TiXmlBase()\n\t{\n\t\tdocument = 0;\n\t\tprev = next = 0;\n\t}\n\n\t#ifdef TIXML_USE_STL\n\t/// std::string constructor.\n\tTiXmlAttribute( const std::string& _name, const std::string& _value )\n\t{\n\t\tname = _name;\n\t\tvalue = _value;\n\t\tdocument = 0;\n\t\tprev = next = 0;\n\t}\n\t#endif\n\n\t/// Construct an attribute with a name and value.\n\tTiXmlAttribute( const char * _name, const char * _value )\n\t{\n\t\tname = _name;\n\t\tvalue = _value;\n\t\tdocument = 0;\n\t\tprev = next = 0;\n\t}\n\n\tconst char*\t\tName()  const\t\t{ return name.c_str(); }\t\t///< Return the name of this attribute.\n\tconst char*\t\tValue() const\t\t{ return value.c_str(); }\t\t///< Return the value of this attribute.\n\t#ifdef TIXML_USE_STL\n\tconst std::string& ValueStr() const\t{ return value; }\t\t\t\t///< Return the value of this attribute.\n\t#endif\n\tint\t\t\t\tIntValue() const;\t\t\t\t\t\t\t\t\t///< Return the value of this attribute, converted to an integer.\n\tdouble\t\t\tDoubleValue() const;\t\t\t\t\t\t\t\t///< Return the value of this attribute, converted to a double.\n\n\t// Get the tinyxml string representation\n\tconst TIXML_STRING& NameTStr() const { return name; }\n\n\t/** QueryIntValue examines the value string. It is an alternative to the\n\t\tIntValue() method with richer error checking.\n\t\tIf the value is an integer, it is stored in 'value' and \n\t\tthe call returns TIXML_SUCCESS. If it is not\n\t\tan integer, it returns TIXML_WRONG_TYPE.\n\n\t\tA specialized but useful call. Note that for success it returns 0,\n\t\twhich is the opposite of almost all other TinyXml calls.\n\t*/\n\tint QueryIntValue( int* _value ) const;\n\t/// QueryDoubleValue examines the value string. See QueryIntValue().\n\tint QueryDoubleValue( double* _value ) const;\n\n\tvoid SetName( const char* _name )\t{ name = _name; }\t\t\t\t///< Set the name of this attribute.\n\tvoid SetValue( const char* _value )\t{ value = _value; }\t\t\t\t///< Set the value.\n\n\tvoid SetIntValue( int _value );\t\t\t\t\t\t\t\t\t\t///< Set the value from an integer.\n\tvoid SetDoubleValue( double _value );\t\t\t\t\t\t\t\t///< Set the value from a double.\n\n    #ifdef TIXML_USE_STL\n\t/// STL std::string form.\n\tvoid SetName( const std::string& _name )\t{ name = _name; }\t\n\t/// STL std::string form.\t\n\tvoid SetValue( const std::string& _value )\t{ value = _value; }\n\t#endif\n\n\t/// Get the next sibling attribute in the DOM. Returns null at end.\n\tconst TiXmlAttribute* Next() const;\n\tTiXmlAttribute* Next() {\n\t\treturn const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); \n\t}\n\n\t/// Get the previous sibling attribute in the DOM. Returns null at beginning.\n\tconst TiXmlAttribute* Previous() const;\n\tTiXmlAttribute* Previous() {\n\t\treturn const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); \n\t}\n\n\tbool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; }\n\tbool operator<( const TiXmlAttribute& rhs )\t const { return name < rhs.name; }\n\tbool operator>( const TiXmlAttribute& rhs )  const { return name > rhs.name; }\n\n\t/*\tAttribute parsing starts: first letter of the name\n\t\t\t\t\t\t returns: the next char after the value end quote\n\t*/\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );\n\n\t// Prints this Attribute to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth ) const {\n\t\tPrint( cfile, depth, 0 );\n\t}\n\tvoid Print( FILE* cfile, int depth, TIXML_STRING* str ) const;\n\n\t// [internal use]\n\t// Set the document pointer so the attribute can report errors.\n\tvoid SetDocument( TiXmlDocument* doc )\t{ document = doc; }\n\nprivate:\n\tTiXmlAttribute( const TiXmlAttribute& );\t\t\t\t// not implemented.\n\tvoid operator=( const TiXmlAttribute& base );\t// not allowed.\n\n\tTiXmlDocument*\tdocument;\t// A pointer back to a document, for error reporting.\n\tTIXML_STRING name;\n\tTIXML_STRING value;\n\tTiXmlAttribute*\tprev;\n\tTiXmlAttribute*\tnext;\n};\n\n\n/*\tA class used to manage a group of attributes.\n\tIt is only used internally, both by the ELEMENT and the DECLARATION.\n\t\n\tThe set can be changed transparent to the Element and Declaration\n\tclasses that use it, but NOT transparent to the Attribute\n\twhich has to implement a next() and previous() method. Which makes\n\tit a bit problematic and prevents the use of STL.\n\n\tThis version is implemented with circular lists because:\n\t\t- I like circular lists\n\t\t- it demonstrates some independence from the (typical) doubly linked list.\n*/\nclass TiXmlAttributeSet\n{\npublic:\n\tTiXmlAttributeSet();\n\t~TiXmlAttributeSet();\n\n\tvoid Add( TiXmlAttribute* attribute );\n\tvoid Remove( TiXmlAttribute* attribute );\n\n\tconst TiXmlAttribute* First()\tconst\t{ return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; }\n\tTiXmlAttribute* First()\t\t\t\t\t{ return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; }\n\tconst TiXmlAttribute* Last() const\t\t{ return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; }\n\tTiXmlAttribute* Last()\t\t\t\t\t{ return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; }\n\n\tTiXmlAttribute*\tFind( const char* _name ) const;\n\tTiXmlAttribute* FindOrCreate( const char* _name );\n\n#\tifdef TIXML_USE_STL\n\tTiXmlAttribute*\tFind( const std::string& _name ) const;\n\tTiXmlAttribute* FindOrCreate( const std::string& _name );\n#\tendif\n\n\nprivate:\n\t//*ME:\tBecause of hidden/disabled copy-constructor in TiXmlAttribute (sentinel-element),\n\t//*ME:\tthis class must be also use a hidden/disabled copy-constructor !!!\n\tTiXmlAttributeSet( const TiXmlAttributeSet& );\t// not allowed\n\tvoid operator=( const TiXmlAttributeSet& );\t// not allowed (as TiXmlAttribute)\n\n\tTiXmlAttribute sentinel;\n};\n\n\n/** The element is a container class. It has a value, the element name,\n\tand can contain other elements, text, comments, and unknowns.\n\tElements also contain an arbitrary number of attributes.\n*/\nclass TiXmlElement : public TiXmlNode\n{\npublic:\n\t/// Construct an element.\n\tTiXmlElement (const char * in_value);\n\n\t#ifdef TIXML_USE_STL\n\t/// std::string constructor.\n\tTiXmlElement( const std::string& _value );\n\t#endif\n\n\tTiXmlElement( const TiXmlElement& );\n\n\tTiXmlElement& operator=( const TiXmlElement& base );\n\n\tvirtual ~TiXmlElement();\n\n\t/** Given an attribute name, Attribute() returns the value\n\t\tfor the attribute of that name, or null if none exists.\n\t*/\n\tconst char* Attribute( const char* name ) const;\n\n\t/** Given an attribute name, Attribute() returns the value\n\t\tfor the attribute of that name, or null if none exists.\n\t\tIf the attribute exists and can be converted to an integer,\n\t\tthe integer value will be put in the return 'i', if 'i'\n\t\tis non-null.\n\t*/\n\tconst char* Attribute( const char* name, int* i ) const;\n\n\t/** Given an attribute name, Attribute() returns the value\n\t\tfor the attribute of that name, or null if none exists.\n\t\tIf the attribute exists and can be converted to an double,\n\t\tthe double value will be put in the return 'd', if 'd'\n\t\tis non-null.\n\t*/\n\tconst char* Attribute( const char* name, double* d ) const;\n\n\t/** QueryIntAttribute examines the attribute - it is an alternative to the\n\t\tAttribute() method with richer error checking.\n\t\tIf the attribute is an integer, it is stored in 'value' and \n\t\tthe call returns TIXML_SUCCESS. If it is not\n\t\tan integer, it returns TIXML_WRONG_TYPE. If the attribute\n\t\tdoes not exist, then TIXML_NO_ATTRIBUTE is returned.\n\t*/\t\n\tint QueryIntAttribute( const char* name, int* _value ) const;\n\t/// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute().\n\tint QueryUnsignedAttribute( const char* name, unsigned* _value ) const;\n\t/** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). \n\t\tNote that '1', 'true', or 'yes' are considered true, while '0', 'false'\n\t\tand 'no' are considered false.\n\t*/\n\tint QueryBoolAttribute( const char* name, bool* _value ) const;\n\t/// QueryDoubleAttribute examines the attribute - see QueryIntAttribute().\n\tint QueryDoubleAttribute( const char* name, double* _value ) const;\n\t/// QueryFloatAttribute examines the attribute - see QueryIntAttribute().\n\tint QueryFloatAttribute( const char* name, float* _value ) const {\n\t\tdouble d;\n\t\tint result = QueryDoubleAttribute( name, &d );\n\t\tif ( result == TIXML_SUCCESS ) {\n\t\t\t*_value = (float)d;\n\t\t}\n\t\treturn result;\n\t}\n\n    #ifdef TIXML_USE_STL\n\t/// QueryStringAttribute examines the attribute - see QueryIntAttribute().\n\tint QueryStringAttribute( const char* name, std::string* _value ) const {\n\t\tconst char* cstr = Attribute( name );\n\t\tif ( cstr ) {\n\t\t\t*_value = std::string( cstr );\n\t\t\treturn TIXML_SUCCESS;\n\t\t}\n\t\treturn TIXML_NO_ATTRIBUTE;\n\t}\n\n\t/** Template form of the attribute query which will try to read the\n\t\tattribute into the specified type. Very easy, very powerful, but\n\t\tbe careful to make sure to call this with the correct type.\n\t\t\n\t\tNOTE: This method doesn't work correctly for 'string' types that contain spaces.\n\n\t\t@return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE\n\t*/\n\ttemplate< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const\n\t{\n\t\tconst TiXmlAttribute* node = attributeSet.Find( name );\n\t\tif ( !node )\n\t\t\treturn TIXML_NO_ATTRIBUTE;\n\n\t\tstd::stringstream sstream( node->ValueStr() );\n\t\tsstream >> *outValue;\n\t\tif ( !sstream.fail() )\n\t\t\treturn TIXML_SUCCESS;\n\t\treturn TIXML_WRONG_TYPE;\n\t}\n\n\tint QueryValueAttribute( const std::string& name, std::string* outValue ) const\n\t{\n\t\tconst TiXmlAttribute* node = attributeSet.Find( name );\n\t\tif ( !node )\n\t\t\treturn TIXML_NO_ATTRIBUTE;\n\t\t*outValue = node->ValueStr();\n\t\treturn TIXML_SUCCESS;\n\t}\n\t#endif\n\n\t/** Sets an attribute of name to a given value. The attribute\n\t\twill be created if it does not exist, or changed if it does.\n\t*/\n\tvoid SetAttribute( const char* name, const char * _value );\n\n    #ifdef TIXML_USE_STL\n\tconst std::string* Attribute( const std::string& name ) const;\n\tconst std::string* Attribute( const std::string& name, int* i ) const;\n\tconst std::string* Attribute( const std::string& name, double* d ) const;\n\tint QueryIntAttribute( const std::string& name, int* _value ) const;\n\tint QueryDoubleAttribute( const std::string& name, double* _value ) const;\n\n\t/// STL std::string form.\n\tvoid SetAttribute( const std::string& name, const std::string& _value );\n\t///< STL std::string form.\n\tvoid SetAttribute( const std::string& name, int _value );\n\t///< STL std::string form.\n\tvoid SetDoubleAttribute( const std::string& name, double value );\n\t#endif\n\n\t/** Sets an attribute of name to a given value. The attribute\n\t\twill be created if it does not exist, or changed if it does.\n\t*/\n\tvoid SetAttribute( const char * name, int value );\n\n\t/** Sets an attribute of name to a given value. The attribute\n\t\twill be created if it does not exist, or changed if it does.\n\t*/\n\tvoid SetDoubleAttribute( const char * name, double value );\n\n\t/** Deletes an attribute with the given name.\n\t*/\n\tvoid RemoveAttribute( const char * name );\n    #ifdef TIXML_USE_STL\n\tvoid RemoveAttribute( const std::string& name )\t{\tRemoveAttribute (name.c_str ());\t}\t///< STL std::string form.\n\t#endif\n\n\tconst TiXmlAttribute* FirstAttribute() const\t{ return attributeSet.First(); }\t\t///< Access the first attribute in this element.\n\tTiXmlAttribute* FirstAttribute() \t\t\t\t{ return attributeSet.First(); }\n\tconst TiXmlAttribute* LastAttribute()\tconst \t{ return attributeSet.Last(); }\t\t///< Access the last attribute in this element.\n\tTiXmlAttribute* LastAttribute()\t\t\t\t\t{ return attributeSet.Last(); }\n\n\t/** Convenience function for easy access to the text inside an element. Although easy\n\t\tand concise, GetText() is limited compared to getting the TiXmlText child\n\t\tand accessing it directly.\n\t\n\t\tIf the first child of 'this' is a TiXmlText, the GetText()\n\t\treturns the character string of the Text node, else null is returned.\n\n\t\tThis is a convenient method for getting the text of simple contained text:\n\t\t@verbatim\n\t\t<foo>This is text</foo>\n\t\tconst char* str = fooElement->GetText();\n\t\t@endverbatim\n\n\t\t'str' will be a pointer to \"This is text\". \n\t\t\n\t\tNote that this function can be misleading. If the element foo was created from\n\t\tthis XML:\n\t\t@verbatim\n\t\t<foo><b>This is text</b></foo> \n\t\t@endverbatim\n\n\t\tthen the value of str would be null. The first child node isn't a text node, it is\n\t\tanother element. From this XML:\n\t\t@verbatim\n\t\t<foo>This is <b>text</b></foo> \n\t\t@endverbatim\n\t\tGetText() will return \"This is \".\n\n\t\tWARNING: GetText() accesses a child node - don't become confused with the \n\t\t\t\t similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are \n\t\t\t\t safe type casts on the referenced node.\n\t*/\n\tconst char* GetText() const;\n\n\t/// Creates a new Element and returns it - the returned element is a copy.\n\tvirtual TiXmlNode* Clone() const;\n\t// Print the Element to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth ) const;\n\n\t/*\tAttribute parsing starts: next char past '<'\n\t\t\t\t\t\t returns: next char past '>'\n\t*/\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );\n\n\tvirtual const TiXmlElement*     ToElement()     const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\tvirtual TiXmlElement*           ToElement()\t          { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\n\t/** Walk the XML tree visiting this node and all of its children. \n\t*/\n\tvirtual bool Accept( TiXmlVisitor* visitor ) const;\n\nprotected:\n\n\tvoid CopyTo( TiXmlElement* target ) const;\n\tvoid ClearThis();\t// like clear, but initializes 'this' object as well\n\n\t// Used to be public [internal use]\n\t#ifdef TIXML_USE_STL\n\tvirtual void StreamIn( std::istream * in, TIXML_STRING * tag );\n\t#endif\n\t/*\t[internal use]\n\t\tReads the \"value\" of the element -- another element, or text.\n\t\tThis should terminate with the current end tag.\n\t*/\n\tconst char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding );\n\nprivate:\n\tTiXmlAttributeSet attributeSet;\n};\n\n\n/**\tAn XML comment.\n*/\nclass TiXmlComment : public TiXmlNode\n{\npublic:\n\t/// Constructs an empty comment.\n\tTiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {}\n\t/// Construct a comment from text.\n\tTiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {\n\t\tSetValue( _value );\n\t}\n\tTiXmlComment( const TiXmlComment& );\n\tTiXmlComment& operator=( const TiXmlComment& base );\n\n\tvirtual ~TiXmlComment()\t{}\n\n\t/// Returns a copy of this Comment.\n\tvirtual TiXmlNode* Clone() const;\n\t// Write this Comment to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth ) const;\n\n\t/*\tAttribute parsing starts: at the ! of the !--\n\t\t\t\t\t\t returns: next char past '>'\n\t*/\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );\n\n\tvirtual const TiXmlComment*  ToComment() const\t{ return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\tvirtual\t\t  TiXmlComment*  ToComment()\t\t{ return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\n\t/** Walk the XML tree visiting this node and all of its children. \n\t*/\n\tvirtual bool Accept( TiXmlVisitor* visitor ) const;\n\nprotected:\n\tvoid CopyTo( TiXmlComment* target ) const;\n\n\t// used to be public\n\t#ifdef TIXML_USE_STL\n\tvirtual void StreamIn( std::istream * in, TIXML_STRING * tag );\n\t#endif\n//\tvirtual void StreamOut( TIXML_OSTREAM * out ) const;\n\nprivate:\n\n};\n\n\n/** XML text. A text node can have 2 ways to output the next. \"normal\" output \n\tand CDATA. It will default to the mode it was parsed from the XML file and\n\tyou generally want to leave it alone, but you can change the output mode with \n\tSetCDATA() and query it with CDATA().\n*/\nclass TiXmlText : public TiXmlNode\n{\n\tfriend class TiXmlElement;\npublic:\n\t/** Constructor for text element. By default, it is treated as \n\t\tnormal, encoded text. If you want it be output as a CDATA text\n\t\telement, set the parameter _cdata to 'true'\n\t*/\n\tTiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT)\n\t{\n\t\tSetValue( initValue );\n\t\tcdata = false;\n\t}\n\tvirtual ~TiXmlText() {}\n\n\t#ifdef TIXML_USE_STL\n\t/// Constructor.\n\tTiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT)\n\t{\n\t\tSetValue( initValue );\n\t\tcdata = false;\n\t}\n\t#endif\n\n\tTiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT )\t{ copy.CopyTo( this ); }\n\tTiXmlText& operator=( const TiXmlText& base )\t\t\t\t\t\t\t \t{ base.CopyTo( this ); return *this; }\n\n\t// Write this text object to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth ) const;\n\n\t/// Queries whether this represents text using a CDATA section.\n\tbool CDATA() const\t\t\t\t{ return cdata; }\n\t/// Turns on or off a CDATA representation of text.\n\tvoid SetCDATA( bool _cdata )\t{ cdata = _cdata; }\n\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );\n\n\tvirtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\tvirtual TiXmlText*       ToText()       { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\n\t/** Walk the XML tree visiting this node and all of its children. \n\t*/\n\tvirtual bool Accept( TiXmlVisitor* content ) const;\n\nprotected :\n\t///  [internal use] Creates a new Element and returns it.\n\tvirtual TiXmlNode* Clone() const;\n\tvoid CopyTo( TiXmlText* target ) const;\n\n\tbool Blank() const;\t// returns true if all white space and new lines\n\t// [internal use]\n\t#ifdef TIXML_USE_STL\n\tvirtual void StreamIn( std::istream * in, TIXML_STRING * tag );\n\t#endif\n\nprivate:\n\tbool cdata;\t\t\t// true if this should be input and output as a CDATA style text element\n};\n\n\n/** In correct XML the declaration is the first entry in the file.\n\t@verbatim\n\t\t<?xml version=\"1.0\" standalone=\"yes\"?>\n\t@endverbatim\n\n\tTinyXml will happily read or write files without a declaration,\n\thowever. There are 3 possible attributes to the declaration:\n\tversion, encoding, and standalone.\n\n\tNote: In this version of the code, the attributes are\n\thandled as special cases, not generic attributes, simply\n\tbecause there can only be at most 3 and they are always the same.\n*/\nclass TiXmlDeclaration : public TiXmlNode\n{\npublic:\n\t/// Construct an empty declaration.\n\tTiXmlDeclaration()   : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {}\n\n#ifdef TIXML_USE_STL\n\t/// Constructor.\n\tTiXmlDeclaration(\tconst std::string& _version,\n\t\t\t\t\t\tconst std::string& _encoding,\n\t\t\t\t\t\tconst std::string& _standalone );\n#endif\n\n\t/// Construct.\n\tTiXmlDeclaration(\tconst char* _version,\n\t\t\t\t\t\tconst char* _encoding,\n\t\t\t\t\t\tconst char* _standalone );\n\n\tTiXmlDeclaration( const TiXmlDeclaration& copy );\n\tTiXmlDeclaration& operator=( const TiXmlDeclaration& copy );\n\n\tvirtual ~TiXmlDeclaration()\t{}\n\n\t/// Version. Will return an empty string if none was found.\n\tconst char *Version() const\t\t\t{ return version.c_str (); }\n\t/// Encoding. Will return an empty string if none was found.\n\tconst char *Encoding() const\t\t{ return encoding.c_str (); }\n\t/// Is this a standalone document?\n\tconst char *Standalone() const\t\t{ return standalone.c_str (); }\n\n\t/// Creates a copy of this Declaration and returns it.\n\tvirtual TiXmlNode* Clone() const;\n\t// Print this declaration to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const;\n\tvirtual void Print( FILE* cfile, int depth ) const {\n\t\tPrint( cfile, depth, 0 );\n\t}\n\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );\n\n\tvirtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\tvirtual TiXmlDeclaration*       ToDeclaration()       { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\n\t/** Walk the XML tree visiting this node and all of its children. \n\t*/\n\tvirtual bool Accept( TiXmlVisitor* visitor ) const;\n\nprotected:\n\tvoid CopyTo( TiXmlDeclaration* target ) const;\n\t// used to be public\n\t#ifdef TIXML_USE_STL\n\tvirtual void StreamIn( std::istream * in, TIXML_STRING * tag );\n\t#endif\n\nprivate:\n\n\tTIXML_STRING version;\n\tTIXML_STRING encoding;\n\tTIXML_STRING standalone;\n};\n\n\n/** Any tag that tinyXml doesn't recognize is saved as an\n\tunknown. It is a tag of text, but should not be modified.\n\tIt will be written back to the XML, unchanged, when the file\n\tis saved.\n\n\tDTD tags get thrown into TiXmlUnknowns.\n*/\nclass TiXmlUnknown : public TiXmlNode\n{\npublic:\n\tTiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN )\t{}\n\tvirtual ~TiXmlUnknown() {}\n\n\tTiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN )\t\t{ copy.CopyTo( this ); }\n\tTiXmlUnknown& operator=( const TiXmlUnknown& copy )\t\t\t\t\t\t\t\t\t\t{ copy.CopyTo( this ); return *this; }\n\n\t/// Creates a copy of this Unknown and returns it.\n\tvirtual TiXmlNode* Clone() const;\n\t// Print this Unknown to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth ) const;\n\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );\n\n\tvirtual const TiXmlUnknown*     ToUnknown()     const\t{ return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\tvirtual TiXmlUnknown*           ToUnknown()\t\t\t\t{ return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\n\t/** Walk the XML tree visiting this node and all of its children. \n\t*/\n\tvirtual bool Accept( TiXmlVisitor* content ) const;\n\nprotected:\n\tvoid CopyTo( TiXmlUnknown* target ) const;\n\n\t#ifdef TIXML_USE_STL\n\tvirtual void StreamIn( std::istream * in, TIXML_STRING * tag );\n\t#endif\n\nprivate:\n\n};\n\n\n/** Always the top level node. A document binds together all the\n\tXML pieces. It can be saved, loaded, and printed to the screen.\n\tThe 'value' of a document node is the xml file name.\n*/\nclass TiXmlDocument : public TiXmlNode\n{\npublic:\n\t/// Create an empty document, that has no name.\n\tTiXmlDocument();\n\t/// Create a document with a name. The name of the document is also the filename of the xml.\n\tTiXmlDocument( const char * documentName );\n\n\t#ifdef TIXML_USE_STL\n\t/// Constructor.\n\tTiXmlDocument( const std::string& documentName );\n\t#endif\n\n\tTiXmlDocument( const TiXmlDocument& copy );\n\tTiXmlDocument& operator=( const TiXmlDocument& copy );\n\n\tvirtual ~TiXmlDocument() {}\n\n\t/** Load a file using the current document value.\n\t\tReturns true if successful. Will delete any existing\n\t\tdocument data before loading.\n\t*/\n\tbool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );\n\t/// Save a file using the current document value. Returns true if successful.\n\tbool SaveFile() const;\n\t/// Load a file using the given filename. Returns true if successful.\n\tbool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );\n\t/// Save a file using the given filename. Returns true if successful.\n\tbool SaveFile( const char * filename ) const;\n\t/** Load a file using the given FILE*. Returns true if successful. Note that this method\n\t\tdoesn't stream - the entire object pointed at by the FILE*\n\t\twill be interpreted as an XML file. TinyXML doesn't stream in XML from the current\n\t\tfile location. Streaming may be added in the future.\n\t*/\n\tbool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );\n\t/// Save a file using the given FILE*. Returns true if successful.\n\tbool SaveFile( FILE* ) const;\n\n\t#ifdef TIXML_USE_STL\n\tbool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING )\t\t\t///< STL std::string version.\n\t{\n\t\treturn LoadFile( filename.c_str(), encoding );\n\t}\n\tbool SaveFile( const std::string& filename ) const\t\t///< STL std::string version.\n\t{\n\t\treturn SaveFile( filename.c_str() );\n\t}\n\t#endif\n\n\t/** Parse the given null terminated block of xml data. Passing in an encoding to this\n\t\tmethod (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml\n\t\tto use that encoding, regardless of what TinyXml might otherwise try to detect.\n\t*/\n\tvirtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );\n\n\t/** Get the root element -- the only top level element -- of the document.\n\t\tIn well formed XML, there should only be one. TinyXml is tolerant of\n\t\tmultiple elements at the document level.\n\t*/\n\tconst TiXmlElement* RootElement() const\t\t{ return FirstChildElement(); }\n\tTiXmlElement* RootElement()\t\t\t\t\t{ return FirstChildElement(); }\n\n\t/** If an error occurs, Error will be set to true. Also,\n\t\t- The ErrorId() will contain the integer identifier of the error (not generally useful)\n\t\t- The ErrorDesc() method will return the name of the error. (very useful)\n\t\t- The ErrorRow() and ErrorCol() will return the location of the error (if known)\n\t*/\t\n\tbool Error() const\t\t\t\t\t\t{ return error; }\n\n\t/// Contains a textual (english) description of the error if one occurs.\n\tconst char * ErrorDesc() const\t{ return errorDesc.c_str (); }\n\n\t/** Generally, you probably want the error string ( ErrorDesc() ). But if you\n\t\tprefer the ErrorId, this function will fetch it.\n\t*/\n\tint ErrorId()\tconst\t\t\t\t{ return errorId; }\n\n\t/** Returns the location (if known) of the error. The first column is column 1, \n\t\tand the first row is row 1. A value of 0 means the row and column wasn't applicable\n\t\t(memory errors, for example, have no row/column) or the parser lost the error. (An\n\t\terror in the error reporting, in that case.)\n\n\t\t@sa SetTabSize, Row, Column\n\t*/\n\tint ErrorRow() const\t{ return errorLocation.row+1; }\n\tint ErrorCol() const\t{ return errorLocation.col+1; }\t///< The column where the error occured. See ErrorRow()\n\n\t/** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol())\n\t\tto report the correct values for row and column. It does not change the output\n\t\tor input in any way.\n\t\t\n\t\tBy calling this method, with a tab size\n\t\tgreater than 0, the row and column of each node and attribute is stored\n\t\twhen the file is loaded. Very useful for tracking the DOM back in to\n\t\tthe source file.\n\n\t\tThe tab size is required for calculating the location of nodes. If not\n\t\tset, the default of 4 is used. The tabsize is set per document. Setting\n\t\tthe tabsize to 0 disables row/column tracking.\n\n\t\tNote that row and column tracking is not supported when using operator>>.\n\n\t\tThe tab size needs to be enabled before the parse or load. Correct usage:\n\t\t@verbatim\n\t\tTiXmlDocument doc;\n\t\tdoc.SetTabSize( 8 );\n\t\tdoc.Load( \"myfile.xml\" );\n\t\t@endverbatim\n\n\t\t@sa Row, Column\n\t*/\n\tvoid SetTabSize( int _tabsize )\t\t{ tabsize = _tabsize; }\n\n\tint TabSize() const\t{ return tabsize; }\n\n\t/** If you have handled the error, it can be reset with this call. The error\n\t\tstate is automatically cleared if you Parse a new XML block.\n\t*/\n\tvoid ClearError()\t\t\t\t\t\t{\terror = false; \n\t\t\t\t\t\t\t\t\t\t\t\terrorId = 0; \n\t\t\t\t\t\t\t\t\t\t\t\terrorDesc = \"\"; \n\t\t\t\t\t\t\t\t\t\t\t\terrorLocation.row = errorLocation.col = 0; \n\t\t\t\t\t\t\t\t\t\t\t\t//errorLocation.last = 0; \n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t/** Write the document to standard out using formatted printing (\"pretty print\"). */\n\tvoid Print() const\t\t\t\t\t\t{ Print( stdout, 0 ); }\n\n\t/* Write the document to a string using formatted printing (\"pretty print\"). This\n\t\twill allocate a character array (new char[]) and return it as a pointer. The\n\t\tcalling code pust call delete[] on the return char* to avoid a memory leak.\n\t*/\n\t//char* PrintToMemory() const; \n\n\t/// Print this Document to a FILE stream.\n\tvirtual void Print( FILE* cfile, int depth = 0 ) const;\n\t// [internal use]\n\tvoid SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding );\n\n\tvirtual const TiXmlDocument*    ToDocument()    const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\tvirtual TiXmlDocument*          ToDocument()          { return this; } ///< Cast to a more defined type. Will return null not of the requested type.\n\n\t/** Walk the XML tree visiting this node and all of its children. \n\t*/\n\tvirtual bool Accept( TiXmlVisitor* content ) const;\n\nprotected :\n\t// [internal use]\n\tvirtual TiXmlNode* Clone() const;\n\t#ifdef TIXML_USE_STL\n\tvirtual void StreamIn( std::istream * in, TIXML_STRING * tag );\n\t#endif\n\nprivate:\n\tvoid CopyTo( TiXmlDocument* target ) const;\n\n\tbool error;\n\tint  errorId;\n\tTIXML_STRING errorDesc;\n\tint tabsize;\n\tTiXmlCursor errorLocation;\n\tbool useMicrosoftBOM;\t\t// the UTF-8 BOM were found when read. Note this, and try to write.\n};\n\n\n/**\n\tA TiXmlHandle is a class that wraps a node pointer with null checks; this is\n\tan incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml\n\tDOM structure. It is a separate utility class.\n\n\tTake an example:\n\t@verbatim\n\t<Document>\n\t\t<Element attributeA = \"valueA\">\n\t\t\t<Child attributeB = \"value1\" />\n\t\t\t<Child attributeB = \"value2\" />\n\t\t</Element>\n\t<Document>\n\t@endverbatim\n\n\tAssuming you want the value of \"attributeB\" in the 2nd \"Child\" element, it's very \n\teasy to write a *lot* of code that looks like:\n\n\t@verbatim\n\tTiXmlElement* root = document.FirstChildElement( \"Document\" );\n\tif ( root )\n\t{\n\t\tTiXmlElement* element = root->FirstChildElement( \"Element\" );\n\t\tif ( element )\n\t\t{\n\t\t\tTiXmlElement* child = element->FirstChildElement( \"Child\" );\n\t\t\tif ( child )\n\t\t\t{\n\t\t\t\tTiXmlElement* child2 = child->NextSiblingElement( \"Child\" );\n\t\t\t\tif ( child2 )\n\t\t\t\t{\n\t\t\t\t\t// Finally do something useful.\n\t@endverbatim\n\n\tAnd that doesn't even cover \"else\" cases. TiXmlHandle addresses the verbosity\n\tof such code. A TiXmlHandle checks for null\tpointers so it is perfectly safe \n\tand correct to use:\n\n\t@verbatim\n\tTiXmlHandle docHandle( &document );\n\tTiXmlElement* child2 = docHandle.FirstChild( \"Document\" ).FirstChild( \"Element\" ).Child( \"Child\", 1 ).ToElement();\n\tif ( child2 )\n\t{\n\t\t// do something useful\n\t@endverbatim\n\n\tWhich is MUCH more concise and useful.\n\n\tIt is also safe to copy handles - internally they are nothing more than node pointers.\n\t@verbatim\n\tTiXmlHandle handleCopy = handle;\n\t@endverbatim\n\n\tWhat they should not be used for is iteration:\n\n\t@verbatim\n\tint i=0; \n\twhile ( true )\n\t{\n\t\tTiXmlElement* child = docHandle.FirstChild( \"Document\" ).FirstChild( \"Element\" ).Child( \"Child\", i ).ToElement();\n\t\tif ( !child )\n\t\t\tbreak;\n\t\t// do something\n\t\t++i;\n\t}\n\t@endverbatim\n\n\tIt seems reasonable, but it is in fact two embedded while loops. The Child method is \n\ta linear walk to find the element, so this code would iterate much more than it needs \n\tto. Instead, prefer:\n\n\t@verbatim\n\tTiXmlElement* child = docHandle.FirstChild( \"Document\" ).FirstChild( \"Element\" ).FirstChild( \"Child\" ).ToElement();\n\n\tfor( child; child; child=child->NextSiblingElement() )\n\t{\n\t\t// do something\n\t}\n\t@endverbatim\n*/\nclass TiXmlHandle\n{\npublic:\n\t/// Create a handle from any node (at any depth of the tree.) This can be a null pointer.\n\tTiXmlHandle( TiXmlNode* _node )\t\t\t\t\t{ this->node = _node; }\n\t/// Copy constructor\n\tTiXmlHandle( const TiXmlHandle& ref )\t\t\t{ this->node = ref.node; }\n\tTiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; }\n\n\t/// Return a handle to the first child node.\n\tTiXmlHandle FirstChild() const;\n\t/// Return a handle to the first child node with the given name.\n\tTiXmlHandle FirstChild( const char * value ) const;\n\t/// Return a handle to the first child element.\n\tTiXmlHandle FirstChildElement() const;\n\t/// Return a handle to the first child element with the given name.\n\tTiXmlHandle FirstChildElement( const char * value ) const;\n\n\t/** Return a handle to the \"index\" child with the given name. \n\t\tThe first child is 0, the second 1, etc.\n\t*/\n\tTiXmlHandle Child( const char* value, int index ) const;\n\t/** Return a handle to the \"index\" child. \n\t\tThe first child is 0, the second 1, etc.\n\t*/\n\tTiXmlHandle Child( int index ) const;\n\t/** Return a handle to the \"index\" child element with the given name. \n\t\tThe first child element is 0, the second 1, etc. Note that only TiXmlElements\n\t\tare indexed: other types are not counted.\n\t*/\n\tTiXmlHandle ChildElement( const char* value, int index ) const;\n\t/** Return a handle to the \"index\" child element. \n\t\tThe first child element is 0, the second 1, etc. Note that only TiXmlElements\n\t\tare indexed: other types are not counted.\n\t*/\n\tTiXmlHandle ChildElement( int index ) const;\n\n\t#ifdef TIXML_USE_STL\n\tTiXmlHandle FirstChild( const std::string& _value ) const\t\t\t\t{ return FirstChild( _value.c_str() ); }\n\tTiXmlHandle FirstChildElement( const std::string& _value ) const\t\t{ return FirstChildElement( _value.c_str() ); }\n\n\tTiXmlHandle Child( const std::string& _value, int index ) const\t\t\t{ return Child( _value.c_str(), index ); }\n\tTiXmlHandle ChildElement( const std::string& _value, int index ) const\t{ return ChildElement( _value.c_str(), index ); }\n\t#endif\n\n\t/** Return the handle as a TiXmlNode. This may return null.\n\t*/\n\tTiXmlNode* ToNode() const\t\t\t{ return node; } \n\t/** Return the handle as a TiXmlElement. This may return null.\n\t*/\n\tTiXmlElement* ToElement() const\t\t{ return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); }\n\t/**\tReturn the handle as a TiXmlText. This may return null.\n\t*/\n\tTiXmlText* ToText() const\t\t\t{ return ( ( node && node->ToText() ) ? node->ToText() : 0 ); }\n\t/** Return the handle as a TiXmlUnknown. This may return null.\n\t*/\n\tTiXmlUnknown* ToUnknown() const\t\t{ return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); }\n\n\t/** @deprecated use ToNode. \n\t\tReturn the handle as a TiXmlNode. This may return null.\n\t*/\n\tTiXmlNode* Node() const\t\t\t{ return ToNode(); } \n\t/** @deprecated use ToElement. \n\t\tReturn the handle as a TiXmlElement. This may return null.\n\t*/\n\tTiXmlElement* Element() const\t{ return ToElement(); }\n\t/**\t@deprecated use ToText()\n\t\tReturn the handle as a TiXmlText. This may return null.\n\t*/\n\tTiXmlText* Text() const\t\t\t{ return ToText(); }\n\t/** @deprecated use ToUnknown()\n\t\tReturn the handle as a TiXmlUnknown. This may return null.\n\t*/\n\tTiXmlUnknown* Unknown() const\t{ return ToUnknown(); }\n\nprivate:\n\tTiXmlNode* node;\n};\n\n\n/** Print to memory functionality. The TiXmlPrinter is useful when you need to:\n\n\t-# Print to memory (especially in non-STL mode)\n\t-# Control formatting (line endings, etc.)\n\n\tWhen constructed, the TiXmlPrinter is in its default \"pretty printing\" mode.\n\tBefore calling Accept() you can call methods to control the printing\n\tof the XML document. After TiXmlNode::Accept() is called, the printed document can\n\tbe accessed via the CStr(), Str(), and Size() methods.\n\n\tTiXmlPrinter uses the Visitor API.\n\t@verbatim\n\tTiXmlPrinter printer;\n\tprinter.SetIndent( \"\\t\" );\n\n\tdoc.Accept( &printer );\n\tfprintf( stdout, \"%s\", printer.CStr() );\n\t@endverbatim\n*/\nclass TiXmlPrinter : public TiXmlVisitor\n{\npublic:\n\tTiXmlPrinter() : depth( 0 ), simpleTextPrint( false ),\n\t\t\t\t\t buffer(), indent( \"    \" ), lineBreak( \"\\n\" ) {}\n\n\tvirtual bool VisitEnter( const TiXmlDocument& doc );\n\tvirtual bool VisitExit( const TiXmlDocument& doc );\n\n\tvirtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute );\n\tvirtual bool VisitExit( const TiXmlElement& element );\n\n\tvirtual bool Visit( const TiXmlDeclaration& declaration );\n\tvirtual bool Visit( const TiXmlText& text );\n\tvirtual bool Visit( const TiXmlComment& comment );\n\tvirtual bool Visit( const TiXmlUnknown& unknown );\n\n\t/** Set the indent characters for printing. By default 4 spaces\n\t\tbut tab (\\t) is also useful, or null/empty string for no indentation.\n\t*/\n\tvoid SetIndent( const char* _indent )\t\t\t{ indent = _indent ? _indent : \"\" ; }\n\t/// Query the indention string.\n\tconst char* Indent()\t\t\t\t\t\t\t{ return indent.c_str(); }\n\t/** Set the line breaking string. By default set to newline (\\n). \n\t\tSome operating systems prefer other characters, or can be\n\t\tset to the null/empty string for no indenation.\n\t*/\n\tvoid SetLineBreak( const char* _lineBreak )\t\t{ lineBreak = _lineBreak ? _lineBreak : \"\"; }\n\t/// Query the current line breaking string.\n\tconst char* LineBreak()\t\t\t\t\t\t\t{ return lineBreak.c_str(); }\n\n\t/** Switch over to \"stream printing\" which is the most dense formatting without \n\t\tlinebreaks. Common when the XML is needed for network transmission.\n\t*/\n\tvoid SetStreamPrinting()\t\t\t\t\t\t{ indent = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t\t  lineBreak = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\t\n\t/// Return the result.\n\tconst char* CStr()\t\t\t\t\t\t\t\t{ return buffer.c_str(); }\n\t/// Return the length of the result string.\n\tsize_t Size()\t\t\t\t\t\t\t\t\t{ return buffer.size(); }\n\n\t#ifdef TIXML_USE_STL\n\t/// Return the result.\n\tconst std::string& Str()\t\t\t\t\t\t{ return buffer; }\n\t#endif\n\nprivate:\n\tvoid DoIndent()\t{\n\t\tfor( int i=0; i<depth; ++i )\n\t\t\tbuffer += indent;\n\t}\n\tvoid DoLineBreak() {\n\t\tbuffer += lineBreak;\n\t}\n\n\tint depth;\n\tbool simpleTextPrint;\n\tTIXML_STRING buffer;\n\tTIXML_STRING indent;\n\tTIXML_STRING lineBreak;\n};\n\n\n#ifdef _MSC_VER\n#pragma warning( pop )\n#endif\n\n#endif\n"
  },
  {
    "path": "src/tinyxml/tinyxmlerror.cpp",
    "content": "/*\nwww.sourceforge.net/projects/tinyxml\nOriginal code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)\n\nThis software is provided 'as-is', without any express or implied \nwarranty. In no event will the authors be held liable for any \ndamages arising from the use of this software.\n\nPermission is granted to anyone to use this software for any \npurpose, including commercial applications, and to alter it and \nredistribute it freely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must\nnot claim that you wrote the original software. If you use this\nsoftware in a product, an acknowledgment in the product documentation\nwould be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and\nmust not be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source\ndistribution.\n*/\n\n#include \"tinyxml.h\"\n\n// The goal of the separate error file is to make the first\n// step towards localization. tinyxml (currently) only supports\n// english error messages, but the could now be translated.\n//\n// It also cleans up the code a bit.\n//\n\nconst char* TiXmlBase::errorString[ TiXmlBase::TIXML_ERROR_STRING_COUNT ] =\n{\n\t\"No error\",\n\t\"Error\",\n\t\"Failed to open file\",\n\t\"Error parsing Element.\",\n\t\"Failed to read Element name\",\n\t\"Error reading Element value.\",\n\t\"Error reading Attributes.\",\n\t\"Error: empty tag.\",\n\t\"Error reading end tag.\",\n\t\"Error parsing Unknown.\",\n\t\"Error parsing Comment.\",\n\t\"Error parsing Declaration.\",\n\t\"Error document empty.\",\n\t\"Error null (0) or unexpected EOF found in input stream.\",\n\t\"Error parsing CDATA.\",\n\t\"Error when TiXmlDocument added to document, because TiXmlDocument can only be at the root.\",\n};\n"
  },
  {
    "path": "src/tinyxml/tinyxmlparser.cpp",
    "content": "/*\nwww.sourceforge.net/projects/tinyxml\nOriginal code by Lee Thomason (www.grinninglizard.com)\n\nThis software is provided 'as-is', without any express or implied \nwarranty. In no event will the authors be held liable for any \ndamages arising from the use of this software.\n\nPermission is granted to anyone to use this software for any \npurpose, including commercial applications, and to alter it and \nredistribute it freely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must \nnot claim that you wrote the original software. If you use this\nsoftware in a product, an acknowledgment in the product documentation\nwould be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and \nmust not be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source \ndistribution.\n*/\n\n#include <ctype.h>\n#include <stddef.h>\n\n#include \"tinyxml.h\"\n\n//#define DEBUG_PARSER\n#if defined( DEBUG_PARSER )\n#\tif defined( DEBUG ) && defined( _MSC_VER )\n#\t\tinclude <windows.h>\n#\t\tdefine TIXML_LOG OutputDebugString\n#\telse\n#\t\tdefine TIXML_LOG printf\n#\tendif\n#endif\n\n// Note that \"PutString\" hardcodes the same list. This\n// is less flexible than it appears. Changing the entries\n// or order will break putstring.\t\nTiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = \n{\n\t{ \"&amp;\",  5, '&' },\n\t{ \"&lt;\",   4, '<' },\n\t{ \"&gt;\",   4, '>' },\n\t{ \"&quot;\", 6, '\\\"' },\n\t{ \"&apos;\", 6, '\\'' }\n};\n\n// Bunch of unicode info at:\n//\t\thttp://www.unicode.org/faq/utf_bom.html\n// Including the basic of this table, which determines the #bytes in the\n// sequence from the lead byte. 1 placed for invalid sequences --\n// although the result will be junk, pass it through as much as possible.\n// Beware of the non-characters in UTF-8:\t\n//\t\t\t\tef bb bf (Microsoft \"lead bytes\")\n//\t\t\t\tef bf be\n//\t\t\t\tef bf bf \n\nconst unsigned char TIXML_UTF_LEAD_0 = 0xefU;\nconst unsigned char TIXML_UTF_LEAD_1 = 0xbbU;\nconst unsigned char TIXML_UTF_LEAD_2 = 0xbfU;\n\nconst int TiXmlBase::utf8ByteTable[256] = \n{\n\t//\t0\t1\t2\t3\t4\t5\t6\t7\t8\t9\ta\tb\tc\td\te\tf\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x00\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x10\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x20\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x30\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x40\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x50\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x60\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x70\tEnd of ASCII range\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x80 0x80 to 0xc1 invalid\n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0x90 \n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0xa0 \n\t\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t// 0xb0 \n\t\t1,\t1,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t// 0xc0 0xc2 to 0xdf 2 byte\n\t\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t2,\t// 0xd0\n\t\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t3,\t// 0xe0 0xe0 to 0xef 3 byte\n\t\t4,\t4,\t4,\t4,\t4,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1,\t1\t// 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid\n};\n\n\nvoid TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length )\n{\n\tconst unsigned long BYTE_MASK = 0xBF;\n\tconst unsigned long BYTE_MARK = 0x80;\n\tconst unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };\n\n\tif (input < 0x80) \n\t\t*length = 1;\n\telse if ( input < 0x800 )\n\t\t*length = 2;\n\telse if ( input < 0x10000 )\n\t\t*length = 3;\n\telse if ( input < 0x200000 )\n\t\t*length = 4;\n\telse\n\t\t{ *length = 0; return; }\t// This code won't covert this correctly anyway.\n\n\toutput += *length;\n\n\t// Scary scary fall throughs.\n\tswitch (*length) \n\t{\n\t\tcase 4:\n\t\t\t--output; \n\t\t\t*output = (char)((input | BYTE_MARK) & BYTE_MASK); \n\t\t\tinput >>= 6;\n\t\tcase 3:\n\t\t\t--output; \n\t\t\t*output = (char)((input | BYTE_MARK) & BYTE_MASK); \n\t\t\tinput >>= 6;\n\t\tcase 2:\n\t\t\t--output; \n\t\t\t*output = (char)((input | BYTE_MARK) & BYTE_MASK); \n\t\t\tinput >>= 6;\n\t\tcase 1:\n\t\t\t--output; \n\t\t\t*output = (char)(input | FIRST_BYTE_MARK[*length]);\n\t}\n}\n\n\n/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ )\n{\n\t// This will only work for low-ascii, everything else is assumed to be a valid\n\t// letter. I'm not sure this is the best approach, but it is quite tricky trying\n\t// to figure out alphabetical vs. not across encoding. So take a very \n\t// conservative approach.\n\n//\tif ( encoding == TIXML_ENCODING_UTF8 )\n//\t{\n\t\tif ( anyByte < 127 )\n\t\t\treturn isalpha( anyByte );\n\t\telse\n\t\t\treturn 1;\t// What else to do? The unicode set is huge...get the english ones right.\n//\t}\n//\telse\n//\t{\n//\t\treturn isalpha( anyByte );\n//\t}\n}\n\n\n/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ )\n{\n\t// This will only work for low-ascii, everything else is assumed to be a valid\n\t// letter. I'm not sure this is the best approach, but it is quite tricky trying\n\t// to figure out alphabetical vs. not across encoding. So take a very \n\t// conservative approach.\n\n//\tif ( encoding == TIXML_ENCODING_UTF8 )\n//\t{\n\t\tif ( anyByte < 127 )\n\t\t\treturn isalnum( anyByte );\n\t\telse\n\t\t\treturn 1;\t// What else to do? The unicode set is huge...get the english ones right.\n//\t}\n//\telse\n//\t{\n//\t\treturn isalnum( anyByte );\n//\t}\n}\n\n\nclass TiXmlParsingData\n{\n\tfriend class TiXmlDocument;\n  public:\n\tvoid Stamp( const char* now, TiXmlEncoding encoding );\n\n\tconst TiXmlCursor& Cursor() const\t{ return cursor; }\n\n  private:\n\t// Only used by the document!\n\tTiXmlParsingData( const char* start, int _tabsize, int row, int col )\n\t{\n\t\tassert( start );\n\t\tstamp = start;\n\t\ttabsize = _tabsize;\n\t\tcursor.row = row;\n\t\tcursor.col = col;\n\t}\n\n\tTiXmlCursor\t\tcursor;\n\tconst char*\t\tstamp;\n\tint\t\t\t\ttabsize;\n};\n\n\nvoid TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding )\n{\n\tassert( now );\n\n\t// Do nothing if the tabsize is 0.\n\tif ( tabsize < 1 )\n\t{\n\t\treturn;\n\t}\n\n\t// Get the current row, column.\n\tint row = cursor.row;\n\tint col = cursor.col;\n\tconst char* p = stamp;\n\tassert( p );\n\n\twhile ( p < now )\n\t{\n\t\t// Treat p as unsigned, so we have a happy compiler.\n\t\tconst unsigned char* pU = (const unsigned char*)p;\n\n\t\t// Code contributed by Fletcher Dunn: (modified by lee)\n\t\tswitch (*pU) {\n\t\t\tcase 0:\n\t\t\t\t// We *should* never get here, but in case we do, don't\n\t\t\t\t// advance past the terminating null character, ever\n\t\t\t\treturn;\n\n\t\t\tcase '\\r':\n\t\t\t\t// bump down to the next line\n\t\t\t\t++row;\n\t\t\t\tcol = 0;\t\t\t\t\n\t\t\t\t// Eat the character\n\t\t\t\t++p;\n\n\t\t\t\t// Check for \\r\\n sequence, and treat this as a single character\n\t\t\t\tif (*p == '\\n') {\n\t\t\t\t\t++p;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase '\\n':\n\t\t\t\t// bump down to the next line\n\t\t\t\t++row;\n\t\t\t\tcol = 0;\n\n\t\t\t\t// Eat the character\n\t\t\t\t++p;\n\n\t\t\t\t// Check for \\n\\r sequence, and treat this as a single\n\t\t\t\t// character.  (Yes, this bizarre thing does occur still\n\t\t\t\t// on some arcane platforms...)\n\t\t\t\tif (*p == '\\r') {\n\t\t\t\t\t++p;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase '\\t':\n\t\t\t\t// Eat the character\n\t\t\t\t++p;\n\n\t\t\t\t// Skip to next tab stop\n\t\t\t\tcol = (col / tabsize + 1) * tabsize;\n\t\t\t\tbreak;\n\n\t\t\tcase TIXML_UTF_LEAD_0:\n\t\t\t\tif ( encoding == TIXML_ENCODING_UTF8 )\n\t\t\t\t{\n\t\t\t\t\tif ( *(p+1) && *(p+2) )\n\t\t\t\t\t{\n\t\t\t\t\t\t// In these cases, don't advance the column. These are\n\t\t\t\t\t\t// 0-width spaces.\n\t\t\t\t\t\tif ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 )\n\t\t\t\t\t\t\tp += 3;\t\n\t\t\t\t\t\telse if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU )\n\t\t\t\t\t\t\tp += 3;\t\n\t\t\t\t\t\telse if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU )\n\t\t\t\t\t\t\tp += 3;\t\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{ p +=3; ++col; }\t// A normal character.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++p;\n\t\t\t\t\t++col;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tif ( encoding == TIXML_ENCODING_UTF8 )\n\t\t\t\t{\n\t\t\t\t\t// Eat the 1 to 4 byte utf8 character.\n\t\t\t\t\tint step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)];\n\t\t\t\t\tif ( step == 0 )\n\t\t\t\t\t\tstep = 1;\t\t// Error case from bad encoding, but handle gracefully.\n\t\t\t\t\tp += step;\n\n\t\t\t\t\t// Just advance one column, of course.\n\t\t\t\t\t++col;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++p;\n\t\t\t\t\t++col;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tcursor.row = row;\n\tcursor.col = col;\n\tassert( cursor.row >= -1 );\n\tassert( cursor.col >= -1 );\n\tstamp = p;\n\tassert( stamp );\n}\n\n\nconst char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding )\n{\n\tif ( !p || !*p )\n\t{\n\t\treturn 0;\n\t}\n\tif ( encoding == TIXML_ENCODING_UTF8 )\n\t{\n\t\twhile ( *p )\n\t\t{\n\t\t\tconst unsigned char* pU = (const unsigned char*)p;\n\t\t\t\n\t\t\t// Skip the stupid Microsoft UTF-8 Byte order marks\n\t\t\tif (\t*(pU+0)==TIXML_UTF_LEAD_0\n\t\t\t\t && *(pU+1)==TIXML_UTF_LEAD_1 \n\t\t\t\t && *(pU+2)==TIXML_UTF_LEAD_2 )\n\t\t\t{\n\t\t\t\tp += 3;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if(*(pU+0)==TIXML_UTF_LEAD_0\n\t\t\t\t && *(pU+1)==0xbfU\n\t\t\t\t && *(pU+2)==0xbeU )\n\t\t\t{\n\t\t\t\tp += 3;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if(*(pU+0)==TIXML_UTF_LEAD_0\n\t\t\t\t && *(pU+1)==0xbfU\n\t\t\t\t && *(pU+2)==0xbfU )\n\t\t\t{\n\t\t\t\tp += 3;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( IsWhiteSpace( *p ) )\t\t// Still using old rules for white space.\n\t\t\t\t++p;\n\t\t\telse\n\t\t\t\tbreak;\n\t\t}\n\t}\n\telse\n\t{\n\t\twhile ( *p && IsWhiteSpace( *p ) )\n\t\t\t++p;\n\t}\n\n\treturn p;\n}\n\n#ifdef TIXML_USE_STL\n/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag )\n{\n\tfor( ;; )\n\t{\n\t\tif ( !in->good() ) return false;\n\n\t\tint c = in->peek();\n\t\t// At this scope, we can't get to a document. So fail silently.\n\t\tif ( !IsWhiteSpace( c ) || c <= 0 )\n\t\t\treturn true;\n\n\t\t*tag += (char) in->get();\n\t}\n}\n\n/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag )\n{\n\t//assert( character > 0 && character < 128 );\t// else it won't work in utf-8\n\twhile ( in->good() )\n\t{\n\t\tint c = in->peek();\n\t\tif ( c == character )\n\t\t\treturn true;\n\t\tif ( c <= 0 )\t\t// Silent failure: can't get document at this scope\n\t\t\treturn false;\n\n\t\tin->get();\n\t\t*tag += (char) c;\n\t}\n\treturn false;\n}\n#endif\n\n// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The\n// \"assign\" optimization removes over 10% of the execution time.\n//\nconst char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding )\n{\n\t// Oddly, not supported on some compilers,\n\t//name->clear();\n\t// So use this:\n\t*name = \"\";\n\tassert( p );\n\n\t// Names start with letters or underscores.\n\t// Of course, in unicode, tinyxml has no idea what a letter *is*. The\n\t// algorithm is generous.\n\t//\n\t// After that, they can be letters, underscores, numbers,\n\t// hyphens, or colons. (Colons are valid only for namespaces,\n\t// but tinyxml can't tell namespaces from names.)\n\tif (    p && *p \n\t\t && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) )\n\t{\n\t\tconst char* start = p;\n\t\twhile(\t\tp && *p\n\t\t\t\t&&\t(\t\tIsAlphaNum( (unsigned char ) *p, encoding ) \n\t\t\t\t\t\t || *p == '_'\n\t\t\t\t\t\t || *p == '-'\n\t\t\t\t\t\t || *p == '.'\n\t\t\t\t\t\t || *p == ':' ) )\n\t\t{\n\t\t\t//(*name) += *p; // expensive\n\t\t\t++p;\n\t\t}\n\t\tif ( p-start > 0 ) {\n\t\t\tname->assign( start, p-start );\n\t\t}\n\t\treturn p;\n\t}\n\treturn 0;\n}\n\nconst char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding )\n{\n\t// Presume an entity, and pull it out.\n    TIXML_STRING ent;\n\tint i;\n\t*length = 0;\n\n\tif ( *(p+1) && *(p+1) == '#' && *(p+2) )\n\t{\n\t\tunsigned long ucs = 0;\n\t\tptrdiff_t delta = 0;\n\t\tunsigned mult = 1;\n\n\t\tif ( *(p+2) == 'x' )\n\t\t{\n\t\t\t// Hexadecimal.\n\t\t\tif ( !*(p+3) ) return 0;\n\n\t\t\tconst char* q = p+3;\n\t\t\tq = strchr( q, ';' );\n\n\t\t\tif ( !q || !*q ) return 0;\n\n\t\t\tdelta = q-p;\n\t\t\t--q;\n\n\t\t\twhile ( *q != 'x' )\n\t\t\t{\n\t\t\t\tif ( *q >= '0' && *q <= '9' )\n\t\t\t\t\tucs += mult * (*q - '0');\n\t\t\t\telse if ( *q >= 'a' && *q <= 'f' )\n\t\t\t\t\tucs += mult * (*q - 'a' + 10);\n\t\t\t\telse if ( *q >= 'A' && *q <= 'F' )\n\t\t\t\t\tucs += mult * (*q - 'A' + 10 );\n\t\t\t\telse \n\t\t\t\t\treturn 0;\n\t\t\t\tmult *= 16;\n\t\t\t\t--q;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Decimal.\n\t\t\tif ( !*(p+2) ) return 0;\n\n\t\t\tconst char* q = p+2;\n\t\t\tq = strchr( q, ';' );\n\n\t\t\tif ( !q || !*q ) return 0;\n\n\t\t\tdelta = q-p;\n\t\t\t--q;\n\n\t\t\twhile ( *q != '#' )\n\t\t\t{\n\t\t\t\tif ( *q >= '0' && *q <= '9' )\n\t\t\t\t\tucs += mult * (*q - '0');\n\t\t\t\telse \n\t\t\t\t\treturn 0;\n\t\t\t\tmult *= 10;\n\t\t\t\t--q;\n\t\t\t}\n\t\t}\n\t\tif ( encoding == TIXML_ENCODING_UTF8 )\n\t\t{\n\t\t\t// convert the UCS to UTF-8\n\t\t\tConvertUTF32ToUTF8( ucs, value, length );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t*value = (char)ucs;\n\t\t\t*length = 1;\n\t\t}\n\t\treturn p + delta + 1;\n\t}\n\n\t// Now try to match it.\n\tfor( i=0; i<NUM_ENTITY; ++i )\n\t{\n\t\tif ( strncmp( entity[i].str, p, entity[i].strLength ) == 0 )\n\t\t{\n\t\t\tassert( strlen( entity[i].str ) == entity[i].strLength );\n\t\t\t*value = entity[i].chr;\n\t\t\t*length = 1;\n\t\t\treturn ( p + entity[i].strLength );\n\t\t}\n\t}\n\n\t// So it wasn't an entity, its unrecognized, or something like that.\n\t*value = *p;\t// Don't put back the last one, since we return it!\n\t//*length = 1;\t// Leave unrecognized entities - this doesn't really work.\n\t\t\t\t\t// Just writes strange XML.\n\treturn p+1;\n}\n\n\nbool TiXmlBase::StringEqual( const char* p,\n\t\t\t\t\t\t\t const char* tag,\n\t\t\t\t\t\t\t bool ignoreCase,\n\t\t\t\t\t\t\t TiXmlEncoding encoding )\n{\n\tassert( p );\n\tassert( tag );\n\tif ( !p || !*p )\n\t{\n\t\tassert( 0 );\n\t\treturn false;\n\t}\n\n\tconst char* q = p;\n\n\tif ( ignoreCase )\n\t{\n\t\twhile ( *q && *tag && ToLower( *q, encoding ) == ToLower( *tag, encoding ) )\n\t\t{\n\t\t\t++q;\n\t\t\t++tag;\n\t\t}\n\n\t\tif ( *tag == 0 )\n\t\t\treturn true;\n\t}\n\telse\n\t{\n\t\twhile ( *q && *tag && *q == *tag )\n\t\t{\n\t\t\t++q;\n\t\t\t++tag;\n\t\t}\n\n\t\tif ( *tag == 0 )\t\t// Have we found the end of the tag, and everything equal?\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nconst char* TiXmlBase::ReadText(\tconst char* p, \n\t\t\t\t\t\t\t\t\tTIXML_STRING * text, \n\t\t\t\t\t\t\t\t\tbool trimWhiteSpace, \n\t\t\t\t\t\t\t\t\tconst char* endTag, \n\t\t\t\t\t\t\t\t\tbool caseInsensitive,\n\t\t\t\t\t\t\t\t\tTiXmlEncoding encoding )\n{\n    *text = \"\";\n\tif (    !trimWhiteSpace\t\t\t// certain tags always keep whitespace\n\t\t || !condenseWhiteSpace )\t// if true, whitespace is always kept\n\t{\n\t\t// Keep all the white space.\n\t\twhile (\t   p && *p\n\t\t\t\t&& !StringEqual( p, endTag, caseInsensitive, encoding )\n\t\t\t  )\n\t\t{\n\t\t\tint len;\n\t\t\tchar cArr[4] = { 0, 0, 0, 0 };\n\t\t\tp = GetChar( p, cArr, &len, encoding );\n\t\t\ttext->append( cArr, len );\n\t\t}\n\t}\n\telse\n\t{\n\t\tbool whitespace = false;\n\n\t\t// Remove leading white space:\n\t\tp = SkipWhiteSpace( p, encoding );\n\t\twhile (\t   p && *p\n\t\t\t\t&& !StringEqual( p, endTag, caseInsensitive, encoding ) )\n\t\t{\n\t\t\tif ( *p == '\\r' || *p == '\\n' )\n\t\t\t{\n\t\t\t\twhitespace = true;\n\t\t\t\t++p;\n\t\t\t}\n\t\t\telse if ( IsWhiteSpace( *p ) )\n\t\t\t{\n\t\t\t\twhitespace = true;\n\t\t\t\t++p;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// If we've found whitespace, add it before the\n\t\t\t\t// new character. Any whitespace just becomes a space.\n\t\t\t\tif ( whitespace )\n\t\t\t\t{\n\t\t\t\t\t(*text) += ' ';\n\t\t\t\t\twhitespace = false;\n\t\t\t\t}\n\t\t\t\tint len;\n\t\t\t\tchar cArr[4] = { 0, 0, 0, 0 };\n\t\t\t\tp = GetChar( p, cArr, &len, encoding );\n\t\t\t\tif ( len == 1 )\n\t\t\t\t\t(*text) += cArr[0];\t// more efficient\n\t\t\t\telse\n\t\t\t\t\ttext->append( cArr, len );\n\t\t\t}\n\t\t}\n\t}\n\tif ( p && *p )\n\t\tp += strlen( endTag );\n\treturn ( p && *p ) ? p : 0;\n}\n\n#ifdef TIXML_USE_STL\n\nvoid TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag )\n{\n\t// The basic issue with a document is that we don't know what we're\n\t// streaming. Read something presumed to be a tag (and hope), then\n\t// identify it, and call the appropriate stream method on the tag.\n\t//\n\t// This \"pre-streaming\" will never read the closing \">\" so the\n\t// sub-tag can orient itself.\n\n\tif ( !StreamTo( in, '<', tag ) ) \n\t{\n\t\tSetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn;\n\t}\n\n\twhile ( in->good() )\n\t{\n\t\tint tagIndex = (int) tag->length();\n\t\twhile ( in->good() && in->peek() != '>' )\n\t\t{\n\t\t\tint c = in->get();\n\t\t\tif ( c <= 0 )\n\t\t\t{\n\t\t\t\tSetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t(*tag) += (char) c;\n\t\t}\n\n\t\tif ( in->good() )\n\t\t{\n\t\t\t// We now have something we presume to be a node of \n\t\t\t// some sort. Identify it, and call the node to\n\t\t\t// continue streaming.\n\t\t\tTiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING );\n\n\t\t\tif ( node )\n\t\t\t{\n\t\t\t\tnode->StreamIn( in, tag );\n\t\t\t\tbool isElement = node->ToElement() != 0;\n\t\t\t\tdelete node;\n\t\t\t\tnode = 0;\n\n\t\t\t\t// If this is the root element, we're done. Parsing will be\n\t\t\t\t// done by the >> operator.\n\t\t\t\tif ( isElement )\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tSetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\t// We should have returned sooner.\n\tSetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN );\n}\n\n#endif\n\nconst char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding )\n{\n\tClearError();\n\n\t// Parse away, at the document level. Since a document\n\t// contains nothing but other tags, most of what happens\n\t// here is skipping white space.\n\tif ( !p || !*p )\n\t{\n\t\tSetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\n\t// Note that, for a document, this needs to come\n\t// before the while space skip, so that parsing\n\t// starts from the pointer we are given.\n\tlocation.Clear();\n\tif ( prevData )\n\t{\n\t\tlocation.row = prevData->cursor.row;\n\t\tlocation.col = prevData->cursor.col;\n\t}\n\telse\n\t{\n\t\tlocation.row = 0;\n\t\tlocation.col = 0;\n\t}\n\tTiXmlParsingData data( p, TabSize(), location.row, location.col );\n\tlocation = data.Cursor();\n\n\tif ( encoding == TIXML_ENCODING_UNKNOWN )\n\t{\n\t\t// Check for the Microsoft UTF-8 lead bytes.\n\t\tconst unsigned char* pU = (const unsigned char*)p;\n\t\tif (\t*(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0\n\t\t\t && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1\n\t\t\t && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 )\n\t\t{\n\t\t\tencoding = TIXML_ENCODING_UTF8;\n\t\t\tuseMicrosoftBOM = true;\n\t\t}\n\t}\n\n    p = SkipWhiteSpace( p, encoding );\n\tif ( !p )\n\t{\n\t\tSetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\treturn 0;\n\t}\n\n\twhile ( p && *p )\n\t{\n\t\tTiXmlNode* node = Identify( p, encoding );\n\t\tif ( node )\n\t\t{\n\t\t\tp = node->Parse( p, &data, encoding );\n\t\t\tLinkEndChild( node );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\t// Did we get encoding info?\n\t\tif (    encoding == TIXML_ENCODING_UNKNOWN\n\t\t\t && node->ToDeclaration() )\n\t\t{\n\t\t\tTiXmlDeclaration* dec = node->ToDeclaration();\n\t\t\tconst char* enc = dec->Encoding();\n\t\t\tassert( enc );\n\n\t\t\tif ( *enc == 0 )\n\t\t\t\tencoding = TIXML_ENCODING_UTF8;\n\t\t\telse if ( StringEqual( enc, \"UTF-8\", true, TIXML_ENCODING_UNKNOWN ) )\n\t\t\t\tencoding = TIXML_ENCODING_UTF8;\n\t\t\telse if ( StringEqual( enc, \"UTF8\", true, TIXML_ENCODING_UNKNOWN ) )\n\t\t\t\tencoding = TIXML_ENCODING_UTF8;\t// incorrect, but be nice\n\t\t\telse \n\t\t\t\tencoding = TIXML_ENCODING_LEGACY;\n\t\t}\n\n\t\tp = SkipWhiteSpace( p, encoding );\n\t}\n\n\t// Was this empty?\n\tif ( !firstChild ) {\n\t\tSetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding );\n\t\treturn 0;\n\t}\n\n\t// All is well.\n\treturn p;\n}\n\nvoid TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\t\n\t// The first error in a chain is more accurate - don't set again!\n\tif ( error )\n\t\treturn;\n\n\tassert( err > 0 && err < TIXML_ERROR_STRING_COUNT );\n\terror   = true;\n\terrorId = err;\n\terrorDesc = errorString[ errorId ];\n\n\terrorLocation.Clear();\n\tif ( pError && data )\n\t{\n\t\tdata->Stamp( pError, encoding );\n\t\terrorLocation = data->Cursor();\n\t}\n}\n\n\nTiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding )\n{\n\tTiXmlNode* returnNode = 0;\n\n\tp = SkipWhiteSpace( p, encoding );\n\tif( !p || !*p || *p != '<' )\n\t{\n\t\treturn 0;\n\t}\n\n\tp = SkipWhiteSpace( p, encoding );\n\n\tif ( !p || !*p )\n\t{\n\t\treturn 0;\n\t}\n\n\t// What is this thing? \n\t// - Elements start with a letter or underscore, but xml is reserved.\n\t// - Comments: <!--\n\t// - Declaration: <?xml\n\t// - Everything else is unknown to tinyxml.\n\t//\n\n\tconst char* xmlHeader = { \"<?xml\" };\n\tconst char* commentHeader = { \"<!--\" };\n\tconst char* dtdHeader = { \"<!\" };\n\tconst char* cdataHeader = { \"<![CDATA[\" };\n\n\tif ( StringEqual( p, xmlHeader, true, encoding ) )\n\t{\n\t\t#ifdef DEBUG_PARSER\n\t\t\tTIXML_LOG( \"XML parsing Declaration\\n\" );\n\t\t#endif\n\t\treturnNode = new TiXmlDeclaration();\n\t}\n\telse if ( StringEqual( p, commentHeader, false, encoding ) )\n\t{\n\t\t#ifdef DEBUG_PARSER\n\t\t\tTIXML_LOG( \"XML parsing Comment\\n\" );\n\t\t#endif\n\t\treturnNode = new TiXmlComment();\n\t}\n\telse if ( StringEqual( p, cdataHeader, false, encoding ) )\n\t{\n\t\t#ifdef DEBUG_PARSER\n\t\t\tTIXML_LOG( \"XML parsing CDATA\\n\" );\n\t\t#endif\n\t\tTiXmlText* text = new TiXmlText( \"\" );\n\t\ttext->SetCDATA( true );\n\t\treturnNode = text;\n\t}\n\telse if ( StringEqual( p, dtdHeader, false, encoding ) )\n\t{\n\t\t#ifdef DEBUG_PARSER\n\t\t\tTIXML_LOG( \"XML parsing Unknown(1)\\n\" );\n\t\t#endif\n\t\treturnNode = new TiXmlUnknown();\n\t}\n\telse if (    IsAlpha( *(p+1), encoding )\n\t\t\t  || *(p+1) == '_' )\n\t{\n\t\t#ifdef DEBUG_PARSER\n\t\t\tTIXML_LOG( \"XML parsing Element\\n\" );\n\t\t#endif\n\t\treturnNode = new TiXmlElement( \"\" );\n\t}\n\telse\n\t{\n\t\t#ifdef DEBUG_PARSER\n\t\t\tTIXML_LOG( \"XML parsing Unknown(2)\\n\" );\n\t\t#endif\n\t\treturnNode = new TiXmlUnknown();\n\t}\n\n\tif ( returnNode )\n\t{\n\t\t// Set the parent, so it can report errors\n\t\treturnNode->parent = this;\n\t}\n\treturn returnNode;\n}\n\n#ifdef TIXML_USE_STL\n\nvoid TiXmlElement::StreamIn (std::istream * in, TIXML_STRING * tag)\n{\n\t// We're called with some amount of pre-parsing. That is, some of \"this\"\n\t// element is in \"tag\". Go ahead and stream to the closing \">\"\n\twhile( in->good() )\n\t{\n\t\tint c = in->get();\n\t\tif ( c <= 0 )\n\t\t{\n\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\tif ( document )\n\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\treturn;\n\t\t}\n\t\t(*tag) += (char) c ;\n\t\t\n\t\tif ( c == '>' )\n\t\t\tbreak;\n\t}\n\n\tif ( tag->length() < 3 ) return;\n\n\t// Okay...if we are a \"/>\" tag, then we're done. We've read a complete tag.\n\t// If not, identify and stream.\n\n\tif (    tag->at( tag->length() - 1 ) == '>' \n\t\t && tag->at( tag->length() - 2 ) == '/' )\n\t{\n\t\t// All good!\n\t\treturn;\n\t}\n\telse if ( tag->at( tag->length() - 1 ) == '>' )\n\t{\n\t\t// There is more. Could be:\n\t\t//\t\ttext\n\t\t//\t\tcdata text (which looks like another node)\n\t\t//\t\tclosing tag\n\t\t//\t\tanother node.\n\t\tfor ( ;; )\n\t\t{\n\t\t\tStreamWhiteSpace( in, tag );\n\n\t\t\t// Do we have text?\n\t\t\tif ( in->good() && in->peek() != '<' ) \n\t\t\t{\n\t\t\t\t// Yep, text.\n\t\t\t\tTiXmlText text( \"\" );\n\t\t\t\ttext.StreamIn( in, tag );\n\n\t\t\t\t// What follows text is a closing tag or another node.\n\t\t\t\t// Go around again and figure it out.\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// We now have either a closing tag...or another node.\n\t\t\t// We should be at a \"<\", regardless.\n\t\t\tif ( !in->good() ) return;\n\t\t\tassert( in->peek() == '<' );\n\t\t\tint tagIndex = (int) tag->length();\n\n\t\t\tbool closingTag = false;\n\t\t\tbool firstCharFound = false;\n\n\t\t\tfor( ;; )\n\t\t\t{\n\t\t\t\tif ( !in->good() )\n\t\t\t\t\treturn;\n\n\t\t\t\tint c = in->peek();\n\t\t\t\tif ( c <= 0 )\n\t\t\t\t{\n\t\t\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\t\t\tif ( document )\n\t\t\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif ( c == '>' )\n\t\t\t\t\tbreak;\n\n\t\t\t\t*tag += (char) c;\n\t\t\t\tin->get();\n\n\t\t\t\t// Early out if we find the CDATA id.\n\t\t\t\tif ( c == '[' && tag->size() >= 9 )\n\t\t\t\t{\n\t\t\t\t\tsize_t len = tag->size();\n\t\t\t\t\tconst char* start = tag->c_str() + len - 9;\n\t\t\t\t\tif ( strcmp( start, \"<![CDATA[\" ) == 0 ) {\n\t\t\t\t\t\tassert( !closingTag );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( !firstCharFound && c != '<' && !IsWhiteSpace( c ) )\n\t\t\t\t{\n\t\t\t\t\tfirstCharFound = true;\n\t\t\t\t\tif ( c == '/' )\n\t\t\t\t\t\tclosingTag = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If it was a closing tag, then read in the closing '>' to clean up the input stream.\n\t\t\t// If it was not, the streaming will be done by the tag.\n\t\t\tif ( closingTag )\n\t\t\t{\n\t\t\t\tif ( !in->good() )\n\t\t\t\t\treturn;\n\n\t\t\t\tint c = in->get();\n\t\t\t\tif ( c <= 0 )\n\t\t\t\t{\n\t\t\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\t\t\tif ( document )\n\t\t\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tassert( c == '>' );\n\t\t\t\t*tag += (char) c;\n\n\t\t\t\t// We are done, once we've found our closing tag.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// If not a closing tag, id it, and stream.\n\t\t\t\tconst char* tagloc = tag->c_str() + tagIndex;\n\t\t\t\tTiXmlNode* node = Identify( tagloc, TIXML_DEFAULT_ENCODING );\n\t\t\t\tif ( !node )\n\t\t\t\t\treturn;\n\t\t\t\tnode->StreamIn( in, tag );\n\t\t\t\tdelete node;\n\t\t\t\tnode = 0;\n\n\t\t\t\t// No return: go around from the beginning: text, closing tag, or node.\n\t\t\t}\n\t\t}\n\t}\n}\n#endif\n\nconst char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\n\tp = SkipWhiteSpace( p, encoding );\n\tTiXmlDocument* document = GetDocument();\n\n\tif ( !p || !*p )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, 0, 0, encoding );\n\t\treturn 0;\n\t}\n\n\tif ( data )\n\t{\n\t\tdata->Stamp( p, encoding );\n\t\tlocation = data->Cursor();\n\t}\n\n\tif ( *p != '<' )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, p, data, encoding );\n\t\treturn 0;\n\t}\n\n\tp = SkipWhiteSpace( p+1, encoding );\n\n\t// Read the name.\n\tconst char* pErr = p;\n\n    p = ReadName( p, &value, encoding );\n\tif ( !p || !*p )\n\t{\n\t\tif ( document )\tdocument->SetError( TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, pErr, data, encoding );\n\t\treturn 0;\n\t}\n\n    TIXML_STRING endTag (\"</\");\n\tendTag += value;\n\n\t// Check for and read attributes. Also look for an empty\n\t// tag or an end tag.\n\twhile ( p && *p )\n\t{\n\t\tpErr = p;\n\t\tp = SkipWhiteSpace( p, encoding );\n\t\tif ( !p || !*p )\n\t\t{\n\t\t\tif ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );\n\t\t\treturn 0;\n\t\t}\n\t\tif ( *p == '/' )\n\t\t{\n\t\t\t++p;\n\t\t\t// Empty tag.\n\t\t\tif ( *p  != '>' )\n\t\t\t{\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_EMPTY, p, data, encoding );\t\t\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn (p+1);\n\t\t}\n\t\telse if ( *p == '>' )\n\t\t{\n\t\t\t// Done with attributes (if there were any.)\n\t\t\t// Read the value -- which can include other\n\t\t\t// elements -- read the end tag, and return.\n\t\t\t++p;\n\t\t\tp = ReadValue( p, data, encoding );\t\t// Note this is an Element method, and will set the error if one happens.\n\t\t\tif ( !p || !*p ) {\n\t\t\t\t// We were looking for the end tag, but found nothing.\n\t\t\t\t// Fix for [ 1663758 ] Failure to report error on bad XML\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\t// We should find the end tag now\n\t\t\t// note that:\n\t\t\t// </foo > and\n\t\t\t// </foo> \n\t\t\t// are both valid end tags.\n\t\t\tif ( StringEqual( p, endTag.c_str(), false, encoding ) )\n\t\t\t{\n\t\t\t\tp += endTag.length();\n\t\t\t\tp = SkipWhiteSpace( p, encoding );\n\t\t\t\tif ( p && *p && *p == '>' ) {\n\t\t\t\t\t++p;\n\t\t\t\t\treturn p;\n\t\t\t\t}\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Try to read an attribute:\n\t\t\tTiXmlAttribute* attrib = new TiXmlAttribute();\n\t\t\tif ( !attrib )\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tattrib->SetDocument( document );\n\t\t\tpErr = p;\n\t\t\tp = attrib->Parse( p, data, encoding );\n\n\t\t\tif ( !p || !*p )\n\t\t\t{\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding );\n\t\t\t\tdelete attrib;\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\t// Handle the strange case of double attributes:\n\t\t\t#ifdef TIXML_USE_STL\n\t\t\tTiXmlAttribute* node = attributeSet.Find( attrib->NameTStr() );\n\t\t\t#else\n\t\t\tTiXmlAttribute* node = attributeSet.Find( attrib->Name() );\n\t\t\t#endif\n\t\t\tif ( node )\n\t\t\t{\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding );\n\t\t\t\tdelete attrib;\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tattributeSet.Add( attrib );\n\t\t}\n\t}\n\treturn p;\n}\n\n\nconst char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\n\tTiXmlDocument* document = GetDocument();\n\n\t// Read in text and elements in any order.\n\tconst char* pWithWhiteSpace = p;\n\tp = SkipWhiteSpace( p, encoding );\n\n\twhile ( p && *p )\n\t{\n\t\tif ( *p != '<' )\n\t\t{\n\t\t\t// Take what we have, make a text element.\n\t\t\tTiXmlText* textNode = new TiXmlText( \"\" );\n\n\t\t\tif ( !textNode )\n\t\t\t{\n\t\t\t    return 0;\n\t\t\t}\n\n\t\t\tif ( TiXmlBase::IsWhiteSpaceCondensed() )\n\t\t\t{\n\t\t\t\tp = textNode->Parse( p, data, encoding );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Special case: we want to keep the white space\n\t\t\t\t// so that leading spaces aren't removed.\n\t\t\t\tp = textNode->Parse( pWithWhiteSpace, data, encoding );\n\t\t\t}\n\n\t\t\tif ( !textNode->Blank() )\n\t\t\t\tLinkEndChild( textNode );\n\t\t\telse\n\t\t\t\tdelete textNode;\n\t\t} \n\t\telse \n\t\t{\n\t\t\t// We hit a '<'\n\t\t\t// Have we hit a new element or an end tag? This could also be\n\t\t\t// a TiXmlText in the \"CDATA\" style.\n\t\t\tif ( StringEqual( p, \"</\", false, encoding ) )\n\t\t\t{\n\t\t\t\treturn p;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tTiXmlNode* node = Identify( p, encoding );\n\t\t\t\tif ( node )\n\t\t\t\t{\n\t\t\t\t\tp = node->Parse( p, data, encoding );\n\t\t\t\t\tLinkEndChild( node );\n\t\t\t\t}\t\t\t\t\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpWithWhiteSpace = p;\n\t\tp = SkipWhiteSpace( p, encoding );\n\t}\n\n\tif ( !p )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_READING_ELEMENT_VALUE, 0, 0, encoding );\n\t}\t\n\treturn p;\n}\n\n\n#ifdef TIXML_USE_STL\nvoid TiXmlUnknown::StreamIn( std::istream * in, TIXML_STRING * tag )\n{\n\twhile ( in->good() )\n\t{\n\t\tint c = in->get();\t\n\t\tif ( c <= 0 )\n\t\t{\n\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\tif ( document )\n\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\treturn;\n\t\t}\n\t\t(*tag) += (char) c;\n\n\t\tif ( c == '>' )\n\t\t{\n\t\t\t// All is well.\n\t\t\treturn;\t\t\n\t\t}\n\t}\n}\n#endif\n\n\nconst char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\n\tTiXmlDocument* document = GetDocument();\n\tp = SkipWhiteSpace( p, encoding );\n\n\tif ( data )\n\t{\n\t\tdata->Stamp( p, encoding );\n\t\tlocation = data->Cursor();\n\t}\n\tif ( !p || !*p || *p != '<' )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, p, data, encoding );\n\t\treturn 0;\n\t}\n\t++p;\n    value = \"\";\n\n\twhile ( p && *p && *p != '>' )\n\t{\n\t\tvalue += *p;\n\t\t++p;\n\t}\n\n\tif ( !p )\n\t{\n\t\tif ( document )\t\n\t\t\tdocument->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding );\n\t}\n\tif ( p && *p == '>' )\n\t\treturn p+1;\n\treturn p;\n}\n\n#ifdef TIXML_USE_STL\nvoid TiXmlComment::StreamIn( std::istream * in, TIXML_STRING * tag )\n{\n\twhile ( in->good() )\n\t{\n\t\tint c = in->get();\t\n\t\tif ( c <= 0 )\n\t\t{\n\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\tif ( document )\n\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\treturn;\n\t\t}\n\n\t\t(*tag) += (char) c;\n\n\t\tif ( c == '>' \n\t\t\t && tag->at( tag->length() - 2 ) == '-'\n\t\t\t && tag->at( tag->length() - 3 ) == '-' )\n\t\t{\n\t\t\t// All is well.\n\t\t\treturn;\t\t\n\t\t}\n\t}\n}\n#endif\n\n\nconst char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\n\tTiXmlDocument* document = GetDocument();\n\tvalue = \"\";\n\n\tp = SkipWhiteSpace( p, encoding );\n\n\tif ( data )\n\t{\n\t\tdata->Stamp( p, encoding );\n\t\tlocation = data->Cursor();\n\t}\n\tconst char* startTag = \"<!--\";\n\tconst char* endTag   = \"-->\";\n\n\tif ( !StringEqual( p, startTag, false, encoding ) )\n\t{\n\t\tif ( document )\n\t\t\tdocument->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding );\n\t\treturn 0;\n\t}\n\tp += strlen( startTag );\n\n\t// [ 1475201 ] TinyXML parses entities in comments\n\t// Oops - ReadText doesn't work, because we don't want to parse the entities.\n\t// p = ReadText( p, &value, false, endTag, false, encoding );\n\t//\n\t// from the XML spec:\n\t/*\n\t [Definition: Comments may appear anywhere in a document outside other markup; in addition, \n\t              they may appear within the document type declaration at places allowed by the grammar. \n\t\t\t\t  They are not part of the document's character data; an XML processor MAY, but need not, \n\t\t\t\t  make it possible for an application to retrieve the text of comments. For compatibility, \n\t\t\t\t  the string \"--\" (double-hyphen) MUST NOT occur within comments.] Parameter entity \n\t\t\t\t  references MUST NOT be recognized within comments.\n\n\t\t\t\t  An example of a comment:\n\n\t\t\t\t  <!-- declarations for <head> & <body> -->\n\t*/\n\n    value = \"\";\n\t// Keep all the white space.\n\twhile (\tp && *p && !StringEqual( p, endTag, false, encoding ) )\n\t{\n\t\tvalue.append( p, 1 );\n\t\t++p;\n\t}\n\tif ( p && *p ) \n\t\tp += strlen( endTag );\n\n\treturn p;\n}\n\n\nconst char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\n\tp = SkipWhiteSpace( p, encoding );\n\tif ( !p || !*p ) return 0;\n\n\tif ( data )\n\t{\n\t\tdata->Stamp( p, encoding );\n\t\tlocation = data->Cursor();\n\t}\n\t// Read the name, the '=' and the value.\n\tconst char* pErr = p;\n\tp = ReadName( p, &name, encoding );\n\tif ( !p || !*p )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );\n\t\treturn 0;\n\t}\n\tp = SkipWhiteSpace( p, encoding );\n\tif ( !p || !*p || *p != '=' )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );\n\t\treturn 0;\n\t}\n\n\t++p;\t// skip '='\n\tp = SkipWhiteSpace( p, encoding );\n\tif ( !p || !*p )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );\n\t\treturn 0;\n\t}\n\t\n\tconst char* end;\n\tconst char SINGLE_QUOTE = '\\'';\n\tconst char DOUBLE_QUOTE = '\\\"';\n\n\tif ( *p == SINGLE_QUOTE )\n\t{\n\t\t++p;\n\t\tend = \"\\'\";\t\t// single quote in string\n\t\tp = ReadText( p, &value, false, end, false, encoding );\n\t}\n\telse if ( *p == DOUBLE_QUOTE )\n\t{\n\t\t++p;\n\t\tend = \"\\\"\";\t\t// double quote in string\n\t\tp = ReadText( p, &value, false, end, false, encoding );\n\t}\n\telse\n\t{\n\t\t// All attribute values should be in single or double quotes.\n\t\t// But this is such a common error that the parser will try\n\t\t// its best, even without them.\n\t\tvalue = \"\";\n\t\twhile (    p && *p\t\t\t\t\t\t\t\t\t\t\t// existence\n\t\t\t\t&& !IsWhiteSpace( *p )\t\t\t\t\t\t\t\t// whitespace\n\t\t\t\t&& *p != '/' && *p != '>' )\t\t\t\t\t\t\t// tag end\n\t\t{\n\t\t\tif ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) {\n\t\t\t\t// [ 1451649 ] Attribute values with trailing quotes not handled correctly\n\t\t\t\t// We did not have an opening quote but seem to have a \n\t\t\t\t// closing one. Give up and throw an error.\n\t\t\t\tif ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tvalue += *p;\n\t\t\t++p;\n\t\t}\n\t}\n\treturn p;\n}\n\n#ifdef TIXML_USE_STL\nvoid TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag )\n{\n\twhile ( in->good() )\n\t{\n\t\tint c = in->peek();\t\n\t\tif ( !cdata && (c == '<' ) ) \n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tif ( c <= 0 )\n\t\t{\n\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\tif ( document )\n\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\treturn;\n\t\t}\n\n\t\t(*tag) += (char) c;\n\t\tin->get();\t// \"commits\" the peek made above\n\n\t\tif ( cdata && c == '>' && tag->size() >= 3 ) {\n\t\t\tsize_t len = tag->size();\n\t\t\tif ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) {\n\t\t\t\t// terminator of cdata.\n\t\t\t\treturn;\n\t\t\t}\n\t\t}    \n\t}\n}\n#endif\n\nconst char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )\n{\n\tvalue = \"\";\n\tTiXmlDocument* document = GetDocument();\n\n\tif ( data )\n\t{\n\t\tdata->Stamp( p, encoding );\n\t\tlocation = data->Cursor();\n\t}\n\n\tconst char* const startTag = \"<![CDATA[\";\n\tconst char* const endTag   = \"]]>\";\n\n\tif ( cdata || StringEqual( p, startTag, false, encoding ) )\n\t{\n\t\tcdata = true;\n\n\t\tif ( !StringEqual( p, startTag, false, encoding ) )\n\t\t{\n\t\t\tif ( document )\n\t\t\t\tdocument->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding );\n\t\t\treturn 0;\n\t\t}\n\t\tp += strlen( startTag );\n\n\t\t// Keep all the white space, ignore the encoding, etc.\n\t\twhile (\t   p && *p\n\t\t\t\t&& !StringEqual( p, endTag, false, encoding )\n\t\t\t  )\n\t\t{\n\t\t\tvalue += *p;\n\t\t\t++p;\n\t\t}\n\n\t\tTIXML_STRING dummy; \n\t\tp = ReadText( p, &dummy, false, endTag, false, encoding );\n\t\treturn p;\n\t}\n\telse\n\t{\n\t\tbool ignoreWhite = true;\n\n\t\tconst char* end = \"<\";\n\t\tp = ReadText( p, &value, ignoreWhite, end, false, encoding );\n\t\tif ( p && *p )\n\t\t\treturn p-1;\t// don't truncate the '<'\n\t\treturn 0;\n\t}\n}\n\n#ifdef TIXML_USE_STL\nvoid TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag )\n{\n\twhile ( in->good() )\n\t{\n\t\tint c = in->get();\n\t\tif ( c <= 0 )\n\t\t{\n\t\t\tTiXmlDocument* document = GetDocument();\n\t\t\tif ( document )\n\t\t\t\tdocument->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );\n\t\t\treturn;\n\t\t}\n\t\t(*tag) += (char) c;\n\n\t\tif ( c == '>' )\n\t\t{\n\t\t\t// All is well.\n\t\t\treturn;\n\t\t}\n\t}\n}\n#endif\n\nconst char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding )\n{\n\tp = SkipWhiteSpace( p, _encoding );\n\t// Find the beginning, find the end, and look for\n\t// the stuff in-between.\n\tTiXmlDocument* document = GetDocument();\n\tif ( !p || !*p || !StringEqual( p, \"<?xml\", true, _encoding ) )\n\t{\n\t\tif ( document ) document->SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding );\n\t\treturn 0;\n\t}\n\tif ( data )\n\t{\n\t\tdata->Stamp( p, _encoding );\n\t\tlocation = data->Cursor();\n\t}\n\tp += 5;\n\n\tversion = \"\";\n\tencoding = \"\";\n\tstandalone = \"\";\n\n\twhile ( p && *p )\n\t{\n\t\tif ( *p == '>' )\n\t\t{\n\t\t\t++p;\n\t\t\treturn p;\n\t\t}\n\n\t\tp = SkipWhiteSpace( p, _encoding );\n\t\tif ( StringEqual( p, \"version\", true, _encoding ) )\n\t\t{\n\t\t\tTiXmlAttribute attrib;\n\t\t\tp = attrib.Parse( p, data, _encoding );\t\t\n\t\t\tversion = attrib.Value();\n\t\t}\n\t\telse if ( StringEqual( p, \"encoding\", true, _encoding ) )\n\t\t{\n\t\t\tTiXmlAttribute attrib;\n\t\t\tp = attrib.Parse( p, data, _encoding );\t\t\n\t\t\tencoding = attrib.Value();\n\t\t}\n\t\telse if ( StringEqual( p, \"standalone\", true, _encoding ) )\n\t\t{\n\t\t\tTiXmlAttribute attrib;\n\t\t\tp = attrib.Parse( p, data, _encoding );\t\t\n\t\t\tstandalone = attrib.Value();\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Read over whatever it is.\n\t\t\twhile( p && *p && *p != '>' && !IsWhiteSpace( *p ) )\n\t\t\t\t++p;\n\t\t}\n\t}\n\treturn 0;\n}\n\nbool TiXmlText::Blank() const\n{\n\tfor ( unsigned i=0; i<value.length(); i++ )\n\t\tif ( !IsWhiteSpace( value[i] ) )\n\t\t\treturn false;\n\treturn true;\n}\n\n"
  },
  {
    "path": "src/user.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"user.h\"\n\nfloat gGourceBeamDist          = 100.0;\nfloat gGourceActionDist        = 50.0;\nfloat gGourcePersonalSpaceDist = 100.0;\n\nRUser::RUser(const std::string& name, vec2 pos, int tagid) : Pawn(name,pos,tagid) {\n\n    this->name = name;\n\n    speed = gGourceSettings.max_user_speed;\n    size = 20.0 * gGourceSettings.user_scale;\n\n    shadowOffset = vec2(2.0, 2.0) * gGourceSettings.user_scale;\n\n    shadow = true;\n\n    highlighted=false;\n\n    assignUserImage();\n\n    setSelected(false);\n\n    last_action = 0.0;\n    action_interval = 0.2;\n    name_interval = 5.0;\n\n    min_units_ps = 100.0;\n\n    actionCount = activeCount = 0;\n}\n\nvoid RUser::addAction(RAction* action) {\n\n    if(action->source != this) return;\n\n    if(isIdle()) showName();\n    //name_interval = name_interval > 0.0 ? std::max(name_interval,nametime-1.0f) : nametime;\n\n    actions.push_back(action);\n    actionCount++;\n}\n\n// remove references to this file\nvoid RUser::fileRemoved(RFile* f) {\n\n    for(std::list<RAction*>::iterator it = actions.begin(); it != actions.end(); ) {\n        RAction* a = *it;\n        if(a->target == f) {\n            it = actions.erase(it);\n            delete a;\n            actionCount--;\n            continue;\n        }\n\n        it++;\n    }\n\n    for(std::list<RAction*>::iterator it = activeActions.begin(); it != activeActions.end(); ) {\n        RAction* a = *it;\n        if(a->target == f) {\n            it = activeActions.erase(it);\n            delete a;\n            continue;\n        }\n\n        it++;\n    }\n}\n\nvoid RUser::applyForceUser(RUser* u) {\n\n    if(u==this) return;\n\n    vec2 u_pos = u->getPos();\n\n    vec2 dir = u_pos - pos;\n\n    float dist = glm::length(dir);\n\n    //different repelling force depending on how busy the user is\n    float desired_dist = getActionCount() == 0 ?\n        gGourcePersonalSpaceDist : (!actions.empty() && activeActions.empty()) ?\n            gGourcePersonalSpaceDist * 0.1 : gGourcePersonalSpaceDist * 0.5;\n\n    //resolve overlap\n    if(dist < 0.001) {\n\n        accel += 1.0f * normalise(vec2( (rand() % 100) - 50, (rand() % 100) - 50));\n\n        return;\n    }\n\n    //repelling force\n    if(dist < desired_dist) {\n        accel -= (desired_dist-dist) * normalise(dir);\n    }\n}\n\nvoid RUser::applyForceAction(RAction* action) {\n    RFile* f = action->target;\n\n    vec2 f_pos = f->getAbsolutePos();\n    vec2 dir = f_pos - pos;\n    float dist = glm::length(dir);\n\n    float desired_dist = gGourceActionDist;\n\n    //resolve overlap\n    if(dist < 0.001) {\n        accel += normalise(vec2( (rand() % 100) - 50, (rand() % 100) - 50));\n        return;\n    }\n\n    //repelling force\n    if(dist < desired_dist) {\n        accel -= (desired_dist - dist) * normalise(dir);\n        return;\n    }\n\n    if(dist > gGourceBeamDist) {\n        accel += (dist-gGourceBeamDist) * normalise(dir);\n    }\n}\n\nvoid RUser::applyForceToActions() {\n    if(activeActions.empty() && actions.empty()) return;\n\n    last_action = elapsed;\n\n    int action_influence = 0;\n    int max_influence    = 3;\n\n    // move towards actions being worked on\n\n    for(std::list<RAction*>::iterator it = activeActions.begin(); it != activeActions.end(); it++) {\n        RAction* action = *it;\n\n        applyForceAction(action);\n\n        action_influence++;\n        if(action_influence >= max_influence) break;\n    }\n\n    if(!activeActions.empty()) return;\n\n    //if no actions being worked on, move towards one pending action\n    for(std::list<RAction*>::iterator it = actions.begin(); it != actions.end(); it++) {\n        RAction* action = *it;\n\n        applyForceAction(action);\n\n        break;\n    }\n}\n\nvoid RUser::colourize() {\n    usercol = colourHash(name);\n}\n\nvoid RUser::assignUserImage() {\n    colourize();\n\n    TextureResource* graphic = 0;\n\n    if(gGourceSettings.user_image_dir.size() > 0) {\n\n        //try their username\n        // TODO: replace with map of name -> image of all pngs and jpgs in directory\n        //gGourceSettings.user_image_dir + name + std::string(\".jpg\");\n\n        std::map<std::string, std::string>::iterator findimage;\n\n        findimage = gGourceSettings.user_image_map.find(name);\n\n        //do we have this image\n        if(findimage != gGourceSettings.user_image_map.end()) {\n            std::string imagefile = findimage->second;\n\n            if(!gGourceSettings.colour_user_images) usercol = vec3(1.0, 1.0, 1.0);\n\n            graphic = texturemanager.grabFile(imagefile, true, GL_CLAMP_TO_EDGE);\n        }\n    }\n\n\n    //TODO: trilinear probably should be an attribute of the texture\n    //      perhaps the mipmap option should be an enum: eg TEX_MIPMAP_TRILINEAR\n\n    //nope\n    if(!graphic) {\n        if(gGourceSettings.default_user_image.size() > 0) {\n            if(!gGourceSettings.colour_user_images) usercol = vec3(1.0, 1.0, 1.0);\n            graphic = texturemanager.grabFile(gGourceSettings.default_user_image, true, GL_CLAMP_TO_EDGE);\n        } else {\n            graphic = texturemanager.grab(\"user.png\", true, GL_CLAMP_TO_EDGE);\n        }\n    }\n\n    setGraphic(graphic);\n\n    usercol = usercol * 0.6f + vec3(1.0f) * 0.4f;\n    usercol *= 0.9f;\n}\n\nint RUser::getActionCount() {\n    return actionCount + activeCount;\n}\n\nint RUser::getPendingActionCount() {\n    return actionCount;\n}\n\nvoid RUser::logic(float t, float dt) {\n    Pawn::logic(dt);\n\n    action_interval -= dt;\n\n    bool find_nearby_action = false;\n\n    if(!actions.empty() && action_interval <= 0) {\n        find_nearby_action = true;\n    }\n\n    //add next active action, if it is in range\n    for(std::list<RAction*>::iterator it = actions.begin(); it != actions.end();) {\n        RAction* action = *it;\n\n        //add all files which are too old\n        if(gGourceSettings.max_file_lag>=0.0 && action->t < t - gGourceSettings.max_file_lag) {\n            it = actions.erase(it);\n            actionCount--;\n            action->rate = 2.0;\n            activeActions.push_back(action);\n            activeCount++;\n            continue;\n        }\n\n        if(!find_nearby_action) break;\n\n        float action_dist = glm::length(action->target->getAbsolutePos() - pos);\n\n        //queue first action in range\n        if(action_dist < gGourceBeamDist) {\n            it = actions.erase(it);\n            activeActions.push_back(action);\n            actionCount--; activeCount++;\n            break;\n        }\n\n        it++;\n    }\n\n    //reset action interval\n    if(action_interval <= 0) {\n        int total_actions = actionCount + activeCount;\n\n        action_interval = total_actions ? (1.0 / (float)total_actions) : 1.0;\n    }\n\n    //update actions\n    for(std::list<RAction*>::iterator it = activeActions.begin(); it != activeActions.end(); ) {\n\n        RAction* action = *it;\n\n        action->logic(dt);\n\n        if(action->isFinished()) {\n            it = activeActions.erase(it);\n            delete action;\n            activeCount--;\n            continue;\n        }\n\n        it++;\n    }\n\n    if(glm::length2(accel) > speed * speed) {\n        accel = normalise(accel) * speed;\n    }\n\n    pos += accel * dt;\n\n    accel = accel * std::max(0.0f, (1.0f - gGourceSettings.user_friction*dt));\n}\n\nvoid RUser::updateFont() {\n    if(selected) {\n        font = fontmanager.grab(gGourceSettings.font_file, 18);\n        font.dropShadow(true);\n    } else {\n        font = fontmanager.grab(gGourceSettings.font_file, gGourceSettings.scaled_user_font_size);\n        font.dropShadow(true);\n    }\n\n    font.alignTop(false);\n\n    namewidth = font.getWidth(name);\n}\n\nvoid RUser::setHighlighted(bool highlight) {\n    this->highlighted = highlight;\n\n    updateFont();\n}\n\nvoid RUser::setSelected(bool selected) {\n    Pawn::setSelected(selected);\n    updateFont();\n}\n\nconst vec3& RUser::getNameColour() const {\n    return selected ? gGourceSettings.selection_colour : highlighted ? gGourceSettings.highlight_colour : namecol;\n}\n\nvec3 RUser::getColour() const{\n    if(selected) return vec3(1.0, 1.0, 1.0);\n\n    return usercol;\n}\n\nconst std::string& RUser::getName() const {\n    return name;\n}\n\nfloat RUser::getAlpha() const {\n    float alpha = Pawn::getAlpha();\n    //user fades out if not doing anything\n    if(elapsed - last_action > gGourceSettings.user_idle_time) {\n        alpha = 1.0 - std::min(elapsed - last_action - gGourceSettings.user_idle_time, 1.0f);\n    }\n\n    return alpha;\n}\n\nbool RUser::isIdle() {\n    return (activeActions.empty() && actions.empty());\n}\n\nbool RUser::isFading() {\n    return isIdle() && (elapsed - last_action) > gGourceSettings.user_idle_time;\n}\n\nbool RUser::isInactive() {\n    return isIdle() && (elapsed - last_action) > 10.0;\n}\n\nbool RUser::nameVisible() const {\n    return (Pawn::nameVisible() || gGourceSettings.highlight_all_users || highlighted) ? true : false;\n}\n\nvoid RUser::calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection) {\n\n    static GLdouble screen_x, screen_y, screen_z;\n\n    vec2 text_pos = pos;\n    text_pos.y -= dims.y * 0.5f;\n\n    gluProject( text_pos.x, text_pos.y, 0.0f, modelview, projection, viewport, &screen_x, &screen_y, &screen_z);\n\n    screen_y = (float)viewport[3] - screen_y;\n\n    screenpos.x = screen_x - namewidth * 0.5;\n    screenpos.y = screen_y - font.getMaxHeight();\n}\n\nvoid RUser::drawNameText(float alpha) {\n    float user_alpha = getAlpha();\n\n    if(gGourceSettings.highlight_all_users || highlighted || selected || alpha>0.0) {\n        vec3 name_col  = getNameColour();\n        float name_alpha = (selected||highlighted||gGourceSettings.highlight_all_users) ? user_alpha : alpha;\n\n        font.setColour(vec4(name_col.x, name_col.y, name_col.z, name_alpha));\n        font.draw(screenpos.x, screenpos.y, name);\n    }\n}\n\nvoid RUser::updateActionsVBO(quadbuf& buffer) {\n\n    for(std::list<RAction*>::iterator it = activeActions.begin(); it != activeActions.end(); it++) {\n        RAction* action = *it;\n        action->drawToVBO(buffer);\n    }\n}\n\nvoid RUser::drawActions(float dt) {\n\n    for(std::list<RAction*>::iterator it = activeActions.begin(); it != activeActions.end(); it++) {\n        RAction* action = *it;\n        action->draw(dt);\n    }\n}\n\nvoid RUser::draw(float dt) {\n    if(gGourceSettings.hide_users) return;\n\n    Pawn::draw(dt);\n}\n"
  },
  {
    "path": "src/user.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef RUSER_H\n#define RUSER_H\n\n#include \"gource_settings.h\"\n\n#include \"pawn.h\"\n#include \"action.h\"\n#include \"file.h\"\n\n#include \"sys/stat.h\"\n\n#include <list>\n\nclass RAction;\nclass RFile;\n\nclass RUser : public Pawn {\n\n    void assignUserImage();\n\n    std::list<RAction*> actions;\n    std::list<RAction*> activeActions;\n    size_t actionCount;\n    size_t activeCount;\n\n    float action_interval;\n    float action_dist;\n\n    float last_action;\n\n    float min_units_ps;\n\n    std::string name;\n    vec3 usercol;\n\n    bool highlighted;\n\n    bool nameVisible() const;\n\n    void updateFont();\n    const vec3& getNameColour() const;\n    void drawNameText(float alpha);\npublic:\n    RUser(const std::string& name, vec2 pos, int tagid);\n\n    vec3 getColour() const;\n    void colourize();\n\n    const std::string& getName() const;\n\n    void fileRemoved(RFile* f);\n    void addAction(RAction* action);\n\n    bool isIdle();\n    bool isFading();\n    bool isInactive();\n\n    void setSelected(bool selected);\n    void setHighlighted(bool highlighted);\n\n    int getActionCount();\n    int getPendingActionCount();\n\n    float getAlpha() const;\n\n    void applyForceToActions();\n    void applyForceAction(RAction* action);\n    void applyForceUser(RUser* u);\n\n    void calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection);\n\n    void logic(float t, float dt);\n\n    void updateActionsVBO(quadbuf& buffer);\n\n    void drawActions(float dt);\n    void draw(float dt);\n};\n\n\nclass UserForceFunctor : public VisitFunctor<QuadItem>{\n\n  private:\n    RUser * this_user;\n    std::set<RUser*> seen;\n    int loopCount;\n\n  public:\n    UserForceFunctor(RUser * user) : this_user(user), seen(), loopCount(0){}\n    int getLoopCount() const{ return loopCount; }\n    void operator()(QuadItem * user){\n\n       std::set<RUser*>::iterator seentest;\n\n       RUser * b = (RUser*) (user);\n\n       if(b==this_user) return;\n\n       if(b->node_count != 1) {\n           if((seentest = seen.find(b)) != seen.end())\n               return;\n\n           seen.insert(b);\n       }\n\n       this_user->applyForceUser(b);\n       loopCount++;\n\n    }\n\n};\n\n#endif\n"
  },
  {
    "path": "src/zoomcamera.cpp",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"zoomcamera.h\"\n\nZoomCamera::ZoomCamera() {\n}\n\nZoomCamera::ZoomCamera(vec3 pos, vec3 target, float min_distance, float max_distance) :\n    pos(pos), _pos(pos), target(target), _target(target),  dest(pos), fov(90.0f) {\n\n    znear = 0.1;\n\n    up = vec3(0.0f, -1.0f, 0.0f);\n\n    setMinDistance(min_distance);\n    setMaxDistance(max_distance);\n\n    padding = 1.0;\n    speed = 1.0;\n    lockon = false;\n    lockon_time = 0.0;\n    reset();\n}\n\nvoid ZoomCamera::reset() {\n    pos    = _pos;\n    target = _target;\n}\n\nfloat ZoomCamera::getMaxDistance() { return max_distance; }\nfloat ZoomCamera::getMinDistance() { return min_distance; }\n\nvoid ZoomCamera::setPadding(float padding) {\n    this->padding = padding;\n}\n\nvoid ZoomCamera::setMaxDistance(float max) {\n    max_distance = max;\n    zfar = max + 1.0;\n}\n\nvoid ZoomCamera::setMinDistance(float min) {\n    min_distance = min;\n}\n\nvoid ZoomCamera::lockOn(bool lockon) {\n\n    if(lockon) {\n         lockon_time = 1.0;\n    }\n\n    this->lockon = lockon;\n}\n\nvoid ZoomCamera::look() {\n    lookAt(target);\n}\n\nvoid ZoomCamera::lookAt(const vec3& target) {\n    gluLookAt( pos.x,    pos.y,    pos.z,\n               target.x, target.y, target.z,\n               up.x,     up.y,     up.z);\n}\n\nvoid ZoomCamera::focus() {\n    display.mode3D(fov, znear, zfar);\n    look();\n}\n\nvoid ZoomCamera::stop() {\n    this->dest = pos;\n}\n\nvoid ZoomCamera::setSpeed(float speed) {\n    this->speed = speed;\n}\nvoid ZoomCamera::adjust(const Bounds2D& bounds) {\n    adjust(bounds, true);\n}\n\nvoid ZoomCamera::adjust(const Bounds2D& bounds, bool adjust_distance) {\n\n    //center camera on bounds\n\n    vec2 centre  = bounds.centre();\n\n    //adjust by screen ratio\n    dest.x = centre.x;\n    dest.y = centre.y;\n\n    if(!adjust_distance) return;\n\n    //scale by 10% so we dont have stuff right on the edge of the screen\n    float width  = bounds.width() * padding;\n    float height = bounds.height() * padding;\n\n    float aspect_ratio = display.width / (float) display.height;\n\n    if(aspect_ratio < 1.0) {\n        height /= aspect_ratio;\n    } else {\n        width /= aspect_ratio;\n    }\n\n    //calc visible width of the opposite wall at a distance of 1 this fov\n    float toa = tan( fov * 0.5f * DEGREES_TO_RADIANS ) * 2.0;\n\n    float distance;\n\n    //TOA = tan = opposite/adjacent (distance = adjacent)\n    //use the larger side of the box\n\n    //cropping: vertical, horizontal or none\n    if(gGourceSettings.crop_vertical) {\n\n        distance =  width / toa;\n\n        \n    } else if (gGourceSettings.crop_horizontal) {\n\n        distance =  height / toa;\n        \n    } else {\n\n        if(width >= height) {\n            distance =  width / toa;\n        } else {\n            distance =  height / toa;\n        }\n    }\n\n    //debugLog(\"toa %.2f, distance %.2f width %.2f height %.2f dratio %.2f\\n\", toa, distance, width, height, dratio);\n\n    //check bounds are valid\n    if(distance < min_distance) distance = min_distance;\n    if(distance > max_distance) distance = max_distance;\n\n    this->dest.z = -distance;\n}\n\nvoid ZoomCamera::setDistance(float distance) {\n    dest.z = -distance;\n}\n\nvoid ZoomCamera::setPos(const vec3& pos, bool keep_angle)  {\n    if(keep_angle) {\n        vec3 dir = target - this->pos;\n        this->pos = pos;\n        this->target = pos + dir;\n    } else {\n        this->pos = pos;\n    }\n}\n\nvoid ZoomCamera::logic(float dt) {\n    vec3 dp = (dest - pos);\n\n    vec3 dpt = dp * dt * speed;\n\n    if(lockon) {\n        dpt = dpt * lockon_time + dp * (1.0f-lockon_time);\n\n        if(lockon_time>0.0) {\n            lockon_time = std::max(0.0f, lockon_time-dt*0.5f);\n        }\n    }\n\n    if(glm::length2(dpt) > glm::length2(dp)) dpt = dp;\n\n    pos += dpt;\n\n    target = vec3(pos.x, pos.y, 0.0);\n}\n"
  },
  {
    "path": "src/zoomcamera.h",
    "content": "/*\n    Copyright (C) 2009 Andrew Caudwell (acaudwell@gmail.com)\n\n    This program is free software; you can redistribute it and/or\n    modify it under the terms of the GNU General Public License\n    as published by the Free Software Foundation; either version\n    3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef ZOOM_CAMERA_H\n#define ZOOM_CAMERA_H\n\n#include \"core/bounds.h\"\n#include \"core/frustum.h\"\n\n#include \"gource_settings.h\"\n\nclass ZoomCamera {\n    vec3 pos;\n    vec3 dest;\n    vec3 target;\n    vec3 up;\n    vec3 _pos;\n    vec3 _target;\n\n    bool lockon;\n    float speed;\n    float lockon_time;\n\n    float padding;\n    \n    float min_distance, max_distance;\n\n    float fov;\n    float znear, zfar;\npublic:\n    ZoomCamera();\n    ZoomCamera(vec3 start, vec3 target, float min_distance, float max_distance);\n\n    void setSpeed(float speed);\n\n    void lockOn(bool lockon);\n\n    void look();\n    void lookAt(const vec3& target);\n    void focus();\n    \n    const vec3& getPos()    const { return pos; };\n    const vec3& getUp()     const { return up; };\n    const vec3& getTarget() const { return target; };\n    const vec3& getDest()   const { return dest; };\n\n    float getFOV()   { return fov; };\n    float getZNear() { return znear; };\n    float getZFar()  { return zfar; };\n    \n    void setPos(const vec3& pos, bool keep_angle = false);\n    \n    float getMinDistance();\n    float getMaxDistance();\n\n    void setPadding(float padding);\n    void setDistance(float distance);\n\n    void setMinDistance(float min);\n    void setMaxDistance(float max);\n\n\n    void reset();\n    void logic(float dt);\n    void adjustDistance();\n    void adjust(const Bounds2D& bounds);\n    void adjust(const Bounds2D& bounds, bool adjust_distance);\n    \n    void stop();\n};\n\nextern bool gGourceVerticalCrop;\nextern bool gGourceHorizontalCrop;\n\n\n#endif\n"
  },
  {
    "path": "tests/logs/custom-dir-delete.log",
    "content": "1277787455|bob|A|path/to/1.txt\n1277787457|bob|A|path/to/blah/1.txt\n1277787457|bob|A|path/to/blah/2.txt\n1277787457|bob|A|path/to/blah/3.txt\n1277787460|bob|D|path/to/\n"
  },
  {
    "path": "tests/logs/file-removal.log",
    "content": "1277787455|bob|A|path/to/file.1\n1277787455|bob|A|path/to/file.2\n1277787455|bob|A|path/to/file.3\n1277787455|bob|A|path/to/file.4\n1277787455|bob|A|path/to/file.5\n1277787456|bob|D|path/to/file.1\n1277787456|bob|D|path/to/file.2\n1277787456|bob|D|path/to/file.3\n1277787456|bob|D|path/to/file.4\n1277787466|bob|D|path/to/file.5\n\n"
  },
  {
    "path": "tests/logs/file-to-dir.log",
    "content": "1277787456|bob|A|path/to/file/Not\n1277787457|bob|A|path/to/file/Not/Not\n1277787457|bob|A|path/to/file/Not/Not/Not\n1277787459|bob|A|path/to/file/Not/Not/Not/File\n"
  },
  {
    "path": "tests/logs/svn-dir-delete.log",
    "content": "<?xml version=\"1.0\"?>\n<log>\n<logentry revision=\"1\">\n<author>bob</author>\n<date>2010-01-20T08:35:18.022581Z</date>\n<paths>\n<path kind=\"\" action=\"A\">/blah/blah/filea.sh</path>\n<path kind=\"\" action=\"A\">/blah/blah/fileb.sh</path>\n<path kind=\"\" action=\"A\">/blah/blah/filec.sh</path>\n<path kind=\"\" action=\"A\">/blah/blah/filed.sh</path>\n<path kind=\"\" action=\"A\">/blah/blah/filee.sh</path>\n</paths>\n</logentry>\n<logentry revision=\"2\">\n<author>bob</author>\n<date>2010-01-20T08:40:18.022581Z</date>\n<paths>\n<path\n   kind=\"dir\"\n   action=\"D\">/blah</path>\n</paths>\n</logentry>\n</log>\n\n"
  },
  {
    "path": "tests/logs/utf8-caption.log",
    "content": "1277787455|Árvíztűrő tükörfúrógép adds a file\n2010-06-29 16:57:45|Árvíztűrő tükörfúrógép removes a file\n"
  },
  {
    "path": "tests/logs/utf8.log",
    "content": "1277787455|Árvíztűrő tükörfúrógép|A|Árvíztűrő/tükörfúrógép/Árvíztűrő tükörfúrógép.txt\n1277787465|Árvíztűrő tükörfúrógép|D|Árvíztűrő/tükörfúrógép/Árvíztűrő tükörfúrógép.txt\n\n"
  },
  {
    "path": "tests/test.conf",
    "content": "[gource]\ntitle=File removal\npath=logs/file-removal.log\nfile-idle-time=0.0\nstop-at-time=10.0\n\n[gource]\ntitle=Convert 'file' to a directory\npath=logs/file-to-dir.log\nstop-at-time=10.0\n\n[gource]\ntitle=User filter (Andrew)\nuser-filter=Andrew\nstop-at-time=5.0\n\n[gource]\ntitle=Filename filter (.cpp)\nfile-filter=\\.cpp\nstop-at-time=5.0\n\n[gource]\ntitle=Filename show filter (.cpp)\nfile-show-filter=\\.cpp\nstop-at-time=5.0\n\n[gource]\ntitle=Directory delete (SVN)\npath=logs/svn-dir-delete.log\nstop-at-time=5.0\n\n[gource]\ntitle=Directory delete (Custom)\npath=logs/custom-dir-delete.log\nstop-at-time=5.0\n\n[gource]\ntitle=UTF-8 handling\ncaption-file=logs/utf8-caption.log\nuser-image-dir=images/\npath=logs/utf8.log\nstop-at-time=5.0\n"
  }
]